Skip to content

Commit b928161

Browse files
committed
feat(panel): add admin panel for docker fleet management
- Extended shared settings to include sizing, lifetime, and cadence parameters (total liquidity, min slice, order size, max orders per side, ttl_secs, tick_interval_secs) with per-pool index support for multi-pool configuration - Added `stitch-panel` binary as a self-hosted fleet admin panel behind the `panel` cargo feature, managing bots via Docker Engine API on a single host - Implemented panel backend with auth (Tailscale identity + optional password), bot lifecycle management (start/stop/restart), settings editing, dry-run and approval runners, and structured logging - Built React + Tailwind SPA frontend embedded into the binary via rust-embed, featuring fleet list with per-bot state, add-bot wizard, settings form with raw TOML editor, log viewer, and SSE-based streaming for logs and one-shot output - Added Dockerfile.panel multi-stage build: node stage builds frontend, rust stage embeds it and builds the binary, runtime is minimal - Created install-panel.sh installer that brings up the panel from published image with one command, prompting for Tailscale setup and tailnet access control - Updated SKILL.md documentation to cover Docker fleet operations with panel and without, including slot-nonce ledger migration guidance - Added CI jobs for panel testing (frontend typecheck + bundle, cargo test with feature) and separate container-panel.yml workflow to avoid coupling frontend and bot image releases - Pinned bot container uid to 1000 so bind-mounted config directories can be owned correctly by the operator or panel - Updated README quick-start to lead with desktop app and Docker+panel options, moving AI agent route to other installation methods
1 parent 630d701 commit b928161

71 files changed

Lines changed: 20850 additions & 130 deletions

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

.claude/skills/stitch/SKILL.md

Lines changed: 63 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -33,7 +33,8 @@ type anything else.
3333

3434
First question — "What do you want to do with Stitch?":
3535

