A declarative, reproducible CachyOS workstation managed with chezmoi: system packages, COSMIC desktop applications, developer toolchains, shell/editor configuration, and root-level system services.
Architecture, enforcement, agentic-system, and security documentation lives in
AGENTS.md(the binding agent contract) and in the long-form posts on Dev Journal Blogs.
- 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; and 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 repository's run_onchange lifecycle hooks 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-keys
rustup show; uv --version; bun --version; go version
printf '%s\n' $PATH # toolchain bins on PATH
chezmoi status # no unexpected drift
test -f ~/.config/goose/permission.yaml; or install -m 600 -D ~/.local/share/chezmoi/dot_config/goose/private_permission.yaml ~/.config/goose/permission.yaml # seed goose posture if absent (runtime-owned, not chezmoi-managed)Defined 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 |
jl |
jless |
read-only JSON pager with tree folding & vi keybindings |
tsv |
csvlens -t |
read-only column-aligned TSV/dataset viewer |
Document review is separated from editing to prevent accidental buffer changes. Markdown and dataset reader workflow bifurcates based on complexity:
| Reader | Target File Types | Invocation | Highlights |
|---|---|---|---|
zed |
Complex Markdown (SDD plans, tables) | zed <file> |
GUI sidecar previewer. Bypasses terminal width limits; soft-wraps dense cells. |
bat |
Simple Markdown (inline CLI) | bat <file> |
Fast, non-interactive syntax highlighting for standard CLI reads. |
jless |
JSON | jl |
Interactive pager with tree folding and Helix/vi keybindings. |
csvlens |
TSV, Anki exports & datasets | tsv |
Column-aligned viewer (-t = tab-separated). |
handoff-copy (Fish function → ~/.config/fish/functions/handoff-copy.fish)
snapshots the latest Goose session for a turn handoff: it streams the latest
assistant text block, session ID, git context (branch, commit, working-tree
status), and plan path into a Markdown buffer, then copies it to the Wayland
clipboard via wl-copy for pasting into Gemini Chat.
Fully local speech-to-text: dictate records from the default PipeWire source,
transcribes with whisper.cpp on the CPU, and types the result into the focused
window via dotool (/dev/uinput — no X11 and no compositor-specific protocol).
One-time model fetch. The ggml weights are data, not a package, and are
deliberately untracked. huggingface.co is unreachable from some networks;
hf-mirror.com serves the identical blob — verify the hash before trusting it:
set -l dir ~/.local/share/whisper/models
mkdir -p $dir
curl -fL -o $dir/ggml-base.en.bin \
https://hf-mirror.com/ggerganov/whisper.cpp/resolve/main/ggml-base.en.bin
sha256sum $dir/ggml-base.en.bin
# must print a03779c86df3323075f5e796cb2ce5029f00ec8869eee3fdfb897afe36c6d002
# (147,964,211 bytes)Shortcut. COSMIC settings are intentionally not chezmoi-managed, so bind it
once by hand: Settings → Keyboard Shortcuts → Custom Shortcuts → add
dictate toggle on Super+Alt+D. (Ctrl+Space is left alone — fcitx5 owns it
for input-method switching.)
Usage
| Command | Effect |
|---|---|
dictate toggle |
start recording; press again to stop, transcribe and type |
dictate start / dictate stop |
explicit start / stop-and-type |
dictate last |
re-transcribe the most recent recording |
dictate status |
report whether a recording is in progress |
dictate last --print |
print the transcript instead of typing it |
dictate last --copy |
copy the transcript to the clipboard instead of typing |
dictate --model small.en |
use another model (fetch ggml-small.en.bin the same way) |
Notes:
- Runtime state lives in
$XDG_RUNTIME_DIR/dictate/(tmpfs) — nothing survives a reboot. - Recording is 16 kHz mono
s16, whisper's native format, so there is no resampling. - The packaged
whisper-cppis a CPU build; the RTX 5060 stays idle. GPU inference would need a from-source CUDA build (ggml-cudais packaged, but the Blackwell/sm_120 path is untested here). - Silent or sub-0.4 s recordings are discarded without typing anything.
Managed via dot_local/bin/symlink_* (tracked in toolchains/local-bin.txt): hx → /usr/bin/helix, zed → ~/.local/zed.app/bin/zed.
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. The goose stage additionally HEAD-probes the upstream stable release asset and skips the re-download while its ETag/Last-Modified fingerprint is unchanged (~/.cache/aup/goose.asset).
| 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 |
| tldr | tldr --update |
tldr page cache |
| goose | goose update |
goose CLI self-update (stable channel); skipped while the upstream stable asset is unchanged (HEAD probe, no download) |
| 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 · tldr --update · goose update.
cm
hx system/keyd/default.conf
cma # hook 20 deploys to /etc/keyd/default.conf and restarts keyd.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. It carries the chezmoi execution model (§1–§2), the repository enforcement and verification gates (§1.4), the goose configuration map (§1.5), the refinement/maintenance protocol and operator prompts (§2.5), and the offline goose-docs root (§2.11).
chezmoi diff compares generated target state with live destination files, so
it includes source changes that have not yet been applied when they affect
managed targets. Use it with chezmoi status; the source of truth remains
~/.local/share/chezmoi. If you edited a live file and want to keep it, run
chezmoi re-add <file> before applying.
All run_onchange_*.sh.tmpl files use Go templates for checksums. Render-test
before committing; bash -n intentionally invokes the rendered hooks' declared
shell for syntax validation:
cd ~/.local/share/chezmoi
for f in run_onchange_*.sh.tmpl
chezmoi execute-template < "$f" | bash -n
or echo "FAIL: $f"
endrun_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.