Files
dotfiles/README.md
domverse d6fe9277cb docs: add standalone chezmoi bootstrap and gotchas
Document how to bring chezmoi up on another VPS without the provisioning
layer, and record the chezmoi behaviours that caused real breakage here.

The umask trap is the load-bearing one: chezmoi derives modes from the
umask at apply time rather than preserving them, so the atuin key dir and
config need both the private_ attribute and umask = 0o022 to land at 0700
and 0600. The apply-into-head SIGPIPE is the other: it truncates the apply
and reports head's exit status, so a partial apply looks successful.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-17 15:05:23 +02:00

7.0 KiB
Raw Blame History

dotfiles

Shell + tooling setup for a fresh Ubuntu box: fish · starship · atuin · eza · macchina, plus the Claude Code statusline.

Two layers, deliberately split:

Layer Owns Tool
Provisioning apt packages, rust toolchain, starship, default shell setup.sh (root)
Dotfile state everything under $HOME chezmoi (user)

setup.sh does the root work and then hands off to chezmoi. Nothing that needs sudo lives inside chezmoi, and nothing under $HOME is touched by the script.

Fresh box

git clone https://git.domverse-berlin.eu/domverse/dotfiles.git
cd dotfiles
sudo bash setup.sh

Run it as your normal user via sudo — the script reads SUDO_USER to know whose home to set up, and refuses to run as root directly.

Expect 1020 minutes: atuin, eza, macchina and oxker are built from source with cargo install --locked, which needs the rust toolchain. That's the deliberate trade for matching the currently-running versions exactly rather than taking whatever apt ships.

chezmoi on another VPS

setup.sh already does all of this. Use this path only when you want the dotfiles without the provisioning layer (no apt, no rust, no chsh) — e.g. a box where the tools are already there, or where you don't have root.

# 1. install chezmoi (user-local, no root)
sh -c "$(curl -fsLS get.chezmoi.io)" -- -b "$HOME/.local/bin"
export PATH="$HOME/.local/bin:$PATH"

# 2. pin the umask BEFORE the first apply — see Notes, this is load-bearing
mkdir -p ~/.config/chezmoi
printf 'umask = 0o022\n' >> ~/.config/chezmoi/chezmoi.toml

# 3. pull the source state (private repo — needs Gitea credentials)
chezmoi init https://git.domverse-berlin.eu/domverse/dotfiles.git

# 4. look before you leap
chezmoi diff

# 5. write it out
chezmoi apply

Notes on the above:

  • Step 2 must come before step 4. The umask is read at apply time; applying first and fixing the config after leaves wrong modes on disk.
  • Step 3 needs repo access. The repo is private. Either have credential.helper=store already populated, or clone over SSH: chezmoi init git@git.domverse-berlin.eu:domverse/dotfiles.git.
  • The source clone lands in ~/.local/share/chezmoi (chezmoi's default). This box is the exception — it sets sourceDir = /home/crabix/projects/dotfiles in ~/.config/chezmoi/chezmoi.toml because the repo predates chezmoi. On a fresh box, don't set sourceDir; the default is what setup.sh expects.
  • .chezmoiroot contains home, so the source root is home/, not the repo root. setup.sh and README.md sit outside chezmoi's view by design.
  • If ~ already has real dotfiles, chezmoi diff in step 4 shows exactly what gets clobbered. Back them up with modes intact first: cp -a ~/.config/fish ~/.config/atuin ~/backup/dotfiles-preapply-$(date -u +%Y%m%dT%H%M%SZ)/
  • Then do the Manual steps below (atuin key, git identity) — they are not specific to setup.sh.

Day-to-day

Configs are managed by chezmoi, so the file in ~/.config is a copy, not a symlink. Edit through chezmoi or your change gets overwritten on the next apply:

chezmoi edit ~/.config/fish/config.fish   # edit source
chezmoi diff                              # what drifted on this box?
chezmoi apply                             # write source -> $HOME
chezmoi cd                                # jump to the source repo, then git push

The fishconfig alias is wired to chezmoi edit for this reason.

What's tracked

home/
  dot_bashrc                                  ~/.bashrc  (stock + atuin init)
  dot_gitconfig                               ~/.gitconfig
  dot_config/fish/config.fish.tmpl            aliases, eza, init lines
  dot_config/fish/conf.d/rustup.fish          sources ~/.cargo/env.fish
  dot_config/atuin/config.toml
  dot_config/starship.toml                    stock-equivalent, pinned
  dot_claude/executable_statusline-command.sh statusline (needs jq)

What's deliberately NOT tracked

See home/.chezmoiignore. The load-bearing exclusions:

  • ~/.local/share/atuin/key — the sync encryption key. Tracked by hand in secrets.yml. Restore it before the first atuin sync, or history encrypted on other hosts will not decrypt.
  • ~/.git-credentials — plaintext tokens (.gitconfig sets helper = store).
  • ~/.claude/settings.json — pins the model and sets skipDangerousModePermissionPrompt; a per-box decision, set by hand.
  • conf.d/fnm.fish, conf.d/dtop.env.fish — node/opencode toolchains, out of scope. Their installers write these themselves.
  • fish_variables — machine-local state, fish rewrites it.

Manual steps after setup.sh

The script prints these; repeated here because they're easy to miss:

  1. atuin — restore key from secrets.yml into ~/.local/share/atuin/key, then atuin login -u <user> and atuin sync.
  2. git identity — not set globally, only credential.helper=store:
    git config --global user.name  '<name>'
    git config --global user.email '<email>'
    
  3. Log out and back in for the fish shell change to take effect.

chezmoi gotchas

Learned the hard way while building this. Don't rediscover them.

  • chezmoi does not preserve file modes. It tracks only the executable_ and private_ source attributes and derives the actual mode from the umask at apply time. With umask 002 (the default for this user), ~/.config/atuin/ silently goes 07000775 and config.toml 06000664. Correct modes need both halves:

    • the source attribute — private_atuin/private_config.toml, and
    • umask = 0o022 in ~/.config/chezmoi/chezmoi.toml (setup.sh writes this).

    Always chezmoi diff before trusting a dotfiles repo on a new box; the mode lines are quiet and easy to scroll past.

  • Never pipe chezmoi apply into a truncating command. chezmoi apply --verbose | head -20 SIGPIPEs chezmoi partway through, leaving the apply incomplete — files sorting after the cutoff are never written. Worse, $? reports head's exit status (0), so it looks like it succeeded. Redirect to a file and read that instead:

    chezmoi apply --verbose > /tmp/apply.log 2>&1; echo "exit=$?"
    
  • The file in ~ is a copy, not a symlink. Editing ~/.config/fish/config.fish directly gets silently clobbered on the next apply. Use chezmoi edit. The fishconfig alias points at chezmoi edit for exactly this reason.

  • chezmoi init on a box with existing dotfiles overwrites without asking once you apply. chezmoi diff first, always.

Notes

  • config.fish aliases cat to batcat — on Ubuntu the bat binary really is batcat, so the apt bat package is a hard dependency of the fish config.
  • rustup is installed with --no-modify-path: conf.d/rustup.fish already sources ~/.cargo/env.fish, and the installer would otherwise add a second PATH edit.
  • starship.toml is currently equivalent to stock defaults. It exists so the prompt is pinned to a known config rather than tracking version defaults.