{ config, lib, pkgs, ... }: let shell = if config.programs.zsh.enable then (lib.getExe config.programs.zsh.package) else "/usr/bin/env sh"; mkScript = name: path: env: deps: let envVars = lib.concatStringsSep "\n" (lib.mapAttrsToList (n: v: "${n}=\"${toString v}\"") env); wrappedScript = pkgs.writeTextFile { name = name; executable = true; destination = "/bin/${name}"; text = '' #!${shell} ${envVars} ${builtins.readFile path} ''; }; in pkgs.symlinkJoin { name = name; paths = [ wrappedScript ] ++ deps; buildInputs = [ pkgs.makeWrapper ]; postBuild = '' wrapProgram $out/bin/${name} \ --prefix PATH : ${lib.makeBinPath deps} ''; meta.mainProgram = name; }; # Script definitions: { path, env?, deps?, conditions? } # condition: attrset of { option (string path), value (expected value) } scriptDefs = { aria2-run = { path = ./aria2-run.sh; deps = [ config.programs.aria2.package ]; conditions = [ { option = "programs.aria2.enable"; value = true; } ]; }; deep-clean = { path = ./deep-clean.sh; }; file-preview = { path = ./file-preview.sh; deps = [ config.programs.kitty.package config.programs.bat.package config.programs.ffmpeg.package config.programs.ouch.package pkgs.file pkgs.librsvg pkgs.poppler-utils ]; conditions = [ { option = "programs.ffmpeg.enable"; value = true; } { option = "programs.ouch.enable"; value = true; } { # not a dependency option = "programs.zathura.enable"; value = true; } { option = "programs.bat.enable"; value = true; } ]; }; file-preview-clean = { path = ./file-preview-clean.sh; deps = [ config.programs.kitty.package ]; conditions = [ { option = "scripts.file-preview.enable"; value = true; } ]; }; fzf-launcher = { path = ./fzf-launcher.sh; deps = [ config.programs.fzf.package pkgs.util-linux ]; conditions = [ { option = "programs.fzf.enable"; value = true; } ]; }; i3-keybindings = { path = ./i3-keybindings.sh; conditions = [ { option = "desktop.enable"; value = true; } ]; }; screen-recording = { path = ./screen-recording.sh; deps = [ config.programs.ffmpeg.package config.programs.slop.package ]; conditions = [ { option = "programs.ffmpeg.enable"; value = true; } { option = "programs.slop.enable"; value = true; } ]; }; system-monitor = { path = ./system-monitor.sh; deps = [ config.programs.bottom.package config.programs.nvtop.package config.programs.tmux.package config.programs.kitty.package ]; conditions = [ { option = "programs.bottom.enable"; value = true; } { option = "programs.nvtop.enable"; value = true; } { option = "programs.tmux.enable"; value = true; } { option = "programs.kitty.enable"; value = true; } ]; }; vpn-connect = { path = ./vpn-connect.sh; env = { SYSTEMD_RESOLVED_PATH = "${config.programs.openvpn.package}/libexec/update-systemd-resolved"; }; deps = [ config.programs.fzf.package config.programs.openvpn.package ]; conditions = [ { option = "programs.openvpn.enable"; value = true; } { option = "programs.fzf.enable"; value = true; } ]; }; vpn-disconnect = { path = ./vpn-disconnect.sh; conditions = [ { option = "scripts.vpn-connect.enable"; value = true; } ]; }; }; # Check whether all conditions for a script are satisfied conditionsMet = name: def: lib.all (cond: lib.attrByPath (lib.splitString "." cond.option) false config == cond.value) ( def.conditions or [ ] ); in { options.scripts = lib.mapAttrs (name: def: { enable = lib.mkOption { type = lib.types.bool; # Auto-enable if all required tool options are satisfied default = conditionsMet name def; description = '' Whether to enable the ${name} script. Defaults to true when all required options are set. ''; }; package = lib.mkOption { type = lib.types.nullOr lib.types.package; readOnly = true; description = "The derivation for the ${name} script. Set automatically when enabled."; }; }) scriptDefs; config = { # Wire up .package for each enabled script, with assertions for unmet conditions scripts = lib.mapAttrs (name: def: { package = lib.mkIf config.scripts.${name}.enable ( mkScript name def.path (def.env or { }) (def.deps or [ ]) ); }) scriptDefs; assertions = lib.concatLists ( lib.mapAttrsToList ( name: def: map (cond: { assertion = !config.scripts.${name}.enable || lib.attrByPath (lib.splitString "." cond.option) false config == cond.value; message = "scripts.${name} is enabled but requires `${cond.option} = ${builtins.toJSON cond.value}`."; }) (def.conditions or [ ]) ) scriptDefs ); home.packages = [ pkgs.file config.programs.bat.package ] ++ lib.filter (x: x != null) (lib.mapAttrsToList (_: s: s.package) config.scripts); }; }