Migration This is my new public git source viewer. Source coming soon. Please contact with concerns/bugs. Contact me

users/ryan/modules/aria2/default.nix

4e6cd5530a533ca220b164472689dd8fd0106889 · 6.7 KB · 184 lines raw

1 # users/ryan/modules/aria2/default.nix
2 #
3 # aria2c RPC daemon + Zen hand-off via baptistecdr's "Aria2 Integration"
4 # (AMO slug aria2-extension). The RPC secret is generated once at activation
5 # into $XDG_STATE_HOME/aria2/rpc-secret (0600) and never touches the repo or
6 # the store; the same activation splices it into both aria2's runtime conf and
7 # the extension's storage.sync row.
8 { config, lib, pkgs, ... }:
9 let
10 cfg = config.ryan.aria2;
11 zen = config.programs.zen-browser;
12 inherit (pkgs.stdenv.hostPlatform) isDarwin isLinux;
13
14 stateDir = "${config.xdg.stateHome}/aria2";
15 secretFile = "${stateDir}/rpc-secret";
16 runtimeConf = "${stateDir}/aria2.conf";
17 sessionFile = "${stateDir}/session";
18
19 extId = "baptistecdr@users.noreply.github.com";
20 # Fixed so captureServer can point at it and re-seeding is idempotent.
21 serverUuid = "6f1c2b7e-0a43-4d8e-9c55-3b2a1e0d9f10";
22
23 # Secret-free half of the config; rpc-secret is appended at activation.
24 baseConf = (pkgs.formats.keyValue { }).generate "aria2-base.conf" ({
25 enable-rpc = true;
26 rpc-listen-all = false;
27 rpc-listen-port = cfg.rpcPort;
28 dir = cfg.downloadDir;
29 input-file = sessionFile;
30 save-session = sessionFile;
31 save-session-interval = 60;
32 continue = true;
33 max-connection-per-server = 8;
34 split = 8;
35 min-split-size = "1M";
36 file-allocation = if isDarwin then "none" else "falloc";
37 log = "${stateDir}/aria2.log";
38 log-level = "warn";
39 } // lib.optionalAttrs isLinux {
40 #ca-certificate = "${pkgs.cacert}/etc/ssl/certs/ca-bundle.crt";
41 });
42
43 # Extension state as stored by src/models/extension-options.ts: one
44 # storage.sync key, "options", holding a *stringified* JSON blob.
45 extOptions = pkgs.writeText "aria2-ext-options.json" (builtins.toJSON {
46 servers.${serverUuid} = {
47 uuid = serverUuid;
48 name = "Local aria2";
49 secure = false;
50 host = "127.0.0.1";
51 port = cfg.rpcPort;
52 path = "/jsonrpc";
53 secret = ""; # filled at activation
54 rpcParameters = { };
55 };
56 captureServer = serverUuid;
57 captureDownloads = true;
58 minFileSizeInBytes = 0;
59 excludedProtocols = [ "blob" "data" ];
60 excludedSites = [ ];
61 excludedFileTypes = [ ];
62 useCompleteFilePath = false;
63 notifyUrlIsAdded = true;
64 notifyFileIsAdded = false;
65 notifyErrorOccurs = true;
66 });
67
68 daemon = pkgs.writeShellApplication {
69 name = "aria2-daemon";
70 runtimeInputs = [ pkgs.aria2 ];
71 text = ''exec aria2c --conf-path="${runtimeConf}" "$@"'';
72 };
73 in
74 {
75 options.ryan.aria2 = {
76 enable = lib.mkEnableOption "aria2 RPC daemon wired into Zen";
77 rpcPort = lib.mkOption { type = lib.types.port; default = 6800; };
78 downloadDir = lib.mkOption {
79 type = lib.types.str;
80 default = "${config.home.homeDirectory}/Downloads";
81 };
82 daemon = lib.mkOption {
83 type = lib.types.package;
84 readOnly = true;
85 default = daemon;
86 description = "Wrapper that runs aria2c against the runtime conf.";
87 };
88 };
89
90 config = lib.mkIf cfg.enable (lib.mkMerge [
91 {
92 home.packages = [ pkgs.aria2 daemon ];
93
94 # Secret + runtime conf. Must precede the launchd/systemd/Shepherd reload and the
95 # Zen fragment, all of which consume what this writes.
96 home.activation.aria2Runtime =
97 lib.hm.dag.entryBetween
98 [ "setupLaunchAgents" "reloadSystemd" "shepherdServices" "zen-browser-default" ]
99 [ "writeBoundary" ] ''
100 if [[ ! -v DRY_RUN ]]; then
101 install -d -m 0700 "${stateDir}"
102 if [ ! -s "${secretFile}" ]; then
103 (umask 077; ${pkgs.openssl}/bin/openssl rand -hex 32 > "${secretFile}")
104 fi
105 [ -e "${sessionFile}" ] || touch "${sessionFile}"
106 tmp="$(mktemp "${stateDir}/.aria2.conf.XXXXXX")"
107 { cat ${baseConf}; printf 'rpc-secret=%s\n' "$(cat "${secretFile}")"; } > "$tmp"
108 chmod 0600 "$tmp"
109 mv -f "$tmp" "${runtimeConf}"
110 fi
111 '';
112
113 programs.zen-browser.policies.ExtensionSettings.${extId} = {
114 install_url = "https://addons.mozilla.org/firefox/downloads/file/5055892/aria2_extension-4.15.4.xpi";
115 installation_mode = "force_installed";
116 updates_disabled = true;
117 };
118
119 # The extension only reads storage.sync (no storage.managed), so
120 # 3rdparty policy can't configure it. With Sync disabled, storage.sync
121 # is the local webext-storage DB; upsert our row there while Zen is
122 # closed. Declarative-owned: UI edits to this extension get reverted.
123 programs.zen-browser.activationFragments.default = [{
124 requiresLock = true;
125 skipSubject = "aria2 extension config";
126 text = ''
127 db="${zen.profilesPath}/${zen.profiles.default.path}/storage-sync-v2.sqlite"
128 if [ ! -f "$db" ]; then
129 echo "aria2: $db not created yet; start Zen once (extension initialises storage.sync), quit, rebuild."
130 elif [[ ! -v DRY_RUN ]]; then
131 payload="$(mktemp)"
132 ${lib.getExe pkgs.jq} -nc \
133 --arg secret "$(cat "${secretFile}")" \
134 --arg uuid "${serverUuid}" \
135 --slurpfile opts ${extOptions} \
136 '{options: ($opts[0] | .servers[$uuid].secret = $secret | tojson)}' > "$payload"
137 ${lib.getBin pkgs.sqlite}/bin/sqlite3 "$db" <<SQL
138 INSERT INTO storage_sync_data (ext_id, data, sync_change_counter)
139 VALUES ('${extId}', CAST(readfile('$payload') AS TEXT), 1)
140 ON CONFLICT(ext_id) DO UPDATE SET
141 data = excluded.data,
142 sync_change_counter = sync_change_counter + 1;
143 SQL
144 rm -f "$payload"
145 fi
146 '';
147 }];
148 }
149
150 (lib.mkIf isDarwin {
151 launchd.agents.aria2 = {
152 enable = true;
153 config = {
154 ProgramArguments = [ (lib.getExe daemon) ];
155 RunAtLoad = true;
156 KeepAlive = true;
157 ProcessType = "Background";
158 # Not read by aria2; changes the plist whenever the base conf
159 # changes, so setupLaunchAgents reloads the agent.
160 EnvironmentVariables.ARIA2_BASE_CONF = "${baseConf}";
161 };
162 };
163 })
164
165 # Linux: a plain systemd user unit. On Guix, targets.guix translates it
166 # to a Shepherd service; on NixOS / systemd distros it runs as-is.
167 (lib.mkIf isLinux {
168 systemd.user.services.aria2 = {
169 Unit = {
170 Description = "aria2 RPC daemon";
171 Documentation = "man:aria2c(1)";
172 # Unit text changes with the base conf -> sd-switch restarts it.
173 X-Restart-Triggers = [ "${baseConf}" ];
174 };
175 Service = {
176 ExecStart = lib.getExe daemon;
177 Restart = "on-failure";
178 };
179 Install.WantedBy = [ "default.target" ];
180 };
181 })
182
183 ]);
184 }