Files
dots/README.md
T

265 lines
17 KiB
Markdown

# 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), work aliases
lw/ # laptop: no NVIDIA, roaming VPNs, network/LLM tools
fl/ # KDE Plasma box (no Hyprland session): llamacpp, whisper, 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.
Run it as a bare `./install.sh` — never `./install.sh common` (`common`/`gui` are layers, not hosts,
and install.sh refuses them).
### How it deploys
Deploy order is `common → gui → <host>`; 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).
### Which host package a machine deploys
**Hostname is not the authority** — the machine remembers its own choice, so several boxes can deploy
the same overlay without sharing a hostname. `install.sh` resolves the host as:
1. the explicit argument (`./install.sh wm`),
2. else the name in **`~/.config/dots-host`** — written by `install.sh` itself on every run that
resolved a real package, so a one-time `./install.sh <host>` is all the setup there is,
3. else `hostname -s`, purely as a convenience guess for boxes where the two happen to agree.
`~/.config/dots-host` is a machine-local, hand-editable one-word file — the sibling of the
`dots-headless` marker above. It is what makes **`bin/dotsync` correct**: dotsync re-links
automatically and has no argument to pass, so without a recorded host it could only guess. Switching
a machine to another overlay is just `./install.sh <other>` — the previously recorded host is
unstowed first, so the base layers don't collide with its leftover links.
Mismatched hostnames used to fail in a way worth recognizing: `./install.sh` on a box whose hostname
matches no package deploys `common`, aborts `gui` on every file the real overlay owns
(`existing target is stowed to a different package`) and skips the overlay entirely — a loud
`exit 1`, but a half-applied one, where newly added files silently get no symlink.
## Install (new machine)
```fish
sudo pacman -S stow
git clone <remote> ~/.dots
cd ~/.dots
./install.sh wm # name the host package once — it is remembered (see above)
```
`./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: `: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 <host> # --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 `<name>/` package (at least a `host.fish`), commit, then
`./install.sh <name>` (naming it once records it — later runs and dotsync need no argument).
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 → <host>`. Host = arg, else `~/.config/dots-host`, else `hostname -s`; the resolved host is recorded there. 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, a previously recorded host is released before re-deploying; fails loudly (exit 1) if a package couldn't stow. |
| `./bin/dotsync [-n\|host]` | Sync with least churn: `git pull`, re-stow only if files were added/removed (via `install.sh`, using the recorded host). |
| `./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 | `<h>/.config/hypr/host.lua` (`hl.monitor` + workspace pins; loaded by the shared `hyprland.lua`) |
| NVIDIA | the NVIDIA block in `wm/.config/hypr/host.lua` (lw has no NVIDIA) |
| Shell aliases | shared `config.fish` sources `~/.config/fish/host.fish` (per host) |
| Bash extras | shared `.bashrc` sources `~/.bashrc.host` (optional, per host) |
| Unique apps | in the owning host overlay (fl: llamacpp/evremap; lw: xremap) |
`aichat`, `pi`, and `opencode` share one config in `common` (LAN-first, with the cloud provider also
listed so any host can switch). `lw` and `wm` each carry a host overlay of all three that adds their
own llama.cpp models (lw: CPU; wm: the GTX 1080 Ti router, plus the duskadiy default) — mirror
edits between the common copy and both overlays. The opencode tree is documented
in `common/.config/opencode/README.md`.
## HyDE dependency (important for GUI hosts)
The Hyprland config is [HyDE](https://github.com/HyDE-Project/HyDE)-based, since 2026-08-21 through
HyDE 26.x's **Lua chain** (live on wm since then and on lw since 2026-11; merged into `main`
2026-11-09 — see `LUA-PORT.md`). The repo pins `HYPRLAND_CONFIG` at
`~/.local/share/hypr/hyde.lua` in two files — `gui/.config/fish/conf.d/00-hypr-config.fish`
(SDDM/fish session path) and `gui/.config/uwsm/env-hyprland.d/10-dots.sh` (uwsm path) — and hyde.lua
loads HyDE's `lua/` modules, then the repo's override `gui/.config/hypr/hyprland.lua`, which pulls in
`dots_rules.lua` + `dots_binds.lua` (split out of it) and the per-host `host.lua`. So a GUI host must have **HyDE installed** — it ships `hyde.lua` and the
modules, both overwritten on HyDE updates; the dots layer lives only in `~/.config/hypr/*.lua`, which
survives them. `LUA-PORT.md` has the full map, flip recipe and rollback.
The legacy hyprlang chain (`gui/.config/hypr/*.conf`, plus the two `~/.local/share/hyde/
{hyprland,hyde}.conf` framework copies HyDE 26.x stopped shipping) was **retired 2026-09-10**
(`LUA-PORT.md` step 8) once the Lua soak closed — nothing reads a `.conf` in Lua mode. One line
survives: `gui/.local/share/hyde/hyde.conf` keeps `$default.FONT`, the fallback HyDE's rofi/menu
scripts resolve FONT through (`get_hyprConf`) when the theme's `hypr.theme` names no font. Rolling
back to hyprlang now means restoring those files from history (`git restore --source=bddbbf3 -- gui/.config/hypr wm/.config/hypr lw/.config/hypr gui/.local/share/hyde`), `./install.sh <host>`,
repointing the two pins and relogging; d851213 is the last hyprlang `main` (the port merged
2026-11-09). After pulling the 2026-08-14 audit, a machine must run
**`./bin/hyde-materialize.sh` before `./install.sh`**: the newly-untracked files leave dangling stow
symlinks otherwise, and hypr/hyprlock treat a missing `source =` as a hard parse error.
- 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 <name>`.
- `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 run by `wbar` (`gui/.local/bin/wbar`) as the supervised systemd
user unit `dots-waybar.service` (`Restart=always` — waybar 0.15.0 dies on every DP-2 link
drop/return, and as a bare process nothing relaunched it). `hc.start.bar` in `hyprland.lua`, the
Ctrl+Alt+W / Super+Shift+R binds and the `wbar` command all go through it; **never `killall
waybar`** — systemd brings it back in seconds and a raw relaunch beside it means two bars
(`wbar toggle|restart|reload|stop`). **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 must be
disabled once, or **two bars appear at every login** — and the launcher differs by HyDE vintage:
- current HyDE: systemd user unit `hyde-<XDG_SESSION_DESKTOP>-bar.service` (auto-restarting) —
`systemctl --user stop <unit>; systemctl --user mask <unit>`.
- older (pre-unit) HyDE: `exec-once = $start.BAR` in HyDE's generated `hyprland.conf` — the
hyprlang-era dots blanked it with `$start.BAR=` in `gui/.config/hypr/hyde.conf`; that file went
with the chain (LUA-PORT.md step 8), and under Lua the lever is `hc.start.bar` in
`hyprland.lua`, which launches the rig itself — see HYDE-UPDATE.md step 4.
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.
- **Colour themes**: the repo ships no third-party theme files, so it needs no notices file.
Each app gets its theme from somewhere else:
- **kitty**: HyDE renders `theme.conf` from the current HyDE theme. `kitty.conf` adds only
this repo's own background, foreground and tab bar.
- **aichat**: *One Half Dark* (MIT, by Son A. Pham) is downloaded from upstream at install
time by `bin/dots-themes-fetch.sh`, pinned by commit and checksum.
- **cava**: it writes its own shaders and themes.
- **zellij and bat**: their gruvbox-dark and OneHalfDark themes are built in.
The pi, opencode and alacritty themes are the repo's own files. They hold colour values from
the Catppuccin Mocha and SynthWave '84 palettes (both MIT, by Catppuccin and Robb Owen),
mapped onto each app's keys. Credit, gladly given: a set of colour values isn't a copy of
either licensed theme. Keep it that way: a theme file copied in brings its licence notice
with it.
- **`gui/.local/share/icons/dots-icons/`** ships only its `index.theme`. Its tray/status icons
are symlinks into each host's own *Tela-circle-dracula* (by vinceliuice, installed by HyDE),
made at install time by `bin/dots-icons-link.sh`. Tela-circle publishes no licence, so the
repo doesn't redistribute its SVGs.
- **`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. The
dmz.rs rabbit (`dmz-rabbit*.png`) and the decentrala.org network logo (`network*.png`) are
the community's own marks, used with permission. `neonom.png` is the repo owner's own render
of the Om glyph, offered as **CC0**.
- **`gui/.config/hypr/hyprlock/assets/`** — the lock screen background and logo are AI-generated
by the repo owner (February 2026). They sit outside the GPL-3 grant and are offered as **CC0**.
The keyhole in `background.jpg` is an AI-altered rendition of the Open Source Initiative mark,
used purely decoratively; no affiliation with or endorsement by the OSI is implied.