Skip to content
Merged
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
14 changes: 14 additions & 0 deletions .github/workflows/deploy-bundle.yml
Original file line number Diff line number Diff line change
Expand Up @@ -31,11 +31,22 @@ jobs:
- name: Validate standalone hosted runtime Compose contract
run: make hosted-managed-runtime-compose-contract PYTHON=python3

- name: Validate TLS-fronted production hosted runtime contract
run: make hosted-managed-production-contract PYTHON=python3

- name: Build standalone hosted runtime image
run: >-
docker build --file Dockerfile.hosted-managed
--tag 0al/platform-hosted-managed:ci .

- name: Build capability-free hosted ingress image
run: |
docker pull caddy:2-alpine
caddy_base="$(docker image inspect --format '{{index .RepoDigests 0}}' caddy:2-alpine)"
docker build --file Dockerfile.hosted-managed-ingress \
--build-arg "CADDY_BASE_IMAGE=${caddy_base}" \
--tag 0al/platform-hosted-managed-ingress:ci .

hosted-managed-postgres:
runs-on: ubuntu-latest
services:
Expand Down Expand Up @@ -85,6 +96,9 @@ jobs:
- name: Start hosted service and worker profile
run: make hosted-managed-compose-smoke PYTHON=python3

- name: Start TLS-fronted production hosted profile
run: make hosted-managed-production-compose-smoke PYTHON=python3

platform-deploy-bundle:
runs-on: ubuntu-latest
steps:
Expand Down
11 changes: 11 additions & 0 deletions Dockerfile.hosted-managed-ingress
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
ARG CADDY_BASE_IMAGE
FROM ${CADDY_BASE_IMAGE}

USER root
RUN apk add --no-cache libcap \
&& setcap -r /usr/bin/caddy \
&& apk del libcap

USER 1000:1000
ENTRYPOINT ["caddy"]
CMD ["run", "--config", "/etc/caddy/Caddyfile", "--adapter", "caddyfile"]
11 changes: 10 additions & 1 deletion Makefile
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
PYTHON ?= $(if $(wildcard .venv/bin/python),.venv/bin/python,python3)

.PHONY: python-quality test hosted-managed-contract hosted-managed-compose-contract hosted-managed-runtime-compose-contract hosted-managed-postgres-integration hosted-managed-compose-smoke
.PHONY: python-quality test hosted-managed-contract hosted-managed-compose-contract hosted-managed-runtime-compose-contract hosted-managed-production-compose-contract hosted-managed-production-contract hosted-managed-postgres-integration hosted-managed-compose-smoke hosted-managed-production-compose-smoke

python-quality:
$(PYTHON) -m unittest discover -s tests
Expand All @@ -20,10 +20,19 @@ hosted-managed-compose-contract:
hosted-managed-runtime-compose-contract:
$(PYTHON) scripts/validate_hosted_managed_runtime_compose.py

hosted-managed-production-compose-contract:
$(PYTHON) scripts/validate_hosted_managed_production_compose.py

hosted-managed-production-contract: hosted-managed-production-compose-contract
$(PYTHON) -m unittest tests.test_hosted_managed_production

hosted-managed-postgres-integration:
@test -n "$(PLATFORM_TEST_POSTGRES_URL)" || \
(echo "PLATFORM_TEST_POSTGRES_URL is required" >&2; exit 2)
$(PYTHON) -m unittest tests.test_hosted_managed_operation_postgres

hosted-managed-compose-smoke:
$(PYTHON) scripts/hosted_managed_compose_smoke.py

hosted-managed-production-compose-smoke:
$(PYTHON) scripts/hosted_managed_production_compose_smoke.py
15 changes: 15 additions & 0 deletions deploy/hosted-managed/Caddyfile
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
{
admin off
auto_https off
}

