No description
Find a file
2026-04-16 16:57:02 -05:00
borg feat: add argon backup secrets 2026-03-02 22:20:37 -06:00
forgejo feat: add argon key 2026-03-02 21:57:35 -06:00
publicKeys feat: add argon key 2026-03-02 21:57:35 -06:00
tailscale fix: rotate tailscale keys 2026-04-16 16:57:02 -05:00
copyparty.age feat: add argon key 2026-03-02 21:57:35 -06:00
grafana.age feat: add argon key 2026-03-02 21:57:35 -06:00
hash-haus.age feat: add hash.haus 2026-03-20 13:34:31 -05:00
lastfm.age feat: add argon key 2026-03-02 21:57:35 -06:00
pds.age feat: add argon key 2026-03-02 21:57:35 -06:00
README.md Revert "feat: lock down keys a bit" 2026-04-09 20:55:23 -05:00
resend.age feat: add argon key 2026-03-02 21:57:35 -06:00
secrets.nix Revert "feat: lock down keys a bit" 2026-04-09 20:55:23 -05:00

secrets

agenix-encrypted secrets for the nix-infra configuration.

how it works

agenix encrypts secrets with age using SSH public keys. each secret is encrypted for specific hosts and users, defined in secrets.nix. only machines with the corresponding private key can decrypt their secrets at activation time.

key participants

  • host keys (publicKeys/root_<host>.pub) - the SSH host key for each NixOS machine. these are the keys in /etc/ssh/ssh_host_ed25519_key.pub on each host.
  • user keys (publicKeys/taxborn_yubikey.pub) - your personal key (for editing secrets locally).

secrets.nix combines all keys and assigns them to each secret file:

let
  hosts = [ "carbon" "helium-01" "tungsten" "uranium" ];
  users = [ "taxborn_yubikey" ];
  systemKeys = builtins.map (host: builtins.readFile ./publicKeys/root_${host}.pub) hosts;
  userKeys = builtins.map (user: builtins.readFile ./publicKeys/${user}.pub) users;
  keys = systemKeys ++ userKeys;
in
{
  "tailscale/auth.age".publicKeys = keys;
  "forgejo/postgres.age".publicKeys = keys;
  # ...
}

current secrets

secret used by purpose
tailscale/auth.age all hosts tailscale auth key
tailscale/caddyAuth.age carbon caddy tailscale auth
forgejo/postgres.age carbon forgejo database password
forgejo/act-runner.age carbon forgejo CI runner token
resend.age carbon resend API key
copyparty.age helium-01 copyparty credentials
grafana.age helium-01 grafana admin password
pds.age carbon bluesky PDS config
lastfm.age carbon last.fm API key

creating a new secret

  1. add the secret entry to secrets.nix:
{
  # ... existing secrets ...
  "my-service.age".publicKeys = keys;
}
  1. create the encrypted secret file:
cd /path/to/secrets
agenix -e my-service.age

this opens your $EDITOR — paste the secret value, save, and close. agenix encrypts it with all the public keys listed in secrets.nix.

  1. if the secret should only be available on specific hosts, use a subset of keys:
let
  carbonKey = builtins.readFile ./publicKeys/root_carbon.pub;
in
{
  "carbon-only.age".publicKeys = [ carbonKey ] ++ userKeys;
}
  1. reference the secret in the host's secrets.nix:
# hosts/<host>/secrets.nix
{ self, ... }:
{
  age.secrets = {
    myServiceSecret = {
      file = "${self.inputs.secrets}/my-service.age";
      # optional: set owner/group/mode
      # owner = "my-service";
      # group = "my-service";
      # mode = "0400";
    };
  };
}
  1. use the decrypted secret path in your service module:
config.age.secrets.myServiceSecret.path

this resolves to something like /run/agenix/myServiceSecret at runtime.

  1. commit both repos (secrets and nixcfg), then rebuild.

re-keying secrets

