A declarative, reproducible CachyOS workstation managed with chezmoi.
This repository defines the full software stack of a COSMIC desktop development machine: system packages, desktop applications, developer toolchains, shell/editor configuration, and root-level system services.
| Layer | Technology |
|---|---|
| OS / Desktop | CachyOS (Arch-based) + COSMIC desktop environment |
| Shell | Fish (with 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 (capslock overload), fcitx5 (Mozc/Rime) |
| Package Management | Pacman + AUR helpers (paru / yay) |
| Dev Toolchains | Standalone upstream installers: rustup, uv, bun, ghcup, go |
System software is installed from the CachyOS/Arch repositories and the AUR via the modular package manifests in packages/.
Language-specific developer tooling is kept out of Pacman and installed via official upstream installers:
| Toolchain | Manager | Manifest | Installation 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 |
This avoids version conflicts with distro packages and lets each toolchain self-update on its own schedule.
Three run_onchange_after_*.sh.tmpl hooks run after chezmoi applies config changes:
run_onchange_after_00-install-packages.sh.tmpl— Installs/updates native Pacman packages, AUR packages, and Flatpaks from the manifests.run_onchange_after_10-install-toolchains.sh.tmpl— Bootstrapsrustup,uv,bun,zed, andghcupif missing, then restores sub-tools fromtoolchains/*.txt.run_onchange_after_20-setup-system.sh.tmpl— Configures root-level services:- Deploys
system/keyd/default.confto/etc/keyd/default.confand reloadskeyd. - Sets UFW defaults: deny incoming, allow outgoing, enable firewall.
- Enables
fstrim.timerandpaccache.timer. - Enables
clash-verge-serviceif installed. - Initializes and starts
postgresqlif installed.
- Deploys
system/keyd/default.conf remaps Caps Lock to a dual-function key:
[ids]
*
[main]
capslock = overload(control, esc)- Tap Caps Lock →
Esc - Hold Caps Lock →
Control
- Install CachyOS with a working internet connection.
- Ensure
gitandchezmoiare available (CachyOS ships them by default). - Have your SSH private key ready, or generate a new one.
This repository tracks only private_dot_ssh/config. 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:
ssh-keygen -t ed25519 -C "your_email@example.com" -f ~/.ssh/id_ed25519Then add the new public key to GitHub / GitLab / servers as needed.
chezmoi init --apply https://github.com/flasheddy/dotfiles.gitReplace the URL with your actual repository URL.
- chezmoi copies all dotfiles to
~/.config/,~/.ssh/config,~/.gitconfig, etc. run_onchange_after_00-install-packages.sh.tmplruns:sudo pacman -S --needed --noconfirm - < packages/00-system-base.txtsudo pacman -S --needed --noconfirm - < packages/10-desktop-environment.txtsudo pacman -S --needed --noconfirm - < packages/20-dev-stacks.txtsudo pacman -S --needed --noconfirm - < packages/30-terminal-utilities.txt- AUR packages from
archive/pacman-foreign.txtviaparuoryay - Flatpaks from
packages/flatpak.txt
run_onchange_after_10-install-toolchains.sh.tmplruns:- Installs
rustup,uv,bun,zed, andghcupif missing - Installs tools from
toolchains/uv.txt,toolchains/cargo.txt,toolchains/bun.txt, andtoolchains/go.txt
- Installs
run_onchange_after_20-setup-system.sh.tmplruns:- Installs/configures
keyd - Enables UFW, fstrim, paccache timers
- Sets up PostgreSQL and Clash Verge service if present
- Installs/configures
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.
After the initial apply completes:
# Reboot into COSMIC desktop
sudo reboot
# Verify keyd is running
sudo systemctl status keyd
sudo keyd list
# Verify toolchains
rustup show
uv --version
bun --version
go version
ghc --version
# Verify PATH includes toolchain bins
printf '%s\n' $PATH
# Check chezmoi status
chezmoi statusDefined in ~/.config/fish/config.fish:
| Abbreviation | Expands To | Purpose |
|---|---|---|
hx |
helix |
Launch Helix editor |
cm |
chezmoi cd |
Jump to the chezmoi source directory |
cma |
chezmoi apply |
Apply chezmoi-managed changes to the live system |
cms |
chezmoi status |
Show pending changes |
cm # cd into ~/.local/share/chezmoi
# edit files with your editor, e.g.:
hx dot_config/fish/config.fish
cma # apply changesThis is the safest flow because chezmoi always knows about the changes.
hx ~/.config/fish/config.fish # edit live file
chezmoi re-add ~/.config/fish/config.fish
cma
⚠️ Warning: If you edit a file directly on disk and then runcma(chezmoi apply) withoutchezmoi re-add, chezmoi will overwrite your live changes with the version in its source tree. Alwaysre-addbefore applying when editing live files.
- Edit the appropriate manifest, by package origin:
- Native (official CachyOS/Arch repos) → one of the modular manifests
under
packages/:packages/00-system-base.txt— core system, drivers, networkingpackages/10-desktop-environment.txt— desktop, fonts, GUI appspackages/20-dev-stacks.txt— compilers, runtimes, dev toolspackages/30-terminal-utilities.txt— terminal, editors, CLI utilities
- AUR-only →
archive/pacman-foreign.txt(installed withparu, oryayas fallback). Never add AUR names to thepackages/*.txtmanifests: they feedpacman -Sdirectly, and an unknown name aborts the whole install run (AGENTS.md §1.3). Unsure of the origin?pacman -Si <pkg>succeeds for native packages;paru -Si <pkg>reveals AUR origin.
- Native (official CachyOS/Arch repos) → one of the modular manifests
under
- Run
cma. - The
run_onchange_after_00-install-packages.sh.tmplhook detects the changed checksum and installs new packages — native viapacman, AUR viaparu/yay.
- Edit the appropriate toolchain manifest:
toolchains/uv.txtfor Python toolstoolchains/cargo.txtfor Rust cratestoolchains/bun.txtfor JS/TS toolstoolchains/go.txtfor Go modules
- Run
cma. - The
run_onchange_after_10-install-toolchains.sh.tmplhook installs the new tool.
The primary update workflow is the aup Fish function (documented below).
The raw commands here are the manual per-manager equivalents — a fallback
reference for debugging, or for updating a single manager without a full
aup run.
# Rust
rustup update
cargo install-update -a
# Python / uv
uv self update
uv tool upgrade --all
# Bun
bun upgrade
bun update --global --latest
# Go
gup update
# Haskell
ghcup upgrade
ghcup install ghc recommended
ghcup install cabal recommended
# tldr pages
tldr --updateThe aup Fish function (defined in dot_config/fish/functions/aup.fish)
automates all of the above with per-stage isolation — one failure never
aborts the rest — and prints an OK / SKIP / FAIL summary.
| 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 |
Behavior notes:
- Isolation: each stage runs independently; a failure is recorded and the
remaining stages still execute.
aupexits 1 if any stage failed. - Guards: a missing tool is reported as
SKIP (not installed)instead of aborting the run. - Success cache: a stage that succeeds writes a marker to
~/.cache/aup/<stage>.okand is auto-skipped for the next 2 hours (SKIP (recently completed));--forcebypasses. The cache is user-local and intentionally unmanaged by chezmoi.
For keyd or other root-level config:
cm
hx system/keyd/default.conf
cmaThe run_onchange_after_20-setup-system.sh.tmpl hook will copy the file to /etc/keyd/default.conf and reload the service.
This workstation is maintained with the help of AI agents (e.g. Goose) working directly in the chezmoi source directory. Agents assist with drift audits, config edits, package/toolchain manifest maintenance, and 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 — it defines the architecture rules, operating protocols, and hard prohibitions every agent must follow.
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.
This repository is personal, but the workflow is portable. To adapt it to a different Arch-based machine:
-
Fork this repository on GitHub, then bootstrap chezmoi from your fork:
chezmoi init <your-fork-url> # clones into ~/.local/share/chezmoi
Do not run
chezmoi applyyet — the manifests describe the original machine, not yours. -
Reconcile with a single agent prompt. Point Goose (or any agent) at the repo and ask for a manifest reconciliation audit:
"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 run
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 out of $HOME
├── AGENTS.md # operational rules for AI agents
├── README.md # this file
├── archive/
│ ├── pacman-foreign.txt # legacy AUR package dump
│ └── pacman-native.txt # legacy native package dump
├── dot_config/
│ ├── alacritty/ # Alacritty terminal config
│ ├── fish/
│ │ ├── completions/bun.fish # Bun completions
│ │ ├── conf.d/
│ │ │ ├── rustup.fish # Rustup env setup
│ │ ├── config.fish # Fish shell config
│ │ └── functions/aup.fish # All-update function
│ ├── 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
├── 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.txt # Bun global packages
├── cargo.txt # cargo-installed crates
├── go.txt # Go modules
├── local-bin.txt # manual ~/.local/bin binaries
└── uv.txt # uv-managed Python tools
This repository is defended in depth. Four boundaries keep routine automation — human or AI — from becoming system compromise:
- Isolated blast radius. Standalone developer toolchains
(
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). Hooks are idempotent, degrade gracefully, and re-run only when their rendered content changes. There are no ad-hoc sudo one-liners, and any proposed 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 the live files on disk. It does not show changes you made inside the source tree that have not yet been applied.
Common mistake:
cm
hx dot_config/fish/config.fish
chezmoi diff # shows diff between source and disk — may look empty/odd
cma # applies the source to diskUse chezmoi status and chezmoi diff together, and remember that the source of truth is ~/.local/share/chezmoi.
If you edited a live file and want to keep it:
chezmoi re-add ~/.config/fish/config.fishAll run_onchange_*.sh.tmpl files use Go templates for checksums. Test them before committing:
cd ~/.local/share/chezmoi
chezmoi execute-template < run_onchange_after_00-install-packages.sh.tmpl | bash -n
chezmoi execute-template < run_onchange_after_10-install-toolchains.sh.tmpl | bash -n
chezmoi execute-template < run_onchange_after_20-setup-system.sh.tmpl | bash -nIf any fail, the rendered script has a syntax or template error.
Because hooks are run_onchange_, they only execute when their rendered content changes. To force a hook manually:
# Render and execute a hook directly
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.
If a hook fails partway through:
- 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.