Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
14 changes: 12 additions & 2 deletions .chezmoiignore
Original file line number Diff line number Diff line change
Expand Up @@ -6,22 +6,32 @@
README.md
LICENSE-MIT.txt
Brewfile
packages-apt.txt
bootstrap.sh
tests
init
theme

# Package-install scripts run only on their own OS
{{ if ne .chezmoi.os "darwin" -}}
.chezmoiscripts/darwin/**
{{ end -}}
{{ if ne .chezmoi.os "linux" -}}
.chezmoiscripts/linux/**
{{ end -}}

{{ if ne .chezmoi.os "darwin" -}}
# macOS GUI configuration has no business on Linux/WSL
com.googlecode.iterm2.plist
.mackup.cfg
{{ end -}}

{{ if eq .machineClass "ephemeral" -}}
# Disposable boxes get shell config only: no identity, no secrets, and no
# tooling that assumes a long-lived machine.
# Disposable boxes get shell config only: no identity, no secrets, no
# package installs, and no tooling that assumes a long-lived machine.
.extra
.gitconfig.local
.claude
CLAUDE.md
.chezmoiscripts/**
{{ end -}}
11 changes: 11 additions & 0 deletions .chezmoiscripts/darwin/run_onchange_install-packages.sh.tmpl
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
#!/bin/bash
# Installs/updates Homebrew packages whenever the Brewfile changes.
# Brewfile hash: {{ include "Brewfile" | sha256sum }}
set -e

if ! command -v brew > /dev/null 2>&1; then
echo "Homebrew not installed — skipping brew bundle (install from https://brew.sh)";
exit 0;
fi

brew bundle --file={{ joinPath .chezmoi.sourceDir "Brewfile" | quote }}
26 changes: 26 additions & 0 deletions .chezmoiscripts/linux/run_onchange_install-packages.sh.tmpl
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
#!/bin/sh
# Installs the Linux CLI toolset whenever packages-apt.txt changes.
# Homebrew is deliberately macOS-only in this setup; Linux/WSL use the
# distro's native package manager with native package names.
# packages-apt.txt hash: {{ include "packages-apt.txt" | sha256sum }}
set -e

if ! command -v apt-get > /dev/null 2>&1; then
echo "No apt-get on this system — install packages-apt.txt equivalents manually.";
exit 0;
fi

if [ "$(id -u)" -ne 0 ] && ! sudo -n true 2> /dev/null; then
echo "No root and no passwordless sudo — skipping package install.";
echo "Run manually: xargs -a packages-apt.txt sudo apt-get install -y";
exit 0;
fi

SUDO="";
[ "$(id -u)" -ne 0 ] && SUDO="sudo";

$SUDO apt-get update -qq;
# Per-package so one missing name (distro drift) doesn't abort the rest.
grep -vE '^[[:space:]]*(#|$)' {{ joinPath .chezmoi.sourceDir "packages-apt.txt" | quote }} | while read -r pkg; do
$SUDO apt-get install -y -qq "$pkg" || echo "skipped (unavailable): $pkg";
done
15 changes: 14 additions & 1 deletion CLAUDE.md
Original file line number Diff line number Diff line change
Expand Up @@ -85,7 +85,8 @@ VMs; the test skips everywhere else unless `MACOS_APPLY_OK=1` is set.
- **`.chezmoiignore`** — target paths chezmoi must not manage (repo-level files everywhere; macOS GUI config off-mac; identity/secrets on `ephemeral`)
- **`bootstrap.sh`** — deprecated wrapper around `chezmoi init --source . --apply`
- **`.macos`** — macOS `defaults write` settings; reads `$COMPUTER_NAME` env var for machine-specific naming
- **`Brewfile`** — Homebrew formulae, casks, and Mac App Store apps
- **`Brewfile`** — Homebrew formulae, casks, and Mac App Store apps (macOS only; `.chezmoiscripts/darwin/` runs `brew bundle` when it changes)
- **`packages-apt.txt`** — Debian/Ubuntu/WSL package list (`.chezmoiscripts/linux/` installs it when it changes; skips gracefully without apt or sudo)
- **`bin/`** — personal scripts added to `$PATH`
- **Claude Code statusline** — lives in `icanalytica/ica-skills` (skill `claude-statusline`), not here. Install once per machine with `/claude-statusline`; the plugin's session-start hook keeps the installed copies in `~/.claude` current after that. On a fresh machine, `.claude/settings.json`'s statusLine command is harmlessly dead until that one-time install.
- **`init/`** — one-time setup scripts
Expand All @@ -112,6 +113,18 @@ set `user.signingkey` to the literal public key (not `op-ssh-sign`, which
only exists locally). There is **no GPG keypair** in this setup despite the
`gpgsign` name.

### Package lists

CLI tools are declared per-OS — `Brewfile` (macOS), `packages-apt.txt`
(Debian/Ubuntu/WSL). **When adding or removing a tool, update every list
where it's available**, using each distro's native package name (e.g.
Ubuntu's `bat` package installs the binary as `batcat`; the shell config's
`command -v` guards tolerate the difference). If a tool exists on one
platform only, note it in the list file. Homebrew is deliberately not used
on Linux. The install scripts re-run automatically on `chezmoi apply`
whenever their list's hash changes; the `ephemeral` machine class never
runs them.

### Machine-local customization

Add `~/.extra` (not committed) for per-machine overrides. Add `~/.path` for per-machine PATH entries. The `.macos` script skips the computer name block if `$COMPUTER_NAME` is unset.
Expand Down
24 changes: 24 additions & 0 deletions packages-apt.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
# Debian/Ubuntu/WSL package list — the Linux counterpart of the Brewfile.
# Keep the two in sync: when adding or removing a CLI tool, update every
# list where the tool is available, using each distro's NATIVE package
# name (e.g. the bat package here installs the binary as batcat; the
# shell config's command -v guards tolerate either).
# Consumed by .chezmoiscripts/linux/run_onchange_install-packages.sh.

zsh
git
curl
wget
vim
tmux
tree
jq
fzf
bat
ripgrep
fd-find
zoxide
direnv
git-delta
xclip
# lazygit and k9s are not in Ubuntu's repos — install manually if wanted
13 changes: 8 additions & 5 deletions tests/chezmoi.bats
Original file line number Diff line number Diff line change
@@ -1,7 +1,9 @@
#!/usr/bin/env bats

# Full init + apply into an isolated $HOME per machine class, so the real
# machine is never touched and no real config is read or written. XDG dirs
# machine is never touched and no real config is read or written. Applies
# use --exclude scripts: the .chezmoiscripts package installers must never
# run brew/apt inside a test. XDG dirs
# must be pinned too, not just HOME: GitHub's ubuntu runners export
# XDG_CONFIG_HOME, which chezmoi prefers over $HOME/.config — without the
# override, the first test's machineClass leaks into every later test via
Expand All @@ -27,7 +29,7 @@ teardown() {

@test "ephemeral class deploys shell config and nothing sensitive" {
cd "$BATS_TEST_DIRNAME/.."
chez init --source "$PWD" --promptString machineClass=ephemeral --apply
chez init --source "$PWD" --promptString machineClass=ephemeral --apply --exclude scripts
# Shell layer lands
[ -f "$TMPHOME/.zshrc" ]
[ -f "$TMPHOME/.aliases" ]
Expand All @@ -44,13 +46,14 @@ teardown() {
# Repo-level files never deploy
[ ! -e "$TMPHOME/README.md" ]
[ ! -e "$TMPHOME/Brewfile" ]
[ ! -e "$TMPHOME/packages-apt.txt" ]
[ ! -e "$TMPHOME/tests" ]
[ ! -e "$TMPHOME/.macos" ]
}

@test "linux class deploys identity without signing" {
cd "$BATS_TEST_DIRNAME/.."
chez init --source "$PWD" --promptString machineClass=linux --apply
chez init --source "$PWD" --promptString machineClass=linux --apply --exclude scripts
[ -f "$TMPHOME/.gitconfig.local" ]
grep -q "email = evan.alter@gmail.com" "$TMPHOME/.gitconfig.local"
grep -q "user = hadees" "$TMPHOME/.gitconfig.local"
Expand All @@ -66,7 +69,7 @@ teardown() {

@test "wsl class wires git through Windows ssh.exe" {
cd "$BATS_TEST_DIRNAME/.."
chez init --source "$PWD" --promptString machineClass=wsl --apply
chez init --source "$PWD" --promptString machineClass=wsl --apply --exclude scripts
grep -q "sshCommand = ssh.exe" "$TMPHOME/.gitconfig.local"
! grep -q "gpgsign = true" "$TMPHOME/.gitconfig.local"
}
Expand All @@ -75,6 +78,6 @@ teardown() {
cd "$BATS_TEST_DIRNAME/.."
mkdir -p "$TMPHOME/.claude"
echo "my custom notes" > "$TMPHOME/.claude/CLAUDE.local.md"
chez init --source "$PWD" --promptString machineClass=linux --apply
chez init --source "$PWD" --promptString machineClass=linux --apply --exclude scripts
grep -q "my custom notes" "$TMPHOME/.claude/CLAUDE.local.md"
}
13 changes: 13 additions & 0 deletions tests/packages.bats
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
#!/usr/bin/env bats

# Sanity floor for the per-OS package lists: non-empty, one package per
# line, no duplicates. The Brewfile is covered by brew bundle itself.

@test "packages-apt.txt is non-empty with no duplicates" {
cd "$BATS_TEST_DIRNAME/.."
pkgs=$(grep -vE '^[[:space:]]*(#|$)' packages-apt.txt)
[ -n "$pkgs" ]
[ -z "$(echo "$pkgs" | sort | uniq -d)" ]
# One package name per line, no stray whitespace
! echo "$pkgs" | grep -qE '[[:space:]]'
}
Loading