Files
govoplan/docs/INSTALLATION_AND_DEPLOYMENT_ARCHITECTURE.md
Albrecht Degering 82e836b720
Some checks are pending
Dependency Audit / dependency-audit (push) Waiting to run
Deployment Installer / deployment-installer (push) Waiting to run
Security Audit / security-audit (push) Waiting to run
feat(deploy): add declarative installation workflow
2026-07-30 15:36:37 +02:00

11 KiB

Installation And Deployment Architecture

Goal

A supported GovOPlaN installation starts with one downloaded, verified bootstrap artifact. The administrator answers a bounded set of questions and receives a working base system. Re-running the same tool repairs or reconfigures that installation instead of creating unrelated state.

The canonical product journey remains System Administrator Lifecycle User Story. This document defines the deployer boundary and the first executable slice.

First Executable Slice

tools/deployment/govoplan-deploy.py is a standard-library-only deployment compiler and reconciler. It can be tested without installing GovOPlaN itself.

It currently supports:

  • evaluation and self-hosted profiles;
  • managed or external PostgreSQL;
  • managed, external, or evaluation-only disabled Redis;
  • disabled mail, an external relay declaration, or an evaluation-only GreenMail service;
  • durable local file storage or external S3-compatible storage;
  • Core, base, or full initial module selections;
  • deterministic Compose JSON accepted by Compose v2;
  • generated secrets stored in a private 0600 file;
  • service-specific environment allowlists so infrastructure containers do not receive unrelated application credentials;
  • plan, render, doctor, status, and apply commands;
  • an installation lock, migration-before-start ordering, readiness polling, and an applied-state receipt;
  • idempotent reconfiguration that preserves generated secrets;
  • a keyed environment fingerprint that detects private binding changes without writing secret values to plans or receipts;
  • host CPU, memory, disk, entropy, architecture, Docker daemon, Compose, listen-port, and external endpoint preflight checks;
  • service removal without implicit data-volume deletion.

Create a local evaluation bundle:

./.venv/bin/python tools/deployment/govoplan-deploy.py init \
  --directory /tmp/govoplan-evaluation \
  --profile evaluation \
  --postgres managed \
  --redis managed \
  --mail test-mail \
  --module-set base

Inspect the generated intent and host requirements:

./.venv/bin/python tools/deployment/govoplan-deploy.py doctor \
  --directory /tmp/govoplan-evaluation

Change a component without rotating existing generated secrets:

./.venv/bin/python tools/deployment/govoplan-deploy.py configure \
  --directory /tmp/govoplan-evaluation \
  --redis external \
  --redis-url 'rediss://:password@redis.example.org:6379/0'

The private installation directory contains:

File Purpose
installation.json Versioned, non-secret desired state
secrets.env Deployment-local secrets and external service bindings
compose.json Deterministic generated Compose definition
plan.json Latest desired-state diff and readiness findings
receipt.json Last successfully applied immutable identities
.deployment.lock Same-host operation exclusion

The specification contract is installation-spec.schema.json.

Build the same dependency-free tool as one downloadable artifact:

./.venv/bin/python tools/deployment/build-deployer-zipapp.py \
  --output /tmp/govoplan-deploy.pyz
python /tmp/govoplan-deploy.pyz --help

To exercise reconciliation with locally available evaluation images:

python /tmp/govoplan-deploy.pyz init \
  --non-interactive \
  --directory /tmp/govoplan-evaluation \
  --profile evaluation \
  --api-image local/govoplan-api:test \
  --web-image local/govoplan-web:test
python /tmp/govoplan-deploy.pyz apply \
  --directory /tmp/govoplan-evaluation \
  --allow-unverified-images \
  --skip-pull

Those images must already contain the selected module set. The override exists only to exercise local orchestration before release artifacts exist; it is rejected for self-hosted.

Current Production Gates

The tool deliberately reports blockers instead of pretending the source tree is a production distribution:

  1. OCI release artifacts. The release pipeline does not yet publish pinned multi-architecture API and WebUI images.
  2. Signed distribution manifest. A channel manifest must bind exact image digests, Compose compatibility, SBOM/provenance references, and revocation state. Recording a URL and checksum is not signature verification.
  3. First administrator. Production needs a one-time, restricted enrollment identity. The development bootstrap must not be enabled in production.
  4. Image/module composition. The selected module set must be proven present in the exact image or installed from verified offline artifacts before it is enabled.
  5. Deployment agent. Web updates need a separate privileged reconciler with a typed command allowlist. The API and browser must never receive the Docker socket or arbitrary shell access.
  6. Ingress and certificates. A self-hosted profile needs an explicit choice between an existing reverse proxy and a supported managed ingress, including trusted-proxy boundaries, TLS certificate issuance, renewal, and health probing through the public route.

