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>
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
trailsuser - 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
trailsuser into the dedicated BRouter host usingBROUTER_DEPLOY_HOST/BROUTER_DEPLOY_SSH_KEYand runsdocker compose up -din~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-Authheader - 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
trailsand successfully runsdocker composecommands 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 bytrails:trails