refactor(secrets): simplify home manager secrets
check / lint (push) Successful in 14s

This commit is contained in:
hektor committed 2026-11-06 16:36:46 +01:00
1 parent c002826bdc
commit 54e235367e
7 files changed
+377 -350

No files matched your search

+38 -48
View File
@@ -2,61 +2,51 @@
config,
lib,
pkgs,
lib',
osConfig ? null,
inputs ? null,
...
}:
let
sops = lib'.sopsAvailability config osConfig;
standalone = osConfig == null;
authReady = config.secrets.templatePath ? "opencode/auth.json";
in
{
config = lib.mkIf config.ai-tools.opencode.enable (
lib.optionalAttrs standalone {
sops = {
secrets = lib'.mkSopsSecrets "${toString inputs.nix-secrets}/secrets" null {
opencode = [ "api-key" ];
};
templates."opencode/auth.json" = {
path = "${config.home.homeDirectory}/.local/share/opencode/auth.json";
content = builtins.toJSON {
zai-coding-plan = {
type = "api";
key = config.sops.placeholder."opencode/api-key";
};
};
};
};
}
// {
warnings =
lib.optional (!sops.available)
"opencode is enabled, but sops templates are not available. the opencode API key will not be configured.";
config = lib.mkIf config.ai-tools.opencode.enable {
secrets.files.opencode = [ "api-key" ];
home.packages = [ pkgs.opencode ];
home.file.".config/opencode/opencode.json".text = builtins.toJSON {
"$schema" = "https://opencode.ai/config.json";
permission = {
external_directory = {
"/run/secrets/" = "deny";
"~/.config/sops/age/keys.txt" = "deny";
"~/.ssh/id_rsa" = "deny";
"~/.ssh/id_ed25519" = "deny";
"~/.ssh/id_ecdsa" = "deny";
"~/.ssh/id_dsa" = "deny";
"/etc/ssh/ssh_host_rsa_key" = "deny";
"/etc/ssh/ssh_host_ed25519_key" = "deny";
"/etc/ssh/ssh_host_ecdsa_key" = "deny";
"/etc/ssh/ssh_host_dsa_key" = "deny";
};
command = {
sops = "deny";
};
secrets.templates."opencode/auth.json" = {
path = ".local/share/opencode/auth.json";
content = builtins.toJSON {
zai-coding-plan = {
type = "api";
key = config.sops.placeholder."opencode/api-key";
};
plugin = [ "@mohak34/opencode-notifier@latest" ];
};
}
);
};
warnings =
lib.optional (!authReady)
"opencode is enabled but its auth template is unavailable. the opencode API key will not be configured.";
home.packages = [ pkgs.opencode ];
home.file.".config/opencode/opencode.json".text = builtins.toJSON {
"$schema" = "https://opencode.ai/config.json";
permission = {
external_directory = {
"/run/secrets/" = "deny";
"~/.config/sops/age/keys.txt" = "deny";
"~/.ssh/id_rsa" = "deny";
"~/.ssh/id_ed25519" = "deny";
"~/.ssh/id_ecdsa" = "deny";
"~/.ssh/id_dsa" = "deny";
"/etc/ssh/ssh_host_rsa_key" = "deny";
"/etc/ssh/ssh_host_ed25519_key" = "deny";
"/etc/ssh/ssh_host_ecdsa_key" = "deny";
"/etc/ssh/ssh_host_dsa_key" = "deny";
};
command = {
sops = "deny";
};
};
plugin = [ "@mohak34/opencode-notifier@latest" ];
};
};
}
+36 -45
View File
@@ -2,60 +2,51 @@
config,
lib,
pkgs,
lib',
osConfig ? null,
inputs ? null,
...
}:
let
cfg = config.anki;
sops = lib'.sopsAvailability config osConfig;
standalone = osConfig == null;
syncReady = config.secrets.path ? "anki/sync-user" && config.secrets.path ? "anki/sync-key";
in
{
options.anki.enable = lib.mkEnableOption "Anki";
config = lib.mkIf cfg.enable (
lib.optionalAttrs standalone {
sops.secrets = lib'.mkSopsSecrets "${toString inputs.nix-secrets}/secrets" null {
anki = [
"sync-user"
"sync-key"
];
};
}
// {
warnings = lib.optional (
!sops.available
) "anki is enabled but sops secrets are not available. anki sync will not be configured.";
config = lib.mkIf cfg.enable {
secrets.files.anki = [
"sync-user"
"sync-key"
];
programs.anki = {
enable = true;
package = config.nixgl.wrap pkgs.anki;
addons = with pkgs.ankiAddons; [
(anki-connect.withConfig {
# https://git.sr.ht/~foosoft/anki-connect/tree/master/item/plugin/config.json
config = {
apiKey = null;
apiLogPath = null;
webBindAddress = "127.0.0.1";
webBindPort = 8765;
webCorsOriginList = [ "http://localhost" ];
ignoreOriginList = [ ];
};
})
puppy-reinforcement
review-heatmap
];
profiles."User 1".sync = lib.mkIf sops.available {
usernameFile = "${sops.secrets."anki/sync-user".path}";
keyFile = "${sops.secrets."anki/sync-key".path}";
};
};
warnings = lib.optional (
!syncReady
) "anki is enabled but its secrets are unavailable. anki sync will not be configured.";
# allow Anki to write prefs
home.file."${config.xdg.dataHome}/Anki2/prefs21.db".enable = lib.mkForce false;
}
);
programs.anki = {
enable = true;
package = config.nixgl.wrap pkgs.anki;
addons = with pkgs.ankiAddons; [
(anki-connect.withConfig {
# https://git.sr.ht/~foosoft/anki-connect/tree/master/item/plugin/config.json
config = {
apiKey = null;
apiLogPath = null;
webBindAddress = "127.0.0.1";
webBindPort = 8765;
webCorsOriginList = [ "http://localhost" ];
ignoreOriginList = [ ];
};
})
puppy-reinforcement
review-heatmap
];
profiles."User 1".sync = lib.mkIf syncReady {
usernameFile = config.secrets.path."anki/sync-user";
keyFile = config.secrets.path."anki/sync-key";
};
};
# allow Anki to write prefs
home.file."${config.xdg.dataHome}/Anki2/prefs21.db".enable = lib.mkForce false;
};
}
+119 -125
View File
@@ -1,22 +1,24 @@
{
config,
inputs,
lib,
lib',
osConfig ? null,
...
}:
let
cfg = config.git;
sops = lib'.sopsAvailability config osConfig;
standalone = osConfig == null;
identities = [
"personal"
"work"
];
personalEmail = sops.templates.".gitconfig.email".path;
workEmail = sops.templates.".gitconfig.work.email".path;
emailTemplate = key: ".gitconfig.${key}.email";
emailInclude =
key:
lib.optionalAttrs (config.secrets.templatePath ? ${emailTemplate key}) {
include.path = config.secrets.templatePath.${emailTemplate key};
};
workSettings = {
include.path = workEmail;
workSettings = emailInclude "work" // {
core.longpaths = true;
user = {
name = "Hektor Misplon";
@@ -47,123 +49,116 @@ in
./gitlab.nix
];
config = lib.mkIf cfg.enable (
lib.optionalAttrs standalone {
sops = {
secrets = lib'.mkSopsSecrets "${toString inputs.nix-secrets}/secrets" null {
email = [
"personal"
"work"
];
};
config = lib.mkIf cfg.enable {
secrets.files.email = identities;
templates = {
".gitconfig.email".content = ''
[user]
email = ${config.sops.placeholder."email/personal"}
'';
".gitconfig.work.email".content = ''
[user]
email = ${config.sops.placeholder."email/work"}
'';
};
};
}
// {
warnings = lib.optional (
!config.nvim.enable
) "'git.enable' is enabled without 'nvim.enable' (not using 'nvimdiff')";
secrets.templates = lib.genAttrs' identities (
key:
lib.nameValuePair (emailTemplate key) {
content = ''
[user]
email = ${config.sops.placeholder."email/${key}"}
'';
}
);
xdg.configFile."git/ignore".source = ./global.gitignore;
warnings = lib.optional (
!config.nvim.enable
) "'git.enable' is enabled without 'nvim.enable' (not using 'nvimdiff')";
programs.git = {
enable = true;
settings = lib.mkMerge [
{
user = {
name = "Hektor Misplon";
username = "hektor";
signingKey = "AEB98353B8D72E465C4236435151AF79E723F21C";
};
alias = {
d = "diff";
ds = "diff --staged";
a = "add";
ap = "add -p";
c = "commit";
cm = "commit -m";
cam = "commit --amend";
l = ''log --pretty=format:(%an)\ \ %h\ \ %ad\ \ %s --date=short'';
s = "status --short";
sv = "status --verbose";
co = "checkout";
cob = "checkout -b";
pullr = "pull --rebase --autostash";
pushf = "push --force-with-lease";
al = "!git config -l | grep alias | cut -c 7-";
alf = "!git config -l | grep alias | cut -c 7- | fzf";
"al-" =
''!git config --local --unset $(git config -l | grep alias | cut --delimiter="=" --fields=1 | fzf)'';
rs = "restore --staged";
rb = "rebase";
rbi = "rebase -i";
wt = "worktree";
wtc = ''config remote.origin.fetch "+refs/heads/*:refs/remotes/origin/*"'';
};
color = {
ui = "auto";
diff = {
meta = "yellow bold";
commit = "green bold";
frag = "magenta bold";
old = "red bold";
new = "green bold";
whitespace = "red reverse";
};
"diff-highlight" = {
oldNormal = "red bold";
oldHighlight = "red bold 52";
newNormal = "green bold";
newHighlight = "green bold 22";
};
branch = {
current = "normal bold";
local = "normal";
remote = "normal italic";
};
status = {
added = "green";
changed = "yellow";
untracked = "normal italic";
};
};
credential.helper = "cache --timeout=3600";
init.defaultBranch = "main";
log = {
date = "relative";
abbrevCommit = true;
};
merge.conflictStyle = "diff3";
diff.colorMoved = "zebra";
commit.gpgSign = false;
interactive.singleKey = true;
pull.rebase = true;
rerere.enabled = true;
}
(lib.mkIf config.nvim.enable {
core.editor = "nvim";
diff.tool = "nvimdiff";
merge.tool = "nvimdiff";
difftool.prompt = false;
mergetool = {
prompt = false;
keepBackup = false;
};
})
];
xdg.configFile."git/ignore".source = ./global.gitignore;
includes = [
{ path = personalEmail; }
programs.git = {
enable = true;
settings = lib.mkMerge [
{
user = {
name = "Hektor Misplon";
username = "hektor";
signingKey = "AEB98353B8D72E465C4236435151AF79E723F21C";
};
alias = {
d = "diff";
ds = "diff --staged";
a = "add";
ap = "add -p";
c = "commit";
cm = "commit -m";
cam = "commit --amend";
l = ''log --pretty=format:(%an)\ \ %h\ \ %ad\ \ %s --date=short'';
s = "status --short";
sv = "status --verbose";
co = "checkout";
cob = "checkout -b";
pullr = "pull --rebase --autostash";
pushf = "push --force-with-lease";
al = "!git config -l | grep alias | cut -c 7-";
alf = "!git config -l | grep alias | cut -c 7- | fzf";
"al-" =
''!git config --local --unset $(git config -l | grep alias | cut --delimiter="=" --fields=1 | fzf)'';
rs = "restore --staged";
rb = "rebase";
rbi = "rebase -i";
wt = "worktree";
wtc = ''config remote.origin.fetch "+refs/heads/*:refs/remotes/origin/*"'';
};
color = {
ui = "auto";
diff = {
meta = "yellow bold";
commit = "green bold";
frag = "magenta bold";
old = "red bold";
new = "green bold";
whitespace = "red reverse";
};
"diff-highlight" = {
oldNormal = "red bold";
oldHighlight = "red bold 52";
newNormal = "green bold";
newHighlight = "green bold 22";
};
branch = {
current = "normal bold";
local = "normal";
remote = "normal italic";
};
status = {
added = "green";
changed = "yellow";
untracked = "normal italic";
};
};
credential.helper = "cache --timeout=3600";
init.defaultBranch = "main";
log = {
date = "relative";
abbrevCommit = true;
};
merge.conflictStyle = "diff3";
diff.colorMoved = "zebra";
commit.gpgSign = false;
interactive.singleKey = true;
pull.rebase = true;
rerere.enabled = true;
}
(lib.mkIf config.nvim.enable {
core.editor = "nvim";
diff.tool = "nvimdiff";
merge.tool = "nvimdiff";
difftool.prompt = false;
mergetool = {
prompt = false;
keepBackup = false;
};
})
];
includes =
lib.optional (config.secrets.templatePath ? ${emailTemplate "personal"}) {
path = config.secrets.templatePath.${emailTemplate "personal"};
}
++ [
{
condition = "gitdir:~/work/";
contents = workSettings;
@@ -175,7 +170,6 @@ in
contentSuffix = "gitconfig-work";
}
];
};
}
);
};
};
}
+80 -10
View File
@@ -2,24 +2,94 @@
config,
lib,
pkgs,
inputs,
lib',
osConfig ? null,
...
}:
let
cfg = config.secrets;
sops = lib'.sopsAvailability config osConfig;
standalone = !sops.osAvailable;
sopsDir = "${toString inputs.nix-secrets}/secrets";
in
{
imports = [ ./vault.nix ];
options.secrets = {
enable = lib.mkEnableOption "secrets";
vault.enable = lib.mkEnableOption "vault CLI";
vault = {
enable = lib.mkEnableOption "vault CLI";
files = lib.mkOption {
type = lib.types.attrsOf (lib.types.listOf lib.types.str);
};
templates = lib.mkOption {
type = lib.types.attrsOf (
lib.types.submodule {
options = {
path = lib.mkOption {
type = lib.types.nullOr lib.types.str;
default = null;
};
content = lib.mkOption {
type = lib.types.str;
};
};
}
);
};
path = lib.mkOption {
type = lib.types.attrsOf lib.types.str;
readOnly = true;
};
templatePath = lib.mkOption {
type = lib.types.attrsOf lib.types.str;
readOnly = true;
};
};
imports = [ ./vault.nix ];
config = lib.mkMerge [
{
secrets.path = lib.optionalAttrs sops.available (
lib.mapAttrs (_: secret: secret.path) sops.secrets
);
config = lib.mkIf config.secrets.enable {
home.packages = with pkgs; [
age
age-plugin-yubikey
sops
];
};
secrets.templatePath = lib.optionalAttrs sops.available (
lib.mapAttrs (_: template: template.path) sops.templates
);
}
(lib.mkIf cfg.enable (
lib.optionalAttrs standalone {
sops = {
secrets = lib'.mkSopsSecrets sopsDir null cfg.files;
templates = lib.mapAttrs (
_: t:
{
inherit (t) content;
}
// lib.optionalAttrs (t.path != null) {
path = "${config.home.homeDirectory}/${t.path}";
}
) cfg.templates;
};
}
// {
home.packages = [
pkgs.age
pkgs.age-plugin-yubikey
pkgs.sops
];
warnings = lib.optional (
!sops.available && (cfg.files != { } || cfg.templates != { })
) "secrets declared, but sops unavailable.";
}
))
];
}
+97 -110
View File
@@ -2,15 +2,11 @@
config,
lib,
pkgs,
lib',
osConfig ? null,
inputs ? null,
...
}:
let
sops = lib'.sopsAvailability config osConfig;
standalone = osConfig == null;
syncReady = config.secrets.templatePath ? "taskrc.d/sync";
syncHook = pkgs.writers.writePython3 "on-exit.sync.py" { flakeIgnore = [ "E501" ]; } ''
# Source: <https://gist.github.com/danmou/83079feac22307813178d7f8c456c544>
@@ -61,111 +57,102 @@ in
{
options.taskwarrior.enable = lib.mkEnableOption "taskwarrior";
config = lib.mkIf config.taskwarrior.enable (
lib.optionalAttrs standalone {
sops = {
secrets = lib'.mkSopsSecrets "${toString inputs.nix-secrets}/secrets" null {
taskwarrior = [
"sync-server-url"
"sync-server-client-id"
"sync-encryption-secret"
];
};
config = lib.mkIf config.taskwarrior.enable {
secrets.files.taskwarrior = [
"sync-server-url"
"sync-server-client-id"
"sync-encryption-secret"
];
templates."taskrc.d/sync" = {
content = ''
sync.server.url=${config.sops.placeholder."taskwarrior/sync-server-url"}
sync.server.client_id=${config.sops.placeholder."taskwarrior/sync-server-client-id"}
sync.encryption_secret=${config.sops.placeholder."taskwarrior/sync-encryption-secret"}
'';
};
secrets.templates."taskrc.d/sync".content = ''
sync.server.url=${config.sops.placeholder."taskwarrior/sync-server-url"}
sync.server.client_id=${config.sops.placeholder."taskwarrior/sync-server-client-id"}
sync.encryption_secret=${config.sops.placeholder."taskwarrior/sync-encryption-secret"}
'';
warnings =
lib.optional (!syncReady)
"taskwarrior is enabled but its sync template is unavailable. taskwarrior sync will not be configured.";
home.file = {
".local/share/task/hooks/on-exit.sync.py".source = syncHook;
".local/share/task/scripts/sync-and-notify.sh".source = lib.getExe syncAndNotify;
};
programs.taskwarrior = {
enable = true;
package = pkgs.taskwarrior3;
colorTheme = "dark-256";
config = {
# taskrc.d/aliases
"alias.a" = "add";
"alias.burndown" = "burndown.daily";
"alias.e" = "modify";
"alias.rm" = "delete";
# taskrc.d/colors
"color.active" = "bold white on black";
"color.alternate" = "";
"color.blocked" = "gray15";
"color.blocking" = "bold";
"color.due" = "";
"color.due.today" = "";
"color.overdue" = "";
"color.scheduled" = "";
"color.uda.priority.H" = "bold";
"color.uda.priority.L" = "";
"color.uda.priority.M" = "";
"color.until" = "";
# taskrc.d/contexts
"context.home.read" = "project:home";
"context.home.write" = "project:home";
"context.studies.read" = "project:studies";
"context.studies.write" = "project:studies";
"context.work.read" = "tags:work";
# taskrc.d/reports
"report.in.columns" = "id,description";
"report.in.description" = "Inbox (untagged)";
"report.in.filter" = "status:pending tags.none:";
"report.in.labels" = "ID,Description";
"report.in.sort" = "entry+";
"report.minimal.columns" = "id,description";
"report.minimal.labels" = ",";
"report.next.columns" =
"id,start.age,entry.age,depends,project,tags,scheduled.countdown,due.relative,until.remaining,description";
"report.next.filter" = "status:pending -WAITING -BLOCKED limit:page";
"report.next.labels" = "ID,Active,Age,Deps,Project,Tag,S,Due,Until,Description";
"report.recent.columns" = "id,description,entry";
"report.recent.description" = "Recently added";
"report.recent.labels" = "ID,Description,Age";
"report.recent.sort" = "entry+";
# taskrc.d/udas
"uda.url.label" = "URL";
"uda.url.type" = "string";
# taskrc.d/urgency
"urgency.uda.priority.L.coefficient" = "-1.0";
"urgency.user.project.admin.coefficient" = "1.0";
"urgency.user.project.creative.coefficient" = "0.0";
"urgency.user.project.groceries.coefficient" = "0.5";
"urgency.user.project.home.coefficient" = "0.2";
"urgency.user.project.personal.coefficient" = "0.5";
"urgency.user.project.side.coefficient" = "0.0";
"urgency.user.project.studies.coefficient" = "1.0";
# misc
"news.version" = "3.5.0";
"rc.json.array" = "on";
"rc.verbose" = "nothing";
"search.case.sensitive" = "no";
"recurrence" = "off";
"reserved.lines" = "3";
};
}
// {
warnings =
lib.optional (!sops.available)
"taskwarrior is enabled, but sops templates are not available. taskwarrior sync will not be configured.";
home.file = {
".local/share/task/hooks/on-exit.sync.py".source = syncHook;
".local/share/task/scripts/sync-and-notify.sh".source = lib.getExe syncAndNotify;
};
programs.taskwarrior = {
enable = true;
package = pkgs.taskwarrior3;
colorTheme = "dark-256";
config = {
# taskrc.d/aliases
"alias.a" = "add";
"alias.burndown" = "burndown.daily";
"alias.e" = "modify";
"alias.rm" = "delete";
# taskrc.d/colors
"color.active" = "bold white on black";
"color.alternate" = "";
"color.blocked" = "gray15";
"color.blocking" = "bold";
"color.due" = "";
"color.due.today" = "";
"color.overdue" = "";
"color.scheduled" = "";
"color.uda.priority.H" = "bold";
"color.uda.priority.L" = "";
"color.uda.priority.M" = "";
"color.until" = "";
# taskrc.d/contexts
"context.home.read" = "project:home";
"context.home.write" = "project:home";
"context.studies.read" = "project:studies";
"context.studies.write" = "project:studies";
"context.work.read" = "tags:work";
# taskrc.d/reports
"report.in.columns" = "id,description";
"report.in.description" = "Inbox (untagged)";
"report.in.filter" = "status:pending tags.none:";
"report.in.labels" = "ID,Description";
"report.in.sort" = "entry+";
"report.minimal.columns" = "id,description";
"report.minimal.labels" = ",";
"report.next.columns" =
"id,start.age,entry.age,depends,project,tags,scheduled.countdown,due.relative,until.remaining,description";
"report.next.filter" = "status:pending -WAITING -BLOCKED limit:page";
"report.next.labels" = "ID,Active,Age,Deps,Project,Tag,S,Due,Until,Description";
"report.recent.columns" = "id,description,entry";
"report.recent.description" = "Recently added";
"report.recent.labels" = "ID,Description,Age";
"report.recent.sort" = "entry+";
# taskrc.d/udas
"uda.url.label" = "URL";
"uda.url.type" = "string";
# taskrc.d/urgency
"urgency.uda.priority.L.coefficient" = "-1.0";
"urgency.user.project.admin.coefficient" = "1.0";
"urgency.user.project.creative.coefficient" = "0.0";
"urgency.user.project.groceries.coefficient" = "0.5";
"urgency.user.project.home.coefficient" = "0.2";
"urgency.user.project.personal.coefficient" = "0.5";
"urgency.user.project.side.coefficient" = "0.0";
"urgency.user.project.studies.coefficient" = "1.0";
# misc
"news.version" = "3.5.0";
"rc.json.array" = "on";
"rc.verbose" = "nothing";
"search.case.sensitive" = "no";
"recurrence" = "off";
"reserved.lines" = "3";
};
extraConfig = lib.optionalString sops.available ''
include ${sops.templates."taskrc.d/sync".path}
'';
};
}
);
extraConfig = lib.optionalString syncReady ''
include ${config.secrets.templatePath."taskrc.d/sync"}
'';
};
};
}