Hermes had no MCP servers at all -- no mcp_servers: key -- so "use the gitea
MCP" had nothing to call. The model said so ("there's no Gitea-specific skill"),
which was true, and then looped reaching for the browser as the only way in.
The obvious fix -- paste a gitea block into ~/.hermes/config.yaml -- is the
wrong one: it makes a SECOND definition of a server this repo already defines
once, free to drift (one pinned to gitea-mcp@0.0.10, the other to whatever got
typed). README already calls this out: "all MCP servers are defined here", and
listed other clients as "not done yet". So render, don't duplicate: sync.sh
already translates LiteLLM's /model/info into opencode's schema; this is the
same move into Hermes' schema, from opencode.gateway.json's mcp block. Add a
server once, both clients get it within the same TTL.
Not a shared server PROCESS, which is what "use the same one" would imply: MCP
stdio servers are subprocesses spawned per client over stdin/stdout and dying
with it. Each client running its own `npx -y <pkg>` is the design -- same
package, same pinned version, same per-user credential, same identity. Sharing
one process would need a persistent daemon plus a stdio->HTTP bridge, for
nothing. And it stays client-side: the gateway hosts no MCP servers on purpose
(values.yaml:892; user-scoped APIs, no shared tokens), and gitea-mcp hard-exits
without a token so LiteLLM's headerless listing never registers it anyway.
Translation notes: Hermes' `command` must be a STRING (a list stringifies to
"['npx', '-y']" and fails at exec), so opencode's argv list splits into
command + args. `{env:VAR}` becomes `${VAR}` -- both by-name, so no secret is
written to either file. Hermes gives an MCP child only a filtered baseline env,
so every secret must be listed in env:, which the rendered block does. Nothing
is added to platform_toolsets: MCP servers auto-enable by name, and listing some
would turn it into an allowlist.
Rendered names are recorded in ~/.config/mcp-sync/hermes-managed.json so a
server dropped upstream is dropped from Hermes, while servers the user added by
hand -- never in the manifest -- are left alone. Only writes when the result
changes, skips silently when Hermes isn't installed, warns when PyYAML is
missing (an absent MCP block is invisible rather than obviously broken).
Verified via Hermes' own discovery: 72 tools register -- 30 gitea (incl.
issue__list) and 42 proxmox -- with ${GITEA_KEY} / ${PROXMOX_MCP_*} resolving
and no secret in config.yaml.
Also fixes a stale README claim that MCP tools come from the gateway's /mcp
endpoint; they never do, and values.yaml:892 says why.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
126 lines
6.3 KiB
Markdown
126 lines
6.3 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.
|
|
- `docker/` — installs full (rootful) Docker Engine from Docker's upstream
|
|
package repo (get.docker.com on Debian-family; the docker-ce repo wired up
|
|
directly on RHEL-family, since the convenience script rejects EL rebuilds
|
|
like Oracle Linux by name), enables the service (systemd hosts), and adds
|
|
the invoking user to the
|
|
`docker` group (takes effect at next login). No-ops where a rootless
|
|
Docker daemon is already running (e.g. Kasm) so it never fights that
|
|
setup. Uses sudo.
|
|
- `bin/` — installs userland devops CLIs as self-contained, statically
|
|
linked release binaries into `~/.local/bin`: jq, kubectl, 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), and packer (HashiCorp's infrastructure build tool).
|
|
kind is installed only when full (non-rootless) Docker is available —
|
|
it doesn't work against rootless Docker, so it is skipped on Kasm.
|
|
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`.
|
|
|
|
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).
|
|
- `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). Independent of `bootstrap.sh`; 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:
|
|
|
|
- `GITEA_KEY` — personal access token for `git.nic-oconnor.com` (API/CLI
|
|
use; HTTPS git auth itself 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.
|
|
- `OP_SERVICE_ACCOUNT_TOKEN` — 1Password service-account token for
|
|
non-interactive `op read` / `op run`.
|
|
- `GITEA_KEY`, `PROXMOX_MCP_TOKEN_ID`, `PROXMOX_MCP_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.
|