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:
@@ -0,0 +1,192 @@
|
||||
# ~/.zshrc
|
||||
#
|
||||
# A PLAIN FILE. Not a chezmoi template, deliberately.
|
||||
#
|
||||
# Templating this file is what broke sync-back: `chezmoi re-add` silently does
|
||||
# nothing on a .tmpl, so every edit made here had to be hand-copied into the
|
||||
# source or lost. Platform differences are handled at runtime below, by zsh,
|
||||
# which it is perfectly capable of doing.
|
||||
#
|
||||
# NO SECRETS LIVE HERE. Keys come from ~/.config/zsh/secrets.zsh, which is
|
||||
# generated from Bitwarden Secrets Manager, mode 600, and in no repository.
|
||||
# If that file is absent this shell still works — you just have no API keys.
|
||||
|
||||
# ---------------------------------------------------------------- platform ---
|
||||
# Set before anything else; everything below branches on these instead of on
|
||||
# chezmoi template conditionals.
|
||||
typeset -g _mac=0 _lin=0 _wsl=0 _ubuntu=0
|
||||
[[ $OSTYPE == darwin* ]] && _mac=1
|
||||
[[ $OSTYPE == linux* ]] && _lin=1
|
||||
[[ -n $WSL_DISTRO_NAME || -r /proc/sys/fs/binfmt_misc/WSLInterop ]] && _wsl=1
|
||||
(( _lin )) && [[ -r /etc/os-release ]] && \
|
||||
grep -qi '^ID=ubuntu' /etc/os-release && _ubuntu=1
|
||||
|
||||
# ----------------------------------------------------------------- secrets ---
|
||||
# One guarded source, and it is the only path by which a key enters this shell.
|
||||
# Regenerate with `dotsecrets`. Never edit by hand, never commit.
|
||||
[[ -r ${XDG_CONFIG_HOME:-$HOME/.config}/zsh/secrets.zsh ]] && \
|
||||
source ${XDG_CONFIG_HOME:-$HOME/.config}/zsh/secrets.zsh
|
||||
|
||||
# The bws token itself is a file, not a secret in this file. It is what lets
|
||||
# `bws` and `dotsecrets` run at all.
|
||||
[[ -r $HOME/.config/bitwarden/bws-token ]] && \
|
||||
export BWS_ACCESS_TOKEN="$(<$HOME/.config/bitwarden/bws-token)"
|
||||
|
||||
# --------------------------------------------------------- pre-prompt setup ---
|
||||
# Anything that must be set before the p10k instant prompt goes here. Nothing
|
||||
# in this section may write to stdout.
|
||||
export ZSH_AI_PROVIDER="gemini"
|
||||
export ZSH_AI_GEMINI_MODEL="gemini-2.5-flash"
|
||||
|
||||
# Endpoint, not a credential — the matching key comes from secrets.zsh.
|
||||
export ZAI_BASE_URL="https://api.z.ai/api/anthropic"
|
||||
|
||||
(( _ubuntu )) && {
|
||||
export GDK_BACKEND=wayland
|
||||
export QT_QPA_PLATFORM=wayland
|
||||
export CLUTTER_BACKEND=wayland
|
||||
export SDL_VIDEODRIVER=wayland
|
||||
}
|
||||
|
||||
# Enable Powerlevel10k instant prompt. Should stay close to the top of ~/.zshrc.
|
||||
# Initialization code that may require console input (password prompts, [y/n]
|
||||
# confirmations, etc.) must go above this block; everything else may go below.
|
||||
if [[ -r "${XDG_CACHE_HOME:-$HOME/.cache}/p10k-instant-prompt-${(%):-%n}.zsh" ]]; then
|
||||
source "${XDG_CACHE_HOME:-$HOME/.cache}/p10k-instant-prompt-${(%):-%n}.zsh"
|
||||
fi
|
||||
|
||||
# -------------------------------------------------------------------- PATH ---
|
||||
# `typeset -U` makes $path a unique-only array, so ordering is intentional and
|
||||
# duplicates are impossible — which is what the eight scattered appends and the
|
||||
# doubled ~/.local/bin used to produce.
|
||||
typeset -U path PATH
|
||||
_prepend() { [[ -d $1 ]] && path=("$1" $path) }
|
||||
_append() { [[ -d $1 ]] && path=($path "$1") }
|
||||
|
||||
_prepend "$HOME/bin"
|
||||
_prepend "$HOME/.local/bin"
|
||||
_prepend "$HOME/.npm-global/bin"
|
||||
_prepend "$HOME/code/speccy/bin"
|
||||
_append /usr/local/bin
|
||||
_append /usr/local/go/bin
|
||||
|
||||
export GOPATH="$HOME/go"
|
||||
export GOMODCACHE="$GOPATH/pkg/mod"
|
||||
_prepend "$GOPATH/bin"
|
||||
|
||||
_append "$HOME/.cache/lm-studio/bin"
|
||||
|
||||
(( _mac )) && {
|
||||
_prepend /opt/homebrew/bin
|
||||
_prepend /opt/homebrew/sbin
|
||||
_prepend /opt/homebrew/opt/hcxtools/bin
|
||||
[[ -n $BUN_INSTALL ]] && _prepend "$BUN_INSTALL/bin"
|
||||
export TERMINFO=/Applications/Ghostty.app/Contents/Resources/terminfo
|
||||
|
||||
# Garmin Connect IQ SDK (tracks the SDK selected in current-sdk.cfg)
|
||||
_ciq="$HOME/Library/Application Support/Garmin/ConnectIQ/current-sdk.cfg"
|
||||
[[ -f $_ciq ]] && _append "$(<"$_ciq")bin"
|
||||
unset _ciq
|
||||
}
|
||||
|
||||
(( _lin )) && {
|
||||
_append /usr/libexec/virtiofsd
|
||||
_append "$HOME/code/vp9enc-rs/target/release"
|
||||
}
|
||||
|
||||
(( _ubuntu )) && export LD_LIBRARY_PATH="/opt/Qt-6.7.3/lib:$LD_LIBRARY_PATH"
|
||||
|
||||
unfunction _prepend _append
|
||||
|
||||
# ------------------------------------------------------------------ limits ---
|
||||
ulimit -n 65535
|
||||
(( _mac )) && ulimit -u 2048 || ulimit -u 65535
|
||||
|
||||
# ------------------------------------------------------------------ oh-my-zsh -
|
||||
export ZSH="$HOME/.oh-my-zsh"
|
||||
export ZSH_CUSTOM="${ZSH_CUSTOM:-$ZSH/custom}"
|
||||
ZSH_THEME="powerlevel10k/powerlevel10k"
|
||||
|
||||
plugins=(git zsh-autosuggestions sudo web-search dirhistory history)
|
||||
(( _mac )) && plugins+=(docker)
|
||||
|
||||
# zsh-ai is loaded HERE and only here. It used to be added to this array on
|
||||
# every platform AND separately sourced from /opt/homebrew on macOS, so it
|
||||
# loaded twice on mac. The existence guard also stops the array referencing a
|
||||
# plugin that was never installed.
|
||||
[[ -z $ZSH_AI_DISABLE && -d $ZSH_CUSTOM/plugins/zsh-ai ]] && plugins+=(zsh-ai)
|
||||
|
||||
# Guarded. Unguarded, a clean bootstrap produces a broken login shell and
|
||||
# nothing tells you why.
|
||||
if [[ -r $ZSH/oh-my-zsh.sh ]]; then
|
||||
source $ZSH/oh-my-zsh.sh
|
||||
else
|
||||
print -u2 "zshrc: oh-my-zsh missing at $ZSH — run dotup to install it"
|
||||
fi
|
||||
|
||||
zle -N create_completion
|
||||
bindkey '^B' create_completion
|
||||
|
||||
[[ -f ~/.p10k.zsh ]] && source ~/.p10k.zsh
|
||||
|
||||
# ----------------------------------------------------------------- aliases ---
|
||||
(( _ubuntu )) && alias fd=fdfind
|
||||
(( _mac )) && {
|
||||
alias py="/opt/homebrew/opt/python@3.13/Frameworks/Python.framework/Versions/3.13/bin/python3"
|
||||
alias tailscale="/Applications/Tailscale.app/Contents/MacOS/Tailscale"
|
||||
}
|
||||
|
||||
alias umpv="$HOME/code/mpv/TOOLS/umpv"
|
||||
(( _lin )) && {
|
||||
alias lada-runner="LADA_RUNNER_CONCURRENCY=2 uv --directory $HOME/tools/lada-runner run lada-runner tui"
|
||||
alias lada-cli="flatpak run --filesystem=host --command=lada-cli io.github.ladaapp.lada"
|
||||
}
|
||||
|
||||
# Work Claude account — separate creds/history/projects, shared CLAUDE.md+rules+skills+agents
|
||||
alias wclaude='CLAUDE_CONFIG_DIR=$HOME/.claude-work claude'
|
||||
|
||||
# A function, not an alias. As an alias the token was expanded into the alias
|
||||
# body at definition time, so `alias` printed the key in full to anyone who ran
|
||||
# it. A function defers expansion to call time.
|
||||
glm() {
|
||||
ANTHROPIC_BASE_URL="$ZAI_BASE_URL" ANTHROPIC_AUTH_TOKEN="$ZAI_API_KEY" \
|
||||
claude --dangerously-skip-permissions \
|
||||
--settings "$HOME/.claude/settings-glm.json" "$@"
|
||||
}
|
||||
|
||||
_open() { (( _mac )) && open "$1" || xdg-open "$1" >/dev/null 2>&1 }
|
||||
|
||||
# ------------------------------------------------------------------- local ---
|
||||
# Identity-bearing shell config -- the gitea aliases and their WAN/LAN hosts,
|
||||
# work shortcuts, anything naming a host you own -- lives in the PRIVATE tier,
|
||||
# which writes this file. It is absent on a public-only machine, and the guard
|
||||
# makes that a silent no-op. `_mac` and `_open` above are still in scope here,
|
||||
# so a moved alias needs no rewriting.
|
||||
[[ -r ${XDG_CONFIG_HOME:-$HOME/.config}/zsh/local.zsh ]] && \
|
||||
source ${XDG_CONFIG_HOME:-$HOME/.config}/zsh/local.zsh
|
||||
|
||||
# ---------------------------------------------------------------- homebrew ---
|
||||
if (( _mac )); then
|
||||
eval "$(/opt/homebrew/bin/brew shellenv 2>/dev/null || /usr/local/bin/brew shellenv)"
|
||||
elif [[ -x /home/linuxbrew/.linuxbrew/bin/brew ]]; then
|
||||
eval "$(/home/linuxbrew/.linuxbrew/bin/brew shellenv)"
|
||||
fi
|
||||
|
||||
# ---------------------------------------------------------------- bitwarden ---
|
||||
# Interactive personal-vault unlock. Nothing automated depends on this; the
|
||||
# unattended path is bws + the token file above.
|
||||
if command -v bw >/dev/null 2>&1; then
|
||||
bwunlock() {
|
||||
export BW_SESSION="$(bw unlock --raw)"
|
||||
print "Bitwarden unlocked. Session expires based on your timeout setting."
|
||||
}
|
||||
fi
|
||||
|
||||
export ZSH_CODEX_PYTHON="$HOME/.local/venvs/codex/bin/python"
|
||||
|
||||
# iris was removed here. Do NOT add `eval "$(iris init zsh)"` — it emits
|
||||
# `exec iris`, which replaces every interactive shell, and combined with the
|
||||
# p10k instant prompt above (upstream #55) logins fork-cascade until SSH
|
||||
# appears frozen. See the sysjournal note before reintroducing it.
|
||||
|
||||
unset _mac _lin _wsl _ubuntu
|
||||
Reference in New Issue
Block a user