Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add home-manager module #180

Merged
merged 7 commits into from
May 12, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
13 changes: 11 additions & 2 deletions .github/workflows/ci.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -25,8 +25,17 @@ jobs:
- run: nix build .#doc
- run: nix fmt . -- --check
- run: nix flake check
- run: |
- name: "Install nix-darwin module"
run: |
system=$(nix build --no-link --print-out-paths .#checks.x86_64-darwin.integration)
${system}/activate-user
sudo ${system}/activate
- run: sudo /run/current-system/sw/bin/agenix-integration
- name: "Test nix-darwin module"
run: |
sudo /run/current-system/sw/bin/agenix-integration
- name: "Test home-manager module"
run: |
# Do the job of `home-manager switch` in-line to avoid rate limiting
nix build .#homeConfigurations.integration-darwin.activationPackage
./result/activate
~/agenix-home-integration/bin/agenix-home-integration
21 changes: 21 additions & 0 deletions flake.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

64 changes: 47 additions & 17 deletions flake.nix
Original file line number Diff line number Diff line change
Expand Up @@ -7,12 +7,17 @@
url = "github:lnl7/nix-darwin/master";
inputs.nixpkgs.follows = "nixpkgs";
};
home-manager = {
url = "github:nix-community/home-manager";
inputs.nixpkgs.follows = "nixpkgs";
};
};

outputs = {
self,
nixpkgs,
darwin,
home-manager,
}: let
agenix = system: nixpkgs.legacyPackages.${system}.callPackage ./pkgs/agenix.nix {};
doc = system: nixpkgs.legacyPackages.${system}.callPackage ./pkgs/doc.nix {};
Expand All @@ -23,6 +28,9 @@
darwinModules.age = import ./modules/age.nix;
darwinModules.default = self.darwinModules.age;

homeManagerModules.age = import ./modules/age-home.nix;
homeManagerModules.default = self.homeManagerModules.age;

overlays.default = import ./overlay.nix;

formatter.x86_64-darwin = nixpkgs.legacyPackages.x86_64-darwin.alejandra;
Expand All @@ -49,24 +57,46 @@
packages.x86_64-linux.agenix = agenix "x86_64-linux";
packages.x86_64-linux.default = self.packages.x86_64-linux.agenix;
packages.x86_64-linux.doc = doc "x86_64-linux";
checks.x86_64-linux.integration = import ./test/integration.nix {
inherit nixpkgs;
pkgs = nixpkgs.legacyPackages.x86_64-linux;
system = "x86_64-linux";
};
checks."aarch64-darwin".integration =
(darwin.lib.darwinSystem {
system = "aarch64-darwin";
modules = [./test/integration_darwin.nix "${darwin.outPath}/pkgs/darwin-installer/installer.nix"];
})
.system;
checks."x86_64-darwin".integration =
(darwin.lib.darwinSystem {
system = "x86_64-darwin";
modules = [./test/integration_darwin.nix "${darwin.outPath}/pkgs/darwin-installer/installer.nix"];

checks =
nixpkgs.lib.genAttrs ["aarch64-darwin" "x86_64-darwin"] (system: {
integration =
(darwin.lib.darwinSystem {
inherit system;
modules = [
./test/integration_darwin.nix
"${darwin.outPath}/pkgs/darwin-installer/installer.nix"
home-manager.darwinModules.home-manager
{
home-manager = {
verbose = true;
useGlobalPkgs = true;
useUserPackages = true;
backupFileExtension = "hmbak";
users.runner = ./test/integration_hm_darwin.nix;
};
}
];
})
.system;
})
.system;
// {
x86_64-linux.integration = import ./test/integration.nix {
inherit nixpkgs home-manager;
pkgs = nixpkgs.legacyPackages.x86_64-linux;
system = "x86_64-linux";
};
};

darwinConfigurations.integration-x86_64.system = self.checks.x86_64-darwin.integration;
darwinConfigurations.integration-aarch64.system = self.checks.aarch64-darwin.integration;

darwinConfigurations.integration.system = self.checks."x86_64-darwin".integration;
# Work-around for https://github.com/nix-community/home-manager/issues/3075
legacyPackages = nixpkgs.lib.genAttrs ["aarch64-darwin" "x86_64-darwin"] (system: {
homeConfigurations.integration-darwin = home-manager.lib.homeManagerConfiguration {

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Probably because of this, usually legacyPackages is followed by the system, so maybe this should be:

${system}.homeConfigurations.integration-darwin

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

genAttrs already prefixes it with the system.

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Hmm, right, so it should be because flake-utils-plus assume anything containing legacyPackages will also contain x86-64-linux inside. I'll create an issue there.

pkgs = nixpkgs.legacyPackages.${system};
modules = [./test/integration_hm_darwin.nix];
};
});
};
}
234 changes: 234 additions & 0 deletions modules/age-home.nix
Original file line number Diff line number Diff line change
@@ -0,0 +1,234 @@
{
config,
options,
lib,
pkgs,
...
}:
with lib; let
cfg = config.age;

ageBin = lib.getExe config.age.package;

newGeneration = ''
_agenix_generation="$(basename "$(readlink "${cfg.secretsDir}")" || echo 0)"
(( ++_agenix_generation ))
echo "[agenix] creating new generation in ${cfg.secretsMountPoint}/$_agenix_generation"
mkdir -p "${cfg.secretsMountPoint}"
chmod 0751 "${cfg.secretsMountPoint}"
mkdir -p "${cfg.secretsMountPoint}/$_agenix_generation"
chmod 0751 "${cfg.secretsMountPoint}/$_agenix_generation"
'';

setTruePath = secretType: ''
${
if secretType.symlink
then ''
_truePath="${cfg.secretsMountPoint}/$_agenix_generation/${secretType.name}"
''
else ''
_truePath="${secretType.path}"
''
}
'';

