jupiter: GC on every boot, silence mdadm warning, migrate sabnzbd off reused ini
nix.gc (common.nix) is weekly, too slow to catch a switch pinning the old generation's closure on a 29G eMMC — add a full nix-collect-garbage on every boot instead. Also set boot.swraid.mdadmConf so eval stops warning that mdmon will crash (dormant here: the RAID0 array uses native superblocks, so mdmon never actually runs). sabnzbd.configFile is deprecated by the module; move to services.sabnzbd.settings with credentials (web login, api/nzb keys, eweka.nl server) sourced from sops via secretValues instead of living in a plaintext ini. admin_dir/log_dir are pinned absolute at their original /mnt/data location so the existing download queue/history isn't reset by the ini moving to /var/lib/sabnzbd. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
This commit is contained in:
@@ -41,6 +41,21 @@
|
|||||||
# but at ~5G free on a 29G eMMC even 5 is too many — override down to 2.
|
# but at ~5G free on a 29G eMMC even 5 is too many — override down to 2.
|
||||||
boot.loader.systemd-boot.configurationLimit = lib.mkForce 2;
|
boot.loader.systemd-boot.configurationLimit = lib.mkForce 2;
|
||||||
|
|
||||||
|
# A `switch` pins the old generation as a GC root until the box reboots onto
|
||||||
|
# the new one (booted-system vs current-system) — common.nix's nix.gc is
|
||||||
|
# weekly, far too slow to catch that on a 29G eMMC. 2026-08-19: one switch
|
||||||
|
# alone took 14G -> 19G used; only reboot (releases the old root) + this GC
|
||||||
|
# brought it back to 14G. Run a full collect right after every boot instead
|
||||||
|
# of waiting on the weekly timer.
|
||||||
|
systemd.services.gc-on-boot = {
|
||||||
|
description = "Full nix-collect-garbage on every boot";
|
||||||
|
wantedBy = [ "multi-user.target" ];
|
||||||
|
serviceConfig = {
|
||||||
|
Type = "oneshot";
|
||||||
|
ExecStart = "${pkgs.nix}/bin/nix-collect-garbage -d";
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
# Root lives on the ZimaBlade eMMC (mmcblk0). nixos-generate-config runs in
|
# Root lives on the ZimaBlade eMMC (mmcblk0). nixos-generate-config runs in
|
||||||
# the RAM installer and does NOT detect these, so pin them here (merged with
|
# the RAM installer and does NOT detect these, so pin them here (merged with
|
||||||
# hardware-configuration.nix) or stage-1 can't mount root and the box panics.
|
# hardware-configuration.nix) or stage-1 can't mount root and the box panics.
|
||||||
@@ -89,6 +104,10 @@
|
|||||||
# Mounted, NOT formatted; kept out of disko so it is never wiped.
|
# Mounted, NOT formatted; kept out of disko so it is never wiped.
|
||||||
# ⚠️ RAID0 = no redundancy: either 16TB disk failing loses ALL data.
|
# ⚠️ RAID0 = no redundancy: either 16TB disk failing loses ALL data.
|
||||||
boot.swraid.enable = true; # assemble the mdadm array at boot
|
boot.swraid.enable = true; # assemble the mdadm array at boot
|
||||||
|
# Silences "mdmon service will crash" eval warning. RAID0 here uses native
|
||||||
|
# superblocks so mdmon (external-metadata arrays only) never actually runs,
|
||||||
|
# but the module warns unconditionally without SOME MAILADDR/PROGRAM set.
|
||||||
|
boot.swraid.mdadmConf = "MAILADDR root";
|
||||||
fileSystems."/mnt/data" = {
|
fileSystems."/mnt/data" = {
|
||||||
# fs UUID (stable) — the array may enumerate as /dev/md127, so avoid /dev/md0.
|
# fs UUID (stable) — the array may enumerate as /dev/md127, so avoid /dev/md0.
|
||||||
device = "/dev/disk/by-uuid/dadbff6f-652e-49b2-bfed-eb1308ab8b78";
|
device = "/dev/disk/by-uuid/dadbff6f-652e-49b2-bfed-eb1308ab8b78";
|
||||||
|
|||||||
@@ -48,4 +48,20 @@
|
|||||||
# ci-bot access token to allow the ci-bot user to push to repos
|
# ci-bot access token to allow the ci-bot user to push to repos
|
||||||
sops.secrets.gitea_ci_bot_token.owner = "gitea";
|
sops.secrets.gitea_ci_bot_token.owner = "gitea";
|
||||||
|
|
||||||
|
# SABnzbd credentials (web UI login, API keys, eweka.nl usenet server) —
|
||||||
|
# migrated off the reused ini in services/media/sabnzbd.nix into
|
||||||
|
# services.sabnzbd.settings + secretValues. sabnzbd_api_key predates this
|
||||||
|
# migration (provisioned for mediamanager's future use, services/experimental/
|
||||||
|
# mediamanager.nix — not currently imported by any host); reused here as the
|
||||||
|
# same single source of truth rather than duplicating it.
|
||||||
|
# owner = sabnzbd: the module's preStart (replace-secret) runs as the
|
||||||
|
# service's own User=/Group=, and sops secrets default to root:root 0400 —
|
||||||
|
# without this, replace-secret gets Permission denied reading /run/secrets.
|
||||||
|
sops.secrets.sabnzbd_web_username.owner = "sabnzbd";
|
||||||
|
sops.secrets.sabnzbd_web_password.owner = "sabnzbd";
|
||||||
|
sops.secrets.sabnzbd_api_key.owner = "sabnzbd";
|
||||||
|
sops.secrets.sabnzbd_nzb_key.owner = "sabnzbd";
|
||||||
|
sops.secrets.sabnzbd_eweka_username.owner = "sabnzbd";
|
||||||
|
sops.secrets.sabnzbd_eweka_password.owner = "sabnzbd";
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -9,6 +9,11 @@ immich_oauth_client_secret: ENC[AES256_GCM,data:+NbUnwImwFTYNjz3luzczpCf7oMetzYB
|
|||||||
gitea_runner_token: ENC[AES256_GCM,data:8ji4Nia7GMCBBsemUeGZRqzhlk1RnzzOLLdo7+to85KIC5Kz4AtDsQ==,iv:2wotlB1B/Co/NrZVcIVB4AlwL7DF9KnEVKe32FJNErU=,tag:zLzKPgGmXCkGfK7P/74pyw==,type:str]
|
gitea_runner_token: ENC[AES256_GCM,data:8ji4Nia7GMCBBsemUeGZRqzhlk1RnzzOLLdo7+to85KIC5Kz4AtDsQ==,iv:2wotlB1B/Co/NrZVcIVB4AlwL7DF9KnEVKe32FJNErU=,tag:zLzKPgGmXCkGfK7P/74pyw==,type:str]
|
||||||
gitea_provisioning_token: ENC[AES256_GCM,data:aVzD+3qb0eAuGCNIXgzR338jMz9MqXun3nbgfZAirewDwT3D7T5T0Q==,iv:OOeDRk+4CHQyRh09qgUp7I4vcrvuqaAPAh5HgJ10Uvo=,tag:2Yey9e9WZteERDoqkIppWQ==,type:str]
|
gitea_provisioning_token: ENC[AES256_GCM,data:aVzD+3qb0eAuGCNIXgzR338jMz9MqXun3nbgfZAirewDwT3D7T5T0Q==,iv:OOeDRk+4CHQyRh09qgUp7I4vcrvuqaAPAh5HgJ10Uvo=,tag:2Yey9e9WZteERDoqkIppWQ==,type:str]
|
||||||
gitea_ci_bot_token: ENC[AES256_GCM,data:isgOYuA8S6w7WCUr2i2tW4F+b8mCRh8e+rjFJtM1fXEkkUIRNaiADA==,iv:W5IoxhuPCoTP2wLhedu4RYKrC8tBFFJ4B+QvNW3jjTc=,tag:ztxEwDdGJLc/JEkls1jrOA==,type:str]
|
gitea_ci_bot_token: ENC[AES256_GCM,data:isgOYuA8S6w7WCUr2i2tW4F+b8mCRh8e+rjFJtM1fXEkkUIRNaiADA==,iv:W5IoxhuPCoTP2wLhedu4RYKrC8tBFFJ4B+QvNW3jjTc=,tag:ztxEwDdGJLc/JEkls1jrOA==,type:str]
|
||||||
|
sabnzbd_web_username: ENC[AES256_GCM,data:yNU=,iv:t6Ev0bTLovn3gYtOltS14Y/ElUVCYGxxz8wGsgl9R44=,tag:k2yXYQ2yot1HaohCNDgycg==,type:str]
|
||||||
|
sabnzbd_web_password: ENC[AES256_GCM,data:9Lo=,iv:H0Kz8A534RxX+7/Aue8Q87gCzSY5e/TrdDjeVYgC+Tg=,tag:tvVN6g5DheTN67oWsSBLHQ==,type:str]
|
||||||
|
sabnzbd_nzb_key: ENC[AES256_GCM,data:DNVenqhJ7wf5Ng0XRA1gJN95e+90e6D9NImOSHJv/Us=,iv:eqFn0stB5pqh0ls4/impD8gc/lOkORwEJzRP6m7u1XU=,tag:Zs8ogLBZEZLyMvFBqhfpIA==,type:str]
|
||||||
|
sabnzbd_eweka_username: ENC[AES256_GCM,data:eLsTZoM8T8fAlGaXWlDaoQ==,iv:eawyGhN7+d6UfBIbI3y1qgq+MYBGrXP6VfAkSOK6llA=,tag:ELOfQGHU5NOxZFhKOKf8LA==,type:str]
|
||||||
|
sabnzbd_eweka_password: ENC[AES256_GCM,data:Mt3ZHAe2wzacCQq3x9Uy8WxjrVNad1SmU6sl8ZgrkMLymfq2eP4JzO/uPdD33A==,iv:PnFT95Zxqz4QBpPF5PRloKpoa15AU7Ef/Owwy+iDotw=,tag:/uRX00RzHLJN3gws5Qz8SA==,type:str]
|
||||||
sops:
|
sops:
|
||||||
age:
|
age:
|
||||||
- enc: |
|
- enc: |
|
||||||
@@ -29,7 +34,7 @@ sops:
|
|||||||
CzjSDQZTcseEXZNwuzZcfB5Mvq0BQvjOj7lGuxzuE4qwWkdJWGfVLQ==
|
CzjSDQZTcseEXZNwuzZcfB5Mvq0BQvjOj7lGuxzuE4qwWkdJWGfVLQ==
|
||||||
-----END AGE ENCRYPTED FILE-----
|
-----END AGE ENCRYPTED FILE-----
|
||||||
recipient: age1zak7glavmg4026p2389fyqe769vqm4jrryknuqckgqq4merz5f7q44rkkt
|
recipient: age1zak7glavmg4026p2389fyqe769vqm4jrryknuqckgqq4merz5f7q44rkkt
|
||||||
lastmodified: "2026-07-29T21:40:50Z"
|
lastmodified: "2026-08-19T18:39:11Z"
|
||||||
mac: ENC[AES256_GCM,data:1tyHBdY4J4Q7QkjQrV7gCzAM4fGRxP1Qorq+nWW2N5chF1G8pUsof/qenjlGVkC0SwW4vsOHDPOxdPQGUatgsH442BY11lZ1YxbI96uu84XFcZkSye8jyvPLo37Lx6tOd10kZiAeY0hpyYGhJjRrO4p7Tmaa3y5ImiP6sS0Q4o0=,iv:RSn6zIjw76qzIFZKhDMBlcj4JiJDfLu8bO78eaCgj60=,tag:MX1ePOrbEzTs5cMUcuLa3Q==,type:str]
|
mac: ENC[AES256_GCM,data:nyfcDjWrmAHPb4wKPIUGOfQGQtX79Kz4KnOQJClMAicvLZThYP32WJqmNeZiS8QfqDGJMrTmEfxraYsDiMgKA6xDPCJDIB8WajEbXMqN0oSa7s8lxYmwZvHEA3n5G/GOezjr1nqYV4U35wLQrIk1ja4I1Pii5pOe2dgE9/7ugVY=,iv:qTV5813A0PBzukPM6CwCare0c6qo1fckqgM5wUGTbPc=,tag:AgtZ2p9Vi+u8LBplScBUTA==,type:str]
|
||||||
unencrypted_suffix: _unencrypted
|
unencrypted_suffix: _unencrypted
|
||||||
version: 3.13.3
|
version: 3.13.3
|
||||||
|
|||||||
+67
-10
@@ -1,22 +1,79 @@
|
|||||||
{ ... }:
|
{ config, ... }:
|
||||||
|
|
||||||
# SABnzbd — usenet downloader. Reuses the config migrated from the old
|
# SABnzbd — usenet downloader. Migrated off a reused hand-authored ini
|
||||||
# ZimaOS docker stack (servers/API key/history already set up) by pointing
|
# (servers/API key/history originally imported from the old ZimaOS docker
|
||||||
# straight at the real ini instead of generating a fresh NixOS-managed one.
|
# stack) onto NixOS-managed `settings`, per the module's own deprecation
|
||||||
# Runs as the module's default dedicated `sabnzbd` system user — after first
|
# notice for `configFile`. Only the values that differ from SABnzbd's own
|
||||||
# deploy, chown the migrated config dir to it (see README/CLAUDE notes):
|
# built-in defaults are declared here — everything else falls back to the
|
||||||
# chown -R sabnzbd:sabnzbd /mnt/data/AppData/sabnzbd/config
|
# same defaults SABnzbd was already using.
|
||||||
|
#
|
||||||
|
# `admin_dir`/`log_dir` MUST stay absolute: the module writes the merged ini
|
||||||
|
# to /var/lib/sabnzbd/sabnzbd.ini (eMMC), and both dirs are otherwise
|
||||||
|
# relative to wherever the ini lives. Pointing them back at the ORIGINAL
|
||||||
|
# /mnt/data location keeps the existing download queue/history database
|
||||||
|
# (admin_dir) intact — a relative default here would silently "reset"
|
||||||
|
# SABnzbd to an empty queue on first switch, even though nothing was deleted.
|
||||||
{
|
{
|
||||||
services.sabnzbd = {
|
services.sabnzbd = {
|
||||||
enable = true;
|
enable = true;
|
||||||
configFile = "/mnt/data/AppData/sabnzbd/config/sabnzbd.ini";
|
allowConfigWrite = true; # let sabnzbd keep saving state (queue, wizard flags, ...)
|
||||||
allowConfigWrite = true; # real pre-existing state — let sabnzbd keep saving it
|
settings = {
|
||||||
|
misc = {
|
||||||
|
host = "::";
|
||||||
|
port = 8085;
|
||||||
|
web_color = "Night";
|
||||||
|
enable_https = false;
|
||||||
|
url_base = "/sabnzbd";
|
||||||
|
cache_limit = "1G";
|
||||||
|
download_dir = "/mnt/data/HighSeas/Downloads/Incomplete";
|
||||||
|
complete_dir = "/mnt/data/HighSeas/Downloads";
|
||||||
|
admin_dir = "/mnt/data/AppData/sabnzbd/config/admin";
|
||||||
|
log_dir = "/mnt/data/AppData/sabnzbd/config/logs";
|
||||||
|
# Verbatim from the migrated ini — includes a pre-existing "izma ace"
|
||||||
|
# (missing comma) left as-is rather than silently "fixed" here.
|
||||||
|
unwanted_extensions = "exe, com, bat, ink, js, vbs, ps1, sh, py, php, pl, rb, jar, class, swf, scr, hta, msi, msp, msu, pif, ink, chm, vb, vba, ws, wsf, wsh, xll, docm, dotm, xlsm, xltm, pptm, potm, ppsm, sldm, thmx, xlam, ppam, docb, dotb, xltb, mht, mhtml, url, iqylink, deamon, elf, dmg, iso, cue, nrg, img, udf, wim, vhd, vhdx, vmdk, ova, tf, pb, savedmodel, h5, ckpt, meta, index, data-00000-of-00001, vocab, config, model, pt, tgz, tar.gz, bz2, xz, izma ace, arc, cab, jar, izh, pea, sit, sitx, sqx, zoo, pak, upk, bsa, dat, nzb, nzbs, nzb.gz, nzb.bz2";
|
||||||
|
host_whitelist = "cd1a98d07ece, helium, sabnzbd.jupiter.sol, localhost, jupiter, jupiter.sol";
|
||||||
|
username = "@sabnzbd_web_username@";
|
||||||
|
password = "@sabnzbd_web_password@";
|
||||||
|
api_key = "@sabnzbd_api_key@";
|
||||||
|
nzb_key = "@sabnzbd_nzb_key@";
|
||||||
|
};
|
||||||
|
servers."news.eweka.nl" = {
|
||||||
|
name = "news.eweka.nl";
|
||||||
|
displayname = "news.eweka.nl";
|
||||||
|
host = "news.eweka.nl";
|
||||||
|
port = 563;
|
||||||
|
connections = 8;
|
||||||
|
ssl = true;
|
||||||
|
ssl_verify = "strict";
|
||||||
|
username = "@sabnzbd_eweka_username@";
|
||||||
|
password = "@sabnzbd_eweka_password@";
|
||||||
|
};
|
||||||
|
categories = {
|
||||||
|
"*" = { name = "*"; order = 0; pp = 3; };
|
||||||
|
movies = { name = "movies"; order = 1; script = "Default"; priority = -100; };
|
||||||
|
tv = { name = "tv"; order = 2; script = "Default"; priority = -100; };
|
||||||
|
audio = { name = "audio"; order = 3; script = "Default"; priority = -100; };
|
||||||
|
software = { name = "software"; order = 4; script = "Default"; priority = -100; };
|
||||||
|
prowlarr = { name = "prowlarr"; order = 5; script = "Default"; priority = -100; };
|
||||||
|
xxx = { name = "xxx"; order = 6; script = "Default"; priority = -100; };
|
||||||
|
readarr = { name = "readarr"; order = 7; script = "Default"; priority = -100; };
|
||||||
|
};
|
||||||
|
};
|
||||||
|
secretValues = {
|
||||||
|
"@sabnzbd_web_username@" = config.sops.secrets.sabnzbd_web_username.path;
|
||||||
|
"@sabnzbd_web_password@" = config.sops.secrets.sabnzbd_web_password.path;
|
||||||
|
"@sabnzbd_api_key@" = config.sops.secrets.sabnzbd_api_key.path;
|
||||||
|
"@sabnzbd_nzb_key@" = config.sops.secrets.sabnzbd_nzb_key.path;
|
||||||
|
"@sabnzbd_eweka_username@" = config.sops.secrets.sabnzbd_eweka_username.path;
|
||||||
|
"@sabnzbd_eweka_password@" = config.sops.secrets.sabnzbd_eweka_password.path;
|
||||||
|
};
|
||||||
};
|
};
|
||||||
|
|
||||||
# Write access to the shared downloads dir (owned darman:users on disk).
|
# Write access to the shared downloads dir (owned darman:users on disk).
|
||||||
users.users.sabnzbd.extraGroups = [ "users" ];
|
users.users.sabnzbd.extraGroups = [ "users" ];
|
||||||
|
|
||||||
# configFile and the downloads dir both live on the array, but systemd only
|
# download/complete/admin dirs all live on the array, but systemd only
|
||||||
# derives RequiresMountsFor from /var/lib/sabnzbd (eMMC) — so with the array
|
# derives RequiresMountsFor from /var/lib/sabnzbd (eMMC) — so with the array
|
||||||
# absent sabnzbd would start and download onto the 29G OS disk.
|
# absent sabnzbd would start and download onto the 29G OS disk.
|
||||||
systemd.services.sabnzbd.unitConfig.RequiresMountsFor = [ "/mnt/data" ];
|
systemd.services.sabnzbd.unitConfig.RequiresMountsFor = [ "/mnt/data" ];
|
||||||
|
|||||||
Reference in New Issue
Block a user