Files
skipper/README.md
2026-04-05 15:28:04 +02:00

348 lines
8.6 KiB
Markdown

# 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: <your-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/<version_tag>.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 `<channel>-<git_sha>-<timestamp>`.
## 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.