# Skipper MVP Skipper is a file-backed control plane and host agent pair for lightweight hosting orchestration. ## Structure - `skipper-api/`: Express `/v1` control plane API - `skippy-agent/`: polling agent that executes declarative work orders - `shared/`: storage, tracing, auth, logging, events, snapshots - `data/`: JSON-backed resources, work orders, logs, events, and auth records ## Local Run ```bash docker compose up --build ``` ## Skipper CLI A minimal CLI is now available for common operator tasks. Run it from the repo with: ```bash npm run cli -- --help ``` Available commands: ```bash npm run cli -- health --url http://127.0.0.1:3000 npm run cli -- deploy apply --url http://127.0.0.1:3000 --admin-token YOUR_ADMIN_TOKEN --tenant-id example-tenant DATA_DIR=/opt/skipper/data npm run cli -- node register --node-id host-1 --role worker --region local npm run cli -- node show --url http://127.0.0.1:3000 --admin-token YOUR_ADMIN_TOKEN --node-id host-1 ``` If you install the repo as an executable package, the binary name is `skipper`. ## Deploy Skipper The controller can be deployed on its own host from the internal registry. First-start behavior: - Skipper now bootstraps its own on-disk data layout on startup. - An empty bind-mounted data directory is valid. - You still need to supply an `ADMIN_TOKEN`. Files: - [`deploy/skipper/docker-compose.yml`](/home/sundown/Projekter/nodeJS/Skipper/deploy/skipper/docker-compose.yml#L1) - [`deploy/skipper/.env.example`](/home/sundown/Projekter/nodeJS/Skipper/deploy/skipper/.env.example#L1) Basic flow on the target server: ```bash mkdir -p /opt/skipper cd /opt/skipper cp /path/to/repo/deploy/skipper/docker-compose.yml . cp /path/to/repo/deploy/skipper/.env.example .env ``` Edit `.env`: - set `SKIPPER_IMAGE` to the tag you released - set a strong `ADMIN_TOKEN` - optionally change `SKIPPER_PORT` Start Skipper: ```bash docker login registry.internal.budgethost.io docker compose up -d ``` One-line first boot with `docker run`: ```bash docker run -d \ --name skipper-api \ --restart unless-stopped \ -p 3000:3000 \ -e HOST=0.0.0.0 \ -e PORT=3000 \ -e DATA_DIR=/app/data \ -e ADMIN_TOKEN='replace-with-a-long-random-admin-token' \ -v /opt/skipper/data:/app/data \ registry.internal.budgethost.io/skipper/skipper-api:latest ``` That command is enough for first boot. The container will create the required `/app/data` subdirectories automatically. Verify: ```bash docker compose ps curl http://127.0.0.1:3000/v1/health ``` Persisted controller state will live in: ```bash /opt/skipper/data ``` The next step after this is deploying one or more `Skippy` agents pointed at the controller URL. ## Attach Skippy Attaching a `Skippy` agent has two parts: 1. register the node on the Skipper host 2. start the agent container on the target host ### 1. Register The Node On Skipper Run this on the Skipper host, in the same repo or deployment workspace that owns `/opt/skipper/data`: ```bash DATA_DIR=/opt/skipper/data npm run cli -- node register --node-id host-1 --role worker --region se-sto-1 ``` That writes: - `/opt/skipper/data/resources/nodes/host-1.json` - `/opt/skipper/data/auth/nodes/host-1.json` The command prints the generated token. Save it and use it as `AGENT_TOKEN` on the target host. You can also provide your own token: ```bash DATA_DIR=/opt/skipper/data npm run cli -- node register --node-id host-1 --token 'your-long-random-token' ``` ### 2. Start The Agent On The Target Host Files: - [`deploy/skippy/docker-compose.yml`](/home/sundown/Projekter/nodeJS/Skipper/deploy/skippy/docker-compose.yml#L1) - [`deploy/skippy/.env.example`](/home/sundown/Projekter/nodeJS/Skipper/deploy/skippy/.env.example#L1) Basic flow on the target host: ```bash mkdir -p /opt/skippy cd /opt/skippy cp /path/to/repo/deploy/skippy/docker-compose.yml . cp /path/to/repo/deploy/skippy/.env.example .env ``` Edit `.env`: - set `SKIPPY_IMAGE` - set `SKIPPER_URL` - set `AGENT_ID` - set `AGENT_TOKEN` Start the agent: ```bash docker login registry.internal.budgethost.io docker compose up -d ``` One-line first boot with `docker run`: ```bash docker run -d \ --name skippy-agent \ --restart unless-stopped \ -e DATA_DIR=/app/data \ -e SKIPPER_URL='http://your-skipper-host:3000' \ -e AGENT_ID='host-1' \ -e AGENT_TOKEN='replace-with-generated-token' \ -e POLL_INTERVAL_MS=5000 \ -e HEARTBEAT_INTERVAL_MS=15000 \ -e SKIPPY_COMPOSE_BASE_DIR=/opt/skipper/tenants \ -v /var/run/docker.sock:/var/run/docker.sock \ -v /opt/skippy/data:/app/data \ -v /opt/skippy/tenants:/opt/skipper/tenants \ registry.internal.budgethost.io/skipper/skippy-agent:latest ``` Verify: ```bash docker logs skippy-agent --tail 50 curl -H "x-admin-token: " http://your-skipper-host:3000/v1/resources/node/host-1 ``` ## Image Build And Push Default image targets: - `registry.internal.budgethost.io/skipper/skipper-api` - `registry.internal.budgethost.io/skipper/skippy-agent` Prerequisites: - Docker daemon access must work from your shell - You must be logged into `registry.internal.budgethost.io` Sanity checks: ```bash docker info docker login registry.internal.budgethost.io ``` Preview tags: ```bash npm run images:print ``` Build locally: ```bash IMAGE_TAG=0.1.0 npm run images:build ``` Build and push to the internal registry: ```bash IMAGE_TAG=0.1.0 npm run images:release ``` Push prebuilt tags only: ```bash IMAGE_TAG=0.1.0 npm run images:push ``` Optional overrides: - `IMAGE_REGISTRY` - `IMAGE_NAMESPACE` - `IMAGE_TAG` - `IMAGE_PLATFORM` Run compose from registry-pushed images instead of local builds: ```bash IMAGE_TAG=0.1.0 docker compose -f docker-compose.yml -f docker-compose.registry.yml up -d ``` First release sequence: ```bash npm run smoke:test docker login registry.internal.budgethost.io IMAGE_TAG=0.1.0 npm run images:release IMAGE_TAG=0.1.0 docker compose -f docker-compose.yml -f docker-compose.registry.yml up -d ``` Verification: ```bash docker compose -f docker-compose.yml -f docker-compose.registry.yml ps curl http://localhost:3000/v1/health ``` Current note: - The application smoke test passed in this repository. - The image release flow was prepared and invoked, but this environment could not access `/var/run/docker.sock`, so the actual build and push must be run from a host session with Docker daemon permissions. ## Release Manifest A release-oriented wrapper is available to generate git-aware image tags and write a deployment manifest. Print the next release plan: ```bash npm run release:print ``` Write manifest files without building: ```bash npm run release:plan ``` Build release-tagged images and write manifest: ```bash RUN_SMOKE_TEST=1 npm run release:build ``` Build, push, and write manifest: ```bash RUN_SMOKE_TEST=1 npm run release:publish ``` Generated manifest files: - `artifacts/releases/latest.json` - `artifacts/releases/.json` Release environment overrides: - `RELEASE_CHANNEL` - `RELEASE_VERSION_TAG` - `RELEASE_GIT_SHA` - `IMAGE_REGISTRY` - `IMAGE_NAMESPACE` Tag behavior: - If git metadata is available, the release tag includes the short commit SHA. - If git metadata is not available, the release tag falls back to `nogit`. - Unless overridden, the generated tag format is `--`. ## Smoke Test ```bash npm run smoke:test ``` This starts the API and agent as local subprocesses, uses a mocked `docker` binary, triggers one deploy, and verifies the completed job plus written compose artifacts. Create a deployment work order: ```bash curl -X POST \ -H 'x-admin-token: dev-admin-token' \ -H 'x-idempotency-key: local-run-1' \ -H 'x-request-id: 11111111-1111-1111-1111-111111111111' \ -H 'x-correlation-id: 22222222-2222-2222-2222-222222222222' \ http://localhost:3000/v1/deployments/example-tenant/apply ``` Inspect work orders: ```bash find data/work-orders -type f | sort ``` ## Example Flow 1. `POST /v1/deployments/example-tenant/apply` loads [`data/resources/tenants/example-tenant.json`](/home/sundown/Projekter/nodeJS/Skipper/data/resources/tenants/example-tenant.json#L1) and creates a declarative `deploy_service` work order in `data/work-orders/pending/`. 2. `skippy-agent` polls `GET /v1/nodes/host-1/work-orders/next` every 5 seconds and receives the next work order for `host-1`. 3. The agent writes the compose file to `/opt/skipper/tenants/example-tenant/docker-compose.yml`, writes `.env`, and runs `docker compose up -d`. 4. The agent sends `POST /v1/work-orders/:id/result` with a structured result object plus state updates. 5. `skipper-api` moves the finished work order into `data/work-orders/finished/`, updates resource state, writes structured logs, and emits events.