apply --allow-unverified-images is therefore restricted to the evaluation profile. It explicitly acknowledges both mutable image identities and unverified image/module composition. It is a local test escape hatch, not a production setting.

Component Choices

PostgreSQL

managed creates a persistent PostgreSQL container and private generated credentials. external requires an explicit DATABASE_URL; switching from managed to external cannot reuse the old postgres Docker hostname accidentally.

Interactive entry hides external URLs because they commonly contain credentials. For unattended automation, provide them through a protected operator mechanism and avoid storing secret-bearing flags in shell history.

Production policy should support external managed databases and local managed PostgreSQL equally at the application boundary. Backup, point-in-time recovery, high availability, and major-version upgrades remain deployment properties.

Redis

managed creates an authenticated, append-only Redis container. external requires an explicit REDIS_URL. disabled is evaluation-only and disables workers while recording the single-process login-throttle risk acknowledgement.

doctor performs a bounded TCP connection check for external PostgreSQL, Redis, and S3 endpoints. This verifies DNS, routing, and that the port accepts a connection; it is not an authentication or semantic health check.

Production base installations include Redis because durable queues, distributed throttling, notifications, scheduled work, and transactional event delivery must survive API restarts.

Mail

The first slice distinguishes:

  • disabled;
  • external-relay, which records the infrastructure decision but leaves Mail server/credential creation as a visible post-install task;
  • test-mail, an evaluation-only GreenMail service.

A bundled production mail server is intentionally not a default. Operating one requires DNS, reverse DNS, TLS, DKIM, SPF, DMARC, reputation, abuse handling, queue monitoring, and upgrade policy. A later profile may support an operator-selected MTA/relay, but it must expose these requirements rather than presenting a container as a complete mail service.

File Storage

local uses a durable Compose volume and is appropriate for one-host installations. s3 requires endpoint, region, access key, secret key, and bucket values. Self-hosted S3 endpoints must use HTTPS.

Local storage must be included in backup and restore drills. Horizontal API or worker scale-out requires shared/object storage.

Reconfiguration Semantics

installation.json is desired state. receipt.json is the last successfully applied state. plan compares their canonical hashes and service sets.

  • Adding a managed component creates its service and persistent volume.
  • Removing a component removes its service container on apply.
  • Volumes are retained by default; deleting data requires a separate, deliberately destructive workflow.
  • Existing generated credentials are retained unless an explicit future rotate operation is requested.
  • Private configuration changes are represented by a keyed fingerprint in the plan and receipt; plaintext values are never copied there.
  • Managed-to-external transitions require the new endpoint in the same operation.
  • Migrations run as a one-shot service before API/worker replacement.
  • Health must recover before a new receipt is committed.

This is sufficient for one-host reconciliation. Production updates additionally need backup/restore gates, maintenance/drain state, database compatibility windows, image signature verification, and rollback/forward-recovery policy.

Web Update Boundary

The intended update path is:

  1. Ops reads the non-secret installation receipt and reports management mode, current release, component health, and update availability.
  2. An authorized administrator asks Core to create a typed deployment request, for example reconcile_release or rollback_release.
  3. Core persists the reviewed immutable plan, actor, expected current receipt, and idempotency key.
  4. A separately deployed, narrow deployment agent claims the request.
  5. The agent verifies signatures/digests, acquires a fenced deployment lock, backs up, pulls, migrates, reconciles, probes health, and writes evidence.
  6. Ops presents durable progress and the resulting receipt.

The agent owns container-runtime access. It accepts no command strings from the browser and has no domain-data permissions. Installations managed by Kubernetes, systemd, or another external orchestrator expose read-only status and an export of the reviewed update recipe instead of a non-functional update button.

Distribution Workflow

The downloadable entry point should eventually be:

curl --proto '=https' --tlsv1.2 --fail --location \
  https://govoplan.add-ideas.de/install/v1/bootstrap.pyz \
  --output govoplan-bootstrap.pyz
python3 govoplan-bootstrap.pyz init

The published documentation must include an independent checksum/signature verification command before execution. The zipapp then downloads only a signed distribution manifest, verifies it against an embedded or explicitly installed keyring, and renders the same installation contract implemented here.

The source-tree script is the test harness for that future zipapp. It is not yet the internet bootstrap artifact.

Verification

Run the focused tests:

./.venv/bin/python -m unittest -v tests.test_deployment_installer

The tests cover profile restrictions, secret persistence, external endpoint requirements, S3 policy, Compose service selection, secret non-disclosure, service-specific environment isolation, private file modes, external endpoint preflight, first-plan generation, apply ordering, and receipt-based idempotency.