{...}: { flake.nixosModules.stow = { config, pkgs, lib, ... }: let cfg = config.ahmza.stow; stowScript = pkgs.writeShellApplication { name = "ahmza-stow"; runtimeInputs = [pkgs.stow pkgs.coreutils pkgs.findutils pkgs.gnused]; text = '' set -euo pipefail REPO="${cfg.repoPath}" TARGET="$HOME" if [ ! -d "$REPO" ]; then echo "ahmza-stow: dotfiles repo not found at $REPO; skipping." >&2 echo " (clone it with: git clone ${cfg.repoUrl} $REPO)" >&2 exit 0 fi packages=(${lib.escapeShellArgs cfg.packages}) if [ ''${#packages[@]} -eq 0 ]; then echo "ahmza-stow: no packages configured; nothing to do." exit 0 fi cd "$REPO" # Back up any existing non-symlink targets that would block stowing $pkg. # Mirrors the dotfiles repo's own install script: list the files/links the # package provides (relative to the package dir) and back up only real # (non-symlink) targets. Skipping symlinks means we never write through an # already-stowed folded directory symlink into the repo itself. # Backups go to .pre-stow.bak. backup_conflicts() { local pkg="$1" rel target while IFS= read -r rel; do target="$HOME/$rel" if [ -e "$target" ] && [ ! -L "$target" ]; then echo "ahmza-stow: backing up existing $target -> $target.pre-stow.bak" mv "$target" "$target.pre-stow.bak" fi done < <(cd "$REPO/$pkg" && find . \( -type f -o -type l \) | sed 's|^\./||') } for pkg in "''${packages[@]}"; do if [ ! -d "$pkg" ]; then echo "ahmza-stow: package '$pkg' not present in repo; skipping." >&2 continue fi echo "ahmza-stow: stow -R $pkg" # Try to stow first; only back up (and retry) if there's a real conflict. if ! stow -R --target="$TARGET" "$pkg" 2>/dev/null; then echo "ahmza-stow: conflicts detected for '$pkg'; backing up and retrying." backup_conflicts "$pkg" if ! stow -R --target="$TARGET" "$pkg"; then echo "ahmza-stow: failed to stow '$pkg' — resolve manually." >&2 fi fi done echo "ahmza-stow: done." ''; }; in { options.ahmza.stow = { enable = lib.mkEnableOption "automatic GNU Stow of dotfiles on login"; user = lib.mkOption { type = lib.types.str; description = "User whose dotfiles should be stowed."; }; repoPath = lib.mkOption { type = lib.types.str; default = "/home/${cfg.user}/dot"; defaultText = "/home/\${config.ahmza.stow.user}/dot"; description = "Absolute path to the stow dotfiles repository."; }; repoUrl = lib.mkOption { type = lib.types.str; default = "https://github.com/ahmzaa/dot.git"; description = "URL of the dotfiles repo (used only in help messages)."; }; packages = lib.mkOption { type = lib.types.listOf lib.types.str; default = []; description = '' Stow package names (top-level dirs in the dotfiles repo) to link. Feature modules append to this via lib.mkAfter / list concatenation. ''; }; }; config = lib.mkIf cfg.enable { environment.systemPackages = [pkgs.stow stowScript]; systemd.user.services.ahmza-stow = { description = "Stow dotfiles for ${cfg.user}"; wantedBy = ["default.target"]; serviceConfig = { Type = "oneshot"; ExecStart = lib.getExe stowScript; }; }; }; }; }