installSecret = secretType: ''
${setTruePath secretType}
echo "decrypting '${secretType.file}' to '$_truePath'..."
TMP_FILE="$_truePath.tmp"

IDENTITIES=()
# shellcheck disable=2043
for identity in ${toString cfg.identityPaths}; do
test -r "$identity" || continue
IDENTITIES+=(-i)
IDENTITIES+=("$identity")
done

test "''${#IDENTITIES[@]}" -eq 0 && echo "[agenix] WARNING: no readable identities found!"

mkdir -p "$(dirname "$_truePath")"
[ "${secretType.path}" != "${cfg.secretsDir}/${secretType.name}" ] && mkdir -p "$(dirname "${secretType.path}")"
(
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}"
)
chmod ${secretType.mode} "$TMP_FILE"
mv -f "$TMP_FILE" "$_truePath"

${optionalString secretType.symlink ''
[ "${secretType.path}" != "${cfg.secretsDir}/${secretType.name}" ] && ln -sfn "${cfg.secretsDir}/${secretType.name}" "${secretType.path}"
''}
'';

testIdentities =
map
(path: ''
test -f ${path} || echo '[agenix] WARNING: config.age.identityPaths entry ${path} not present!'
'')
cfg.identityPaths;

cleanupAndLink = ''
_agenix_generation="$(basename "$(readlink "${cfg.secretsDir}")" || echo 0)"
(( ++_agenix_generation ))
echo "[agenix] symlinking new secrets to ${cfg.secretsDir} (generation $_agenix_generation)..."
ln -sfn "${cfg.secretsMountPoint}/$_agenix_generation" "${cfg.secretsDir}"

(( _agenix_generation > 1 )) && {
echo "[agenix] removing old secrets (generation $(( _agenix_generation - 1 )))..."
rm -rf "${cfg.secretsMountPoint}/$(( _agenix_generation - 1 ))"
}
'';

installSecrets = builtins.concatStringsSep "\n" (
["echo '[agenix] decrypting secrets...'"]
++ testIdentities
++ (map installSecret (builtins.attrValues cfg.secrets))
++ [cleanupAndLink]
);

secretType = types.submodule ({
config,
name,
...
}: {
options = {
name = mkOption {
type = types.str;
default = name;
description = ''
Name of the file used in ''${cfg.secretsDir}
'';
};
file = mkOption {
type = types.path;
description = ''
Age file the secret is loaded from.
'';
};
path = mkOption {
type = types.str;
default = "${cfg.secretsDir}/${config.name}";
description = ''
Path where the decrypted secret is installed.
'';
};
mode = mkOption {
type = types.str;
default = "0400";
description = ''
Permissions mode of the decrypted secret in a format understood by chmod.
'';
};
symlink = mkEnableOption "symlinking secrets to their destination" // {default = true;};
};
});

mountingScript = let
app = pkgs.writeShellApplication {
name = "agenix-home-manager-mount-secrets";
runtimeInputs = with pkgs; [coreutils];
text = ''
${newGeneration}
${installSecrets}
exit 0
'';
};
in
lib.getExe app;

userDirectory = dir: let
inherit (pkgs.stdenv.hostPlatform) isDarwin;
baseDir =
if isDarwin
then "$(getconf DARWIN_USER_TEMP_DIR)"
else "$XDG_RUNTIME_DIR";
in "${baseDir}/${dir}";

userDirectoryDescription = dir: ''
"$XDG_RUNTIME_DIR"/${dir} on linux or "$(getconf DARWIN_USER_TEMP_DIR)"/${dir} on darwin.
'';
in {
options.age = {
package = mkPackageOption pkgs "rage" {};

secrets = mkOption {
type = types.attrsOf secretType;
default = {};
description = ''
Attrset of secrets.
'';
};

identityPaths = mkOption {
type = types.listOf types.path;
default = [
"${config.home.homeDirectory}/.ssh/id_ed25519"
"${config.home.homeDirectory}/.ssh/id_rsa"
];
defaultText = litteralExpression ''
[
"''${config.home.homeDirectory}/.ssh/id_ed25519"
"''${config.home.homeDirectory}/.ssh/id_rsa"
]
'';
description = ''
Path to SSH keys to be used as identities in age decryption.
'';
};

secretsDir = mkOption {
type = types.str;
default = userDirectory "agenix";
defaultText = userDirectoryDescription "agenix";
description = ''
Folder where secrets are symlinked to
'';
};

secretsMountPoint = mkOption {
default = userDirectory "agenix.d";
defaultText = userDirectoryDescription "agenix.d";
description = ''
Where secrets are created before they are symlinked to ''${cfg.secretsDir}
'';
};
};

config = mkIf (cfg.secrets != {}) {
assertions = [
{
assertion = cfg.identityPaths != [];
message = "age.identityPaths must be set.";
}
];

systemd.user.services.agenix = lib.mkIf pkgs.stdenv.hostPlatform.isLinux {
Unit = {
Description = "agenix activation";
};
Service = {
Type = "oneshot";
ExecStart = mountingScript;
};
Install.WantedBy = ["default.target"];
};

launchd.agents.activate-agenix = {
ryantm marked this conversation as resolved.
Show resolved Hide resolved
enable = true;
config = {
ProgramArguments = [mountingScript];
KeepAlive = {
Crashed = false;
SuccessfulExit = false;
};
RunAtLoad = true;
ProcessType = "Background";
StandardOutPath = "${config.home.homeDirectory}/Library/Logs/agenix/stdout";
StandardErrorPath = "${config.home.homeDirectory}/Library/Logs/agenix/stderr";
};
};
};
}
Loading