catence 0.2.0-beta.2 → 0.2.0-beta.21
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +28 -82
- package/config.example.json +25 -0
- package/dist/contracts/index.d.ts +1 -0
- package/dist/contracts/index.js +1 -0
- package/dist/contracts/progress.d.ts +23 -0
- package/dist/contracts/progress.js +49 -0
- package/dist/contracts/release.d.ts +1 -1
- package/dist/contracts/release.js +1 -1
- package/dist/core/logging.d.ts +16 -0
- package/dist/core/logging.js +68 -0
- package/dist/core/query/repository.d.ts +2 -0
- package/dist/core/query/repository.js +48 -0
- package/dist/core/runtime/catalog.js +17 -7
- package/dist/core/runtime/configuration.d.ts +3 -1
- package/dist/core/runtime/configuration.js +5 -1
- package/dist/core/runtime/opencode-go.d.ts +58 -0
- package/dist/core/runtime/opencode-go.js +246 -0
- package/dist/core/runtime/secrets.js +51 -13
- package/dist/core/runtime/self-update.d.ts +44 -0
- package/dist/core/runtime/self-update.js +138 -0
- package/dist/elt/application/detached-sync.d.ts +50 -0
- package/dist/elt/application/detached-sync.js +99 -0
- package/dist/elt/application/management.d.ts +15 -0
- package/dist/elt/application/management.js +435 -19
- package/dist/elt/ingestion/importer.d.ts +3 -2
- package/dist/elt/ingestion/importer.js +5 -4
- package/dist/elt/ingestion/providers/intervals.d.ts +12 -1
- package/dist/elt/ingestion/providers/intervals.js +37 -2
- package/dist/elt/ingestion/providers/registry.d.ts +13 -0
- package/dist/elt/ingestion/providers/registry.js +18 -1
- package/dist/elt/ingestion/providers/strava/service.js +7 -1
- package/dist/elt/normalization/normalizers.js +111 -15
- package/dist/elt/storage/database.d.ts +21 -2
- package/dist/elt/storage/database.js +184 -6
- package/dist/elt/storage/migrations.js +25 -1
- package/dist/elt/storage/progress-sidecar.d.ts +61 -0
- package/dist/elt/storage/progress-sidecar.js +200 -0
- package/dist/interfaces/cli/commands.js +116 -1
- package/dist/interfaces/http/generation-status.d.ts +19 -0
- package/dist/interfaces/http/generation-status.js +55 -0
- package/dist/interfaces/http/server.d.ts +7 -1
- package/dist/interfaces/http/server.js +184 -1
- package/dist/interfaces/mcp/server.d.ts +2 -1
- package/dist/interfaces/mcp/server.js +50 -13
- package/dist/runtime/index.d.ts +8 -1
- package/dist/runtime/index.js +4 -1
- package/docs/architecture.md +11 -1
- package/docs/configuration.md +243 -0
- package/docs/deployment/docker.md +528 -0
- package/docs/deployment/local-console.md +263 -0
- package/docs/deployment/local-mcp.md +329 -0
- package/docs/development.md +337 -0
- package/docs/distribution.md +58 -0
- package/docs/llm-providers.md +249 -6
- package/docs/mcp-activity-retrieval.md +37 -30
- package/package.json +5 -1
- package/pyproject.toml +1 -1
- package/python/catence/providers/garmin/cli.py +43 -9
- package/python/catence/providers/garmin/progress.py +85 -0
- package/python/catence/providers/garmin/registry.py +1 -0
- package/python/catence/providers/garmin/streams.py +38 -0
- package/python/catence/providers/garmin/swim_backfill.py +83 -0
- package/python/catence/providers/garmin/worker.py +372 -11
- package/scripts/discover-opencode-go.mjs +102 -0
- package/scripts/discover-opencode-zen.mjs +218 -0
- package/uv.lock +3 -3
|
@@ -0,0 +1,528 @@
|
|
|
1
|
+
# Docker deployment
|
|
2
|
+
|
|
3
|
+
This guide runs the full Catence stack — runtime, MCP server, and Console — in
|
|
4
|
+
one container built from the public registries. It is the single deployment
|
|
5
|
+
path for a remote or always-on setup and covers the same Console, data
|
|
6
|
+
directory layout, model configuration, and credentials model as the
|
|
7
|
+
[local path](local-console.md).
|
|
8
|
+
|
|
9
|
+
Everything below assumes a recent beta or stable release; channels are
|
|
10
|
+
explained in the [beta release notes](../../README.md#beta-releases).
|
|
11
|
+
|
|
12
|
+
## Prerequisites
|
|
13
|
+
|
|
14
|
+
- Docker Engine with the `docker compose` plugin.
|
|
15
|
+
- Provider credentials for the athletes you choose to sync (Garmin,
|
|
16
|
+
Intervals.icu, Strava).
|
|
17
|
+
- One model-provider key (OpenAI, Anthropic, OpenCode Go, Azure, or any
|
|
18
|
+
OpenAI-compatible endpoint).
|
|
19
|
+
|
|
20
|
+
## What gets stored where
|
|
21
|
+
|
|
22
|
+
| What | Where | Example |
|
|
23
|
+
| --- | --- | --- |
|
|
24
|
+
| Catalog + athlete stores | data home | `/data` (volume or bind mount) |
|
|
25
|
+
| Model profiles (no secrets) | `config.json` in the data home | `console.profiles` |
|
|
26
|
+
| Provider credentials | per-athlete secret file, mode 0600 | `catence-data secret set` |
|
|
27
|
+
| API keys | process environment only | `.env` |
|
|
28
|
+
| Chat history + preferences | data home | `console/chat-history.sqlite3` |
|
|
29
|
+
|
|
30
|
+
Credentials never land in `config.json`; profiles reference environment
|
|
31
|
+
variable *names* (`apiKeyEnv`, `apiBaseEnv`, `apiVersionEnv`). See
|
|
32
|
+
[`llm-providers.md`](../llm-providers.md) for the profile reference and
|
|
33
|
+
[`configuration.md`](../configuration.md) for the complete `config.json`
|
|
34
|
+
schema.
|
|
35
|
+
|
|
36
|
+
## 1. Deploy with the script
|
|
37
|
+
|
|
38
|
+
```sh
|
|
39
|
+
curl -fsSL https://raw.githubusercontent.com/rifusaki/catence/beta/scripts/deploy-console.sh | bash -s beta
|
|
40
|
+
# or from a checkout: ./scripts/deploy-console.sh beta
|
|
41
|
+
# use `stable` instead of `beta` for the stable channel
|
|
42
|
+
```
|
|
43
|
+
|
|
44
|
+
The script resolves the newest matching versions from npm and PyPI, writes a
|
|
45
|
+
self-contained `catence-deploy/` project (Dockerfile, docker-compose.yml, and
|
|
46
|
+
`.env` with placeholders), builds the image, **seeds a starter
|
|
47
|
+
`/data/config.json`** into the data volume, and starts the stack — or, when
|
|
48
|
+
secrets are missing, prints the remaining steps and exits.
|
|
49
|
+
|
|
50
|
+
Script options (run `./deploy-console.sh --help` for the full list):
|
|
51
|
+
|
|
52
|
+
| Option | Default | Meaning |
|
|
53
|
+
| --- | --- | --- |
|
|
54
|
+
| `--channel stable\|beta` | — | Release channel (also the first positional argument) |
|
|
55
|
+
| `--port` | — | Console UI host port |
|
|
56
|
+
| `--bind` | `127.0.0.1` | Console UI bind address on the host |
|
|
57
|
+
| `--mcp-bind` | `127.0.0.1` | MCP server bind (also sets the compose port mapping; see [MCP exposure](#connecting-to-the-docker-based-mcp-from-outside)) |
|
|
58
|
+
| `--dir` | `catence-deploy` | Output directory for the generated scaffold |
|
|
59
|
+
| `--username` | — | Console username written into `.env` |
|
|
60
|
+
| `--password-hash` | — | bcrypt hash for `CATENCE_CONSOLE_PASSWORD_HASH` |
|
|
61
|
+
| `--auth-secret` | — | JWT secret for `CHAINLIT_AUTH_SECRET` |
|
|
62
|
+
| `--home <dir>` | — | Host directory bind-mounted to `/data` instead of a named volume |
|
|
63
|
+
| `--volume` | — | Named volume name (default `catence`/`catence-beta`) |
|
|
64
|
+
| `--env-file` | — | Use an existing `.env` instead of writing a new one |
|
|
65
|
+
| `--npm-version` / `--console-version` | — | Pin exact versions instead of resolving the channel |
|
|
66
|
+
| `--no-build` / `--no-pull` | — | Skip image build / base image pull |
|
|
67
|
+
| `--generate-secrets` | — | Interactively prompt for the password and write the bcrypt hash to `.env` |
|
|
68
|
+
| `--dry-run` | — | Print what would be written without changing anything |
|
|
69
|
+
|
|
70
|
+
## 2. Fill in `catence-deploy/.env`
|
|
71
|
+
|
|
72
|
+
```sh
|
|
73
|
+
CATENCE_CONSOLE_USERNAME=coach
|
|
74
|
+
CATENCE_CONSOLE_PASSWORD_HASH='…' # generate: ./catence-deploy/hash-password.sh
|
|
75
|
+
CHAINLIT_AUTH_SECRET='…' # generate: openssl rand -hex 32
|
|
76
|
+
OPENAI_API_KEY='…' # or ANTHROPIC_API_KEY / OPENCODE_GO_API_KEY …
|
|
77
|
+
OPENAI_API_BASE='' # only for OpenAI-compatible endpoints
|
|
78
|
+
OPENCODE_GO_API_KEY='…' # the OpenCode Go base URLs are pre-filled below
|
|
79
|
+
OPENCODE_GO_API_BASE='https://opencode.ai/zen/go/v1'
|
|
80
|
+
OPENCODE_GO_MESSAGES_API_BASE='https://opencode.ai/zen/go'
|
|
81
|
+
```
|
|
82
|
+
|
|
83
|
+
Generate the Console login hash with the scaffold helper (it prompts on the
|
|
84
|
+
terminal, never echoes, and uses the compose project so it works whatever image
|
|
85
|
+
tag the script built):
|
|
86
|
+
|
|
87
|
+
```sh
|
|
88
|
+
./catence-deploy/hash-password.sh
|
|
89
|
+
```
|
|
90
|
+
|
|
91
|
+
Or re-run the deploy script with `--generate-secrets` and answer the
|
|
92
|
+
interactive prompt. Without the helper (for example an older deployment), the
|
|
93
|
+
equivalent command is:
|
|
94
|
+
|
|
95
|
+
```sh
|
|
96
|
+
docker run --rm -it --entrypoint /opt/catence-console/bin/catence-console <image-tag> auth hash-password
|
|
97
|
+
```
|
|
98
|
+
|
|
99
|
+
Then start the stack (or re-run the script, which re-reads the saved `.env`):
|
|
100
|
+
|
|
101
|
+
```sh
|
|
102
|
+
docker compose -f catence-deploy/docker-compose.yml --env-file catence-deploy/.env up -d
|
|
103
|
+
```
|
|
104
|
+
|
|
105
|
+
## 3. Initialize an athlete store
|
|
106
|
+
|
|
107
|
+
The data volume already contains a seeded `config.json`, so `setup` accepts
|
|
108
|
+
it. Run through the container:
|
|
109
|
+
|
|
110
|
+
```sh
|
|
111
|
+
docker compose -f catence-deploy/docker-compose.yml --env-file catence-deploy/.env run --rm \
|
|
112
|
+
--entrypoint catence-data console setup --athlete alex --label "Alex"
|
|
113
|
+
```
|
|
114
|
+
|
|
115
|
+
## 4. Store provider credentials
|
|
116
|
+
|
|
117
|
+
Garmin (`email`, `password`), Intervals.icu (`apiKey`, `athleteId`), and Strava
|
|
118
|
+
(`clientId`, `clientSecret`), one `printf` per field:
|
|
119
|
+
|
|
120
|
+
```sh
|
|
121
|
+
# Garmin
|
|
122
|
+
printf %s 'alex@example.com' | docker compose -f catence-deploy/docker-compose.yml --env-file catence-deploy/.env run --rm -T \
|
|
123
|
+
--entrypoint catence-data console --athlete alex secret set \
|
|
124
|
+
--provider garmin --field email --value-stdin
|
|
125
|
+
printf %s 'your-garmin-password' | docker compose -f catence-deploy/docker-compose.yml --env-file catence-deploy/.env run --rm -T \
|
|
126
|
+
--entrypoint catence-data console --athlete alex secret set \
|
|
127
|
+
--provider garmin --field password --value-stdin
|
|
128
|
+
# Intervals.icu
|
|
129
|
+
printf %s 'intervals-api-key' | docker compose -f catence-deploy/docker-compose.yml --env-file catence-deploy/.env run --rm -T \
|
|
130
|
+
--entrypoint catence-data console --athlete alex secret set \
|
|
131
|
+
--provider intervals --field apiKey --value-stdin
|
|
132
|
+
printf %s '12345' | docker compose -f catence-deploy/docker-compose.yml --env-file catence-deploy/.env run --rm -T \
|
|
133
|
+
--entrypoint catence-data console --athlete alex secret set \
|
|
134
|
+
--provider intervals --field athleteId --value-stdin
|
|
135
|
+
# Strava (create an API application at https://www.strava.com/settings/api)
|
|
136
|
+
printf %s 'strava-client-id' | docker compose -f catence-deploy/docker-compose.yml --env-file catence-deploy/.env run --rm -T \
|
|
137
|
+
--entrypoint catence-data console --athlete alex secret set \
|
|
138
|
+
--provider strava --field clientId --value-stdin
|
|
139
|
+
printf %s 'strava-client-secret' | docker compose -f catence-deploy/docker-compose.yml --env-file catence-deploy/.env run --rm -T \
|
|
140
|
+
--entrypoint catence-data console --athlete alex secret set \
|
|
141
|
+
--provider strava --field clientSecret --value-stdin
|
|
142
|
+
```
|
|
143
|
+
|
|
144
|
+
Set only the providers you actually sync. Use the same `--entrypoint
|
|
145
|
+
catence-data` pattern for `sync`, `backfill`, and `auth strava`.
|
|
146
|
+
|
|
147
|
+
## 5. Sync data and build retrieval context
|
|
148
|
+
|
|
149
|
+
```sh
|
|
150
|
+
docker compose -f catence-deploy/docker-compose.yml --env-file catence-deploy/.env exec console \
|
|
151
|
+
catence-data sync --athlete alex --provider all --home /data
|
|
152
|
+
docker compose -f catence-deploy/docker-compose.yml --env-file catence-deploy/.env exec console \
|
|
153
|
+
catence-data build-retrieval-index --athlete alex --home /data
|
|
154
|
+
```
|
|
155
|
+
|
|
156
|
+
## 6. Configure the model
|
|
157
|
+
|
|
158
|
+
The seeded `config.json` already offers OpenAI, OpenAI-compatible, and
|
|
159
|
+
Anthropic starter profiles, so the settings panel works before the first chat.
|
|
160
|
+
Edit `/data/config.json` inside the volume:
|
|
161
|
+
|
|
162
|
+
```sh
|
|
163
|
+
docker compose -f catence-deploy/docker-compose.yml --env-file catence-deploy/.env run --rm \
|
|
164
|
+
--entrypoint sh console -c 'cat > /data/config.json' # paste JSON, then Ctrl-D
|
|
165
|
+
```
|
|
166
|
+
|
|
167
|
+
With `--home <dir>` (bind mount), edit `<dir>/config.json` on the host
|
|
168
|
+
instead. To add OpenCode Go models from the image (the script ships inside the
|
|
169
|
+
npm package):
|
|
170
|
+
|
|
171
|
+
```sh
|
|
172
|
+
docker compose -f catence-deploy/docker-compose.yml --env-file catence-deploy/.env run --rm \
|
|
173
|
+
--entrypoint node console \
|
|
174
|
+
/usr/local/lib/node_modules/catence/scripts/discover-opencode-go.mjs --write /data/config.json
|
|
175
|
+
```
|
|
176
|
+
|
|
177
|
+
The generated `.env` already contains `OPENCODE_GO_API_BASE` and
|
|
178
|
+
`OPENCODE_GO_MESSAGES_API_BASE`, so on Docker only the key is missing:
|
|
179
|
+
|
|
180
|
+
```sh
|
|
181
|
+
export OPENCODE_GO_API_KEY='…' # any non-empty value passes the key check
|
|
182
|
+
```
|
|
183
|
+
|
|
184
|
+
## 7. Access the Console
|
|
185
|
+
|
|
186
|
+
Open `http://127.0.0.1:8000` (or the `--bind:--port` you configured) and log in
|
|
187
|
+
with `CATENCE_CONSOLE_USERNAME` and the password whose hash is in
|
|
188
|
+
`CATENCE_CONSOLE_PASSWORD_HASH`.
|
|
189
|
+
|
|
190
|
+
## 8. Verify with doctor
|
|
191
|
+
|
|
192
|
+
`catence-console doctor` checks each profile's environment variables and the
|
|
193
|
+
local Catence runtime. The deploy scaffold includes a helper for it:
|
|
194
|
+
|
|
195
|
+
```sh
|
|
196
|
+
./catence-deploy/doctor.sh
|
|
197
|
+
```
|
|
198
|
+
|
|
199
|
+
The helper runs the doctor *inside the running container* with `exec` — the
|
|
200
|
+
container's own loopback holds the live MCP server at `127.0.0.1:8787`, and the
|
|
201
|
+
provider keys from `.env` are already in its environment. (A `docker compose
|
|
202
|
+
run` container has a separate loopback and would report the runtime as
|
|
203
|
+
unreachable.) The equivalent long form is:
|
|
204
|
+
|
|
205
|
+
```sh
|
|
206
|
+
docker compose -f catence-deploy/docker-compose.yml --env-file catence-deploy/.env exec console \
|
|
207
|
+
/opt/catence-console/bin/catence-console doctor --home /data --mcp-url http://127.0.0.1:8787/mcp
|
|
208
|
+
```
|
|
209
|
+
|
|
210
|
+
Expect `"ready": true` once the model key is set; missing keys show up per
|
|
211
|
+
profile without ever printing their values.
|
|
212
|
+
|
|
213
|
+
## Common operations
|
|
214
|
+
|
|
215
|
+
The `sync.sh` helper in `catence-deploy/` wraps the most frequent
|
|
216
|
+
`catence-data` calls inside the **running** container (so the provider secrets
|
|
217
|
+
from the data volume are already available):
|
|
218
|
+
|
|
219
|
+
```sh
|
|
220
|
+
# Incremental sync for one athlete (all providers)
|
|
221
|
+
./sync.sh --athlete alex sync
|
|
222
|
+
|
|
223
|
+
# Sync a specific provider
|
|
224
|
+
./sync.sh --athlete alex sync --provider intervals
|
|
225
|
+
|
|
226
|
+
# Sync from a specific date (Garmin)
|
|
227
|
+
./sync.sh --athlete alex sync --provider garmin --from 2025-07-29
|
|
228
|
+
|
|
229
|
+
# Full backfill from a date
|
|
230
|
+
./sync.sh --athlete alex backfill 2026-01-01
|
|
231
|
+
|
|
232
|
+
# Backfill with refresh (re-fetch existing data)
|
|
233
|
+
./sync.sh --athlete alex backfill 2026-01-01 --refresh
|
|
234
|
+
|
|
235
|
+
# Retry a failed sync run
|
|
236
|
+
./sync.sh --athlete alex retry --run <run-id>
|
|
237
|
+
|
|
238
|
+
# Coverage and error status
|
|
239
|
+
./sync.sh --athlete alex status
|
|
240
|
+
|
|
241
|
+
# List athlete IDs in the catalog
|
|
242
|
+
./sync.sh athletes
|
|
243
|
+
```
|
|
244
|
+
|
|
245
|
+
`sync` defaults to `--provider all`; extra flags pass through. Set a default
|
|
246
|
+
athlete with `export CATENCE_ATHLETE=alex` to drop the `--athlete` argument.
|
|
247
|
+
`--detach` starts a sync or backfill in the background and returns immediately;
|
|
248
|
+
a detached run keeps streaming into the container logs (`docker compose -f
|
|
249
|
+
catence-deploy/docker-compose.yml logs -f console`), keeps writing heartbeats,
|
|
250
|
+
and is recoverable by the next sync.
|
|
251
|
+
|
|
252
|
+
### Sync progress
|
|
253
|
+
|
|
254
|
+
```sh
|
|
255
|
+
./sync.sh --athlete alex sync --detach # start the run in the background and return
|
|
256
|
+
./sync.sh --athlete alex progress --watch # follow it live
|
|
257
|
+
./sync.sh --athlete alex progress # one-shot snapshot
|
|
258
|
+
```
|
|
259
|
+
|
|
260
|
+
For the full progress-record contract and the sidecar mechanism, see
|
|
261
|
+
[`local-mcp.md`](local-mcp.md#background-sync-and-live-progress).
|
|
262
|
+
|
|
263
|
+
### Strava OAuth
|
|
264
|
+
|
|
265
|
+
```sh
|
|
266
|
+
# Strava OAuth (uses clientId/clientSecret stored for this athlete)
|
|
267
|
+
docker compose -f catence-deploy/docker-compose.yml --env-file catence-deploy/.env exec console \
|
|
268
|
+
catence-data auth strava --athlete alex --callback --home /data
|
|
269
|
+
docker compose -f catence-deploy/docker-compose.yml --env-file catence-deploy/.env exec console \
|
|
270
|
+
catence-data disconnect strava --athlete alex --home /data
|
|
271
|
+
```
|
|
272
|
+
|
|
273
|
+
#### Strava OAuth via SSH (remote Docker host)
|
|
274
|
+
|
|
275
|
+
**Option 1: SSH port forwarding (recommended).** On your local machine, forward
|
|
276
|
+
the callback port to the remote Docker host:
|
|
277
|
+
|
|
278
|
+
```sh
|
|
279
|
+
ssh -L 8765:127.0.0.1:8765 user@docker-host
|
|
280
|
+
```
|
|
281
|
+
|
|
282
|
+
Then run the standard callback flow on the remote host (it uses
|
|
283
|
+
`http://127.0.0.1:8765/strava/callback`):
|
|
284
|
+
|
|
285
|
+
```sh
|
|
286
|
+
docker compose -f catence-deploy/docker-compose.yml --env-file catence-deploy/.env exec console \
|
|
287
|
+
catence-data auth strava --athlete alex --callback --home /data
|
|
288
|
+
```
|
|
289
|
+
|
|
290
|
+
**Option 2: manual code exchange with `http://localhost`.** On the remote host,
|
|
291
|
+
get the authorization URL:
|
|
292
|
+
|
|
293
|
+
```sh
|
|
294
|
+
docker compose -f catence-deploy/docker-compose.yml --env-file catence-deploy/.env run --rm \
|
|
295
|
+
--entrypoint catence-data console auth strava \
|
|
296
|
+
--athlete alex --redirect-uri "http://localhost" --home /data
|
|
297
|
+
```
|
|
298
|
+
|
|
299
|
+
Open the printed URL in your local browser, approve Strava, then copy the
|
|
300
|
+
`code` from the redirect URL and exchange it on the remote host:
|
|
301
|
+
|
|
302
|
+
```sh
|
|
303
|
+
docker compose -f catence-deploy/docker-compose.yml --env-file catence-deploy/.env run --rm \
|
|
304
|
+
--entrypoint catence-data console auth strava \
|
|
305
|
+
--athlete alex --code "THE_CODE_FROM_URL" --redirect-uri "http://localhost" --home /data
|
|
306
|
+
```
|
|
307
|
+
|
|
308
|
+
### Raw compose exec one-off commands
|
|
309
|
+
|
|
310
|
+
The generated scaffold is self-contained, so the same commands work from
|
|
311
|
+
inside `catence-deploy/` without the `-f`/`--env-file` prefixes:
|
|
312
|
+
|
|
313
|
+
```bash
|
|
314
|
+
cd catence-deploy
|
|
315
|
+
docker compose -f docker-compose.yml --env-file .env exec console \
|
|
316
|
+
catence-data sync --athlete martina --provider all --home /data # incremental sync, all providers
|
|
317
|
+
docker compose -f docker-compose.yml --env-file .env exec console \
|
|
318
|
+
catence-data backfill --athlete martina --from 2026-01-01 --home /data # full backfill from a date
|
|
319
|
+
docker compose -f docker-compose.yml --env-file .env exec console \
|
|
320
|
+
catence-data status --athlete martina --home /data # coverage/errors
|
|
321
|
+
docker compose -f docker-compose.yml --env-file .env exec console \
|
|
322
|
+
catence-data progress --athlete martina --home /data --watch # live sync progress
|
|
323
|
+
docker compose -f docker-compose.yml --env-file .env exec console \
|
|
324
|
+
catence-data athlete list --home /data # list athlete IDs
|
|
325
|
+
```
|
|
326
|
+
|
|
327
|
+
## Connecting to the Docker-based MCP from outside
|
|
328
|
+
|
|
329
|
+
### The MCP server inside the container
|
|
330
|
+
|
|
331
|
+
By default, the Catence MCP server runs on **loopback only** inside the
|
|
332
|
+
container at `http://127.0.0.1:8787/mcp`. It is **never exposed** to the host
|
|
333
|
+
network or the internet by default — this is intentional for security.
|
|
334
|
+
|
|
335
|
+
The `CATENCE_MCP_BIND` variable controls **two things at once**:
|
|
336
|
+
|
|
337
|
+
1. **Docker port mapping** — which host interface exposes port 8787 to the
|
|
338
|
+
Docker host.
|
|
339
|
+
2. **Server listen address** — which address the MCP server binds to inside
|
|
340
|
+
the container.
|
|
341
|
+
|
|
342
|
+
| Value | Docker maps | Server binds | Effect |
|
|
343
|
+
|-------|-------------|--------------|--------|
|
|
344
|
+
| `127.0.0.1` (default) | `127.0.0.1:8787 → container:8787` | `127.0.0.1` | MCP reachable only from the Docker host's loopback — requires SSH tunnel for remote access. |
|
|
345
|
+
| `0.0.0.0` | `0.0.0.0:8787 → container:8787` | `0.0.0.0` | MCP reachable from any network interface — suitable for Tailscale or VPN exposure. |
|
|
346
|
+
|
|
347
|
+
Set it at deploy time with `--mcp-bind`, or by editing `CATENCE_MCP_BIND` in
|
|
348
|
+
`.env`:
|
|
349
|
+
|
|
350
|
+
```sh
|
|
351
|
+
# Default (loopback only — SSH tunnel required for remote access)
|
|
352
|
+
./scripts/deploy-console.sh beta
|
|
353
|
+
|
|
354
|
+
# Tailscale / network exposure
|
|
355
|
+
./scripts/deploy-console.sh beta --mcp-bind 0.0.0.0
|
|
356
|
+
```
|
|
357
|
+
|
|
358
|
+
To change the binding after deployment, update `CATENCE_MCP_BIND` in `.env`
|
|
359
|
+
and restart:
|
|
360
|
+
|
|
361
|
+
```sh
|
|
362
|
+
sed -i 's/CATENCE_MCP_BIND=.*/CATENCE_MCP_BIND=0.0.0.0/' catence-deploy/.env
|
|
363
|
+
docker compose -f catence-deploy/docker-compose.yml --env-file catence-deploy/.env up -d
|
|
364
|
+
```
|
|
365
|
+
|
|
366
|
+
### SSH port forwarding (recommended for remote MCP clients)
|
|
367
|
+
|
|
368
|
+
From your **local machine**, forward the container's MCP port through the
|
|
369
|
+
Docker host:
|
|
370
|
+
|
|
371
|
+
```sh
|
|
372
|
+
# Forward local port 8787 → Docker host port 8787 → container loopback 8787
|
|
373
|
+
ssh -L 8787:127.0.0.1:8787 user@docker-host
|
|
374
|
+
```
|
|
375
|
+
|
|
376
|
+
Then point your MCP client at `http://127.0.0.1:8787/mcp` on your local
|
|
377
|
+
machine.
|
|
378
|
+
|
|
379
|
+
### Exposing MCP on Tailscale (alternative)
|
|
380
|
+
|
|
381
|
+
To expose the MCP server directly on a Tailscale interface (or any network
|
|
382
|
+
interface) without SSH tunneling:
|
|
383
|
+
|
|
384
|
+
```sh
|
|
385
|
+
# Deploy with MCP bound to all interfaces (0.0.0.0) — restrict via Tailscale ACLs/firewall
|
|
386
|
+
./scripts/deploy-console.sh beta --mcp-bind 0.0.0.0
|
|
387
|
+
```
|
|
388
|
+
|
|
389
|
+
Or set the environment variable before deploying:
|
|
390
|
+
|
|
391
|
+
```sh
|
|
392
|
+
CATENCE_MCP_BIND=0.0.0.0 ./scripts/deploy-console.sh beta
|
|
393
|
+
```
|
|
394
|
+
|
|
395
|
+
After deployment, the MCP server is available at
|
|
396
|
+
`http://<tailscale-ip>:8787/mcp` from any device on your tailnet.
|
|
397
|
+
|
|
398
|
+
**Security note:** The MCP server has **no authentication** — it trusts the
|
|
399
|
+
caller's `athleteId` parameter. When exposing on `0.0.0.0`, you **must**
|
|
400
|
+
restrict access at the network layer (Tailscale ACLs, firewall rules, etc.).
|
|
401
|
+
The Console UI remains on loopback (`127.0.0.1:8000`) and should still be
|
|
402
|
+
fronted by Cloudflare Tunnel or a reverse proxy.
|
|
403
|
+
|
|
404
|
+
**Examples: common MCP clients**
|
|
405
|
+
|
|
406
|
+
```sh
|
|
407
|
+
# Via SSH tunnel (default loopback deployment)
|
|
408
|
+
# Opencode
|
|
409
|
+
opencode mcp add catence -- http://127.0.0.1:8787/mcp
|
|
410
|
+
|
|
411
|
+
# Codex
|
|
412
|
+
codex mcp add catence -- http://127.0.0.1:8787/mcp
|
|
413
|
+
|
|
414
|
+
# Claude Desktop
|
|
415
|
+
claude mcp add --transport http catence -- http://127.0.0.1:8787/mcp
|
|
416
|
+
```
|
|
417
|
+
|
|
418
|
+
```sh
|
|
419
|
+
# Via Tailscale (when deployed with --mcp-bind 0.0.0.0)
|
|
420
|
+
# Replace <tailscale-ip> with your server's Tailscale IP (from `tailscale ip`)
|
|
421
|
+
opencode mcp add catence -- http://<tailscale-ip>:8787/mcp
|
|
422
|
+
codex mcp add catence -- http://<tailscale-ip>:8787/mcp
|
|
423
|
+
claude mcp add --transport http catence -- http://<tailscale-ip>:8787/mcp
|
|
424
|
+
```
|
|
425
|
+
|
|
426
|
+
### Why not expose port 8787 directly?
|
|
427
|
+
|
|
428
|
+
- The MCP server has **no authentication** — it trusts the caller's
|
|
429
|
+
`athleteId` parameter.
|
|
430
|
+
- Exposing it would allow any network caller to read all athlete data in the
|
|
431
|
+
catalog.
|
|
432
|
+
- The Console proxies dashboard requests through its **authenticated**
|
|
433
|
+
same-origin route.
|
|
434
|
+
- SSH port forwarding keeps the trust boundary at your SSH credentials.
|
|
435
|
+
|
|
436
|
+
## Exposing the Console safely
|
|
437
|
+
|
|
438
|
+
Keep the Console port bound to `127.0.0.1` (the default) and front it with a
|
|
439
|
+
Cloudflare Tunnel or reverse proxy at HTTPS. Never expose Catence's MCP port
|
|
440
|
+
8787 — it stays loopback-only inside the container. For remote MCP checks use
|
|
441
|
+
SSH port forwarding (`ssh -L 8787:127.0.0.1:8787 server`).
|
|
442
|
+
|
|
443
|
+
### Cloudflare Tunnel
|
|
444
|
+
|
|
445
|
+
The Console image contains the Node runtime, Catence, and Console. Its MCP
|
|
446
|
+
server remains loopback-only inside the container; only the Console port is
|
|
447
|
+
exposed. Route a Cloudflare Tunnel to `http://127.0.0.1:8000`; do not run a
|
|
448
|
+
tunnel client in this image. Use HTTPS at the tunnel and retain the Console
|
|
449
|
+
login — the dashboard is fetched through the authenticated Console origin
|
|
450
|
+
rather than directly from port 8787.
|
|
451
|
+
|
|
452
|
+
### Building the image manually
|
|
453
|
+
|
|
454
|
+
For a source build instead of the registry artifacts:
|
|
455
|
+
|
|
456
|
+
```sh
|
|
457
|
+
docker build -f console/Dockerfile -t catence-console .
|
|
458
|
+
docker run --rm -p 127.0.0.1:8000:8000 -v catence-data:/data \
|
|
459
|
+
-e CATENCE_CONSOLE_USERNAME='coach' \
|
|
460
|
+
-e CATENCE_CONSOLE_PASSWORD_HASH='replace-with-bcrypt-hash' \
|
|
461
|
+
-e CHAINLIT_AUTH_SECRET='replace-with-a-long-random-secret' \
|
|
462
|
+
-e OPENAI_API_KEY='…' \
|
|
463
|
+
catence-console
|
|
464
|
+
```
|
|
465
|
+
|
|
466
|
+
Initialize the mounted volume before its first production sync, for example:
|
|
467
|
+
|
|
468
|
+
```sh
|
|
469
|
+
docker run --rm -it -v catence-data:/data --entrypoint catence-data catence-console \
|
|
470
|
+
setup --athlete alex --label "Alex"
|
|
471
|
+
```
|
|
472
|
+
|
|
473
|
+
Use the same `--entrypoint catence-data` pattern for `secret set` and `sync`.
|
|
474
|
+
|
|
475
|
+
## Updating
|
|
476
|
+
|
|
477
|
+
Docker deployments: re-run `deploy-console.sh beta` (or `stable`); it
|
|
478
|
+
re-resolves the newest versions from the registries and rebuilds. The data
|
|
479
|
+
volume and your `config.json` are preserved.
|
|
480
|
+
|
|
481
|
+
```sh
|
|
482
|
+
curl -fsSL https://raw.githubusercontent.com/rifusaki/catence/beta/scripts/deploy-console.sh | bash -s beta
|
|
483
|
+
# or from checkout: ./scripts/deploy-console.sh beta
|
|
484
|
+
```
|
|
485
|
+
|
|
486
|
+
For local (`catence-data update`) updates see
|
|
487
|
+
[`local-console.md`](local-console.md#updating).
|
|
488
|
+
|
|
489
|
+
## Non-default catalog location (bind mount)
|
|
490
|
+
|
|
491
|
+
Stop the stack, edit `catence-deploy/docker-compose.yml` to replace the volume
|
|
492
|
+
with a bind mount:
|
|
493
|
+
|
|
494
|
+
```yaml
|
|
495
|
+
volumes:
|
|
496
|
+
- /srv/catence:/data
|
|
497
|
+
```
|
|
498
|
+
|
|
499
|
+
Then restart and run the same commands (`--home /data` is implied by
|
|
500
|
+
`CATENCE_HOME`). The deploy script's `--home <dir>` option does this for you at
|
|
501
|
+
deploy time.
|
|
502
|
+
|
|
503
|
+
## Quick reference: `docker compose run` vs `docker compose exec`
|
|
504
|
+
|
|
505
|
+
| Command Type | Use For | Example |
|
|
506
|
+
|--------------|---------|---------|
|
|
507
|
+
| `run --rm --entrypoint catence-data console` | **One-off setup/secrets** — needs fresh stdin for `--value-stdin`, no running container required | `secret set`, `setup`, `auth strava --code` |
|
|
508
|
+
| `exec console catence-data` | **Recurring ops** — uses the running container's environment (secrets already in volume) | `sync`, `backfill`, `status`, `build-retrieval-index` |
|
|
509
|
+
|
|
510
|
+
The `sync.sh` helper uses `exec` for this reason.
|
|
511
|
+
|
|
512
|
+
## Troubleshooting
|
|
513
|
+
|
|
514
|
+
- **"No Catence config exists"** — should not happen after a seeded deploy or
|
|
515
|
+
wizard run; ensure `/data/config.json` exists and contains a `console`
|
|
516
|
+
section.
|
|
517
|
+
- **"Refusing to initialize"** — the data home contains unrelated files; the
|
|
518
|
+
error lists them. Console artifacts (`config.json`, `console/`, and
|
|
519
|
+
Chainlit's `.files/`, `.chainlit/`, `public/`) are allowed; anything else
|
|
520
|
+
blocks `setup`.
|
|
521
|
+
- **Settings panel has no Model dropdown** — the Console loads profiles from
|
|
522
|
+
`config.json`; write one via the wizard, a manual copy, or the discovery
|
|
523
|
+
script.
|
|
524
|
+
- **Profile not ready** — `catence-console doctor` lists the missing
|
|
525
|
+
environment variables; set them in `.env` and restart the Console.
|
|
526
|
+
- **Doctor reports runtime unreachable** — use `./catence-deploy/doctor.sh`
|
|
527
|
+
(exec) rather than `docker compose run`; a `run` container has a separate
|
|
528
|
+
loopback that cannot see the running MCP server.
|