Files
marvin-turnstone/README.md
T
marvin 299e412518 Add scripts/expose.sh: tailcat tunnel for remote access
tailcat (Tailscale data plane, no control plane) forwards the VM's
web UI and SSH ports to another machine via a stable saved-key
address; outbound-only, so it works through the nested slirp NAT.
2026-09-26 16:32:29 +02:00

115 lines
4.5 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)
- 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.
## 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.