# dots Unified dotfiles for all machines: one shared base + per-host overlays, deployed with [GNU Stow](https://www.gnu.org/software/stow/). ## Layout Every top-level directory is a Stow *package* (they all share one stow dir, which `--override` requires). `bin/` holds repo tooling and is **not** a package (never stowed): ``` common/ # shared everywhere (mirrors $HOME): fish, nvim, bat, btop, tmux, pi, claude, presenterm, ... gui/ # GUI configs shared by graphical hosts: hypr, waybar, kitty, alacritty, mpv, zellij wm/ # desktop: 4K monitors, NVIDIA, cloud LLM (duskadiy), Wingman work aliases lw/ # laptop: no NVIDIA, roaming VPNs, network/LLM tools fl/ # KDE Plasma box: a few Hyprland overrides + llamacpp, evremap bin/ # tooling (not a stow package): install.sh companions install.sh ``` A **headless** (terminal-only) machine has no overlay package — it runs `common` only. Mark any box headless with `touch ~/.config/dots-headless` (a machine-local, hostname-independent marker); it then skips `gui` too, so the same terminal-only setup replicates across any number of machines. In other words `common` *is* the headless baseline; `gui` and the host packages layer graphical machines on top. ### How it deploys Deploy order is `common → gui → `; later layers win (the host package is stowed last with `--override`, so a host file replaces the common/gui file at the same path). Stow runs with `--no-folding`, so each *file* is symlinked individually (directories stay real) — this is what lets a host override a single file in a shared dir without shadowing the rest. **Editing a config edits the repo copy directly** (it's a symlink) — no deploy step. You only re-run `install.sh` when files are **added or removed** (to create/prune symlinks). See **Syncing** below. The repo must be cloned to **`~/.dots`** (the relative `../.dots` symlink convention). A host's package name must match `hostname -s` (or pass the host explicitly, e.g. `./install.sh lw`). ## Install (new machine) ```fish sudo pacman -S stow git clone ~/.dots cd ~/.dots ./install.sh # auto-detects host via `hostname -s` ``` `./install.sh -h` lists all options: `-n` dry-run, `-a` adopt (first run), `-D` unstow (revert), `-h` help. GUI hosts also need **HyDE installed** first — see *HyDE dependency* below. Once per machine afterwards: `bat cache --build` (registers the repo's custom bat themes — without it every `bat` run warns `Unknown theme 'onedark'`), and `:Lazy restore` inside nvim (`restore` obeys the shared `lazy-lock.json` pins; `sync`/`update` would rewrite them). nvim's treesitter (`main` branch) builds parsers via the `tree-sitter` CLI — `pacman -S tree-sitter-cli` first, or `:TSInstallAll` fails with `ENOENT ... 'tree-sitter'`. ### First run on a machine that still has real config files Stow **aborts the whole package on the first conflict** (one real file blocks *everything* in `common`), and refuses to clobber real files without `--adopt` — you'll see `cannot stow … since neither a link nor a directory`. Adopt them, review, then keep the repo's versions: ```fish cd ~/.dots git status # make sure the repo is clean first (see the restore caveat) ./install.sh -a # --adopt: pull existing files into the repo, symlink them back git diff --stat # what this machine had vs the repo (review the drift!) git restore . # discard adopted content, keep the repo versions ``` `--adopt` overwrites the repo copies with the machine's files, so `git restore .` is what keeps the repo versions. Cautions: adopted changes can land under `common/`/`gui/` even when host-specific, and `git restore .` drops **all** uncommitted edits — so commit/stash anything you want first. To keep a genuine machine change (e.g. custom pyradio `stations.csv`), hand-copy it into the right package instead of restoring that path. ## Syncing (day-to-day) Because configs are symlinks into the repo, **you almost never run `install.sh` to sync**: - **Pull/propagate edits:** `cd ~/.dots && git pull` — changes are live in place instantly. No re-stow, so **no symlink churn and no app reloads** (kitty etc. keep runtime state). - **`bin/dotsync`** does this safely: `git pull`, then re-link via `install.sh` **only if** the pull added/removed/renamed files (pure edits skip the re-stow). `dotsync -n` previews incoming changes. - **`install.sh` is a (re)deploy tool, not a sync tool** — its `stow -R` deletes+recreates every symlink, which is what makes apps reload. Only needed for the initial link or when files are added/removed. - **Never `stow` the repo root** (`stow .dots`, `stow */`) — it folds package dirs into `~` as stray `~/common`, `~/gui`, … symlinks. `install.sh` only ever links package *contents*. - **New host:** create a top-level `/` package (at least a `host.fish`), commit, then `./install.sh`. If it's terminal-only, skip the package and just `touch ~/.config/dots-headless` (it runs `common` only). ## Tooling (`bin/`) | Command | What it does | |---|---| | `./install.sh [host]` | Deploy `common → gui → `. Flags: `-n` dry-run, `-a` adopt (first run), `-D`/`--unstow` revert (remove this host's symlinks; repo + real files kept), `-h` help. Self-heals: headless hosts get any stray `gui` unstowed; fails loudly (exit 1) if a package couldn't stow. | | `./bin/dotsync [-n]` | Sync with least churn: `git pull`, re-stow only if files were added/removed. | | `./bin/reconcile-hyde.sh [-i\|--relink] [host]` | After a HyDE update overwrote `~/.config/hypr`, reconcile HyDE's new files against your dots versions (report / `-i` keep·take·merge each / `--relink` re-symlink). | ## How per-host differences are expressed | Difference | Mechanism | |---|---| | Monitors | `/.config/hypr/monitors.conf` (sourced by the shared `hyprland.conf`) | | NVIDIA | `wm/.config/hypr/nvidia.conf`, pulled in by wm's `host.conf` (fl/lw have no NVIDIA) | | Shell aliases | shared `config.fish` sources `~/.config/fish/host.fish` (per host) | | Bash extras | shared `.bashrc` sources `~/.bashrc.host` (optional, per host) | | fl's Hyprland | overrides ~9 hypr files (`hyprland.conf`, keybinds, windowrules, theme colors, monitors); inherits the rest from `gui` | | Unique apps | in the owning host overlay (fl: llamacpp/evremap; lw: xremap) | `aichat`, `pi`, and `opencode` are **single shared configs** in `common` (LAN-first, with the cloud provider also listed so any host can switch) — no per-host overrides. ## HyDE dependency (important for GUI hosts) The Hyprland config is [HyDE](https://github.com/HyDE-Project/HyDE)-based. `gui/.config/hypr/*` is your customization layer, but it **sources HyDE's framework**: `source = ~/.local/share/hyde/hyprland.conf` — which defines `$mainMod` and other variables and is **not tracked by dots** (HyDE generates it). So a GUI host must have **HyDE installed**, or Hyprland fails to start (`source globbing error` + `invalid mod $mainMod`). - Install/update HyDE with its own installer (`~/HyDE/Scripts/install.sh`); it also pulls the required apps (`xdg-desktop-portal-hyprland`, `xdg-desktop-portal-gtk`, `hyprpolkitagent`). - HyDE's `-r`/restore overwrites `~/.config/hypr` — use **`bin/reconcile-hyde.sh`** afterward to fold its new defaults back against your dots configs. - **Wallpapers and generated themes are HyDE-owned, never in dots** (`**/hyde/themes/` and `**/wallpapers/` are gitignored — they once bloated a source repo to 255 MB). A fresh machine gets them from HyDE, not from this repo. ## Waybar (two worlds: HyDE-generated vs the standalone rig) **HyDE owns `~/.config/waybar/config.jsonc` AND `style.css`** — its `waybar.py` *writes* both on any `--set`/`--update`, **through the stow symlinks, i.e. into this repo's checkout**. So dots track neither. HyDE-managed customization goes via: - `gui/.config/waybar/layouts/*.jsonc` — dots-tracked layouts; switch with `hyde-shell waybar --set `. - `gui/.config/waybar/modules/*.jsonc` — module defs, globbed into the generated config. **The custom cava rig is NOT HyDE-managed**: `layouts/custom.jsonc` + `layouts/custom.css` form a self-contained two-bar config launched raw (`waybar -c …custom.jsonc -s …custom.css` — exec-once, the Ctrl+Alt+W / Super+Shift+R binds, and the `wbar` abbr). **Never route it through `hyde-shell waybar --set`** — waybar.py would clobber the repo's files as above. HyDE only reads from `layouts/`, which is what makes them safe to track. On a rig host, HyDE's own bar (systemd user unit `hyde--bar.service`, auto-restarting) must be disabled once: `systemctl --user stop ; systemctl --user mask `. Note: the Arch `waybar` build has **no native `cava`** module — the rig uses the script module `custom/cava` running `cava-waybar.sh` (self-contained, no HyDE dependency). ## Secrets Never committed. Put `DUSKADIY_API_KEY` etc. in `~/.config/fish/conf.d/secrets.fish` (gitignored). `pi`'s `auth.json` is **not** tracked — it only ever holds `$VAR` references, but pi's `/login` rewrites it with the literal key, so one stray `/login` would commit a real token. A new host bootstraps it with `cp common/.pi/agent/auth.json.example common/.pi/agent/auth.json`, then `./install.sh`. Also gitignored: `fish_variables`, `greet.txt`, pi agent runtime/state, `tmux/plugins/`, `.claude/` (Claude Code project dir/worktrees), `*.bak`/`*.log`. ## Merge notes (history — the one-time unification) The four former per-machine repos had drifted; the merge made some judgment calls: - **`config.fish` / `.bashrc`** were hand-reconciled into a neutral shared base + per-host `host.fish` / `.bashrc.host` (removed a committed `fnm_multishells` runtime PATH, stray absolute home-dir paths, duplicate git aliases). - **`pi` lives at `~/.pi`** (repo `.pi/`); **`tmux` at `~/.config/tmux/`** (delete a legacy `~/.tmux.conf` if present after migrating). - **Appearance drift was unified to one canonical** (mostly wm): bat, btop, cava, fastfetch, lazygit, mpv, pyradio, waybar, zellij, most of nvim. - **nvim** is shared entirely (no host overrides); plugin set / `lazy-lock.json` are shared — run `:Lazy restore` per machine once (obeys the lockfile pins — `:Lazy update` on one machine + commit is how versions move forward; other machines converge with `restore`). - **`zellij`** is one shared config with **vim keybinds** (h/j/k/l) + the `guvbox-dark` theme. - **`misc/{clis,tuis}.txt`** launcher inventories use wm's lists in `common`; a machine needing its own can override in its overlay. ## Third-party content and licensing The repo is GPL-3.0 (`LICENSE`). What that does and doesn't cover: - **`hypr/`, `waybar/` and the other desktop configs are derived from [HyDE](https://github.com/HyDE-Project/HyDE)** (and, further back, legacy Hyprdots). HyDE is itself GPL-3.0, so the derivation and this repo's licence agree — no conflict, and the Hyprdots-era ASCII banners in those files are left in place as attribution. - **`common/.config/aichat/dark.tmTheme`** is *One Half Dark* by Son A. Pham, **MIT**. The licence header at the top of that file is the attribution — don't strip it. - **`common/.config/fastfetch/pngs/`** holds project and distro marks (Arch, the Open Source Initiative and Open Source Hardware logos, …). Trademarks aren't relicensable, so GPL-3 does not extend to them; they are bundled as nominative use, the way most dotfiles repos do. - **`gui/.config/hypr/hyprlock/assets/`** — the lock screen background and logo. EXIF is stripped, so the files carry no provenance; confirm these are yours to redistribute before publishing, or swap them for something you know the origin of.