GNU Stow

Manage dotfiles as symlink farms — one repo, multiple machines, instant propagation.

Basic Operations

Stow a package — creates symlinks from package dir into target
cd ~/atelier/_projects/personal/dots-quantum && stow -t ~ hyprland

The package directory structure mirrors the target. hyprland/.config/hypr/hyprland.conf becomes ~/.config/hypr/hyprland.conf.

Unstow a package — removes symlinks
cd ~/atelier/_projects/personal/dots-quantum && stow -D -t ~ hyprland
Restow — unstow then stow (use after restructuring a package)
cd ~/atelier/_projects/personal/dots-quantum && stow -R -t ~ hyprland
Dry run — preview what stow would do without making changes
cd ~/atelier/_projects/personal/dots-quantum && stow -n -v -t ~ hyprland

-n simulates, -v shows each symlink operation.

Stow Multiple Packages

Stow several packages at once
cd ~/atelier/_projects/personal/dots-quantum && stow -t ~ tmux nvim git bash
Stow all packages in a directory (careful — includes non-stow dirs)
cd ~/atelier/_projects/personal/dots-quantum && stow -t ~ */
Some directories are NOT stow packages (e.g., hosts/, gpg/). Stow them individually.

Verification

Verify a package is stowed — symlink should point into dots-quantum
ls -la ~/.config/hypr/hyprland.conf
Check which packages are stowed vs missing
cd ~/atelier/_projects/personal/dots-quantum
for pkg in */; do
    pkg="${pkg%/}"
    [[ "$pkg" == "hosts" || "$pkg" == "gpg" ]] && continue
    sample=$(find "$pkg" -type f | head -1)
    [[ -z "$sample" ]] && continue
    target="$HOME/${sample#$pkg/}"
    if [[ -L "$target" ]]; then
        echo "✓ $pkg"
    else
        echo "✗ $pkg — needs: stow -t ~ $pkg"
    fi
done
Find broken symlinks in home directory
find ~ -maxdepth 4 -xtype l 2>/dev/null | head -20

Conflict Resolution

Stow refuses to overwrite existing files — adopt them first
stow --adopt -t ~ tmux

--adopt moves the existing file INTO the stow package, replacing it with a symlink. Then git diff in dots-quantum shows what changed.

--adopt overwrites the package file with the target’s version. Review the diff before committing.
Force stow when target exists as a directory (not a symlink)
# Remove the real directory first, then stow
rm -rf ~/.config/nvim
cd ~/atelier/_projects/personal/dots-quantum && stow -t ~ nvim

Package Structure

Correct package layout — mirrors target directory structure
# Package: hyprland/
# hyprland/.config/hypr/hyprland.conf  →  ~/.config/hypr/hyprland.conf
# hyprland/.config/hypr/hypridle.conf  →  ~/.config/hypr/hypridle.conf

tree hyprland/
NOT a stow package — per-host config containers
# hosts/ and gpg/ are NOT stow packages
# hosts/<hostname>/env-gpu.conf is manually symlinked:
ln -sf ~/atelier/_projects/personal/dots-quantum/hosts/p16g/env-gpu.conf ~/.config/hypr/env-gpu.conf

dots-quantum Workflow

New machine setup — stow core packages
cd ~/atelier/_projects/personal/dots-quantum
for pkg in bash git tmux nvim hyprland waybar foot claude shell; do
    stow -v -t ~ "$pkg"
done
After editing a config — commit in dots-quantum, push
cd ~/atelier/_projects/personal/dots-quantum
git add -A && git commit -m "feat(hyprland): add Zoom window rules"
git push origin main

Changes are live immediately — the symlink means the running config IS the repo file.

Ignoring Files

Create .stow-local-ignore in a package to exclude files
# hyprland/.stow-local-ignore
README.md
\.git

Stow uses Perl regex. Default ignores: .git, CVS, .svn, README.*.

See Also