{ bitcoin-pulse, config, lib, pkgs, ... }: let containerName = "bitcoin-pulse-postgres"; serviceName = "podman-${containerName}"; runtimeDirectory = "/run/${containerName}"; secretFiles = { admin = config.sops.secrets."bitcoin-pulse/postgres/admin-password".path; app = config.sops.secrets."bitcoin-pulse/postgres/app-password".path; migration = config.sops.secrets."bitcoin-pulse/postgres/migration-password".path; }; prepareSecrets = pkgs.writeShellApplication { name = "bitcoin-pulse-postgresql-prepare-secrets"; runtimeInputs = [ config.virtualisation.podman.package pkgs.coreutils ]; text = '' copy_secret() { source="$1" destination="$2" podman unshare rm -f "$destination" # PostgreSQL runs as uid 999 in the container. Store each runtime copy # with the corresponding rootless subordinate uid and a private mode. podman unshare install -m 0400 -o 999 -g 999 "$source" "$destination" } copy_secret ${lib.escapeShellArg secretFiles.admin} \ ${lib.escapeShellArg "${runtimeDirectory}/admin-password"} copy_secret ${lib.escapeShellArg secretFiles.app} \ ${lib.escapeShellArg "${runtimeDirectory}/app-password"} copy_secret ${lib.escapeShellArg secretFiles.migration} \ ${lib.escapeShellArg "${runtimeDirectory}/migration-password"} ''; }; cleanupSecrets = pkgs.writeShellApplication { name = "bitcoin-pulse-postgresql-cleanup-secrets"; runtimeInputs = [ config.virtualisation.podman.package pkgs.coreutils ]; text = '' podman unshare rm -f \ ${lib.escapeShellArg "${runtimeDirectory}/admin-password"} \ ${lib.escapeShellArg "${runtimeDirectory}/app-password"} \ ${lib.escapeShellArg "${runtimeDirectory}/migration-password"} ''; }; in { imports = [ bitcoin-pulse.nixosModules.default ]; services.bitcoin-pulse = { # BIT-28 will enable the production instance after PostgreSQL readiness # and migrations have been integrated with these credentials. enable = false; bindAddress = "127.0.0.1"; port = 3000; mempool.baseUrl = "http://127.0.0.1:8999"; }; users.groups = { bitcoin-pulse = { }; bitcoin-pulse-migration = { }; bitcoin-pulse-postgres.gid = 400; }; users.users.bitcoin-pulse-postgres = { description = "Rootless PostgreSQL container account for Bitcoin Pulse"; isSystemUser = true; uid = 400; group = "bitcoin-pulse-postgres"; extraGroups = [ "bitcoin-pulse" "bitcoin-pulse-migration" ]; home = "/var/lib/bitcoin-pulse-postgres"; homeMode = "0700"; createHome = true; linger = true; autoSubUidGidRange = true; }; sops.secrets = { "bitcoin-pulse/postgres/admin-password" = { group = "bitcoin-pulse-postgres"; mode = "0440"; }; "bitcoin-pulse/postgres/app-password" = { group = "bitcoin-pulse"; mode = "0440"; }; "bitcoin-pulse/postgres/migration-password" = { group = "bitcoin-pulse-migration"; mode = "0440"; }; }; virtualisation.oci-containers = { backend = "podman"; containers.${containerName} = { # Official PostgreSQL 18.4 Bookworm multi-architecture image, pinned to # the immutable digest tested by BitcoinPulseAPI on 2026-07-16. image = "docker.io/library/postgres@sha256:1961f96e6029a02c3812d7cb329a3b03a3ac2bb067058dec17b0f5596aca9296"; pull = "missing"; autoStart = true; autoRemoveOnStop = true; podman = { user = "bitcoin-pulse-postgres"; sdnotify = "healthy"; }; environment = { POSTGRES_DB = "bitcoin_pulse"; POSTGRES_USER = "bitcoin_pulse_admin"; POSTGRES_PASSWORD_FILE = "/run/secrets/admin-password"; POSTGRES_HOST_AUTH_METHOD = "scram-sha-256"; POSTGRES_INITDB_ARGS = "--auth-host=scram-sha-256 --data-checksums"; }; ports = [ "127.0.0.1:5432:5432" ]; volumes = [ "bitcoin-pulse-db:/var/lib/postgresql" "${runtimeDirectory}/admin-password:/run/secrets/admin-password:ro" "${runtimeDirectory}/app-password:/run/secrets/app-password:ro" "${runtimeDirectory}/migration-password:/run/secrets/migration-password:ro" "${bitcoin-pulse}/resources/postgres/initdb/010-roles.sql:/docker-entrypoint-initdb.d/010-roles.sql:ro" ]; extraOptions = [ "--health-cmd=pg_isready --host=127.0.0.1 --port=5432 --username=bitcoin_pulse_admin --dbname=bitcoin_pulse" "--health-interval=10s" "--health-timeout=5s" "--health-retries=6" "--health-start-period=60s" "--stop-signal=SIGINT" "--stop-timeout=60" "--pids-limit=256" "--security-opt=no-new-privileges" ]; }; }; systemd.services.${serviceName}.serviceConfig = { ExecStartPre = lib.mkAfter [ "${prepareSecrets}/bin/bitcoin-pulse-postgresql-prepare-secrets" ]; ExecStartPost = [ "${cleanupSecrets}/bin/bitcoin-pulse-postgresql-cleanup-secrets" ]; ExecStopPost = lib.mkAfter [ "${cleanupSecrets}/bin/bitcoin-pulse-postgresql-cleanup-secrets" ]; RuntimeDirectoryMode = "0700"; RestartSec = "5s"; TimeoutStopSec = lib.mkForce "90s"; UMask = "0077"; }; environment.systemPackages = [ pkgs.postgresql_18 ]; }