diff --git a/.idea/data_source_mapping.xml b/.idea/data_source_mapping.xml
deleted file mode 100644
index b53bc61..0000000
--- a/.idea/data_source_mapping.xml
+++ /dev/null
@@ -1,6 +0,0 @@
-
-
-
-
-
-
\ No newline at end of file
diff --git a/.mise/tasks/pkg/build b/.mise/tasks/pkg/build
index 542901b..5588b3c 100755
--- a/.mise/tasks/pkg/build
+++ b/.mise/tasks/pkg/build
@@ -12,7 +12,9 @@ TARBALL="solstice-ci.tar.gz"
TMPDIR=$(mktemp -d)
trap 'rm -rf "$TMPDIR"' EXIT
-git ls-files -z | tar --null -czf "$TMPDIR/$TARBALL" -T -
+# Create a tarball with a top-level solstice-ci/ prefix so PKGBUILDs can `cd "$srcdir/solstice-ci"`
+# Use git archive to ensure only tracked files are included and the prefix is present.
+git archive --format=tar.gz --prefix=solstice-ci/ -o "$TMPDIR/$TARBALL" HEAD
for pkg in solstice-orchestrator solstice-forge-integration; do
PKG_DIR="$ROOT_DIR/packaging/arch/$pkg"
diff --git a/crates/github-integration/Cargo.toml b/crates/github-integration/Cargo.toml
index 09a4415..a3aa500 100644
--- a/crates/github-integration/Cargo.toml
+++ b/crates/github-integration/Cargo.toml
@@ -9,3 +9,4 @@ clap = { version = "4", features = ["derive", "env"] }
miette = { version = "7", features = ["fancy"] }
tracing = "0.1"
tokio = { version = "1", features = ["rt-multi-thread", "macros", "signal"] }
+axum = { version = "0.8", features = ["macros"] }
diff --git a/crates/github-integration/src/main.rs b/crates/github-integration/src/main.rs
index 04c2bce..7749b23 100644
--- a/crates/github-integration/src/main.rs
+++ b/crates/github-integration/src/main.rs
@@ -1,6 +1,8 @@
+use std::net::SocketAddr;
use clap::Parser;
use miette::Result;
use tracing::{info, warn};
+use axum::{Router, routing::post, response::IntoResponse, http::StatusCode};
#[derive(Parser, Debug)]
#[command(
@@ -9,10 +11,14 @@ use tracing::{info, warn};
about = "Solstice CI — GitHub Integration (GitHub App)"
)]
struct Opts {
- /// HTTP bind address for GitHub webhooks (e.g., 0.0.0.0:8081)
- #[arg(long, env = "HTTP_ADDR", default_value = "0.0.0.0:8081")]
+ /// HTTP bind address for GitHub webhooks (e.g., 0.0.0.0:8082)
+ #[arg(long, env = "HTTP_ADDR", default_value = "0.0.0.0:8082")]
http_addr: String,
+ /// Webhook path (route)
+ #[arg(long, env = "WEBHOOK_PATH", default_value = "/webhooks/github")]
+ webhook_path: String,
+
/// GitHub App ID
#[arg(long, env = "GITHUB_APP_ID")]
app_id: Option,
@@ -26,15 +32,29 @@ struct Opts {
otlp: Option,
}
+async fn handle_github_webhook() -> impl IntoResponse {
+ // For now, accept and log. Implement signature verification and event handling later.
+ StatusCode::OK
+}
+
#[tokio::main(flavor = "multi_thread")]
async fn main() -> Result<()> {
let _t = common::init_tracing("solstice-github-integration")?;
let opts = Opts::parse();
- info!(http_addr = %opts.http_addr, "github integration starting");
+ info!(http_addr = %opts.http_addr, path = %opts.webhook_path, "github integration starting");
- // TODO: Start HTTP server, validate signatures, implement GitHub App auth flow
- warn!("github-integration skeleton running; HTTP/GitHub App not implemented yet");
+ let path: &'static str = Box::leak(opts.webhook_path.clone().into_boxed_str());
+ let app = Router::new().route(path, post(handle_github_webhook));
+
+ let addr: SocketAddr = opts.http_addr.parse().expect("invalid HTTP_ADDR");
+ warn!("github-integration webhook endpoint is active but handler is minimal; implement GitHub App flow");
+
+ axum::serve(
+ tokio::net::TcpListener::bind(addr).await.expect("bind"),
+ app,
+ )
+ .await
+ .expect("server error");
- tokio::signal::ctrl_c().await.expect("listen for ctrl-c");
Ok(())
}
diff --git a/deploy/images/forge-integration/Containerfile b/deploy/images/forge-integration/Containerfile
new file mode 100644
index 0000000..6411ecd
--- /dev/null
+++ b/deploy/images/forge-integration/Containerfile
@@ -0,0 +1,25 @@
+# syntax=docker/dockerfile:1.7
+# Build Solstice Forge Integration using upstream official images (no sccache)
+
+FROM docker.io/library/rust:bookworm AS builder
+ENV CARGO_HOME=/cargo
+WORKDIR /work
+# Install protoc for tonic/prost builds
+RUN apt-get update \
+ && apt-get install -y --no-install-recommends protobuf-compiler ca-certificates \
+ && rm -rf /var/lib/apt/lists/*
+# Configure cargo target-dir so it can be cached between layers
+RUN mkdir -p /cargo && printf "[build]\ntarget-dir = \"/cargo/target\"\n" > /cargo/config.toml
+COPY Cargo.toml ./
+COPY crates ./crates
+RUN --mount=type=cache,target=/cargo/registry \
+ --mount=type=cache,target=/cargo/git \
+ --mount=type=cache,target=/cargo/target \
+ cargo build --release -p forge-integration && cp /cargo/target/release/forge-integration /forge-integration
+
+FROM docker.io/library/debian:bookworm-slim
+RUN apt-get update \
+ && apt-get install -y --no-install-recommends ca-certificates \
+ && rm -rf /var/lib/apt/lists/*
+COPY --from=builder /forge-integration /usr/local/bin/forge-integration
+ENTRYPOINT ["/usr/local/bin/forge-integration"]
diff --git a/deploy/images/github-integration/Containerfile b/deploy/images/github-integration/Containerfile
new file mode 100644
index 0000000..b096a96
--- /dev/null
+++ b/deploy/images/github-integration/Containerfile
@@ -0,0 +1,25 @@
+# syntax=docker/dockerfile:1.7
+# Build Solstice GitHub Integration using upstream official images (no sccache)
+
+FROM docker.io/library/rust:bookworm AS builder
+ENV CARGO_HOME=/cargo
+WORKDIR /work
+# Install protoc for tonic/prost builds
+RUN apt-get update \
+ && apt-get install -y --no-install-recommends protobuf-compiler ca-certificates \
+ && rm -rf /var/lib/apt/lists/*
+# Configure cargo target-dir so it can be cached between layers
+RUN mkdir -p /cargo && printf "[build]\ntarget-dir = \"/cargo/target\"\n" > /cargo/config.toml
+COPY Cargo.toml ./
+COPY crates ./crates
+RUN --mount=type=cache,target=/cargo/registry \
+ --mount=type=cache,target=/cargo/git \
+ --mount=type=cache,target=/cargo/target \
+ cargo build --release -p github-integration && cp /cargo/target/release/github-integration /github-integration
+
+FROM docker.io/library/debian:bookworm-slim
+RUN apt-get update \
+ && apt-get install -y --no-install-recommends ca-certificates \
+ && rm -rf /var/lib/apt/lists/*
+COPY --from=builder /github-integration /usr/local/bin/github-integration
+ENTRYPOINT ["/usr/local/bin/github-integration"]
diff --git a/deploy/images/orchestrator/Containerfile b/deploy/images/orchestrator/Containerfile
new file mode 100644
index 0000000..e2bda0f
--- /dev/null
+++ b/deploy/images/orchestrator/Containerfile
@@ -0,0 +1,30 @@
+# syntax=docker/dockerfile:1.7
+# Build Solstice Orchestrator using upstream official images (no sccache)
+
+FROM docker.io/library/rust:bookworm AS builder
+ENV CARGO_HOME=/cargo
+WORKDIR /work
+# Install build dependencies: protoc, headers, pkg-config
+RUN apt-get update \
+ && apt-get install -y --no-install-recommends \
+ protobuf-compiler pkg-config libsqlite3-dev libpq-dev ca-certificates \
+ && rm -rf /var/lib/apt/lists/*
+# Configure cargo target-dir so it can be cached between layers
+RUN mkdir -p /cargo && printf "[build]\ntarget-dir = \"/cargo/target\"\n" > /cargo/config.toml
+# Pre-copy manifests for better caching
+COPY Cargo.toml ./
+COPY crates ./crates
+# Build orchestrator only
+RUN --mount=type=cache,target=/cargo/registry \
+ --mount=type=cache,target=/cargo/git \
+ --mount=type=cache,target=/cargo/target \
+ cargo build --release -p orchestrator && cp /cargo/target/release/orchestrator /orchestrator
+
+FROM docker.io/library/debian:bookworm-slim
+# Minimal runtime image with required shared libs for sqlite/postgres
+RUN apt-get update \
+ && apt-get install -y --no-install-recommends libsqlite3-0 libpq5 ca-certificates \
+ && rm -rf /var/lib/apt/lists/*
+COPY --from=builder /orchestrator /usr/local/bin/orchestrator
+EXPOSE 50051 8081
+ENTRYPOINT ["/usr/local/bin/orchestrator"]
diff --git a/deploy/podman/.env.sample b/deploy/podman/.env.sample
new file mode 100644
index 0000000..370bb03
--- /dev/null
+++ b/deploy/podman/.env.sample
@@ -0,0 +1,36 @@
+# Copy to .env and adjust values. This file is consumed by podman compose.
+# Deployment environment: staging or prod
+ENV=staging
+# Base domain used for routing. External hostnames are *.svc.${DOMAIN} (no ENV in hostname)
+DOMAIN=solstice-ci.org
+# ACME email for Let's Encrypt registration
+TRAEFIK_ACME_EMAIL=ops@solstice-ci.org
+# Optional: set Let's Encrypt CA server (leave empty for production, set to staging for tests)
+# For staging, uncomment:
+# TRAEFIK_ACME_CASERVER=https://acme-staging-v02.api.letsencrypt.org/directory
+
+# Admin credentials (override in real deployments via secret store)
+POSTGRES_USER=solstice
+POSTGRES_PASSWORD=change-me
+# Databases are created by postgres-setup: solstice_staging and solstice_prod
+# Services will connect to postgres database: solstice_${ENV}
+POSTGRES_DB=solstice
+
+# RabbitMQ uses a single broker with per-env vhosts: solstice-staging, solstice-prod
+RABBITMQ_DEFAULT_USER=solstice
+RABBITMQ_DEFAULT_PASS=change-me
+
+MINIO_ROOT_USER=solstice
+MINIO_ROOT_PASSWORD=change-me
+# Buckets per env (created by minio-setup): solstice-logs-staging, solstice-logs-prod
+# Optionally set to the env-specific bucket name (set in your shell, not here): e.g., solstice-logs-staging or solstice-logs-prod
+# Leave empty to skip custom bucket creation in minio-setup
+MINIO_BUCKET=
+
+# Traefik dashboard basic auth user:password hash (htpasswd -nB admin)
+# Example: admin:$2y$05$kN2K0... (bcrypt)
+TRAEFIK_DASHBOARD_AUTH=
+
+# Host ports to bind Traefik
+TRAEFIK_HTTP_PORT=80
+TRAEFIK_HTTPS_PORT=443
diff --git a/deploy/podman/README.md b/deploy/podman/README.md
new file mode 100644
index 0000000..f60a9b0
--- /dev/null
+++ b/deploy/podman/README.md
@@ -0,0 +1,71 @@
+Solstice CI — Production deployment with Podman Compose + Traefik
+
+This stack deploys Solstice CI services behind Traefik with automatic TLS certificates from Let’s Encrypt. It uses upstream official images for system services and multi-stage Rust builds on official Rust/Debian images that rely on container layer caching (no sccache) for fast, reproducible builds.
+
+Prerequisites
+- Podman 4.9+ with podman-compose compatibility (podman compose)
+- Public DNS records for subdomains pointing to the host running this stack
+- Ports 80 and 443 open to the Internet
+- Email address for ACME registration
+
+DNS
+Create A/AAAA records for the following hostnames under your base domain (no environment in hostname; env separation is logical via DB/vhost/buckets):
+- traefik.svc.DOMAIN
+- api.svc.DOMAIN
+- grpc.svc.DOMAIN
+- runner.svc.DOMAIN
+- forge.svc.DOMAIN (Forge/Forgejo webhooks)
+- github.svc.DOMAIN (GitHub App/webhooks)
+- minio.svc.DOMAIN (console UI)
+- s3.svc.DOMAIN (S3 API, TLS via TCP SNI)
+- mq.svc.DOMAIN (RabbitMQ mgmt UI; AMQP remains internal)
+
+Quick start
+1. Copy env template and edit secrets and settings:
+ cp .env.sample .env
+ # Edit .env (ENV=staging|prod, DOMAIN, passwords, ACME email)
+2. (Optional) Use Let’s Encrypt staging CA to test issuance without rate limits by setting in .env:
+ TRAEFIK_ACME_CASERVER=https://acme-staging-v02.api.letsencrypt.org/directory
+3. Bring up the stack:
+ podman compose -f compose.yml up -d --build
+4. Monitor logs:
+ podman compose logs -f traefik
+
+Services and routing
+- Traefik dashboard: https://traefik.svc.${DOMAIN} (protect with TRAEFIK_DASHBOARD_AUTH in .env)
+- Orchestrator HTTP: https://api.svc.${DOMAIN}
+- Orchestrator gRPC (h2/TLS via SNI): grpc.svc.${DOMAIN}
+- Forge webhooks: https://forge.svc.${DOMAIN}
+- GitHub webhooks: https://github.svc.${DOMAIN}
+- Runner static server: https://runner.svc.${DOMAIN}
+- MinIO console: https://minio.svc.${DOMAIN}
+- S3 API: s3.svc.${DOMAIN}
+- RabbitMQ management: https://mq.svc.${DOMAIN}
+
+Environment scoping (single infra, logical separation)
+- RabbitMQ: single broker; per-environment vhosts named solstice-${ENV} (staging/prod). Services connect to amqp://.../solstice-${ENV}.
+- Postgres: single cluster; databases solstice_staging and solstice_prod are created by the postgres-setup job. Services use postgres://.../solstice_${ENV}.
+- MinIO: single server; buckets solstice-logs-staging and solstice-logs-prod are created by the minio-setup job. Set S3 bucket per service to the env-appropriate bucket.
+
+Security notes
+- Secrets are provided via podman compose secrets referencing your environment variables. Do not commit real secrets.
+- Only management UIs are exposed publicly via Traefik. Data planes (Postgres, AMQP, S3 API) terminate TLS at Traefik and route internally. Adjust exposure policy as needed.
+
+Images and builds
+- System services use Chainguard images (postgres, rabbitmq). MinIO uses upstream images.
+- Rust services are built with multi-stage Containerfiles using cgr.dev/chainguard/rust and run on cgr.dev/chainguard/glibc-dynamic.
+- Build caches are mounted in-build for cargo registry/git and the cargo target directory (via ~/.cargo/config target-dir=/cargo/target).
+
+Maintenance
+- Upgrade images by editing tags in compose.yml and rebuilding: podman compose build --pull
+- Renewals are automatic via Traefik ACME. Certificates are stored in the traefik-acme volume.
+- Backups: persist volumes (postgres-data, rabbitmq-data, minio-data, traefik-acme).
+
+Tear down
+- Stop: podman compose down
+- Remove volumes (DANGEROUS: destroys data): podman volume rm solstice-ci_traefik-acme solstice-ci_postgres-data solstice-ci_rabbitmq-data solstice-ci_minio-data
+
+Troubleshooting
+- Certificate issues: check Traefik logs; verify DNS and ports 80/443. For testing, use ACME staging server.
+- No routes: verify labels on services and that traefik sees the podman socket.
+- Healthchecks failing: inspect service logs with podman logs .
diff --git a/deploy/podman/compose.yml b/deploy/podman/compose.yml
new file mode 100644
index 0000000..af5a28a
--- /dev/null
+++ b/deploy/podman/compose.yml
@@ -0,0 +1,268 @@
+# Podman Compose production stack for Solstice CI with Traefik + Let's Encrypt
+# Usage:
+# 1) cp .env.sample .env && edit values (ENV=staging|prod, secrets, email)
+# 2) Ensure DNS A/AAAA for required hostnames pointing to this host:
+# - traefik.svc.${DOMAIN}
+# - api.${ENV}.${DOMAIN}
+# - grpc.${ENV}.${DOMAIN}
+# - forge.${ENV}.${DOMAIN}
+# - github.${ENV}.${DOMAIN}
+# - minio.svc.${DOMAIN}
+# - s3.svc.${DOMAIN}
+# - mq.svc.${DOMAIN}
+# - db.svc.${DOMAIN} (optional)
+# 3) podman compose -f compose.yml up -d --build
+
+name: solstice-ci
+
+networks:
+ core:
+ driver: bridge
+
+volumes:
+ traefik-acme:
+ postgres-data:
+ rabbitmq-data:
+ minio-data:
+
+
+services:
+ traefik:
+ image: docker.io/library/traefik:v3.1
+ container_name: traefik
+ restart: unless-stopped
+ command:
+ - --api.dashboard=true
+ - --providers.docker=true
+ - --providers.docker.exposedbydefault=false
+ - --entrypoints.web.address=:80
+ - --entrypoints.websecure.address=:443
+ - --certificatesresolvers.le.acme.email=${TRAEFIK_ACME_EMAIL}
+ - --certificatesresolvers.le.acme.storage=/acme/acme.json
+ - --certificatesresolvers.le.acme.httpchallenge=true
+ - --certificatesresolvers.le.acme.httpchallenge.entrypoint=web
+ - --serversTransport.insecureSkipVerify=true
+ # Optional: override ACME CA server via .env (e.g., staging URL)
+ - --certificatesresolvers.le.acme.caserver=${TRAEFIK_ACME_CASERVER}
+ ports:
+ - ${TRAEFIK_HTTP_PORT:-80}:80
+ - ${TRAEFIK_HTTPS_PORT:-443}:443
+ volumes:
+ - /var/run/podman/podman.sock:/var/run/docker.sock:Z
+ - traefik-acme:/acme
+ networks:
+ - core
+ labels:
+ - traefik.enable=true
+ # Dashboard at traefik.svc.${DOMAIN}
+ - traefik.http.routers.traefik.rule=Host(`traefik.svc.${DOMAIN}`)
+ - traefik.http.routers.traefik.entrypoints=websecure
+ - traefik.http.routers.traefik.tls.certresolver=le
+ - traefik.http.routers.traefik.service=api@internal
+ - traefik.http.middlewares.traefik-auth.basicauth.users=${TRAEFIK_DASHBOARD_AUTH}
+ - traefik.http.routers.traefik.middlewares=traefik-auth
+
+ postgres:
+ image: docker.io/library/postgres:16-alpine
+ container_name: solstice-postgres
+ restart: unless-stopped
+ environment:
+ POSTGRES_USER: ${POSTGRES_USER}
+ POSTGRES_DB: ${POSTGRES_DB}
+ POSTGRES_PASSWORD: ${POSTGRES_PASSWORD}
+ healthcheck:
+ test: ["CMD-SHELL", "pg_isready -U ${POSTGRES_USER}"]
+ interval: 10s
+ timeout: 5s
+ retries: 5
+ start_period: 10s
+ volumes:
+ - postgres-data:/var/lib/postgresql/data:Z
+ networks:
+ - core
+
+ postgres-setup:
+ image: docker.io/library/postgres:16-alpine
+ container_name: solstice-postgres-setup
+ depends_on:
+ postgres:
+ condition: service_healthy
+ entrypoint: ["/bin/sh", "-c"]
+ environment:
+ POSTGRES_USER: ${POSTGRES_USER}
+ POSTGRES_PASSWORD: ${POSTGRES_PASSWORD}
+ command: >-
+ "export PGPASSWORD=${POSTGRES_PASSWORD} &&
+ psql -h postgres -U ${POSTGRES_USER} -v ON_ERROR_STOP=1 -tc \"SELECT 'CREATE DATABASE solstice_staging' WHERE NOT EXISTS (SELECT FROM pg_database WHERE datname='solstice_staging')\" | psql -h postgres -U ${POSTGRES_USER} &&
+ psql -h postgres -U ${POSTGRES_USER} -v ON_ERROR_STOP=1 -tc \"SELECT 'CREATE DATABASE solstice_prod' WHERE NOT EXISTS (SELECT FROM pg_database WHERE datname='solstice_prod')\" | psql -h postgres -U ${POSTGRES_USER}"
+ networks:
+ - core
+
+ rabbitmq:
+ image: docker.io/library/rabbitmq:4-management-alpine
+ container_name: solstice-rabbitmq
+ restart: unless-stopped
+ environment:
+ RABBITMQ_DEFAULT_USER: ${RABBITMQ_DEFAULT_USER}
+ RABBITMQ_DEFAULT_PASS: ${RABBITMQ_DEFAULT_PASS}
+ RABBITMQ_DEFAULT_VHOST: solstice-${ENV}
+ healthcheck:
+ test: ["CMD", "rabbitmq-diagnostics", "-q", "ping"]
+ interval: 10s
+ timeout: 5s
+ retries: 5
+ start_period: 5s
+ volumes:
+ - rabbitmq-data:/var/lib/rabbitmq:Z
+ networks:
+ - core
+ labels:
+ - traefik.enable=true
+ # Management UI at mq.svc.${DOMAIN}
+ - traefik.http.routers.mq.rule=Host(`mq.svc.${DOMAIN}`)
+ - traefik.http.routers.mq.entrypoints=websecure
+ - traefik.http.routers.mq.tls.certresolver=le
+ - traefik.http.services.mq.loadbalancer.server.port=15672
+
+ minio:
+ image: quay.io/minio/minio:RELEASE.2025-02-07T22-39-53Z
+ container_name: solstice-minio
+ restart: unless-stopped
+ command: server /data --console-address ":9001"
+ environment:
+ MINIO_ROOT_USER: ${MINIO_ROOT_USER}
+ MINIO_ROOT_PASSWORD: ${MINIO_ROOT_PASSWORD}
+ healthcheck:
+ test: ["CMD", "curl", "-f", "http://localhost:9000/minio/health/live"]
+ interval: 10s
+ timeout: 5s
+ retries: 5
+ start_period: 10s
+ volumes:
+ - minio-data:/data:Z
+ networks:
+ - core
+ labels:
+ - traefik.enable=true
+ # MinIO Console at minio.svc.${DOMAIN}
+ - traefik.http.routers.minio.rule=Host(`minio.svc.${DOMAIN}`)
+ - traefik.http.routers.minio.entrypoints=websecure
+ - traefik.http.routers.minio.tls.certresolver=le
+ - traefik.http.services.minio.loadbalancer.server.port=9001
+ # S3 API via TCP router on s3.svc.${DOMAIN}
+ - traefik.tcp.routers.s3.rule=HostSNI(`s3.svc.${DOMAIN}`)
+ - traefik.tcp.routers.s3.entrypoints=websecure
+ - traefik.tcp.routers.s3.tls=true
+ - traefik.tcp.routers.s3.tls.certresolver=le
+ - traefik.tcp.services.s3.loadbalancer.server.port=9000
+
+ minio-setup:
+ image: quay.io/minio/mc:RELEASE.2025-02-07T22-47-51Z
+ container_name: solstice-minio-setup
+ depends_on:
+ minio:
+ condition: service_healthy
+ entrypoint: ["/bin/sh", "-c"]
+ command: >-
+ "mc alias set local http://minio:9000 ${MINIO_ROOT_USER} ${MINIO_ROOT_PASSWORD} &&
+ mc mb -p local/solstice-logs-staging || true &&
+ mc mb -p local/solstice-logs-prod || true &&
+ if [ -n \"${MINIO_BUCKET}\" ]; then mc mb -p local/${MINIO_BUCKET} || true; fi"
+ networks:
+ - core
+
+ orchestrator:
+ build:
+ context: ../..
+ dockerfile: deploy/images/orchestrator/Containerfile
+ args:
+ BIN: orchestrator
+ image: local/solstice-orchestrator:latest
+ container_name: solstice-orchestrator
+ restart: unless-stopped
+ environment:
+ RUST_LOG: info
+ DATABASE_URL: postgres://${POSTGRES_USER}:${POSTGRES_PASSWORD}@postgres:5432/solstice_${ENV}
+ AMQP_URL: amqp://${RABBITMQ_DEFAULT_USER}:${RABBITMQ_DEFAULT_PASS}@rabbitmq:5672/solstice-${ENV}
+ AMQP_EXCHANGE: solstice.jobs
+ AMQP_QUEUE: solstice.jobs.v1
+ AMQP_ROUTING_KEY: jobrequest.v1
+ GRPC_ADDR: 0.0.0.0:50051
+ HTTP_ADDR: 0.0.0.0:8081
+ depends_on:
+ postgres:
+ condition: service_healthy
+ postgres-setup:
+ condition: service_completed_successfully
+ rabbitmq:
+ condition: service_healthy
+ networks:
+ - core
+ labels:
+ - traefik.enable=true
+ # HTTP endpoints at api.${ENV}.${DOMAIN}
+ - traefik.http.routers.api.rule=Host(`api.${ENV}.${DOMAIN}`)
+ - traefik.http.routers.api.entrypoints=websecure
+ - traefik.http.routers.api.tls.certresolver=le
+ - traefik.http.services.api.loadbalancer.server.port=8081
+ # gRPC on grpc.${ENV}.${DOMAIN} (TLS, h2)
+ - traefik.tcp.routers.grpc.rule=HostSNI(`grpc.${ENV}.${DOMAIN}`)
+ - traefik.tcp.routers.grpc.entrypoints=websecure
+ - traefik.tcp.routers.grpc.tls=true
+ - traefik.tcp.routers.grpc.tls.certresolver=le
+ - traefik.tcp.services.grpc.loadbalancer.server.port=50051
+
+ forge-integration:
+ build:
+ context: ../..
+ dockerfile: deploy/images/forge-integration/Containerfile
+ args:
+ BIN: forge-integration
+ image: local/solstice-forge-integration:latest
+ container_name: solstice-forge-integration
+ restart: unless-stopped
+ environment:
+ RUST_LOG: info
+ AMQP_URL: amqp://${RABBITMQ_DEFAULT_USER}:${RABBITMQ_DEFAULT_PASS}@rabbitmq:5672/solstice-${ENV}
+ # HTTP server config for webhooks
+ HTTP_ADDR: 0.0.0.0:8080
+ WEBHOOK_PATH: /webhooks/forgejo
+ depends_on:
+ rabbitmq:
+ condition: service_healthy
+ networks:
+ - core
+ labels:
+ - traefik.enable=true
+ # Forge webhooks at forge.svc.${DOMAIN}
+ - traefik.http.routers.forge.rule=Host(`forge.${ENV}.${DOMAIN}`)
+ - traefik.http.routers.forge.entrypoints=websecure
+ - traefik.http.routers.forge.tls.certresolver=le
+ - traefik.http.services.forge.loadbalancer.server.port=8080
+
+ github-integration:
+ build:
+ context: ../..
+ dockerfile: deploy/images/github-integration/Containerfile
+ args:
+ BIN: github-integration
+ image: local/solstice-github-integration:latest
+ container_name: solstice-github-integration
+ restart: unless-stopped
+ environment:
+ RUST_LOG: info
+ AMQP_URL: amqp://${RABBITMQ_DEFAULT_USER}:${RABBITMQ_DEFAULT_PASS}@rabbitmq:5672/solstice-${ENV}
+ # HTTP server for GitHub webhooks (skeleton service; implement handler later)
+ HTTP_ADDR: 0.0.0.0:8082
+ depends_on:
+ rabbitmq:
+ condition: service_healthy
+ networks:
+ - core
+ labels:
+ - traefik.enable=true
+ # GitHub webhooks at github.svc.${DOMAIN}
+ - traefik.http.routers.github.rule=Host(`github.${ENV}.${DOMAIN}`)
+ - traefik.http.routers.github.entrypoints=websecure
+ - traefik.http.routers.github.tls.certresolver=le
+ - traefik.http.services.github.loadbalancer.server.port=8082
diff --git a/mise.toml b/mise.toml
index d2bd8ff..edddea0 100644
--- a/mise.toml
+++ b/mise.toml
@@ -1,4 +1,5 @@
[tools]
age = "latest"
fnox = "latest"
+protoc = "latest"
python = "latest"
diff --git a/packaging/arch/solstice-orchestrator/PKGBUILD b/packaging/arch/solstice-orchestrator/PKGBUILD
index 6c76ced..d263b4e 100644
--- a/packaging/arch/solstice-orchestrator/PKGBUILD
+++ b/packaging/arch/solstice-orchestrator/PKGBUILD
@@ -6,7 +6,7 @@ pkgdesc="Solstice CI Orchestrator service"
arch=(x86_64)
url="https://codeberg.org/your-namespace/solstice-ci"
license=(MPL2)
-depends=(glibc libvirt)
+depends=(glibc libvirt sqlite postgresql-libs zstd)
makedepends=(rust cargo)
source=("solstice-ci.tar.gz"
"solstice-orchestrator.service"