Initial public release
A chezmoi-based fleet-dotfiles template for macOS workstations: - Two-way auto-sync via launchd watcher + 5-min puller - Mesh SSH via modify_authorized_keys driven by .chezmoidata/fleet.yaml - age-encrypted secrets file - Bundled Claude Code agentic team (11 agents) + /lite + /lite-sub commands - Verify-before-claiming Stop hook - Generic statusline + project-boundary validate-path hook - Reference launchd plist for cross-fleet task-durations aggregation (companion repo: gitea.tojo.team/cardinale/task-durations) - AGENTS.md walks an agent through the entire setup Q&A interactively - docs/ covers architecture, security model, fleet onboarding
This commit is contained in:
@@ -0,0 +1,213 @@
|
||||
# AGENTS.md — agent-led setup instructions
|
||||
|
||||
> **For Claude Code, Codex, Aider, or any other coding agent dropped into this repo.**
|
||||
> Follow this file top-to-bottom. It walks the user through replacing every placeholder with real values. Treat each numbered section as one Q&A round: ask the listed questions, wait for answers, fill in the files, then move on.
|
||||
|
||||
## Operating rules for this setup session
|
||||
|
||||
1. **One question at a time.** Don't fire all questions in a giant wall of text. Ask one, wait for the answer, ask the next.
|
||||
2. **Confirm before destructive actions.** Generating a new SSH keypair, generating a new age keypair, force-pushing — always confirm. Show what you're about to do; let the user say yes/no.
|
||||
3. **Never paste secrets back to the user in plaintext.** When the user gives you an API token, treat it like radioactive material: write it to the right file, then forget it. Don't echo it in a status message, don't put it in a commit, don't show it in a `cat` of the file.
|
||||
4. **Fail loud on missing prerequisites.** If `chezmoi`, `age`, or `duckdb` isn't installed, stop and tell the user the brew command. Don't try to proceed.
|
||||
5. **Verify before claiming.** When a step is "done", run a check that proves it: a `chezmoi diff`, a file existence test, a token round-trip. Tell the user what you verified.
|
||||
6. **Don't commit secrets.** Verify with `git status` before any commit that no `.env`, no unencrypted secret, no SSH private key is staged. If chezmoi's autoCommit fires, double-check the diff.
|
||||
|
||||
## Section 0 — Prerequisites check
|
||||
|
||||
Run silently, only surface findings to the user:
|
||||
|
||||
```bash
|
||||
command -v chezmoi >/dev/null && chezmoi --version
|
||||
command -v age >/dev/null && age --version
|
||||
command -v age-keygen >/dev/null
|
||||
command -v duckdb >/dev/null && duckdb --version
|
||||
command -v git >/dev/null && git --version
|
||||
```
|
||||
|
||||
If any are missing, tell the user the install command (`brew install chezmoi age duckdb`) and stop until they confirm.
|
||||
|
||||
## Section 1 — Identify the user
|
||||
|
||||
Ask:
|
||||
|
||||
> **Q1:** What name and email should I use for git commits on this fleet?
|
||||
> (e.g., `Alice Smith <alice@example.com>`)
|
||||
|
||||
Take their answer and write to `dot_gitconfig` (replacing the `examples/gitconfig.example` placeholders). Don't render the file yet — just stage the chezmoi source.
|
||||
|
||||
## Section 2 — Generate or reuse the age keypair
|
||||
|
||||
Ask:
|
||||
|
||||
> **Q2:** Do you already have an age keypair you want to use for this fleet? (yes / no)
|
||||
>
|
||||
> If **yes**: where is the private key file? (default: `~/.config/chezmoi/key.txt`)
|
||||
> If **no**: I'll generate a new one. Confirm before I create the file.
|
||||
|
||||
If generating new:
|
||||
|
||||
```bash
|
||||
mkdir -p ~/.config/chezmoi
|
||||
age-keygen -o ~/.config/chezmoi/key.txt
|
||||
chmod 600 ~/.config/chezmoi/key.txt
|
||||
```
|
||||
|
||||
Then extract the public key (it's printed to stderr by `age-keygen`, and on the first commented line of the file). Show ONLY the public key to the user.
|
||||
|
||||
Edit `.chezmoi.toml.tmpl`: replace `REPLACE_ME_WITH_YOUR_AGE_PUBLIC_KEY` with the public key.
|
||||
|
||||
> **Verify:** `chezmoi execute-template '{{ (index .age "recipient") }}'` should print the new public key.
|
||||
|
||||
## Section 3 — Identify this machine and the fleet
|
||||
|
||||
Ask:
|
||||
|
||||
> **Q3:** What's the hostname of this machine? (run `hostname -s` if you need a default)
|
||||
>
|
||||
> **Q4:** What's the username on this machine? (run `whoami` for the default)
|
||||
>
|
||||
> **Q5:** How many other machines will be in this fleet, and what are their hostnames + usernames?
|
||||
|
||||
Generate this machine's identity SSH key if missing:
|
||||
|
||||
```bash
|
||||
[ -f ~/.ssh/id_ed25519 ] || ssh-keygen -t ed25519 -f ~/.ssh/id_ed25519 -N ""
|
||||
```
|
||||
|
||||
Read the public key from `~/.ssh/id_ed25519.pub` and capture it.
|
||||
|
||||
Build `.chezmoidata/fleet.yaml` from `.chezmoidata/fleet.yaml.example`, replacing the placeholder entries with the machines the user named. For now, leave the OTHER machines' pubkeys as placeholders — they'll get filled in when those machines run their own onboarding (Section 9 covers this).
|
||||
|
||||
## Section 4 — SSH config for the fleet
|
||||
|
||||
Use `examples/ssh-config.tmpl.example` as a starting point. Render it for this fleet by:
|
||||
|
||||
- Replacing `laptop1` / `laptop2` / `desktop` placeholders with the real hostnames from Q5.
|
||||
- Replacing `<USERNAME_FOR_*>` with the real usernames from Q5.
|
||||
|
||||
Save to `private_dot_ssh/config.tmpl` in the chezmoi source.
|
||||
|
||||
> **Verify:** `chezmoi execute-template < private_dot_ssh/config.tmpl` renders cleanly with no `{{` literals leaking through.
|
||||
|
||||
## Section 5 — Decide on a forge
|
||||
|
||||
Ask:
|
||||
|
||||
> **Q6:** Where will this fleet's git remote live? (gitea, github, forgejo, gitlab, self-hosted bare repo)
|
||||
> If on a service that requires auth, how do you authenticate? (SSH key in agent, HTTPS PAT in keychain, gh CLI, etc.)
|
||||
|
||||
Verify the user can `git push` to that forge from this machine. If they're on a private gitea (like `gitea.tojo.team`), confirm they have a PAT in their environment or a credential helper configured.
|
||||
|
||||
## Section 6 — Secrets
|
||||
|
||||
Ask:
|
||||
|
||||
> **Q7:** Which API tokens do you want to manage via the fleet's encrypted secrets file?
|
||||
> The default list (in `examples/secrets.env.example`) covers Cloudflare, Porkbun, Tailscale, HuggingFace, OpenAI/Anthropic/Gemini, and the forge token. You can add or remove freely.
|
||||
|
||||
For each token the user wants:
|
||||
|
||||
- Ask for it ONE AT A TIME.
|
||||
- Append it to a temp file at `~/.config/fleet-dotfiles/secrets.env` (create the dir + chmod 600 the file FIRST).
|
||||
- Don't echo the value.
|
||||
|
||||
When all tokens are entered:
|
||||
|
||||
```bash
|
||||
chmod 600 ~/.config/fleet-dotfiles/secrets.env
|
||||
chezmoi add --encrypt ~/.config/fleet-dotfiles/secrets.env
|
||||
```
|
||||
|
||||
> **Verify:** `ls $(chezmoi source-path)/dot_config/private_fleet-dotfiles/` should now show `encrypted_private_secrets.env.age` (the encrypted source file). The original `~/.config/fleet-dotfiles/secrets.env` stays unencrypted on disk for runtime use; never commit it directly.
|
||||
|
||||
> **Sanity check:** `git status` in the chezmoi source dir must NOT show any unencrypted secret file staged.
|
||||
|
||||
## Section 7 — CLAUDE.md
|
||||
|
||||
Ask:
|
||||
|
||||
> **Q8:** I've copied a generic CLAUDE.md skeleton from `examples/CLAUDE.md.example` to `dot_claude/CLAUDE.md`. Do you want me to add machine-specific or project-specific sections on top? (e.g., your servers, your common commands, your workflow rules)
|
||||
|
||||
If yes, ask what sections, write them.
|
||||
|
||||
If no, leave the skeleton — they can add later.
|
||||
|
||||
> **Verify:** `chezmoi diff dot_claude/CLAUDE.md` shows the file ready to apply.
|
||||
|
||||
## Section 8 — First apply
|
||||
|
||||
Show the user `chezmoi diff --no-pager` summary (file count + paths, NOT contents — the diff might leak secret-shaped strings). Confirm:
|
||||
|
||||
> **Q9:** Ready to materialize all of this to the live filesystem? (yes / no)
|
||||
|
||||
On yes:
|
||||
|
||||
```bash
|
||||
chezmoi apply
|
||||
```
|
||||
|
||||
Then verify the launchd jobs registered:
|
||||
|
||||
```bash
|
||||
launchctl list | grep -E "chezmoi|taskdurations"
|
||||
```
|
||||
|
||||
You should see three labels:
|
||||
|
||||
- `com.chezmoi.claude-watcher`
|
||||
- `com.chezmoi.claude-puller`
|
||||
- `com.taskdurations.pull-fleet`
|
||||
|
||||
If any are missing, tell the user to run `chezmoi apply --force ~/Library/LaunchAgents/` and reload manually:
|
||||
|
||||
```bash
|
||||
for plist in ~/Library/LaunchAgents/com.{chezmoi,taskdurations}.*.plist; do
|
||||
launchctl unload "$plist" 2>/dev/null
|
||||
launchctl load "$plist"
|
||||
done
|
||||
```
|
||||
|
||||
## Section 9 — Push to the forge
|
||||
|
||||
Initial commit + push:
|
||||
|
||||
```bash
|
||||
cd $(chezmoi source-path)
|
||||
git remote -v # confirm it points at the user's forge
|
||||
git status # confirm no unencrypted secrets are staged
|
||||
git add -A
|
||||
git commit -m "Initial fleet dotfiles for $(hostname -s)"
|
||||
git push -u origin main
|
||||
```
|
||||
|
||||
> **Final verify:** Open the forge URL in a browser. Confirm `examples/`, `dot_local/bin/...`, `private_Library/LaunchAgents/...` are all present. Confirm `dot_config/private_fleet-dotfiles/encrypted_private_secrets.env.age` is the encrypted form (`.age` extension), not a plaintext `.env`.
|
||||
|
||||
## Section 10 — Onboarding additional machines
|
||||
|
||||
Tell the user:
|
||||
|
||||
> Each additional fleet machine repeats Sections 0, 2 (reuse the SAME age private key — copy it via secure channel; do NOT push it to git), 3 (generate that machine's identity SSH key), and then runs `chezmoi init https://<forge>/<user>/<repo>.git` and `chezmoi apply`.
|
||||
>
|
||||
> The age public key in `.chezmoi.toml.tmpl` doesn't need changing — it's the same recipient for the whole fleet.
|
||||
>
|
||||
> After the new machine applies, edit `.chezmoidata/fleet.yaml` to add its real pubkey. The watcher commits the change; within 7 min, every other fleet machine has it in `authorized_keys`.
|
||||
|
||||
Walk them through the first additional machine if they're available.
|
||||
|
||||
## Done — what to leave the user with
|
||||
|
||||
- A running fleet sync on this machine.
|
||||
- A `~/.local/bin/chezmoi-auto-sync.sh` that the watcher will call on every change to a managed path.
|
||||
- A `~/.config/fleet-dotfiles/secrets.env` (cleartext on disk, encrypted in source) sourced by `.zshrc` on shell start.
|
||||
- The full Claude Code agentic team available at `~/.claude/agents/agentic-team/`, plus `/lite` and `/lite-sub` slash commands.
|
||||
- A pointer to the task-durations repo (separate) if they want fleet-wide time estimates.
|
||||
- A note that any subsequent machine they want to add: see [`docs/add-machine.md`](docs/add-machine.md).
|
||||
|
||||
## What to NEVER do during this setup
|
||||
|
||||
- Never echo a secret back to the user in plaintext (not in chat, not in a log, not in a commit message).
|
||||
- Never run `git push --force` without explicit user confirmation.
|
||||
- Never delete the user's existing `~/.ssh/id_ed25519` if they already have one — work with what's there.
|
||||
- Never commit `~/.config/chezmoi/key.txt` (it's the age private key — losing it locks them out of every encrypted file in the repo).
|
||||
- Never overwrite `~/.zshrc` without showing a diff first; users often have hand-tuned aliases there.
|
||||
- Never assume the user wants every example file — confirm each section before applying.
|
||||
Reference in New Issue
Block a user