From 301e2a14790b926401d550fde3b04e9bf3f7ae73 Mon Sep 17 00:00:00 2001 From: Ivan Malison Date: Sat, 31 Jan 2026 20:14:35 -0800 Subject: [PATCH] Split org-api secrets into auth password and SSH key - Auth password uses env file format for systemd EnvironmentFile - SSH key is mounted as a file at /secrets/ssh_key in container - Fixes multi-line SSH key parsing issue in environment files - Update codex PR patch hash Co-Authored-By: Claude Opus 4.5 --- nixos/flake.nix | 15 +++++++++++---- nixos/machines/railbird-sf.nix | 17 +++++++++++------ nixos/org-agenda-api-host.nix | 15 ++++++++++++--- nixos/secrets/secrets.nix | 2 ++ 4 files changed, 36 insertions(+), 13 deletions(-) diff --git a/nixos/flake.nix b/nixos/flake.nix index bd46bd4a..c0181329 100644 --- a/nixos/flake.nix +++ b/nixos/flake.nix @@ -228,7 +228,7 @@ defaultConfigurationParams = builtins.listToAttrs (map mkConfigurationParams machineFilenames); # Build org-agenda-api container for a given system - mkOrgAgendaApiContainer = system: let + mkOrgAgendaApiContainerInfo = system: let pkgs = import nixpkgs { inherit system; }; orgApiRev = builtins.substring 0 7 (org-agenda-api.rev or "unknown"); dotfilesRev = builtins.substring 0 7 (self.rev or self.dirtyRev or "dirty"); @@ -239,7 +239,11 @@ containerLib = import ../org-agenda-api/container.nix { inherit pkgs system tangledConfig org-agenda-api orgApiRev dotfilesRev; }; - in containerLib.containers.colonelpanic; + tag = "colonelpanic-${orgApiRev}-${dotfilesRev}"; + in { + imageFile = containerLib.containers.colonelpanic; + imageName = "org-agenda-api:${tag}"; + }; customParams = { biskcomp = { @@ -249,8 +253,11 @@ system = "aarch64-linux"; }; railbird-sf = { - specialArgs = { - orgAgendaApiContainer = mkOrgAgendaApiContainer "x86_64-linux"; + specialArgs = let + containerInfo = mkOrgAgendaApiContainerInfo "x86_64-linux"; + in { + orgAgendaApiContainer = containerInfo.imageFile; + orgAgendaApiImageName = containerInfo.imageName; }; }; }; diff --git a/nixos/machines/railbird-sf.nix b/nixos/machines/railbird-sf.nix index 5a410324..b425c2ab 100644 --- a/nixos/machines/railbird-sf.nix +++ b/nixos/machines/railbird-sf.nix @@ -1,4 +1,4 @@ -{ config, lib, pkgs, forEachUser, inputs, orgAgendaApiContainer ? null, ... }: +{ config, lib, pkgs, forEachUser, inputs, orgAgendaApiContainer ? null, orgAgendaApiImageName ? "org-agenda-api", ... }: { imports = [ ../configuration.nix @@ -8,17 +8,22 @@ networking.hostName = "railbird-sf"; # org-agenda-api hosting with nginx + Let's Encrypt - age.secrets.org-api-env = { - file = ../secrets/org-api-passwords.age; - # Readable by the podman container service + # Separate secrets for org-agenda-api: auth password (env format) and SSH key (raw file) + age.secrets.org-api-auth-password = { + file = ../secrets/org-api-auth-password.age; + }; + age.secrets.org-api-ssh-key = { + file = ../secrets/org-api-ssh-key.age; + mode = "0400"; # Restrictive permissions for SSH key }; services.org-agenda-api-host = { enable = true; domain = "rbsf.tplinkdns.com"; - containerImage = "colonelpanic-org-agenda-api"; + containerImage = orgAgendaApiImageName; containerImageFile = orgAgendaApiContainer; - secretsFile = config.age.secrets.org-api-env.path; + secretsFile = config.age.secrets.org-api-auth-password.path; + sshKeyFile = config.age.secrets.org-api-ssh-key.path; }; hardware.enableRedistributableFirmware = true; diff --git a/nixos/org-agenda-api-host.nix b/nixos/org-agenda-api-host.nix index dba01b35..91cba81e 100644 --- a/nixos/org-agenda-api-host.nix +++ b/nixos/org-agenda-api-host.nix @@ -15,7 +15,7 @@ in domain = mkOption { type = types.str; default = "rbsf.tplinkdns.com"; - description = "Domain name for the service (used for Let's Encrypt)"; + description = "Base domain name (service will be at org-agenda-api.)"; }; acmeEmail = mkOption { @@ -62,7 +62,13 @@ in secretsFile = mkOption { type = types.path; - description = "Path to agenix-decrypted secrets file containing AUTH_PASSWORD and GIT_SSH_PRIVATE_KEY"; + description = "Path to agenix-decrypted secrets file containing AUTH_PASSWORD"; + }; + + sshKeyFile = mkOption { + type = types.nullOr types.path; + default = null; + description = "Path to agenix-decrypted SSH private key file (mounted at /secrets/ssh_key in container)"; }; timezone = mkOption { @@ -87,7 +93,7 @@ in recommendedOptimisation = true; recommendedGzipSettings = true; - virtualHosts.${cfg.domain} = { + virtualHosts."org-agenda-api.${cfg.domain}" = { enableACME = true; forceSSL = true; locations."/" = { @@ -120,6 +126,9 @@ in AUTH_USER = cfg.authUser; }; environmentFiles = [ cfg.secretsFile ]; + volumes = lib.optionals (cfg.sshKeyFile != null) [ + "${cfg.sshKeyFile}:/secrets/ssh_key:ro" + ]; extraOptions = [ "--pull=never" # Image is from nix store, don't try to pull ]; diff --git a/nixos/secrets/secrets.nix b/nixos/secrets/secrets.nix index 8c4c3d25..07e12bd7 100644 --- a/nixos/secrets/secrets.nix +++ b/nixos/secrets/secrets.nix @@ -21,6 +21,8 @@ in "discourse-secret-key-base.age".publicKeys = keys.hostKeys; "vaultwarden-environment-file.age".publicKeys = keys.hostKeys; "org-api-passwords.age".publicKeys = keys.hostKeys ++ keys.kanivanKeys ++ keys.railbird-sf; + "org-api-auth-password.age".publicKeys = keys.hostKeys ++ keys.kanivanKeys ++ keys.railbird-sf; + "org-api-ssh-key.age".publicKeys = keys.hostKeys ++ keys.kanivanKeys ++ keys.railbird-sf; "google-assistant-integration-service-key.age".publicKeys = keys.hostKeys ++ keys.kanivanKeys; "zwave-js.json.age".publicKeys = keys.hostKeys ++ keys.kanivanKeys; }