No description
  • Lua 44.2%
  • Python 28.6%
  • Shell 12.9%
  • Go Template 8.8%
  • C 3.8%
  • Other 1.7%
Find a file
Ben Trinh 6f6a633244 feat(bin): add --docker snapshot mode to bkp
Why:
- Wanted point-in-time tar.zst snapshots of the compose files and docker data
  on the external pool, separate from the incremental rsync mirror.
- Archiving a live docker_data yields a torn copy that only reveals itself at
  restore time, so the stacks have to come down first — and anything that
  stops services must guarantee it starts them again.

What:
- --docker mode reusing the existing mount/marker guards.
- Brings down only the stacks that are actually running, so one deliberately
  left down is not started by the backup, and restores them from an EXIT/INT
  trap so a failed tar, a full disk or Ctrl-C still leaves services up.
- tar piped into zstd -T0 rather than `tar -I`: the -I form is GNU-only and
  misparses under bsdtar, and the pipe also means tar reads as root while
  zstd writes as the user, so archives are not left root-owned on the pool.
- --numeric-owner so container UIDs survive a restore onto a system without
  matching user names.
- Verifies each archive with zstd -t, deletes it on failure, and prunes to
  KEEP=3 per source only when the new archive verified.
- Reports uncompressed size against free space, noting that mergerfs cannot
  split one archive across branches.

Impact:
- Default rsync behaviour unchanged; --docker is opt-in and dry-run by default.
- Verified end to end with stubbed docker/mountpoint: dry run touches nothing,
  running vs stopped stacks handled separately, archives restore byte-for-byte,
  retention holds at 3, and the failure path still restarts the stack.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-09-25 00:53:09 +10:00
.chezmoidata refactor(chezmoi): drop machine.yaml, read config from chezmoi.yaml 2026-09-24 23:49:32 +10:00
.chezmoitemplates fix(brew): inline package ranges in run script; remove unused brewfile.tmpl 2026-09-20 16:07:58 +10:00
.opencode chore(opencode): add opencode plugin config and update gitignore 2026-09-16 19:47:56 +10:00
arch feat(docker): promote to essential and unify post-install across all platforms 2026-09-17 00:25:08 +10:00
common feat(setup): offer database-only refresh on arch 2026-09-25 00:08:47 +10:00
debian feat(setup): prompt before the system update on arch and ubuntu 2026-09-25 00:04:36 +10:00
dot_aws chore(config): add ft_PROD_RO AWS profile and update blink.cmp setup 2026-09-16 19:45:34 +10:00
dot_claude-personal feat(claude): add personal Claude Code config (agents, commands, settings) 2026-09-16 19:48:11 +10:00
dot_config fix(nvim): make the treesitter FileType autocmd fail-safe 2026-09-25 00:15:24 +10:00
macos feat(setup): add OS-specific bootstrap scripts 2026-09-16 19:27:30 +10:00
private_dot_local/bin feat(bin): add --docker snapshot mode to bkp 2026-09-25 00:53:09 +10:00
private_Library/private_LaunchAgents feat(launchagents): add new plist templates for task calendar and weather scripts 2024-07-27 16:52:34 +10:00
utils_scripts chore(utils): drop stale requirements.txt, sync uv.lock 2026-09-24 23:50:10 +10:00
.chezmoi.yaml.tmpl fix(chezmoi): pin useBuiltinAge off so SSH age identities work 2026-09-24 23:54:51 +10:00
.chezmoiignore fix(chezmoi): replace dangling machine_type with install_gui 2026-09-24 23:49:44 +10:00
.gitignore chore(opencode): add opencode plugin config and update gitignore 2026-09-16 19:47:56 +10:00
dot_gitconfig.tmpl fix(git): remove global SSH URL rewrite for github.com 2026-09-16 23:19:36 +10:00
dot_mbsyncrc fix(mbsync): update fonto IMAP host to 192.168.1.130 2026-09-24 23:50:10 +10:00
dot_ripgreprc feat(shell): update zsh, git, ripgrep and remove taskwarrior template 2026-09-16 19:27:30 +10:00
dot_zshrc feat(shell): update zsh, git, ripgrep and remove taskwarrior template 2026-09-16 19:27:30 +10:00
encrypted_dot_taskrc.age feat(apps): add tool configs for aerc, aerospace, aichat, fish, jj, mutt, newsboat, sketchybar and more 2026-09-16 19:27:30 +10:00
monorepo.json chore: add misc configs and utility scripts 2026-09-16 19:27:30 +10:00
myterm.info chore: add misc configs and utility scripts 2026-09-16 19:27:30 +10:00
README.md feat(setup): offer database-only refresh on arch 2026-09-25 00:08:47 +10:00
restic_exclude.txt chore: add misc configs and utility scripts 2026-09-16 19:27:30 +10:00
run_onchange_darwin-install-packages.sh.tmpl refactor(brew): use Brewfile trusted: true instead of manual brew trust 2026-09-21 10:01:58 +10:00
run_onchange_linux-arch-install-packages.sh.tmpl fix(packages): match distro family in the linux install hooks 2026-09-25 00:20:03 +10:00
run_onchange_linux-ubuntu-install-packages.sh.tmpl fix(packages): match distro family in the linux install hooks 2026-09-25 00:20:03 +10:00
setup-arch.sh feat(setup): offer database-only refresh on arch 2026-09-25 00:08:47 +10:00
setup-macos.sh refactor(setup): replace confirm-config TUI with chezmoi native prompts 2026-09-20 00:46:38 +10:00
setup-ubuntu.sh feat(setup): prompt before the system update on arch and ubuntu 2026-09-25 00:04:36 +10:00
setup.sh fix(setup): detect distro derivatives via ID_LIKE 2026-09-24 23:58:18 +10:00
stylua.toml chore: add misc configs and utility scripts 2026-09-16 19:27:30 +10:00

