feat(devbox-docker): shared multi-user dev machine copier template#38
Open
ncimino wants to merge 2 commits into
Open
feat(devbox-docker): shared multi-user dev machine copier template#38ncimino wants to merge 2 commits into
ncimino wants to merge 2 commits into
Conversation
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]>
Contributor
There was a problem hiding this comment.
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.mdand.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/. |
… 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]>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
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 canonicalanythingllm-dockertemplate; 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.ymlapp layer) pattern fromdocs/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)
var.ssh_key_fingerprint) is the only root access (PermitRootLogin prohibit-password).devsgroup gated by sshdAllowGroups devs root.authorized_key exclusive: true— a member's key opens only their own account.Per-user OpenRouter keys (Zed AI)
scripts/setup-zed.sh(run by each member, as themselves):read -rsprompt → stored only in a0600 ~/.config/<slug>/openrouter.env— never insettings.json, argv, or logs. Optional--infisicalpath keeps the key in the member's own Infisical account behind aninfisical run -- zedlauncher.Onboarding / offboarding (edit + re-run, never
tofu taint)Roster lives in
ansible/members.yml(gitignored;members.example.ymlshipped).scripts/add-user.sh <login>scripts/offboard-user.sh root@<host> <login>— archives the member's/homeoff the box, removes the account, then prints an Infisical + shared-secret-rotation revocation checklist.Secrets (none in git)
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)SPACES_ACCESS_KEY/SPACES_SECRET_KEYfor daily backupsValidation
copierrender → 22 filestofu validate+tofu fmt -checkcleanbash -non every scripttemplatefile())Deploy gotchas / next steps (NOT in this PR)
copier copy devbox-docker devbox-docker/sites/<name>), fillterraform.tfvars+ansible/members.yml(confirm real CCC IDs + paste members' public keys), then./init.sh→tofu apply→./scripts/deploy.sh root@<ip>.members.ymlis gitignored by default — commit deliberately if you want the roster version-controlled (public keys aren't secrets).🤖 Generated with Claude Code