Wire Forge-native release infrastructure
Some checks failed
Cache: Publish Nix / Publish Nix Cache (push) Waiting to run
Build Rust / Cargo Test (push) Successful in 4m14s
Build Site / Next.js Build (push) Failing after 6s
Infra: OpenTofu / OpenTofu (grafana) (push) Successful in 5s
Lint Governance / BEP Metadata (push) Successful in 0s
Build: Android / Android Rust Core Stub (push) Failing after 23s

This commit is contained in:
Conrad Kramer 2026-06-07 05:51:12 -07:00
parent 97c569fb35
commit 002bd382e9
199 changed files with 14268 additions and 185 deletions

View file

@ -0,0 +1,230 @@
# `BEP-0009` - Release Infrastructure and Store Upload Pipeline
```text
Status: Draft
Proposal: BEP-0009
Authors: gpt-5.5
Coordinator: gpt-5.5
Reviewers: Pending
Constitution Sections: II, III, IV, V
Implementation PRs: Pending
Decision Date: Pending
```
## Summary
Burrow needs a release pipeline that behaves like the project infrastructure it is replacing: release eligibility is detected from forge build tags, build artifacts are produced per platform, upload lanes are separate from build lanes, and store credentials are explicit automation inputs rather than hidden local state.
The first implementation creates the release spine now: `release-if-needed` dispatches a build when HEAD has not been tagged as released, `build-release` packages current artifacts on Namespace-backed runners, and `publish-store-uploads` performs guarded upload steps for App Store Connect, Sparkle, Microsoft Store, and storage-backed public channels after Authentik-backed Google WIF authentication.
The same change also makes the forge itself the release authority: release tags prefer the Burrow Forgejo remote, dispatch helpers talk to Forgejo directly with a live-host fallback, runner identity is discoverable from persistent host state, repository mirror sync is disabled for the canonical Burrow repository, `nix.burrow.net` provides a Burrow-owned Nix cache for workers, and Grafana observability is bootstrapped with Nix-owned Prometheus/OpenTelemetry/Jaeger service state plus OpenTofu-owned dashboards.
## Motivation
- Burrow already controls its forge and runner infrastructure, but the release path only produced a Linux CLI tarball.
- Apple, Linux, Sparkle, and future Windows outputs need the same build-number and artifact layout so release evidence is inspectable.
- The Namespace account should be part of the live runner surface, including macOS and Windows lanes, instead of sitting idle behind unused configuration.
- Authentication and store credentials should be named and checked at lane boundaries so dedicated accounts can be added without rewriting workflows.
## Detailed Design
- Use lightweight `builds/<number>` tags as the authoritative release watermark.
- Keep `Scripts/version.sh status` as the release-needed check:
- `clean` means HEAD is exactly the latest authoritative build tag.
- `dirty` means the release workflow should create a new build number.
- Split workflows:
- `.forgejo/workflows/release-if-needed.yml` checks release status and dispatches `build-release.yml`.
- `.forgejo/workflows/build-release.yml` builds and stages artifacts.
- `.forgejo/workflows/publish-store-uploads.yml` uploads staged artifacts to external stores and public channels.
- Stage artifacts under `dist/builds/<build-number>/<platform>/`.
- Publish generic release assets to a Forgejo release named `Build <number>`.
- Upload release artifacts through `Scripts/ci/upload-release-storage.sh`. The wrapper requires Garage as the primary S3-compatible target by default, then mirrors to the `burrow-net-releases` GCS backup bucket unless `BURROW_RELEASE_GCS_BACKUP=false`.
- Publish signed package repository trees through `Scripts/ci/upload-package-storage.sh`. The wrapper requires Garage as the primary S3-compatible target by default, then mirrors to the `burrow-net-packages` GCS backup bucket unless `BURROW_PACKAGE_GCS_BACKUP=false`.
- Publish selected Nix closures through `.forgejo/workflows/publish-nix-cache.yml` and `Scripts/ci/publish-nix-cache.sh`. The workflow logs into `nix.burrow.net` with the scoped `BURROW_NIX_CACHE_PUSH_TOKEN` generated by host bootstrap and skips when the token is absent unless explicitly required.
- Mirror Garage buckets through `.forgejo/workflows/backup-garage-storage.yml` and `Scripts/ci/backup-garage-to-gcs.sh`. The scheduled workflow uses the host-generated Garage read-only backup key, Authentik-backed Google WIF, and `gcloud storage rsync` to back up `burrow-releases`, `burrow-packages`, and `attic` to GCS.
- Build Apple artifacts on `namespace-profile-macos-large` through Bazel wrapper targets:
- `//bazel/apple:release_ios_stamp`
- `//bazel/apple:release_macos_stamp`
These targets wrap the existing Xcode project and give the workflow one stable entrypoint that shares the same Bazel/Nix cache setup while Burrow decides whether deeper `rules_apple` targets are worth the migration.
- Use Namespace cache setup before Bazel/Nix work. Prefer the Namespace Bazel remote cache when `nsc` is authenticated and fall back to a local disk/repository cache when remote setup is unavailable.
- Produce unsigned Apple validation artifacts when signing is absent, but require signing for an App Store requested iOS release. Uploadable artifacts are produced only when signing assets are installed and provisioning profiles can be synced from App Store Connect credentials.
- Resolve release credentials through age/agenix before signed Apple lanes run. Forgejo secrets should carry only the runner age identity fallback and the Authentik WIF client secret until OpenBao can mint the runner token directly. App Store Connect keys and signing certificates live as sealed files under `secrets/`.
- Persist the forge runner SSH key as `/var/lib/forgejo-runner-agent/age_keystore` and export `BURROW_RUNNER_AGE_IDENTITY_PATH` so jobs can resolve agenix identities without embedding long-lived material in every workflow.
- Add `Scripts/forgejo-dispatch.sh` and `Scripts/forgejo-dispatch-via-host.sh`:
- local dispatch uses an explicit token, local intake token, or the sealed forgejo-nsc dispatcher config when available;
- host fallback SSHes to the live forge and reads the agenix-materialized dispatcher config there;
- neither path relies on a GitHub remote or GitHub workflow API.
- Default build tag reads and pushes to the Burrow Forgejo remote when that remote exists. `BURROW_VERSION_REMOTE` remains the override for unusual migrations.
- Keep the canonical Forgejo repo as an ordinary source repository, not a mirror. The host config disables new mirror creation and removes mirror/push-mirror rows for the canonical Burrow repository at activation time.
- Add `.forgejo/workflows/infra-opentofu.yml` and `Scripts/grafana-tofu.sh` for Grafana API-managed folders and dashboards. NixOS owns the Grafana service, SSO, reverse proxy, datasources, Prometheus, OpenTelemetry collector, Jaeger, secrets, and boot-time configuration; OpenTofu owns API-created dashboard objects.
- Sync provisioning profiles from App Store Connect in CI using the decrypted API key:
- iOS App Store profiles for `com.hackclub.burrow` and `com.hackclub.burrow.network`.
- macOS Developer ID profiles for the same bundle identifiers.
Provisioning profile `.age` files are not part of the normal release path.
- Add a non-exportable Google KMS RSA-2048/SHA-256 key named
`apple-developer-id-application` in the Burrow identity key ring. The manual
`apple-developer-id-kms-csr.yml` workflow uses Authentik-backed Google WIF to
generate a standard Developer ID Application CSR without exporting private key
material.
- Treat Developer ID certificate issuance as a Developer website or Xcode step
until Apple documents App Store Connect API creation for Developer ID
certificates. The returned `.cer` is public release material; the private key
remains in Google KMS and must not be converted into `.p12` form.
- The first KMS-backed G2 Developer ID Application certificate is
`9JKN6HXBHC`, stored at
`Apple/Certificates/developer-id-application-9JKN6HXBHC.cer`, and expires on
`2031-06-08`.
- Add a non-exportable Google KMS RSA-2048/SHA-256 key named
`apple-ios-distribution` in the Burrow identity key ring. The KMS-backed CSR
can be submitted through the App Store Connect API with
`Scripts/apple/create-asc-certificate.mjs`.
- The first KMS-backed iOS Distribution certificate is `3G42677598`, stored at
`Apple/Certificates/ios-distribution-3G42677598.cer`, and expires on
`2027-06-07`.
- When `BURROW_IOS_DISTRIBUTION_CERTIFICATE_ID` is set, provisioning-profile
sync must verify App Store profiles reference that certificate. Stale profiles
with the same name may be deleted and recreated so CI does not silently sign
against an older distribution identity.
- Add a non-exportable Google KMS key named `sparkle-ed25519` for Sparkle
appcast signatures. It uses `EC_SIGN_ED25519` with software protection level
because Google KMS rejects Ed25519 for HSM protection. macOS builds embed
public EdDSA key `Myv9ZNZT6YGKMtMezh52ra4WqaeEKc4VlvVU0evhJeI=`.
- Use Namespace labels for platform lanes:
- `namespace-profile-linux-medium`
- `namespace-profile-macos-large`
- `namespace-profile-windows-large`
- Add a Rust `burrow-windows-stub` binary so the Windows lane can validate packaging and upload mechanics before native Windows networking support is implemented.
- Treat Sparkle as a macOS public-channel upload lane:
- Generate staged appcast content from the signed macOS artifact when present.
- Publish `sparkle/<channel>/`, `sparkle/appcast.xml`, and `sparkle/default/` pointers from `publish-store-uploads`.
- Full Sparkle client integration remains a follow-up because the current Apple app does not embed Sparkle.
- Add `infra/releases` as the OpenTofu boundary for the GCS release and package repository backup buckets. Garage is the required S3-compatible primary distribution target after host bootstrap. `infra/identity` remains the boundary for Google KMS signing keys, the Authentik WIF pool/provider, and the runner service account.
## Security and Operational Considerations
- Store credentials are consumed only by signed build or upload jobs, not by ordinary build/test jobs.
- GCS backup and Google KMS access must use Authentik-backed Workload Identity Federation against project `project-88c23ce9-918a-470a-b33`; workflows must not use Google service-account JSON keys.
- Garage uploads use S3-compatible access keys scoped to release/package buckets. They are required by default; GCS is a backup mirror, not the success fallback.
- Garage backup uses a separate read-only key spanning the release, package, and Attic buckets. That key may be sealed into Forgejo/OpenBao for backup jobs, but it must not be reused for release/package writes.
- Attic cache publishing uses a scoped CI push token for the `burrow` cache instead of the bootstrap admin token.
- rclone is not part of the release path. Multi-cloud failover should be added through provider-specific or S3-compatible wrappers with explicit credentials and evidence.
- Required upload credentials are checked explicitly after `./.forgejo/actions/decrypt-release-secrets` materializes them from age:
- `secrets/apple/appstore-connect.env.age`: `ASC_API_KEY_ID`, `ASC_API_ISSUER_ID`, and base64-encoded `.p8` key material.
- `secrets/apple/distribution-signing.env.age`: base64-encoded distribution `.p12` plus its password.
- `secrets/apple/sparkle.env.age`: base64-encoded Sparkle EdDSA private key.
- future Microsoft Partner Center credentials
- The temporary CI keychain password is always the distribution certificate password; there is no separate keychain-password release secret.
- KMS-backed Apple certificates do not satisfy the existing Xcode/keychain
import path. Shipping with these certificates requires a signer that can
delegate Apple code-signing operations to Google KMS.
- If the first key ring or Developer ID key is bootstrapped before the
OpenTofu remote state backend is available locally, the live resources must be
imported into `infra/identity` before enabling managed identity applies.
- Dedicated store accounts should be sealed with agenix before enabling always-on uploads. The runner only needs an age identity such as `/var/lib/forgejo-runner-agent/age_keystore`; `AGE_FORGE_SSH_KEY` is a fallback bootstrap secret, not a place for release material.
- The canonical identity registry reserves `release@burrow.net` for release automation and `namespace@burrow.net` for Namespace runner capacity credentials; secrets stay outside `contributors.nix`.
- Grafana starts with a sealed admin password and a pending OIDC client secret. Local admin login remains available until the real OIDC secret is rotated and the Authentik reconciliation service creates the Grafana provider.
- OpenTofu apply/import modes require an explicit confirmation and remote state. Validation may run without remote state.
- Build tags are pushed only after artifacts have been assembled for the build number.
- External TestFlight distribution and public app-store rollout must stay explicit; internal or private lanes can be defaulted only after credentials and metadata are verified.
## Contributor Playbook
1. Check whether a release is needed:
```bash
Scripts/version.sh status
```
2. Build local artifacts:
```bash
BUILD_NUMBER=local Scripts/ci/build-release-artifacts.sh all
```
3. Build Apple release validation artifacts on macOS:
```bash
BUILD_NUMBER=local bazel build //bazel/apple:release_ios_stamp //bazel/apple:release_macos_stamp
```
4. Verify the Windows stub still builds:
```bash
cargo test --locked -p burrow-windows-stub
```
5. Validate BEP metadata:
```bash
python3 Scripts/check-bep-metadata.py
```
6. Before turning on an upload lane, seal the dedicated service-account secret and run:
```bash
Scripts/seal-release-secrets.sh --source-dir intake/release
Scripts/ci/check-release-config.sh app-store
Scripts/ci/check-release-config.sh apple-signing
Scripts/ci/check-release-config.sh gcs
```
7. Treat successful Forgejo release asset publication and successful store upload jobs as separate evidence points.
8. Validate Grafana OpenTofu syntax locally:
```bash
Scripts/grafana-tofu.sh init -backend=false
Scripts/grafana-tofu.sh validate
```
9. Validate release storage OpenTofu syntax locally:
```bash
Scripts/releases-tofu.sh init -backend=false
Scripts/releases-tofu.sh validate
```
10. Validate release storage wrapper syntax:
```bash
bash -n Scripts/ci/upload-release-storage.sh Scripts/ci/upload-package-storage.sh Scripts/ci/backup-garage-to-gcs.sh Scripts/ci/publish-nix-cache.sh
```
11. Dispatch a Forgejo workflow without GitHub:
```bash
Scripts/forgejo-dispatch.sh build-release.yml --ref main
```
12. After deploying the forge host, seal:
```text
BURROW_NIX_CACHE_PUSH_TOKEN from /var/lib/burrow/nix-cache/ci-push-token
BURROW_GARAGE_BACKUP_ACCESS_KEY_ID from /var/lib/burrow/garage/env
BURROW_GARAGE_BACKUP_SECRET_ACCESS_KEY from /var/lib/burrow/garage/env
```
## Alternatives Considered
- Keep one monolithic release workflow. Rejected because build failures, upload failures, and credential gaps become hard to distinguish.
- Wait for native Windows support before adding a Windows lane. Rejected because packaging and Microsoft Store authentication can be validated with a Rust stub first.
- Add Sparkle to the Apple app in the same change. Rejected because Sparkle changes the client update trust boundary and should be reviewed separately from release plumbing.
## Impact on Other Work
- Future Apple release work can fill in signed archive/export details without changing release numbering.
- Future Windows work can replace the stub package with the native client while preserving upload shape.
- Identity and credential follow-up should derive long-lived automation accounts from `contributors.nix` where they represent Burrow-operated identities.
## Decision
Pending.
## References
- `CONSTITUTION.md`
- `contributors.nix`
- `.forgejo/workflows/release-if-needed.yml`
- `.forgejo/workflows/build-release.yml`
- `.forgejo/workflows/publish-store-uploads.yml`
- `Scripts/version.sh`
- `Scripts/ci/nscloud-cache.sh`
- `Scripts/ci/ensure-nix.sh`
- `Scripts/ci/package-apple-artifacts.sh`
- `Scripts/ci/upload-release-storage.sh`
- `Scripts/ci/upload-package-storage.sh`
- `Scripts/ci/publish-nix-cache.sh`
- `Scripts/ci/backup-garage-to-gcs.sh`
- `bazel/apple/BUILD.bazel`
- `Scripts/forgejo-dispatch.sh`
- `Scripts/forgejo-dispatch-via-host.sh`
- `Scripts/grafana-tofu.sh`
- `Scripts/releases-tofu.sh`
- `.forgejo/workflows/infra-opentofu.yml`
- `.forgejo/workflows/publish-nix-cache.yml`
- `.forgejo/workflows/backup-garage-storage.yml`
- `infra/grafana/`
- `infra/releases/`
- `nixos/modules/burrow-garage.nix`
- `nixos/modules/burrow-observability.nix`
- `services/grafana/dashboards/burrow-overview.json`
- `services/grafana/dashboards/headscale.json`
- `services/grafana/dashboards/observability.json`

