2a93bfd5b197eaa9ef31abfff6c226b301897813
6 Commits
| Author | SHA1 | Message | Date | |
|---|---|---|---|---|
|
|
2a93bfd5b1 |
test: assert dotup's absolute path before touching PATH
The check was `export PATH="$HOME/.local/bin:$PATH"` followed by `command -v dotup`, which proves the export worked and nothing else. It passed on every run while a real operator on a fresh box hit `dotup: command not found`. ~/.local/bin is put on PATH only by the .zshrc this tier ships, for a zsh that dotup itself installs. So on any new machine the first invocation must be ~/.local/bin/dotup -- which the README already documents -- and bare `dotup` cannot work until zsh is installed and logged into. Assert `-x ~/.local/bin/dotup` first, then export for the rest of the script's convenience. Reproduced the original failure in a clean root container before changing anything, and re-ran the end-to-end after: PASS. |
||
|
|
cebb38b97a |
test: move the end-to-end here, and fix the race that made it flaky
The e2e harness and its mock endpoint were living in the private repo, justified
by "they name the endpoint host". That was true when written and false two
commits later, once the hardcoded URL came out of the wrapper so the route would
live only in Bitwarden. Rechecked: all three name the endpoint zero times -- the
URL, username and password are supplied at runtime -- and the only host they
mention is this repo. They belong next to the dotup they exercise and the unit
suite that covers the rest of it.
The move surfaced a worse problem than the misplacement. The harness was flaky,
and an earlier PASS was partly luck.
chezmoi writes "git user.email?" with the tty still in cooked mode and only then
switches to raw mode -- with TCSAFLUSH, which discards whatever is already
buffered. Answering on the prompt TEXT races that switch. One run answered all
seven prompts; the next lost the Enter after user.email, leaving the field
unsubmitted. Every later expect then waited out its own timeout and the whole
thing surfaced at the 600s ceiling as an unattributed "a prompt went
unanswered".
Two fixes:
- each answer now waits for \033[?2004h, bracketed-paste-on, which the TUI
emits only AFTER raw mode is established. "Probably ready" becomes
"demonstrably ready", and each prompt emits its own, so it is per-answer.
- a bare `expect -re {pat} {...}` treats timeout as "carry on", which is what
turned one lost keystroke into a ten-minute mystery. Prompts now fail
immediately naming which one was missed, and distinguish EOF (dotup exited
early) from timeout.
Also moved red(), the redactor, above the run. It was defined below the new
early-abort path that calls it, so the one branch that most needs redaction
would have hit an undefined function.
Verified by running it twice end to end against a mock endpoint, both passing
identically, including the three assertions the argv fix exists for.
README: the suite is 101 assertions, not 81, and the end-to-end is documented.
|
||
|
|
63bed1d32d |
fix: keep the gitea token out of chezmoi init's argv and .git/config
The endpoint hands back a clone URL with the read-only token inline, and that
URL went straight onto `chezmoi init`'s command line. Two durable exposures
followed, neither of which the existing redaction touched -- it only kept the
token out of the trace line:
- /proc/<pid>/cmdline is world-readable, so any account on the box could read
the live token for as long as the clone ran
- the resulting .git/config recorded the credential-bearing remote and kept
it until the tree was deleted
Split the credential out of the URL before anything executes. The token goes to
$STATE/private-credentials at 600 in git-credential-store format; the URL that
reaches argv and .git/config is clean. The helper is passed via GIT_CONFIG_*
for the clone and then written into the clone's own config, so a later
`chezmoi update` still authenticates without the token being stored.
Verified: clone succeeds with the clean URL, .git/config contains no token, and
a subsequent fetch authenticates from the credential file alone. Suite 101/101.
|
||
|
|
400bd9b9f1 |
fix: install bws from the private tier, not the manifest
The previous commit put the bws install on the private/bws-secrets row as a tarball channel. That row can never install anything: selected_packages drops every row flagged `private`, which is the invariant that makes --unattended safe to run. The container proved it — unzip installed, bws did not, and the row still could not deliver on its promise. Nor can it be a safe row: the defaults preset ticks every safe package, so that would put a Bitwarden binary and a 12 MB GitHub download on every throwaway public VM, for a tool those machines have no credential to use. So ensure_bws lives in dotup and is called from cmd_private immediately after the token is written. Nothing installs bws unless something is about to hand it a token, and the manifest keeps its invariant. core/unzip stays a real package with the @needs edge — the release is a .zip and 24.04 minimal has no unzip. |
||
|
|
90bca39396 |
fix: install bws, isolate the private config, keep credentials out of logs
Three defects the private tier could not survive a fresh machine with. bws was never installed. The private/bws-secrets row promised seven API keys and shipped no way to fetch them: dotsecrets shells out to `bws secret get`, and nothing put that binary on the box. Added as a tarball channel with checksum verification, musl rather than gnu so it does not pin a glibc newer than an older LTS carries, plus core/unzip as a real @needs dependency since the release is a .zip and 24.04 minimal has no unzip. The releases API needs filtering by tag prefix: sdk-sm is a monorepo and `latest` usually points at a python SDK, not bws. Both tiers rendered their config to ~/.config/chezmoi/chezmoi.toml, so re-running the public installer overwrote the private config and took its seven promptStringOnce answers with it. Silently -- the templates degrade politely when data is missing, so the symptom was `git commit` not knowing who you are, days later. The private tier now renders to private.toml and the cmp alias carries the matching -c. `run` echoes its argv to stderr, and the private init argv ends in https://user:TOKEN@host -- into scrollback, any `dotup 2>log`, and any agent transcript. Traced through redact_url instead, in both the live and dry-run branches. The clone is also chmod -R go-rwx afterwards: it lands at the caller umask, and .git/config stores that same credential URL. Tests: 90 passing, 9 new covering all three. |
||
|
|
b487b0e855 |
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>
|