Skip to content

gl0bal01/devbox

Repository files navigation

DevBox v1.0.0

Tailscale-first dev / pentest / AI station for Ubuntu 24.04 + Docker.

Highlights

  • Runtime contractscripts/lib/devbox-contract.sh is the single source of truth for install layout, service identities, container names, Traefik hosts, named volumes, backup targets, and the pinned alpine backup image. setup.sh installs the contract; helpers source it; Bats tests pin its shape.
  • Tailscale-only HTTP and (optionally) HTTPS — no public 0.0.0.0 bind in either mode. HTTPS uses an opt-in Compose overlay + OVH DNS-01 cert. See ARCHITECTURE.md.
  • Per-install Ollama basic-auth credential — random 32-char password generated by setup.sh, plaintext at ~/docker/.secrets/ollama-auth.txt (0600), rotatable via rotate-ollama-auth.sh. Open WebUI uses internal Compose DNS, no auth crossing.
  • Image digest lockfiles committed per service. Setup.sh emits the compose chain (docker-compose.yml[:docker-compose.https.yml][:docker-compose.lock.yml]) via the contract. CI gates on update-images.sh --check.
  • Verified downloads — every curl|sh routed through fetch_and_verify with SHA256 pinned in scripts/lib/download-manifest.sh. Emergency override: DEVBOX_ALLOW_UNVERIFIED=1.
  • Signed weekly release tarball — cosign keyless + CycloneDX SBOM + SLSA Build L3 provenance. Fork-friendly CI (conditional Docker Hub login). Alpine digest auto-PR (never auto-merges main).
  • Systemd integrationdevbox.service (oneshot + RemainAfterExit) and daily devbox-backup.timer, rendered from contract values. Installed via make install-systemd.
  • Operator Makefilehelp|doctor|test|lint|compose-check|start|stop|status|backup|security-check|rotate-ollama-auth|install-systemd|uninstall-systemd.
  • Spoof-resistant collision guardsetup.sh refuses to install into a non-devbox ~/docker/ unless both the contract and a marker file are present (upgrade-in-place).
  • Honest privilege modeldev is in the docker group (root-equivalent for socket access). Security boundary = Tailscale ACL + SSH key auth + UFW default-deny. See docs/security.md.
  • 35 contract bats tests + 55 unit bats tests gate every PR via .github/workflows/pr-validate.yml.

Table of Contents

Overview

DevBox provisions Ubuntu 24.04 with a hardened, reproducible stack: Traefik behind docker-socket-proxy, Ollama + Open WebUI for local LLM inference, and optional Exegol for pentest work. Every service is bound to the Tailscale IP — no public listener beyond SSH on a custom port.

Two access paths are supported:

  • Path A — Tailscale-only HTTP (default). Port 80 bound to the Tailscale IP. No domain or certificates needed. HTTP over WireGuard is already encrypted.
  • Path B — Tailscale-only HTTPS (opt-in). Port 443 also bound to the Tailscale IP (no 0.0.0.0 listener). Let's Encrypt wildcard cert via OVH DNS-01. Requires ENABLE_HTTPS=true, DEVBOX_DOMAIN, and ~/.config/devbox/ovh.env with OVH API credentials. Certificate is publicly issued; listener is reachable only through Tailscale.

Architecture diagram

                           Path A (default)          Path B (opt-in)
                           ────────────────          ───────────────
[Your Devices] ──Tailscale──> [Traefik :80]    [Your Devices] ──Tailscale──> [Traefik :443]
                                    |                                              |
                  (Tailscale IP)    |                      (Tailscale IP, TLS)     |
                                    v                                              v
