This CCE module configures SOPS + age secret management for the Arroyo System. Secrets are encrypted to your Yubikey (via age-plugin-yubikey), a backup age keypair, and each host's SSH ed25519 key (converted to an age recipient with ssh-to-age).
The .sops.yaml recipient rules are generated from the NixOS Host Definitions in systems.org: each host heading declares :ARROYO_SYSTEM_ROLE: and :ARROYO_AGE_RECIPIENT:, and the Lua generators below query the Arroyo DB to emit YAML anchors and per-role key_groups.
Replace the placeholder yubikey and backup recipients below with your own (on your personal branch). The template branch carries <YOUR_*> placeholders.
Static recipient keys
These are your personal decryption identities:
rrix_tpm_vc— a age-plugin-tpm identity sealed in the TPM 2.0 of virtuous-cassette (per-machine, no PIN/tap)rrix_backup— a plain age keypair whose private half is stored offline (paper + password manager), the emergency fallback
Host age recipients (derived from SSH host keys) are generated dynamically below. When a YubiKey 5 is available, add a rrix_yubikey recipient here for portable hardware-backed editing — the .sops.yaml key_groups below already reference it.
- &rrix_tpm_vc age1tag1q2sxwd40w7hlhpqx0dmy24cclxr0la9q0cf75434wnmw6czjw9c6q3fcw7p
# - &rrix_yubikey age1yubikey1<YOUR_YUBIKEY_RECIPIENT>
- &rrix_backup age1cpjqjf6v3wdkxxupdzma2zzn0az34k0npylc0zq0m3vetn2la57q0qw6f0
- &nixflix age1gu5w2m2ngdernhqaelk4cteu2tnuq5nxk8ux3a6wlzqyc0hwnd6qtucd8q.sops.yaml
# Generated from systems.org by arroyo-system/secrets.org.
# Do not edit by hand — edit the org source and re-tangle.
keys:
<<sops_static_keys()>>
<<arroyo_age_recipients_yaml()>>
creation_rules:
- path_regex: secrets/nixflix\.yaml$
key_groups:
- age: [ *rrix_tpm_vc, *rrix_backup, *nixflix ]
<<arroyo_sops_hosts_yaml()>>Generated host recipients
arroyo.age_recipients_all() queries the DB for every host heading with :ARROYO_AGE_RECIPIENT: and returns host (slugified title), recipient, and role. This block emits one YAML anchor per host.
local hosts = arroyo.age_recipients_all()
local lines = {}
for _, h in ipairs(hosts) do
table.insert(lines, string.format(" - &%s %s", h.host, h.recipient))
end
return table.concat(lines, "\n")Generated creation_rules: rules
This block groups hosts by role and emits one path_regex / key_groups rule per role. Each role's secrets are encrypted to all personal identities (TPM, yubikey, backup) + every host of that role. A final catch-all rule encrypts unscoped secret files to just the personal identities.
local all = arroyo.age_recipients_all()
local roles = {}
local role_order = {}
for _, h in ipairs(all) do
if not roles[h.role] then
roles[h.role] = {}
table.insert(role_order, h.role)
end
table.insert(roles[h.role], h)
end
-- local identities = { "*rrix_tpm_vc", "*rrix_yubikey", "*rrix_backup" }
local identities = { "*rrix_tpm_vc", "*rrix_backup" }
local lines = {}
for _, role in ipairs(role_order) do
local refs = {}
for _, id in ipairs(identities) do table.insert(refs, id) end
for _, h in ipairs(roles[role]) do table.insert(refs, "*" .. h.host) end
table.insert(lines, string.format(
" - path_regex: secrets/%s\\.yaml$\n key_groups:\n - age: [ %s ]",
role, table.concat(refs, ", ")))
end
-- table.insert(lines, " - path_regex: secrets/([^/]+)\\.yaml$\n key_groups:\n - age: [ *rrix_tpm_vc, *rrix_yubikey, *rrix_backup ]")
table.insert(lines, " - path_regex: secrets/([^/]+)\\.yaml$\n key_groups:\n - age: [ *rrix_tpm_vc, *rrix_backup ]")
return table.concat(lines, "\n")lib/secrets.nix — host recipient attrset
A Nix attrset mapping host slugs to their age recipients, for programmatic use by other modules if needed.
local all = arroyo.age_recipients_all()
local lines = { "{", " hosts = {" }
for _, h in ipairs(all) do
table.insert(lines, string.format(" %s = %q;", h.host, h.recipient))
end
table.insert(lines, " };")
table.insert(lines, "}")
return table.concat(lines, "\n")<<arroyo_secrets_nix()>>SOPS-Nix Flake Input
The SOPS secret management module imports sops-nix from the flake.
sops-nix = {
url = "github:Mic92/sops-nix";
inputs.nixpkgs.follows = "nixpkgs";
};