dotfiles
Health Uyari
- No license — Repository has no license file
- Description — Repository has a description
- Active repo — Last push 0 days ago
- Low visibility — Only 5 GitHub stars
Code Basarisiz
- rm -rf — Recursive force deletion command in .github/actions/setup-shellcheck/action.yml
Permissions Gecti
- Permissions — No dangerous permissions requested
Bu listing icin henuz AI raporu yok.
More AI-augmented dev environment than config store: a git-crypt-secured, bats-tested setup for macOS and headless GPU servers, with interactive and non-interactive bash/zsh scaffolding, a local knowledge stack (qmd, gbrain), Claude Code session archival and redaction, supply-chain-gated package managers, Tailscale routing, and systemd services.
Dotfiles
Cross-platform dotfiles for macOS and headless Ubuntu servers — managed with
GNU Stow, secured with git-crypt.
Quick Start
# 1. Install Homebrew (skip if already installed)
# macOS
/bin/bash -c "$(curl -fsSL https://raw.githubusercontent.com/Homebrew/install/HEAD/install.sh)"
eval "$(/opt/homebrew/bin/brew shellenv)"
# Linux
/bin/bash -c "$(curl -fsSL https://raw.githubusercontent.com/Homebrew/install/HEAD/install.sh)"
eval "$(/home/linuxbrew/.linuxbrew/bin/brew shellenv)"
# 2. Install core tools
brew install stow git-crypt
# 3. Clone and unlock
git clone [email protected]:brettdavies/dotfiles.git ~/dotfiles
cd ~/dotfiles
git-crypt unlock ~/.config/git-crypt/key
# 4. Deploy
scripts/stow-deploy --all # macOS: shared + desktop packages
scripts/stow-deploy --headless --all # Linux: shared packages only
Stow >= 2.4.0 required. Ubuntu 24.04 apt only has 2.3.1, which has a
bug with nesteddot-directories. Use Homebrew/Linuxbrew.
For detailed platform-specific setup (oh-my-zsh, Ghostty, Cursor extensions, iCloud sync), see
BOOTSTRAP.md.
Repository Layout
dotfiles/
├── stow/ Stow packages (symlinked into $HOME; ollama targets /etc)
├── config/
│ ├── shell/ Shell fragments auto-sourced by .profile
│ ├── git/ Per-platform git config templates
│ ├── qmd/ Per-platform qmd collections templates
│ ├── apparmor.d/ System-level AppArmor profiles (deployed via apparmor-deploy.sh)
│ └── systemd/system/ System-level units (NAS mounts via nas-deploy.sh, apparmor-playwright via apparmor-deploy.sh)
├── scripts/
│ ├── stow-deploy Stow wrapper with conflict resolution
│ ├── nas-deploy.sh System-level NAS mount/automount deploy
│ ├── apparmor-deploy.sh System-level AppArmor profile deploy + boot unit (Playwright/Chromium)
│ ├── sshd-locale-deploy.sh Strip LANG/LC_* from sshd AcceptEnv so sessions use the server locale
│ ├── playwright-browsers-deploy.sh Playwright browser binaries into the shared cache (curl + unzip)
│ ├── playwright-deps-deploy.sh Playwright browser launch provisioning (binaries + apparmor + opt-in browser deps)
│ ├── *-enable.sh Service enablers (qmd-serve, qmd-launchd, opendataloader-pdf)
│ ├── tailscale-serve-setup.sh Reproducible tailnet serve config (svc:ollama)
│ ├── macos-gpu-monitor.sh Metal GPU residency/power trace around any command (macOS)
│ ├── lint-shell, lint-workflows, run-tests CI gate dispatchers, shared by CI and the git hooks
│ ├── generate-changelog.py Release changelog extraction from merged PR bodies
│ ├── sync-dev-after-release.sh Backport the released CHANGELOG.md to dev by PR
│ ├── release/ Release gates (drift.sh, guarded-paths.sh)
│ ├── tools-atime/ Multi-package-manager unused-tool audit + reclaim
│ └── sync/ iCloud and Box sync
├── .githooks/ Repo-local git hooks (core.hooksPath)
├── .github/
│ ├── workflows/ CI: release, shellcheck (+ actionlint), bats, and three main-PR guards
│ └── rulesets/ Branch protection rules (protect-dev, protect-main)
├── tests/ bats-core test suites
└── docs/
├── solutions/ Solved problems and patterns (symlink to a separate private repo)
├── runbooks/ Operational runbooks (GPU driver drift, Playwright launch)
├── plans/ Implementation plans (dev only)
└── brainstorms/ Design explorations (dev only)
The engineering-doc directories marked dev only are inert planning text that never ships. They live on dev, the
release recipe strips them from the release branch, and guard-main-docs.yml blocks them from any PR to main — so a
checkout of main carries docs/runbooks/ and the docs/solutions/ symlink but none of the planning trees.
Stow Packages
Each directory under stow/ is a package. Files prefixed with dot- become dotfiles (. prefix) when symlinked viastow --dotfiles.
| Package | What it manages |
|---|---|
bash |
.bashrc, .bash_profile, .bash_aliases |
brew |
Brewfile, Brewfile.optional |
bun |
.bunfig.toml |
caam |
.caam/ (Claude account rotation config + vault, git-crypt encrypted) |
caddy |
.config/caddy/Caddyfile, caddy.service — loopback proxy fronting Ollama for svc:ollama (Host rewrite) (Linux only) |
cargo |
.cargo/config.toml — git dependency fetches routed through the git CLI (Linux only) |
claude |
.claude/ (settings, hooks, statusline, templates), .markdownlint-cli2.yaml |
codex |
.codex/config.toml |
codex-proxy |
systemd user unit for the docker-compose codex-proxy backend (Linux only) |
cursor |
.cursor/rules/, extensions.txt |
gh |
.config/gh/ (GitHub CLI config), .local/bin/gh (merge guard wrapper) |
ghostty |
.config/ghostty/config |
git |
.gitconfig, .config/git/ (ignore, allowed_signers) |
github |
.config/github/ (PR template and other repo-workflow assets) |
gogcli |
.config/gogcli/config.json — Google Workspace CLI config |
launchagent |
~/Library/LaunchAgents/ (macOS only) |
lazygit |
.config/lazygit/config.yml — clipboard over SSH via OSC 52 |
local |
.local/bin/ — signing, prose, session, qmd, and commit helpers (see Local Binaries) |
micro |
.config/micro/settings.json — editor settings |
obsidian |
.config/obsidian/, systemd service, CLI wrapper (Linux only) |
ollama |
systemd service override binding Ollama to loopback 127.0.0.1:11434; system-level, stowed into /etc with sudo (Linux only) |
opencode |
.config/opencode/config.json |
opendataloader-pdf |
Socket-activated hybrid PDF server with idle-exit, systemd user units (Linux only) |
pip |
.config/pip/pip.conf |
qmd |
.local/bin/qmd wrapper (both platforms) + systemd user units for the qmd-serve daemon and index timers (Linux) |
rclone |
.config/rclone/, Box bisync systemd service + timer (Linux only) |
rust |
rustup-update.service + .timer (nightly rustup update stable --no-self-update; units land on Linux only) |
secrets |
.secrets (git-crypt encrypted) |
shell |
.profile — the shell config chain's entry file, which auto-sources config/shell/ |
ssh |
.ssh/config (git-crypt encrypted) |
tmux |
.config/tmux/tmux.conf |
tmuxinator |
Session configs read in place from the repo; never stowed (see Tmuxinator Sessions) |
yazi |
.config/yazi/ — file manager config, keymaps, theme, packages |
zsh |
.zshrc, .zshenv, .zprofile, .p10k.zsh |
Two directories under stow/ are not in any package set and never deploy through stow-deploy: ollama targets /etc
rather than $HOME and is stowed with sudo stow -t /etc -d stow ollama (see
stow/ollama/README.md), and tmuxinator is read in place rather than symlinked. Both are
recorded as exemptions in tests/stow-deploy-packages.bats, which fails when any other stow/ directory is missing
from a package set.
Local Binaries
The local package deploys stow/local/dot-local/bin/ to ~/.local/bin/. These are machine-level entry points: repo
hooks, systemd units, and agent tooling call them by name, so each one resolves the same way on every host without
knowing where the underlying implementation lives.
| Binary | Purpose |
|---|---|
brave-search |
Query the Brave Search API, printing ranked results as plain text or raw JSON |
charcount |
Count characters against platform length caps, exiting non-zero so it gates in scripts |
env |
Put ~/.local/bin on PATH for user-installed binaries |
gh-revision-audit |
List your issues and PRs that still carry prior edit revisions on GitHub |
gstack-config-apply |
Converge ~/.gstack/config.yaml, which gstack rewrites and so cannot be a stow package |
op-ssh-sign-wrapper |
Cross-platform commit signing: 1Password agent on macOS, ssh-keygen -Y sign headless |
qmd-gpu-verify |
Prove the qmd-serve daemon runs its model work on the GPU rather than the CPU |
qmd-ollama-unload-all |
Evict resident Ollama models, but only when VRAM is too low for qmd embed to load safely |
sd-commit-doc |
Commit and push docs/solutions/ files from the shared clone without racing another agent |
tmux-new-session |
Write a tmuxinator config for a repo and start its 3-pane session |
transcribe-diarize |
Local GPU speaker-diarized transcription (ffmpeg → whisperx → pyannote) |
unslop |
Machine-level entry point for the prose scorer, resolved through ~/.claude/skills |
unslop-gate |
Git-hook prose gate over unslop, scoring only the markdown in the change being pushed |
uuidv7 |
Print a time-ordered UUIDv7, used for collision-proof /tmp artifact names |
xurl |
Real xurl on PATH that execs xurl-rs's xr, for callers that load no interactive alias |
Tmuxinator Sessions
Session configs live in stow/tmuxinator/dot-config/tmuxinator/ and are not stowed. config/shell/tmuxinator.sh
exports TMUXINATOR_CONFIG to point at that directory, so tmuxinator reads, writes, and lists projects in the repo
itself — tmuxinator new and tmuxinator copy land on the source of truth with no deploy step.
Keep ~/.config/tmuxinator empty. tmuxinator searches that path in start and stop but not in list, so a config
sitting there shadows the repo: it starts a session that never appears in tmuxinator list. sudo tmuxinator … is the
usual way one gets there, because sudo scrubs TMUXINATOR_CONFIG and falls back to the XDG path — and it strands the
tmux server at UID 0, invisible to your own tmux ls. Run tmuxinator as yourself.
Every config defines the same 3-pane working layout: yazi on the left (1/3 width, full height), a bare shell top-right
(2/3 × 2/3), and lazygit bottom-right (2/3 × 1/3). All three panes start in the project's root.
Start or attach to a configured session with tmuxinator start <name> — it creates the session on the first call and
attaches on every subsequent call, so the same command works whether or not the session is already running:
tmuxinator start anc # local terminal
mux start anc # zsh shell alias (same thing)
ssh <dev-host> -t tmuxinator start anc # over SSH (preferred connection idiom)
Raw tmux attach -t <name> only works after the session has already been started, which makes it the wrong default for
SSH.
To create a new session from scratch (config + first start in one shot), use tmux-new-session <name> <repo-path> — it
writes a new tmuxinator config into stow/tmuxinator/dot-config/tmuxinator/, then runs tmuxinator start.
tmuxinator copy duplicates a config verbatim, including the source project's name: and its on_project_first_start
resize targets. Edit both after copying, or the new session resizes panes in the project it was copied from.tests/tmuxinator-configs.bats enforces that every config's resize targets match its own name:.
System-Level Units (config/systemd/system/)
System-level systemd units are not managed by stow (which targets $HOME). They live in config/systemd/system/
and are deployed via scripts/nas-deploy.sh, which copies them to /etc/systemd/system/ and activates them.
| Unit | Purpose |
|---|---|
mnt-nas.mount |
SMB mount for the NAS (//<nas-host>/openclaw) |
mnt-nas.automount |
On-demand automount, solves WiFi boot race |
Deploy: sudo scripts/nas-deploy.sh (requires /root/.smbcredentials-<nas-host> from 1Password).
Three more system-level configs deploy through their own paths: apparmor-playwright.service (with the AppArmor
profile, via scripts/apparmor-deploy.sh, below), the ollama loopback override (sudo stow -t /etc, see the ollama
package), and the sshd locale change (sudo scripts/sshd-locale-deploy.sh, which edits /etc/ssh/sshd_config in place;
see BOOTSTRAP.md § SSH session locale).
Playwright / browse browser launch (scripts/playwright-deps-deploy.sh)
On Linux the browse tool and Playwright e2e need three things to launch browsers: the browser binaries in the shared
cache, an AppArmor profile (for Chromium's sandbox), and, for Safari/iOS testing, WebKit system libraries. One script
provisions all three, run as your normal user (it escalates to sudo only where needed):
scripts/playwright-deps-deploy.sh # browser binaries + AppArmor profile + boot persistence (Chromium / browse)
scripts/playwright-deps-deploy.sh --webkit # + Safari/iOS system libs (WebKit, heavy ~380 MB)
scripts/playwright-deps-deploy.sh --all # + Chromium and WebKit system libs
The browser binaries are provisioned into the shared cache ($PLAYWRIGHT_BROWSERS_PATH) byscripts/playwright-browsers-deploy.sh (run directly, or via the script above) using curl + unzip rather thanplaywright install — node's extractor deadlocks on this host's io_uring/kernel combo. One canonical version serves
every repo, so per-repo playwright install becomes a no-op; bumping it is a dotfiles edit (the revision map in that
script). WebKit deps are opt-in because they pull ~180 packages and are only needed for Safari/iOS e2e (the mobile-ios
/ tablet projects). See docs/runbooks/playwright-browser-launch.md for
failure signatures, the io_uring root cause, and recovery.
AppArmor profiles (config/apparmor.d/) are deployed by scripts/apparmor-deploy.sh (called by the script above,
or run standalone as sudo scripts/apparmor-deploy.sh), which copies each file to /etc/apparmor.d/, loads it withapparmor_parser -r, and installs apparmor-playwright.service to reload it at boot. The boot unit is required because
Ubuntu's own apparmor.service is skipped at boot on minimized server images, so /etc/apparmor.d/ is otherwise never
reloaded and the profile drops on reboot.
| Profile | Purpose |
|---|---|
playwright |
Grants userns to Playwright's bundled Chromium so the browse tool works on Ubuntu 24.04 |
Shell Environment (config/shell/)
.profile sources every *.sh file in config/shell/ automatically — drop a file in and it's picked up, no manifest
needed.
The loop runs after .profile finishes assembling PATH (Homebrew, ~/.local/bin, bun, cargo). Files here routinely
gate their contents on command -v <tool>, and that guard is evaluated at source time: sourced any earlier, every such
file would silently no-op in a shell that did not inherit a populated PATH — a launchd-spawned terminal, cron, or ssh host cmd. tests/shell-startup-shapes.bats pins the ordering and exercises each shell shape;tests/shell-path-matrix.bats checks PATH assembly across all eight supported invocation shapes, tabulated in
AGENTS.md.
Shell startup latency budgets live in tests/perf/, outside the tests/*.bats glob that the pre-push hook and CI use.
The hook runs that directory first, on a quiet machine, and each measurement is a best-of-N minimum: run at the tail of
the full suite, the several hundred shells it spawns push interactive zsh past its budget with nothing about the config
having changed.
| File | Purpose |
|---|---|
build-flags.sh |
Native-CPU build flags (-march=native) for local compilation |
caam.sh |
Claude account rotation wrapper + daemon |
caches.sh |
XDG cache directory locations |
claude-code.sh |
Claude Code environment variables |
github.sh |
GitHub CLI aliases |
gogcli.sh |
Google Workspace CLI keyring password injection |
languagetool.sh |
LanguageTool wrapper for the shared prose-lint stage |
litellm.sh |
LiteLLM proxy configuration |
lm-studio.sh |
LM Studio PATH setup |
local-paths.sh |
Custom local PATH additions |
models.sh |
AI/ML model storage locations |
platform-linux.sh |
Linux-specific platform checks and config |
python.sh |
Python tooling config |
qmd.sh |
QMD_REMOTE_URL export (qmd-serve daemon URL) |
run-flags.sh |
Runtime performance env vars (CUDA, io_uring, PyTorch) — Linux |
supply-chain.sh |
Supply-chain safety (package age gates) |
taildrive.sh |
Taildrive mount helpers (macOS) |
telemetry.sh |
Telemetry opt-out environment variables |
tmuxinator.sh |
mux and mux-all tmuxinator wrappers |
xurl.sh |
Alias xurl to the xr binary (xurl-rs) |
shell-functions |
Interactive shell utilities (sourced by bashrc/zshrc) |
Secrets Management
Sensitive files are encrypted with git-crypt:
stow/secrets/dot-secrets— API keys and tokensstow/ssh/dot-ssh/config— SSH host configurationsstow/git/dot-config/git/allowed_signers— SSH allowed signers
Git hooks auto-unlock on checkout and merge. Back up ~/.config/git-crypt/key in a password manager — if lost,
encrypted files cannot be recovered.
Git Hooks
Activated via core.hooksPath (set automatically by stow-deploy):
| Hook | Purpose |
|---|---|
| Hook | Purpose |
| --------------- | -------------------------------------------------------------------- |
pre-commit |
Blocks commits on main, verifies commit.gpgsign, gates staged paths |
post-checkout |
Auto-unlocks git-crypt, chains Git LFS |
post-merge |
Auto-unlocks git-crypt, chains Git LFS |
pre-push |
Mirrors CI (shellcheck, actionlint, bats), chains Git LFS |
Both gates call the same dispatchers CI calls — scripts/lint-shell, scripts/lint-workflows, scripts/run-tests — so
the three cannot drift. pre-push runs them over the whole repo; pre-commit runs them over staged paths only. See
AGENTS.md § Local gates mirror CI.
CI and Testing
| Workflow | Trigger | Purpose |
|---|---|---|
release.yml |
Push to main |
CalVer tag + GitHub Release, notes read from CHANGELOG.md |
shellcheck.yml |
Pull request | Lints shell scripts and hooks, then workflows with actionlint |
bats.yml |
Pull request | Runs the bats-core test suites |
guard-main-docs.yml |
Pull request to main |
Blocks engineering docs from reaching main |
guard-main-provenance.yml |
Pull request to main |
Requires every commit to carry a PR reference |
guard-release-branch.yml |
Pull request to main |
Rejects any head branch that is not release/* |
shellcheck.yml and bats.yml are required status checks on both dev and main; main additionally requires the
three guard callers. They run on every pull request with no path filter so the required context is always reported. Thepre-push hook runs the same checks locally and skips them for markdown-only pushes.
release.yml does not generate the changelog. CHANGELOG.md is generated on the release branch byscripts/generate-changelog.py and committed through the release PR; the workflow only extracts its topmost section as
the GitHub Release body. See RELEASES.md.
Shell scripts are tested with bats-core (scripts/run-tests --all). Suites
cover stow-deploy and its package sets, git hooks and the gate dispatchers, the shell config chain and its PATH
matrix, supply-chain age gates, symlinks, the qmd-serve and opendataloader-pdf units, tmuxinator configs, and the CLI
wrappers.
Performance
Shell startup budgets are enforced — non-interactive shells must start under 200ms, interactive shells under 500ms.
See docs/solutions/performance-issues/ for optimization details.
Cross-Platform Notes
$OSTYPEchecks gate macOS-specific features;$HOMEused everywhere- Homebrew paths:
/opt/homebrew(macOS) vs/home/linuxbrew/.linuxbrew(Linux) - Git signing: 1Password on macOS,
ssh-keygenfallback on Linux (viaop-ssh-sign-wrapper) - SSH config uses
Match execfor platform-conditional 1Password agent paths - All GitHub/Gist URLs rewritten to SSH via
url.insteadOfin.gitconfig - SSH key:
~/.ssh/brett_ed25519on all machines - oh-my-zsh plugins: brew symlinks on macOS, git clones on Linux
Release Automation
Every squash merge to main triggers a GitHub Action that computes a CalVer version (YYYY.MM.DD), tags the commit,
and creates a GitHub Release. Release notes are extracted from the topmost section of the committed CHANGELOG.md. See
RELEASES.md for the end-to-end flow (feature branch → dev → release/* overlay branch → main).
CI_RELEASE_TOKEN Secret
The workflow pushes back to protected main, which requires a fine-grained PAT stored as the CI_RELEASE_TOKEN repo
secret.
Create / rotate the token:
op read "op://secrets-dev/dotfiles_RELEASE_TOKEN/credential" \
| gh secret set CI_RELEASE_TOKEN
Creating a new PAT (if the 1Password entry doesn't exist):
- Go to https://github.com/settings/personal-access-tokens/new
- Repository access:
brettdavies/dotfilesonly - Permissions: Contents: Read and write
- Save to 1Password at
secrets-dev/dotfiles_RELEASE_TOKEN - Run the
gh secret setcommand above
Documentation
Project vocabulary is defined in CONCEPTS.md. Operational runbooks live in docs/runbooks/ (headless GPU
driver drift, Playwright browser launch). Past solutions and design decisions live in docs/solutions/ (a symlink to a
separate private repo):
- Deployment — cross-platform stow deployment, shell config fixes, headless git signing, conflict resolution
- Configuration — branch divergence reconciliation, workflow enforcement
- Performance — shell startup optimization, zsh interactive startup tuning
License
Personal dotfiles — use at your own risk.
Yorumlar (0)
Yorum birakmak icin giris yap.
Yorum birakSonuc bulunamadi