116 lines
3.7 KiB
Nix
116 lines
3.7 KiB
Nix
{...}: {
|
|||
|
|
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 <file>.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;
|
||
|
|
};
|
||
|
|
};
|
||
|
|
};
|
||
|
|
};
|
||
|
|
}
|