From c0ff6d8bed5decbb545f5a1b12cfc96e9aebe92a Mon Sep 17 00:00:00 2001 From: Gabriel Brown Date: Wed, 24 Jun 2026 09:58:42 -0400 Subject: [PATCH] docs: personalized dev environment (dotfiles + persistent home) --- docs/dotfiles.md | 78 ++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 78 insertions(+) create mode 100644 docs/dotfiles.md diff --git a/docs/dotfiles.md b/docs/dotfiles.md new file mode 100644 index 0000000..26716e5 --- /dev/null +++ b/docs/dotfiles.md @@ -0,0 +1,78 @@ +# Personalized dev environment (dotfiles + persistent home) + +Makes the workspace terminal feel like the user's own machine: a Fedora image +preloaded with QoL CLI tooling, a persistent per-user home, and user dotfiles. + +## The model + +- **Persistent per-user home.** Each user gets a home directory on the worker + host at `${SPOON_AGENT_WORKDIR}/homes/{username}`, bind-mounted into every + job/terminal container at `/home/{username}` (`HOME`). It survives across + sessions, so dotfiles, installed tools, nvim plugins, and shell history persist. + `username` is derived from the user's profile first name (sanitized). +- **Threads as folders.** Each thread's checkout lives at + `~/Code/{spoon}/{branch}` inside that home, so every thread shows up as a + folder in one home. The agent (`codex --cd …`) and the terminal both open there. +- **Neutral defaults (everyone).** The Fedora job image + (`docker/agent-job.Dockerfile`) ships zoxide, eza, bat, fzf, fd, ripgrep, gh, + gum, neovim, tmux, oh-my-posh, etc., plus system-wide defaults that work even + with an empty home: `/etc/profile.d/spoon.sh` (tool init + aliases), + `/etc/tmux.conf` (login-shell panes), `/etc/spoon/omp.json` (prompt theme). +- **User dotfiles (per-user).** Configured in **Settings → Dotfiles**, applied on + top of the neutral defaults. + +## Settings → Dotfiles + +A mini file-browser workspace rooted at `home/{firstName}`: + +- **Editable overlay tree** — drag in files/folders (or use Upload folder/files), + edit them in the Monaco editor, add/delete. Files are placed **relative to + `$HOME`** (`.bashrc` → `~/.bashrc`, `.config/nvim/…` → `~/.config/nvim/…`). + Stored encrypted at rest (`userDotfiles`, AES-256-GCM via `secretCrypto`). +- **Dotfiles repo (optional)** — a **public** git repo URL + optional ref + a + setup script path. On start the container clones it to `~/.dotfiles` and runs + `bash ~/.dotfiles/` (e.g. a bootstrap that symlinks configs, like the + user's Panama `install`). +- **Precedence (hybrid):** repo clone + setup runs first; then the editable + overlay is written on top — **overlay wins**. + +Secrets: dotfiles are encrypted, but real API keys/tokens belong in a Spoon's +**Secrets** feature (injected as env vars), not in dotfiles. The UI nudges this. + +## Materialization (worker) + +`apps/agent-worker/src/user-environment.ts`: + +1. `fetchUserEnvironment(jobId)` — a worker-token Convex action + (`userDotfilesNode.getEnvironmentForJob`) returns the owner's decrypted + dotfiles + repo/setup config. +2. `materializeUserHome` — ensures `~/.bash_profile` (so login shells source + `~/.bashrc` in a mounted home with no `/etc/skel`); clones the repo + runs the + setup command **inside the job image** (so the user's tools/paths apply), only + when the config hash changes (`~/.spoon/env-hash`); writes the overlay files. + +## Configuration + +| Variable | Default | Notes | +| ------------------------------------------ | -------------------------------------------------------------- | ---------------------------------------------------------------------------------------- | +| `SPOON_AGENT_WORKDIR` | `.local/agent-work` (dev) / `/var/lib/spoon-agent/work` (prod) | Per-user homes live under `homes/{username}`; reuses the existing host-path translation. | +| `SPOON_ENCRYPTION_KEY` / `INSTANCE_SECRET` | — | Already required; encrypts dotfiles like other secrets. | + +No new required env. The home is a host directory under the existing workdir, so +the prod bind-mount + `SPOON_AGENT_HOST_WORKDIR` translation already covers it. + +## Notes / limits (Phase 1) + +- **Repo auth:** public repos only. Private/self-hosted (e.g. Gitea) dotfiles + repos are a follow-up (store a token/deploy key). +- **Binary files:** the overlay is text-first. +- **Cleanup:** `~/Code/{spoon}/{branch}` checkouts persist (threads as folders); + a per-thread "delete checkout" action is a follow-up. +- **Concurrency:** jobs share one home; fine at the default + `SPOON_AGENT_MAX_CONCURRENT_JOBS=1`. + +## Phase 2 north star + +A single long-running per-user container that every thread `exec`s into (agent +via `docker exec`, not `docker run --rm`). The per-user home + `~/Code/{spoon}/ +{branch}` layout built here is its foundation.