if you add a new host or rotate a key, re-encrypt all secrets so the new key can decrypt them:

cd /path/to/secrets

# update the public key file in publicKeys/ first, then:
agenix -r

this re-encrypts every secret listed in secrets.nix with the current set of public keys.

provisioning a new host

1. generate the host key

on the new machine (or during install), grab the SSH host public key:

cat /etc/ssh/ssh_host_ed25519_key.pub

if the machine doesn't have one yet:

ssh-keygen -t ed25519 -f /etc/ssh/ssh_host_ed25519_key -N ""
cat /etc/ssh/ssh_host_ed25519_key.pub

2. add the host key to secrets

# copy the public key into the secrets repo
cp /etc/ssh/ssh_host_ed25519_key.pub publicKeys/root_<hostname>.pub

# add the hostname to the hosts list in secrets.nix

edit secrets.nix and add the hostname to the hosts list:

hosts = [
  "carbon"
  "helium-01"
  "tungsten"
  "uranium"
  "new-host"  # add here
];

3. re-key all secrets

agenix -r

this re-encrypts every secret so the new host can decrypt them. commit and push the secrets repo.

4. create the host configuration in nixcfg

mkdir -p hosts/<hostname>

create the following files:

hosts/<hostname>/default.nix - NixOS system config:

{ self, ... }:
{
  imports = [
    ./home.nix
    ./secrets.nix
    self.diskoConfigurations.<disko-config>  # or define a new one
    self.nixosModules.locale-en-us
  ];

  networking.hostName = "<hostname>";
  time.timeZone = "America/Chicago";
  system.stateVersion = "25.11";

  myNixOS = {
    base.enable = true;
    programs.nix.enable = true;
    # enable what you need:
    # programs.systemd-boot.enable = true;
    # services.tailscale = { enable = true; operator = "taxborn"; };
  };

  myHardware = {
    # intel.cpu.enable = true;
    # profiles.ssd.enable = true;
  };

  myUsers.taxborn = {
    enable = true;
    password = "<hashed-password>";  # mkpasswd -m yescrypt
  };

  boot.initrd.availableKernelModules = [
    # check `lspci` and `lsmod` on the target machine
  ];
}

hosts/<hostname>/home.nix - home-manager config:

{ self, ... }:
{
  home-manager.users.taxborn = {
    imports = [
      # use self.homeModules.taxborn for desktop hosts
      # use self.homeModules.default for servers
      self.homeModules.default
    ];

    config = {
      home = {
        username = "taxborn";
        homeDirectory = "/home/taxborn";
        stateVersion = "25.11";
      };

      programs = {
        home-manager.enable = true;
        fish.enable = true;
      };

      myHome.taxborn.programs = {
        git.enable = true;
        gpg.enable = true;
        tmux.enable = true;
        yubikey.enable = true;
      };
    };
  };
}

hosts/<hostname>/secrets.nix - agenix secret declarations:

{ self, ... }:
{
  age.secrets = {
    tailscaleAuthKey.file = "${self.inputs.secrets}/tailscale/auth.age";
    # add other secrets this host needs
  };
}

5. create a disko configuration (if needed)

add a disk layout under modules/disko/. look at existing configs for reference:

  • luks-btrfs-uranium/ and luks-btrfs-tungsten/ for LUKS-encrypted btrfs (desktops)
  • btrfs-carbon/ and btrfs-helium-01/ for plain btrfs (servers)

6. register the host in the flake

edit modules/flake/nixos.nix and add the hostname to the genAttrs list:

inputs.nixpkgs.lib.genAttrs
  [
    "carbon"
    "helium-01"
    "tungsten"
    "uranium"
    "new-host"  # add here
  ]

7. build and deploy

# first build (from a machine that can reach the target):
just deploy <hostname>

# or if installing fresh with disko:
nix run github:nix-community/disko -- --mode disko --flake .#<hostname>
nixos-install --flake .#<hostname>