trails/openspec/changes/relocate-brouter-to-dedicated-host/specs/infrastructure/spec.md
Ullrich Schäfer 102a744e67
Add Hetzner vSwitch network for BRouter relocation
Adds an OpenSpec change scoping the relocation of BRouter from the
co-located flagship (cx23, 4 GB RAM, 40 GB SSD, Europe segments only) to
a dedicated Hetzner Robot host in the same datacenter, with private
connectivity over Hetzner vSwitch #80672 (VLAN 4000).

This first PR only lays the network prerequisite:

- Terraform: a Hetzner Cloud Network (10.0.0.0/16) with a cloud subnet
  (10.0.0.0/24) hosting the flagship at 10.0.0.2, and a vSwitch subnet
  (10.0.1.0/24) bridged to Robot VLAN 4000. The dedicated host's VLAN
  sub-interface (10.0.1.10 on enp4s0.4000) is configured out-of-band via
  netplan and is not Terraform-managed.
- lifecycle { ignore_changes = [user_data] } on the flagship server to
  prevent the Hetzner provider's post-1.45 user_data hash drift from
  triggering a spurious full-server replacement on unrelated applies.
- OpenSpec change with proposal, design, specs (delta for
  brouter-integration / infrastructure / observability /
  security-hardening), and tasks; Section 1 (pre-flight) is checked off
  with operator notes.

Verification: ping both directions across the vSwitch is 0% loss,
sub-ms latency; dedicated host's VLAN config persists across reboot
(verified ~60 s to restore private reachability).

Follow-up PRs will land the BRouter host compose project, Planner
shared-secret header, CD workflow retarget, observability wiring, and
the cutover.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-04-23 22:43:57 +02:00

3.6 KiB

MODIFIED Requirements

Requirement: BRouter segment management

The infrastructure SHALL support downloading and updating planet-wide RD5 segments from brouter.de to the dedicated BRouter host. Segment files SHALL live under ~trails/brouter/segments/ on the dedicated host and SHALL be owned by the trails user.

Scenario: Download segments

  • WHEN the segment download script runs on the dedicated host as the trails user
  • THEN all planet-wide RD5 files referenced by the tile list are downloaded to ~trails/brouter/segments/, skipping files that already exist

Scenario: Segment update

  • WHEN an operator re-runs the segment download script
  • THEN outdated or missing RD5 files are re-fetched from brouter.de and the BRouter container is restarted

Requirement: CI/CD pipeline

GitHub Actions SHALL use separate workflows for app deployment, infrastructure deployment, and BRouter deployment, with secrets decrypted from a SOPS-encrypted file.

Scenario: App deployment

  • WHEN code changes are pushed to main in apps/ or packages/
  • THEN the cd-apps workflow builds Docker images, pushes to ghcr.io, and deploys app containers to the flagship host

Scenario: Infrastructure deployment

  • WHEN changes are pushed to main in infrastructure/
  • THEN the cd-infra workflow copies configs and restarts infrastructure services on the flagship host without rebuilding app images and without touching the BRouter host

Scenario: BRouter deployment

  • WHEN changes are pushed to main in docker/brouter/ or the BRouter host compose config
  • THEN the cd-brouter workflow SSHes as the trails user into the dedicated BRouter host using BROUTER_DEPLOY_HOST / BROUTER_DEPLOY_SSH_KEY and runs docker compose up -d in ~trails/brouter/

Scenario: Secret decryption at deploy time

  • WHEN any CD workflow runs
  • THEN the SOPS-encrypted secrets file is decrypted and provided to docker-compose as an env file

Scenario: Gitleaks scan

  • WHEN a PR is opened
  • THEN gitleaks scans for committed secrets

Scenario: Dependency audit

  • WHEN CI runs
  • THEN pnpm audit checks for high/critical vulnerabilities

ADDED Requirements

Requirement: Private network between flagship and BRouter hosts

The flagship host and the dedicated BRouter host SHALL be joined on a Hetzner vSwitch in the same datacenter. All traffic between Planner and BRouter SHALL traverse this private network.

Scenario: vSwitch reachability

  • WHEN the flagship host issues a request to the BRouter host's vSwitch IP on the BRouter service port with a valid X-BRouter-Auth header
  • THEN the request succeeds over the private network without traversing the public internet

Scenario: No public BRouter exposure

  • WHEN Hetzner Cloud firewall rules or equivalent host firewall rules are inspected
  • THEN no rule allows inbound traffic to the BRouter service port from any public IP

Requirement: Non-root deploy user on the BRouter host

The BRouter host SHALL be administered by the trails.cool project through a non-root trails user that is a member of the docker group. The CD workflow SHALL NOT require sudo or root SSH access on this host.

Scenario: Deploy with trails user

  • WHEN the cd-brouter workflow connects to the BRouter host
  • THEN it authenticates as trails and successfully runs docker compose commands without invoking sudo

Scenario: Scoped ownership

  • WHEN files are created by the deploy or segment-download scripts
  • THEN they live under ~trails/brouter/ and are owned by trails:trails