Skip to content

Commit

Permalink
feat: add darwin support
Browse files Browse the repository at this point in the history
  • Loading branch information
veselyn committed Dec 13, 2024
1 parent fc3e595 commit 998374e
Show file tree
Hide file tree
Showing 6 changed files with 213 additions and 121 deletions.
3 changes: 2 additions & 1 deletion flake.nix
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,8 @@
};

}) // {
nixosModules.default = import ./modules/op-secrets.nix;
darwinModules.default = import ./modules/darwin.nix;
nixosModules.default = import ./modules/nixos.nix;

# test is a hostname for our machine
nixosConfigurations.test = nixpkgs.lib.nixosSystem {
Expand Down
56 changes: 56 additions & 0 deletions modules/common.nix
Original file line number Diff line number Diff line change
@@ -0,0 +1,56 @@
{ lib, pkgs, config, ... }:
with lib;
let
inherit (import ./types.nix {
inherit lib;
inherit config;
})
secretFileDeclaration;
in {
options.opnix = {
opBin = mkOption {
type = types.str;
default = "${pkgs._1password-cli}/bin/op";
description = "The 1Password CLI `op` executable to use";
};
environmentFile = mkOption {
type = types.str;
description = ''
Path to a environment file which contains your service account token. Format should be `OP_SERVICE_ACCOUNT_TOKEN="{ your token here }"`. This is used to authorize the 1Password CLI.'';
};
secretsDir = mkOption {
type = types.path;
default = "/run/opnix";
description = ''
Directory where secrets are symlinked to
'';
};
secretsMountPoint = mkOption {
type = types.addCheck types.str (s:
(trim s) != "" # non-empty
&& (builtins.match ".+/" s) == null) # without trailing slash
// {
description =
"${types.str.description} (with check: non-empty without trailing slash)";
};
default = "/run/opnix.d";
};
secrets = mkOption {
type = types.attrsOf secretFileDeclaration;
description = "The secrets you want to use in your NixOS deployment";
default = { };
example = {
my-secret = {
source = "{{ op://VaultName/ItemName/FieldName }}";
mode = "0400";
inherit (config.services.some_service) user;
inherit (config.services.some_service) group;
};
another-secret.source = ''
[SomeTomlHeader]
SomeValue = "{{ op://AnotherVault/AnotherItem/AnotherField }}"
'';
};
};
};
}
53 changes: 53 additions & 0 deletions modules/darwin.nix
Original file line number Diff line number Diff line change
@@ -0,0 +1,53 @@
toplevel @ {
config,
lib,
pkgs,
...
}: let
inherit (lib) mkIf mkMerge;

cfg = config.opnix;
scripts = import ./scripts.nix toplevel;
in {
imports = [./common.nix];

config = let
opnixScript = ''
${scripts.installSecrets}
${scripts.chownSecrets}
'';
in
mkIf (cfg.secrets != {}) (mkMerge [
{
launchd.daemons.activate-opnix = {
script = ''
set -euo pipefail
export PATH="${pkgs.gnugrep}/bin:${pkgs.coreutils}/bin:@out@/sw/bin:/usr/bin:/bin:/usr/sbin:/sbin"
source ${cfg.environmentFile}
export OP_SERVICE_ACCOUNT_TOKEN
${opnixScript}
'';
serviceConfig = {
RunAtLoad = true;
KeepAlive.SuccessfulExit = false;
};
};
}
{
system.activationScripts = {
# if no generation already exists, rely on the launchd startup job;
# otherwise, if there already is an existing generation, reprovision
# secrets because we did a darwin-rebuild
postActivation.text = lib.mkAfter ''
${scripts.setOpnixGeneration}
(( _opnix_generation > 1 )) && {
# shellcheck disable=SC1091
source ${cfg.environmentFile}
export OP_SERVICE_ACCOUNT_TOKEN
${opnixScript}
}
'';
};
}
]);
}
77 changes: 77 additions & 0 deletions modules/nixos.nix
Original file line number Diff line number Diff line change
@@ -0,0 +1,77 @@
toplevel @ {
config,
lib,
...
}: let
inherit
(lib)
mkIf
mkMerge
mkOption
types
;

