diff --git a/README.md b/README.md index b8ef68a..7fc4400 100644 --- a/README.md +++ b/README.md @@ -1,38 +1,20 @@ # 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`). +Local turnstone deployment as a [boco](https://git.nubo.sh/hulthe/boco) **container-VM**. -``` -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) -``` + 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 ## 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) +- `podman`, `qemu-base`, `virtiofsd`, `boco` ## Build @@ -49,41 +31,11 @@ image, then packs it as `boco` qcow2 into the boco registry. scripts/run.sh ``` -- Console (proxy to the node; chat UI + admin panel): :8080 — login - `marvin` / `marvin-turnstone` unless `ADMIN_PASSWORD` was overridden. - The node binds :8081 guest-internal only (no host forward); the - console routes to it, so all chat/admin goes through one port. -- 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. +This exposes the turnstone console on `:8080`, and ssh on `:2222` ### 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`). +Set up `configs/turnstone/config.toml` with an OpenAI-compatible endpoint. ## Shares (virtiofs, survives reboots) @@ -93,29 +45,26 @@ OpenAI-compatible endpoint reachable from the guest (see | `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. +Note that virtiofsd runs under `podman unshare`, which means that files +written by the guest-VM will be owned by a subuid-range. ## Configuration -Edit `configs/turnstone/config.toml` (gitignored; see -`config.toml.example`): +Edit `configs/turnstone/config.toml` (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`). +- `[models.]` - LLM provider; `api_key` must be set. +- `[auth] jwt_secret` - required, generate once. +- `[database] path` - SQLite on the `data/` share. +- `[tools]` - `workspace_dir` surfaced to the model. ### 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: +Turnstone offers a few built-in personas (Engineer, Writer, Researcher, +Scribe, Manager, Executive), which 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 @@ -126,11 +75,9 @@ 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): +Skills are checked in under `skills` in the `SKILL.md format.` +These are imported into turnstone's DB-backed catalog via +`scripts/import_skills.py`. The script runs inside the VM. ```sh scp -O -i configs/ssh/id_ed25519 -P 2222 -r skills scripts/import_skills.py \ @@ -140,13 +87,5 @@ ssh -i configs/ssh/id_ed25519 -p 2222 marvin@localhost \ ``` Idempotent (`--update` re-imports + appends version rows). **Reboot the -VM after importing** — the running server holds a stale WAL snapshot +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. diff --git a/skills/gitea/SKILL.md b/skills/gitea/SKILL.md index 89dbd23..d9ab28f 100644 --- a/skills/gitea/SKILL.md +++ b/skills/gitea/SKILL.md @@ -34,7 +34,7 @@ Use the `tea` command to interact with Gitea. - **Communication**: Use `tea comment "message"` to communicate with project maintainers (e.g., `@hulthe`). ## Responsibility -- **Don't Drop the Ball**: When assigned a task or receiving a review comment, you MUST address it before moving on to other things. Ensure all follow-up actions ( (like replying to a comment) are completed. +- **Don't Drop the Ball**: When assigned a task or receiving a review comment, you MUST address it before moving on to other things. Ensure all follow-up actions (like replying to a comment) are completed. ## Tips for Agents - **Stay Updated**: Regularly run `tea notifications` to check for new issues or PR reviews. diff --git a/start-turnstone.sh b/start-turnstone.sh index 6d10ca5..ea06999 100755 --- a/start-turnstone.sh +++ b/start-turnstone.sh @@ -6,11 +6,9 @@ set -euo pipefail export HOME=/home/marvin -# The boco kernel boots with hostname "(none)" (no systemd to read -# /etc/hostname) — set it live so turnstone node IDs and service URLs -# are sane (turnstone-vm_xxxx instead of (none)_xxxx). The Arch base -# has no `hostname` binary, so use the syscall via python. -python3 -c 'import socket; socket.sethostname("turnstone-vm")' || true +# Set hostname here since podman prevents us from setting it during +# build.-time. Turnstone requires a hostname to be set. +python3 -c 'import socket; socket.sethostname("marvin-vm")' || true # sshd: host keys baked per-boot, ops channel on guest port 22 ssh-keygen -A