format nix with rfc style

This commit is contained in:
Ryan Mulligan 2025-08-04 09:26:41 -07:00
parent 856df6f692
commit 0814fdc0de
13 changed files with 500 additions and 453 deletions

View file

@ -14,7 +14,7 @@ jobs:
extra-experimental-features = recursive-nix nix-command flakes
- run: nix build
- run: nix build .#doc
- run: nix fmt . -- --check
- run: nix fmt . -- --ci
- run: nix flake check
tests-darwin:
runs-on: macos-latest
@ -27,7 +27,7 @@ jobs:
extra-experimental-features = recursive-nix nix-command flakes
- run: nix build
- run: nix build .#doc
- run: nix fmt . -- --check
- run: nix fmt . -- --ci
- run: nix flake check
- name: "Install nix-darwin module"
run: |

View file

@ -1,3 +1,6 @@
{pkgs ? import <nixpkgs> {}}: {
agenix = pkgs.callPackage ./pkgs/agenix.nix {};
{
pkgs ? import <nixpkgs> { },
}:
{
agenix = pkgs.callPackage ./pkgs/agenix.nix { };
}

View file

@ -1,13 +1,23 @@
let
user1 = "ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIL0idNvgGiucWgup/mP78zyC23uFjYq0evcWdjGQUaBH";
system1 = "ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIPJDyIr/FSz1cJdcoW69R+NrWzwGK/+3gJpqD1t8L2zE";
in {
"secret1.age".publicKeys = [user1 system1];
"secret2.age".publicKeys = [user1];
"passwordfile-user1.age".publicKeys = [user1 system1];
"-leading-hyphen-filename.age".publicKeys = [user1 system1];
in
{
"secret1.age".publicKeys = [
user1
system1
];
"secret2.age".publicKeys = [ user1 ];
"passwordfile-user1.age".publicKeys = [
user1
system1
];
"-leading-hyphen-filename.age".publicKeys = [
user1
system1
];
"armored-secret.age" = {
publicKeys = [user1];
publicKeys = [ user1 ];
armor = true;
};
}

View file

@ -14,15 +14,18 @@
systems.url = "github:nix-systems/default";
};
outputs = {
outputs =
{
self,
nixpkgs,
darwin,
home-manager,
systems,
}: let
}:
let
eachSystem = nixpkgs.lib.genAttrs (import systems);
in {
in
{
nixosModules.age = ./modules/age.nix;
nixosModules.default = self.nixosModules.age;
@ -34,16 +37,16 @@
overlays.default = import ./overlay.nix;
formatter = eachSystem (system: nixpkgs.legacyPackages.${system}.alejandra);
formatter = eachSystem (system: nixpkgs.legacyPackages.${system}.nixfmt-tree);
packages = eachSystem (system: {
agenix = nixpkgs.legacyPackages.${system}.callPackage ./pkgs/agenix.nix {};
doc = nixpkgs.legacyPackages.${system}.callPackage ./pkgs/doc.nix {inherit self;};
agenix = nixpkgs.legacyPackages.${system}.callPackage ./pkgs/agenix.nix { };
doc = nixpkgs.legacyPackages.${system}.callPackage ./pkgs/doc.nix { inherit self; };
default = self.packages.${system}.agenix;
});
checks =
nixpkgs.lib.genAttrs ["aarch64-darwin" "x86_64-darwin"] (system: {
nixpkgs.lib.genAttrs [ "aarch64-darwin" "x86_64-darwin" ] (system: {
integration =
(darwin.lib.darwinSystem {
inherit system;
@ -51,7 +54,7 @@
./test/integration_darwin.nix
# Allow new-style nix commands in CI
{nix.extraOptions = "experimental-features = nix-command flakes";}
{ nix.extraOptions = "experimental-features = nix-command flakes"; }
home-manager.darwinModules.home-manager
{
@ -64,8 +67,7 @@
};
}
];
})
.system;
}).system;
})
// {
x86_64-linux.integration = import ./test/integration.nix {
@ -79,10 +81,10 @@
darwinConfigurations.integration-aarch64.system = self.checks.aarch64-darwin.integration;
# Work-around for https://github.com/nix-community/home-manager/issues/3075
legacyPackages = nixpkgs.lib.genAttrs ["aarch64-darwin" "x86_64-darwin"] (system: {
legacyPackages = nixpkgs.lib.genAttrs [ "aarch64-darwin" "x86_64-darwin" ] (system: {
homeConfigurations.integration-darwin = home-manager.lib.homeManagerConfiguration {
pkgs = nixpkgs.legacyPackages.${system};
modules = [./test/integration_hm_darwin.nix];
modules = [ ./test/integration_hm_darwin.nix ];
};
});
};

View file

@ -5,7 +5,8 @@
pkgs,
...
}:
with lib; let
with lib;
let
cfg = config.age;
ageBin = lib.getExe config.age.package;
@ -22,11 +23,12 @@ with lib; let
setTruePath = secretType: ''
${
if secretType.symlink
then ''
if secretType.symlink then
''
_truePath="${cfg.secretsMountPoint}/$_agenix_generation/${secretType.name}"
''
else ''
else
''
_truePath="${secretType.path}"
''
}
@ -54,7 +56,9 @@ with lib; let
umask u=r,g=,o=
test -f "${secretType.file}" || echo '[agenix] WARNING: encrypted file ${secretType.file} does not exist!'
test -d "$(dirname "$TMP_FILE")" || echo "[agenix] WARNING: $(dirname "$TMP_FILE") does not exist!"
LANG=${config.i18n.defaultLocale or "C"} ${ageBin} --decrypt "''${IDENTITIES[@]}" -o "$TMP_FILE" "${secretType.file}"
LANG=${
config.i18n.defaultLocale or "C"
} ${ageBin} --decrypt "''${IDENTITIES[@]}" -o "$TMP_FILE" "${secretType.file}"
)
chmod ${secretType.mode} "$TMP_FILE"
mv -f "$TMP_FILE" "$_truePath"
@ -65,12 +69,9 @@ with lib; let
''}
'';
testIdentities =
map
(path: ''
testIdentities = map (path: ''
test -f ${path} || echo '[agenix] WARNING: config.age.identityPaths entry ${path} not present!'
'')
cfg.identityPaths;
'') cfg.identityPaths;
cleanupAndLink = ''
_agenix_generation="$(basename "$(readlink "${cfg.secretsDir}")" || echo 0)"
@ -85,17 +86,19 @@ with lib; let
'';
installSecrets = builtins.concatStringsSep "\n" (
["echo '[agenix] decrypting secrets...'"]
[ "echo '[agenix] decrypting secrets...'" ]
++ testIdentities
++ (map installSecret (builtins.attrValues cfg.secrets))
++ [cleanupAndLink]
++ [ cleanupAndLink ]
);
secretType = types.submodule ({
secretType = types.submodule (
{
config,
name,
...
}: {
}:
{
options = {
name = mkOption {
type = types.str;
@ -124,14 +127,18 @@ with lib; let
Permissions mode of the decrypted secret in a format understood by chmod.
'';
};
symlink = mkEnableOption "symlinking secrets to their destination" // {default = true;};
symlink = mkEnableOption "symlinking secrets to their destination" // {
default = true;
};
});
};
}
);
mountingScript = let
mountingScript =
let
app = pkgs.writeShellApplication {
name = "agenix-home-manager-mount-secrets";
runtimeInputs = with pkgs; [coreutils];
runtimeInputs = with pkgs; [ coreutils ];
text = ''
${newGeneration}
${installSecrets}
@ -141,25 +148,27 @@ with lib; let
in
lib.getExe app;
userDirectory = dir: let
userDirectory =
dir:
let
inherit (pkgs.stdenv.hostPlatform) isDarwin;
baseDir =
if isDarwin
then "$(getconf DARWIN_USER_TEMP_DIR)"
else "\${XDG_RUNTIME_DIR}";
in "${baseDir}/${dir}";
baseDir = if isDarwin then "$(getconf DARWIN_USER_TEMP_DIR)" else "\${XDG_RUNTIME_DIR}";
in
"${baseDir}/${dir}";
userDirectoryDescription = dir:
userDirectoryDescription =
dir:
literalExpression ''
"''${XDG_RUNTIME_DIR}"/''${dir} on linux or "$(getconf DARWIN_USER_TEMP_DIR)"/''${dir} on darwin.
'';
in {
in
{
options.age = {
package = mkPackageOption pkgs "age" {};
package = mkPackageOption pkgs "age" { };
secrets = mkOption {
type = types.attrsOf secretType;
default = {};
default = { };
description = ''
Attrset of secrets.
'';
@ -200,10 +209,10 @@ in {
};
};
config = mkIf (cfg.secrets != {}) {
config = mkIf (cfg.secrets != { }) {
assertions = [
{
assertion = cfg.identityPaths != [];
assertion = cfg.identityPaths != [ ];
message = "age.identityPaths must be set.";
}
];
@ -216,13 +225,13 @@ in {
Type = "oneshot";
ExecStart = mountingScript;
};
Install.WantedBy = ["default.target"];
Install.WantedBy = [ "default.target" ];
};
launchd.agents.activate-agenix = {
enable = true;
config = {
ProgramArguments = [mountingScript];
ProgramArguments = [ mountingScript ];
KeepAlive = {
Crashed = false;
SuccessfulExit = false;

View file

@ -5,23 +5,25 @@
pkgs,
...
}:
with lib; let
with lib;
let
cfg = config.age;
isDarwin = lib.attrsets.hasAttrByPath ["environment" "darwinConfig"] options;
isDarwin = lib.attrsets.hasAttrByPath [ "environment" "darwinConfig" ] options;
ageBin = config.age.ageBin;
users = config.users.users;
sysusersEnabled =
if isDarwin
then false
else options.systemd ? sysusers && (config.systemd.sysusers.enable || config.services.userborn.enable);
if isDarwin then
false
else
options.systemd ? sysusers && (config.systemd.sysusers.enable || config.services.userborn.enable);
mountCommand =
if isDarwin
then ''
if isDarwin then
''
if ! diskutil info "${cfg.secretsMountPoint}" &> /dev/null; then
num_sectors=1048576
dev=$(hdiutil attach -nomount ram://"$num_sectors" | sed 's/[[:space:]]*$//')
@ -29,7 +31,8 @@ with lib; let
mount -t hfs -o nobrowse,nodev,nosuid,-m=0751 "$dev" "${cfg.secretsMountPoint}"
fi
''
else ''
else
''
grep -q "${cfg.secretsMountPoint} ramfs" /proc/mounts ||
mount -t ramfs none "${cfg.secretsMountPoint}" -o nodev,nosuid,mode=0751
'';
@ -44,10 +47,7 @@ with lib; let
chmod 0751 "${cfg.secretsMountPoint}/$_agenix_generation"
'';
chownGroup =
if isDarwin
then "admin"
else "keys";
chownGroup = if isDarwin then "admin" else "keys";
# chown the secrets mountpoint and the current generation to the keys group
# instead of leaving it root:root.
chownMountPoint = ''
@ -56,11 +56,12 @@ with lib; let
setTruePath = secretType: ''
${
if secretType.symlink
then ''
if secretType.symlink then
''
_truePath="${cfg.secretsMountPoint}/$_agenix_generation/${secretType.name}"
''
else ''
else
''
_truePath="${secretType.path}"
''
}
@ -87,7 +88,9 @@ with lib; let
umask u=r,g=,o=
test -f "${secretType.file}" || echo '[agenix] WARNING: encrypted file ${secretType.file} does not exist!'
test -d "$(dirname "$TMP_FILE")" || echo "[agenix] WARNING: $(dirname "$TMP_FILE") does not exist!"
LANG=${config.i18n.defaultLocale or "C"} ${ageBin} --decrypt "''${IDENTITIES[@]}" -o "$TMP_FILE" "${secretType.file}"
LANG=${
config.i18n.defaultLocale or "C"
} ${ageBin} --decrypt "''${IDENTITIES[@]}" -o "$TMP_FILE" "${secretType.file}"
)
chmod ${secretType.mode} "$TMP_FILE"
mv -f "$TMP_FILE" "$_truePath"
@ -97,12 +100,9 @@ with lib; let
''}
'';
testIdentities =
map
(path: ''
testIdentities = map (path: ''
test -f ${path} || echo '[agenix] WARNING: config.age.identityPaths entry ${path} not present!'
'')
cfg.identityPaths;
'') cfg.identityPaths;
cleanupAndLink = ''
_agenix_generation="$(basename "$(readlink ${cfg.secretsDir})" || echo 0)"
@ -117,10 +117,10 @@ with lib; let
'';
installSecrets = builtins.concatStringsSep "\n" (
["echo '[agenix] decrypting secrets...'"]
[ "echo '[agenix] decrypting secrets...'" ]
++ testIdentities
++ (map installSecret (builtins.attrValues cfg.secrets))
++ [cleanupAndLink]
++ [ cleanupAndLink ]
);
chownSecret = secretType: ''
@ -129,12 +129,14 @@ with lib; let
'';
chownSecrets = builtins.concatStringsSep "\n" (
["echo '[agenix] chowning...'"]
++ [chownMountPoint]
[ "echo '[agenix] chowning...'" ]
++ [ chownMountPoint ]
++ (map chownSecret (builtins.attrValues cfg.secrets))
);
secretType = types.submodule ({config, ...}: {
secretType = types.submodule (
{ config, ... }:
{
options = {
name = mkOption {
type = types.str;
@ -184,12 +186,16 @@ with lib; let
Group of the decrypted secret.
'';
};
symlink = mkEnableOption "symlinking secrets to their destination" // {default = true;};
symlink = mkEnableOption "symlinking secrets to their destination" // {
default = true;
};
});
in {
};
}
);
in
{
imports = [
(mkRenamedOptionModule ["age" "sshKeyPaths"] ["age" "identityPaths"])
(mkRenamedOptionModule [ "age" "sshKeyPaths" ] [ "age" "identityPaths" ])
];
options.age = {
@ -205,7 +211,7 @@ in {
};
secrets = mkOption {
type = types.attrsOf secretType;
default = {};
default = { };
description = ''
Attrset of secrets.
'';
@ -219,12 +225,14 @@ in {
};
secretsMountPoint = mkOption {
type =
types.addCheck types.str
(s:
(builtins.match "[ \t\n]*" s)
== null # non-empty
&& (builtins.match ".+/" s) == null) # without trailing slash
// {description = "${types.str.description} (with check: non-empty without trailing slash)";};
types.addCheck types.str (
s:
(builtins.match "[ \t\n]*" s) == null # non-empty
&& (builtins.match ".+/" s) == null
) # without trailing slash
// {
description = "${types.str.description} (with check: non-empty without trailing slash)";
};
default = "/run/agenix.d";
description = ''
Where secrets are created before they are symlinked to {option}`age.secretsDir`
@ -233,14 +241,17 @@ in {
identityPaths = mkOption {
type = types.listOf types.path;
default =
if isDarwin
then [
if isDarwin then
[
"/etc/ssh/ssh_host_ed25519_key"
"/etc/ssh/ssh_host_rsa_key"
]
else if (config.services.openssh.enable or false)
then map (e: e.path) (lib.filter (e: e.type == "rsa" || e.type == "ed25519") config.services.openssh.hostKeys)
else [];
else if (config.services.openssh.enable or false) then
map (e: e.path) (
lib.filter (e: e.type == "rsa" || e.type == "ed25519") config.services.openssh.hostKeys
)
else
[ ];
defaultText = literalExpression ''
if isDarwin
then [
@ -257,11 +268,11 @@ in {
};
};
config = mkIf (cfg.secrets != {}) (mkMerge [
config = mkIf (cfg.secrets != { }) (mkMerge [
{
assertions = [
{
assertion = cfg.identityPaths != [];
assertion = cfg.identityPaths != [ ];
message = "age.identityPaths must be set, for example by enabling openssh.";
}
];
@ -270,20 +281,18 @@ in {
# When using sysusers we no longer be started as an activation script
# because those are started in initrd while sysusers is started later.
systemd.services.agenix-install-secrets = mkIf sysusersEnabled {
wantedBy = ["sysinit.target"];
after = ["systemd-sysusers.service"];
wantedBy = [ "sysinit.target" ];
after = [ "systemd-sysusers.service" ];
unitConfig.DefaultDependencies = "no";
path = [pkgs.mount];
path = [ pkgs.mount ];
serviceConfig = {
Type = "oneshot";
ExecStart = pkgs.writeShellScript "agenix-install" (
concatLines [
ExecStart = pkgs.writeShellScript "agenix-install" (concatLines [
newGeneration
installSecrets
chownSecrets
]
);
]);
RemainAfterExit = true;
};
};
@ -308,7 +317,7 @@ in {
};
# So user passwords can be encrypted.
users.deps = ["agenixInstall"];
users.deps = [ "agenixInstall" ];
# Change ownership and group after users and groups are made.
agenixChown = {
@ -322,7 +331,7 @@ in {
# So other activation scripts can depend on agenix being done.
agenix = {
text = "";
deps = ["agenixChown"];
deps = [ "agenixChown" ];
};
};
})

View file

@ -1,3 +1,3 @@
final: prev: {
agenix = prev.callPackage ./pkgs/agenix.nix {};
agenix = prev.callPackage ./pkgs/agenix.nix { };
}

View file

@ -9,10 +9,11 @@
replaceVars,
ageBin ? "${age}/bin/age",
shellcheck,
}: let
}:
let
bin = "${placeholder "out"}/bin/agenix";
in
stdenv.mkDerivation rec {
stdenv.mkDerivation rec {
pname = "agenix";
version = "0.15.0";
src = replaceVars ./agenix.sh {
@ -24,7 +25,7 @@ in
};
dontUnpack = true;
doInstallCheck = true;
installCheckInputs = [shellcheck];
installCheckInputs = [ shellcheck ];
postInstallCheck = ''
shellcheck ${bin}
${bin} -h | grep ${version}
@ -62,4 +63,4 @@ in
'';
meta.description = "age-encrypted secrets for NixOS";
}
}

View file

@ -6,6 +6,6 @@
stdenvNoCC.mkDerivation rec {
name = "agenix-doc";
src = ../doc;
phases = ["mmdocPhase"];
phases = [ "mmdocPhase" ];
mmdocPhase = "${mmdoc}/bin/mmdoc agenix $src $out";
}

View file

@ -1,6 +1,7 @@
# Do not copy this! It is insecure. This is only okay because we are testing.
{config, ...}: {
system.activationScripts.agenixInstall.deps = ["installSSHHostKeys"];
{ config, ... }:
{
system.activationScripts.agenixInstall.deps = [ "installSSHHostKeys" ];
system.activationScripts.installSSHHostKeys.text = ''
USER1_UID="${toString config.users.users.user1.uid}"

View file

@ -1,21 +1,22 @@
{
nixpkgs ? <nixpkgs>,
pkgs ?
import <nixpkgs> {
pkgs ? import <nixpkgs> {
inherit system;
config = {};
config = { };
},
system ? builtins.currentSystem,
home-manager ? <home-manager>,
}:
pkgs.nixosTest {
name = "agenix-integration";
nodes.system1 = {
nodes.system1 =
{
config,
pkgs,
options,
...
}: {
}:
{
imports = [
../modules/age.nix
./install_ssh_host_keys.nix
@ -29,10 +30,10 @@ pkgs.nixosTest {
leading-hyphen.file = ../example/-leading-hyphen-filename.age;
};
age.identityPaths = options.age.identityPaths.default ++ ["/etc/ssh/this_key_wont_exist"];
age.identityPaths = options.age.identityPaths.default ++ [ "/etc/ssh/this_key_wont_exist" ];
environment.systemPackages = [
(pkgs.callPackage ../pkgs/agenix.nix {})
(pkgs.callPackage ../pkgs/agenix.nix { })
];
users = {
@ -47,7 +48,9 @@ pkgs.nixosTest {
};
};
home-manager.users.user1 = {options, ...}: {
home-manager.users.user1 =
{ options, ... }:
{
imports = [
../modules/age-home.nix
];
@ -55,7 +58,7 @@ pkgs.nixosTest {
home.stateVersion = pkgs.lib.trivial.release;
age = {
identityPaths = options.age.identityPaths.default ++ ["/home/user1/.ssh/this_key_wont_exist"];
identityPaths = options.age.identityPaths.default ++ [ "/home/user1/.ssh/this_key_wont_exist" ];
secrets.secret2 = {
# Only decryptable by user1's key
file = ../example/secret2.age;
@ -71,13 +74,15 @@ pkgs.nixosTest {
};
};
testScript = let
testScript =
let
user = "user1";
password = "password1234";
secret2 = "world!";
hyphen-secret = "filename started with hyphen";
armored-secret = "Hello World!";
in ''
in
''
system1.wait_for_unit("multi-user.target")
system1.wait_until_succeeds("pgrep -f 'agetty.*tty1'")
system1.sleep(2)

View file

@ -3,7 +3,8 @@
pkgs,
options,
...
}: let
}:
let
secret = "hello";
testScript = pkgs.writeShellApplication {
name = "agenix-integration";
@ -11,18 +12,19 @@
grep "${secret}" "${config.age.secrets.system-secret.path}"
'';
};
in {
in
{
imports = [
./install_ssh_host_keys_darwin.nix
../modules/age.nix
];
age = {
identityPaths = options.age.identityPaths.default ++ ["/etc/ssh/this_key_wont_exist"];
identityPaths = options.age.identityPaths.default ++ [ "/etc/ssh/this_key_wont_exist" ];
secrets.system-secret.file = ../example/secret1.age;
};
environment.systemPackages = [testScript];
environment.systemPackages = [ testScript ];
system.stateVersion = 6;
}

View file

@ -4,11 +4,12 @@
options,
lib,
...
}: {
imports = [../modules/age-home.nix];
}:
{
imports = [ ../modules/age-home.nix ];
age = {
identityPaths = options.age.identityPaths.default ++ ["/Users/user1/.ssh/this_key_wont_exist"];
identityPaths = options.age.identityPaths.default ++ [ "/Users/user1/.ssh/this_key_wont_exist" ];
secrets.user-secret.file = ../example/secret2.age;
};
@ -18,14 +19,18 @@
stateVersion = lib.trivial.release;
};
home.file = let
home.file =
let
name = "agenix-home-integration";
in {
in
{
${name}.source = pkgs.writeShellApplication {
inherit name;
text = let
text =
let
secret = "world!";
in ''
in
''
diff -q "${config.age.secrets.user-secret.path}" <(printf '${secret}\n')
'';
};