Dotfiles

Personal machine configuration managed with chezmoi.

Supports macOS, Arch Linux (including Arch Linux ARM and Omarchy), Ubuntu/Debian, and headless Linux servers. Derivatives are detected via ID_LIKE, so Manjaro, EndeavourOS, Linux Mint, Pop!_OS and Raspberry Pi OS route to their parent distro.


Fresh machine setup

1. Retrieve the age encryption keys

All secrets in this repo are encrypted with age using an SSH key pair stored in Bitwarden.

Before anything else, restore both keys from Bitwarden:

mkdir -p ~/.ssh

# Private key — Bitwarden item "id_chezmoi" (file attachment or secure note)
nvim ~/.ssh/id_chezmoi        # paste private key, save
chmod 600 ~/.ssh/id_chezmoi

# Public key — Bitwarden item "id_chezmoi.pub" (file attachment or secure note)
nvim ~/.ssh/id_chezmoi.pub    # paste public key, save

Why both? chezmoi uses the private key (id_chezmoi) to decrypt .age files during apply, and the public key (id_chezmoi.pub) as the recipient when encrypting new secrets with chezmoi add --encrypt.
Omitting id_chezmoi.pub won't break decryption but will prevent adding new secrets.

The setup scripts run common/age-key.sh before chezmoi apply — it checks for the private key and will prompt you if it's missing.

2. Run the platform bootstrap

# clone and enter the source directory
git clone https://github.com/huantrinh1802/dotfiles.git ~/.local/share/chezmoi
cd ~/.local/share/chezmoi

# auto-detects OS and runs the right script
./setup.sh

# Arch with Tailscale (optional)
./setup-arch.sh --tailscale-network 192.168.1

Each setup script runs chezmoi init after installing packages, which prompts for machine configuration using chezmoi's built-in interactive prompts:

Device type [personal/work] (default "personal"):
Neovim profile [full/dev/minimal] (default "full"):
Default shell [fish/zsh/bash] (default "fish"):
Install development tools [Y/n]: y
Install AWS tools [y/N]: n
Install nice-to-have packages [Y/n]: y
Install GUI packages [Y/n]: y
Set up Docker [Y/n]: y

Values are stored in ~/.config/chezmoi/chezmoi.yaml and are only prompted once — subsequent runs of chezmoi init reuse the saved values without re-prompting.

Script Platform Steps
setup.sh any detects OS, delegates below
setup-macos.sh macOS Homebrew → chezmoi init → defaults → shell → age key → apply → Docker
setup-arch.sh Arch update → shell → packages → chezmoi init → age key → apply → Docker
setup-ubuntu.sh Ubuntu/Debian apt → shell → packages → chezmoi init → age key → apply → Docker → Bitwarden

