16 KiB
BEP-0010 - Platform Services, Core, and Distribution
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/identityas 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, withburrow-automationkept as the human/group policy boundary.
- Google project id:
- Add
infra/releasesas the OpenTofu boundary for Google Cloud Storage-backed release backups:burrow-net-releasesbacks up build artifacts and Sparkle public channels.burrow-net-packagesbacks up signed APT, RPM, pacman, Flatpak, and Arch repository trees.burrow-net-nix-cachebacks up the Garageatticbucket and remains private by default.- Garage is the required S3-compatible primary target for release and package uploads.
services.burrow.garageis enabled by the forge host and bootstraps the single-node layout, theattic,burrow-releases, andburrow-packagesbuckets, 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 storagefor backup and Google KMS for signing; rclone and Google service-account JSON keys are not part of the release path.
- Add
services.burrow.nixCachefornix.burrow.net:- Attic serves
https://nix.burrow.net/burrowas the public Burrow cache. - Attic stores chunks in the Garage
atticbucket through the local S3 API. - Workers use the cache only after
BURROW_NIX_CACHE_PUBLIC_KEYis published in Forgejo variables. - Push access uses
/var/lib/burrow/nix-cache/ci-push-token, sealed into Forgejo/OpenBao asBURROW_NIX_CACHE_PUSH_TOKEN, instead of the host-local bootstrap admin token. .forgejo/workflows/publish-nix-cache.ymlbuilds selected flake outputs and publishes their closures to Attic.
- Attic serves
- Add
services.burrow.observabilityas 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:4317or127.0.0.1:4318. - The collector exports traces to local Jaeger, while Grafana queries Jaeger server-side through a provisioned datasource.
- Add
infra/openbaoas 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.openbaoandservices.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.netis 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-mailoption. - 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.
- target repository:
- Create a
burrow-coreRust 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.
- Kotlin app shell in
- 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.
- platform lanes expose wrapper targets under
- Use a shared Burrow owl identity across app shells:
design/brand/burrow-owl-icon-master.pngis the generated raster master for Apple app icon outputs.- Apple asset catalogs derive all
AppIconPNG sizes from that master and use a restrained warm brown/gold accent color. - GTK keeps a simple vector owl/burrow icon and symbolic icon so Linux desktops do not inherit the old blue gear-style mark.
- Brown/black stays the core palette, with lighter brown, orange, or gold used only as accents for eyes, beak, highlights, and platform tint.
- 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-gittracked in this repo before publication toaur.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.
- DEB, RPM, pacman, AUR, and the NixOS flake/module install
Security and Operational Considerations
- Google KMS keys are non-exportable and have
prevent_destroyenabled. - 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.netonly 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.netonly 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 --hostas the normal production path. - Mobile VPN support uses platform VPN APIs:
- Android:
VpnServicewith Play policy disclosure for Play Store builds. - iOS and visionOS: NetworkExtension packet tunnel provider.
- Android:
- 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.ymlis 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
- Validate BEP metadata:
python3 Scripts/check-bep-metadata.py - Check the Android/Rust stub:
cargo check --locked -p burrow-mobile-ffi bazel build //bazel/android:check_stub_stamp - Validate identity OpenTofu syntax:
Scripts/identity-tofu.sh init -backend=false Scripts/identity-tofu.sh validate - Validate release storage OpenTofu syntax:
Scripts/releases-tofu.sh init -backend=false Scripts/releases-tofu.sh validate - Validate observability Nix wiring and dashboard JSON:
nix eval .#nixosConfigurations.burrow-forge.config.services.prometheus.scrapeConfigs jq empty services/grafana/dashboards/headscale.json services/grafana/dashboards/observability.json - Validate the Nix cache module wiring:
nix eval .#nixosConfigurations.burrow-forge.config.services.atticd.settings.storage --json - Validate cache publishing and Garage backup wrapper syntax:
bash -n Scripts/ci/publish-nix-cache.sh Scripts/ci/backup-garage-to-gcs.sh - Validate OpenBao OpenTofu syntax:
Scripts/openbao-tofu.sh init -backend=false Scripts/openbao-tofu.sh validate - Before enabling
vault.burrow.net, create or import the Google KMS seal key, configure OpenBao bootstrap access, and record a restore test. - Before enabling
meet.burrow.net, verify DNS, TLS, UDP reachability, and Authentik access policy. - Before distributing Flatpak as more than a GUI, prove the host daemon/helper path on a real Linux desktop.
- Before Play Store distribution, complete the
VpnServicedeclaration and data-safety review. - 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.
- 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.mdcontributors.nixevolution/proposals/BEP-0004-hosted-mail-and-saas-identity.mdevolution/proposals/BEP-0005-daemon-ipc-and-apple-boundary.mdevolution/proposals/BEP-0006-tailnet-authority-first-control-plane.mdevolution/proposals/BEP-0009-release-infrastructure-and-store-upload-pipeline.mdinfra/identity/infra/releases/infra/openbao/docs/OBSERVABILITY.mddocs/NIX_CACHE.md.forgejo/workflows/publish-nix-cache.yml.forgejo/workflows/backup-garage-storage.ymlScripts/ci/publish-nix-cache.shScripts/ci/backup-garage-to-gcs.shpackage/repositories/nixos/modules/burrow-garage.nixnixos/modules/burrow-nix-cache.nixnixos/modules/burrow-observability.nixnixos/modules/burrow-openbao.nixnixos/modules/burrow-jitsi.nixcrates/burrow-core/crates/burrow-mobile-ffi/Android/bazel/android/services/mcp-hub/