Compare commits

..

1 Commits

Author SHA1 Message Date
Alexander Heldt 37078353c9 manatee: Add komga-reading-stats 2026-03-22 18:47:05 +01:00
38 changed files with 306 additions and 1247 deletions
Generated
+88 -193
View File
@@ -43,11 +43,11 @@
] ]
}, },
"locked": { "locked": {
"lastModified": 1782073106, "lastModified": 1773436376,
"narHash": "sha256-dnS5SaZlPqR1E0dPXaPc+lFkBwLUbAgbwsVMk7uA6dY=", "narHash": "sha256-OUPRrprbgN27BXHuWkMAPSCfLLQ/uwpWghEfKYN2iAg=",
"owner": "hyprwm", "owner": "hyprwm",
"repo": "aquamarine", "repo": "aquamarine",
"rev": "6d6e2384f381def4ea4ea81543cba4bbdac72457", "rev": "43f10d24391692bba3d762931ee35e7f17f8e8b8",
"type": "github" "type": "github"
}, },
"original": { "original": {
@@ -85,11 +85,11 @@
] ]
}, },
"locked": { "locked": {
"lastModified": 1781152676, "lastModified": 1773889306,
"narHash": "sha256-RxWs5ND31KzTG7wvMM+PMfUjyNpmIEr999lqNARaM5o=", "narHash": "sha256-PAqwnsBSI9SVC2QugvQ3xeYCB0otOwCacB1ueQj2tgw=",
"owner": "nix-community", "owner": "nix-community",
"repo": "disko", "repo": "disko",
"rev": "ff8702b4de27f72b4c78573dfb89ec74e36abdf1", "rev": "5ad85c82cc52264f4beddc934ba57f3789f28347",
"type": "github" "type": "github"
}, },
"original": { "original": {
@@ -106,11 +106,11 @@
"nixpkgs-stable": "nixpkgs-stable" "nixpkgs-stable": "nixpkgs-stable"
}, },
"locked": { "locked": {
"lastModified": 1784109865, "lastModified": 1773912849,
"narHash": "sha256-UCJ0O811UeGSe3GlLj8QxVa4ykHRfjl9rZvqFpiECME=", "narHash": "sha256-j8+nTPoUiiyyMAN0bk/8AqqkApusi38laEaQ4m45KIA=",
"owner": "nix-community", "owner": "nix-community",
"repo": "emacs-overlay", "repo": "emacs-overlay",
"rev": "422352494e740d44d7551549916655b122a9fd01", "rev": "4ce92db83efd3393ba51df6bbc06cc34f48c4475",
"type": "github" "type": "github"
}, },
"original": { "original": {
@@ -187,39 +187,25 @@
"type": "github" "type": "github"
} }
}, },
"flake-utils_3": { "gitignore": {
"inputs": { "inputs": {
"systems": "systems_6" "nixpkgs": [
"hyprland",
"pre-commit-hooks",
"nixpkgs"
]
}, },
"locked": { "locked": {
"lastModified": 1731533236, "lastModified": 1709087332,
"narHash": "sha256-l0KFg5HjrsfsO/JpG+r7fRrqm12kzFHyUHqHCVpMMbI=", "narHash": "sha256-HG2cCnktfHsKV0s4XW83gU3F57gaTljL9KNSuG6bnQs=",
"owner": "numtide", "owner": "hercules-ci",
"repo": "flake-utils", "repo": "gitignore.nix",
"rev": "11707dc2f618dd54ca8739b309ec4fc024de578b", "rev": "637db329424fd7e46cf4185293b9cc8c88c95394",
"type": "github" "type": "github"
}, },
"original": { "original": {
"owner": "numtide", "owner": "hercules-ci",
"repo": "flake-utils", "repo": "gitignore.nix",
"type": "github"
}
},
"flake-utils_4": {
"inputs": {
"systems": "systems_7"
},
"locked": {
"lastModified": 1731533236,
"narHash": "sha256-l0KFg5HjrsfsO/JpG+r7fRrqm12kzFHyUHqHCVpMMbI=",
"owner": "numtide",
"repo": "flake-utils",
"rev": "11707dc2f618dd54ca8739b309ec4fc024de578b",
"type": "github"
},
"original": {
"owner": "numtide",
"repo": "flake-utils",
"type": "github" "type": "github"
} }
}, },
@@ -251,11 +237,11 @@
] ]
}, },
"locked": { "locked": {
"lastModified": 1783963347, "lastModified": 1773898372,
"narHash": "sha256-r376E2XpakiXwModDHIxlvB6qLq4iFVEq730vxOO4JY=", "narHash": "sha256-PqeDgmyI/Df3/Mv0B81FP/ZC4KuO88YRQF5ZfeFyA4k=",
"owner": "nix-community", "owner": "nix-community",
"repo": "home-manager", "repo": "home-manager",
"rev": "a45a7c451455a51ae740ec3bce4024b312809c29", "rev": "ecf019baf47df009937b5f8c4604cee10f410a76",
"type": "github" "type": "github"
}, },
"original": { "original": {
@@ -280,11 +266,11 @@
] ]
}, },
"locked": { "locked": {
"lastModified": 1776511930, "lastModified": 1772461003,
"narHash": "sha256-fCpwFiTW0rT7oKJqr3cqHMnkwypSwQKpbtUEtxdkgrM=", "narHash": "sha256-pVICsV7FtcEeVwg5y/LFh3XFUkVJninm/P1j/JHzEbM=",
"owner": "hyprwm", "owner": "hyprwm",
"repo": "hyprcursor", "repo": "hyprcursor",
"rev": "39435900785d0c560c6ae8777d29f28617d031ef", "rev": "b62396457b9cfe2ebf24fe05404b09d2a40f8ed7",
"type": "github" "type": "github"
}, },
"original": { "original": {
@@ -309,11 +295,11 @@
] ]
}, },
"locked": { "locked": {
"lastModified": 1782566056, "lastModified": 1772461523,
"narHash": "sha256-haEZcHzYrePnjFOYSWTbxm/Nrla0aPslJfmvdCvqtVc=", "narHash": "sha256-mI6A51do+hEUzeJKk9YSWfVHdI/SEEIBi2tp5Whq5mI=",
"owner": "hyprwm", "owner": "hyprwm",
"repo": "hyprgraphics", "repo": "hyprgraphics",
"rev": "c6e7b9f673f4360bc813d3dc75028f75ee88d3f8", "rev": "7d63c04b4a2dd5e59ef943b4b143f46e713df804",
"type": "github" "type": "github"
}, },
"original": { "original": {
@@ -341,11 +327,11 @@
"xdph": "xdph" "xdph": "xdph"
}, },
"locked": { "locked": {
"lastModified": 1784043142, "lastModified": 1773865169,
"narHash": "sha256-vd4VSlddmDAToJv/twyr0O8Siq4U/sOIXeo6DFZIolc=", "narHash": "sha256-3LpwmNjKfZttXsR/CIKTn+z7GiJPeF5ENJJSS6Yjizk=",
"owner": "hyprwm", "owner": "hyprwm",
"repo": "Hyprland", "repo": "Hyprland",
"rev": "79bc0ca16e7cca40c247a9200634d150fa8193d1", "rev": "d635b499e1b2b9cf54b780ca7aee2b97cadeee89",
"type": "github" "type": "github"
}, },
"original": { "original": {
@@ -361,11 +347,11 @@
] ]
}, },
"locked": { "locked": {
"lastModified": 1782811467, "lastModified": 1771502235,
"narHash": "sha256-JP0D8r8o9+jnYk0/B5O722La+oZeC5iNQ3lonKFTmbQ=", "narHash": "sha256-aH8h5ZOiyEGtHmEyuE/eFxx8TN7a+NGDnl4V+dbzJ6E=",
"owner": "hyprwm", "owner": "hyprwm",
"repo": "contrib", "repo": "contrib",
"rev": "3dcbce715ae8b93107fa8632db15bf976862a573", "rev": "918f266dddae39fa4184a1b8bf51ec5381cf29f7",
"type": "github" "type": "github"
}, },
"original": { "original": {
@@ -407,11 +393,11 @@
] ]
}, },
"locked": { "locked": {
"lastModified": 1782563850, "lastModified": 1772467975,
"narHash": "sha256-rs/EzgrgPHbCtJjFZN4aR1HYldH/0NtGAempWVpWQTs=", "narHash": "sha256-kipyuDBxrZq+beYpZqWzGvFWm4QbayW9agAvi94vDXY=",
"owner": "hyprwm", "owner": "hyprwm",
"repo": "hyprland-guiutils", "repo": "hyprland-guiutils",
"rev": "5ba080ee036c30cb2485f2647ff8a61f7aa08178", "rev": "5e1c6b9025aaf4d578f3eff7c0eb1f0c197a9507",
"type": "github" "type": "github"
}, },
"original": { "original": {
@@ -461,11 +447,11 @@
] ]
}, },
"locked": { "locked": {
"lastModified": 1777320127, "lastModified": 1772459629,
"narHash": "sha256-Qu+Wf2Bp5qUjyn2YpZNq8a7JyzTGowhT1knrwE38a9U=", "narHash": "sha256-/iwvNUYShmmnwmz/czEUh6+0eF5vCMv0xtDW0STPIuM=",
"owner": "hyprwm", "owner": "hyprwm",
"repo": "hyprlang", "repo": "hyprlang",
"rev": "090117506ddc3d7f26e650ff344d378c2ec329cc", "rev": "7615ee388de18239a4ab1400946f3d0e498a8186",
"type": "github" "type": "github"
}, },
"original": { "original": {
@@ -513,11 +499,11 @@
] ]
}, },
"locked": { "locked": {
"lastModified": 1782554491, "lastModified": 1772462885,
"narHash": "sha256-+p3MlyN/nqRefcf2IckPlGRUn9+hielqpS9XClbLleM=", "narHash": "sha256-5pHXrQK9zasMnIo6yME6EOXmWGFMSnCITcfKshhKJ9I=",
"owner": "hyprwm", "owner": "hyprwm",
"repo": "hyprtoolkit", "repo": "hyprtoolkit",
"rev": "bdba25ced39ea39ab004a8f31593ba0b0ff1ca35", "rev": "9af245a69fa6b286b88ddfc340afd288e00a6998",
"type": "github" "type": "github"
}, },
"original": { "original": {
@@ -538,11 +524,11 @@
] ]
}, },
"locked": { "locked": {
"lastModified": 1783002634, "lastModified": 1773436263,
"narHash": "sha256-xGqHIUK0wIZoW7SiMalwvO6uGOO/VrlQwoRobpE7dDI=", "narHash": "sha256-n+2xFJngUkBqUJD5FsbVnYEHBTyDFSqtBIwQIGPXWWo=",
"owner": "hyprwm", "owner": "hyprwm",
"repo": "hyprutils", "repo": "hyprutils",
"rev": "41fb809557abd29a57151b6e1aaeabd05f9437e1", "rev": "5e228db6821380a5875d5643176c5c46a47b8134",
"type": "github" "type": "github"
}, },
"original": { "original": {
@@ -563,11 +549,11 @@
] ]
}, },
"locked": { "locked": {
"lastModified": 1777159683, "lastModified": 1772459835,
"narHash": "sha256-Jxixw6wZphUp+nHYxOKUYSckL17QMBx2d5Zp0rJHr1g=", "narHash": "sha256-978jRz/y/9TKmZb/qD4lEYHCQGHpEXGqy+8X2lFZsak=",
"owner": "hyprwm", "owner": "hyprwm",
"repo": "hyprwayland-scanner", "repo": "hyprwayland-scanner",
"rev": "b8632713a6beaf28b56f2a7b0ab2fb7088dbb404", "rev": "0a692d4a645165eebd65f109146b8861e3a925e7",
"type": "github" "type": "github"
}, },
"original": { "original": {
@@ -592,11 +578,11 @@
] ]
}, },
"locked": { "locked": {
"lastModified": 1778410714, "lastModified": 1773074819,
"narHash": "sha256-o6RzFj4nJXaPRY7EM01siuCQeT41RfwwmcmFQqwFJJg=", "narHash": "sha256-qRqYnXiKoJLRTcfaRukn7EifmST2IVBUMZOeZMAc5UA=",
"owner": "hyprwm", "owner": "hyprwm",
"repo": "hyprwire", "repo": "hyprwire",
"rev": "85148a8e612808cf5ddb25d0b3c5840f3498a7dc", "rev": "f68afd0e73687598cc2774804fedad76693046f0",
"type": "github" "type": "github"
}, },
"original": { "original": {
@@ -655,11 +641,11 @@
] ]
}, },
"locked": { "locked": {
"lastModified": 1782065297, "lastModified": 1774185820,
"narHash": "sha256-/pRul59iTUd3Oz8fJNEeB0A2qXfNCMai5LwYlpLTkvY=", "narHash": "sha256-ASExCDbdujwneZ/tZeNXxzKPbUFLroBnmPBJ5jEniCI=",
"ref": "main", "ref": "main",
"rev": "1975a872f6587813e9f3741bdab29d9dc1573bc3", "rev": "769bd540e8975050b2778025fdebc6fdd5c5e2b5",
"revCount": 43, "revCount": 42,
"type": "git", "type": "git",
"url": "ssh://gitea@git.ppp.pm:1122/alex/komga-reading-stats.git" "url": "ssh://gitea@git.ppp.pm:1122/alex/komga-reading-stats.git"
}, },
@@ -733,11 +719,11 @@
"systems": "systems_5" "systems": "systems_5"
}, },
"locked": { "locked": {
"lastModified": 1783761716, "lastModified": 1773491467,
"narHash": "sha256-1I1HEeTEBHcBy/jIO2l5NiJJZSqrQSMjwKo9A9ONZr4=", "narHash": "sha256-PuCDdZyWQRP1F0fQ7urr+mJ5szDyHBcfqBv4jBuKit0=",
"owner": "nix-community", "owner": "nix-community",
"repo": "nix-jetbrains-plugins", "repo": "nix-jetbrains-plugins",
"rev": "dc3c00516880f4fd82671bea4b3500451b29958d", "rev": "6f77b7d7a109a8a11fa3e7a24a6051d812aa2a77",
"type": "github" "type": "github"
}, },
"original": { "original": {
@@ -747,15 +733,12 @@
} }
}, },
"nixos-hardware": { "nixos-hardware": {
"inputs": {
"nixpkgs": "nixpkgs_2"
},
"locked": { "locked": {
"lastModified": 1784100666, "lastModified": 1773533765,
"narHash": "sha256-HF/mrw5NYQfKFZgkfV2h1UL5/E3ccfsE2XJ1AbbLLJ0=", "narHash": "sha256-qonGfS2lzCgCl59Zl63jF6dIRRpvW3AJooBGMaXjHiY=",
"owner": "nixos", "owner": "nixos",
"repo": "nixos-hardware", "repo": "nixos-hardware",
"rev": "fccfa9031a85b78a437f2f153c1f6449f3bc3185", "rev": "f8e82243fd601afb9f59ad230958bd073795cbfe",
"type": "github" "type": "github"
}, },
"original": { "original": {
@@ -783,40 +766,27 @@
}, },
"nixpkgs-stable": { "nixpkgs-stable": {
"locked": { "locked": {
"lastModified": 1784011430, "lastModified": 1773814637,
"narHash": "sha256-lDebytrYdd47IBLwvNOD+6AGeoqZ78CIKlp70hzW280=", "narHash": "sha256-GNU+ooRmrHLfjlMsKdn0prEKVa0faVanm0jrgu1J/gY=",
"owner": "NixOS", "owner": "NixOS",
"repo": "nixpkgs", "repo": "nixpkgs",
"rev": "8eeec934ae0dbeca3d7868c059568a65c08b2fc3", "rev": "fea3b367d61c1a6592bc47c72f40a9f3e6a53e96",
"type": "github" "type": "github"
}, },
"original": { "original": {
"owner": "NixOS", "owner": "NixOS",
"ref": "nixos-26.05", "ref": "nixos-25.11",
"repo": "nixpkgs", "repo": "nixpkgs",
"type": "github" "type": "github"
} }
}, },
"nixpkgs_2": { "nixpkgs_2": {
"locked": { "locked": {
"lastModified": 1767892417, "lastModified": 1773821835,
"narHash": "sha256-8bW3q88CEg2u4hSP66Vf4lpbLonHz7hqDNBMcCY7E9U=", "narHash": "sha256-TJ3lSQtW0E2JrznGVm8hOQGVpXjJyXY2guAxku2O9A4=",
"rev": "3497aa5c9457a9d88d71fa93a4a8368816fbeeba",
"type": "tarball",
"url": "https://releases.nixos.org/nixos/unstable/nixos-26.05pre924538.3497aa5c9457/nixexprs.tar.xz"
},
"original": {
"type": "tarball",
"url": "https://channels.nixos.org/nixos-unstable/nixexprs.tar.xz"
}
},
"nixpkgs_3": {
"locked": {
"lastModified": 1784007870,
"narHash": "sha256-djcLt/JJphyNt4eDY9XTly+/WbCK5lqWq9lSgCmJkkQ=",
"owner": "nixos", "owner": "nixos",
"repo": "nixpkgs", "repo": "nixpkgs",
"rev": "18b9261cb3294b6d2a06d03f96872827b8fe2698", "rev": "b40629efe5d6ec48dd1efba650c797ddbd39ace0",
"type": "github" "type": "github"
}, },
"original": { "original": {
@@ -850,17 +820,18 @@
"pre-commit-hooks": { "pre-commit-hooks": {
"inputs": { "inputs": {
"flake-compat": "flake-compat", "flake-compat": "flake-compat",
"gitignore": "gitignore",
"nixpkgs": [ "nixpkgs": [
"hyprland", "hyprland",
"nixpkgs" "nixpkgs"
] ]
}, },
"locked": { "locked": {
"lastModified": 1783008725, "lastModified": 1772893680,
"narHash": "sha256-jGiy6+sxjNWXSjp25uoJuNfyH9zBK1PEDY0lVoL4ibQ=", "narHash": "sha256-JDqZMgxUTCq85ObSaFw0HhE+lvdOre1lx9iI6vYyOEs=",
"owner": "cachix", "owner": "cachix",
"repo": "git-hooks.nix", "repo": "git-hooks.nix",
"rev": "bca82caa46d5ec0f5d422c61fb1e30bc51313cbe", "rev": "8baab586afc9c9b57645a734c820e4ac0a604af9",
"type": "github" "type": "github"
}, },
"original": { "original": {
@@ -869,28 +840,6 @@
"type": "github" "type": "github"
} }
}, },
"puppy-tracker": {
"inputs": {
"flake-utils": "flake-utils_3",
"nixpkgs": [
"nixpkgs"
]
},
"locked": {
"lastModified": 1784906229,
"narHash": "sha256-9ZzpwieFwushXgbZZj1W4i8uhI0dmLZCcQSb6qVhn18=",
"ref": "main",
"rev": "374e630d8fd0fa063753ffaa3537ada4fb4bc11e",
"revCount": 55,
"type": "git",
"url": "ssh://gitea@git.ppp.pm:1122/alex/puppy-tracker.git"
},
"original": {
"ref": "main",
"type": "git",
"url": "ssh://gitea@git.ppp.pm:1122/alex/puppy-tracker.git"
}
},
"root": { "root": {
"inputs": { "inputs": {
"agenix": "agenix", "agenix": "agenix",
@@ -907,36 +856,12 @@
"nix-gc-env": "nix-gc-env", "nix-gc-env": "nix-gc-env",
"nix-jetbrains-plugins": "nix-jetbrains-plugins", "nix-jetbrains-plugins": "nix-jetbrains-plugins",
"nixos-hardware": "nixos-hardware", "nixos-hardware": "nixos-hardware",
"nixpkgs": "nixpkgs_3", "nixpkgs": "nixpkgs_2",
"pppdotpm-site": "pppdotpm-site", "pppdotpm-site": "pppdotpm-site",
"puppy-tracker": "puppy-tracker",
"solo-referee": "solo-referee",
"whib-backend": "whib-backend", "whib-backend": "whib-backend",
"whib-frontend": "whib-frontend" "whib-frontend": "whib-frontend"
} }
}, },
"solo-referee": {
"inputs": {
"flake-utils": "flake-utils_4",
"nixpkgs": [
"nixpkgs"
]
},
"locked": {
"lastModified": 1785015267,
"narHash": "sha256-NHaaFMznPus9jABGXlSHo++bYcbQeBnxvYPt/CzBEHg=",
"ref": "main",
"rev": "42af5dc48d1e2466a6b09273c7c96dfe2ab13c0b",
"revCount": 10,
"type": "git",
"url": "ssh://gitea@git.ppp.pm:1122/alex/solo-referee.git"
},
"original": {
"ref": "main",
"type": "git",
"url": "ssh://gitea@git.ppp.pm:1122/alex/solo-referee.git"
}
},
"systems": { "systems": {
"locked": { "locked": {
"lastModified": 1681028828, "lastModified": 1681028828,
@@ -1012,36 +937,6 @@
"type": "github" "type": "github"
} }
}, },
"systems_6": {
"locked": {
"lastModified": 1681028828,
"narHash": "sha256-Vy1rq5AaRuLzOxct8nz4T6wlgyUR7zLU309k9mBC768=",
"owner": "nix-systems",
"repo": "default",
"rev": "da67096a3b9bf56a91d16901293e51ba5b49a27e",
"type": "github"
},
"original": {
"owner": "nix-systems",
"repo": "default",
"type": "github"
}
},
"systems_7": {
"locked": {
"lastModified": 1681028828,
"narHash": "sha256-Vy1rq5AaRuLzOxct8nz4T6wlgyUR7zLU309k9mBC768=",
"owner": "nix-systems",
"repo": "default",
"rev": "da67096a3b9bf56a91d16901293e51ba5b49a27e",
"type": "github"
},
"original": {
"owner": "nix-systems",
"repo": "default",
"type": "github"
}
},
"whib-backend": { "whib-backend": {
"inputs": { "inputs": {
"nixpkgs": [ "nixpkgs": [
@@ -1049,11 +944,11 @@
] ]
}, },
"locked": { "locked": {
"lastModified": 1780482259, "lastModified": 1739029248,
"narHash": "sha256-buOczAkw78U+g7DYcB7nMabTGzQoN15HtVE3y0kIt3I=", "narHash": "sha256-ux/Udy0Mhs66P/EQQ8S+xIuXRm9UHEYwSy12IZtlbnA=",
"ref": "master", "ref": "master",
"rev": "b9ee418d14d6cb500506f9ef0cb9d54a8e78afa9", "rev": "222a8f6dde2e9270f6390b5e1e83c7ae1ea48290",
"revCount": 373, "revCount": 371,
"type": "git", "type": "git",
"url": "ssh://gitea@git.ppp.pm:1122/alex/whib.git" "url": "ssh://gitea@git.ppp.pm:1122/alex/whib.git"
}, },
@@ -1070,11 +965,11 @@
] ]
}, },
"locked": { "locked": {
"lastModified": 1780483645, "lastModified": 1761508816,
"narHash": "sha256-Nr0WTh72uBCSO5jCcvHPE+4dqAPn07HZ5U1lAE4/3II=", "narHash": "sha256-adV/lyxcmuopyuzZ49v46Yt0gft+ioEL4yl1S+vUbus=",
"ref": "master", "ref": "master",
"rev": "14f98eced1ccf1e62493ad65eb38502b38db5cba", "rev": "ab10bf50cb6b023a1b99f91c7e8d550231135eef",
"revCount": 224, "revCount": 223,
"type": "git", "type": "git",
"url": "ssh://gitea@git.ppp.pm:1122/alex/whib-react.git" "url": "ssh://gitea@git.ppp.pm:1122/alex/whib-react.git"
}, },
@@ -1112,11 +1007,11 @@
] ]
}, },
"locked": { "locked": {
"lastModified": 1782644412, "lastModified": 1772669058,
"narHash": "sha256-/iSa/bL1QQFLv+uJ9gI0N87J8gOeZXvca7EjoPGKE6w=", "narHash": "sha256-XhnY0aRuDo5LT8pmJVPofPOgO2hAR7T+XRoaQxtNPzQ=",
"owner": "hyprwm", "owner": "hyprwm",
"repo": "xdg-desktop-portal-hyprland", "repo": "xdg-desktop-portal-hyprland",
"rev": "c01c99fc278ec68c82e9865923088f043c7c1621", "rev": "906d0ac159803a7df2dc1f948df9327670380f69",
"type": "github" "type": "github"
}, },
"original": { "original": {
-12
View File
@@ -86,18 +86,6 @@
# url = "path:/home/alex/code/own/komga-reading-stats"; # url = "path:/home/alex/code/own/komga-reading-stats";
inputs.nixpkgs.follows = "nixpkgs"; inputs.nixpkgs.follows = "nixpkgs";
}; };
puppy-tracker = {
url = "git+ssh://gitea@git.ppp.pm:1122/alex/puppy-tracker.git?ref=main";
# url = "path:/home/alex/code/puppy-tracker";
inputs.nixpkgs.follows = "nixpkgs";
};
solo-referee = {
url = "git+ssh://gitea@git.ppp.pm:1122/alex/solo-referee.git?ref=main";
# url = "path:/home/alex/code/solo-referee";
inputs.nixpkgs.follows = "nixpkgs";
};
}; };
outputs = outputs =
-5
View File
@@ -79,16 +79,11 @@
{ device = config.disko.devices.disk.root.device; } { device = config.disko.devices.disk.root.device; }
{ device = config.disko.devices.disk.disk1.device; } { device = config.disko.devices.disk.disk1.device; }
{ device = config.disko.devices.disk.disk2.device; } { device = config.disko.devices.disk.disk2.device; }
{ device = config.disko.devices.disk.disk3.device; }
{ device = config.disko.devices.disk.disk4.device; }
]; ];
}; };
services.zfs.autoScrub.enable = true; services.zfs.autoScrub.enable = true;
# Don't force-import the pool if it appears in use elsewhere; safer default in 26.11+.
boot.zfs.forceImportRoot = false;
networking.hostId = "0a9474e7"; # Required by ZFS networking.hostId = "0a9474e7"; # Required by ZFS
disko.devices = { disko.devices = {
disk = { disk = {
-2
View File
@@ -15,8 +15,6 @@
home.packages = [ home.packages = [
pkgs.streamrip pkgs.streamrip
pkgs.claude-code
pkgs.wl-clipboard
]; ];
home.stateVersion = "24.11"; home.stateVersion = "24.11";
-26
View File
@@ -33,32 +33,6 @@
"--http-timeout=60" "--http-timeout=60"
]; ];
}; };
"romm.ppp.pm" = {
dnsProvider = "hetzner";
environmentFile = config.age.secrets.hetzner-dns.path;
group = "nginx";
extraLegoFlags = [
"--dns.resolvers=1.1.1.1:53,8.8.8.8:53"
"--dns.propagation-wait=60s"
"--dns-timeout=60"
"--http-timeout=60"
];
};
"puppy.ppp.pm" = {
dnsProvider = "hetzner";
environmentFile = config.age.secrets.hetzner-dns.path;
group = "nginx";
extraLegoFlags = [
"--dns.resolvers=1.1.1.1:53,8.8.8.8:53"
"--dns.propagation-wait=60s"
"--dns-timeout=60"
"--http-timeout=60"
];
};
}; };
}; };
+1 -6
View File
@@ -12,21 +12,16 @@ in
ssh.enable = true; ssh.enable = true;
git.enable = true; git.enable = true;
tmux.enable = true;
nginx.enable = true; nginx.enable = true;
syncthing.enable = true; syncthing.enable = true;
transmission.enable = true; transmission.enable = true;
audiobookshelf.enable = true; audiobookshelf.enable = true;
jellyfin.enable = true; jellyfin.enable = true;
immich.enable = false; immich.enable = true;
navidrome.enable = true; navidrome.enable = true;
komga.enable = true; komga.enable = true;
romm.enable = true;
homepage.enable = true; homepage.enable = true;
disk-smart.enable = true;
puppy-tracker.enable = true;
solo-referee.enable = true;
}; };
}; };
} }
@@ -1,159 +0,0 @@
{
pkgs,
lib,
config,
...
}:
let
enabled = config.mod.disk-smart.enable;
disks = [
{ path = "/dev/disk/by-id/ata-ST8000VN004-3CP101_WWZ8QCG4"; name = "seagate_8tb_1"; label = "Seagate 8TB #1"; }
{ path = "/dev/disk/by-id/ata-ST8000VN004-3CP101_WWZ8QDJ5"; name = "seagate_8tb_2"; label = "Seagate 8TB #2"; }
{ path = "/dev/disk/by-id/ata-TOSHIBA_MG10ACA20TE_85K2A0UCF4MJ"; name = "toshiba_20tb_1"; label = "Toshiba 20TB #1"; }
{ path = "/dev/disk/by-id/ata-TOSHIBA_MG10ACA20TE_85K2A0V6F4MJ"; name = "toshiba_20tb_2"; label = "Toshiba 20TB #2"; }
];
outputDir = "/var/lib/disk-smart";
collectScript = pkgs.writeShellScript "disk-smart-collect" ''
set -euo pipefail
export PATH="${lib.makeBinPath [ pkgs.smartmontools pkgs.jq pkgs.coreutils ]}"
mkdir -p ${outputDir}
result="{"
${lib.concatMapStringsSep "\n" (disk: ''
raw=$(smartctl -j -A -H ${disk.path} 2>/dev/null || true)
temp=$(echo "$raw" | jq -r '.temperature.current // empty')
power_on=$(echo "$raw" | jq -r '.power_on_time.hours // empty')
smart_status=$(echo "$raw" | jq -r '.smart_status.passed // empty')
reallocated=$(echo "$raw" | jq -r '[.ata_smart_attributes.table[] | select(.name == "Reallocated_Sector_Ct")][0].raw.value // empty')
pending=$(echo "$raw" | jq -r '[.ata_smart_attributes.table[] | select(.name == "Current_Pending_Sector")][0].raw.value // empty')
result="$result\"${disk.name}\":{\"temperature\":$temp,\"power_on_hours\":$power_on,\"smart_passed\":$smart_status,\"reallocated_sectors\":$reallocated,\"pending_sectors\":$pending},"
'') disks}
# Remove trailing comma, close object
result="''${result%,}}"
echo "$result" | jq . > ${outputDir}/smart.json.tmp
mv ${outputDir}/smart.json.tmp ${outputDir}/smart.json
'';
indent = prefix: s:
lib.concatMapStringsSep "\n"
(line: if line == "" then line else prefix + line)
(lib.splitString "\n" s);
mkSensor = disk: ''
- name: "${disk.label} Temperature"
value_template: "{{ value_json.${disk.name}.temperature }}"
unit_of_measurement: "°C"
device_class: temperature
state_class: measurement
- name: "${disk.label} Power On Hours"
value_template: "{{ value_json.${disk.name}.power_on_hours }}"
unit_of_measurement: "h"
state_class: total_increasing
- name: "${disk.label} SMART Passed"
value_template: "{{ value_json.${disk.name}.smart_passed }}"
- name: "${disk.label} Reallocated Sectors"
value_template: "{{ value_json.${disk.name}.reallocated_sectors }}"
state_class: measurement
- name: "${disk.label} Pending Sectors"
value_template: "{{ value_json.${disk.name}.pending_sectors }}"
state_class: measurement
'';
sensorYaml = indent " " (lib.concatMapStrings mkSensor disks);
sectorEntities = lib.concatMap (disk: [
"sensor.${disk.name}_reallocated_sectors"
"sensor.${disk.name}_pending_sectors"
]) disks;
sectorEntitiesYaml = lib.concatMapStringsSep "\n"
(id: " - ${id}") sectorEntities;
smartPassedEntities = map (disk: "sensor.${disk.name}_smart_passed") disks;
smartPassedEntitiesYaml = lib.concatMapStringsSep "\n"
(id: " - ${id}") smartPassedEntities;
in
{
options = {
mod.disk-smart = {
enable = lib.mkEnableOption "Enable disk SMART monitoring module";
};
};
config = lib.mkIf enabled {
mod.home-assistant.extraConfig = ''
rest:
- resource: http://127.0.0.1:9633/smart.json
scan_interval: 60
sensor:
${sensorYaml}
automation disk_smart:
- alias: "Disk sector count increased"
trigger:
- platform: state
entity_id:
${sectorEntitiesYaml}
condition:
- condition: template
value_template: "{{ trigger.from_state.state | int(-1) >= 0 and trigger.to_state.state | int(0) > trigger.from_state.state | int(0) }}"
action:
- service: notify.mobile_app_pixel_9_pro
data:
title: "Disk SMART warning"
message: "{{ trigger.to_state.attributes.friendly_name }} increased from {{ trigger.from_state.state }} to {{ trigger.to_state.state }}"
- alias: "Disk SMART check failed"
trigger:
- platform: state
entity_id:
${smartPassedEntitiesYaml}
condition:
- condition: template
value_template: "{{ trigger.to_state.state | lower == 'false' }}"
action:
- service: notify.mobile_app_pixel_9_pro
data:
title: "Disk SMART FAILURE"
message: "{{ trigger.to_state.attributes.friendly_name }} reports SMART failure drive is likely failing"
'';
systemd.services.disk-smart-collect = {
description = "Collect disk SMART data";
serviceConfig = {
Type = "oneshot";
ExecStart = collectScript;
};
};
systemd.timers.disk-smart-collect = {
description = "Periodically collect disk SMART data";
wantedBy = [ "timers.target" ];
timerConfig = {
OnBootSec = "1min";
OnUnitActiveSec = "1min";
};
};
services.nginx.virtualHosts."127.0.0.1" = {
listen = [
{ addr = "127.0.0.1"; port = 9633; }
];
locations."= /smart.json" = {
alias = "${outputDir}/smart.json";
extraConfig = ''
default_type application/json;
'';
};
};
};
}
+172 -229
View File
@@ -6,42 +6,8 @@
}: }:
let let
nginxEnabled = config.mod.nginx.enable; nginxEnabled = config.mod.nginx.enable;
cfg = config.mod.home-assistant;
configFile = pkgs.writeText "ha-configuration.yaml" '' script = pkgs.writeShellScript "bt-reset" ''
# Loads default set of integrations. Do not remove.
default_config:
http:
use_x_forwarded_for: true
trusted_proxies:
- 127.0.0.1
# Load frontend themes from the themes folder
frontend:
themes: !include_dir_merge_named themes
automation: !include automations.yaml
script: !include scripts.yaml
scene: !include scenes.yaml
recorder:
purge_keep_days: 365
alert:
fridge_door:
name: Fridge is open
done_message: Fride is closed
entity_id: binary_sensor.kyldorr
state: "on"
repeat: 2
skip_first: true
notifiers:
- mobile_app_pixel_9_pro
${cfg.extraConfig}'';
btResetScript = pkgs.writeShellScript "bt-reset" ''
set -euo pipefail set -euo pipefail
export PATH="${ export PATH="${
lib.makeBinPath [ lib.makeBinPath [
@@ -96,204 +62,181 @@ ${cfg.extraConfig}'';
''; '';
in in
{ {
options = { mod.homepage.services = [
mod.home-assistant = { {
extraConfig = lib.mkOption { name = "Home Assistant";
type = lib.types.lines; port = 8123;
default = ""; description = "Home automation";
description = "Extra YAML to append to Home Assistant's configuration.yaml"; }
];
hardware.bluetooth.enable = true;
virtualisation.oci-containers = {
backend = "podman";
containers.homeassistant = {
image = "ghcr.io/home-assistant/home-assistant:stable";
volumes = [
"/home/alex/.config/home-assistant:/config"
# Pass in bluetooth
"/run/dbus:/run/dbus:ro"
];
environment.TZ = "Europe/Stockholm";
extraOptions = [
"--network=host"
# Allows HA to perform low-level network operations (scan/reset adapter)
"--cap-add=NET_ADMIN"
"--cap-add=NET_RAW"
# Pass in Zigbee antenna
"--device=/dev/serial/by-id/usb-Nabu_Casa_ZBT-2_9C139EAAD464-if00:/dev/ttyACM0"
];
};
};
services = {
blueman.enable = true;
nginx = lib.mkIf nginxEnabled {
recommendedProxySettings = true;
virtualHosts."ha.ppp.pm" = {
forceSSL = true;
useACMEHost = "ha.ppp.pm";
extraConfig = ''
proxy_buffering off;
'';
locations."/" = {
proxyPass = "http://127.0.0.1:8123";
proxyWebsockets = true;
};
};
};
# Trigger reset via udev when hci0 disappears
udev.extraRules = ''
ACTION=="remove", SUBSYSTEM=="bluetooth", KERNEL=="hci0", \
TAG+="systemd", ENV{SYSTEMD_WANTS}+="bt-reset.service"
'';
};
systemd = {
services = {
# Trigger reset on bluetoothd failure
bluetooth = {
unitConfig.OnFailure = [ "bt-reset.service" ];
};
bt-reset = {
description = "Reset Bluetooth adapter";
after = [ "bluetooth.service" ];
serviceConfig = {
Type = "oneshot";
ExecStart = script;
Restart = "on-failure";
RestartSec = "10s";
StartLimitIntervalSec = "120";
StartLimitBurst = 3;
};
};
};
timers.bt-reset = {
description = "Periodically reset Bluetooth adapter";
wantedBy = [ "timers.target" ];
timerConfig = {
OnBootSec = "5min"; # first run 5 min after boot
OnUnitActiveSec = "4h"; # then every 4 hours
RandomizedDelaySec = "5min";
};
};
user = {
timers = {
"update-hetzner-dns" = {
unitConfig = {
Description = "updates Hetzner DNS records";
};
timerConfig = {
Unit = "update-hetzner-dns.service";
OnCalendar = "*-*-* *:00/30:00";
Persistent = true;
};
wantedBy = [ "timers.target" ];
};
};
services = {
"update-hetzner-dns" = {
unitConfig = {
Description = "updates Hetzner DNS records";
};
serviceConfig = {
Type = "exec";
EnvironmentFile = config.age.secrets.hetzner-dns.path;
};
path = [
pkgs.curl
pkgs.coreutils
pkgs.jq
];
script = ''
SUBDOMAINS="ha komga"
INTERFACE="enp3s0"
CURRENT_IP=$(curl -s --fail --interface "$INTERFACE" ifconfig.me)
for SUBDOMAIN in $SUBDOMAINS; do
LAST_IP_FILE="/tmp/hetzner-dns-''${SUBDOMAIN}-ip"
LAST_IP=""
if [[ -f "$LAST_IP_FILE" ]]; then
LAST_IP=$(cat "$LAST_IP_FILE")
fi
if [[ "$CURRENT_IP" == "$LAST_IP" ]]; then
echo "$SUBDOMAIN: IP unchanged, NOOP update."
else
echo "$SUBDOMAIN: Updating IP"
JSON_BODY=$(jq -n --arg ip "$CURRENT_IP" '{records: [{value: $ip}]}')
curl \
--fail \
-X POST \
-H "Authorization: Bearer $HETZNER_API_TOKEN" \
-H "Content-Type: application/json" \
-d "$JSON_BODY" \
"https://api.hetzner.cloud/v1/zones/ppp.pm/rrsets/''${SUBDOMAIN}/A/actions/set_records" \
&& echo $CURRENT_IP > $LAST_IP_FILE
fi
done
'';
};
}; };
}; };
}; };
config = { age = {
mod.homepage.services = [ secrets = {
{ "hetzner-dns" = {
name = "Home Assistant"; file = ../../../../secrets/manatee/hetzner-dns.age;
port = 8123; owner = "alex";
description = "Home automation"; group = "users";
}
];
hardware.bluetooth.enable = true;
# Give up and enter a `failed` state (visible in `systemctl --failed`) if the
# container restarts more than 5 times in 5 minutes, instead of crash-looping
# forever. The window is deliberately long: a slow crash-loop (~5s per attempt)
# stays under systemd's default 5-starts-per-10s limit and would otherwise
# retry indefinitely.
systemd.services.podman-homeassistant = {
startLimitIntervalSec = 300;
startLimitBurst = 5;
};
virtualisation.oci-containers = {
backend = "podman";
containers.homeassistant = {
image = "ghcr.io/home-assistant/home-assistant:stable";
volumes = [
"/home/alex/.config/home-assistant:/config"
"${configFile}:/config/configuration.yaml:ro"
# Pass in bluetooth
"/run/dbus:/run/dbus:ro"
];
environment.TZ = "Europe/Stockholm";
extraOptions = [
"--network=host"
# Allows HA to perform low-level network operations (scan/reset adapter)
"--cap-add=NET_ADMIN"
"--cap-add=NET_RAW"
# Pass in Zigbee antenna
"--device=/dev/serial/by-id/usb-Nabu_Casa_ZBT-2_9C139EAAD464-if00:/dev/ttyACM0"
];
};
};
services = {
blueman.enable = true;
nginx = lib.mkIf nginxEnabled {
recommendedProxySettings = true;
virtualHosts."ha.ppp.pm" = {
forceSSL = true;
useACMEHost = "ha.ppp.pm";
extraConfig = ''
proxy_buffering off;
'';
locations."/" = {
proxyPass = "http://127.0.0.1:8123";
proxyWebsockets = true;
};
};
};
# Trigger reset via udev when hci0 disappears
udev.extraRules = ''
ACTION=="remove", SUBSYSTEM=="bluetooth", KERNEL=="hci0", \
TAG+="systemd", ENV{SYSTEMD_WANTS}+="bt-reset.service"
'';
};
systemd = {
services = {
# Trigger reset on bluetoothd failure
bluetooth = {
unitConfig.OnFailure = [ "bt-reset.service" ];
};
bt-reset = {
description = "Reset Bluetooth adapter";
after = [ "bluetooth.service" ];
serviceConfig = {
Type = "oneshot";
ExecStart = btResetScript;
Restart = "on-failure";
RestartSec = "10s";
StartLimitIntervalSec = "120";
StartLimitBurst = 3;
};
};
};
timers.bt-reset = {
description = "Periodically reset Bluetooth adapter";
wantedBy = [ "timers.target" ];
timerConfig = {
OnBootSec = "5min"; # first run 5 min after boot
OnUnitActiveSec = "4h"; # then every 4 hours
RandomizedDelaySec = "5min";
};
};
user = {
timers = {
"update-hetzner-dns" = {
unitConfig = {
Description = "updates Hetzner DNS records";
};
timerConfig = {
Unit = "update-hetzner-dns.service";
OnCalendar = "*-*-* *:00/30:00";
Persistent = true;
};
wantedBy = [ "timers.target" ];
};
};
services = {
"update-hetzner-dns" = {
unitConfig = {
Description = "updates Hetzner DNS records";
};
serviceConfig = {
Type = "exec";
EnvironmentFile = config.age.secrets.hetzner-dns.path;
};
path = [
pkgs.curl
pkgs.coreutils
pkgs.jq
];
script = ''
SUBDOMAINS="ha komga romm puppy"
INTERFACE="enp3s0"
CURRENT_IP=$(curl -s --fail --interface "$INTERFACE" ifconfig.me)
for SUBDOMAIN in $SUBDOMAINS; do
LAST_IP_FILE="/tmp/hetzner-dns-''${SUBDOMAIN}-ip"
LAST_IP=""
if [[ -f "$LAST_IP_FILE" ]]; then
LAST_IP=$(cat "$LAST_IP_FILE")
fi
if [[ "$CURRENT_IP" == "$LAST_IP" ]]; then
echo "$SUBDOMAIN: IP unchanged, NOOP update."
else
echo "$SUBDOMAIN: Updating IP"
JSON_BODY=$(jq -n --arg ip "$CURRENT_IP" '{records: [{value: $ip}]}')
curl \
--fail \
-X POST \
-H "Authorization: Bearer $HETZNER_API_TOKEN" \
-H "Content-Type: application/json" \
-d "$JSON_BODY" \
"https://api.hetzner.cloud/v1/zones/ppp.pm/rrsets/''${SUBDOMAIN}/A/actions/set_records" \
&& echo $CURRENT_IP > $LAST_IP_FILE
fi
done
'';
};
};
};
};
age = {
secrets = {
"hetzner-dns" = {
file = ../../../../secrets/manatee/hetzner-dns.age;
owner = "alex";
group = "users";
};
}; };
}; };
}; };
+7 -17
View File
@@ -9,18 +9,13 @@ let
nginxEnabled = config.mod.nginx.enable; nginxEnabled = config.mod.nginx.enable;
services = config.mod.homepage.services; services = config.mod.homepage.services;
serviceToCard = serviceToCard = svc: ''
svc: <a class="card" href="http://manatee:${toString svc.port}">
let <div class="name">${svc.name}</div>
href = if svc.url != null then svc.url else "http://manatee:${toString svc.port}"; <div class="desc">${svc.description}</div>
in <div class="port">:${toString svc.port}</div>
'' </a>
<a class="card" href="${href}"> '';
<div class="name">${svc.name}</div>
<div class="desc">${svc.description}</div>
<div class="port">:${toString svc.port}</div>
</a>
'';
page = pkgs.writeTextDir "index.html" '' page = pkgs.writeTextDir "index.html" ''
<!DOCTYPE html> <!DOCTYPE html>
@@ -88,11 +83,6 @@ in
name = lib.mkOption { type = lib.types.str; }; name = lib.mkOption { type = lib.types.str; };
port = lib.mkOption { type = lib.types.port; }; port = lib.mkOption { type = lib.types.port; };
description = lib.mkOption { type = lib.types.str; }; description = lib.mkOption { type = lib.types.str; };
url = lib.mkOption {
type = lib.types.nullOr lib.types.str;
default = null;
description = "Link target for the card; defaults to http://manatee:<port>.";
};
}; };
} }
); );
+1 -2
View File
@@ -19,7 +19,6 @@ in
imports = [ imports = [
inputs.komga-comictracker.nixosModules.default inputs.komga-comictracker.nixosModules.default
inputs.komga-bookmanager.nixosModules.default inputs.komga-bookmanager.nixosModules.default
inputs.komga-reading-stats.nixosModules.default
]; ];
config = lib.mkIf enabled { config = lib.mkIf enabled {
@@ -146,7 +145,7 @@ in
}; };
"komga-reading-stats-komga-api-key" = { "komga-reading-stats-komga-api-key" = {
file = ../../../../secrets/manatee/komga-reading-stats-komga-api-key.age; file = ../../../../secrets/manatee/komga-reading-stats-claude-api-key.age;
}; };
}; };
}; };
@@ -1,63 +0,0 @@
{
inputs,
lib,
config,
...
}:
let
enabled = config.mod.puppy-tracker.enable;
nginxEnabled = config.mod.nginx.enable;
port = 8089;
in
{
options = {
mod.puppy-tracker = {
enable = lib.mkEnableOption "Enable puppy-tracker module";
};
};
imports = [
inputs.puppy-tracker.nixosModules.default
];
config = lib.mkIf enabled {
mod.homepage.services = [
{
name = "Puppy Tracker";
port = port;
description = "Sleep, meals, pees, poos";
# Login needs HTTPS (Secure cookies), so link to the public vhost.
url = "https://puppy.ppp.pm";
}
];
services.puppy-tracker = {
enable = true;
inherit port;
openFirewall = true;
# Served publicly over HTTPS via the nginx vhost below.
secureCookies = true;
# Shared registration secret; the file holds `PUPPY_INVITE_CODE=...`.
inviteCodeFile = config.age.secrets."puppy-tracker-invite-code".path;
};
services.nginx = lib.mkIf nginxEnabled {
virtualHosts."puppy.ppp.pm" = {
forceSSL = true;
useACMEHost = "puppy.ppp.pm";
# Photo uploads are up to 15 MB; give nginx headroom over its 1 MB default.
extraConfig = ''
client_max_body_size 20m;
'';
locations."/" = {
proxyPass = "http://127.0.0.1:${toString port}";
};
};
};
age.secrets."puppy-tracker-invite-code".file =
../../../../secrets/manatee/puppy-tracker-invite-code.age;
};
}
-230
View File
@@ -1,230 +0,0 @@
{
pkgs,
lib,
config,
...
}:
let
enabled = config.mod.romm.enable;
nginxEnabled = config.mod.nginx.enable;
configFile = pkgs.writeText "romm-config.yml" ''
filesystem:
skip_hash_calculation: false
exclude:
roms:
single_file:
extensions:
- xml
- txt
- nfo
- dat
- jpg
- png
names:
- '._*'
- 'Thumbs.db'
- '.DS_Store'
'';
# Give up and enter a `failed` state (visible in `systemctl --failed`) if a
# container restarts more than 5 times in 5 minutes, instead of crash-looping
# forever. The window is deliberately long: a slow crash-loop (~5s per attempt)
# stays under systemd's default 5-starts-per-10s limit and would otherwise
# retry indefinitely.
crashLoopGuard = {
startLimitIntervalSec = 300;
startLimitBurst = 5;
};
in
{
options = {
mod.romm = {
enable = lib.mkEnableOption "Enable romm module";
};
};
config = lib.mkIf enabled {
mod.homepage.services = [
{
name = "RomM";
port = 8085;
description = "ROM library manager";
}
];
systemd.tmpfiles.rules = [
"d /var/lib/romm 0755 root root -"
"d /var/lib/romm/db 0755 root root -"
"d /var/lib/romm/redis 0755 999 1000 -"
"d /var/lib/romm/resources 0755 root root -"
"d /var/lib/romm/assets 0755 root root -"
];
systemd.services.podman-romm = crashLoopGuard;
systemd.services.podman-romm-db = crashLoopGuard;
systemd.services.podman-romm-redis = crashLoopGuard;
systemd.services.romm-net = {
description = "Create Podman network for RomM";
after = [ "podman.service" ];
requires = [ "podman.service" ];
before = [
"podman-romm.service"
"podman-romm-db.service"
"podman-romm-redis.service"
];
requiredBy = [
"podman-romm.service"
"podman-romm-db.service"
"podman-romm-redis.service"
];
serviceConfig = {
Type = "oneshot";
RemainAfterExit = true;
ExecStart = pkgs.writeShellScript "romm-net-create" ''
${pkgs.podman}/bin/podman network exists romm-net \
|| ${pkgs.podman}/bin/podman network create romm-net
'';
ExecStop = "${pkgs.podman}/bin/podman network rm -f romm-net";
};
};
virtualisation.oci-containers = {
backend = "podman";
containers.romm-db = {
image = "mariadb:latest";
environment = {
MARIADB_DATABASE = "romm";
MARIADB_USER = "romm";
};
environmentFiles = [
config.age.secrets.romm-db-password.path
];
volumes = [
"/var/lib/romm/db:/var/lib/mysql"
];
extraOptions = [
"--network=romm-net"
];
};
containers.romm-redis = {
image = "redis:alpine";
volumes = [
"/var/lib/romm/redis:/data"
];
extraOptions = [
"--network=romm-net"
"--user=root"
];
};
containers.romm = {
image = "rommapp/romm:latest";
dependsOn = [
"romm-db"
"romm-redis"
];
environment = {
DB_HOST = "romm-db";
DB_PORT = "3306";
DB_NAME = "romm";
DB_USER = "romm";
REDIS_HOST = "romm-redis";
REDIS_PORT = "6379";
ROMM_AUTH_ENABLED = "true";
};
environmentFiles = [
config.age.secrets.romm-auth-secret-key.path
config.age.secrets.romm-db-password.path
config.age.secrets.romm-metadata-api-keys.path
];
ports = [
"127.0.0.1:8086:8080"
];
volumes = [
"${configFile}:/romm/config/config.yml:ro"
"/mnt/media/public/games:/romm/library"
"/var/lib/romm/resources:/romm/resources"
"/var/lib/romm/assets:/romm/assets"
];
extraOptions = [
"--network=romm-net"
];
};
};
services.nginx = lib.mkIf nginxEnabled {
virtualHosts."romm-local" = {
listen = [
{
addr = "0.0.0.0";
port = 8085;
}
];
extraConfig = ''
client_max_body_size 0;
'';
locations."/" = {
proxyPass = "http://127.0.0.1:8086";
proxyWebsockets = true;
};
};
virtualHosts."romm.ppp.pm" = {
forceSSL = true;
useACMEHost = "romm.ppp.pm";
extraConfig = ''
client_max_body_size 0;
'';
locations."/" = {
proxyPass = "http://127.0.0.1:8086";
proxyWebsockets = true;
};
};
};
boot.kernel.sysctl."net.ipv4.ip_forward" = 1;
networking.firewall.allowedTCPPorts = [ 8085 ];
age.secrets = {
"romm-auth-secret-key" = {
file = ../../../../secrets/manatee/romm-auth-secret-key.age;
owner = "root";
group = "root";
};
"romm-db-password" = {
file = ../../../../secrets/manatee/romm-db-password.age;
owner = "root";
group = "root";
};
"romm-metadata-api-keys" = {
file = ../../../../secrets/manatee/romm-metadata-api-keys.age;
owner = "root";
group = "root";
};
};
};
}
@@ -1,46 +0,0 @@
{
inputs,
lib,
config,
...
}:
let
enabled = config.mod.solo-referee.enable;
port = 8090;
in
{
options = {
mod.solo-referee = {
enable = lib.mkEnableOption "Enable solo-referee module";
};
};
imports = [
inputs.solo-referee.nixosModules.default
];
config = lib.mkIf enabled {
mod.homepage.services = [
{
name = "Solo Referee";
port = port;
description = "Solo-RPG tool with Claude as GM";
# url omitted → homepage links to http://manatee:${port}.
}
];
services.solo-referee = {
enable = true;
inherit port;
# Reachable on the LAN / Tailscale at manatee:${port}; no public vhost.
address = "0.0.0.0";
openFirewall = true;
# Anthropic API key kept out of the store; the file holds
# `ANTHROPIC_API_KEY=...`. Without it the offline stub GM runs.
apiKeyFile = config.age.secrets."solo-referee-api-key".path;
};
age.secrets."solo-referee-api-key".file =
../../../../secrets/manatee/solo-referee-api-key.age;
};
}
+1 -2
View File
@@ -21,9 +21,8 @@ in
home-manager.users.alex = { home-manager.users.alex = {
programs.ssh = { programs.ssh = {
enable = true; enable = true;
enableDefaultConfig = false;
settings = { matchBlocks = {
"git.ppp.pm" = { "git.ppp.pm" = {
hostname = "git.ppp.pm"; hostname = "git.ppp.pm";
identityFile = "/home/alex/.ssh/alex.manatee-git.ppp.pm"; identityFile = "/home/alex/.ssh/alex.manatee-git.ppp.pm";
-71
View File
@@ -1,71 +0,0 @@
{
pkgs,
lib,
config,
...
}:
let
enabled = config.mod.tmux.enable;
in
{
options = {
mod.tmux = {
enable = lib.mkEnableOption "enable tmux module";
};
};
config = lib.mkIf enabled {
home-manager.users.alex = {
programs.tmux = {
enable = true;
baseIndex = 1;
keyMode = "vi";
# Allow vi mode to be enabled instantly
escapeTime = 0;
plugins = [ pkgs.tmuxPlugins.sensible ];
extraConfig = ''
set -g renumber-windows on
# https://old.reddit.com/r/tmux/comments/mesrci/tmux_2_doesnt_seem_to_use_256_colors/
set -g default-terminal "xterm-256color"
set -ga terminal-overrides ",*256col*:Tc"
set -ga terminal-overrides ',*:Ss=\E[%p1%d q:Se=\E[ q'
set-environment -g COLORTERM "truecolor"
set-option -g allow-rename off
# Remove date/time etc. on the right side
set -g status-right ""
bind r source-file ~/.config/tmux/tmux.conf \; display "Config reloaded"
# Remove accidental `suspend-client` triggers
unbind C-z
bind | split-window -h -c "#{pane_current_path}"
bind - split-window -v -c "#{pane_current_path}"
# Move panes shortcuts
bind h select-pane -L
bind j select-pane -D
bind k select-pane -U
bind l select-pane -R
# Resize panes
bind -r H resize-pane -L 5
bind -r J resize-pane -D 5
bind -r K resize-pane -U 5
bind -r L resize-pane -R 5
# Move windows
bind -r Left swap-window -t -1 \; select-window -t -1
bind -r Right swap-window -t +1 \; select-window -t +1
'';
};
};
};
}
+2
View File
@@ -16,6 +16,7 @@
home.packages = [ home.packages = [
inputs.whib-backend.packages.${pkgs.stdenv.hostPlatform.system}.whib-import inputs.whib-backend.packages.${pkgs.stdenv.hostPlatform.system}.whib-import
# pkgs.beekeeper-studio # pkgs.beekeeper-studio
pkgs.bitwarden-desktop
pkgs.gimp pkgs.gimp
pkgs.zip pkgs.zip
pkgs.unzip pkgs.unzip
@@ -26,6 +27,7 @@
pkgs.onlyoffice-desktopeditors pkgs.onlyoffice-desktopeditors
pkgs.wdisplays pkgs.wdisplays
pkgs.vlc pkgs.vlc
pkgs.claude-code
pkgs.opencode pkgs.opencode
]; ];
-29
View File
@@ -1,29 +0,0 @@
{
pkgs,
lib,
config,
...
}:
let
enabled = config.mod.ai.enable;
in
{
options = {
mod.ai = {
enable = lib.mkEnableOption "enable ai module";
};
};
config = lib.mkIf enabled {
home-manager.users.alex = {
home.packages = [
pkgs.claude-code
];
programs.zsh.shellAliases = {
wclaude = "CLAUDE_CONFIG_DIR=$HOME/.claude-personal claude";
pclaude = "CLAUDE_CONFIG_DIR=$HOME/.claude-work claude";
};
};
};
}
-2
View File
@@ -42,8 +42,6 @@ in
}; };
vm.enable = true; vm.enable = true;
scripts.enable = true; scripts.enable = true;
ai.enable = true;
}; };
}; };
} }
+4 -1
View File
@@ -58,7 +58,7 @@
#+END_SRC #+END_SRC
*** This makes emacsclient startup faster in TUI-mode *** This makes emacsclient startup faster in TUI-mode
#+BEGIN_SRC emacs-lisp #+BEGIN_SRC emacs-lisp
(setq-default xterm-query-timeout 0.1) (setq-default xterm-query-timeout nil)
#+END_SRC #+END_SRC
*** Disable startup messages *** Disable startup messages
#+BEGIN_SRC emacs-lisp #+BEGIN_SRC emacs-lisp
@@ -588,6 +588,9 @@ Setup prefix for keybindings.
) )
) )
(use-package eglot-booster
:after eglot
:config (eglot-booster-mode))
#+END_SRC #+END_SRC
** Go ** Go
#+BEGIN_SRC emacs-lisp #+BEGIN_SRC emacs-lisp
+13
View File
@@ -12,6 +12,18 @@ let
epkgs.flymake-go-staticcheck epkgs.flymake-go-staticcheck
epkgs.tree-sitter-langs epkgs.tree-sitter-langs
epkgs.treesit-grammars.with-all-grammars epkgs.treesit-grammars.with-all-grammars
(epkgs.trivialBuild {
pname = "eglot-booster";
version = "main-2024-04-11";
src = pkgs.fetchFromGitHub {
owner = "jdtsmith";
repo = "eglot-booster";
rev = "e19dd7ea81bada84c66e8bdd121408d9c0761fe6";
hash = "sha256-vF34ZoUUj8RENyH9OeKGSPk34G6KXZhEZozQKEcRNhs=";
};
})
]; ];
}; };
@@ -38,6 +50,7 @@ in
ec ec
emacs emacs
pkgs.wl-clipboard pkgs.wl-clipboard
pkgs.emacs-lsp-booster
pkgs.nixd pkgs.nixd
]; ];
}; };
+1 -45
View File
@@ -6,38 +6,6 @@
}: }:
let let
enabled = config.mod.git.enable; enabled = config.mod.git.enable;
# Wrapper around `ssh-keygen` used as git's SSH signing program. Before a
# signing operation it ensures the passphrase-protected signing key is loaded
# into the agent — `ssh-keygen -Y sign` reads the key from disk and re-prompts
# every commit otherwise, since `AddKeysToAgent` only ever caches auth keys.
# Loading it once (through the GUI askpass) lets later commits reuse the
# cached key from the agent. Verification and every other op pass straight
# through to the real ssh-keygen untouched.
sshSignWrapper = pkgs.writeShellApplication {
name = "git-ssh-sign";
runtimeInputs = [
pkgs.openssh
pkgs.gawk
pkgs.gnugrep
];
text = ''
key="${config.age.secrets."alex.pinwheel-github.com-signing".path}"
case " $* " in
*" -Y sign "*)
fp=""
fp="$(ssh-keygen -lf "$key.pub" 2>/dev/null | awk '{print $2}')" || true
if [ -n "$fp" ] && ! ssh-add -l 2>/dev/null | grep -qF "$fp"; then
# </dev/null detaches stdin so ssh-add uses SSH_ASKPASS (the GUI).
ssh-add "$key" </dev/null || true
fi
;;
esac
exec ssh-keygen "$@"
'';
};
in in
{ {
options = { options = {
@@ -47,7 +15,7 @@ in
}; };
config = lib.mkIf enabled { config = lib.mkIf enabled {
home-manager.users.alex = { lib, ... }: { home-manager.users.alex = {
programs.git = { programs.git = {
enable = true; enable = true;
@@ -65,21 +33,9 @@ in
# Tells Git to use SSH instead of the default GPG # Tells Git to use SSH instead of the default GPG
gpg.format = "ssh"; gpg.format = "ssh";
# Sign via a wrapper that loads the signing key into the agent on
# first use, so subsequent signed commits reuse the cached key
# instead of re-prompting for the passphrase every time.
gpg.ssh.program = "${sshSignWrapper}/bin/git-ssh-sign";
}; };
}; };
home.file.".ssh/config".target = ".ssh/config_source";
home.activation.sshConfig = lib.hm.dag.entryAfter [ "writeBoundary" ] ''
run cat ~/.ssh/config_source > ~/.ssh/config
run chmod 600 ~/.ssh/config
'';
home.packages = [ pkgs.tig ]; home.packages = [ pkgs.tig ];
home.file.".tigrc".text = '' home.file.".tigrc".text = ''
+4 -15
View File
@@ -55,22 +55,12 @@ let
esac esac
} }
# Start the event listener first so monitoradded events emitted during # Bind workspaces on startup
# session startup are not lost in the gap before we begin reading them.
${pkgs.socat}/bin/socat -U - UNIX-CONNECT:"$XDG_RUNTIME_DIR/hypr/$HYPRLAND_INSTANCE_SIGNATURE/.socket2.sock" | while read -r line; do
handle_event "$line"
done &
LISTENER_PID=$!
# Give socat a moment to actually connect before the initial bind.
sleep 0.2
bind_workspaces bind_workspaces
# Re-bind once more after the DRM subsystem has had time to enumerate ${pkgs.socat}/bin/socat -U - UNIX-CONNECT:"$XDG_RUNTIME_DIR/hypr/$HYPRLAND_INSTANCE_SIGNATURE/.socket2.sock" | while read -r line; do
# external connectors, in case they were not yet present at session start. handle_event "$line"
(sleep 3; bind_workspaces) & done
wait $LISTENER_PID
''; '';
in in
{ {
@@ -97,7 +87,6 @@ in
wayland.windowManager.hyprland = { wayland.windowManager.hyprland = {
enable = true; enable = true;
systemd.enable = false; systemd.enable = false;
configType = "hyprlang";
extraConfig = '' extraConfig = ''
exec-once = uwsm app -- waybar exec-once = uwsm app -- waybar
+1 -1
View File
@@ -45,7 +45,7 @@ in
valign = "center"; valign = "center";
outline_thickness = 2; outline_thickness = 2;
dots_center = true; dots_center = true;
fade_on_empty = false; fade_on_empty = true;
placeholder_text = ""; placeholder_text = "";
} }
]; ];
@@ -1,6 +1,6 @@
{ pkgs, ... }: { pkgs, ... }:
{ {
home-manager.users.alex = { home-manager.users.alex = {
home.packages = [ pkgs.typescript-language-server ]; home.packages = [ pkgs.nodePackages.typescript-language-server ];
}; };
} }
+1 -19
View File
@@ -3,28 +3,10 @@
# Enable gnome-keyring at system level for PAM integration # Enable gnome-keyring at system level for PAM integration
services.gnome.gnome-keyring.enable = true; services.gnome.gnome-keyring.enable = true;
# Use openssh's own ssh-agent — gcr's ssh-agent stalls signing RSA keys.
services.gnome.gcr-ssh-agent.enable = false;
programs.ssh.startAgent = true;
home-manager.users.alex = { home-manager.users.alex = {
services.gnome-keyring = { services.gnome-keyring = {
enable = true; enable = true;
components = [ "secrets" ]; components = [ "secrets" "ssh" ];
};
home.sessionVariables = {
# gnome-keyring's PAM hooks export SSH_AUTH_SOCK pointing at a dead gcr
# socket (gcr-ssh-agent is disabled above), which shadows openssh's own
# agent and silently breaks passphrase caching. Force it back to the
# openssh agent started by `programs.ssh.startAgent`.
SSH_AUTH_SOCK = "$XDG_RUNTIME_DIR/ssh-agent";
# Route passphrase prompts through seahorse's GUI askpass instead of the
# terminal. `prefer` uses the GUI even when a tty is attached (ssh only
# falls back to askpass with no controlling terminal otherwise).
SSH_ASKPASS = "${pkgs.seahorse}/libexec/seahorse/ssh-askpass";
SSH_ASKPASS_REQUIRE = "prefer";
}; };
programs.ssh = { programs.ssh = {
+1 -17
View File
@@ -1,23 +1,7 @@
{ pkgs, ... }: { ... }:
{ {
services.tailscale.enable = true; services.tailscale.enable = true;
# Pinned to 1.96.5. 1.98.0 regressed split-DNS handling under work-vpn: the
# netmap's "resolve <tailnet>.ts.net locally via MagicDNS" hint is dropped
# when translated into systemd-resolved config, so *.ts.net queries get sent
# to a public resolver (199.247.155.53) that the corporate VPN's port-53
# egress filter blocks.
services.tailscale.package = pkgs.tailscale.overrideAttrs (_: rec {
version = "1.96.5";
src = pkgs.fetchFromGitHub {
owner = "tailscale";
repo = "tailscale";
tag = "v${version}";
hash = "sha256-vYYb+2OtuXftjGGG0zWJesHccrClB8YZpclv9KzNN/c=";
};
vendorHash = "sha256-rhuWEEN+CtumVxOw6Dy/IRxWIrZ2x6RJb6ULYwXCQc4=";
});
networking.firewall = { networking.firewall = {
checkReversePath = "loose"; checkReversePath = "loose";
allowedUDPPorts = [ 41641 ]; allowedUDPPorts = [ 41641 ];
-18
View File
@@ -22,24 +22,6 @@ in
[[ "$PATH" == "${pkgs.bashInteractive}/bin:"* ]] || export PATH="${pkgs.bashInteractive}/bin:$PATH" [[ "$PATH" == "${pkgs.bashInteractive}/bin:"* ]] || export PATH="${pkgs.bashInteractive}/bin:$PATH"
} }
precmd_functions+=(_ensure_bash_interactive) precmd_functions+=(_ensure_bash_interactive)
# Source the zsh-specific rc file that nix-direnv emits ($DIRENV_ZSH_RC)
# so devshell completions and zsh setup are picked up. direnv itself only
# exports env vars, so without this hook the zsh side of the devshell is
# never loaded. Guarded by LAST_LOADED_DIRENV_ZSH_RC so we don't re-source
# it on every precmd.
_nix_direnv_bridge_hook() {
if [[ -n "$DIRENV_ZSH_RC" && "$LAST_LOADED_DIRENV_ZSH_RC" != "$DIRENV_ZSH_RC" ]]; then
if [[ -f "$DIRENV_ZSH_RC" ]]; then
source "$DIRENV_ZSH_RC"
export LAST_LOADED_DIRENV_ZSH_RC="$DIRENV_ZSH_RC"
echo " direnv zsh loaded..."
fi
fi
}
autoload -Uz add-zsh-hook
add-zsh-hook precmd _nix_direnv_bridge_hook
''; '';
# Configure IntelliJ to exclude .direnv from indexing # Configure IntelliJ to exclude .direnv from indexing
-8
View File
@@ -56,14 +56,6 @@ in
initContent = lib.strings.concatStringsSep "\n" [ initContent = lib.strings.concatStringsSep "\n" [
"export KEYTIMEOUT=1" "export KEYTIMEOUT=1"
# Point every interactive shell at openssh's ssh-agent. home-manager's
# session vars set this too, but hm-session-vars runs once and is then
# inherited — so a tmux server that outlives this change (or started
# with the stale gcr socket) hands new panes a dead SSH_AUTH_SOCK.
# Re-exporting the fixed path per-shell keeps every pane on the same
# agent, so each key is only ever prompted for once per session.
''export SSH_AUTH_SOCK="$XDG_RUNTIME_DIR/ssh-agent"''
"bindkey -v '^?' backward-delete-char" "bindkey -v '^?' backward-delete-char"
"bindkey '^a' beginning-of-line" "bindkey '^a' beginning-of-line"
"bindkey '^e' end-of-line" "bindkey '^e' end-of-line"
+1 -2
View File
@@ -48,7 +48,6 @@ in
mode = "0755"; mode = "0755";
text = '' text = ''
#!${pkgs.bash}/bin/bash #!${pkgs.bash}/bin/bash
[ "$1" = "alex" ] || exit 0
for file in ${authorizedKeysPath}/*; do for file in ${authorizedKeysPath}/*; do
${pkgs.coreutils}/bin/cat "$file" ${pkgs.coreutils}/bin/cat "$file"
done done
@@ -72,7 +71,7 @@ in
KbdInteractiveAuthentication = false; KbdInteractiveAuthentication = false;
}; };
authorizedKeysCommand = "/etc/ssh/authorized_keys_command %u"; authorizedKeysCommand = "/etc/ssh/authorized_keys_command";
authorizedKeysCommandUser = "root"; authorizedKeysCommandUser = "root";
}; };
}; };
@@ -1,7 +0,0 @@
age-encryption.org/v1
-> ssh-ed25519 wkRvNA 1U7rIOHezvIpeb7QBkwz+NOgagrPDltkFtWY2N/hdE4
9rLqhxhc4c7cGB9hDPy9r0y5QbDp0CcYFwIWczB0mlU
-> ssh-ed25519 +oNaHQ w9QggF6DOTSMUC5n+7OKpFOM8iDSQakgx/10pIhM9ws
GOk7OQpLh5OjyZTiJQxd7hhN93EcSMnMxueNh6AwCOk
--- mzQrbeaXCVPu0MqNC0iAMLCiZbopyfYanwhsgDjFbZE
¡&ÈEÆÝ¼žÊÆ öfh(tJn´Ð†ç$^åkì[ Cå
Binary file not shown.
Binary file not shown.
@@ -1,8 +0,0 @@
age-encryption.org/v1
-> ssh-ed25519 wkRvNA 5d22LU+2Mn6fq8SHOCwDht/ebnI2uOk6WKf+t1kwwCM
hCGnLoCy1PX5PJx2IjQnyESmtKM6wVQmyS6aHNhkb1g
-> ssh-ed25519 +oNaHQ gPUMsavbGVPOuvTtNgoDuzrT+q0I7Wbkd6QK5z4oUGc
M3HhrugFlNQkL7WxF1qrW+ocGRqOXid32AVVYLSSxPI
--- TGURCDEIuSFCfXBHxzFHA2svHES7Ubagy1uYjbWCO7g
gá¿Ó†Ò™±£Qâö
oF[H:t aÆr3úZ0ßx @:˜0Ó´¡µÂI[Õ\í=º@eâPíW€Ã†§rX,¶âÈÇ*sš$c:FlÎÙ ±z|B#
Binary file not shown.
Binary file not shown.
Binary file not shown.
+7 -6
View File
@@ -1,7 +1,8 @@
age-encryption.org/v1 age-encryption.org/v1
-> ssh-ed25519 vxPbZg GMTnaun91WNPRFQYkN7xPqdALyMQpXCOq6jj4Q4O1Q0 -> ssh-ed25519 vxPbZg bCF+MdTMA8jH26XEosgyd5N0RsTa9WT/VAIZGsVemHg
OyA/Zk7KQlbSyJlXETFh4JZ57S92oXULa9/mgC019PE 1DNCO2mpsJ68osmFZIzAlY6kjoxCbThpSI4XNEyUNjY
-> ssh-ed25519 +oNaHQ C0K34MjLZDIKv6ci2efBxv1nfvuHKn9OCj26DxjtmBo -> ssh-ed25519 +oNaHQ L91QfGk0r81Df5fHWHdrvXJ54FJ+3S30vus/h4v/H2k
tMG+KxpFX2K8F34iNxDBpb2epd94QPFWo8X/mY67LEI Vi/5VdJhl72cwLiD2qxbmQiKD0RPb4vv6VddGWrPvF4
--- FBMSSr82MYSwER9O8dEs3o2vy/+rc29DxUuziFZqYzw --- F8ff4nu9K9cXId34L2RMBzH4vE3efIuzISN1spbvDHo
l'c(êˆ6 ônnxgA7j"%ÎIÂ'¢{úH¯˜@Þ™vä°ÔêyŸåJûT°©ˆ¼)ÇÃ…¿nSÝ&ìÉš¦e;õ 4zNº½„K U¤Á€,d”Cñæ®®U;Dc«.x` KÙŒ¬Ë­âey¾z’ÛŸ%Œ]­Ÿ…˜m®s÷«¥úb°U×qX{¸±‹!©(u‹|ø!œ_‘}˜íÿ<ùÓ#ù–ÞºˆžXU^¸üs”W­´wñNÐÆU×ù¥D
ú˜
-5
View File
@@ -39,11 +39,6 @@ in {
"manatee/komga-bookmanager-credentials.age".publicKeys = [ manatee alex]; "manatee/komga-bookmanager-credentials.age".publicKeys = [ manatee alex];
"manatee/komga-reading-stats-claude-api-key.age".publicKeys = [ manatee alex]; "manatee/komga-reading-stats-claude-api-key.age".publicKeys = [ manatee alex];
"manatee/komga-reading-stats-komga-api-key.age".publicKeys = [ manatee alex]; "manatee/komga-reading-stats-komga-api-key.age".publicKeys = [ manatee alex];
"manatee/romm-auth-secret-key.age".publicKeys = [ manatee alex ];
"manatee/romm-db-password.age".publicKeys = [ manatee alex ];
"manatee/romm-metadata-api-keys.age".publicKeys = [ manatee alex ];
"manatee/puppy-tracker-invite-code.age".publicKeys = [ manatee alex ];
"manatee/solo-referee-api-key.age".publicKeys = [ manatee alex ];
"backwards/root.backwards.age".publicKeys = [ backwards alex ]; "backwards/root.backwards.age".publicKeys = [ backwards alex ];
"backwards/root.backwards.pub.age".publicKeys = [ backwards alex ]; "backwards/root.backwards.pub.age".publicKeys = [ backwards alex ];