:8443 {
tls /run/secrets/managed_operation_tls_certificate /run/secrets/managed_operation_tls_private_key
encode zstd gzip
header -Server
reverse_proxy managed-operation-service:8091
log {
output stdout
format json
}
}
18 changes: 18 additions & 0 deletions deploy/hosted-managed/production.env.example
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
# Non-secret deployment inputs only. Keep secret values in the referenced files.
PLATFORM_MANAGED_OPERATION_IMAGE=ghcr.io/0al-spec/platform@sha256:<digest>
PLATFORM_MANAGED_OPERATION_POSTGRES_IMAGE=postgres@sha256:<digest>
PLATFORM_MANAGED_OPERATION_INGRESS_IMAGE=ghcr.io/0al-spec/platform-hosted-managed-ingress@sha256:<digest>
PLATFORM_MANAGED_OPERATION_ALLOWLIST=review_status_execute

PLATFORM_MANAGED_OPERATION_ARTIFACT_ROOT=/srv/0al/specgraph
PLATFORM_MANAGED_OPERATION_STATE_DIR=/srv/0al/specspace-state
PLATFORM_MANAGED_OPERATION_BACKUP_ROOT=/srv/0al/backups
PLATFORM_MANAGED_OPERATION_INGRESS_BIND_IP=0.0.0.0
PLATFORM_MANAGED_OPERATION_INGRESS_PORT=443

PLATFORM_MANAGED_OPERATION_TOKEN_FILE=/srv/0al/secrets/service-token
PLATFORM_MANAGED_OPERATION_DB_PASSWORD_FILE=/srv/0al/secrets/database-password
PLATFORM_MANAGED_OPERATION_DATABASE_URL_FILE=/srv/0al/secrets/database-url
PLATFORM_MANAGED_OPERATION_GITHUB_TOKEN_FILE=/srv/0al/secrets/github-token
PLATFORM_MANAGED_OPERATION_TLS_CERTIFICATE_FILE=/srv/0al/secrets/tls-certificate.pem
PLATFORM_MANAGED_OPERATION_TLS_PRIVATE_KEY_FILE=/srv/0al/secrets/tls-private-key.pem
221 changes: 221 additions & 0 deletions docker-compose.hosted-managed-production.example.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,221 @@
services:
managed-operation-postgres:
image: "${PLATFORM_MANAGED_OPERATION_POSTGRES_IMAGE:?set a digest-pinned PostgreSQL image}"
restart: unless-stopped
environment:
POSTGRES_DB: "${PLATFORM_MANAGED_OPERATION_DB:-managed_operations}"
POSTGRES_USER: "${PLATFORM_MANAGED_OPERATION_DB_USER:-managed_operations}"
POSTGRES_PASSWORD_FILE: /run/secrets/managed_operation_db_password
volumes:
- managed-operation-postgres:/var/lib/postgresql/data
secrets:
- managed_operation_db_password
networks:
- managed-backend
healthcheck:
test:
- CMD-SHELL
- >-
pg_isready -U "$${POSTGRES_USER}" -d "$${POSTGRES_DB}"
interval: 5s
timeout: 5s
retries: 12

