# users/ryan/modules/aria2/default.nix # # aria2c RPC daemon + Zen hand-off via baptistecdr's "Aria2 Integration" # (AMO slug aria2-extension). The RPC secret is generated once at activation # into $XDG_STATE_HOME/aria2/rpc-secret (0600) and never touches the repo or # the store; the same activation splices it into both aria2's runtime conf and # the extension's storage.sync row. { config, lib, pkgs, ... }: let cfg = config.ryan.aria2; zen = config.programs.zen-browser; inherit (pkgs.stdenv.hostPlatform) isDarwin isLinux; stateDir = "${config.xdg.stateHome}/aria2"; secretFile = "${stateDir}/rpc-secret"; runtimeConf = "${stateDir}/aria2.conf"; sessionFile = "${stateDir}/session"; extId = "baptistecdr@users.noreply.github.com"; # Fixed so captureServer can point at it and re-seeding is idempotent. serverUuid = "6f1c2b7e-0a43-4d8e-9c55-3b2a1e0d9f10"; # Secret-free half of the config; rpc-secret is appended at activation. baseConf = (pkgs.formats.keyValue { }).generate "aria2-base.conf" ({ enable-rpc = true; rpc-listen-all = false; rpc-listen-port = cfg.rpcPort; dir = cfg.downloadDir; input-file = sessionFile; save-session = sessionFile; save-session-interval = 60; continue = true; max-connection-per-server = 8; split = 8; min-split-size = "1M"; file-allocation = if isDarwin then "none" else "falloc"; log = "${stateDir}/aria2.log"; log-level = "warn"; } // lib.optionalAttrs isLinux { #ca-certificate = "${pkgs.cacert}/etc/ssl/certs/ca-bundle.crt"; }); # Extension state as stored by src/models/extension-options.ts: one # storage.sync key, "options", holding a *stringified* JSON blob. extOptions = pkgs.writeText "aria2-ext-options.json" (builtins.toJSON { servers.${serverUuid} = { uuid = serverUuid; name = "Local aria2"; secure = false; host = "127.0.0.1"; port = cfg.rpcPort; path = "/jsonrpc"; secret = ""; # filled at activation rpcParameters = { }; }; captureServer = serverUuid; captureDownloads = true; minFileSizeInBytes = 0; excludedProtocols = [ "blob" "data" ]; excludedSites = [ ]; excludedFileTypes = [ ]; useCompleteFilePath = false; notifyUrlIsAdded = true; notifyFileIsAdded = false; notifyErrorOccurs = true; }); daemon = pkgs.writeShellApplication { name = "aria2-daemon"; runtimeInputs = [ pkgs.aria2 ]; text = ''exec aria2c --conf-path="${runtimeConf}" "$@"''; }; in { options.ryan.aria2 = { enable = lib.mkEnableOption "aria2 RPC daemon wired into Zen"; rpcPort = lib.mkOption { type = lib.types.port; default = 6800; }; downloadDir = lib.mkOption { type = lib.types.str; default = "${config.home.homeDirectory}/Downloads"; }; daemon = lib.mkOption { type = lib.types.package; readOnly = true; default = daemon; description = "Wrapper that runs aria2c against the runtime conf."; }; }; config = lib.mkIf cfg.enable (lib.mkMerge [ { home.packages = [ pkgs.aria2 daemon ]; # Secret + runtime conf. Must precede the launchd/systemd/Shepherd reload and the # Zen fragment, all of which consume what this writes. home.activation.aria2Runtime = lib.hm.dag.entryBetween [ "setupLaunchAgents" "reloadSystemd" "shepherdServices" "zen-browser-default" ] [ "writeBoundary" ] '' if [[ ! -v DRY_RUN ]]; then install -d -m 0700 "${stateDir}" if [ ! -s "${secretFile}" ]; then (umask 077; ${pkgs.openssl}/bin/openssl rand -hex 32 > "${secretFile}") fi [ -e "${sessionFile}" ] || touch "${sessionFile}" tmp="$(mktemp "${stateDir}/.aria2.conf.XXXXXX")" { cat ${baseConf}; printf 'rpc-secret=%s\n' "$(cat "${secretFile}")"; } > "$tmp" chmod 0600 "$tmp" mv -f "$tmp" "${runtimeConf}" fi ''; } (lib.mkIf config.ryan.zen.enable { ryan.zen.extensions.aria2 = { id = extId; version = "4.15.4"; }; # The extension only reads storage.sync (no storage.managed), so # 3rdparty policy can't configure it. With Sync disabled, storage.sync # is the local webext-storage DB; upsert our row there while Zen is # closed. Declarative-owned: UI edits to this extension get reverted. programs.zen-browser.activationFragments.default = [{ requiresLock = true; skipSubject = "aria2 extension config"; text = '' db="${zen.profilesPath}/${zen.profiles.default.path}/storage-sync-v2.sqlite" if [ ! -f "$db" ]; then echo "aria2: $db not created yet; start Zen once (extension initialises storage.sync), quit, rebuild." elif [[ ! -v DRY_RUN ]]; then payload="$(mktemp)" ${lib.getExe pkgs.jq} -nc \ --arg secret "$(cat "${secretFile}")" \ --arg uuid "${serverUuid}" \ --slurpfile opts ${extOptions} \ '{options: ($opts[0] | .servers[$uuid].secret = $secret | tojson)}' > "$payload" ${lib.getBin pkgs.sqlite}/bin/sqlite3 "$db" < sd-switch restarts it. X-Restart-Triggers = [ "${baseConf}" ]; }; Service = { ExecStart = lib.getExe daemon; Restart = "on-failure"; }; Install.WantedBy = [ "default.target" ]; }; }) ]); }