A declarative, reproducible CachyOS workstation managed with chezmoi: system packages, COSMIC desktop applications, developer toolchains, shell/editor configuration, and root-level system services.
| Layer | Technology |
|---|---|
| OS / Desktop | CachyOS (Arch-based) + COSMIC desktop environment |
| Shell | Fish (CachyOS defaults overridden by custom config) |
| Editors | Helix (primary), Zed, nano |
| Prompt | Starship |
| Terminals | Alacritty, Kitty |
| Theme | Catppuccin Mocha (shell, Helix, Starship, cursors, Zed) |
| Input | keyd (Caps Lock overload), fcitx5 (Mozc/Rime) |
| Package Management | Pacman + AUR helpers (paru/yay) |
| Dev Toolchains | Standalone upstream installers: rustup, uv, bun, ghcup, go |
System software comes from the CachyOS/Arch repositories and the AUR via the modular manifests in packages/. Language tooling is kept out of Pacman and installed via official upstream installers — no version conflicts with distro packages, and each toolchain self-updates on its own schedule:
| Toolchain | Manager | Manifest | Install Location |
|---|---|---|---|
| Rust | rustup + cargo |
toolchains/cargo.txt |
~/.cargo/bin |
| Python CLI tools | uv |
toolchains/uv.txt |
~/.local/bin |
| JS/TS tools | bun |
toolchains/bun.txt |
~/.bun/bin |
| Go tools | go install |
toolchains/go.txt |
~/go/bin |
| Haskell | ghcup |
managed by GHCup | ~/.ghcup/bin, ~/.cabal/bin |
| Manual binaries | — | toolchains/local-bin.txt |
~/.local/bin |
Three run_onchange_after_*.sh.tmpl hooks re-run whenever their rendered content changes:
00-install-packages— installs/updates native Pacman packages, AUR packages (paru/yay), and Flatpaks from the manifests.10-install-toolchains— bootstrapsrustup,uv,bun,zed,ghcupif missing, then restores sub-tools fromtoolchains/*.txt.20-setup-system— root-level services: deployssystem/keyd/default.conf→/etc/keyd/default.confand reloadskeyd; sets UFW defaults (deny incoming, allow outgoing, enable); enablesfstrim.timerandpaccache.timer; enablesclash-verge-serviceand initializes/startspostgresqlif installed. Also ensures the user-level symlink~/AGENTS.md→~/.AGENTS.mdexists (no sudo).
system/keyd/default.conf makes Caps Lock dual-function — tap → Esc, hold → Control:
[ids]
*
[main]
capslock = overload(control, esc)- Install CachyOS with a working internet connection (
gitandchezmoiship by default). - Have your SSH private key ready, or generate a new one.
Only private_dot_ssh/config is tracked; private keys are excluded by .gitignore and must be restored separately.
Option A — copy from backup:
mkdir -p ~/.ssh && chmod 700 ~/.ssh
cp /path/to/backup/id_ed25519 ~/.ssh/
cp /path/to/backup/id_ed25519.pub ~/.ssh/
chmod 600 ~/.ssh/id_ed25519
chmod 644 ~/.ssh/id_ed25519.pubOption B — generate a new key (then add the public key to GitHub/GitLab/servers):
ssh-keygen -t ed25519 -C "your_email@example.com" -f ~/.ssh/id_ed25519chezmoi init --apply https://github.com/flasheddy/dotfiles.git # replace with your repo URLThis copies all dotfiles to ~/.config/, ~/.ssh/config, ~/.gitconfig, etc., then runs the three lifecycle hooks described above in order: packages → toolchains → system services.
Note: The first run can take a long time — AUR builds, Flatpak downloads, and toolchain bootstrapping all happen sequentially. Keep the machine plugged in and online.
sudo reboot # reboot into COSMIC
sudo systemctl status keyd # verify keyd
sudo keyd list
rustup show; uv --version; bun --version; go version; ghc --version
printf '%s\n' $PATH # toolchain bins on PATH
chezmoi status # no unexpected driftDefined in ~/.config/fish/config.fish:
| Abbreviation | Expands To | Purpose |
|---|---|---|
cm |
cd ~/.local/share/chezmoi |
jump to the chezmoi source directory (native cd — no wrapper process) |
cma |
chezmoi apply |
apply source changes to the live system |
cms |
chezmoi status |
show pending changes |
Managed via dot_local/bin/symlink_* (tracked in toolchains/local-bin.txt): hx → /usr/bin/helix.
Flow A — via the source directory (safest; chezmoi always knows about the changes):
cm
hx dot_config/fish/config.fish
cmaFlow B — edit the live file directly:
hx ~/.config/fish/config.fish
chezmoi re-add ~/.config/fish/config.fish # mandatory before applying
cma
⚠️ Warning:cmawithoutchezmoi re-addoverwrites your live edits with the source-tree version. Alwaysre-addfirst when editing live files.
System / AUR package: append to the manifest matching its origin, then cma — hook 00 detects the changed checksum and installs it (native via pacman, AUR via paru/yay).
- Native (official CachyOS/Arch repos) →
packages/00-system-base.txt(core/drivers/networking),10-desktop-environment.txt(desktop/fonts/GUI),20-dev-stacks.txt(compilers/runtimes/dev tools), or30-terminal-utilities.txt(terminals/editors/CLI). - AUR-only →
archive/pacman-foreign.txt. Never add AUR names topackages/*.txt: they feedpacman -Sdirectly, and an unknown name aborts the whole run (AGENTS.md §1.3). Verify origin:pacman -Si <pkg>(native) vsparu -Si <pkg>(AUR).
Language CLI tool: append to toolchains/uv.txt (Python), cargo.txt (Rust), bun.txt (JS/TS), or go.txt (Go), then cma — hook 10 installs it.
The primary update workflow is the aup Fish function (dot_config/fish/functions/aup.fish): per-stage isolation (one failure never aborts the rest), missing tools reported as SKIP (not installed), a 2-hour per-stage success cache in ~/.cache/aup/ (user-local, unmanaged; --force bypasses), and an OK/SKIP/FAIL summary. Exits 1 if any stage failed.
| Stage | Command | Notes |
|---|---|---|
| system | cachy-update |
pacman + AUR via arch-update (prompts for sudo) |
| rustup | rustup update |
Rust toolchain manager |
| cargo | cargo install-update -a |
all cargo-installed crates |
| uv-self | uv self update |
uv itself |
| uv-tools | uv tool upgrade --all |
all uv-managed Python tools |
| bun-self | bun upgrade |
bun itself |
| bun-globals | bun update --global --latest |
global npm packages, no cd needed |
| go | gup update |
all go installed binaries |
| ghcup | ghcup upgrade |
ghcup itself (GHC/cabal via ghcup install) |
| tldr | tldr --update |
tldr page cache |
| Flag | Effect |
|---|---|
| (none) | run the system stage and all toolchain stages |
-t, --toolchains (alias --no-sys) |
skip the system stage — retry toolchains only |
-s, --sys-only |
run only the system stage |
-f, --force |
ignore the success cache and re-run every stage |
-h, --help |
show usage |
Manual per-manager equivalents (debugging fallback, or single-manager updates): rustup update + cargo install-update -a · uv self update + uv tool upgrade --all · bun upgrade + bun update --global --latest · gup update · ghcup upgrade + ghcup install ghc recommended + ghcup install cabal recommended · tldr --update.
cm
hx system/keyd/default.conf
cma # hook 20 deploys to /etc/keyd/default.conf and reloads the serviceThis workstation is maintained with AI agents (e.g. Goose) working directly in the chezmoi source directory: drift audits, config edits, package/toolchain manifest maintenance, hook updates — always under strict operating rules.
The binding contract for all agents is AGENTS.md. Point any agent at it before letting it touch this repository or the live system.
Context-loading note: agents that support context files (goose:
AGENTS.md/.goosehints) auto-load them from the working directory up to the repo root into every session — this repo'sAGENTS.mdqualifies. Rules living above the repo root are covered too: global inlining via~/.config/goose/.goosehintsis active and managed by chezmoi (dot_config/goose/dot_goosehints.tmplrendersdot_AGENTS.mdinline into every goose session), and hook 20 keeps~/AGENTS.mdsymlinked to~/.AGENTS.mdfor tools that read the home path directly.
Audit (read-only):
"Run
chezmoi statusandchezmoi diff, report any drift between the source tree and the live system. Change nothing."
Update packages:
"Update the system, then reconcile newly installed/removed packages with the manifests under
packages/andarchive/pacman-foreign.txt. FollowAGENTS.mdand ask before removing anything."
Add a toolchain:
"Install
<tool>with the appropriate toolchain manager (uv/cargo/bun/go), append it to the matchingtoolchains/*.txtmanifest, and verify withchezmoi status. FollowAGENTS.md."
Refine & Reconcile System (periodic maintenance):
"Run a complete dotfiles refinement audit per AGENTS.md §2.5. Reconcile pacman, AUR, Flatpak, and standalone toolchains with the manifests, scan for drift and portability issues, and report findings before modifying any files."
Rule of thumb: agents may read freely, must document every install in a manifest, and must ask before anything destructive.
-
Fork this repository, then
chezmoi init <your-fork-url>(clones into~/.local/share/chezmoi). Do not apply yet — the manifests describe the original machine, not yours. -
Reconcile with a single agent prompt:
"I forked this dotfiles repository. Run a package and manifest reconciliation per AGENTS.md §2.5 Phase 2: compare my installed pacman, AUR, and Flatpak packages and my standalone toolchains against the manifests under
packages/,archive/, andtoolchains/. Report what to add or remove so the manifests describe THIS machine. Follow AGENTS.md and stop before modifying any files." -
Review, then apply. The agent reports Critical / Missing / Polish findings and waits for approval (AGENTS.md §2.5 Phase 4). Approve the manifest edits, then
chezmoi apply— therun_onchangehooks install everything declaratively.
Before committing to your fork, personalize the machine-specific parts: replace the hosts in private_dot_ssh/config (never commit key material — AGENTS.md §3), review system/keyd/default.conf against your keyboard, and trim fish abbreviations or environment variables you don't want.
~/.local/share/chezmoi
├── .chezmoiignore # keeps repo-meta files (AGENTS.md, README.md) out of $HOME
├── AGENTS.md # binding operational rules for AI agents
├── README.md # this file
├── archive/
│ ├── pacman-foreign.txt # AUR package manifest (paru/yay)
│ └── pacman-native.txt # legacy native package dump
├── dot_AGENTS.md # global agent rules & safety floor → ~/.AGENTS.md
├── dot_config/
│ ├── alacritty/ # Alacritty terminal config
│ ├── fish/ # config.fish, completions/bun.fish, conf.d/rustup.fish, functions/aup.fish
│ ├── goose/ # Goose agent config (config.yaml, dot_goosehints.tmpl → .goosehints)
│ ├── helix/ # Helix editor config
│ ├── kitty/ # Kitty terminal config
│ ├── private_fcitx5/ # fcitx5 input method (Mozc/Rime) config
│ ├── starship.toml # Starship prompt config
│ └── zed/ # Zed editor config
├── dot_gitconfig # Git user config
├── dot_local/bin/symlink_hx # hx → /usr/bin/helix symlink
├── packages/
│ ├── 00-system-base.txt # core system packages
│ ├── 10-desktop-environment.txt # COSMIC / GUI / fonts
│ ├── 20-dev-stacks.txt # compilers / runtimes / LSPs
│ ├── 30-terminal-utilities.txt # terminals / editors / CLI tools
│ ├── flatpak.txt # Flatpak applications
│ └── manifest.md # package manifest documentation
├── private_dot_ssh/
│ ├── .gitignore # ignores private keys
│ └── config # SSH client config
├── run_onchange_after_00-install-packages.sh.tmpl
├── run_onchange_after_10-install-toolchains.sh.tmpl
├── run_onchange_after_20-setup-system.sh.tmpl
├── system/keyd/default.conf # keyd keyboard remapping
└── toolchains/ # bun / cargo / go / local-bin / uv manifests
Four boundaries keep routine automation — human or AI — from becoming system compromise:
- Isolated blast radius. Standalone toolchains (
~/.cargo,~/.local/bin,~/.bun,~/go) live entirely in user space and never touch Pacman or/etc: a broken or malicious user-level tool cannot corrupt the system package layer, and a system update cannot silently replace a pinned toolchain. - Controlled privilege escalation. Root mutations exist in exactly one place: the tracked, checksummed
run_onchange_*.sh.tmplhooks (and the trackedsystem/keyd/default.confthey deploy) — idempotent, graceful, re-run only on content change. No ad-hoc sudo one-liners; any change to a sudo-invoking template must declare a[ROOT IMPACT]tag (AGENTS.md §2.7). - Zero-secret baseline. No private keys, tokens, or credentials are tracked — ever. Only
private_dot_ssh/configis managed;private_dot_ssh/.gitignoreexcludes key material, and hooks never print secrets. Untrusted external code is never executed or ingested into tracked state (AGENTS.md §3). - Human-gated AI mutations. Agents operate under
AGENTS.md: they read freely, but every state change passes the §2.7 review protocol (ground-truth probes → constraint audit → refined prompt → explicit approval), destructive operations always require confirmation, and forbidden actions stop the task rather than being refined around.
chezmoi diff compares the source tree to live files on disk — it does not show unapplied edits you just made inside the source tree. Use chezmoi status and chezmoi diff together; the source of truth is ~/.local/share/chezmoi. If you edited a live file and want to keep it: chezmoi re-add <file>.
All run_onchange_*.sh.tmpl files use Go templates for checksums. Render-test before committing:
cd ~/.local/share/chezmoi
for f in run_onchange_*.sh.tmpl; do
chezmoi execute-template < "$f" | bash -n || echo "FAIL: $f"
donerun_onchange_ hooks only execute when their rendered content changes. To force one manually:
chezmoi execute-template < run_onchange_after_20-setup-system.sh.tmpl | bashOr temporarily rename the file to run_once_after_..., apply, then rename it back.
- Fix the manifest/template that caused the failure.
- Re-run
cma. - If needed, run the rendered hook manually with
bashto see the exact error.
This repository intentionally does not track SSH private keys. If git status ever shows an untracked key in private_dot_ssh/, do not stage it — the directory is protected by private_dot_ssh/.gitignore.
Personal dotfiles. Use at your own risk.