Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
120 changes: 120 additions & 0 deletions .github/workflows/publish-images-mo4islona.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,120 @@
name: Publish Images (mo4islona / Docker Hub)

# Personal/dev image publish to Docker Hub under docker.io/mo4islona/centaur-*.
# Separate from publish-images.yml (which targets the upstream GHCR namespace).
# Single-arch linux/amd64 only — the target GKE node pools are all amd64, so
# skipping arm64 + the digest/merge dance keeps this fast.
#
# Requires two repo secrets: DOCKERHUB_USERNAME and DOCKERHUB_TOKEN
# (a Docker Hub access token with Read/Write).

on:
workflow_dispatch:
inputs:
extra_tag:
description: "Extra tag to publish alongside latest + sha (e.g. per-user-tenancy)"
required: false
default: ""
push:
branches: [feat/per-user-tenancy]
paths:
- .github/workflows/publish-images-mo4islona.yml
- services/**
- centaur_sdk/**
- packages/**
- tools/**
- package.json
- pnpm-lock.yaml
- pnpm-workspace.yaml
- .agents/skills/**

concurrency:
group: publish-mo4islona-${{ github.ref }}
cancel-in-progress: true

permissions:
contents: read

env:
REGISTRY: docker.io
IMAGE_NAMESPACE: mo4islona
IMAGE_SOURCE: https://github.com/subsquid/centaur
RUST_BUILD_PROFILE: release

jobs:
build:
runs-on: ubuntu-latest
strategy:
fail-fast: false
matrix:
include:
- image: centaur-api-rs
context: .
dockerfile: services/api-rs/Dockerfile
target: ""
- image: centaur-slackbotv2
context: .
dockerfile: services/slackbotv2/Dockerfile
target: ""
- image: centaur-discordbot
context: .
dockerfile: services/discordbot/Dockerfile
target: ""
- image: centaur-agent
context: .
dockerfile: services/sandbox/Dockerfile
target: sandbox
- image: centaur-iron-proxy
context: .
dockerfile: services/iron-proxy/Dockerfile
target: ""
- image: centaur-console
context: services/console
dockerfile: services/console/Dockerfile
target: ""

steps:
- name: Checkout
uses: actions/checkout@34e114876b0b11c390a56381ad16ebd13914f8d5 # v4
with:
persist-credentials: false

- name: Set up Docker Buildx
uses: docker/setup-buildx-action@8d2750c68a42422c14e847fe6c8ac0403b4cbd6f # v3

- name: Log in to Docker Hub
uses: docker/login-action@c94ce9fb468520275223c153574b00df6fe4bcc9 # v3
with:
registry: ${{ env.REGISTRY }}
username: ${{ secrets.DOCKERHUB_USERNAME }}
password: ${{ secrets.DOCKERHUB_TOKEN }}

- name: Docker metadata for ${{ matrix.image }}
id: meta
uses: docker/metadata-action@c299e40c65443455700f0fdfc63efafe5b349051 # v5
with:
images: ${{ env.REGISTRY }}/${{ env.IMAGE_NAMESPACE }}/${{ matrix.image }}
flavor: |
latest=false
tags: |
type=raw,value=latest
type=sha
type=raw,value=${{ github.event.inputs.extra_tag }},enable=${{ github.event.inputs.extra_tag != '' }}
labels: |
org.opencontainers.image.title=${{ matrix.image }}
org.opencontainers.image.source=${{ env.IMAGE_SOURCE }}

- name: Build and push ${{ matrix.image }}
uses: docker/build-push-action@10e90e3645eae34f1e60eeb005ba3a3d33f178e8 # v6
with:
context: ${{ matrix.context }}
file: ${{ matrix.dockerfile }}
target: ${{ matrix.target }}
platforms: linux/amd64
push: true
tags: ${{ steps.meta.outputs.tags }}
labels: ${{ steps.meta.outputs.labels }}
build-args: |
RUST_BUILD_PROFILE=${{ env.RUST_BUILD_PROFILE }}
cache-from: type=gha,scope=mo4islona-${{ matrix.image }}
cache-to: type=gha,mode=max,scope=mo4islona-${{ matrix.image }}
673 changes: 673 additions & 0 deletions MULTITENANT.md

Large diffs are not rendered by default.

12 changes: 12 additions & 0 deletions contrib/chart/templates/apirs.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -135,6 +135,18 @@ spec:
serviceAccountName: {{ $apiRsName }}
{{- with .Values.global.imagePullSecrets }}
imagePullSecrets:
{{ toYaml . | nindent 8 }}
{{- end }}
{{- with .Values.apiRs.nodeSelector }}
nodeSelector:
{{ toYaml . | nindent 8 }}
{{- end }}
{{- with .Values.apiRs.affinity }}
affinity:
{{ toYaml . | nindent 8 }}
{{- end }}
{{- with .Values.apiRs.tolerations }}
tolerations:
{{ toYaml . | nindent 8 }}
{{- end }}
containers:
Expand Down
12 changes: 12 additions & 0 deletions contrib/chart/templates/console-worker.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,18 @@ spec:
automountServiceAccountToken: false
{{- with .Values.global.imagePullSecrets }}
imagePullSecrets:
{{ toYaml . | nindent 8 }}
{{- end }}
{{- with .Values.console.nodeSelector }}
nodeSelector:
{{ toYaml . | nindent 8 }}
{{- end }}
{{- with .Values.console.affinity }}
affinity:
{{ toYaml . | nindent 8 }}
{{- end }}
{{- with .Values.console.tolerations }}
tolerations:
{{ toYaml . | nindent 8 }}
{{- end }}
initContainers:
Expand Down
12 changes: 12 additions & 0 deletions contrib/chart/templates/console.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -48,6 +48,18 @@ spec:
automountServiceAccountToken: false
{{- with .Values.global.imagePullSecrets }}
imagePullSecrets:
{{ toYaml . | nindent 8 }}
{{- end }}
{{- with .Values.console.nodeSelector }}
nodeSelector:
{{ toYaml . | nindent 8 }}
{{- end }}
{{- with .Values.console.affinity }}
affinity:
{{ toYaml . | nindent 8 }}
{{- end }}
{{- with .Values.console.tolerations }}
tolerations:
{{ toYaml . | nindent 8 }}
{{- end }}
initContainers:
Expand Down
16 changes: 16 additions & 0 deletions contrib/chart/templates/networkpolicy.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,22 @@ spec:
port: 53
- protocol: TCP
port: 53
{{- with .Values.networkPolicy.dnsServerCidrs }}
# Extra DNS resolver IPs reachable by ipBlock rather than namespaceSelector.
# Needed on GKE with NodeLocal DNSCache, whose resolver runs hostNetwork and
# so is not selectable as a kube-system pod (link-local 169.254.20.10 + the
# kube-dns ClusterIP). Empty by default (K3s needs nothing here).
- to:
{{- range . }}
- ipBlock:
cidr: {{ . | quote }}
{{- end }}
ports:
- protocol: UDP
port: 53
- protocol: TCP
port: 53
{{- end }}
---
{{- if .Values.postgres.enabled }}
apiVersion: networking.k8s.io/v1
Expand Down
12 changes: 12 additions & 0 deletions contrib/chart/templates/slackbotv2.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,18 @@ spec:
automountServiceAccountToken: false
{{- with .Values.global.imagePullSecrets }}
imagePullSecrets:
{{ toYaml . | nindent 8 }}
{{- end }}
{{- with .Values.slackbotv2.nodeSelector }}
nodeSelector:
{{ toYaml . | nindent 8 }}
{{- end }}
{{- with .Values.slackbotv2.affinity }}
affinity:
{{ toYaml . | nindent 8 }}
{{- end }}
{{- with .Values.slackbotv2.tolerations }}
tolerations:
{{ toYaml . | nindent 8 }}
{{- end }}
containers:
Expand Down
12 changes: 12 additions & 0 deletions contrib/chart/templates/workloads.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,18 @@ spec:
automountServiceAccountToken: false
{{- with .Values.global.imagePullSecrets }}
imagePullSecrets:
{{ toYaml . | nindent 8 }}
{{- end }}
{{- with .Values.postgres.nodeSelector }}
nodeSelector:
{{ toYaml . | nindent 8 }}
{{- end }}
{{- with .Values.postgres.affinity }}
affinity:
{{ toYaml . | nindent 8 }}
{{- end }}
{{- with .Values.postgres.tolerations }}
tolerations:
{{ toYaml . | nindent 8 }}
{{- end }}
containers:
Expand Down
98 changes: 98 additions & 0 deletions contrib/chart/values.mo4islona-pool.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,98 @@
# Pin the whole Centaur stack to a dedicated spot node pool.
#
# Pool contract (created out-of-band, see deploy notes):
# label: centaur-pool=true
# taint: centaur=true:NoSchedule (so nothing else lands on the pool)
#
# Layer AFTER the image + env overlays, e.g.:
# helm upgrade --install centaur contrib/chart -n centaur --create-namespace \
# -f contrib/chart/values.dev.yaml \
# -f contrib/chart/values.mo4islona.yaml \
# -f contrib/chart/values.mo4islona-pool.yaml
#
# Control-plane scheduling (api-rs / slackbotv2 / postgres / repo-cache) is the
# chart support added in PR feat/chart-node-scheduling. Sandbox runtime pods are
# pinned via api-rs (SESSION_SANDBOX_NODE_SELECTOR / _TOLERATIONS on extraEnv),
# which requires the api-rs build carrying that support.

# GKE runs NodeLocal DNSCache (hostNetwork resolver), which the chart's default
# kube-system namespaceSelector DNS rule does not cover, so pod DNS is denied.
# Allow the node-local resolver + kube-dns ClusterIP by ipBlock.
networkPolicy:
dnsServerCidrs:
- 169.254.20.10/32
- 10.0.16.10/32

apiRs:
nodeSelector:
centaur-pool: "true"
tolerations:
- key: centaur
operator: Equal
value: "true"
effect: NoSchedule
extraEnv:
SESSION_SANDBOX_NODE_SELECTOR: "centaur-pool=true"
SESSION_SANDBOX_TOLERATIONS: '[{"key":"centaur","operator":"Equal","value":"true","effect":"NoSchedule"}]'

slackbotv2:
nodeSelector:
centaur-pool: "true"
tolerations:
- key: centaur
operator: Equal
value: "true"
effect: NoSchedule

postgres:
nodeSelector:
centaur-pool: "true"
tolerations:
- key: centaur
operator: Equal
value: "true"
effect: NoSchedule
persistence:
# premium-rwo = pd-ssd, and WaitForFirstConsumer so the PD is provisioned in
# the pod's zone. The default `standard` class is HDD + Immediate-binding
# (wrong zone), which fails to attach to the single-zone pool.
storageClassName: premium-rwo
# PGDATA must be a subdirectory of the mount: a freshly-formatted PD mounted
# directly at /var/lib/postgresql/data carries an ext4 lost+found, and initdb
# refuses a non-empty data dir.
subPath: pgdata

# repo-cache hostPath DaemonSet: confine it to the pool so it runs only on the
# pool node(s) (not every cluster node) and colocates with the sandbox pods that
# read its hostPath.
repoCache:
nodeSelector:
centaur-pool: "true"
tolerations:
- key: centaur
operator: Equal
value: "true"
effect: NoSchedule

# console = the iron-control plane api-rs binds to (IRON_CONTROL_URL). Required
# when apiRs.ironProxy.mode != disabled. Reads its config from centaur-infra-env
# (IRON_CONTROL_* keys seeded by bootstrap) and self-provisions its DBs.
console:
enabled: true
nodeSelector:
centaur-pool: "true"
tolerations:
- key: centaur
operator: Equal
value: "true"
effect: NoSchedule

# agent-sandbox controller (subchart) — pin to the pool too.
agentSandbox:
nodeSelector:
centaur-pool: "true"
tolerations:
- key: centaur
operator: Equal
value: "true"
effect: NoSchedule
39 changes: 39 additions & 0 deletions contrib/chart/values.mo4islona.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
# Image overrides pointing the chart at the personal Docker Hub namespace
# (docker.io/mo4islona/centaur-*), built by .github/workflows/publish-images-mo4islona.yml.
#
# Layer this AFTER the environment values file, e.g.:
# helm upgrade --install centaur contrib/chart -n centaur --create-namespace \
# -f contrib/chart/values.dev.yaml -f contrib/chart/values.mo4islona.yaml
#
# tag stays "latest" (set in the base chart); pullPolicy: Always so the mutable
# latest tag is re-pulled on each rollout.

apiRs:
image:
repository: docker.io/mo4islona/centaur-api-rs
pullPolicy: Always

slackbotv2:
image:
repository: docker.io/mo4islona/centaur-slackbotv2
pullPolicy: Always

discordbot:
image:
repository: docker.io/mo4islona/centaur-discordbot
pullPolicy: Always

sandbox:
image:
repository: docker.io/mo4islona/centaur-agent
pullPolicy: Always

ironProxy:
image:
repository: docker.io/mo4islona/centaur-iron-proxy
pullPolicy: Always

console:
image:
repository: docker.io/mo4islona/centaur-console
pullPolicy: Always
6 changes: 6 additions & 0 deletions services/api-rs/crates/centaur-iron-control/src/error.rs
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,12 @@ pub enum IronControlError {
#[source]
source: reqwest::Error,
},
/// A caller supplied an explicit principal foreign_id that isn't a valid
/// URL-safe slug, so it can't be registered. Rejected rather than silently
/// falling back to a thread-derived principal (which would run the session
/// under the wrong identity / key).
#[error("invalid principal foreign_id {foreign_id:?}: must be URL-safe (A-Za-z0-9-._~)")]
InvalidPrincipalForeignId { foreign_id: String },
}

pub type Result<T> = std::result::Result<T, IronControlError>;
Loading
Loading