initial aria2 downloader

4e6cd5530a53… · Ryan Schanzenbacher ·

parent 3d323731bc26… view tree

Changed files

diff --git a/users/ryan/common.nix b/users/ryan/common.nix
index aaca782..cd1557c 100644
--- a/users/ryan/common.nix
+++ b/users/ryan/common.nix
@@ -1,7 +1,7 @@
{ config, pkgs, lib, ... }:
{
- imports = [ ./modules/nvim ];
+ imports = [ ./modules/nvim ./modules/aria2 ];
news.display = "silent";
@@ -11,6 +11,8 @@
# My own modules
ryan.neovim.enable = true;
+ ryan.aria2.enable = true;
+
programs.starship = {
enable = true;
settings = {
diff --git a/users/ryan/modules/aria2/default.nix b/users/ryan/modules/aria2/default.nix
new file mode 100644
index 0000000..0f06b9b
--- /dev/null
+++ b/users/ryan/modules/aria2/default.nix
@@ -0,0 +1,184 @@
+# 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
+ '';
+
+ programs.zen-browser.policies.ExtensionSettings.${extId} = {
+ install_url = "https://addons.mozilla.org/firefox/downloads/file/5055892/aria2_extension-4.15.4.xpi";
+ installation_mode = "force_installed";
+ updates_disabled = true;
+ };
+
+ # 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" <<SQL
+ INSERT INTO storage_sync_data (ext_id, data, sync_change_counter)
+ VALUES ('${extId}', CAST(readfile('$payload') AS TEXT), 1)
+ ON CONFLICT(ext_id) DO UPDATE SET
+ data = excluded.data,
+ sync_change_counter = sync_change_counter + 1;
+ SQL
+ rm -f "$payload"
+ fi
+ '';
+ }];
+ }
+
+ (lib.mkIf isDarwin {
+ launchd.agents.aria2 = {
+ enable = true;
+ config = {
+ ProgramArguments = [ (lib.getExe daemon) ];
+ RunAtLoad = true;
+ KeepAlive = true;
+ ProcessType = "Background";
+ # Not read by aria2; changes the plist whenever the base conf
+ # changes, so setupLaunchAgents reloads the agent.
+ EnvironmentVariables.ARIA2_BASE_CONF = "${baseConf}";
+ };
+ };
+ })
+
+ # Linux: a plain systemd user unit. On Guix, targets.guix translates it
+ # to a Shepherd service; on NixOS / systemd distros it runs as-is.
+ (lib.mkIf isLinux {
+ systemd.user.services.aria2 = {
+ Unit = {
+ Description = "aria2 RPC daemon";
+ Documentation = "man:aria2c(1)";
+ # Unit text changes with the base conf -> sd-switch restarts it.
+ X-Restart-Triggers = [ "${baseConf}" ];
+ };
+ Service = {
+ ExecStart = lib.getExe daemon;
+ Restart = "on-failure";
+ };
+ Install.WantedBy = [ "default.target" ];
+ };
+ })
+
+ ]);
+}