feat: public dotfiles tier — no credential, no identity, one installer

Fresh history. This is the repo a throwaway VM clones anonymously: it brings a
machine to a working baseline and carries nothing that makes it mine.

56 files. 50 land in $HOME, 3 are chezmoi metadata, 2 are repo documentation,
1 is the manifest, and a 15-file test harness stays behind in .tests/.

What did not travel, and why:

  encrypted_private_bws-token.age   a real credential; age is dropped entirely
  .chezmoidata/bws.toml             env-var -> secret-id map; belongs with the
                                    tier that can use it
  SECRETS.md                        documentation of the rules, not config
  finish-setup.sh.tmpl              superseded by dotup
  nvim/init.lua.backup              dead file
  dot_claude/**, dot_codex/**,      120 files of agent config, private tier
  dot_pi/**

De-identified rather than dropped:

  .gitconfig   [user], the GitHub ssh rewrite and both Gitea host rewrites are
               identity, not configuration. They move behind an [include] of
               ~/.config/git/config.local, which the private tier writes. Git
               treats a missing include as a no-op, so a public-only machine
               reads the file and stops.
  .zshrc       the two gitea aliases carried a personal domain and a LAN IP.
               They move behind a guarded source of ~/.config/zsh/local.zsh,
               the sibling of the secrets.zsh seam phase 2 established.
  nvim         a commented-out LM Studio endpoint naming a LAN address.
  ghostty      a stale auto-generated header naming an absolute home directory.

Newly captured, never tracked before: ~/.zshenv, ~/.config/gh/config.yml. The
former sourced ~/.cargo/env unguarded, so every zsh on a machine without rustup
printed an error -- the same shape as the unguarded oh-my-zsh source phase 2
fixed. It is guarded now.

.chezmoiexternal.toml grows from one entry to six. oh-my-zsh, powerlevel10k,
zsh-autosuggestions, zsh-ai and tpm were hand-installed and declared nowhere,
which is why `chezmoi init --apply` on a clean box produced a .zshrc that broke
the shell it configures. The theme and both plugins nest under
.oh-my-zsh/custom/, which is what $ZSH_CUSTOM resolves to.

dotup gains an install engine. It resolves each selected package to a channel
(apt, brew, npm, uv, snap, deb, flatpak, tarball, script, builtin) through one
function every consumer reads, probes apt-cache before batching so a name apt
does not know moves to brew instead of failing all thirty, and retries
individually if a batch still fails -- which earned its keep on the first real
container run, where mermaid-cli's puppeteer dependency failed and the other
twelve npm packages installed anyway. --unattended computes safe defaults fresh
from the manifest rather than inheriting a state file, and refuses private and
invasive rows outright even when a stale state file ticks them.

The manifest gains @spec, a second directive kind alongside @needs, carrying the
argument a channel needs but a package name cannot supply -- the scoped npm
name, the flatpak app id, the .deb source. The TSV stays five columns wide.

Three bugs the container runs found, all fixed here:

  * `apt install nodejs` gives you node WITHOUT npm on Ubuntu, so all thirteen
    npm packages failed on a fresh box. The manifest asks apt for both names.
  * A tool installed a moment ago is not on this process's PATH -- uv lands in
    ~/.local/bin, npm -g honours the ~/.npmrc prefix, linuxbrew is outside a
    non-login PATH. Resolved by looking in the places we just wrote to, never by
    exporting a modified PATH.
  * `A || { B && C; }` is one || list, so when `command -v sudo` failed the list
    failed and `set -e` killed dotup at load. On a non-root machine with no
    sudo it died before printing anything. There is a regression test.

.zshenv and .p10k.zsh are marked private_. Both are shell code the login shell
executes and both applied at 664, group-writable. Third occurrence of the class
of bug phase 1 found on .pi/agent/auth.json and phase 2 found on .zshrc; the
first one found on purpose rather than by accident.

Verification: 81 assertions, 81/81 on this box and in ubuntu:24.04, ubuntu:22.04
and debian:12. The installer is driven against a directory of fake package
managers that record what they were asked to do and install nothing, so the
engine is exercised end to end without a package landing on the test machine.
`gitleaks detect` over the full history and the working tree: no leaks found,
with no allowlist and no .gitleaks.toml.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
bcherb2
2026-08-17 00:11:52 -04:00
commit b487b0e855
71 changed files with 7824 additions and 0 deletions
+119
View File
@@ -0,0 +1,119 @@
#group pkg flag apt brew note
#
# Five columns, tab separated, greppable by hand. Anything that would have been
# a sixth column is an @ directive instead, so a package with no dependency and
# no install argument -- most of them -- costs nothing to read.
#
# @needs <group/pkg> <dep>... dep is group/pkg or a whole group.
# Closure is transitive in both directions.
# @spec <group/pkg> <arg>... Argument for a channel that cannot be named
# by the package: the npm spec, the flatpak
# app id, the .deb source. Defaults to the
# package name when absent.
# @<group> <note> Group note. Only needed where the members
# fail the safe test for different reasons;
# otherwise the worst child's note is right.
#
# A leading `-` in the apt or brew column means "not from this package manager":
# -tarball -npm -script -snap -deb -flatpak -uv -builtin -xcode.
# A bare `-` means unavailable there. Linux falls back to the brew column before
# giving up -- omp, herdr and lazygit have no apt package at all. The fallback is
# one-directional: there is no apt on a Mac, so a `-` in the brew column is the
# end of the road rather than a reason to read a column of Debian package names.
#
@needs networking/xrdp desktop
@needs networking/xorgxrdp desktop
@needs gpu/container-toolkit docker
@needs agents/codex core/node
@needs agents/pi core/node
@needs agents/pi-plugins core/node
@needs agents/pi-plugins agents/pi
@needs agents/specify-cli core/uv
@needs core/mermaid-cli core/node
@needs core/neovim core/imagemagick core/mermaid-cli
#
# npm names carry scopes that the plugin's short name does not. `npm i -g
# rpiv-btw` installs somebody else's package.
@spec agents/codex @openai/codex
@spec agents/pi @earendil-works/pi-coding-agent
@spec agents/pi-plugins @juicesharp/rpiv-ask-user-question @juicesharp/rpiv-btw @juicesharp/rpiv-todo @samfp/pi-memory @tmustier/pi-ralph-wiggum pi-markdown-preview pi-powerline-footer pi-simplify pi-subagents pi-web-access
@spec agents/specify-cli specify-cli
@spec core/mermaid-cli @mermaid-js/mermaid-cli
@spec core/bitwarden-cli bw
@spec apps/obsidian md.obsidian.Obsidian
@spec apps/chrome https://dl.google.com/linux/direct/google-chrome-stable_current_amd64.deb
@spec apps/ghostty gh:mkasberg/ghostty-ubuntu:_amd64.deb
core neovim safe -tarball neovim apt ships 0.9.5 — tarball to /opt/nvim
core ripgrep safe ripgrep ripgrep binary is rg
core fd safe fd-find fd binary is fdfind on ubuntu
core bat safe bat bat binary is batcat on ubuntu
core fzf safe fzf fzf for your shell (ctrl-r); the picker uses its own pinned copy
core eza safe eza eza
core zsh safe zsh -builtin login shell everywhere
core tmux safe tmux tmux
core git-lfs safe git-lfs git-lfs
core lazygit safe - lazygit no apt package
core gh safe gh gh
core chezmoi safe -script chezmoi bootstrap cannot come from the manifest
core go safe -tarball go /usr/local/go on linux
core uv safe -script uv astral installer to ~/.local/bin
core node safe nodejs npm node apt's nodejs ships node WITHOUT npm — both names needed
core imagemagick safe imagemagick imagemagick required by the nvim markdown render path
core mermaid-cli safe -npm -npm mmdc — nvim renders mermaid fences with it
core btop safe btop btop
core htop safe htop htop
core ncdu safe ncdu ncdu
core tree safe tree tree
core cmake safe cmake cmake
core ninja safe ninja-build ninja package name differs from binary
core build-tools safe build-essential -xcode
core magic-wormhole safe magic-wormhole magic-wormhole snap wormhole on dev
core bitwarden-cli safe -snap bitwarden-cli snap bw on ubuntu
core mosh safe mosh mosh
core nmap safe nmap nmap
core binwalk safe binwalk binwalk
core pipx safe pipx pipx
core age safe age age general purpose only now
agents codex safe -npm -npm @openai/codex — unpinned, always latest
agents pi safe -npm -npm @earendil-works/pi-coding-agent
agents pi-plugins safe -npm -npm 10 plugins: rpiv-*, pi-memory, pi-subagents, ...
agents omp safe - can1357/tap/omp oh my pi — tap only, pulls linuxbrew on linux
agents herdr safe - herdr terminal workspace manager for agents
agents specify-cli safe -uv -uv uv tool install
fonts hack-nerd-font safe fonts-powerline font-hack-nerd-font
fonts iosevka-nerd-font safe fonts-powerline font-iosevka-nerd-font
media ffmpeg safe ffmpeg ffmpeg
media sox safe sox sox
media p7zip safe p7zip-full p7zip
apps obsidian gui -flatpak obsidian
apps ghostty gui -deb ghostty
apps chrome gui -deb google-chrome
apps firefox gui firefox firefox
apps vlc gui vlc vlc
@networking daemons, listening ports, and setuid mount helpers
networking openssh-server invasive openssh-server -builtin opens port 22 on every network this box can reach
networking tailscale invasive tailscale tailscale daemon; joins a private network and rewrites DNS
networking avahi-daemon invasive avahi-daemon - daemon; broadcasts this host on the LAN
networking xrdp invasive xrdp - opens port 3389 · useless without the desktop group
networking xorgxrdp invasive xorgxrdp - xrdp's X backend
networking nfs-common invasive nfs-common - setuid mount helper
networking cifs-utils invasive cifs-utils - setuid mount helper
networking davfs2 invasive davfs2 - setuid mount helper
@docker daemon; membership in the docker group is root-equivalent
docker docker-ce invasive docker-ce - daemon; docker group is root-equivalent
docker docker-buildx invasive docker-buildx-plugin -
docker docker-compose invasive docker-compose-plugin -
@desktop changes the display manager — can leave you at a black screen
desktop xfce4 invasive xfce4 - changes the display manager
desktop lightdm invasive lightdm - CAN LEAVE YOU AT A BLACK SCREEN
@gpu kernel modules; a bad driver can break boot
gpu nvidia-driver invasive nvidia-driver-570 - kernel modules; can break boot
gpu cuda-toolkit invasive nvidia-cuda-toolkit -
gpu container-toolkit invasive nvidia-container-toolkit - requires docker
@virt daemon, bridges, and group membership
virt qemu invasive qemu-kvm -
virt libvirt invasive libvirt-daemon-system - daemon + group membership
virt virt-manager invasive virt-manager -
@private one password, typed after the install finishes
private private-repo private - - ~/.local/share/dotfiles-private — agent config, ssh config
private bws-secrets private - - 7 API keys into ~/.config/zsh/secrets.zsh
Can't render this file because it contains an unexpected character in line 17 and column 49.