Add sops-nix secrets + dev defaults so local stack runs with zero setup
check / flake-check (pull_request) Has been cancelled
check / flake-check (pull_request) Has been cancelled
- sops-nix flake input, age + ssh-to-age in devShell, .sops.yaml with per-host recipients (real pubkeys still TODO until lars bootstraps). - nix/modules/gebos-secrets.nix centralises per-host secret subsets and renders /run/secrets/gebos-env via sops.templates. Service modules now read EnvironmentFile= from services.gebos.secrets.envFile instead of the placeholder /etc/gebos/secrets.env. - Falls back to a writeText env file when nix/secrets/secrets.yaml is absent, so `nix flake check` and first-boot eval work pre-bootstrap. - nix/secrets/README.md walks through age key + sops bootstrap. - Dev defaults: ingester binary falls back to local tcp/postgres URLs, process-compose adds mosquitto and wires GEBOS_MQTT_BROKER / GEBOS_POSTGRES_URL / GEBOS_POSTGRES_PASSWORD + VITE_SUPABASE_URL / VITE_SUPABASE_ANON_KEY, frontend defaults to 127.0.0.1:8000 in DEV. Refs #23 (comment #110).
This commit is contained in:
@@ -0,0 +1,95 @@
|
||||
{ config, lib, pkgs, ... }:
|
||||
|
||||
let
|
||||
cfg = config.services.gebos.secrets;
|
||||
secretsFile = ../secrets/secrets.yaml;
|
||||
hasSecrets = builtins.pathExists secretsFile;
|
||||
|
||||
# Map from logical group → list of secret keys in secrets.yaml.
|
||||
# Keys are flat (e.g. "supabase_jwt_secret") so .sops.yaml regex rules can
|
||||
# filter per-host without nested-path footguns.
|
||||
supabaseKeys = [
|
||||
"supabase_postgres_password"
|
||||
"supabase_jwt_secret"
|
||||
"supabase_anon_key"
|
||||
"supabase_service_role_key"
|
||||
"supabase_dashboard_password"
|
||||
];
|
||||
ingesterKeys = [ "ingester_postgres_password" ];
|
||||
postgresAdminKeys = [ "postgres_admin_password" ];
|
||||
|
||||
# Render an env file body from a list of (key, envVarName) pairs.
|
||||
envBody = pairs: lib.concatStringsSep "\n" (map
|
||||
({ key, var }: "${var}=${config.sops.placeholder.${key}}")
|
||||
pairs);
|
||||
|
||||
enabledPairs =
|
||||
lib.optionals cfg.supabase [
|
||||
{ key = "supabase_postgres_password"; var = "POSTGRES_PASSWORD"; }
|
||||
{ key = "supabase_jwt_secret"; var = "JWT_SECRET"; }
|
||||
{ key = "supabase_anon_key"; var = "ANON_KEY"; }
|
||||
{ key = "supabase_service_role_key"; var = "SERVICE_ROLE_KEY"; }
|
||||
{ key = "supabase_dashboard_password"; var = "DASHBOARD_PASSWORD"; }
|
||||
]
|
||||
++ lib.optionals cfg.ingester [
|
||||
{ key = "ingester_postgres_password"; var = "GEBOS_POSTGRES_PASSWORD"; }
|
||||
]
|
||||
++ lib.optionals cfg.postgresAdmin [
|
||||
{ key = "postgres_admin_password"; var = "POSTGRES_ADMIN_PASSWORD"; }
|
||||
];
|
||||
|
||||
enabledKeys =
|
||||
lib.optionals cfg.supabase supabaseKeys
|
||||
++ lib.optionals cfg.ingester ingesterKeys
|
||||
++ lib.optionals cfg.postgresAdmin postgresAdminKeys;
|
||||
in
|
||||
{
|
||||
options.services.gebos.secrets = {
|
||||
supabase = lib.mkEnableOption ''
|
||||
Supabase compose stack secrets: POSTGRES_PASSWORD, JWT_SECRET, ANON_KEY,
|
||||
SERVICE_ROLE_KEY, DASHBOARD_PASSWORD.'';
|
||||
|
||||
ingester = lib.mkEnableOption ''
|
||||
Ingester DB password: GEBOS_POSTGRES_PASSWORD (for the gebos_ingest role).'';
|
||||
|
||||
postgresAdmin = lib.mkEnableOption ''
|
||||
Postgres bootstrap admin password applied via a one-shot ALTER ROLE unit.'';
|
||||
|
||||
envFile = lib.mkOption {
|
||||
type = lib.types.path;
|
||||
readOnly = true;
|
||||
description = ''
|
||||
Path of the rendered env file consumed by service units' EnvironmentFile=.
|
||||
Resolves to the sops-templated file under /run/secrets when sops material
|
||||
is present, or to a baked-in dev-defaults file otherwise (so first-boot
|
||||
eval and local NixOS VMs work without any secrets bootstrap).
|
||||
'';
|
||||
default =
|
||||
if hasSecrets
|
||||
then config.sops.templates."gebos-env".path
|
||||
else "${pkgs.writeText "gebos-env-dev-defaults" (
|
||||
# Dev fallback. Never used in prod — overridden once secrets.yaml exists.
|
||||
lib.concatStringsSep "\n" (map ({ var, ... }: "${var}=dev-${var}") enabledPairs)
|
||||
)}";
|
||||
};
|
||||
};
|
||||
|
||||
config = lib.mkIf hasSecrets {
|
||||
sops = {
|
||||
defaultSopsFile = secretsFile;
|
||||
# Derive each host's age identity from its existing ssh host key.
|
||||
# No extra key material to provision — ssh-to-age handles conversion.
|
||||
age.sshKeyPaths = [ "/etc/ssh/ssh_host_ed25519_key" ];
|
||||
|
||||
secrets = lib.listToAttrs (map
|
||||
(key: { name = key; value = { }; })
|
||||
enabledKeys);
|
||||
|
||||
templates."gebos-env" = lib.mkIf (enabledPairs != []) {
|
||||
content = envBody enabledPairs;
|
||||
mode = "0400";
|
||||
owner = "root";
|
||||
};
|
||||
};
|
||||
};
|
||||
}
|
||||
Reference in New Issue
Block a user