diff --git a/.idea/dataSources.xml b/.idea/dataSources.xml
index 96021f3..e5905a9 100644
--- a/.idea/dataSources.xml
+++ b/.idea/dataSources.xml
@@ -5,7 +5,7 @@
postgresql
true
org.postgresql.Driver
- jdbc:postgresql://172.18.0.5:5432/postgres
+ jdbc:postgresql://127.0.0.1:5432/postgres
$ProjectFileDir$
diff --git a/.mise/tasks/build/deb b/.mise/tasks/build/deb
new file mode 100755
index 0000000..9a62b79
--- /dev/null
+++ b/.mise/tasks/build/deb
@@ -0,0 +1,15 @@
+#!/usr/bin/env bash
+set -euo pipefail
+
+# Build Debian package for the orchestrator using cargo-deb
+if ! command -v cargo-deb >/dev/null 2>&1; then
+ cargo install cargo-deb
+fi
+
+# Build release binary first for reproducible asset path
+cargo build -p orchestrator --release
+
+# Package orchestrator
+cargo deb -p orchestrator --no-build
+
+echo "\nDebs written under target/debian/*.deb"
\ No newline at end of file
diff --git a/crates/orchestrator/Cargo.toml b/crates/orchestrator/Cargo.toml
index 9542b32..c946ea2 100644
--- a/crates/orchestrator/Cargo.toml
+++ b/crates/orchestrator/Cargo.toml
@@ -1,6 +1,6 @@
[package]
name = "orchestrator"
-version = "0.1.0"
+version = "0.1.1"
edition = "2024"
build = "build.rs"
@@ -38,7 +38,27 @@ chrono = { version = "0.4", default-features = false, features = ["clock", "std"
dashmap = "6"
async-trait = "0.1"
uuid = { version = "1", features = ["v4", "serde"] }
-futures-util = "0.3.31"
[target.'cfg(target_os = "linux")'.dependencies]
virt = { version = "0.4.3" }
+
+[package.metadata.deb]
+name = "solstice-orchestrator"
+maintainer = "Solstice CI "
+section = "utils"
+priority = "optional"
+assets = [
+ ["target/release/orchestrator", "/usr/bin/solstice-orchestrator", "755"],
+ ["packaging/solstice-orchestrator.service", "/lib/systemd/system/solstice-orchestrator.service", "644"],
+ ["../../examples/orchestrator-image-map.yaml", "/etc/solstice/orchestrator.yaml", "644"],
+ ["../../examples/etc/solstice/orchestrator.env.sample", "/etc/solstice/orchestrator.env", "640"],
+]
+depends = [
+ "libvirt-daemon-system",
+ "libvirt-clients",
+ "ca-certificates",
+ "openssh-client",
+]
+recommends = ["qemu-kvm", "virtinst"]
+conf-files = ["/etc/solstice/orchestrator.yaml", "/etc/solstice/orchestrator.env"]
+maintainer-scripts = "packaging/debian/"
diff --git a/crates/orchestrator/packaging/debian/postinst b/crates/orchestrator/packaging/debian/postinst
new file mode 100644
index 0000000..fd18478
--- /dev/null
+++ b/crates/orchestrator/packaging/debian/postinst
@@ -0,0 +1,20 @@
+#!/bin/sh
+set -e
+
+# Create solstice system user/group if not present
+if ! id -u solstice >/dev/null 2>&1; then
+ adduser --system --group --no-create-home --home /nonexistent --shell /usr/sbin/nologin solstice || true
+fi
+
+# Ensure config directory exists
+mkdir -p /etc/solstice
+chown root:root /etc/solstice
+chmod 755 /etc/solstice
+
+# Reload systemd units and enable service
+if command -v systemctl >/dev/null 2>&1; then
+ systemctl daemon-reload || true
+ systemctl enable solstice-orchestrator.service || true
+fi
+
+exit 0
diff --git a/crates/orchestrator/packaging/solstice-orchestrator.service b/crates/orchestrator/packaging/solstice-orchestrator.service
new file mode 100644
index 0000000..a6614a7
--- /dev/null
+++ b/crates/orchestrator/packaging/solstice-orchestrator.service
@@ -0,0 +1,23 @@
+[Unit]
+Description=Solstice CI Orchestrator
+After=network-online.target
+Wants=network-online.target
+
+[Service]
+Type=simple
+EnvironmentFile=-/etc/default/solstice-orchestrator
+EnvironmentFile=-/etc/solstice/orchestrator.env
+ExecStart=/usr/bin/solstice-orchestrator
+Restart=on-failure
+RestartSec=3s
+User=solstice
+Group=solstice
+AmbientCapabilities=
+NoNewPrivileges=yes
+ProtectSystem=full
+ProtectHome=true
+PrivateTmp=true
+RuntimeDirectory=solstice
+
+[Install]
+WantedBy=multi-user.target
diff --git a/deploy/podman/compose.yml b/deploy/podman/compose.yml
index aa5004b..6a7afe5 100644
--- a/deploy/podman/compose.yml
+++ b/deploy/podman/compose.yml
@@ -77,6 +77,8 @@ services:
timeout: 5s
retries: 5
start_period: 10s
+ ports:
+ - "127.0.0.1:5432:5432" # expose Postgres to host only
volumes:
- postgres-data:/var/lib/postgresql/data:Z
networks:
@@ -113,6 +115,8 @@ services:
timeout: 5s
retries: 5
start_period: 5s
+ ports:
+ - "127.0.0.1:5672:5672" # expose AMQP to host only
volumes:
- rabbitmq-data:/var/lib/rabbitmq:Z
networks:
@@ -236,6 +240,22 @@ services:
- traefik.http.routers.api.tls.certresolver=le
- traefik.http.services.api.loadbalancer.server.port=8081
+ orchestrator-logs-proxy:
+ image: docker.io/library/nginx:alpine
+ container_name: solstice-orchestrator-logs
+ restart: unless-stopped
+ volumes:
+ - ./nginx/orchestrator-logs.conf:/etc/nginx/conf.d/default.conf:ro,Z
+ networks:
+ - core
+ labels:
+ - traefik.enable=true
+ # Expose orchestrator HTTP (logs) running on the host via a tiny proxy
+ - traefik.http.routers.logs.rule=Host(`logs.${ENV}.${DOMAIN}`)
+ - traefik.http.routers.logs.entrypoints=websecure
+ - traefik.http.routers.logs.tls.certresolver=le
+ - traefik.http.services.logs.loadbalancer.server.port=80
+
forge-integration:
build:
context: ../..
diff --git a/deploy/podman/nginx/orchestrator-logs.conf b/deploy/podman/nginx/orchestrator-logs.conf
new file mode 100644
index 0000000..6f86183
--- /dev/null
+++ b/deploy/podman/nginx/orchestrator-logs.conf
@@ -0,0 +1,14 @@
+server {
+ listen 80;
+ server_name _;
+
+ location / {
+ proxy_set_header Host $host;
+ proxy_set_header X-Real-IP $remote_addr;
+ proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
+ proxy_set_header X-Forwarded-Proto $scheme;
+ proxy_read_timeout 300s;
+ proxy_connect_timeout 5s;
+ proxy_pass http://host.containers.internal:8081;
+ }
+}
diff --git a/examples/etc/solstice/orchestrator.env.sample b/examples/etc/solstice/orchestrator.env.sample
new file mode 100644
index 0000000..0f9aeb0
--- /dev/null
+++ b/examples/etc/solstice/orchestrator.env.sample
@@ -0,0 +1,27 @@
+# Solstice Orchestrator environment overrides
+# Copy to /etc/solstice/orchestrator.env and edit values as needed.
+# Anything unset falls back to compiled defaults or other env vars.
+
+# Networking
+HTTP_ADDR=0.0.0.0:8081
+
+# Messaging (RabbitMQ)
+AMQP_URL=amqp://user:pass@mq.svc.example:5672/%2f
+AMQP_EXCHANGE=solstice.jobs
+AMQP_QUEUE=solstice.jobs.v1
+AMQP_ROUTING_KEY=jobrequest.v1
+AMQP_PREFETCH=16
+
+# Database (optional)
+# Leave empty to disable persistence
+DATABASE_URL=postgres://solstice:solstice@db.svc.example:5432/solstice_prod
+
+# Libvirt
+LIBVIRT_URI=qemu:///system
+LIBVIRT_NETWORK=default
+
+# Scheduler
+MAX_CONCURRENCY=2
+
+# Telemetry
+# OTEL_EXPORTER_OTLP_ENDPOINT=http://otelcol.svc.example:4317