Files
bcherb2 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>
2026-08-17 00:24:41 -04:00

83 lines
3.7 KiB
Lua

-- Options are automatically loaded before lazy.nvim startup
-- Default options that are always set: https://github.com/LazyVim/LazyVim/blob/main/lua/lazyvim/config/options.lua
-- Add any additional options here
-- Clipboard provider. LazyVim disables `clipboard=unnamedplus` under SSH, so
-- select the right provider and explicitly re-enable clipboard-linked yanks:
-- * tmux -> tmux's own clipboard buffer (readable)
-- * SSH/remote -> OSC 52 copy-only (see note below)
-- * local macOS -> native pbcopy/pbpaste.
-- Do NOT use OSC 52 locally: yanky's `sync_with_ring` reads the system
-- clipboard, and OSC 52 reads depend on the terminal answering a query, which
-- Ghostty refuses by default -> OSC 52 error + yanky stack trace. Neovim 0.12
-- also auto-prefers OSC 52 over pbcopy, so the macOS provider must be explicit.
if vim.env.TMUX then
vim.g.clipboard = "tmux"
elseif vim.env.SSH_CONNECTION then
-- Remote session (bare SSH, or a multiplexer like herdr that exports no env
-- var of its own). Copy via OSC 52 so yanks reach the OUTER terminal's
-- clipboard, but NEVER read the clipboard back: OSC 52 reads require the
-- terminal to answer a query, which Warp/herdr don't, so a read returns stale
-- data (or hangs) and blows up yanky's ring sync. Trade-off: pasting FROM the
-- local host INTO nvim over SSH won't work here -- use the terminal's own
-- paste in insert mode.
--
-- The paste function below must NOT call vim.fn.getreg('"')/'*'/'+' itself:
-- Neovim's clipboard provider (autoload/provider/clipboard.vim) guards
-- against re-entrant provider calls (`s:here`, see nvim#7184), so a getreg()
-- of a clipboard-linked register issued from *inside* a registered paste()
-- callback is swallowed and silently returns empty -- not the register's
-- real content. Cache what copy() was last given instead, and have paste()
-- return that cached value directly.
local osc52 = require("vim.ui.clipboard.osc52")
local last = { lines = {}, regtype = "" }
local function make_copy(reg)
local osc52_copy = osc52.copy(reg)
return function(lines, regtype)
last.lines, last.regtype = lines, regtype
osc52_copy(lines, regtype)
end
end
local function paste_from_cache()
return { last.lines, last.regtype }
end
vim.g.clipboard = {
name = "osc52-copyonly",
copy = { ["+"] = make_copy("+"), ["*"] = make_copy("*") },
paste = { ["+"] = paste_from_cache, ["*"] = paste_from_cache },
cache_enabled = 0,
}
elseif vim.fn.has("mac") == 1 then
vim.g.clipboard = {
name = "pbcopy",
copy = { ["+"] = "pbcopy", ["*"] = "pbcopy" },
paste = { ["+"] = "pbpaste", ["*"] = "pbpaste" },
cache_enabled = 0,
}
end
-- Make ordinary y/Y use the selected provider. LazyVim applies its SSH default
-- after this file is read, so restore the option once startup has settled. The
-- schedule keeps this callback after LazyVim's own VeryLazy clipboard callback.
vim.api.nvim_create_autocmd("User", {
pattern = "VeryLazy",
once = true,
callback = function()
vim.schedule(function()
vim.opt.clipboard = "unnamedplus"
end)
end,
})
-- VS Code-like visual improvements
vim.opt.cursorline = true -- Highlight current line
-- vim.opt.colorcolumn = "80,120" -- Disabled: renders poorly with some themes
vim.opt.scrolloff = 8 -- Keep 8 lines visible above/below cursor
vim.opt.sidescrolloff = 8 -- Keep 8 columns visible left/right
vim.opt.smoothscroll = true -- Smooth scrolling
-- Make Shift+movement and mouse selections behave like a conventional editor.
-- Keep `cmd` out of selectmode so v/V continue to enter Vim's Visual mode.
vim.opt.keymodel = { "startsel", "stopsel" }
vim.opt.selectmode = { "key", "mouse" }