docs: add core-devops CLI design (S4.6)
Design for portable dev environment commands: - core dev install/boot/shell/serve/test/update/status/stop - core dev claude - sandboxed AI session with auth forwarding - Image sources: GitHub Releases, Container Registry, CDN - Mount & serve with auto-detection - Test config via .core/test.yaml Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
parent
5e0252b5ee
commit
e5f9276fcd
1 changed files with 306 additions and 0 deletions
306
docs/plans/2026-01-29-core-devops-design.md
Normal file
306
docs/plans/2026-01-29-core-devops-design.md
Normal file
|
|
@ -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
|
||||||
Loading…
Add table
Reference in a new issue