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 <noreply@anthropic.com>
This commit is contained in:
2026-01-31 20:14:35 -08:00
parent 34cacdc40d
commit 301e2a1479
4 changed files with 36 additions and 13 deletions

View File

@@ -228,7 +228,7 @@
defaultConfigurationParams = defaultConfigurationParams =
builtins.listToAttrs (map mkConfigurationParams machineFilenames); builtins.listToAttrs (map mkConfigurationParams machineFilenames);
# Build org-agenda-api container for a given system # Build org-agenda-api container for a given system
mkOrgAgendaApiContainer = system: let mkOrgAgendaApiContainerInfo = system: let
pkgs = import nixpkgs { inherit system; }; pkgs = import nixpkgs { inherit system; };
orgApiRev = builtins.substring 0 7 (org-agenda-api.rev or "unknown"); orgApiRev = builtins.substring 0 7 (org-agenda-api.rev or "unknown");
dotfilesRev = builtins.substring 0 7 (self.rev or self.dirtyRev or "dirty"); dotfilesRev = builtins.substring 0 7 (self.rev or self.dirtyRev or "dirty");
@@ -239,7 +239,11 @@
containerLib = import ../org-agenda-api/container.nix { containerLib = import ../org-agenda-api/container.nix {
inherit pkgs system tangledConfig org-agenda-api orgApiRev dotfilesRev; 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 = { customParams = {
biskcomp = { biskcomp = {
@@ -249,8 +253,11 @@
system = "aarch64-linux"; system = "aarch64-linux";
}; };
railbird-sf = { railbird-sf = {
specialArgs = { specialArgs = let
orgAgendaApiContainer = mkOrgAgendaApiContainer "x86_64-linux"; containerInfo = mkOrgAgendaApiContainerInfo "x86_64-linux";
in {
orgAgendaApiContainer = containerInfo.imageFile;
orgAgendaApiImageName = containerInfo.imageName;
}; };
}; };
}; };

View File

@@ -1,4 +1,4 @@
{ config, lib, pkgs, forEachUser, inputs, orgAgendaApiContainer ? null, ... }: { config, lib, pkgs, forEachUser, inputs, orgAgendaApiContainer ? null, orgAgendaApiImageName ? "org-agenda-api", ... }:
{ {
imports = [ imports = [
../configuration.nix ../configuration.nix
@@ -8,17 +8,22 @@
networking.hostName = "railbird-sf"; networking.hostName = "railbird-sf";
# org-agenda-api hosting with nginx + Let's Encrypt # org-agenda-api hosting with nginx + Let's Encrypt
age.secrets.org-api-env = { # Separate secrets for org-agenda-api: auth password (env format) and SSH key (raw file)
file = ../secrets/org-api-passwords.age; age.secrets.org-api-auth-password = {
# Readable by the podman container service 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 = { services.org-agenda-api-host = {
enable = true; enable = true;
domain = "rbsf.tplinkdns.com"; domain = "rbsf.tplinkdns.com";
containerImage = "colonelpanic-org-agenda-api"; containerImage = orgAgendaApiImageName;
containerImageFile = orgAgendaApiContainer; 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; hardware.enableRedistributableFirmware = true;

View File

@@ -15,7 +15,7 @@ in
domain = mkOption { domain = mkOption {
type = types.str; type = types.str;
default = "rbsf.tplinkdns.com"; 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.<domain>)";
}; };
acmeEmail = mkOption { acmeEmail = mkOption {
@@ -62,7 +62,13 @@ in
secretsFile = mkOption { secretsFile = mkOption {
type = types.path; 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 { timezone = mkOption {
@@ -87,7 +93,7 @@ in
recommendedOptimisation = true; recommendedOptimisation = true;
recommendedGzipSettings = true; recommendedGzipSettings = true;
virtualHosts.${cfg.domain} = { virtualHosts."org-agenda-api.${cfg.domain}" = {
enableACME = true; enableACME = true;
forceSSL = true; forceSSL = true;
locations."/" = { locations."/" = {
@@ -120,6 +126,9 @@ in
AUTH_USER = cfg.authUser; AUTH_USER = cfg.authUser;
}; };
environmentFiles = [ cfg.secretsFile ]; environmentFiles = [ cfg.secretsFile ];
volumes = lib.optionals (cfg.sshKeyFile != null) [
"${cfg.sshKeyFile}:/secrets/ssh_key:ro"
];
extraOptions = [ extraOptions = [
"--pull=never" # Image is from nix store, don't try to pull "--pull=never" # Image is from nix store, don't try to pull
]; ];

View File

@@ -21,6 +21,8 @@ in
"discourse-secret-key-base.age".publicKeys = keys.hostKeys; "discourse-secret-key-base.age".publicKeys = keys.hostKeys;
"vaultwarden-environment-file.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-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; "google-assistant-integration-service-key.age".publicKeys = keys.hostKeys ++ keys.kanivanKeys;
"zwave-js.json.age".publicKeys = keys.hostKeys ++ keys.kanivanKeys; "zwave-js.json.age".publicKeys = keys.hostKeys ++ keys.kanivanKeys;
} }