From c015ca8a61eda8c4fea48284e875afba8377ef25 Mon Sep 17 00:00:00 2001 From: h7x4 Date: Mon, 5 May 2025 18:27:06 +0200 Subject: [PATCH] home/modules/downloads-sorter: init --- flake.nix | 1 + home/config/downloads-sorter.nix | 13 +++ home/home.nix | 1 + home/modules/services/downloads-sorter.nix | 111 ++++++++++++++++++++- 4 files changed, 123 insertions(+), 3 deletions(-) create mode 100644 home/config/downloads-sorter.nix diff --git a/flake.nix b/flake.nix index c6ee335..89ac6a6 100644 --- a/flake.nix +++ b/flake.nix @@ -150,6 +150,7 @@ cargo = ./home/modules/programs/cargo; colors = ./home/modules/colors.nix; direnv-auto-prune = ./home/modules/programs/direnv/auto-prune.nix; + downloads-sorter = ./home/modules/services/downloads-sorter.nix; gpg = ./home/modules/programs/gpg; mpd-auto-updater = ./home/modules/services/mpd.nix; neovim-auto-clean-swapfiles = ./home/modules/programs/neovim/auto-clean-swapfiles.nix; diff --git a/home/config/downloads-sorter.nix b/home/config/downloads-sorter.nix new file mode 100644 index 0000000..f00977b --- /dev/null +++ b/home/config/downloads-sorter.nix @@ -0,0 +1,13 @@ +{ ... }: +{ + services.downloads-sorter = { + enable = true; + mappings = { + "pictures" = [ + "*.jpg" + "*.png" + "*.gif" + ]; + }; + }; +} diff --git a/home/home.nix b/home/home.nix index 9cf3c40..0b53823 100644 --- a/home/home.nix +++ b/home/home.nix @@ -8,6 +8,7 @@ in { ./config/xdg ./config/ensure-homedir-structure.nix + ./config/downloads-sorter.nix ./programs/aria2.nix ./programs/atuin.nix diff --git a/home/modules/services/downloads-sorter.nix b/home/modules/services/downloads-sorter.nix index 163f257..7e97da0 100644 --- a/home/modules/services/downloads-sorter.nix +++ b/home/modules/services/downloads-sorter.nix @@ -1,5 +1,110 @@ -{ ... }: +{ config, lib, pkgs, ... }: +let + cfg = config.services.downloads-sorter; +in { - # TODO: create abstraction over `systemd.path` thingy that looks at incoming files and - # sorts them into subdirs by extension. + imports = [ + ../systemd-tmpfiles.nix + ]; + + options.services.downloads-sorter = { + enable = lib.mkEnableOption "downloads sorter units, path activated units to keep the download dir clean"; + + downloadsDir = lib.mkOption { + type = lib.types.path; + description = "Which directory to keep clean"; + default = if config.xdg.userDirs.enable then config.xdg.userDirs.download else "${config.home.homeDirectory}/Downloads"; + defaultText = '' + if config.xdg.userDirs.enable then config.xdg.userDirs.download else "''${config.home.homeDirectory}/Downloads" + ''; + example = '' + "''${config.home.homeDirectory}/downloads" + ''; + }; + + # TODO: allow specifying a dynamic filter together with a system path trigger in an attrset. + mappings = lib.mkOption { + type = with lib.types; attrsOf (listOf str); + description = '' + A mapping from a file pattern to the location where it should be moved. + + By default, the output mapping is relative to the download dir. + If an absolute path is given, the sorter will move the files out of the downloads dir. + ''; + default = { }; + example = { + "pictures" = [ + "*.png" + "*.jpg" + ]; + "documents" = [ "*.pdf" ]; + "/home//archives" = [ "*.rar" ]; + }; + }; + }; + + config = lib.mkIf cfg.enable { + systemd.user.paths = lib.mapAttrs' (dir: globs: let + unitName = "downloads-sorter@${dir}"; + in { + name = unitName; + value = { + Install.WantedBy = [ "paths.target" ]; + Path = { + PathExistsGlob = map (g: "${cfg.downloadsDir}/${g}") globs; + Unit = "${unitName}.service"; + TriggerLimitIntervalSec = "1s"; + TriggerLimitBurst = "1"; + }; + }; + }) cfg.mappings; + + # TODO: deduplicate + systemd.user.services = lib.mapAttrs' (dir: globs: let + unitName = "downloads-sorter@${dir}"; + in { + name = unitName; + value = { + Unit.Description = "Downloads directory watchdog, sorts the downloads directory"; + Service = { + Type = "oneshot"; + SyslogIdentifier = unitName; + ExecStart = let + absolutePath = if (builtins.substring 0 1 dir) == "/" then dir else "${cfg.downloadsDir}/${dir}"; + + script = pkgs.writeShellApplication { + name = "downloads-sorter-${dir}.sh"; + runtimeInputs = [ pkgs.coreutils ]; + text = '' + shopt -s nullglob + + FILES=(${lib.concatMapStringsSep " " (g: "'${cfg.downloadsDir}'/${g}") globs}) + + for file in "''${FILES[@]}"; do + echo "$file -> ${absolutePath}" + mv "$file" '${absolutePath}' + done + ''; + }; + in lib.getExe script; + + PrivateUsers = true; + ProtectSystem = true; + NoNewPrivileges = true; + ProtectKernelTunables = true; + ProtectControlGroups = true; + PrivateNetwork = true; + RestrictNamespaces = true; + }; + }; + }) cfg.mappings; + + systemd.user.tmpfiles.settings."10-downloads-sorter-service" = let + absolutePaths = map (p: if (builtins.substring 0 1 p) == "/" then p else "${cfg.downloadsDir}/${p}") (builtins.attrNames cfg.mappings); + in lib.genAttrs absolutePaths (_: { + d = { + user = config.home.username; + }; + }); + }; }