Skip to content

feat(devbox-docker): shared multi-user dev machine copier template#38

Open
ncimino wants to merge 2 commits into
mainfrom
feature/nik-devbox-docker
Open

feat(devbox-docker): shared multi-user dev machine copier template#38
ncimino wants to merge 2 commits into
mainfrom
feature/nik-devbox-docker

Conversation

@ncimino
Copy link
Copy Markdown
Contributor

@ncimino ncimino commented Jun 1, 2026

What

Adds devbox-docker/, a copier template for a shared multi-user developer machine — a single DigitalOcean droplet the team SSHes into for Zed (zed.dev) remote development. Forked from the canonical anythingllm-docker template; same Layer 1 (DO Spaces remote tofu state) + Layer 2 (first-boot Infisical Machine-Identity bootstrap-secret rotation) + Path C (thin cloud-init + ansible/deploy.yml app layer) pattern from docs/INFRA_BOOTSTRAP_PATTERN.md.

Source: Jason's 2026-05-20 "Zed is mandatory for the entire team" mandate + the shared-dev-machine ask (Signal ♾️ WeOwn.Dev), and the per-user-account / no-shared-root direction.

Security model (no shared team root)

  • Break-glass admin key (var.ssh_key_fingerprint) is the only root access (PermitRootLogin prohibit-password).
  • Each member gets their own non-root account (login = lowercased CCC Short ID, uid ≥ 1000) in a devs group gated by sshd AllowGroups devs root.
  • authorized_key exclusive: true — a member's key opens only their own account.
  • Members get no sudo; docker-group membership is opt-in per member (root-equivalent — documented; offboarding rotates secrets a docker member could read).
  • SSH (22) is the only inbound port — no Caddy / compose / 80 / 443.

Per-user OpenRouter keys (Zed AI)

scripts/setup-zed.sh (run by each member, as themselves): read -rs prompt → stored only in a 0600 ~/.config/<slug>/openrouter.env — never in settings.json, argv, or logs. Optional --infisical path keeps the key in the member's own Infisical account behind an infisical run -- zed launcher.

Onboarding / offboarding (edit + re-run, never tofu taint)

Roster lives in ansible/members.yml (gitignored; members.example.yml shipped).

  • Onboard: scripts/add-user.sh <login>
  • Offboard: scripts/offboard-user.sh root@<host> <login> — archives the member's /home off the box, removes the account, then prints an Infisical + shared-secret-rotation revocation checklist.

Secrets (none in git)

Where What
terraform.tfvars (gitignored) minimus_token (DO API), ssh_key_fingerprint (break-glass admin), spaces_* (tfstate backend), infisical_client_id/secret (Machine Identity, rotated to v2 at first boot)
Infisical project (runtime) SPACES_ACCESS_KEY / SPACES_SECRET_KEY for daily backups
Per-user (on the box) each member's own OpenRouter key — never in this repo

Validation

  • copier render → 22 files
  • tofu validate + tofu fmt -check clean
  • bash -n on every script
  • YAML parse of cloud-init / ansible / roster (after simulating templatefile())
  • gitleaks + yamllint + markdownlint pre-commit clean