The first step of both Linux setup scripts prompts before touching the system.

setup-arch.sh offers three choices, defaulting to the first:

Choice Flag Runs
Full upgrade --system-update, --yes pacman -Syu
Refresh databases only --refresh-db pacman -Sy
Skip entirely --skip-system-update nothing

Refresh-only is not the safe middle option. pacman -Sy leaves the sync database ahead of the installed system, so every package installed afterwards is a partial upgrade — a state Arch does not support, and one that can break dynamically linked binaries. Use it when a stale database is stopping package lookups, then run a full pacman -Syu as soon as the bootstrap finishes.

setup-ubuntu.sh asks a simple yes/no, defaulting to yes, and accepts --skip-system-update / --system-update / --yes. It governs only apt-get update — the package-list refresh — because nothing in the Ubuntu path runs apt-get upgrade at all.

3. Enable Bitwarden SSH agent

The SSH agent built into the Bitwarden desktop app is used for all SSH operations on graphical machines.

  1. Open Bitwarden → Settings → SSH Agent → enable it.
  2. Add your SSH keys to the Bitwarden vault.
  3. Re-open a shell — SSH_AUTH_SOCK is set automatically when the agent socket is live.

On macOS the socket is ~/Library/Containers/com.bitwarden.desktop/Data/.bitwarden-ssh-agent.sock.
On Linux it is ~/.bitwarden-ssh-agent.sock.
SSH_AUTH_SOCK is only set when the socket file exists, so SSH won't fail if Bitwarden isn't running.

On servers, use a dedicated SSH key instead of the desktop Bitwarden agent:

# Generate an Ed25519 key, add to ssh-agent, print upload instructions
~/.local/share/chezmoi/common/ssh-keygen.sh --comment "myserver"

The script prints direct URLs to register the public key on GitHub, GitLab, and Bitbucket, and an ssh-copy-id one-liner for authorized_keys on remote hosts.

4. Apply dotfiles

chezmoi init https://github.com/huantrinh1802/dotfiles.git  # if not already cloned
chezmoi diff    # preview changes
chezmoi apply

Per-machine configuration

Machine-level flags are prompted for by chezmoi init (see .chezmoi.yaml.tmpl) and written to ~/.config/chezmoi/chezmoi.yaml. They can also be edited manually:

data:
    device_type: "personal"       # personal | work
    nvim_profile: "full"          # full | dev | minimal
    default_shell: "fish"         # fish | zsh | bash
    install_development: true     # languages, databases, build tools, IDEs
    install_aws: false            # awscli, sam, cloudformation-cli, aws/tap
    install_nice_to_have: true    # media tools, specialty CLIs, extras
    install_gui: true             # desktop apps and graphical dotfiles
    setup_docker: true            # install Docker and run post-install config

device_type

Selects the personal or work sub-bucket within every package category in packages.yaml. The common sub-bucket is always installed.

install_gui

Value Effect
true (default) Desktop packages and graphical dotfiles are installed
false CLI-only: gui package tier skipped, Bitwarden desktop not installed, graphical dotfiles (wezterm, autostart) excluded via .chezmoiignore

nvim_profile

Controls which Neovim plugin groups are loaded via lua/user/lazy.lua.tmpl:

Profile Plugin groups loaded
full plugins + technical_plugins + nth_plugins + firenvim_plugins
dev plugins + technical_plugins
minimal plugins only

install_nice_to_have

When true, the package install hooks also install the nice_to_have / extra tier — media tools, macOS extras, specialty CLIs, and optional tap-based utilities.

setup_docker

When true (default), Docker runtime packages are installed and post-install configuration runs:

  • macOS — installs colima, docker, docker-compose, lazydocker; starts colima as a background launchd service
  • Linux — installs docker + docker-compose; enables docker.service via systemd; adds the current user to the docker group

Set to false for headless servers or minimal installs that don't need Docker.


What's managed

Area Tools
Shell fish, aliases, environment, env-selector
Editor Neovim + lazy.nvim (profile-based)
Terminal tmux, Zellij, WezTerm
VCS git, jj, lazygit
File manager yazi
Prompt & shell tools starship, atuin, fzf, zoxide, thefuck
Containers Docker (colima on macOS), lazydocker
Email aerc, neomutt, isync/mbsync, msmtp, notmuch
Calendar / contacts khal, khard, vdirsyncer
Secrets Bitwarden (desktop + CLI), pass, age
macOS bar / WM sketchybar, aerospace
AWS CLI, credential templates
AI tools opencode, aichat

Package management

Packages are declared in .chezmoidata/packages.yaml under essential, docker, and extra tiers per platform:

packages:
  darwin:
    taps: [...]
    brews:
      essential: [...]   # always installed
      docker: [...]      # installed when setup_docker = true
      extra: [...]       # installed when install_nice_to_have = true
    casks:
      essential: [...]
      extra: [...]
  linux:
    arch:
      pacman: { essential: [...], docker: [...], extra: [...] }
      aur:    { essential: [...], docker: [...], extra: [...] }
    ubuntu:
      apt:    { essential: [...], docker: [...], extra: [...] }
      brew:   { essential: [...], docker: [...], extra: [...] }   # Linuxbrew

The install hooks are triggered by chezmoi whenever packages.yaml changes:

  • run_onchange_darwin-install-packages.sh.tmpl — macOS (brew bundle)
  • run_onchange_linux-arch-install-packages.sh.tmpl — Arch (pacman + yay)
  • run_onchange_linux-ubuntu-install-packages.sh.tmpl — Ubuntu (apt + Linuxbrew)

Arch uses pacman + AUR only (no Linuxbrew — AUR already has bleeding-edge versions).
Ubuntu uses apt for bootstrap deps and Linuxbrew for developer tooling.

To regenerate the macOS brew list from the current Brewfile:

python3 utils_scripts/brewfile_generator.py

Encryption

Files with an .age suffix are encrypted at rest and decrypted automatically by chezmoi during apply.

dot_config/aerc/encrypted_aerc.conf.age
dot_config/mutt/encrypted_muttrc.age
dot_config/vdirsyncer/encrypted_config.age

Chezmoi is configured to use age with:

  • Identity: ~/.ssh/id_chezmoi (private key — retrieve from Bitwarden)
  • Recipients: ~/.ssh/id_chezmoi.pub (public key — retrieve from Bitwarden)

Neither key is committed to the repo. Both must be in place before chezmoi apply.


Secrets

No plaintext credentials are committed. Templates pull secrets from:

  • Bitwarden CLI (bw get) — runtime lookups during chezmoi apply
  • pass — GPG-encrypted local store (seeded via common/pass.sh)
  • age — file-level encryption for sensitive config files

AWS credentials are resolved via .chezmoitemplates/aws_credentials.tmpl.

To seed the local pass store from Bitwarden after first apply:

~/.local/share/chezmoi/common/pass.sh

Repository layout

.
├── .chezmoidata/           # Template data (packages.yaml)
├── .chezmoitemplates/      # Reusable chezmoi template snippets
├── .chezmoiignore          # Platform/GUI conditional excludes
├── arch/                   # Arch Linux setup scripts
├── common/                 # Cross-platform scripts
│   ├── startup.sh          # Shell install, plugin manager, chsh
│   ├── age-key.sh          # Check/restore chezmoi age key before apply
│   ├── docker-setup.sh     # Post-install Docker config (systemd + group on Linux, colima on macOS)
│   ├── pass.sh             # Seed pass store from Bitwarden
│   └── ssh-keygen.sh       # Generate SSH key with upload instructions
├── debian/                 # Ubuntu/Debian setup scripts
├── macos/                  # macOS setup scripts and defaults
├── dot_claude-personal/    # Claude Code personal config (agents, commands)
├── dot_config/             # Files deployed to ~/.config/
├── dot_*/                  # Other files deployed to ~/
├── run_onchange_*.tmpl     # Chezmoi install hooks (run on data change)
├── setup.sh                # Master bootstrap (auto-detects OS)
├── setup-{macos,arch,ubuntu}.sh
└── utils_scripts/          # Repo maintenance utilities

Notes

  • Commits should be atomic and grouped by tool or concern.
  • Do not commit plaintext secrets, API tokens, or private keys.
  • macOS-only configs (sketchybar, aerospace, yabai, skhd) are excluded on Linux via .chezmoiignore.
  • Graphical dotfiles (wezterm, autostart) are excluded when install_gui = false.
  • Encrypted files are committed as .age ciphertext only.