diff --git a/docs/plans/2026-01-29-core-devops-design.md b/docs/plans/2026-01-29-core-devops-design.md new file mode 100644 index 00000000..1b66e671 --- /dev/null +++ b/docs/plans/2026-01-29-core-devops-design.md @@ -0,0 +1,306 @@ +# Core DevOps CLI Design (S4.6) + +## Summary + +Portable development environment CLI commands for the core-devops LinuxKit image. Provides a sandboxed, immutable environment with 100+ embedded tools. + +## Design Decisions + +- **Image sources**: GitHub Releases + Container Registry + CDN (try in order, configurable) +- **Local storage**: `~/.core/images/` with `CORE_IMAGES_DIR` env override +- **Shell connection**: SSH by default, `--console` for serial fallback +- **Serve**: Mount PWD into VM via 9P/SSHFS, run auto-detected dev server +- **Test**: Auto-detect framework + `.core/test.yaml` config + `--` override +- **Update**: Simple hash/version check, `--force` to always download +- **Claude sandbox**: SSH in with forwarded auth, safe experimentation in immutable image + +## Package Structure + +``` +pkg/devops/ +├── devops.go # DevOps struct, Boot/Stop/Status +├── images.go # ImageManager, manifest handling +├── mount.go # Directory mounting (9P, SSHFS) +├── serve.go # Project detection, serve command +├── test.go # Test detection, .core/test.yaml parsing +├── config.go # ~/.core/config.yaml handling +└── sources/ + ├── source.go # ImageSource interface + ├── github.go # GitHub Releases + ├── registry.go # Container registry + └── cdn.go # CDN/S3 + +cmd/core/cmd/dev.go # CLI commands +``` + +## Image Storage + +``` +~/.core/ +├── config.yaml # Global config (image source preference, etc.) +└── images/ + ├── core-devops-darwin-arm64.qcow2 + ├── core-devops-darwin-amd64.qcow2 + ├── core-devops-linux-amd64.qcow2 + └── manifest.json # Tracks versions, hashes, last-updated +``` + +## ImageSource Interface + +```go +type ImageSource interface { + Name() string + Available() bool + LatestVersion() (string, error) + Download(ctx context.Context, dest string) error +} +``` + +Sources tried in order: GitHub → Registry → CDN, or respect user preference in config. + +## CLI Commands + +```go +// cmd/core/cmd/dev.go + +func AddDevCommand(app *clir.Cli) { + devCmd := app.NewSubCommand("dev", "Portable development environment") + + // core dev install [--source github|registry|cdn] + // Downloads core-devops image for current platform + + // core dev boot [--memory 4096] [--cpus 4] [--name mydev] + // Boots the dev environment (detached by default) + + // core dev shell [--console] + // SSH into running dev env (or serial console with --console) + + // core dev serve [--port 8000] + // Mount PWD → /app, run FrankenPHP, forward port + + // core dev test [-- custom command] + // Auto-detect tests or use .core/test.yaml or pass custom + + // core dev claude [--auth] [--model opus|sonnet] + // SSH in with forwarded auth, start Claude in sandbox + + // core dev update [--force] + // Check for newer image, download if available + + // core dev status + // Show if dev env is running, resource usage, ports + + // core dev stop + // Stop the running dev environment +} +``` + +## Command Flow + +``` +First time: + core dev install → Downloads ~/.core/images/core-devops-{os}-{arch}.qcow2 + core dev boot → Starts VM in background + core dev shell → SSH in + +Daily use: + core dev boot → Start (if not running) + core dev serve → Mount project, start server + core dev test → Run tests inside VM + core dev shell → Interactive work + +AI sandbox: + core dev claude → SSH + forward auth + start Claude CLI + +Maintenance: + core dev update → Get latest image + core dev status → Check what's running +``` + +## `core dev claude` - Sandboxed AI Session + +```bash +core dev claude # Forward all auth by default +core dev claude --no-auth # Clean session, no host credentials +core dev claude --auth=gh,anthropic # Selective forwarding +``` + +**What it does:** +1. Ensures dev VM is running (auto-boots if not) +2. Forwards auth credentials from host: + - `~/.anthropic/` or `ANTHROPIC_API_KEY` + - `~/.config/gh/` (GitHub CLI auth) + - SSH agent forwarding + - Git config (name, email) +3. SSHs into VM with agent forwarding (`ssh -A`) +4. Starts `claude` CLI inside with forwarded context +5. Current project mounted at `/app` + +**Why this is powerful:** +- Immutable base = reset anytime with `core dev boot --fresh` +- Claude can experiment freely, install packages, make mistakes +- Host system untouched +- Still has real credentials to push code, create PRs +- Full 100+ tools available in core-devops image + +## Test Configuration + +**`.core/test.yaml` format:** +```yaml +version: 1 + +# Commands to run (in order) +commands: + - name: unit + run: vendor/bin/pest --parallel + - name: types + run: vendor/bin/phpstan analyse + - name: lint + run: vendor/bin/pint --test + +# Or simple single command +command: npm test + +# Environment variables +env: + APP_ENV: testing + DB_CONNECTION: sqlite +``` + +**Auto-Detection Priority:** +1. `.core/test.yaml` +2. `composer.json` scripts.test → `composer test` +3. `package.json` scripts.test → `npm test` +4. `go.mod` → `go test ./...` +5. `pytest.ini` or `pyproject.toml` → `pytest` +6. `Taskfile.yaml` → `task test` + +**CLI Usage:** +```bash +core dev test # Auto-detect and run +core dev test --unit # Run only "unit" from .core/test.yaml +core dev test -- go test -v ./pkg/... # Override with custom +``` + +## `core dev serve` - Mount & Serve + +**How it works:** +1. Ensure VM is running +2. Mount current directory into VM via 9P virtio-fs (or SSHFS fallback) +3. Start auto-detected dev server on /app inside VM +4. Forward port to host + +**Mount Strategy:** +```go +type MountMethod int +const ( + Mount9P MountMethod = iota // QEMU virtio-9p (faster) + MountSSHFS // sshfs reverse mount + MountRSync // Fallback: rsync on change +) +``` + +**CLI Usage:** +```bash +core dev serve # Mount PWD, serve on :8000 +core dev serve --port 3000 # Custom port +core dev serve --path ./backend # Serve subdirectory +``` + +**Project Detection:** +```go +func detectServeCommand(projectDir string) string { + if exists("artisan") { + return "php artisan octane:start --host=0.0.0.0 --port=8000" + } + if exists("package.json") && hasScript("dev") { + return "npm run dev -- --host 0.0.0.0" + } + if exists("composer.json") { + return "frankenphp php-server" + } + return "python -m http.server 8000" // Fallback +} +``` + +## Image Sources & Updates + +**~/.core/config.yaml:** +```yaml +version: 1 + +images: + source: auto # auto | github | registry | cdn + + cdn: + url: https://images.example.com/core-devops + + github: + repo: host-uk/core-images + + registry: + image: ghcr.io/host-uk/core-devops +``` + +**Manifest for Update Checking:** +```json +// ~/.core/images/manifest.json +{ + "core-devops-darwin-arm64.qcow2": { + "version": "v1.2.0", + "sha256": "abc123...", + "downloaded": "2026-01-29T10:00:00Z", + "source": "github" + } +} +``` + +**Update Flow:** +```go +func (d *DevOps) Update(force bool) error { + local := d.manifest.Get(imageName) + remote, _ := d.source.LatestVersion() + + if force || local.Version != remote { + fmt.Printf("Updating %s → %s\n", local.Version, remote) + return d.source.Download(ctx, imagePath) + } + fmt.Println("Already up to date") + return nil +} +``` + +## Commands Summary + +| Command | Description | +|---------|-------------| +| `core dev install` | Download image for platform | +| `core dev boot` | Start VM (auto-installs if needed) | +| `core dev shell` | SSH in (--console for serial) | +| `core dev serve` | Mount PWD, run dev server | +| `core dev test` | Run tests inside VM | +| `core dev claude` | Start Claude session in sandbox | +| `core dev update` | Check/download newer image | +| `core dev status` | Show VM state, ports, resources | +| `core dev stop` | Stop the VM | + +## Dependencies + +- Reuse existing `pkg/container` for VM management (LinuxKitManager) +- SSH client for shell/exec (golang.org/x/crypto/ssh) +- Progress bar for downloads (charmbracelet/bubbles or similar) + +## Implementation Steps + +1. Create `pkg/devops/` package structure +2. Implement ImageSource interface and sources (GitHub, Registry, CDN) +3. Implement image download with manifest tracking +4. Implement config loading (`~/.core/config.yaml`) +5. Add CLI commands to `cmd/core/cmd/dev.go` +6. Implement boot/stop using existing LinuxKitManager +7. Implement shell (SSH + serial console) +8. Implement serve (mount + project detection) +9. Implement test (detection + .core/test.yaml) +10. Implement claude (auth forwarding + sandbox) +11. Implement update (version check + download) +12. Implement status