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

@ -10,6 +10,8 @@ let
dataVolume = "burrow-authentik-data:/data";
directorySyncScript = ../../Scripts/authentik-sync-burrow-directory.sh;
forgejoOidcSyncScript = ../../Scripts/authentik-sync-forgejo-oidc.sh;
grafanaOidcSyncScript = ../../Scripts/authentik-sync-grafana-oidc.sh;
googleWifOidcSyncScript = ../../Scripts/authentik-sync-google-wif-oidc.sh;
tailscaleOidcSyncScript = ../../Scripts/authentik-sync-tailscale-oidc.sh;
onePasswordOidcSyncScript = ../../Scripts/authentik-sync-1password-oidc.sh;
zulipSamlSyncScript = ../../Scripts/authentik-sync-zulip-saml.sh;
@ -136,6 +138,60 @@ in
description = "Authentik application slug for Forgejo.";
};
grafanaDomain = lib.mkOption {
type = lib.types.str;
default = "graphs.burrow.net";
description = "Grafana public domain used for the bundled OIDC client.";
};
grafanaProviderSlug = lib.mkOption {
type = lib.types.str;
default = "grafana";
description = "Authentik application slug for Grafana.";
};
grafanaClientId = lib.mkOption {
type = lib.types.str;
default = "graphs.burrow.net";
description = "Client ID Authentik should present to Grafana.";
};
grafanaClientSecretFile = lib.mkOption {
type = lib.types.nullOr lib.types.str;
default = null;
description = "Host-local file containing the Authentik Grafana OIDC client secret.";
};
grafanaAccessGroupName = lib.mkOption {
type = lib.types.nullOr lib.types.str;
default = null;
description = "Authentik group allowed to launch Grafana.";
};
googleWifProviderSlug = lib.mkOption {
type = lib.types.str;
default = "google-cloud";
description = "Authentik application slug for Google Cloud Workload Identity Federation.";
};
googleWifClientId = lib.mkOption {
type = lib.types.str;
default = "google-wif.burrow.net";
description = "Client ID Authentik should present to Google Cloud WIF consumers.";
};
googleWifClientSecretFile = lib.mkOption {
type = lib.types.nullOr lib.types.str;
default = null;
description = "Host-local file containing the Authentik Google WIF OIDC client secret.";
};
googleWifAccessGroupName = lib.mkOption {
type = lib.types.str;
default = "burrow-automation";
description = "Authentik group allowed to mint Google WIF tokens.";
};
tailscaleProviderSlug = lib.mkOption {
type = lib.types.str;
default = "tailscale";
@ -388,6 +444,12 @@ in
description = "Authentik group granted Burrow administrator access.";
};
extraGroupNames = lib.mkOption {
type = lib.types.listOf lib.types.str;
default = [ ];
description = "Additional Authentik groups that should exist even if no bootstrap user currently belongs to them.";
};
bootstrapUsers = lib.mkOption {
type = with lib.types; listOf (submodule {
options = {
@ -490,6 +552,13 @@ in
fi
''}
${lib.optionalString (cfg.googleWifClientSecretFile != null) ''
if [ ! -s ${lib.escapeShellArg cfg.googleWifClientSecretFile} ]; then
echo "Google WIF client secret missing: ${cfg.googleWifClientSecretFile}" >&2
exit 1
fi
''}
install -d -m 0750 -o root -g root ${runtimeDir} ${blueprintDir}
install -m 0644 -o root -g root ${authentikBlueprint} ${blueprintFile}
@ -517,6 +586,7 @@ AUTHENTIK_BOOTSTRAP_PASSWORD=$AUTHENTIK_BOOTSTRAP_PASSWORD
AUTHENTIK_BOOTSTRAP_TOKEN=$AUTHENTIK_BOOTSTRAP_TOKEN
AUTHENTIK_BURROW_TS_CLIENT_SECRET=$(read_secret ${lib.escapeShellArg cfg.headscaleClientSecretFile})
${lib.optionalString (cfg.forgejoClientSecretFile != null) "AUTHENTIK_BURROW_FORGEJO_CLIENT_SECRET=$(read_secret ${lib.escapeShellArg cfg.forgejoClientSecretFile})"}
${lib.optionalString (cfg.googleWifClientSecretFile != null) "AUTHENTIK_BURROW_GOOGLE_WIF_CLIENT_SECRET=$(read_secret ${lib.escapeShellArg cfg.googleWifClientSecretFile})"}
EOF
chown root:root ${envFile}
chmod 0600 ${envFile}
@ -667,7 +737,7 @@ EOF
'';
};
systemd.services.burrow-authentik-directory = lib.mkIf (cfg.bootstrapUsers != [ ]) {
systemd.services.burrow-authentik-directory = lib.mkIf (cfg.bootstrapUsers != [ ] || cfg.extraGroupNames != [ ]) {
description = "Reconcile Burrow Authentik users and groups";
after =
[
@ -706,6 +776,7 @@ EOF
export AUTHENTIK_URL=https://${cfg.domain}
export AUTHENTIK_BURROW_USERS_GROUP=${lib.escapeShellArg cfg.userGroupName}
export AUTHENTIK_BURROW_ADMINS_GROUP=${lib.escapeShellArg cfg.adminGroupName}
export AUTHENTIK_BURROW_EXTRA_GROUPS_JSON='${builtins.toJSON cfg.extraGroupNames}'
export AUTHENTIK_FORGEJO_APPLICATION_SLUG=${lib.escapeShellArg cfg.forgejoProviderSlug}
export AUTHENTIK_BURROW_DIRECTORY_JSON='${builtins.toJSON (map (user: {
inherit (user) username name email isAdmin passwordFile;
@ -810,12 +881,110 @@ EOF
export AUTHENTIK_FORGEJO_CLIENT_ID=${lib.escapeShellArg cfg.forgejoClientId}
export AUTHENTIK_FORGEJO_CLIENT_SECRET="$(tr -d '\r\n' < ${lib.escapeShellArg cfg.forgejoClientSecretFile})"
export AUTHENTIK_FORGEJO_LAUNCH_URL=https://${cfg.forgejoDomain}/
export AUTHENTIK_FORGEJO_REDIRECT_URIS_JSON='["https://${cfg.forgejoDomain}/user/oauth2/burrow.net/callback","https://${cfg.forgejoDomain}/user/oauth2/authentik/callback","https://${cfg.forgejoDomain}/user/oauth2/GitHub/callback"]'
export AUTHENTIK_FORGEJO_REDIRECT_URIS_JSON='["https://${cfg.forgejoDomain}/user/oauth2/burrow.net/callback","https://${cfg.forgejoDomain}/user/oauth2/authentik/callback"]'
${pkgs.bash}/bin/bash ${forgejoOidcSyncScript}
'';
};
systemd.services.burrow-authentik-grafana-oidc = lib.mkIf (cfg.grafanaClientSecretFile != null) {
description = "Reconcile the Burrow Authentik Grafana OIDC application";
after = [
"burrow-authentik-ready.service"
"network-online.target"
];
wants = [
"burrow-authentik-ready.service"
"network-online.target"
];
wantedBy = [ "multi-user.target" ];
restartTriggers = [
grafanaOidcSyncScript
cfg.envFile
cfg.grafanaClientSecretFile
];
path = [
pkgs.bash
pkgs.coreutils
pkgs.curl
pkgs.jq
];
serviceConfig = {
Type = "oneshot";
User = "root";
Group = "root";
};
script = ''
set -euo pipefail
set -a
source ${lib.escapeShellArg cfg.envFile}
set +a
export AUTHENTIK_URL=https://${cfg.domain}
export AUTHENTIK_GRAFANA_APPLICATION_SLUG=${lib.escapeShellArg cfg.grafanaProviderSlug}
export AUTHENTIK_GRAFANA_APPLICATION_NAME="Burrow Grafana"
export AUTHENTIK_GRAFANA_PROVIDER_NAME="Burrow Grafana"
export AUTHENTIK_GRAFANA_TEMPLATE_SLUG=${lib.escapeShellArg cfg.headscaleProviderSlug}
export AUTHENTIK_GRAFANA_CLIENT_ID=${lib.escapeShellArg cfg.grafanaClientId}
export AUTHENTIK_GRAFANA_CLIENT_SECRET="$(tr -d '\r\n' < ${lib.escapeShellArg cfg.grafanaClientSecretFile})"
export AUTHENTIK_GRAFANA_LAUNCH_URL=https://${cfg.grafanaDomain}/
export AUTHENTIK_GRAFANA_REDIRECT_URIS_JSON='["https://${cfg.grafanaDomain}/login/generic_oauth"]'
${lib.optionalString (cfg.grafanaAccessGroupName != null) ''
export AUTHENTIK_GRAFANA_ACCESS_GROUP=${lib.escapeShellArg cfg.grafanaAccessGroupName}
''}
${pkgs.bash}/bin/bash ${grafanaOidcSyncScript}
'';
};
systemd.services.burrow-authentik-google-wif-oidc = lib.mkIf (cfg.googleWifClientSecretFile != null) {
description = "Reconcile the Burrow Authentik Google WIF OIDC application";
after = [
"burrow-authentik-ready.service"
"network-online.target"
];
wants = [
"burrow-authentik-ready.service"
"network-online.target"
];
wantedBy = [ "multi-user.target" ];
restartTriggers = [
googleWifOidcSyncScript
cfg.envFile
cfg.googleWifClientSecretFile
];
path = [
pkgs.bash
pkgs.coreutils
pkgs.curl
pkgs.jq
];
serviceConfig = {
Type = "oneshot";
User = "root";
Group = "root";
};
script = ''
set -euo pipefail
set -a
source ${lib.escapeShellArg cfg.envFile}
set +a
export AUTHENTIK_URL=https://${cfg.domain}
export AUTHENTIK_GOOGLE_WIF_APPLICATION_SLUG=${lib.escapeShellArg cfg.googleWifProviderSlug}
export AUTHENTIK_GOOGLE_WIF_APPLICATION_NAME="Google Cloud WIF"
export AUTHENTIK_GOOGLE_WIF_PROVIDER_NAME="Google Cloud WIF"
export AUTHENTIK_GOOGLE_WIF_TEMPLATE_SLUG=${lib.escapeShellArg cfg.headscaleProviderSlug}
export AUTHENTIK_GOOGLE_WIF_CLIENT_ID=${lib.escapeShellArg cfg.googleWifClientId}
export AUTHENTIK_GOOGLE_WIF_CLIENT_SECRET="$(tr -d '\r\n' < ${lib.escapeShellArg cfg.googleWifClientSecretFile})"
export AUTHENTIK_GOOGLE_WIF_LAUNCH_URL=https://console.cloud.google.com/
export AUTHENTIK_GOOGLE_WIF_REDIRECT_URIS_JSON='[]'
export AUTHENTIK_GOOGLE_WIF_ACCESS_GROUP=${lib.escapeShellArg cfg.googleWifAccessGroupName}
${pkgs.bash}/bin/bash ${googleWifOidcSyncScript}
'';
};
systemd.services.burrow-authentik-tailscale-oidc = lib.mkIf (cfg.tailscaleClientSecretFile != null) {
description = "Reconcile the Burrow Authentik Tailscale OIDC application";
after = [

View file

@ -7,6 +7,7 @@ let
runnerFile = "${stateDir}/.runner";
registrationFingerprintFile = "${stateDir}/.runner-registration-fingerprint";
configFile = "${stateDir}/runner.yaml";
ageIdentityFile = "${stateDir}/age_keystore";
labelsCsv = lib.concatStringsSep "," (map (label: "${label}:host") cfg.labels);
registrationFingerprint = builtins.hashString "sha256" "${cfg.instanceUrl}\n${cfg.name}\n${labelsCsv}";
sshPrivateKeyFile = cfg.sshPrivateKeyFile or "";
@ -164,6 +165,9 @@ EOF
install -m 0600 -o ${cfg.user} -g ${cfg.group} \
${lib.escapeShellArg sshPrivateKeyFile} \
${stateDir}/.ssh/id_ed25519
install -m 0600 -o ${cfg.user} -g ${cfg.group} \
${lib.escapeShellArg sshPrivateKeyFile} \
${ageIdentityFile}
cat > ${stateDir}/.ssh/config <<EOF
Host *
IdentityFile ${stateDir}/.ssh/id_ed25519
@ -208,7 +212,13 @@ EOF
User = cfg.user;
Group = cfg.group;
WorkingDirectory = stateDir;
Environment = [ "BURROW_RUNNER_REGISTRATION_FINGERPRINT=${registrationFingerprint}" ];
Environment = [
"BURROW_RUNNER_REGISTRATION_FINGERPRINT=${registrationFingerprint}"
"BURROW_RUNNER_AGE_IDENTITY_PATH=${ageIdentityFile}"
"FORGEJO_RUNNER_HOME=${stateDir}"
"RUNNER_HOME=${stateDir}"
"HOME=${stateDir}"
];
Restart = "on-failure";
RestartSec = 2;
ExecStart = pkgs.writeShellScript "burrow-forgejo-runner" ''

View file

@ -10,6 +10,7 @@ let
forgejoAdminArgs = "--config ${lib.escapeShellArg forgejoConfigFile} --work-path ${lib.escapeShellArg forgejoWorkPath} --custom-path ${lib.escapeShellArg forgejoCustomPath}";
homeRepoPath = "/${cfg.homeOwner}/${cfg.homeRepo}";
homeRepoUrl = "https://${cfg.gitDomain}${homeRepoPath}";
sqlLiteral = value: "'${lib.replaceStrings [ "'" ] [ "''" ] value}'";
in
{
options.services.burrow.forge = {
@ -236,6 +237,7 @@ in
repository = {
DEFAULT_BRANCH = "main";
ENABLE_PUSH_CREATE_USER = false;
DISABLE_MIRRORS = true;
};
ui = {
@ -334,6 +336,84 @@ in
'';
};
systemd.services.burrow-forgejo-repository-policy = {
description = "Apply Burrow Forgejo repository authority policy";
after = [
"forgejo.service"
"postgresql.service"
];
requires = [
"forgejo.service"
"postgresql.service"
];
wantedBy = [ "multi-user.target" ];
path = [
pkgs.postgresql
];
serviceConfig = {
Type = "oneshot";
User = forgejoCfg.user;
Group = forgejoCfg.group;
WorkingDirectory = forgejoCfg.stateDir;
};
script = ''
set -euo pipefail
ready=0
for attempt in $(seq 1 60); do
if ${pkgs.postgresql}/bin/psql -h /run/postgresql -U forgejo forgejo -tAc \
"SELECT 1 FROM pg_tables WHERE schemaname='public' AND tablename='repository';" \
| grep -q 1; then
ready=1
break
fi
sleep 1
done
if [ "$ready" -ne 1 ]; then
echo "Forgejo repository table did not become ready" >&2
exit 1
fi
${pkgs.postgresql}/bin/psql -v ON_ERROR_STOP=1 \
-h /run/postgresql -U forgejo forgejo <<'SQL'
DO $$
DECLARE
repo_ids BIGINT[];
BEGIN
SELECT array_agg(r.id)
INTO repo_ids
FROM repository r
JOIN "user" u ON u.id = r.owner_id
WHERE u.lower_name = lower(${sqlLiteral cfg.homeOwner})
AND r.lower_name = lower(${sqlLiteral cfg.homeRepo});
IF repo_ids IS NULL THEN
RETURN;
END IF;
IF EXISTS (
SELECT 1
FROM information_schema.columns
WHERE table_schema = 'public'
AND table_name = 'repository'
AND column_name = 'is_mirror'
) THEN
UPDATE repository SET is_mirror = FALSE WHERE id = ANY(repo_ids);
END IF;
IF to_regclass('public.mirror') IS NOT NULL THEN
DELETE FROM mirror WHERE repo_id = ANY(repo_ids);
END IF;
IF to_regclass('public.push_mirror') IS NOT NULL THEN
DELETE FROM push_mirror WHERE repo_id = ANY(repo_ids);
END IF;
END $$;
SQL
'';
};
systemd.services.burrow-forgejo-oidc-bootstrap = lib.mkIf (cfg.oidcClientSecretFile != null) {
description = "Seed the Burrow Forgejo OIDC login source";
after = [

View file

@ -0,0 +1,284 @@
{ config, lib, pkgs, ... }:
let
cfg = config.services.burrow.garage;
in
{
options.services.burrow.garage = {
enable = lib.mkEnableOption "Burrow Garage S3-compatible object storage";
environmentFile = lib.mkOption {
type = lib.types.path;
default = "/var/lib/burrow/garage/env";
description = "Host-local environment file containing Garage and S3 bootstrap secrets.";
};
region = lib.mkOption {
type = lib.types.str;
default = "garage";
description = "S3 region name advertised by Garage.";
};
apiListenAddress = lib.mkOption {
type = lib.types.str;
default = "127.0.0.1";
description = "Address used by the Garage S3 API listener.";
};
apiPort = lib.mkOption {
type = lib.types.port;
default = 3900;
description = "Port used by the Garage S3 API listener.";
};
webListenAddress = lib.mkOption {
type = lib.types.str;
default = "127.0.0.1";
description = "Address used by the Garage static-web listener.";
};
webPort = lib.mkOption {
type = lib.types.port;
default = 3902;
description = "Port used by the Garage static-web listener.";
};
adminListenAddress = lib.mkOption {
type = lib.types.str;
default = "127.0.0.1";
description = "Address used by the Garage admin listener.";
};
adminPort = lib.mkOption {
type = lib.types.port;
default = 3903;
description = "Port used by the Garage admin listener.";
};
zone = lib.mkOption {
type = lib.types.str;
default = "burrow-forge";
description = "Garage layout zone assigned to the forge host.";
};
layoutCapacity = lib.mkOption {
type = lib.types.str;
default = "100GB";
description = "Initial Garage layout capacity assigned to the forge host.";
};
s3RootDomain = lib.mkOption {
type = lib.types.str;
default = "s3.burrow.net";
description = "Wildcard root domain used for bucket-style S3 API requests.";
};
webRootDomain = lib.mkOption {
type = lib.types.str;
default = "web.burrow.net";
description = "Wildcard root domain used for bucket static website requests.";
};
apiDomain = lib.mkOption {
type = lib.types.str;
default = "objects.burrow.net";
description = "Public S3 API reverse-proxy domain.";
};
enableApiProxy = lib.mkOption {
type = lib.types.bool;
default = false;
description = "Expose the Garage S3 API through Caddy.";
};
enableWebProxy = lib.mkOption {
type = lib.types.bool;
default = false;
description = "Expose the Garage static-web listener through Caddy wildcard routes.";
};
atticBucket = lib.mkOption {
type = lib.types.str;
default = "attic";
description = "Garage bucket used by the Burrow Nix cache.";
};
releaseBucket = lib.mkOption {
type = lib.types.str;
default = "burrow-releases";
description = "Garage bucket used for release artifacts.";
};
packageBucket = lib.mkOption {
type = lib.types.str;
default = "burrow-packages";
description = "Garage bucket used for signed package repositories.";
};
};
config = lib.mkIf cfg.enable {
services.garage = {
enable = true;
package = pkgs.garage;
environmentFile = cfg.environmentFile;
settings = {
metadata_dir = "/var/lib/garage/meta";
data_dir = "/var/lib/garage/data";
rpc_bind_addr = "127.0.0.1:3901";
rpc_public_addr = "127.0.0.1:3901";
replication_factor = 1;
s3_api = {
s3_region = cfg.region;
api_bind_addr = "${cfg.apiListenAddress}:${toString cfg.apiPort}";
root_domain = ".${cfg.s3RootDomain}";
};
s3_web = {
bind_addr = "${cfg.webListenAddress}:${toString cfg.webPort}";
root_domain = ".${cfg.webRootDomain}";
index = "index.html";
};
admin.api_bind_addr = "${cfg.adminListenAddress}:${toString cfg.adminPort}";
};
};
systemd.services.burrow-garage-env = {
description = "Create Burrow Garage bootstrap environment";
before = [ "garage.service" ];
wantedBy = [ "garage.service" ];
requiredBy = [ "garage.service" ];
path = [
pkgs.coreutils
pkgs.gnugrep
pkgs.openssl
];
serviceConfig = {
Type = "oneshot";
User = "root";
Group = "root";
RemainAfterExit = true;
};
script = ''
set -euo pipefail
install -d -m 0700 "$(dirname ${lib.escapeShellArg cfg.environmentFile})"
touch ${lib.escapeShellArg cfg.environmentFile}
chmod 0600 ${lib.escapeShellArg cfg.environmentFile}
chown root:root ${lib.escapeShellArg cfg.environmentFile}
has_var() {
grep -q "^$1=" ${lib.escapeShellArg cfg.environmentFile}
}
add_var() {
local name="$1"
local value="$2"
if ! has_var "$name"; then
printf '%s=%s\n' "$name" "$value" >> ${lib.escapeShellArg cfg.environmentFile}
fi
}
add_var GARAGE_RPC_SECRET "$(${pkgs.openssl}/bin/openssl rand -hex 32)"
add_var GARAGE_ATTIC_ACCESS_KEY_ID "GK$(${pkgs.openssl}/bin/openssl rand -hex 12)"
add_var GARAGE_ATTIC_SECRET_ACCESS_KEY "$(${pkgs.openssl}/bin/openssl rand -hex 32)"
add_var GARAGE_RELEASE_ACCESS_KEY_ID "GK$(${pkgs.openssl}/bin/openssl rand -hex 12)"
add_var GARAGE_RELEASE_SECRET_ACCESS_KEY "$(${pkgs.openssl}/bin/openssl rand -hex 32)"
add_var GARAGE_PACKAGE_ACCESS_KEY_ID "GK$(${pkgs.openssl}/bin/openssl rand -hex 12)"
add_var GARAGE_PACKAGE_SECRET_ACCESS_KEY "$(${pkgs.openssl}/bin/openssl rand -hex 32)"
add_var GARAGE_BACKUP_ACCESS_KEY_ID "GK$(${pkgs.openssl}/bin/openssl rand -hex 12)"
add_var GARAGE_BACKUP_SECRET_ACCESS_KEY "$(${pkgs.openssl}/bin/openssl rand -hex 32)"
add_var ATTIC_SERVER_TOKEN_RS256_SECRET_BASE64 "$(${pkgs.openssl}/bin/openssl genrsa -traditional 4096 | ${pkgs.coreutils}/bin/base64 -w0)"
. ${lib.escapeShellArg cfg.environmentFile}
add_var AWS_ACCESS_KEY_ID "$GARAGE_ATTIC_ACCESS_KEY_ID"
add_var AWS_SECRET_ACCESS_KEY "$GARAGE_ATTIC_SECRET_ACCESS_KEY"
'';
};
systemd.services.burrow-garage-bootstrap = {
description = "Bootstrap Burrow Garage layout, keys, and buckets";
after = [ "garage.service" ];
requires = [ "garage.service" ];
wantedBy = [ "multi-user.target" ];
path = [
config.services.garage.package
pkgs.coreutils
pkgs.gawk
pkgs.gnugrep
];
serviceConfig = {
Type = "oneshot";
User = "root";
Group = "root";
};
script = ''
set -euo pipefail
set -a
. ${lib.escapeShellArg cfg.environmentFile}
set +a
for _ in $(seq 1 60); do
if garage status >/dev/null 2>&1; then
break
fi
sleep 1
done
node_id="$(garage status | tail -n1 | awk '{ print $1 }')"
if [ -n "$node_id" ]; then
garage layout assign -c ${lib.escapeShellArg cfg.layoutCapacity} -z ${lib.escapeShellArg cfg.zone} "$node_id" >/dev/null 2>&1 || true
garage layout apply --version 1 >/dev/null 2>&1 || true
fi
ensure_key() {
local key_id="$1"
local secret="$2"
if ! garage key info "$key_id" >/dev/null 2>&1; then
garage key import "$key_id" "$secret" --yes >/dev/null
fi
}
ensure_bucket() {
local bucket="$1"
local key_id="$2"
if ! garage bucket info "$bucket" >/dev/null 2>&1; then
garage bucket create "$bucket" >/dev/null
fi
garage bucket allow --read --write --owner "$bucket" --key "$key_id" >/dev/null
}
ensure_bucket_reader() {
local bucket="$1"
local key_id="$2"
garage bucket allow --read "$bucket" --key "$key_id" >/dev/null
}
ensure_key "$GARAGE_ATTIC_ACCESS_KEY_ID" "$GARAGE_ATTIC_SECRET_ACCESS_KEY"
ensure_key "$GARAGE_RELEASE_ACCESS_KEY_ID" "$GARAGE_RELEASE_SECRET_ACCESS_KEY"
ensure_key "$GARAGE_PACKAGE_ACCESS_KEY_ID" "$GARAGE_PACKAGE_SECRET_ACCESS_KEY"
ensure_key "$GARAGE_BACKUP_ACCESS_KEY_ID" "$GARAGE_BACKUP_SECRET_ACCESS_KEY"
ensure_bucket ${lib.escapeShellArg cfg.atticBucket} "$GARAGE_ATTIC_ACCESS_KEY_ID"
ensure_bucket ${lib.escapeShellArg cfg.releaseBucket} "$GARAGE_RELEASE_ACCESS_KEY_ID"
ensure_bucket ${lib.escapeShellArg cfg.packageBucket} "$GARAGE_PACKAGE_ACCESS_KEY_ID"
ensure_bucket_reader ${lib.escapeShellArg cfg.atticBucket} "$GARAGE_BACKUP_ACCESS_KEY_ID"
ensure_bucket_reader ${lib.escapeShellArg cfg.releaseBucket} "$GARAGE_BACKUP_ACCESS_KEY_ID"
ensure_bucket_reader ${lib.escapeShellArg cfg.packageBucket} "$GARAGE_BACKUP_ACCESS_KEY_ID"
'';
};
services.caddy.virtualHosts = lib.mkMerge [
(lib.mkIf cfg.enableApiProxy {
"${cfg.apiDomain}".extraConfig = ''
encode gzip zstd
reverse_proxy ${cfg.apiListenAddress}:${toString cfg.apiPort}
'';
})
(lib.mkIf cfg.enableWebProxy {
"*.${cfg.webRootDomain}".extraConfig = ''
encode gzip zstd
reverse_proxy ${cfg.webListenAddress}:${toString cfg.webPort}
'';
})
];
};
}

View file

@ -26,6 +26,24 @@ in
description = "Local Headscale listen port.";
};
enableMetrics = lib.mkOption {
type = lib.types.bool;
default = true;
description = "Expose Headscale Prometheus metrics on the local host.";
};
metricsListenAddress = lib.mkOption {
type = lib.types.str;
default = "127.0.0.1";
description = "Address used for the Headscale metrics listener.";
};
metricsPort = lib.mkOption {
type = lib.types.port;
default = 9098;
description = "Port used for the Headscale metrics listener.";
};
oidcIssuer = lib.mkOption {
type = lib.types.str;
default = "https://${config.services.burrow.authentik.domain}/application/o/${config.services.burrow.authentik.headscaleProviderSlug}/";
@ -154,6 +172,8 @@ in
method = "S256";
};
};
} // lib.optionalAttrs cfg.enableMetrics {
metrics_listen_addr = "${cfg.metricsListenAddress}:${toString cfg.metricsPort}";
};
};

View file

@ -0,0 +1,45 @@
{ config, lib, ... }:
let
cfg = config.services.burrow.jitsi;
in
{
options.services.burrow.jitsi = {
enable = lib.mkEnableOption "the Burrow Jitsi Meet deployment";
domain = lib.mkOption {
type = lib.types.str;
default = "meet.burrow.net";
description = "Public Jitsi Meet domain.";
};
videobridgeUdpPort = lib.mkOption {
type = lib.types.port;
default = 10000;
description = "Public UDP port used by Jitsi Videobridge media traffic.";
};
enableHttpProxy = lib.mkOption {
type = lib.types.bool;
default = true;
description = "Expose Jitsi through the repo-owned Caddy reverse proxy.";
};
};
config = lib.mkIf cfg.enable {
services.jitsi-meet = {
enable = true;
hostName = cfg.domain;
nginx.enable = false;
};
services.jitsi-videobridge.openFirewall = true;
networking.firewall.allowedUDPPorts = [ cfg.videobridgeUdpPort ];
services.caddy.virtualHosts."${cfg.domain}".extraConfig = lib.mkIf cfg.enableHttpProxy ''
encode gzip zstd
reverse_proxy 127.0.0.1:5280
'';
};
}

View file

@ -0,0 +1,162 @@
{ config, lib, pkgs, ... }:
let
cfg = config.services.burrow.nixCache;
garageCfg = config.services.burrow.garage;
in
{
options.services.burrow.nixCache = {
enable = lib.mkEnableOption "Burrow Nix binary cache";
domain = lib.mkOption {
type = lib.types.str;
default = "nix.burrow.net";
description = "Public domain for the Burrow Nix binary cache.";
};
listenAddress = lib.mkOption {
type = lib.types.str;
default = "127.0.0.1";
description = "Attic listen address.";
};
port = lib.mkOption {
type = lib.types.port;
default = 8080;
description = "Attic listen port.";
};
cacheName = lib.mkOption {
type = lib.types.str;
default = "burrow";
description = "Default public Attic cache name.";
};
environmentFile = lib.mkOption {
type = lib.types.path;
default = garageCfg.environmentFile;
description = "Environment file containing Attic JWT and Garage S3 credentials.";
};
adminTokenFile = lib.mkOption {
type = lib.types.path;
default = "/var/lib/burrow/nix-cache/admin-token";
description = "Host-local file containing the generated Attic admin token.";
};
ciPushTokenFile = lib.mkOption {
type = lib.types.path;
default = "/var/lib/burrow/nix-cache/ci-push-token";
description = "Host-local file containing the generated Attic CI push token.";
};
ciPushTokenValidity = lib.mkOption {
type = lib.types.str;
default = "1y";
description = "Validity duration for the generated Attic CI push token.";
};
};
config = lib.mkIf cfg.enable {
assertions = [
{
assertion = garageCfg.enable;
message = "services.burrow.nixCache requires services.burrow.garage.enable = true.";
}
];
services.atticd = {
enable = true;
environmentFile = cfg.environmentFile;
settings = {
listen = "${cfg.listenAddress}:${toString cfg.port}";
allowed-hosts = [ cfg.domain ];
api-endpoint = "https://${cfg.domain}/";
require-proof-of-possession = true;
storage = {
type = "s3";
bucket = garageCfg.atticBucket;
region = garageCfg.region;
endpoint = "http://${garageCfg.apiListenAddress}:${toString garageCfg.apiPort}";
};
};
};
environment.systemPackages = [
pkgs.attic-client
];
services.caddy.virtualHosts."${cfg.domain}".extraConfig = ''
encode gzip zstd
reverse_proxy ${cfg.listenAddress}:${toString cfg.port}
'';
systemd.services.atticd = {
after = [ "burrow-garage-bootstrap.service" ];
wants = [ "burrow-garage-bootstrap.service" ];
requires = [ "burrow-garage-bootstrap.service" ];
};
systemd.services.burrow-nix-cache-bootstrap = {
description = "Bootstrap Burrow Nix binary cache";
after = [ "atticd.service" ];
requires = [ "atticd.service" ];
wantedBy = [ "multi-user.target" ];
path = [
pkgs.attic-client
pkgs.coreutils
];
serviceConfig = {
Type = "oneshot";
User = "root";
Group = "root";
};
script = ''
set -euo pipefail
install -d -m 0700 "$(dirname ${lib.escapeShellArg cfg.adminTokenFile})"
install -d -m 0700 "$(dirname ${lib.escapeShellArg cfg.ciPushTokenFile})"
for _ in $(seq 1 60); do
if (: > /dev/tcp/${cfg.listenAddress}/${toString cfg.port}) >/dev/null 2>&1; then
break
fi
sleep 1
done
if [ ! -s ${lib.escapeShellArg cfg.adminTokenFile} ]; then
umask 077
atticd-atticadm make-token \
--sub burrow-nix-cache-bootstrap \
--validity 10y \
--create-cache '*' \
--pull '*' \
--push '*' \
--delete '*' \
--configure-cache '*' \
--configure-cache-retention '*' \
> ${lib.escapeShellArg cfg.adminTokenFile}
fi
token="$(cat ${lib.escapeShellArg cfg.adminTokenFile})"
export HOME=/var/lib/burrow/nix-cache
install -d -m 0700 "$HOME"
attic login local http://${cfg.listenAddress}:${toString cfg.port} "$token" --set-default
if ! attic cache info ${lib.escapeShellArg "local:${cfg.cacheName}"} >/dev/null 2>&1; then
attic cache create --public --priority 39 ${lib.escapeShellArg "local:${cfg.cacheName}"}
fi
if [ ! -s ${lib.escapeShellArg cfg.ciPushTokenFile} ]; then
umask 077
atticd-atticadm make-token \
--sub burrow-ci-cache-publisher \
--validity ${lib.escapeShellArg cfg.ciPushTokenValidity} \
--pull ${lib.escapeShellArg cfg.cacheName} \
--push ${lib.escapeShellArg cfg.cacheName} \
> ${lib.escapeShellArg cfg.ciPushTokenFile}
fi
'';
};
};
}

View file

@ -0,0 +1,302 @@
{ config, lib, pkgs, ... }:
let
cfg = config.services.burrow.observability;
otelCollectorPackage =
if builtins.hasAttr "opentelemetry-collector-contrib" pkgs then
pkgs."opentelemetry-collector-contrib"
else
pkgs.opentelemetry-collector;
garageCfg = config.services.burrow.garage;
headscaleCfg = config.services.burrow.headscale;
grafanaHttpPort = config.services.grafana.settings.server.http_port or 3000;
grafanaHttpAddress = config.services.grafana.settings.server.http_addr or "127.0.0.1";
tailscaleExporterEnabled = cfg.tailscaleExporterEnvironmentFile != null;
in
{
options.services.burrow.observability = {
enable = lib.mkEnableOption "Burrow Prometheus, OpenTelemetry, Jaeger, and Grafana wiring";
prometheusListenAddress = lib.mkOption {
type = lib.types.str;
default = "127.0.0.1";
description = "Address used by the local Prometheus server.";
};
prometheusPort = lib.mkOption {
type = lib.types.port;
default = 9090;
description = "Port used by the local Prometheus server.";
};
otelGrpcEndpoint = lib.mkOption {
type = lib.types.str;
default = "127.0.0.1:4317";
description = "Local OTLP gRPC endpoint accepted by the OpenTelemetry collector.";
};
otelHttpEndpoint = lib.mkOption {
type = lib.types.str;
default = "127.0.0.1:4318";
description = "Local OTLP HTTP endpoint accepted by the OpenTelemetry collector.";
};
otelPrometheusEndpoint = lib.mkOption {
type = lib.types.str;
default = "127.0.0.1:9464";
description = "Prometheus endpoint where the OpenTelemetry collector exports its own metrics.";
};
jaegerImage = lib.mkOption {
type = lib.types.str;
default = "docker.io/jaegertracing/all-in-one:1.57";
description = "OCI image used for the local Jaeger all-in-one service.";
};
jaegerUiPort = lib.mkOption {
type = lib.types.port;
default = 16686;
description = "Local Jaeger UI and query API port.";
};
jaegerOtlpGrpcPort = lib.mkOption {
type = lib.types.port;
default = 14317;
description = "Local remapped OTLP gRPC port forwarded to Jaeger.";
};
jaegerOtlpHttpPort = lib.mkOption {
type = lib.types.port;
default = 14318;
description = "Local remapped OTLP HTTP port forwarded to Jaeger.";
};
jaegerAdminPort = lib.mkOption {
type = lib.types.port;
default = 14269;
description = "Local Jaeger admin and metrics port.";
};
tailscaleExporterEnvironmentFile = lib.mkOption {
type = lib.types.nullOr lib.types.path;
default = null;
description = "Optional environment file for the Prometheus Tailscale exporter OAuth credentials.";
};
};
config = lib.mkIf cfg.enable (lib.mkMerge [
{
virtualisation.podman.enable = true;
services.grafana = {
settings = {
metrics = {
enabled = true;
disable_total_stats = true;
};
};
provision = {
enable = true;
datasources.settings = {
apiVersion = 1;
prune = true;
datasources = [
{
name = "Prometheus";
uid = "prometheus";
type = "prometheus";
access = "proxy";
url = "http://${cfg.prometheusListenAddress}:${toString cfg.prometheusPort}";
isDefault = true;
jsonData = {
timeInterval = "15s";
httpMethod = "POST";
};
}
{
name = "Jaeger";
uid = "jaeger";
type = "jaeger";
access = "proxy";
url = "http://127.0.0.1:${toString cfg.jaegerUiPort}";
}
];
};
};
};
services.prometheus = {
enable = true;
listenAddress = cfg.prometheusListenAddress;
port = cfg.prometheusPort;
retentionTime = "30d";
checkConfig = "syntax-only";
exporters = {
node = {
enable = true;
listenAddress = "127.0.0.1";
};
systemd = {
enable = true;
listenAddress = "127.0.0.1";
};
};
scrapeConfigs = [
{
job_name = "prometheus";
static_configs = [
{ targets = [ "${cfg.prometheusListenAddress}:${toString cfg.prometheusPort}" ]; }
];
}
{
job_name = "node";
static_configs = [
{ targets = [ "127.0.0.1:${toString config.services.prometheus.exporters.node.port}" ]; }
];
}
{
job_name = "systemd";
static_configs = [
{ targets = [ "127.0.0.1:${toString config.services.prometheus.exporters.systemd.port}" ]; }
];
}
{
job_name = "grafana";
metrics_path = "/metrics";
static_configs = [
{ targets = [ "${grafanaHttpAddress}:${toString grafanaHttpPort}" ]; }
];
}
{
job_name = "headscale";
metrics_path = "/metrics";
static_configs = [
{ targets = [ "${headscaleCfg.metricsListenAddress}:${toString headscaleCfg.metricsPort}" ]; }
];
}
{
job_name = "otel-collector";
static_configs = [
{ targets = [ cfg.otelPrometheusEndpoint ]; }
];
}
{
job_name = "jaeger";
metrics_path = "/metrics";
static_configs = [
{ targets = [ "127.0.0.1:${toString cfg.jaegerAdminPort}" ]; }
];
}
] ++ lib.optionals garageCfg.enable [
{
job_name = "garage";
metrics_path = "/metrics";
static_configs = [
{ targets = [ "${garageCfg.adminListenAddress}:${toString garageCfg.adminPort}" ]; }
];
}
] ++ lib.optionals tailscaleExporterEnabled [
{
job_name = "tailscale";
static_configs = [
{ targets = [ "127.0.0.1:${toString config.services.prometheus.exporters.tailscale.port}" ]; }
];
}
];
};
services.opentelemetry-collector = {
enable = true;
package = otelCollectorPackage;
settings = {
receivers = {
otlp = {
protocols = {
grpc.endpoint = cfg.otelGrpcEndpoint;
http.endpoint = cfg.otelHttpEndpoint;
};
};
};
processors = {
resource.attributes = [
{
key = "service.namespace";
value = "burrow";
action = "upsert";
}
{
key = "deployment.environment";
value = config.networking.hostName;
action = "upsert";
}
];
batch = { };
};
exporters = {
"otlp/jaeger" = {
endpoint = "127.0.0.1:${toString cfg.jaegerOtlpGrpcPort}";
tls.insecure = true;
};
prometheus.endpoint = cfg.otelPrometheusEndpoint;
};
service.pipelines = {
traces = {
receivers = [ "otlp" ];
processors = [
"resource"
"batch"
];
exporters = [ "otlp/jaeger" ];
};
metrics = {
receivers = [ "otlp" ];
processors = [
"resource"
"batch"
];
exporters = [ "prometheus" ];
};
};
};
};
virtualisation.oci-containers.containers.burrow-jaeger = {
image = cfg.jaegerImage;
autoStart = true;
environment = {
COLLECTOR_OTLP_ENABLED = "true";
SPAN_STORAGE_TYPE = "badger";
BADGER_EPHEMERAL = "false";
BADGER_DIRECTORY_VALUE = "/badger/data";
BADGER_DIRECTORY_KEY = "/badger/key";
};
volumes = [
"burrow-jaeger-badger:/badger"
];
ports = [
"127.0.0.1:${toString cfg.jaegerUiPort}:16686"
"127.0.0.1:${toString cfg.jaegerOtlpGrpcPort}:4317"
"127.0.0.1:${toString cfg.jaegerOtlpHttpPort}:4318"
"127.0.0.1:${toString cfg.jaegerAdminPort}:14269"
];
extraOptions = [ "--pull=always" ];
};
systemd.services.opentelemetry-collector.after = [ "podman-burrow-jaeger.service" ];
systemd.services.opentelemetry-collector.wants = [ "podman-burrow-jaeger.service" ];
environment.sessionVariables = {
OTEL_EXPORTER_OTLP_ENDPOINT = "http://${cfg.otelGrpcEndpoint}";
OTEL_EXPORTER_OTLP_PROTOCOL = "grpc";
};
}
(lib.mkIf tailscaleExporterEnabled {
services.prometheus.exporters.tailscale = {
enable = true;
listenAddress = "127.0.0.1";
environmentFile = cfg.tailscaleExporterEnvironmentFile;
};
})
]);
}

View file

@ -0,0 +1,101 @@
{ config, lib, ... }:
let
cfg = config.services.burrow.openbao;
in
{
options.services.burrow.openbao = {
enable = lib.mkEnableOption "the Burrow OpenBao deployment";
domain = lib.mkOption {
type = lib.types.str;
default = "vault.burrow.net";
description = "Public OpenBao domain.";
};
listenAddress = lib.mkOption {
type = lib.types.str;
default = "127.0.0.1";
description = "Local OpenBao listener address.";
};
port = lib.mkOption {
type = lib.types.port;
default = 8200;
description = "Local OpenBao API port.";
};
clusterPort = lib.mkOption {
type = lib.types.port;
default = 8201;
description = "Local OpenBao cluster port.";
};
dataDir = lib.mkOption {
type = lib.types.str;
default = "/var/lib/burrow/openbao";
description = "Persistent OpenBao state directory.";
};
googleKmsSeal = {
enable = lib.mkEnableOption "Google Cloud KMS seal wrapping";
project = lib.mkOption {
type = lib.types.str;
default = "project-88c23ce9-918a-470a-b33";
description = "Google Cloud project containing the seal key.";
};
location = lib.mkOption {
type = lib.types.str;
default = "global";
description = "Google Cloud KMS key ring location.";
};
keyRing = lib.mkOption {
type = lib.types.str;
default = "burrow-identity";
description = "Google Cloud KMS key ring name.";
};
cryptoKey = lib.mkOption {
type = lib.types.str;
default = "openbao-seal";
description = "Google Cloud KMS crypto key name used for seal wrapping.";
};
};
};
config = lib.mkIf cfg.enable {
systemd.tmpfiles.rules = [
"d ${cfg.dataDir} 0700 openbao openbao - -"
"d ${cfg.dataDir}/data 0700 openbao openbao - -"
];
services.openbao = {
enable = true;
settings = {
ui = true;
api_addr = "https://${cfg.domain}";
cluster_addr = "http://${cfg.listenAddress}:${toString cfg.clusterPort}";
storage.file.path = "${cfg.dataDir}/data";
listener.tcp = {
address = "${cfg.listenAddress}:${toString cfg.port}";
tls_disable = true;
};
} // lib.optionalAttrs cfg.googleKmsSeal.enable {
seal.gcpckms = {
project = cfg.googleKmsSeal.project;
region = cfg.googleKmsSeal.location;
key_ring = cfg.googleKmsSeal.keyRing;
crypto_key = cfg.googleKmsSeal.cryptoKey;
};
};
};
services.caddy.virtualHosts."${cfg.domain}".extraConfig = ''
encode gzip zstd
reverse_proxy ${cfg.listenAddress}:${toString cfg.port}
'';
};
}