cfg = config.opnix;
scripts = import ./scripts.nix toplevel;
in {
imports = [./common.nix];

options.opnix = {
systemdWantedBy = mkOption {
type = types.listOf types.str;
default = [];
description = ''
A list of `systemd` service names that depend on secrets from `opnix`. This option will set `after = [ "opnix.service" ]` and `wants = [ "opnix.service" ]` for each specified `systemd` unit.'';
example = ["homepage-dashboard" "wg-quick-vpn"];
};
};

config = let
opnixScript = ''
${scripts.installSecrets}
${scripts.chownSecrets}
'';
in
mkIf (cfg.secrets != {}) (mkMerge [
{
systemd.services.opnix = {
wants = ["network-online.target"];
after = ["network.target" "network-online.target"];

serviceConfig = {
Type = "oneshot";
EnvironmentFile = cfg.environmentFile;
RemainAfterExit = true;
};

script = opnixScript;
};
}
{
system.activationScripts.opnix-on-rebuild = {
# if no generation already exists, rely on the systemd startup job;
# otherwise, if there already is an existing generation, reprovision
# secrets because we did a nixos-rebuild
text = ''
${scripts.setOpnixGeneration}
(( _opnix_generation > 1 )) && {
source ${cfg.environmentFile}
export OP_SERVICE_ACCOUNT_TOKEN
${opnixScript}
}
'';
deps = ["usrbinenv"];
};
}
{
systemd.services = builtins.listToAttrs (builtins.map (systemdName: {
name = systemdName;
value = {
after = ["opnix.service"];
wants = ["opnix.service"];
};
})
cfg.systemdWantedBy);
}
]);
}
112 changes: 0 additions & 112 deletions modules/op-secrets.nix

This file was deleted.

33 changes: 25 additions & 8 deletions modules/scripts.nix
Original file line number Diff line number Diff line change
@@ -1,9 +1,11 @@
{ config, lib, pkgs, ... }:
{ config, lib, options, pkgs, ... }:
let
isDarwin = lib.attrsets.hasAttrByPath ["environment" "darwinConfig"] options;

cfg = config.opnix;
op = cfg.opBin;
op_tmp_dir = "/root/op_tmp";
op_cfg_dir = "/root/.config/op";
op_tmp_dir = "${lib.optionalString isDarwin "/var"}/root/op_tmp";
op_cfg_dir = "${lib.optionalString isDarwin "/var"}/root/.config/op";
# fixes permissions issues with op session files
createTmpDirShim = ''
rm -rf ${op_tmp_dir}
Expand All @@ -23,10 +25,20 @@ let
fi
chmod 600 ${op_cfg_dir}/config
'';
mountCommand = ''
grep -q "${cfg.secretsMountPoint} ramfs" /proc/mounts ||
${pkgs.util-linux}/bin/mount -t ramfs none "${cfg.secretsMountPoint}" -o nodev,nosuid,mode=0751
'';
mountCommand =
if isDarwin
then ''
if ! diskutil info "${cfg.secretsMountPoint}" &> /dev/null; then
num_sectors=1048576
dev=$(hdiutil attach -nomount ram://"$num_sectors" | sed 's/[[:space:]]*$//')
newfs_hfs -v agenix "$dev"
mount -t hfs -o nobrowse,nodev,nosuid,-m=0751 "$dev" "${cfg.secretsMountPoint}"
fi
''
else ''
grep -q "${cfg.secretsMountPoint} ramfs" /proc/mounts ||
${pkgs.util-linux}/bin/mount -t ramfs none "${cfg.secretsMountPoint}" -o nodev,nosuid,mode=0751
'';
setOpnixGeneration = ''
_opnix_generation="$(basename "$(readlink ${cfg.secretsDir})" || echo 0)"
'';
Expand All @@ -40,7 +52,10 @@ let
mkdir -p "${cfg.secretsMountPoint}/$_opnix_generation"
chmod 0751 "${cfg.secretsMountPoint}/$_opnix_generation"
'';
chownGroup = "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 = ''
Expand Down Expand Up @@ -76,6 +91,7 @@ let
TMP_FILE="$_truePath.tmp"
mkdir -p "$(dirname "$_truePath")"
# shellcheck disable=SC2050
[ "${secretType.path}" != "${cfg.secretsDir}/${secretType.name}" ] && mkdir -p "$(dirname "${secretType.path}")"
(
umask u=r,g=,o=
Expand All @@ -89,6 +105,7 @@ let
mv -f "$TMP_FILE" "$_truePath"
${lib.optionalString secretType.symlink ''
# shellcheck disable=SC2050
[ "${secretType.path}" != "${cfg.secretsDir}/${secretType.name}" ] && ln -sfT "${cfg.secretsDir}/${secretType.name}" "${secretType.path}"
''}
'';
Expand Down

0 comments on commit 998374e

Please sign in to comment.