managed-operation-service:
image: "${PLATFORM_MANAGED_OPERATION_IMAGE:?set a digest-pinned Platform image}"
restart: unless-stopped
command:
- python3
- scripts/platform.py
- managed-operation
- serve
- --queue-adapter
- postgresql
- --database-url-file
- /run/secrets/managed_operation_database_url
- --artifact-root
- /workspace/SpecGraph
- --state-dir
- /data/specspace-state
- --specgraph-dir
- /workspace/SpecGraph
- --host
- 0.0.0.0
- --port
- "8091"
- --auth-token-file
- /run/secrets/managed_operation_token
expose:
- "8091"
read_only: true
tmpfs:
- /tmp
cap_drop:
- ALL
security_opt:
- no-new-privileges:true
volumes:
- "${PLATFORM_MANAGED_OPERATION_ARTIFACT_ROOT:?set PLATFORM_MANAGED_OPERATION_ARTIFACT_ROOT}:/workspace/SpecGraph:ro"
- "${PLATFORM_MANAGED_OPERATION_STATE_DIR:?set PLATFORM_MANAGED_OPERATION_STATE_DIR}:/data/specspace-state:ro"
secrets:
- managed_operation_token
- managed_operation_database_url
environment:
PLATFORM_MANAGED_OPERATION_ALLOWLIST: "${PLATFORM_MANAGED_OPERATION_ALLOWLIST:?set PLATFORM_MANAGED_OPERATION_ALLOWLIST}"
networks:
- managed-backend
depends_on:
managed-operation-postgres:
condition: service_healthy
healthcheck:
test:
- CMD-SHELL
- >-
python3 -c "import urllib.request;
urllib.request.urlopen('http://127.0.0.1:8091/v1/health', timeout=3).read()"
interval: 10s
timeout: 5s
retries: 6

managed-operation-worker:
image: "${PLATFORM_MANAGED_OPERATION_IMAGE:?set a digest-pinned Platform image}"
restart: unless-stopped
entrypoint:
- /bin/sh
- -ec
command:
- >-
export GH_TOKEN="$$(cat /run/secrets/managed_operation_github_token)";
exec python3 scripts/platform.py managed-operation worker
--queue-adapter postgresql
--database-url-file /run/secrets/managed_operation_database_url
--artifact-root /workspace/SpecGraph
--state-dir /data/specspace-state
--specgraph-dir /workspace/SpecGraph
--worker-id "$${HOSTNAME}"
--health-file /tmp/managed-operation-worker-health.json
read_only: true
tmpfs:
- /tmp
cap_drop:
- ALL
security_opt:
- no-new-privileges:true
volumes:
- "${PLATFORM_MANAGED_OPERATION_ARTIFACT_ROOT:?set PLATFORM_MANAGED_OPERATION_ARTIFACT_ROOT}:/workspace/SpecGraph"
- "${PLATFORM_MANAGED_OPERATION_STATE_DIR:?set PLATFORM_MANAGED_OPERATION_STATE_DIR}:/data/specspace-state:ro"
secrets:
- managed_operation_database_url
- managed_operation_github_token
environment:
PLATFORM_MANAGED_OPERATION_ALLOWLIST: "${PLATFORM_MANAGED_OPERATION_ALLOWLIST:?set PLATFORM_MANAGED_OPERATION_ALLOWLIST}"
networks:
- managed-backend
- managed-egress
depends_on:
managed-operation-postgres:
condition: service_healthy
healthcheck:
test:
- CMD-SHELL
- >-
python3 -c "import json,pathlib,time;
p=pathlib.Path('/tmp/managed-operation-worker-health.json');
d=json.loads(p.read_text());
raise SystemExit(0 if d.get('ok') is True and time.time()-p.stat().st_mtime < 15 else 1)"
interval: 10s
timeout: 5s
retries: 6
start_period: 30s

managed-operation-ingress:
image: "${PLATFORM_MANAGED_OPERATION_INGRESS_IMAGE:?set a digest-pinned Caddy image}"
restart: unless-stopped
user: "1000:1000"
read_only: true
tmpfs:
- /tmp
- /data
- /config
cap_drop:
- ALL
security_opt:
- no-new-privileges:true
command:
- run
- --config
- /etc/caddy/Caddyfile
- --adapter
- caddyfile
ports:
- "${PLATFORM_MANAGED_OPERATION_INGRESS_BIND_IP:-0.0.0.0}:${PLATFORM_MANAGED_OPERATION_INGRESS_PORT:-443}:8443"
volumes:
- ./deploy/hosted-managed/Caddyfile:/etc/caddy/Caddyfile:ro
secrets:
- managed_operation_tls_certificate
- managed_operation_tls_private_key
networks:
- managed-backend
- managed-ingress
depends_on:
managed-operation-service:
condition: service_healthy
healthcheck:
test:
- CMD-SHELL
- >-
wget --quiet --no-check-certificate --output-document=-
https://127.0.0.1:8443/v1/health >/dev/null
interval: 10s
timeout: 5s
retries: 6

