The box this was written on had a half-finished install: vscode.repo
present under /etc/yum.repos.d, `code` never installed, and nothing in
this repo recording that VS Code was meant to be there. Microsoft's
rpm/deb would also need root, write outside $HOME, and be wiped by a
Kasm redeploy. Upstream's portable tarball is the same build with none
of that, so the module keeps bin/'s contract: no sudo, nothing outside
$HOME, pinned rather than floating.
The Chromium sandbox survives a userland install — the bundled
chrome-sandbox is not setuid, so Electron falls back to the
unprivileged user-namespace sandbox. Verified by launching without
--no-sandbox, so dropping root costs nothing.
Adds install_tarball_tree to lib/fetch.sh: install_tarball_bin picks a
single binary out of an archive, and VS Code only runs from an intact
Electron tree. It stages beside the target and swaps in after
extraction succeeds, so an interrupted run leaves the old version
intact.
Two things the verification caught rather than the design:
- argv.json lives at ~/.vscode/argv.json (product.json's
dataFolderName), not under the user-data-dir. A copy written
anywhere else is read by nothing, so the first attempt at
suppressing the keyring prompt silently did nothing.
- Run as root, `code --version` prints a "super user" warning
instead of the version and still exits 0, so a bare run-check
never matches and would re-download 325MB on every bootstrap.
Root falls back to the .versions stamp.
Sets password-store=basic in argv.json so Electron's safeStorage stops
waking kwalletd, which prompts for an unlock on every launch because
kwallet-pam is not installed. Confirmed with dbus-monitor: no
org.kde.KWallet traffic on launch. The trade-off — tokens obfuscated
on disk rather than sealed behind a wallet password — and the route
back to the wallet are documented in the module and the README.
No-ops on headless hosts; DOTFILES_VSCODE overrides either way.
Verified per verify-module: shellcheck -s sh clean, idempotent re-run
on both paths, and fresh install plus re-run as root on oraclelinux:9
and debian:13.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
166 lines
9.2 KiB
Markdown
166 lines
9.2 KiB
Markdown
# dotfiles
|
|
|
|
Configures fresh VMs (RHEL-family or Debian-family) with a consistent
|
|
environment. Run `./bootstrap.sh` (or curl-pipe it) to apply everything;
|
|
each module also runs standalone and is safe to re-run.
|
|
|
|
## Quick start
|
|
|
|
On a fresh box, apply everything with one command:
|
|
|
|
```sh
|
|
curl -fsSL https://git.nic-oconnor.com/public/dotfiles/raw/branch/main/bootstrap.sh | sh
|
|
```
|
|
|
|
When piped like this (no local checkout), `bootstrap.sh` clones the repo to
|
|
`~/.dotfiles` and runs from there, so `git` must be installed first. Re-running
|
|
the command updates the checkout (`git pull`) and re-applies every module.
|
|
Override the destination or source with `DOTFILES_DIR` / `DOTFILES_REPO`.
|
|
|
|
Already have a clone? Just run it directly:
|
|
|
|
```sh
|
|
./bootstrap.sh
|
|
```
|
|
|
|
## Modules
|
|
|
|
- `zsh/` — installs zsh + oh-my-zsh, drops a managed `~/.zshrc`
|
|
(robbyrussell theme, git plugin), and sets zsh as the login shell. The
|
|
managed rc sources `~/.zshrc.local` last for per-machine customizations
|
|
(see below).
|
|
- `git/` — sets git `user.name`/`user.email` and configures
|
|
`credential.helper store` (plaintext `~/.git-credentials`, 0600) for
|
|
HTTPS auth.
|
|
- `bin/` — installs userland devops CLIs as self-contained, statically
|
|
linked release binaries into `~/.local/bin`: jq, kubectl, kubelogin (the
|
|
`kubectl oidc-login` plugin, for OIDC auth to Omni clusters), helm, k9s,
|
|
kubectx/kubens, stern, yq, OpenTofu, terragrunt, task (go-task, the
|
|
Taskfile runner), the 1Password CLI `op` for secrets storage, the
|
|
opencode coding agent, zarf (DevSecOps for Offline and Airgapped
|
|
Kubernetes), packer (HashiCorp's infrastructure build tool), and the
|
|
coder CLI for Coder workspaces.
|
|
kind is installed only when full (non-rootless) Docker is available —
|
|
it doesn't work against rootless Docker, so it is skipped on Kasm.
|
|
Dotfiles does not install Docker itself; install it on the host (or bake
|
|
it into the image) where you want it.
|
|
No sudo, nothing outside `$HOME`, so tools persist across Kasm
|
|
redeploys with no reinstall. Idempotent: each tool records its version
|
|
under `~/.local/bin/.versions` and is skipped when the pinned version
|
|
is already present. `zsh/zshrc` puts `~/.local/bin` on `PATH`.
|
|
- `secrets/` — reads your 1Password vault once and writes every field to
|
|
`~/.config/dotfiles/secrets.env` (0600), which the managed `~/.zshrc`
|
|
sources, so opening a shell never calls `op`. One vault per user and one
|
|
item in it, both named after your login by default; each field's label is
|
|
the variable name (`GITEA_KEY`, `LITELLM_KEY`, `PROXMOX_TOKEN_SECRET`, …).
|
|
The first run prompts for the service-account token and checks it with
|
|
1Password straight away, re-prompting on a bad paste. It then picks the vault
|
|
and item: the only one the token can see, or a prompt that lists them. It keeps
|
|
all three in `~/.config/dotfiles/op.env` (0600, not loaded into shells). If
|
|
that fails, bootstrap skips mcp-config/ and hermes/ with an error rather
|
|
than asking for the LiteLLM key by hand.
|
|
Runs after `bin/` (needs `op` and `jq`); bootstrap loads the result so later
|
|
modules get the keys without prompting. Day to day: `opr NAME` reads a
|
|
field, `opc NAME [VALUE]` creates or updates one (prompts, hidden, without
|
|
VALUE), `opd NAME` deletes one; both of those refresh the file and this
|
|
shell. `opl [VAULT [ITEM]]` shows every field with its current value, and
|
|
`ops` refreshes after a change made elsewhere. No sudo.
|
|
|
|
opencode is configured by `mcp-config/`: it points at the covert-gpt
|
|
LiteLLM gateway (`https://llm.covert-gpt.com/v1`) for models. MCP tools do
|
|
NOT come from the gateway — it hosts no MCP servers, deliberately; they run
|
|
client-side under your own credentials (see `mcp-config/README.md`). Run
|
|
`mcp-config/install.sh` once to store your LiteLLM key and enable
|
|
config sync — without it, opencode installs but can't reach the
|
|
gateway.
|
|
- `ansible/` — installs Ansible into a virtualenv at `~/.local/opt/ansible`
|
|
(it's Python, not a single binary) and symlinks its CLIs into
|
|
`~/.local/bin`. Persists across redeploys; only rebuilt when the venv is
|
|
missing or broken. Building it may install `python3`/`python3-venv` via
|
|
the system package manager (the one place this module uses sudo).
|
|
- `vscode/` — installs Visual Studio Code from upstream's portable tarball into
|
|
`~/.local/opt/vscode`, symlinks `code` into `~/.local/bin`, and writes a
|
|
user-level `.desktop` entry so it shows up in the application menu. **No
|
|
sudo, nothing outside `$HOME`** — the same contract as `bin/`, so it survives
|
|
a Kasm redeploy. Pinned via `VSCODE_VERSION` in the module; bump it to upgrade.
|
|
Also sets `password-store: basic` in `~/.vscode/argv.json` so KDE's kwallet
|
|
does not prompt for an unlock on every launch (see below). **No-ops on
|
|
headless hosts** — it's a ~400MB GUI app — override with `DOTFILES_VSCODE`.
|
|
- `docker-rootless-cgroup/` — on systemd-less hosts (e.g. Kasm), hand-delegates
|
|
cgroup v2 controllers to the rootless Docker daemon so per-container cgroups
|
|
work (rootless Docker otherwise comes up with
|
|
`CgroupDriver=none`). Uses sudo for host cgroup writes and restarts the
|
|
daemon; no-ops on systemd or non-rootless hosts. Re-run at each bootstrap
|
|
(the cgroup layout resets on redeploy); its PATH shim lives in `$HOME`.
|
|
- `mcp-config/` — shared MCP gateway config sync for laptops (LiteLLM /
|
|
opencode). Run by `bootstrap.sh`, and standalone too. The config and skills
|
|
are read from the **local checkout**, not re-downloaded from Gitea, so this
|
|
repo does not have to be public for a shell to work; see its own README.
|
|
|
|
## Per-machine customizations and secrets
|
|
|
|
`~/.zshrc` is managed — bootstrap overwrites it, so never hand-edit it.
|
|
Per-machine settings and secrets go in `~/.zshrc.local`, which the managed
|
|
rc sources last (so it can override anything) and bootstrap never touches:
|
|
|
|
```sh
|
|
cp zsh/zshrc.local.example ~/.zshrc.local && chmod 600 ~/.zshrc.local
|
|
```
|
|
|
|
Variables modules look for. With `secrets/` set up, put these in your vault
|
|
item instead of `~/.zshrc.local`:
|
|
|
|
- `GITEA_KEY` — personal access token for `git.nic-oconnor.com`. Used for
|
|
API/CLI work, and by `bootstrap.sh`/`mcp-config/` to read this repo when it
|
|
is **private** and there is no checkout yet. With a checkout on disk nothing
|
|
here needs it; HTTPS git auth otherwise goes through `git/`'s credential
|
|
helper.
|
|
- `LITELLM_KEY` — covert-gpt gateway key used by opencode. Normally you
|
|
don't set this by hand: run `mcp-config/install.sh` once and its shell
|
|
hook exports it from `~/.config/mcp-sync/key`. Set it in `~/.zshrc.local`
|
|
only on a box without mcp-sync.
|
|
- `DOTFILES_VSCODE` — controls the `vscode/` module. Unset (default) installs
|
|
VS Code only when a graphical session is detected; `1` forces the install on
|
|
a headless host (e.g. for `code tunnel`); `0` skips it everywhere. A plain
|
|
shell/env var, not a vault secret.
|
|
|
|
On the keyring: VS Code's secret storage reaches for the OS keyring through
|
|
libsecret, which on KDE makes kwalletd prompt for an unlock before the editor
|
|
is usable — every session, since `kwallet-pam` is not installed. The module
|
|
sets `"password-store": "basic"` in `~/.vscode/argv.json` to stop that, which
|
|
means VS Code's stored tokens are obfuscated on disk rather than sealed
|
|
behind a wallet password. To use the wallet instead, install `kwallet-pam` so
|
|
it unlocks at login, then change that value to `"kwallet5"`.
|
|
- `OP_SERVICE_ACCOUNT_TOKEN` — 1Password service-account token. Not a shell
|
|
variable any more: `secrets/install.sh` asks for it once and stores it in
|
|
`~/.config/dotfiles/op.env`. Pass it in the env only for an unattended run.
|
|
- `GITEA_KEY`, `PROXMOX_TOKEN_ID`, `PROXMOX_TOKEN_SECRET` — **your own**
|
|
credentials for the client-side MCP servers in `mcp-config/`. opencode reads
|
|
them from your environment (`{env:VAR}`) and passes them to the MCP server it
|
|
spawns, so the tools act as *you*. Keep them in `~/.zshrc.local`; they are
|
|
never written into any config file.
|
|
|
|
These are NOT used by `ai-k8s-iac` / `task up`: the LiteLLM gateway hosts no
|
|
MCP servers and stores no MCP credentials. See `mcp-config/README.md`.
|
|
- `hermes/` — installs Hermes Agent (Nous Research's self-improving AI agent)
|
|
via npm. Hermes uses npx to spawn and downloads the Python hermes-agent from
|
|
PyPI. Everything under $HOME, so it survives Kasm redeploys.
|
|
|
|
It then merges `hermes/gateway.yaml` into `~/.hermes/config.yaml`, wiring
|
|
Hermes to the covert-gpt LiteLLM gateway as a named custom provider. The merge
|
|
is idempotent and touches only that provider entry, so the rest of your
|
|
personal config is left alone; `model.default` / `model.provider` are seeded
|
|
only on a fresh box. Unlike `mcp-config/`, this deploys at BOOTSTRAP time, not
|
|
per-shell — re-run `hermes/install.sh` to pick up a gateway.yaml change.
|
|
|
|
Depends on `mcp-config/install.sh` having run: gateway.yaml names the key
|
|
(`key_env: LITELLM_KEY`) but never contains it, and `mcp-config/sync.sh` is
|
|
what exports LITELLM_KEY into your shell. Without it Hermes 401s.
|
|
|
|
Secrets never go in this repo or in `zsh/zshrc` — that file is shared
|
|
config applied to every box.
|
|
|
|
`lib/os-detect.sh` provides shared package-manager detection (`dnf`/`yum`
|
|
vs `apt`); `lib/fetch.sh` provides arch detection and binary-download
|
|
helpers. Both are used by module install scripts.
|