+---------------------------[VPS]-------------------------------------------+
|                                                                           |
|   [Tailscale] <──> [Your Devices]   (no public binding in either path)    |
|        |                                                                  |
|        v                                                                  |
|   [Traefik] ──────+──── [Open WebUI]    ai.internal / ai.DOMAIN           |
|        |          |                                                       |
|        |          +──── [Ollama API]    ollama.internal (basicAuth)       |
|        |          |                                                       |
|        |          +──── [Traefik Dashboard] traefik.internal (basicAuth)  |
|        |                                                                  |
|        +──> [Docker Socket Proxy] ──> /var/run/docker.sock                |
|             (internal network, read-only API subset)                      |
|                                                                           |
|   [Exegol Container] <──> [HTB/THM VPN]  (optional, upstream exegol CLI)  |
|        (noVNC bound via UFW to tailscale0 only)                           |
|                                                                           |
+---------------------------------------------------------------------------+

For the load-bearing design decisions (trust model, install layout, contract, HTTPS overlay, lockfile workflow, systemd integration, snapshot recovery, etc.) see ARCHITECTURE.md.

Requirements

  • Ubuntu 24.04 LTS.
  • Docker + Docker Compose plugin (v2.22+) pre-installed.
  • 4 GB RAM minimum; 24 GB+ recommended for Ollama with larger models.
  • Root access for the initial setup run.
  • SSH public key for authentication.

Quick start

Production installs: Prefer the signed weekly release tarball — it ships with cosign keyless signature, SHA256 digest, CycloneDX SBOM, and SLSA provenance. See Install from a signed release tarball in docs/updating.md for the verified-download sequence.

The git clone path below is the dev / contributor path. No signature verification, tracks main HEAD.

git clone https://github.com/gl0bal01/devbox.git
cd devbox

# Optional: override defaults via env
export DEVBOX_USER=dev
export DEVBOX_SSH_PORT=5522
export SSH_PUBLIC_KEY="$(cat ~/.ssh/id_ed25519.pub)"

# Optional: HTTPS mode — create OVH credentials first
install -d -m 0700 ~/.config/devbox
cat > ~/.config/devbox/ovh.env <<EOF
OVH_ENDPOINT=ovh-eu
OVH_APPLICATION_KEY=your-key
OVH_APPLICATION_SECRET=your-secret
OVH_CONSUMER_KEY=your-consumer-key
EOF
chmod 600 ~/.config/devbox/ovh.env
export ENABLE_HTTPS=true
export DEVBOX_DOMAIN=example.com

# Inspect every Phase without touching the host
./setup.sh --dry-run

# Install
sudo ./setup.sh

After install, follow Post-installation.

Configuration

Environment variables override the defaults in setup.sh:

Variable Default Purpose
DEVBOX_USER dev Operator account created by setup.sh
DEVBOX_EMAIL admin@example.com Let's Encrypt registration email
DEVBOX_SSH_PORT 5522 SSH port
DEVBOX_DOMAIN example.com Domain (HTTPS mode only)
ENABLE_HTTPS false Opt-in to Tailscale-only HTTPS
SSH_PUBLIC_KEY (unset) Your SSH pubkey string

Setup.sh CLI flags:

Flag Effect
--dry-run Print every Phase + an rsync --dry-run per service. No mutations. No root needed.
--check Source the contract; report whether the installed contract matches the repo copy. No root needed.
--yes / -y Skip interactive prompts (continue confirm, UFW reset). Unattended install.
--help / -h Print usage + exit.

HTTPS mode requires ~/.config/devbox/ovh.env (mode 0600) — setup.sh refuses ENABLE_HTTPS=true without it. OVH credentials never live in tracked source. See ARCHITECTURE.md for the trust rationale.

SSH public key resolution

setup.sh reads your SSH public key from (in order):

  1. SSH_PUBLIC_KEY="ssh-ed25519 AAAA..." env var.
  2. ~/.ssh/devbox_authorized_key or /root/.ssh/devbox_authorized_key.
  3. Manual: add to ~/.ssh/authorized_keys after setup completes.

Post-installation

1. Verify SSH access

From a NEW terminal on your local machine (do not close the setup terminal until this works):

ssh -p 5522 dev@YOUR_SERVER_IP

2. Authenticate Tailscale

sudo tailscale up --accept-routes --advertise-tags=tag:devbox

3. Start services

cd ~/docker
./start-all.sh