managed-operation-maintenance:
image: "${PLATFORM_MANAGED_OPERATION_IMAGE:?set a digest-pinned Platform image}"
profiles:
- maintenance
user: "1000:1000"
read_only: true
tmpfs:
- /tmp
cap_drop:
- ALL
security_opt:
- no-new-privileges:true
command:
- python3
- scripts/hosted_managed_runtime_backup.py
- --help
volumes:
- "${PLATFORM_MANAGED_OPERATION_ARTIFACT_ROOT:?set PLATFORM_MANAGED_OPERATION_ARTIFACT_ROOT}:/workspace/SpecGraph:ro"
- "${PLATFORM_MANAGED_OPERATION_BACKUP_ROOT:?set PLATFORM_MANAGED_OPERATION_BACKUP_ROOT}:/backups"
secrets:
- managed_operation_database_url
networks:
- managed-backend
depends_on:
managed-operation-postgres:
condition: service_healthy

volumes:
managed-operation-postgres:

networks:
managed-backend:
internal: true
managed-egress: {}
managed-ingress: {}

secrets:
managed_operation_token:
file: "${PLATFORM_MANAGED_OPERATION_TOKEN_FILE:?set PLATFORM_MANAGED_OPERATION_TOKEN_FILE}"
managed_operation_db_password:
file: "${PLATFORM_MANAGED_OPERATION_DB_PASSWORD_FILE:?set PLATFORM_MANAGED_OPERATION_DB_PASSWORD_FILE}"
managed_operation_database_url:
file: "${PLATFORM_MANAGED_OPERATION_DATABASE_URL_FILE:?set PLATFORM_MANAGED_OPERATION_DATABASE_URL_FILE}"
managed_operation_github_token:
file: "${PLATFORM_MANAGED_OPERATION_GITHUB_TOKEN_FILE:?set PLATFORM_MANAGED_OPERATION_GITHUB_TOKEN_FILE}"
managed_operation_tls_certificate:
file: "${PLATFORM_MANAGED_OPERATION_TLS_CERTIFICATE_FILE:?set PLATFORM_MANAGED_OPERATION_TLS_CERTIFICATE_FILE}"
managed_operation_tls_private_key:
file: "${PLATFORM_MANAGED_OPERATION_TLS_PRIVATE_KEY_FILE:?set PLATFORM_MANAGED_OPERATION_TLS_PRIVATE_KEY_FILE}"
22 changes: 22 additions & 0 deletions docs/deployment.md
Original file line number Diff line number Diff line change
Expand Up @@ -175,6 +175,28 @@ ambiguous consume-on-attempt or irreversible leases are quarantined. It fails
if a receipt contradicts the operation registry, so a deployment cannot hide a
replay-policy drift behind a green recovery command.

### TLS-fronted production profile

The standalone VM profile proves the container boundary but publishes only on
host loopback. Use `docker-compose.hosted-managed-production.example.yml` for a
remote production SpecSpace connection. This separate profile adds a
digest-pinned Caddy ingress and keeps PostgreSQL and the Platform HTTP service
on an internal Docker network. Only the worker receives an egress network for
read-only GitHub review inspection. The Platform service has no direct host
port.

Production rollout and sign-off are documented in
[`hosted-managed-operations.md`](hosted-managed-operations.md#production-rollout-and-sign-off).
The production contract is checked in CI with:

```bash
make hosted-managed-production-contract
```

It fails when images are mutable, the allowlist is absent, the Platform service
publishes a direct port, TLS ingress is missing, maintenance tooling is enabled
by default, or service/worker network authority expands.

## Local Compose Entry Point

The working plan for this phase is maintained in
Expand Down
Loading