View file

@ -0,0 +1,317 @@
# `BEP-0010` - Platform Services, Core, and Distribution
```text
Status: Draft
Proposal: BEP-0010
Authors: gpt-5.5
Coordinator: gpt-5.5
Reviewers: Pending
Constitution Sections: II, III, IV, V
Implementation PRs: Pending
Decision Date: Pending
```
## Summary
Burrow should grow from a forge-hosted VPN project into a coherent platform: a
KMS-backed identity and signing plane, OpenBao-backed secret distribution,
private meeting and mail services, an extracted MCP hub for operator and agent
workflows, and app builds for Apple, Linux, Android, and later store channels.
The first implementation is deliberately scaffolding-heavy. It adds OpenTofu
boundaries for Google KMS, Workload Identity Federation, GCS release backup
storage, and OpenBao; enables Garage-backed object storage and a Burrow-owned
Nix cache at `nix.burrow.net`; adds
NixOS wiring for Grafana, Prometheus, OpenTelemetry, and Jaeger; adds
disabled-by-default NixOS service switch points for `vault.burrow.net` and
`meet.burrow.net`; introduces a small cross-platform Rust core and Android
Kotlin stub; and records the distribution model before stores or public
production services are enabled.
## Motivation
- The forge now controls source and release automation, but release signing,
SAML signing, and runner cloud access still need a stronger non-exportable key
story.
- Secrets are still primarily age-managed files. That remains a good bootstrap
path, but long-running services and agents need short-lived, attributable
runtime access.
- Burrow needs mobile and desktop clients that share protocol logic instead of
repeatedly embedding platform-local behavior.
- The GTK Flatpak can be a useful desktop shell, but the actual VPN data plane
cannot be assumed to work inside an unprivileged Flatpak sandbox.
- App store, F-Droid, Flatpak, and Play Store distribution should be designed as
separate release surfaces with explicit signing, policy, and metadata gates.
## Detailed Design
- Add `infra/identity` as the OpenTofu boundary for Google Cloud identity keys
and Authentik-backed Workload Identity Federation:
- Google project id: `project-88c23ce9-918a-470a-b33`.
- Google project number: `416198671487`.
- KMS key ring: `burrow-identity`.
- non-exportable signing keys for Authentik signing, SAML CA signing, and
release signing.
- a Forgejo runner service account that can be assumed only through the
configured Authentik WIF provider and IAM bindings. Machine-to-machine
runner tokens are scoped by the Authentik WIF client ID
`google-wif.burrow.net`, with `burrow-automation` kept as the human/group
policy boundary.
- Add `infra/releases` as the OpenTofu boundary for Google Cloud
Storage-backed release backups:
- `burrow-net-releases` backs up build artifacts and Sparkle public channels.
- `burrow-net-packages` backs up signed APT, RPM, pacman, Flatpak, and Arch
repository trees.
- `burrow-net-nix-cache` backs up the Garage `attic` bucket and remains
private by default.
- Garage is the required S3-compatible primary target for release and package
uploads.
- `services.burrow.garage` is enabled by the forge host and bootstraps the
single-node layout, the `attic`, `burrow-releases`, and `burrow-packages`
buckets, scoped owner/write S3 access keys, and a read-only backup key from
a host-local environment file.
- Forgejo jobs authenticate with Authentik-backed WIF before using `gcloud
storage` for backup and Google KMS for signing; rclone and Google
service-account JSON keys are not part of the release path.
- Add `services.burrow.nixCache` for `nix.burrow.net`:
- Attic serves `https://nix.burrow.net/burrow` as the public Burrow cache.
- Attic stores chunks in the Garage `attic` bucket through the local S3 API.
- Workers use the cache only after `BURROW_NIX_CACHE_PUBLIC_KEY` is published
in Forgejo variables.
- Push access uses `/var/lib/burrow/nix-cache/ci-push-token`, sealed into
Forgejo/OpenBao as `BURROW_NIX_CACHE_PUSH_TOKEN`, instead of the host-local
bootstrap admin token.
- `.forgejo/workflows/publish-nix-cache.yml` builds selected flake outputs and
publishes their closures to Attic.
- Add `services.burrow.observability` as the forge host observability spine:
- Prometheus scrapes local system, Grafana, Headscale, OpenTelemetry
collector, and Jaeger metrics.
- Headscale exposes local metrics on `127.0.0.1:9098`.
- Tailscale SaaS metrics can be enabled later through the Prometheus
Tailscale exporter once the OAuth environment file is provisioned.
- Burrow backend/frontend processes emit OTLP to the local collector at
`127.0.0.1:4317` or `127.0.0.1:4318`.
- The collector exports traces to local Jaeger, while Grafana queries Jaeger
server-side through a provisioned datasource.
- Add `infra/openbao` as the OpenTofu boundary for OpenBao:
- Google KMS seal wrapping.
- an `age/` KV v2 mount for migrated runtime secrets.
- Authentik OIDC roles for administrators and automation.
- AppRole policy scaffolding for machine consumers.
- Add NixOS modules under `services.burrow.openbao` and
`services.burrow.jitsi`:
- both are imported by the forge host but disabled by default.
- DNS, reverse proxy, KMS seal, storage, and UDP/video bridge requirements are
visible in repo-owned options before activation.
- live enablement requires a separate deploy with secrets, DNS, and rollback
evidence.
- Keep webmail as a second mail phase:
- `inbox.burrow.net` is the intended webmail surface.
- Stalwart is the planned mail server, but the current Nixpkgs module set in
this flake does not expose the expected `services.stalwart-mail` option.
- Forward Email remains the current production mail path until a Stalwart BEP
revision names the exact NixOS module, ports, spam policy, backup target,
DKIM rotation, and migration plan.
- Extract an MCP hub to a dedicated compatible.systems repository:
- target repository: `compatible.systems/burrow/mcp-hub`.
- this repository keeps `services/mcp-hub/` as the extraction plan and
bootstrap manifest until the external repo exists.
- MCP tools should cover OpenBao, Authentik, KMS/WIF, Forgejo runners, Jitsi,
mail, release signing, and Burrow agent identity.
- Create a `burrow-core` Rust crate as the cross-platform protocol/application
core boundary:
- no direct TUN, NetworkExtension, VpnService, GTK, SwiftUI, or desktop UI
dependencies.
- platform shells link it through a thin FFI crate or native Rust API.
- target platforms include Linux x86_64/aarch64/riscv64, Android, macOS, iOS,
visionOS, and future Windows.
- Add an Android stub:
- Kotlin app shell in `Android/app`.
- Rust JNI/FFI crate in `crates/burrow-mobile-ffi`.
- the stub loads the Rust core and displays its version.
- full VPN support must use Android `VpnService`; it must not bypass platform
disclosure and consent flows.
- Keep Bazel as the stable build orchestration entrypoint:
- platform lanes expose wrapper targets under `bazel/`.
- Bazel actions may call repo-owned scripts, Cargo, Gradle, Xcode, Meson, and
OpenTofu while the project migrates deeper native rules selectively.
- Namespace cache setup remains the fast path for remote Bazel and local disk
caches.
- For Linux distribution, split the GUI package from the privileged daemon:
- DEB, RPM, pacman, AUR, and the NixOS flake/module install `burrowd`,
systemd units, D-Bus policy, and polkit policy.
- APT, RPM, and pacman repository metadata is generated from release package
artifacts and signed through Google KMS-backed OpenPGP signatures.
- Repository trees publish through the storage wrapper: Garage first when
configured, then GCS backup after the runner obtains a short-lived Google
credential from Authentik-backed WIF.
- AUR is a source-build git repository, with `burrow-git` tracked in this
repo before publication to `aur.archlinux.org`.
- Flatpak and AppImage packages are GUI-first shells that connect to the host
daemon/helper.
- Flatpak may request background/autostart permission for the GUI, but it
must not be responsible for the privileged VPN data plane.
- PackageKit may be used only as an optional native-package bootstrap on
supported distros, with narrow D-Bus permission and polkit authorization.
## Security and Operational Considerations
- Google KMS keys are non-exportable and have `prevent_destroy` enabled.
- Workload Identity Federation is scoped by issuer, audience, mapped
attributes, and service-account IAM bindings. Runners should not receive
static Google credentials.
- OpenBao bootstrap tokens and AppRole SecretIDs must never enter OpenTofu
state. OpenTofu manages mounts, policies, roles, and cloud KMS objects only.
- OpenBao should start behind `vault.burrow.net` only after:
- KMS seal key exists and decrypt permission is limited to the service
identity.
- root/admin token bootstrap is recorded out of band.
- backup/restore and seal/unseal drills are documented.
- Jitsi should start behind `meet.burrow.net` only after:
- HTTPS and XMPP domains resolve.
- UDP media ports are intentionally opened.
- Authentik guest/member policy is decided.
- Flatpak cannot be the privileged VPN backend by itself. A Flatpak GUI may talk
to a host daemon or privileged helper, but opening TUN devices, installing
routes, and owning system DNS/routing policy belong outside the sandbox.
- Host service bootstrap should be auditable. A Flatpak may discover the daemon,
open native install instructions, or request a host helper through a narrow
D-Bus name. It must not require broad system-bus access, host filesystem
access, or `flatpak-spawn --host` as the normal production path.
- Mobile VPN support uses platform VPN APIs:
- Android: `VpnService` with Play policy disclosure for Play Store builds.
- iOS and visionOS: NetworkExtension packet tunnel provider.
- Release signing, SAML CA signing, and Authentik signing should remain separate
keys even when they share a KMS key ring.
- Package repository signing should stay split by repository format. APT, RPM,
pacman, Flatpak, and AUR source surfaces each get a dedicated KMS key so a
compromised publisher path does not automatically sign every Linux channel.
- Observability ingress is local-first. Prometheus, the OpenTelemetry collector,
and Jaeger bind to loopback; Grafana is the authenticated public surface.
- Jaeger should not be exposed publicly until an Authentik-protected route or
equivalent access boundary is added.
- GCS is a backup target, not the storage abstraction. Future multi-cloud
failover should add explicit provider mirrors or S3-compatible endpoints
behind the storage wrappers.
- Garage needs real host storage for object data and metadata. It replicates
across Garage nodes, but it does not manage multiple external storage
backends. GCS and later providers should be explicit mirror/backup jobs.
- `.forgejo/workflows/backup-garage-storage.yml` is the first explicit mirror.
It uses a read-only Garage key and Authentik-backed WIF to copy Garage bucket
contents to GCS without rclone or static Google service-account keys.
## Contributor Playbook
1. Validate BEP metadata:
```bash
python3 Scripts/check-bep-metadata.py
```
2. Check the Android/Rust stub:
```bash
cargo check --locked -p burrow-mobile-ffi
bazel build //bazel/android:check_stub_stamp
```
3. Validate identity OpenTofu syntax:
```bash
Scripts/identity-tofu.sh init -backend=false
Scripts/identity-tofu.sh validate
```
4. Validate release storage OpenTofu syntax:
```bash
Scripts/releases-tofu.sh init -backend=false
Scripts/releases-tofu.sh validate
```
5. Validate observability Nix wiring and dashboard JSON:
```bash
nix eval .#nixosConfigurations.burrow-forge.config.services.prometheus.scrapeConfigs
jq empty services/grafana/dashboards/headscale.json services/grafana/dashboards/observability.json
```
6. Validate the Nix cache module wiring:
```bash
nix eval .#nixosConfigurations.burrow-forge.config.services.atticd.settings.storage --json
```
7. Validate cache publishing and Garage backup wrapper syntax:
```bash
bash -n Scripts/ci/publish-nix-cache.sh Scripts/ci/backup-garage-to-gcs.sh
```
8. Validate OpenBao OpenTofu syntax:
```bash
Scripts/openbao-tofu.sh init -backend=false
Scripts/openbao-tofu.sh validate
```
9. Before enabling `vault.burrow.net`, create or import the Google KMS seal key,
configure OpenBao bootstrap access, and record a restore test.
10. Before enabling `meet.burrow.net`, verify DNS, TLS, UDP reachability, and
Authentik access policy.
11. Before distributing Flatpak as more than a GUI, prove the host daemon/helper
path on a real Linux desktop.
12. Before Play Store distribution, complete the `VpnService` declaration and
data-safety review.
13. Before relying on PackageKit bootstrap, test Fedora, Debian/Ubuntu, and an
immutable/atomic desktop separately. PackageKit support is not universal and
should degrade to native install instructions.
14. Before promoting package repositories to stable, run the repository
publisher, import the generated public key on a clean VM for each package
family, and install Burrow from that repository rather than from the loose
artifact.
## Alternatives Considered
- Enable OpenBao and Jitsi immediately on the forge host. Rejected because both
change public ingress and security boundaries before secrets, DNS, and
rollback evidence are complete.
- Store Google service-account JSON in Forgejo secrets. Rejected because WIF
gives the runner an attributable short-lived credential path without static
cloud keys.
- Put Android directly on the existing daemon crate. Rejected for now because
the daemon carries OS-specific TUN and service assumptions. A small
cross-platform core gives mobile a safer first boundary.
- Treat Flatpak as the complete Linux VPN package. Rejected because routing,
TUN, and DNS changes need host authority.
- Use broad Flatpak host escape permissions to fork the daemon directly.
Rejected because that defeats the value of Flatpak and makes review harder
than shipping a small native daemon package with explicit polkit policy.
- Use one package signing key for every Linux repository. Rejected because
format-specific keys give cleaner revocation and narrower IAM grants.
## Impact on Other Work
- BEP-0009 release work gains identity and app-distribution follow-up lanes.
- BEP-0005 and BEP-0006 remain authoritative for daemon/client boundaries.
- BEP-0004 remains authoritative for current hosted mail until a Stalwart
revision supersedes it.
- MCP extraction should not move forge runner code until the external
compatible.systems repository exists and import paths are stable.
## Decision
Pending.
## References
- `CONSTITUTION.md`
- `contributors.nix`
- `evolution/proposals/BEP-0004-hosted-mail-and-saas-identity.md`
- `evolution/proposals/BEP-0005-daemon-ipc-and-apple-boundary.md`
- `evolution/proposals/BEP-0006-tailnet-authority-first-control-plane.md`
- `evolution/proposals/BEP-0009-release-infrastructure-and-store-upload-pipeline.md`
- `infra/identity/`
- `infra/releases/`
- `infra/openbao/`
- `docs/OBSERVABILITY.md`
- `docs/NIX_CACHE.md`
- `.forgejo/workflows/publish-nix-cache.yml`
- `.forgejo/workflows/backup-garage-storage.yml`
- `Scripts/ci/publish-nix-cache.sh`
- `Scripts/ci/backup-garage-to-gcs.sh`
- `package/repositories/`
- `nixos/modules/burrow-garage.nix`
- `nixos/modules/burrow-nix-cache.nix`
- `nixos/modules/burrow-observability.nix`
- `nixos/modules/burrow-openbao.nix`
- `nixos/modules/burrow-jitsi.nix`
- `crates/burrow-core/`
- `crates/burrow-mobile-ffi/`
- `Android/`
- `bazel/android/`
- `services/mcp-hub/`