36-
- **Start / resume live**[Start](#start) (cloud: [AWS cloud](#aws-cloud-ecs-fargate))
36+
- **Start / resume live**[Start](#start) (cloud: [AWS cloud](#aws-cloud-ecs-fargate),
37+
Docker fleet: [Docker fleet](#docker-fleet))
3738
- **Stop / pause**[Stop](#stop)
3839
- **Inspect or change it** — status/logs, parameters, approvals, or upgrade
3940

@@ -58,7 +59,7 @@ binary only means "not installed" for the local layouts — the cloud layout has
5859
local binary by design, so don't treat a missing `stitch` as not-installed until
5960
you've ruled out cloud.
6061

61-
Four standard layouts:
62+
Five standard layouts:
6263

6364
- **Foreground / manual** (macOS, foreground Linux, Windows): config lives in
6465
`~/Stitch/` (`%USERPROFILE%\Stitch\` on Windows) — `stitch.toml`, `stitch.key`,
@@ -77,15 +78,21 @@ Four standard layouts:
7778
- **AWS cloud** (ECS Fargate): the operator-owned `deploy/aws` stack. No local
7879
binary — Stitch runs as a one-task ECS service, key + config live in Secrets
7980
Manager. See [AWS cloud](#aws-cloud-ecs-fargate) below.
81+
- **Docker fleet**: several bots as containers on one host, one container per bot,
82+
each with its own config dir and key. Either driven by hand with
83+
`docker compose`, or through `stitch-panel` — a web UI on the same host that
84+
manages the same containers. See [Docker fleet](#docker-fleet) below.
8085

8186
Detect it: for the local layouts, `stitch --version` plus `systemctl status
8287
stitch` (Linux), `launchctl list | grep -i stitch` (macOS), or Task Scheduler
8388
(Windows); a running desktop app shows up as a `stitch-setup` (or `Stitch`)
84-
process supervising a `stitch` child. For cloud, an ECS service named
85-
`<bot>-stitch` in the operator's AWS account (the request itself usually tells you
86-
`aws`/ECS talk means cloud). Operate against whichever is real. Only if it's a
87-
local layout and `stitch` isn't on PATH is it genuinely not installed — then see
88-
[Not installed yet](#not-installed-yet).
89+
process supervising a `stitch` child. For a Docker fleet,
90+
`docker ps --filter ancestor=ghcr.io/textile-protocol/textile-stitch` lists the
91+
bots, and a `stitch-panel` container means the panel is in play. For cloud, an ECS
92+
service named `<bot>-stitch` in the operator's AWS account (the request itself
93+
usually tells you — `aws`/ECS talk means cloud). Operate against whichever is
94+
real. Only if it's a local layout and `stitch` isn't on PATH is it genuinely not
95+
installed — then see [Not installed yet](#not-installed-yet).
8996

9097
## Golden rules — every operation
9198

@@ -221,6 +228,51 @@ you deployed under (default `stitch-operator`; the README example uses
221228
Same golden rules apply: never put the key in a file or on a command line (it's a
222229
Secrets Manager value), and dry-run/pause around any pricing or sizing change.
223230

231+
## Docker fleet
232+
233+
Several bots on one host, one container each. Two ways it's driven, and it matters
234+
which:
235+
236+
- **`stitch-panel` is running** (`docker ps` shows a `stitch-panel` container). The
237+
operator has a web UI at `https://<host>.<tailnet>.ts.net` that does start/stop,
238+
settings, approvals, dry runs and logs. **Point them at it instead of doing it
239+
from the CLI.** Racing the panel isn't dangerous, but it's confusing: the panel
240+
shows container state live, so an operator watching it will see you fight them.
241+
Everything the panel does is also reachable over its API if they'd rather you
242+
drive: `GET /api/bots`, `POST /api/bots/<name>/{start,stop,restart}`,
243+
`PATCH /api/bots/<name>/settings`. The full guide is `docs/install-panel.md`.
244+
If you're restoring a host from the panel's compose export
245+
(`GET /api/compose-export`), note that bots which were stopped when it was
246+
exported carry `profiles: [stopped]` and won't come up on a plain
247+
`docker compose up -d` — that's deliberate, so don't "fix" it. Start one with
248+
`docker compose --profile stopped up -d <bot-name>` if the operator asks.
249+
- **Compose only.** Operate the compose file directly, per bot:
250+
251+
```bash
252+
docker compose ps # which bots exist and their state
253+
docker compose logs -f <service> # tail one bot
254+
docker compose stop <service> # SIGTERM, finishes the tick
255+
docker compose restart <service> # after any stitch.toml edit
256+
docker compose run --rm <service> stitch approve --config /home/stitch/run/stitch.toml
257+
docker compose run --rm <service> stitch --config /home/stitch/run/stitch.toml --dry-run
258+
```
259+
260+
Each bot's config is the `stitch.toml` bind-mounted into it. Find it with
261+
`docker inspect -f '{{range .Mounts}}{{.Source}} -> {{.Destination}}{{"\n"}}{{end}}' <container>`
262+
rather than guessing which `stitch.<name>.toml` belongs to which service. Edit
263+
that file on the host, then restart just that container — the usual dry-run rule
264+
applies for pricing or sizing changes.
265+
266+
**One trap worth knowing.** If a bot's compose service mounts `stitch.toml` and
267+
the key as two individual files rather than mounting a whole directory, the Permit2
268+
slot-nonce ledger lives on the container filesystem and is destroyed by
269+
`docker compose up -d --force-recreate` or an image bump. The bot then restarts its
270+
nonce sequence and the orders it signs collide with ones still resting on the book.
271+
Tell the operator before you recreate anything in that layout. The panel detects it
272+
and offers a one-click migration; by hand it means moving the two files into a
273+
per-bot directory and mounting that directory read-write, with the config and key
274+
remounted read-only on top.
275+
224276
## Not installed yet
225277

226278
If `stitch` isn't on PATH, install it first. Don't reconstruct the steps from
@@ -239,6 +291,10 @@ Two setup paths that don't need you to drive it, if the operator prefers:
239291
- **Desktop app** (no terminal): download the release for their OS and open Stitch
240292
— macOS `Stitch.dmg`, Windows `stitch-setup.exe`, Linux `stitch-setup`. Pick a
241293
corridor, paste the operator key, click Create; the same window then runs the bot.
294+
- **Admin panel** (several bots on one Docker host): `docs/install-panel.md` brings
295+
up `stitch-panel` behind Tailscale, and the operator adds bots from a wizard in
296+
the browser. This is the right answer when they say "I want to run more than one
297+
bot" or already have a compose file they're tired of editing.
242298
- **`stitch init`** (built-in wizard): `cd ~/Stitch && stitch init` picks a
243299
corridor from the catalog (cNGN/USDT on BSC, XAUt/USDT on Ethereum, wARS/USDT and
244300
wBRL/USDT on Celo), prompts for the key (hidden), and writes `stitch.toml`,

.codex/skills/stitch/SKILL.md

Lines changed: 63 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -33,7 +33,8 @@ the operator can type anything else.
3333

3434
First question — "What do you want to do with Stitch?":
3535

36-
- **Start / resume live**[Start](#start) (cloud: [AWS cloud](#aws-cloud-ecs-fargate))
36+
- **Start / resume live**[Start](#start) (cloud: [AWS cloud](#aws-cloud-ecs-fargate),
37+
Docker fleet: [Docker fleet](#docker-fleet))
3738
- **Stop / pause**[Stop](#stop)
3839
- **Inspect or change it** — status/logs, parameters, approvals, or upgrade
3940

@@ -58,7 +59,7 @@ binary only means "not installed" for the local layouts — the cloud layout has
5859
local binary by design, so don't treat a missing `stitch` as not-installed until
5960
you've ruled out cloud.
6061

61-
Four standard layouts:
62+
Five standard layouts:
6263

6364
- **Foreground / manual** (macOS, foreground Linux, Windows): config lives in
6465
`~/Stitch/` (`%USERPROFILE%\Stitch\` on Windows) — `stitch.toml`, `stitch.key`,
@@ -77,15 +78,21 @@ Four standard layouts:
7778
- **AWS cloud** (ECS Fargate): the operator-owned `deploy/aws` stack. No local
7879
binary — Stitch runs as a one-task ECS service, key + config live in Secrets
7980
Manager. See [AWS cloud](#aws-cloud-ecs-fargate) below.
81+
- **Docker fleet**: several bots as containers on one host, one container per bot,
82+
each with its own config dir and key. Either driven by hand with
83+
`docker compose`, or through `stitch-panel` — a web UI on the same host that
84+
manages the same containers. See [Docker fleet](#docker-fleet) below.
8085

8186
Detect it: for the local layouts, `stitch --version` plus `systemctl status
8287
stitch` (Linux), `launchctl list | grep -i stitch` (macOS), or Task Scheduler
8388
(Windows); a running desktop app shows up as a `stitch-setup` (or `Stitch`)
84-
process supervising a `stitch` child. For cloud, an ECS service named
85-
`<bot>-stitch` in the operator's AWS account (the request itself usually tells you
86-
`aws`/ECS talk means cloud). Operate against whichever is real. Only if it's a
87-
local layout and `stitch` isn't on PATH is it genuinely not installed — then see
88-
[Not installed yet](#not-installed-yet).
89+
process supervising a `stitch` child. For a Docker fleet,
90+
`docker ps --filter ancestor=ghcr.io/textile-protocol/textile-stitch` lists the
91+
bots, and a `stitch-panel` container means the panel is in play. For cloud, an ECS
92+
service named `<bot>-stitch` in the operator's AWS account (the request itself
93+
usually tells you — `aws`/ECS talk means cloud). Operate against whichever is
94+
real. Only if it's a local layout and `stitch` isn't on PATH is it genuinely not
95+
installed — then see [Not installed yet](#not-installed-yet).
8996

9097
## Golden rules — every operation
9198

@@ -221,6 +228,51 @@ you deployed under (default `stitch-operator`; the README example uses
221228
Same golden rules apply: never put the key in a file or on a command line (it's a
222229
Secrets Manager value), and dry-run/pause around any pricing or sizing change.
223230

231+
## Docker fleet
232+
233+
Several bots on one host, one container each. Two ways it's driven, and it matters
234+
which:
235+
236+
- **`stitch-panel` is running** (`docker ps` shows a `stitch-panel` container). The
237+
operator has a web UI at `https://<host>.<tailnet>.ts.net` that does start/stop,
238+
settings, approvals, dry runs and logs. **Point them at it instead of doing it
239+
from the CLI.** Racing the panel isn't dangerous, but it's confusing: the panel
240+
shows container state live, so an operator watching it will see you fight them.
241+
Everything the panel does is also reachable over its API if they'd rather you
242+
drive: `GET /api/bots`, `POST /api/bots/<name>/{start,stop,restart}`,
243+
`PATCH /api/bots/<name>/settings`. The full guide is `docs/install-panel.md`.
244+
If you're restoring a host from the panel's compose export
245+
(`GET /api/compose-export`), note that bots which were stopped when it was
246+
exported carry `profiles: [stopped]` and won't come up on a plain
247+
`docker compose up -d` — that's deliberate, so don't "fix" it. Start one with
248+
`docker compose --profile stopped up -d <bot-name>` if the operator asks.
249+
- **Compose only.** Operate the compose file directly, per bot:
250+
251+
```bash
252+
docker compose ps # which bots exist and their state
253+
docker compose logs -f <service> # tail one bot
254+
docker compose stop <service> # SIGTERM, finishes the tick
255+
docker compose restart <service> # after any stitch.toml edit
256+
docker compose run --rm <service> stitch approve --config /home/stitch/run/stitch.toml
257+
docker compose run --rm <service> stitch --config /home/stitch/run/stitch.toml --dry-run
258+
```
259+
260+
Each bot's config is the `stitch.toml` bind-mounted into it. Find it with
261+
`docker inspect -f '{{range .Mounts}}{{.Source}} -> {{.Destination}}{{"\n"}}{{end}}' <container>`
262+
rather than guessing which `stitch.<name>.toml` belongs to which service. Edit
263+
that file on the host, then restart just that container — the usual dry-run rule
264+
applies for pricing or sizing changes.
265+
266+
**One trap worth knowing.** If a bot's compose service mounts `stitch.toml` and
267+
the key as two individual files rather than mounting a whole directory, the Permit2
268+
slot-nonce ledger lives on the container filesystem and is destroyed by
269+
`docker compose up -d --force-recreate` or an image bump. The bot then restarts its
270+
nonce sequence and the orders it signs collide with ones still resting on the book.
271+
Tell the operator before you recreate anything in that layout. The panel detects it
272+
and offers a one-click migration; by hand it means moving the two files into a
273+
per-bot directory and mounting that directory read-write, with the config and key
274+
remounted read-only on top.
275+
224276
## Not installed yet
225277

226278
If `stitch` isn't on PATH, install it first. Don't reconstruct the steps from
@@ -239,6 +291,10 @@ Two setup paths that don't need you to drive it, if the operator prefers:
239291
- **Desktop app** (no terminal): download the release for their OS and open Stitch
240292
— macOS `Stitch.dmg`, Windows `stitch-setup.exe`, Linux `stitch-setup`. Pick a
241293
corridor, paste the operator key, click Create; the same window then runs the bot.
294+
- **Admin panel** (several bots on one Docker host): `docs/install-panel.md` brings
295+
up `stitch-panel` behind Tailscale, and the operator adds bots from a wizard in
296+
the browser. This is the right answer when they say "I want to run more than one
297+
bot" or already have a compose file they're tired of editing.
242298
- **`stitch init`** (built-in wizard): `cd ~/Stitch && stitch init` picks a
243299
corridor from the catalog (cNGN/USDT on BSC, XAUt/USDT on Ethereum, wARS/USDT and
244300
wBRL/USDT on Celo), prompts for the key (hidden), and writes `stitch.toml`,

.dockerignore

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,3 +6,9 @@ stitch.key
66
stitch.env
77
deploy/aws/.terraform
88
deploy/aws/terraform.tfstate*
9+
10+
# The panel's frontend is built inside the image's node stage. Shipping a local
11+
# node_modules into the build context would be slow and, on a different host
12+
# arch, wrong; a local dist would silently override what the stage just built.
13+
web/node_modules
14+
web/dist

.github/workflows/ci.yml

Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -36,3 +36,38 @@ jobs:
3636
with:
3737
name: stitch-linux-x86_64-${{ github.sha }}
3838
path: target/release/stitch
39+
40+
# The admin panel is behind a cargo feature, so the job above never compiles it.
41+
# Its own job keeps the bot's CI fast while still gating the panel's tests and
42+
# the frontend typecheck.
43+
panel:
44+
runs-on: ubuntu-latest
45+
46+
steps:
47+
- uses: actions/checkout@v4
48+
49+
- uses: dtolnay/rust-toolchain@stable
50+
51+
- uses: Swatinem/rust-cache@v2
52+
with:
53+
key: panel
54+
55+
- uses: actions/setup-node@v4
56+
with:
57+
node-version: 22
58+
cache: npm
59+
cache-dependency-path: web/package-lock.json
60+
61+
# Typecheck and bundle first: cargo embeds whatever is in web/dist, so
62+
# building the frontend here also proves the embed path works.
63+
- name: Build the frontend
64+
working-directory: web
65+
run: |
66+
npm ci --no-audit --no-fund
67+
npm run build
68+
69+
- name: Run panel tests
70+
run: cargo test --features panel
71+
72+
- name: Build the panel binary
73+
run: cargo build --release --features panel --bin stitch-panel
Lines changed: 78 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,78 @@
1+
name: Container (panel)
2+
3+
# The admin panel image. Separate from the bot image so a frontend change doesn't
4+
# rebuild and republish the bot, and so a broken panel build can never hold up a
5+
# bot release.
6+
7+
on:
8+
pull_request:
9+
paths:
10+
- '.github/workflows/container-panel.yml'
11+
- 'Cargo.lock'
12+
- 'Cargo.toml'
13+
- 'Dockerfile.panel'
14+
- '.dockerignore'
15+
- 'src/**'
16+
- 'web/**'
17+
push:
18+
branches: [main]
19+
paths:
20+
- '.github/workflows/container-panel.yml'
21+
- 'Cargo.lock'
22+
- 'Cargo.toml'
23+
- 'Dockerfile.panel'
24+
- '.dockerignore'
25+
- 'src/**'
26+
- 'web/**'
27+
workflow_dispatch:
28+
29+
permissions:
30+
contents: read
31+
packages: write
32+
33+
concurrency:
34+
group: container-panel-${{ github.ref }}
35+
cancel-in-progress: true
36+
37+
jobs:
38+
build:
39+
runs-on: ubuntu-22.04
40+
steps:
41+
- uses: actions/checkout@v6
42+
with:
43+
persist-credentials: false
44+
45+
- uses: docker/setup-buildx-action@v4
46+
47+
- name: Log in to GitHub Container Registry
48+
if: github.event_name != 'pull_request'
49+
uses: docker/login-action@v4
50+
with:
51+
registry: ghcr.io
52+
username: ${{ github.actor }}
53+
password: ${{ secrets.GITHUB_TOKEN }}
54+
55+
- name: Extract Docker metadata
56+
id: meta
57+
uses: docker/metadata-action@v6
58+
with:
59+
images: ghcr.io/textile-protocol/textile-stitch-panel
60+
tags: |
61+
type=sha,prefix=sha-
62+
type=raw,value=latest,enable={{is_default_branch}}
63+
type=ref,event=branch
64+
type=ref,event=pr
65+
66+
- name: Build container image
67+
uses: docker/build-push-action@v6
68+
with:
69+
context: .
70+
file: Dockerfile.panel
71+
push: ${{ github.event_name != 'pull_request' }}
72+
tags: ${{ steps.meta.outputs.tags }}
73+
labels: ${{ steps.meta.outputs.labels }}
74+
cache-from: type=gha,scope=panel
75+
# Only write the shared build cache from trusted (non-PR) runs. A PR can
76+
# run arbitrary code during the build; letting it populate the gha cache
77+
# would let it poison a layer later consumed by the main publish build.
78+
cache-to: ${{ github.event_name != 'pull_request' && 'type=gha,mode=max,scope=panel' || '' }}

.textile-monorepo-source

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1 +1 @@
1-
1fa5615dd2f9d1d26d8a9a1ea5eb7609e2fd5269
1+
424fb335f7766039d3e299876d65a2f1ee9e35a2

.textile-stitch-release-version

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1 +1 @@
1-
0.1.106
1+
0.1.107

0 commit comments

Comments
 (0)