Lockfiles are committed in services/*/docker-compose.lock.yml. The emitted compose chain in ~/.config/devbox/config.env already includes them; helpers filter chain entries by [ -f ... ] so a missing overlay never blocks startup. To verify lockfile freshness:

./scripts/update-images.sh --check     # exit 0 = in sync

4. Configure local DNS

Add to /etc/hosts on your local machine (replace with your Tailscale IP):

100.X.Y.Z  ai.internal traefik.internal ollama.internal exegol.internal

Get your Tailscale IP with tailscale ip -4.

5. Pull Ollama models

docker exec -it ollama ollama pull llama3.2
docker exec -it ollama ollama pull codellama

External clients of http://ollama.internal (Tailscale-only) authenticate with basic auth. The per-install credential is at:

~/docker/.secrets/ollama-auth.txt        # mode 0600, format: ollama:<password>

Rotate with ~/docker/rotate-ollama-auth.sh. Open WebUI is unaffected — it talks to Ollama on the internal Compose network (http://ollama:11434, no auth).

6. Optional — install AI coding tools

~/docker/install-ai-dev-stack.sh           # interactive menu
~/docker/install-ai-dev-stack.sh --all     # install all
~/docker/install-ai-dev-stack.sh --status
~/docker/install-ai-dev-stack.sh --update

Tools shipped: Claude Code, OpenCode, Goose, LLM, Fabric.

7. Optional — enable systemd

sudo make install-systemd
# systemctl enable: devbox.service + devbox-backup.timer (not auto-started)
sudo systemctl start devbox.service
sudo systemctl list-timers devbox-backup.timer

devbox.service is Type=oneshot RemainAfterExit=yes — it calls start-all.sh on boot and stop-all.sh on shutdown. The daily backup timer runs backup.sh with Persistent=true RandomizedDelaySec=1800.

8. Verify security

~/docker/security-check.sh

Reproducibility

Three install modes are documented in docs/updating.md:

Mode How Signed? Smoke-tested? Recommended for
latest git pull origin main No No Dev / contribution only
weekly-YYYYMMDD gh release download weekly-... + cosign verify Yes Yes Production
@sha256:<digest> Pin release tarball by SHA256 Yes Yes Maximum paranoia

The weekly job at .github/workflows/weekly-rebuild.yml builds a deterministic devbox.tar, signs with cosign keyless (GitHub OIDC + Sigstore Rekor), attaches a CycloneDX SBOM + SLSA Build L3 provenance, and publishes via gh release create. A separate alpine-digest-check job opens a PR on alpine digest drift — never auto-merges.

Verify a weekly release

gh release download weekly-20260420 \
  -p devbox.tar -p devbox.tar.sha256 \
  -p devbox.tar.sig -p devbox.tar.pem

sha256sum -c devbox.tar.sha256

cosign verify-blob devbox.tar \
  --signature devbox.tar.sig \
  --certificate devbox.tar.pem \
  --certificate-identity 'https://github.com/gl0bal01/devbox/.github/workflows/weekly-rebuild.yml@refs/heads/main' \
  --certificate-oidc-issuer 'https://token.actions.githubusercontent.com'

Both --certificate-identity AND --certificate-oidc-issuer are required — omitting either accepts any GitHub Actions workflow as a valid signer.

Multi-arch caveat

Committed docker-compose.lock.yml files target x86_64 (the CI runner architecture). Operators on arm64 must regenerate lockfiles locally with ./scripts/update-images.sh --apply before start-all.sh. See docs/updating.md.

Sigstore unavailability fallback

If Sigstore (Rekor/Fulcio) is down, CI fails closed and no weekly-* tag is cut. Emergency path: git checkout main && sudo ./setup.sh. Treat as untrusted and re-verify by re-running weekly-rebuild once Sigstore recovers.

Operator commands

The Makefile is the single entry point.

make help                 # one-line summary of every target
make doctor               # preflight: docker daemon, tailscale, config.env, contract, marker
make test                 # lint + compose-check + anchor-check + docs-tree + contract bats
make lint                 # shellcheck + bash -n + 55 unit bats
make compose-check        # validate compose configs (HTTP + HTTPS for every service)
make start                # ${HOME}/docker/start-all.sh (refuses without install marker)
make stop                 # ${HOME}/docker/stop-all.sh
make status               # ${HOME}/docker/status.sh
make backup               # named-volume tar via alpine@sha256 + acme.json + .env files
make security-check       # 10 hardening assertions
make rotate-ollama-auth   # regenerate per-install ollama basic-auth credential
make install-systemd      # sudo — install + enable devbox.service + devbox-backup.timer
make uninstall-systemd    # sudo — stop, disable, remove units

Install-dependent targets gate on the install marker at ~/docker/lib/.devbox-marker and refuse with a diagnostic message when absent. make doctor always runs.

Penetration testing workflow

# Connect to HTB VPN (PID-file based, no global pkill)
htb-vpn ~/htb/lab.ovpn

htb-vpn status

# Start Exegol with desktop (random per-container password)
exegol                             # Default: exegol-htb on port 45377
exegol osint-box --port 45378      # Multiple containers on different ports

# Rotate VNC password for a running container
~/docker/exegol-reset-vnc.sh exegol-htb

# Access: http://exegol.internal:45377/vnc.html
# Username: root, password printed once at container start.
# UFW restricts the port to the tailscale0 interface only.

See docs/exegol.md for the full workflow. Exegol uses the upstream exegol CLI — no Compose stack under services/.

Security model

Per docs/security.md and ARCHITECTURE.md:

dev is in the docker group. This is equivalent to root for anyone who compromises the dev account (docker socket → bind-mount /). The security boundary is Tailscale ACL + SSH key auth + UFW default-deny, not local privilege separation.

Sudoers whitelist (only commands NOT handled by the docker boundary):

dev ALL=(root) NOPASSWD: /usr/sbin/ufw, /usr/bin/tailscale, /usr/sbin/openvpn
dev ALL=(root) NOPASSWD: /bin/systemctl restart docker, /bin/systemctl reload ufw
dev ALL=(root) PASSWD: ALL

Network exposure

  • Only SSH (default port 5522) is exposed to the public internet.
  • Traefik ports 80 and (optional) 443 bound to the Tailscale IP — inaccessible from LAN.
  • All .internal URLs require Tailscale.
  • Exegol noVNC port: container-internal 0.0.0.0, host-side UFW rule scoped to tailscale0.

Container security

Measure Implementation
Secrets management .env files mode 0600, rendered at install via whitelisted envsubst
Docker socket protection Traefik uses tecnativa/docker-socket-proxy (read-only API subset)
Privilege escalation prevention security_opt: no-new-privileges:true on every container
Capability dropping cap_drop: ALL + explicit cap_add whitelist
Resource limits Memory, CPU, and PID limits per container
Log rotation json-file driver, 10 MB × 3 files (drift-checked by CI)
Stop grace period Tuned per service (60s for Ollama to drain in-flight inference)

Authentication

Service Method
SSH Key-based only (password disabled, root disabled)
Open WebUI Application-level (disable signup after admin creation)
Traefik Dashboard Basic Auth (dashboard-auth middleware)
Ollama external route Basic Auth (ollama-auth middleware, per-install random credential)

Optional hardening modules

Three opt-in operator scripts under scripts/host/ add per-target hardening on top of the baseline. Each is dry-run by default; --apply is required to mutate. See docs/harden-modules.md.

sudo scripts/host/harden-dnat-scope.sh               # dry-run
sudo scripts/host/harden-dnat-scope.sh --apply       # scope Docker DNAT to Tailscale CGNAT
sudo scripts/host/harden-fail2ban.sh --apply         # traefik-auth + recidive jails (publicly bound only)
sudo scripts/host/harden-backup-skeleton.sh \
  --tag myapp --path /home/myapp --apply             # age-encrypted, systemd-timed backup

Troubleshooting

sudo systemctl status ssh         # SSH up?
sudo ss -tlnp | grep ssh          # Listening port?
docker ps                         # Containers up?
docker logs traefik               # Traefik logs
tailscale status                  # Tailscale connected?
~/docker/status.sh                # Full status dashboard
~/docker/diagnose.sh              # Bundle full diagnostic tarball

If setup.sh blocks with "Another devbox setup.sh is already running":

sudo ls -la /var/lock/devbox-setup.lock
sudo rm /var/lock/devbox-setup.lock   # only if no real setup is running

If setup.sh refuses with a collision error, the ~/docker/ tree contains non-devbox files. Either move them aside or pick a clean DEVBOX_HOME. The upgrade-in-place mode triggers automatically when both ~/docker/lib/devbox-contract.sh AND ~/docker/lib/.devbox-marker are present.

For more scenarios, see docs/troubleshooting.md and docs/ops.md.

Directory structure

Repository

devbox/
├── setup.sh                            # Host bootstrap (sources contract, rsyncs, renders, writes marker)
├── ARCHITECTURE.md                     # Load-bearing design decisions
├── Makefile                            # Operator entry point
├── README.md
├── CONTRIBUTING.md
├── LICENSE
├── config.env.example                  # Model for ~/.config/devbox/config.env
├── services/
│   ├── README.md                       # Compose conventions + rendering table
│   ├── traefik/
│   │   ├── docker-compose.yml
│   │   ├── docker-compose.https.yml
│   │   ├── docker-compose.lock.yml
│   │   ├── traefik.yml
│   │   ├── traefik.https.yml.template
│   │   ├── .env.template
│   │   ├── .env.example
│   │   └── dynamic/
│   │       ├── dashboard-auth.yml.template
│   │       ├── ollama-auth.yml.template
│   │       ├── middlewares-base.yml
│   │       ├── middlewares-https.yml
│   │       ├── middlewares-rate.yml
│   │       └── middlewares-allowlist.yml
│   └── ollama-openwebui/
│       ├── docker-compose.yml
│       ├── docker-compose.https.yml
│       ├── docker-compose.lock.yml
│       ├── .env.template
│       └── .env.example
├── scripts/
│   ├── host/                           # Helpers rsynced to ~/docker/ by setup.sh
│   │   ├── start-all.sh
│   │   ├── stop-all.sh
│   │   ├── status.sh
│   │   ├── security-check.sh
│   │   ├── backup.sh                   # named-volume tar via alpine@sha256
│   │   ├── rotate-ollama-auth.sh       # per-install credential rotation
│   │   ├── install-systemd.sh          # render + enable systemd units
│   │   ├── diagnose.sh
│   │   ├── install-ai-dev-stack.sh
│   │   ├── htb-vpn.sh
│   │   ├── exegol-start.sh
│   │   ├── exegol-reset-vnc.sh
│   │   ├── harden-dnat-scope.sh        # opt-in
│   │   ├── harden-fail2ban.sh          # opt-in
│   │   └── harden-backup-skeleton.sh   # opt-in
│   ├── install/
│   │   ├── dev-zshrc
│   │   └── mise-profile.sh
│   ├── laptop/                         # Run on your laptop
│   │   ├── ollama-setup.sh
│   │   ├── zed-setup.sh
│   │   └── README.md
│   ├── lib/
│   │   ├── devbox-contract.sh          # CANONICAL CONTRACT
│   │   ├── fetch-verify.sh
│   │   ├── download-manifest.sh
│   │   └── sources/                    # per-upstream version refresh handlers
│   ├── systemd/
│   │   ├── devbox.service.template
│   │   ├── devbox-backup.service.template
│   │   └── devbox-backup.timer.template
│   ├── ci/
│   │   ├── lint.sh                     # shellcheck + bash -n + 55 unit bats + docs-tree
│   │   ├── check-compose-config.sh
│   │   ├── check-anchor-consistency.sh
│   │   ├── check-docs-tree.sh
│   │   ├── smoke-test.sh
│   │   ├── sbom-targets.sh
│   │   └── release-notes.sh
│   ├── update-images.sh                # Two-pass lockfile generator
│   └── update-manifest.sh
├── tests/
│   ├── contract/
│   │   ├── contract.bats               # 24 install-contract assertions
│   │   └── systemd.bats                # 11 systemd-unit assertions
│   ├── unit/                           # 55 bats (anchor, fetch-verify, htb-vpn, parse-image, render-env)
│   └── lib/test-helpers.bash
├── docs/
│   ├── security.md
│   ├── ops.md
│   ├── updating.md
│   ├── harden-modules.md
│   ├── https-setup.md
│   ├── exegol.md
│   ├── ollama-optimization.md
│   ├── remote-ide-setup.md
│   └── troubleshooting.md
└── .github/
    └── workflows/
        ├── pr-validate.yml             # lint + compose-check + anchor + docs-tree + contract bats
        └── weekly-rebuild.yml          # signed tarball + SBOM + SLSA + alpine-digest-check PR

Server (after setup.sh)

~/
├── .devbox-credentials[.gpg]           # Generated credentials (delete after saving)
├── .config/devbox/
│   ├── config.env                      # ENABLE_HTTPS, DOMAIN, TAILSCALE_IP, DEVBOX_USER, COMPOSE_FILE_<SVC>
│   └── ovh.env                         # OVH credentials (HTTPS mode; mode 0600)
├── .local/share/devbox/backups/        # Pre-rsync snapshots + backup.sh archives
├── docker/                             # rsynced from services/ + scripts/host/ + scripts/systemd/
│   ├── lib/
│   │   ├── devbox-contract.sh          # Installed runtime authority
│   │   └── .devbox-marker              # Spoof-resistant install marker
│   ├── .secrets/
│   │   └── ollama-auth.txt             # Per-install plaintext credential (0600)
│   ├── traefik/
│   ├── ollama-openwebui/
│   ├── systemd/                        # Unit templates for install-systemd.sh
│   ├── start-all.sh, stop-all.sh, status.sh, security-check.sh, backup.sh
│   ├── rotate-ollama-auth.sh, install-systemd.sh
│   ├── exegol-start.sh, exegol-reset-vnc.sh, htb-vpn.sh
│   └── (per-service .env files rendered from .env.template at install time)
├── projects/
└── htb/                                # HTB .ovpn files

Documentation index

Document Topic
ARCHITECTURE.md Load-bearing design decisions (replaces the old docs/adr/ tree)
docs/security.md Trust model, privilege boundaries, ollama-auth rotation semantics
docs/ops.md Backup/restore, incident response, snapshot recovery
docs/updating.md Signed tarball install, digest refresh, alpine auto-PR
docs/harden-modules.md Opt-in harden-*.sh modules (DNAT, fail2ban, age-encrypted backups)
docs/https-setup.md OVH DNS-01 setup for Tailscale-only HTTPS
docs/exegol.md Multi-container pentest workflows
docs/ollama-optimization.md Performance tuning
docs/remote-ide-setup.md Configure local IDE with remote Ollama
docs/troubleshooting.md Common issues
services/README.md Compose layout, rendering table, anchor pattern
scripts/laptop/README.md Laptop-side setup for remote Ollama
CONTRIBUTING.md How to contribute

Tested environments

Provider Instance type Status
Hostinger KVM 8 (32 GB / 8 vCPU) Verified
Hetzner CX31 Compatible
DigitalOcean Droplet Compatible
AWS EC2 t3.medium+ Compatible

License

MIT. See LICENSE.

References


Last updated: 2026-05-16 (v1.0.0 — runtime contract, Tailscale-only HTTPS, per-install ollama-auth, signed weekly tarball, 35 contract bats + 55 unit bats)

About

Tailscale-first dev/pentest/AI station: Traefik + Ollama + Open WebUI on Ubuntu 24.04 + Docker. Signed weekly tarball with cosign + SBOM + SLSA.

Topics

Resources

License

Contributing

Security policy

Stars

Watchers

Forks

Packages

 
 
 

Contributors