Console (cluster dashboard + admin panel) starts as a background daemon under marvin on :8090, sharing the same config.toml and SQLite DB (WAL multi-writer). Forwarded via run.sh and exposed through tailcat; same login as the server.
154 lines
6.1 KiB
Markdown
154 lines
6.1 KiB
Markdown
# marvin-turnstone
|
|
|
|
Local turnstone deployment as a **container-VM**: a podman image is turned
|
|
into a bootable VM with [boco](https://git.nubo.sh/hulthe/boco) (custom
|
|
init + ext4 qcow2 + QEMU direct kernel boot). Replaces the older
|
|
opencode-in-a-container setup (`~/marvin`).
|
|
|
|
```
|
|
host podman build ──> boco build qcow2 ──> boco boot
|
|
│ QEMU/KVM (virtio, user-net)
|
|
┌─────────────────────┴───────────────────┐
|
|
│ VM (Arch) │
|
|
│ /boco/init → start-turnstone.sh (root) │
|
|
│ ├─ sshd (ops channel) │
|
|
│ └─ setpriv marvin │
|
|
│ └─ turnstone-server :8080 PID 1 │
|
|
└─────────────────────────────────────────┘
|
|
host :8080 ──hostfwd──> web UI host :2222 ──> guest sshd :22
|
|
guest ──HTTPS (double slirp egress)──> api.tensorx.ai (LLM)
|
|
```
|
|
|
|
## Prerequisites
|
|
|
|
Host (Arch):
|
|
- `podman`, `qemu-base`, `virtiofsd`, `croc` (only if fetching the kernel)
|
|
- `boco` CLI on PATH and `boco-init` at `~/.local/share/boco/boco-init`
|
|
(build from the boco repo)
|
|
- Kernel at `~/.local/share/boco/registry/vmlinuz` (x86 bzImage with
|
|
`CONFIG_VIRTIO_FS=y`; transfer via croc)
|
|
|
|
VM image:
|
|
- Arch base with python + turnstone (venv at `/opt/turnstone`), git,
|
|
helix, podman (rootless, subuid range), openssh, build toolchain
|
|
- `marvin` user; sshd + turnstone-server started by
|
|
`start-turnstone.sh` (server runs as `marvin`, PID 1)
|
|
|
|
## Build
|
|
|
|
```sh
|
|
scripts/build.sh
|
|
```
|
|
|
|
Creates `configs/ssh/id_ed25519` (ops keypair, once), builds the podman
|
|
image, then packs it as `boco` qcow2 into the boco registry.
|
|
|
|
## Run
|
|
|
|
```sh
|
|
scripts/run.sh
|
|
```
|
|
|
|
- Web UI: <http://localhost:8080> (login `marvin` / `marvin-turnstone`
|
|
unless `ADMIN_PASSWORD` was overridden — change it)
|
|
- Console (admin panel + usage/verdicts/audit/settings): port 8090,
|
|
same login — shares the DB with the server, so workstreams, tools,
|
|
judges, policies, and settings are managed dynamically
|
|
- Ops SSH: `ssh -i configs/ssh/id_ed25519 -p 2222 marvin@localhost`
|
|
- QEMU runs foreground; quit with `ctrl-a x`
|
|
|
|
### Access from another machine (tailcat)
|
|
|
|
`localhost` above is the machine running QEMU. To reach the VM from
|
|
elsewhere, run `scripts/expose.sh` next to it — a tailcat tunnel
|
|
(WireGuard over Tailscale's DERP relays, outbound-only, no account):
|
|
|
|
```
|
|
host $ scripts/expose.sh
|
|
# 🐈 Server listening with saved key "default": tcpGFwWCCH...
|
|
|
|
your $ tailcat forward tcpGFwWCCH... 8080:8080 2222:2222
|
|
```
|
|
|
|
Then browse `http://localhost:8080` / `ssh -p 2222 marvin@localhost`
|
|
from *your* machine. The address is stable across VM restarts (saved
|
|
key) and is a bearer secret — share it only privately.
|
|
|
|
### LLM backend
|
|
|
|
`[models.tensorx]` in `configs/turnstone/config.toml` points at the
|
|
TensorX OpenAI-compatible gateway (`https://api.tensorx.ai/v1`, same
|
|
key this machine's opencode uses — see
|
|
`~/.local/share/opencode/auth.json`). Guest egress to HTTPS endpoints
|
|
works through both slirp layers; no relay needed.
|
|
|
|
Self-hosted provider later: add a `[models.*]` entry with any
|
|
OpenAI-compatible endpoint reachable from the guest (see
|
|
`config.toml.example`).
|
|
|
|
## Shares (virtiofs, survives reboots)
|
|
|
|
| Host path | Guest path | Purpose |
|
|
|------------------------|--------------------------------|--------------------------|
|
|
| `configs/turnstone/` | `/home/marvin/.config/turnstone` | turnstone config.toml |
|
|
| `data/` | `/var/lib/turnstone` | SQLite database |
|
|
| `work/` | `/home/marvin/work` | agent workspace |
|
|
|
|
Guest-written files appear on the host owned by subuid-range UIDs
|
|
(virtiofsd runs under `podman unshare`) — cosmetic, and stable across
|
|
boots.
|
|
|
|
## Configuration
|
|
|
|
Edit `configs/turnstone/config.toml` (gitignored; see
|
|
`config.toml.example`):
|
|
|
|
- `[models.tensorx]` — TensorX gateway; `api_key` must be set
|
|
(currently mirrored from opencode's `auth.json`).
|
|
- `[auth] jwt_secret` — required, generate once.
|
|
- `[database] path` — SQLite on the `data/` share.
|
|
- `[tools]` — `workspace_dir` surfaced to the model
|
|
(`/home/marvin/work`, mounted from `./work`).
|
|
|
|
### Personas
|
|
|
|
Built-ins (Engineer, Writer, Researcher, Scribe, Manager, Executive)
|
|
are seeded by turnstone's migration. This deployment adds a custom
|
|
default persona `marvin` (VM-specific: persistence rules, Gitea remotes,
|
|
podman) via `scripts/persona.py` — the prompt text is versioned there
|
|
and re-applied after a DB reset:
|
|
|
|
```sh
|
|
python3 scripts/persona.py # --db data/turnstone.db default
|
|
```
|
|
|
|
(Without a console deployment, persona CRUD is a direct SQLite write;
|
|
with `turnstone-console` running, use its admin API/Personas tab.)
|
|
|
|
### Skills
|
|
|
|
The 13 skills from the old opencode setup (`~/marvin/configs/opencode/skills`,
|
|
copied into this repo under `skills/`) are imported into turnstone's
|
|
DB-backed catalog via `scripts/import_skills.py`. The script runs
|
|
INSIDE the VM (uses turnstone's storage layer + SKILL.md parser +
|
|
risk scanner):
|
|
|
|
```sh
|
|
scp -O -i configs/ssh/id_ed25519 -P 2222 -r skills scripts/import_skills.py \
|
|
marvin@localhost:/tmp/
|
|
ssh -i configs/ssh/id_ed25519 -p 2222 marvin@localhost \
|
|
/opt/turnstone/bin/python /tmp/import_skills.py --dir /tmp/skills
|
|
```
|
|
|
|
Idempotent (`--update` re-imports + appends version rows). **Reboot the
|
|
VM after importing** — the running server holds a stale WAL snapshot
|
|
and won't see new rows until restart.
|
|
|
|
## Notes / trade-offs
|
|
|
|
- boco boots with `-snapshot`: **only the virtiofs shares persist.**
|
|
Agent-installed packages, git clones outside `work/`, and podman image
|
|
storage inside the VM are discarded on shutdown.
|
|
- boco hardcodes `-smp 8`; memory via `VM_MEMORY=...M scripts/run.sh`.
|
|
- The old setup's Traefik is dropped — hostfwd replaces it.
|