Deploy gotchas / next steps (NOT in this PR)

  • This adds the template only. To deploy: render a site (copier copy devbox-docker devbox-docker/sites/<name>), fill terraform.tfvars + ansible/members.yml (confirm real CCC IDs + paste members' public keys), then ./init.shtofu apply./scripts/deploy.sh root@<ip>.
  • Confirm the Infisical Machine Identity can manage its own client secrets (else Layer 2 auto-rotation falls back to the manual runbook in the README).
  • members.yml is gitignored by default — commit deliberately if you want the roster version-controlled (public keys aren't secrets).

🤖 Generated with Claude Code

Add devbox-docker/, a copier template for a single DigitalOcean droplet the
team SSHes into for Zed (zed.dev) remote development (Jason's 2026-05-20
"Zed mandatory for the team" mandate, Signal #WeOwn.Dev). Forked from the
canonical anythingllm-docker pattern: Layer 1 (DO Spaces remote tofu state) +
Layer 2 (first-boot Infisical Machine-Identity bootstrap-secret rotation) +
Path C (thin cloud-init + ansible/deploy.yml app layer).

Security model (no shared team root):
- break-glass admin key is the only root access (PermitRootLogin prohibit-password)
- each member gets a non-root account (login = lowercased CCC Short ID, uid >= 1000)
  in a `devs` group gated by sshd `AllowGroups devs root`
- authorized_key exclusive: a member's key opens only their own account
- members get no sudo; docker-group membership is opt-in per member
  (root-equivalent, documented)
- SSH (22) is the only inbound port -- no Caddy/compose/80/443

Per-user OpenRouter keys for Zed AI via scripts/setup-zed.sh (read -rs prompt,
stored only in a 0600 ~/.config/<slug>/openrouter.env -- never in settings.json,
argv, or logs; optional --infisical path keeps the key in the member's own
Infisical account behind an `infisical run -- zed` launcher).

Roster lives in ansible/members.yml (gitignored; members.example.yml shipped),
so onboarding (scripts/add-user.sh) and offboarding (scripts/offboard-user.sh --
archives the member's /home off the box, removes the account, then prints an
Infisical + shared-secret-rotation revocation checklist) are edit + re-run,
never `tofu taint`.

Also ships team-standard Zed settings, a dev toolchain (git, node, opentofu,
doctl, copier, ansible-lint, YAML/Python LSPs), daily skinny backups of
/home + /etc (secrets hard-excluded), DO monitoring alerts, and operator +
end-user docs (README.md, docs/CONNECTING-WITH-ZED.md).

Validated: copier render + `tofu validate` + `tofu fmt -check` clean,
`bash -n` on all scripts, YAML parse of cloud-init/ansible/roster.

Also updates .github/CODEOWNERS (/devbox-docker/ -> @ncimino @mshahid538) and
CHANGELOG.md [Unreleased].

Co-Authored-By: Claude Opus 4.8 (1M context) <[email protected]>
Copilot AI review requested due to automatic review settings June 1, 2026 14:42
Copy link
Copy Markdown
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Adds a new devbox-docker/ copier template to provision and operate a shared, multi-user DigitalOcean “dev box” droplet intended for Zed remote development, following the repo’s Layer 1 (Spaces tfstate) + Layer 2 (Infisical bootstrap secret rotation) + Path C (thin cloud-init + Ansible app layer) bootstrap pattern.

Changes:

  • Introduces a full devbox template: OpenTofu infra (droplet/reserved IP/firewall/monitoring) + cloud-init bootstrap (Docker + Infisical CLI + secret rotation).
  • Adds Ansible “app layer” to reconcile per-user accounts, Zed baseline config, dev toolchain, and optional skinny backups.
  • Adds operator/user scripts + docs, and wires ownership/versioning via root CHANGELOG.md and .github/CODEOWNERS.

Reviewed changes

Copilot reviewed 25 out of 25 changed files in this pull request and generated 12 comments.

Show a summary per file
File Description
devbox-docker/copier.yaml Defines copier prompts/defaults for the devbox template (region/size/SSH CIDRs/Infisical/backups/monitoring).
devbox-docker/template/.gitignore.jinja Gitignore rules for state, tfvars, roster, env files, backups, logs, etc.
devbox-docker/template/CHANGELOG.md.jinja Template-local changelog describing initial devbox template release and security posture.
devbox-docker/template/README.md.jinja Operator-facing documentation for provisioning, deploy, onboarding/offboarding, backups, and Layer 2 runbook.
devbox-docker/template/ansible/deploy.yml.jinja App-layer reconciliation: accounts/keys, toolchain, Zed settings distribution, backups cron/logrotate, droplet tagging.
devbox-docker/template/ansible/members.example.yml Example roster schema for per-user accounts (login/uid/keys/docker/state).
devbox-docker/template/docs/CONNECTING-WITH-ZED.md.jinja End-user guide for connecting via Zed remote development and setting up OpenRouter key.
devbox-docker/template/files/zed-settings.json Team baseline Zed settings (JSONC) seeded to users and /etc/skel.
devbox-docker/template/scripts/add-user.sh.jinja Wrapper to validate a roster entry then run deploy and print first-login instructions.
devbox-docker/template/scripts/backup.sh.jinja Skinny backup script for /home + config snapshot with optional DO Spaces upload + retention.
devbox-docker/template/scripts/deploy.sh.jinja Thin wrapper around ansible-playbook including operator-side collection bootstrapping.
devbox-docker/template/scripts/offboard-user.sh.jinja Offboarding helper: archive member home, guide roster removal, optional direct userdel, prints revocation checklist.
devbox-docker/template/scripts/restore.sh.jinja Restore script for skinny backups (remote wrapper via Infisical + local restore logic).
devbox-docker/template/scripts/setup-zed.sh.jinja Per-user OpenRouter key setup for Zed (secure prompt, env file, settings merge; optional Infisical path).
devbox-docker/template/terraform/backend.tf.jinja DO Spaces (S3) remote backend config (SSE-C) for tfstate.
devbox-docker/template/terraform/init.sh.jinja Bridges terraform.tfvars backend creds into tofu init -backend-config ....
devbox-docker/template/terraform/main.tf.jinja Provisions droplet + reserved IP + firewall; injects cloud-init user_data; sets lifecycle ignores for user_data/tags.
devbox-docker/template/terraform/monitoring.tf.jinja Optional DO monitoring alerts (CPU/memory/disk).
devbox-docker/template/terraform/outputs.tf.jinja Outputs for droplet IP/id/domain/SSH hint/Infisical project id.
devbox-docker/template/terraform/terraform.tfvars.example.jinja Example tfvars with strong guidance on what belongs where (and what must not be committed).
devbox-docker/template/terraform/templates/cloud-init.yaml.jinja Cloud-init bootstrap: packages, Docker, Infisical CLI, sshd hardening drop-in, Layer 2 secret rotation script.
devbox-docker/template/terraform/variables.tf.jinja Terraform variables for infra, backend creds, Infisical MI creds, backup/monitoring config.
devbox-docker/template/terraform/versions.tf.jinja OpenTofu/provider version constraints and DO provider token wiring.
CHANGELOG.md Adds a repo-level changelog entry documenting the new devbox-docker/ template.
.github/CODEOWNERS Adds CODEOWNERS coverage for /devbox-docker/.

Comment thread devbox-docker/template/ansible/deploy.yml.jinja Outdated
Comment thread devbox-docker/template/ansible/deploy.yml.jinja
Comment thread devbox-docker/template/scripts/backup.sh.jinja
Comment thread devbox-docker/template/scripts/backup.sh.jinja
Comment thread devbox-docker/template/scripts/restore.sh.jinja
Comment thread devbox-docker/template/README.md.jinja Outdated
Comment thread devbox-docker/template/ansible/members.example.yml Outdated
Comment thread devbox-docker/template/ansible/members.example.yml Outdated
Comment thread devbox-docker/template/ansible/members.example.yml Outdated
Comment thread devbox-docker/template/ansible/members.example.yml Outdated
… hygiene)

Real bugs:
- ansible/deploy.yml: install Zed LSPs via `npm -g` directly instead of
  community.general.npm, which the operator workstation never installs
  (deploy.sh only pins community.docker) -> would fail to resolve the module.
- ansible/deploy.yml: upload restore.sh to the droplet alongside backup.sh.
  restore.sh remote mode execs /opt/<slug>/restore.sh, which was never uploaded.

Hardening:
- backup.sh / restore.sh: run the local-mode body via a temp script + `bash`
  instead of `eval`, removing the second shell parse of rendered values.
- backup.sh / offboard-user.sh: also exclude `.zed-server` (dash) alongside
  `.zed_server` (underscore) so Zed's remote-server cache is reliably skipped.
- setup-zed.sh: fix the client-side note -- never paste the key into
  settings.json; use Zed's OS keychain or OPENAI_API_KEY env (consistent with
  the rest of the flow).

Docs / public-repo hygiene:
- README.md: correct offboard-user.sh usage (root@<host> <login>) and its
  description (archives + prints steps; operator sets state: absent + redeploy,
  or uses the userdel fallback).
- members.example.yml + README/outputs/CONNECTING docs: genericize examples to
  fictitious placeholders (ccc-alice / Alice Example), drop internal tracker
  references and the team name -- this repo is public.

Re-validated: copier render + tofu validate + tofu fmt -check clean, bash -n on
all scripts, YAML parse of ansible/roster.

Co-Authored-By: Claude Opus 4.8 (1M context) <[email protected]>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants