Porting · porting/2026-05-05-hetzner-runtime-config.md Docs Home

Hetzner Runtime Config

Date: 2026-05-05

Objective

Make the first runtime config setup on Hetzner repeatable without copying commands by hand and without risking checked-in secrets.

The helper creates real, ignored runtime config files from the checked-in templates. It does not fill in secrets. A human still replaces placeholders with real staging values on the Hetzner host.

What Was Added

  • scripts/hetzner-config.mjs Safe runtime config initializer for Hetzner.
  • package.json scripts:
    • hetzner:config:plan
    • hetzner:config:status
    • hetzner:config:checklist
    • hetzner:config:init:dry
    • hetzner:config:init

Files Managed

The helper can create these ignored runtime files when they are missing:

  • configs/hetzner/studio-api.env
  • configs/hetzner/zweistein-server.env
  • configs/hetzner/zweistein-query-engine.env
  • configs/hetzner/zweistein-ingestion-worker.env
  • configs/hetzner/frontend-build.env
  • configs/hetzner/reverse-proxy.env
  • configs/hetzner/backup.env
  • configs/hetzner/Caddyfile

The .env files are created with mode 0600. The Caddyfile is created with mode 0644.

configs/hetzner/frontend-build.env is intentionally a Compose-level file, not only a frontend file. It also carries the first infrastructure values that Docker Compose needs before services start:

  • POSTGRES_PASSWORD
  • MINIO_ROOT_USER
  • MINIO_ROOT_PASSWORD

configs/hetzner/backup.env also points backup and restore commands at that same Compose env file through:

COMPOSE_ENV_FILE=configs/hetzner/frontend-build.env

That keeps backup and restore rehearsals in the same Compose variable context as the normal stack commands.

Safety Rules

  • Existing runtime config files are skipped, not overwritten.
  • No config values or secrets are printed.
  • Real configs/hetzner/*.env files stay ignored by .gitignore.
  • configs/hetzner/Caddyfile stays ignored by .gitignore.
  • The helper creates placeholder copies only; npm run hetzner:env:check is still the strict gate before builds.

Server Workflow

Run this on the Hetzner host after the source tree has been synced:

npm run hetzner:config:plan
npm run hetzner:config:status
npm run hetzner:config:checklist
npm run hetzner:config:init:dry
npm run hetzner:config:init

Then edit the newly created runtime files on the server and replace every placeholder with real staging values.

After editing:

npm run hetzner:env:check

That strict check fails if a required runtime file is missing, if an obvious placeholder remains, or if a required key is empty. It prints only file names, key names, and line numbers.

Runtime Value Checklist

The checklist command is a safe preparation step before editing real server files:

npm run hetzner:config:checklist

It reads only the checked-in .example templates and prints:

  • required keys that must be filled before hetzner:env:check can pass;
  • other placeholders that must be replaced before hetzner:env:check;
  • staging defaults that should be reviewed;
  • optional blank keys;
  • the target runtime file and file mode for each template.

It does not read configs/hetzner/*.env, does not print runtime values, and does not create files.

Local Verification Evidence

  • node --check scripts/hetzner-config.mjs passed.
  • npm run hetzner:config:plan prints the runtime config workflow.
  • npm run hetzner:config:status reports the expected missing runtime files locally without printing values.
  • npm run hetzner:config:checklist prints the server value checklist from templates only.
  • npm run hetzner:config:init:dry shows the files it would create and writes nothing.
  • npm run legacy:verify includes scripts/hetzner-config.mjs as a required foundation file.

Current Boundary

The actual npm run hetzner:config:init command should be run on Hetzner, not on this local Mac, because the real runtime config files are server-local and intentionally ignored.