mirror of
https://github.com/Kpa-clawbot/meshcore-analyzer.git
synced 2026-09-26 17:07:53 +00:00
Red commit: `3898dbc5` (verified locally — CI run URL pending)
## Problem
A standalone `mqtt-broker` container (`eclipse-mosquitto:2`) was
provisioned out-of-band on the staging VM. It now owns MQTT, is attached
to external docker network `meshcore-net`, and binds host port `8883`.
The current `docker-compose.staging.yml` still:
- Publishes `1883:1883` on the host (dead weight; conflicts the moment
the broker moves to that port).
- Defaults `DISABLE_MOSQUITTO=false`, so the in-container mosquitto
burns RAM and briefly contests the `mqtt-broker` docker DNS name on cold
start.
- Doesn't join `meshcore-net`, so the ingestor can't resolve
`mqtt-broker:1883` via docker DNS without manual surgery.
## Fix (`docker-compose.staging.yml` only)
1. Remove the `1883:1883` host port publish from `staging-go`.
2. Flip `DISABLE_MOSQUITTO` default from `false` to `true`. Operators
can opt back in with the env var.
3. Attach `staging-go` to both `default` and `meshcore-net`; declare
`meshcore-net` as `external: true` so the file never tries to
create/destroy operator state.
Healthcheck and Caddy/443 plumbing untouched (out of scope).
## Test added (TDD framing: Option A — Go shape-asserts)
`cmd/server/staging_compose_broker_test.go:1` adds four regex-based
assertions on the compose file shape:
- staging-go does **not** bind port `1883` in ANY form (quoted/unquoted
short form, or long-form `target: 1883` / `published: 1883`).
- `DISABLE_MOSQUITTO` uses the interpolated default form
`${DISABLE_MOSQUITTO:-true}` (preserves operator override). Bare literal
`true`, or a later `=false` override in the same env block, is rejected.
- Top-level `networks:` declares `meshcore-net` as `external: true`.
- `staging-go` attaches to `meshcore-net` via a real
`services.staging-go.networks:` sub-key (comment-stripped so an
in-comment example can't masquerade).
Regex (not YAML byte-equality) so cosmetic edits don't break the guard.
No new go module deps. Red commit `3898dbc5` fails all 4 assertions on
master. Green commit `38297ff4` makes them pass. Round-1 hardening
commit `9f7155e2` tightens the regexes (per adversarial + kent-beck
must-fixes) and was verified against master's YAML shape — all 4 tests
fail on `origin/master`'s compose, pass on branch, proving the tightened
regexes still gate a real regression.
## Risk
Low, with one intentional semantic change.
- **Semantic change (v3.7+):** `DISABLE_MOSQUITTO` in
`docker-compose.staging.yml` now defaults to `true`. This is a
**deliberate flip** — the standalone `mqtt-broker` container is now
authoritative on the staging host, and running the in-container
mosquitto alongside it wastes RAM and races the docker DNS name
`mqtt-broker` on cold start. Operators who want the pre-v3.7 shape
(in-container mosquitto + host-published `1883`) must explicitly opt
back in via env override AND re-add the `1883:1883` port mapping
(concrete snippet is inline in the compose file and in `DEPLOY.md` under
"Standalone MQTT broker (staging)"). This intent is called out in a
`SEMANTIC CHANGE (v3.7+)` header comment at the top of
`docker-compose.staging.yml`.
- **Deploy prereq:** the external `meshcore-net` docker network MUST
already exist on the host before `docker compose up`. If it doesn't,
compose refuses to start `staging-go`. This is documented inline in the
compose file (with the `docker network create meshcore-net` one-liner)
and in `DEPLOY.md`.
- **Only takes effect where the standalone broker is deployed** — which
it already is on staging today. The legacy `DISABLE_MOSQUITTO=false`
path remains reachable via env override; the ingestor's upstream config
is untouched.
Partial fix — no tracking issue; follow-up to operator-side broker
provisioning.
---------
Co-authored-by: corescope-bot <bot@corescope.local>
Co-authored-by: openclaw-bot <bot@openclaw.local>
286 lines
9.1 KiB
Markdown
286 lines
9.1 KiB
Markdown
# Deploy CoreScope
|
|
|
|
Pre-built images are published to GHCR for `linux/amd64` and `linux/arm64` (Raspberry Pi 4/5).
|
|
|
|
## Quick Start
|
|
|
|
### Docker run
|
|
|
|
```bash
|
|
docker run -d --name corescope \
|
|
-p 80:80 \
|
|
-v corescope-data:/app/data \
|
|
-e DISABLE_CADDY=true \
|
|
ghcr.io/kpa-clawbot/corescope:latest
|
|
```
|
|
|
|
Open `http://localhost` — done.
|
|
|
|
### Docker Compose
|
|
|
|
```bash
|
|
curl -sL https://raw.githubusercontent.com/Kpa-clawbot/CoreScope/master/docker-compose.example.yml \
|
|
-o docker-compose.yml
|
|
docker compose up -d
|
|
```
|
|
|
|
## Image Tags
|
|
|
|
| Tag | Description |
|
|
|-----|-------------|
|
|
| `v3.4.1` | Pinned release (recommended for production) |
|
|
| `v3.4` | Latest patch in v3.4.x |
|
|
| `v3` | Latest minor+patch in v3.x |
|
|
| `latest` | Latest release tag |
|
|
| `edge` | Built from master — unstable, for testing |
|
|
|
|
## Configuration
|
|
|
|
Settings can be overridden via environment variables:
|
|
|
|
| Variable | Default | Description |
|
|
|----------|---------|-------------|
|
|
| `DISABLE_CADDY` | `false` | Skip internal Caddy (set `true` behind a reverse proxy) |
|
|
| `DISABLE_MOSQUITTO` | `true` in `docker-compose.staging.yml`; `false` elsewhere | Skip internal MQTT broker. Default flipped to `true` for the staging deploy in v3.7+ because a standalone `mqtt-broker` container owns MQTT on that host — see "Standalone MQTT broker (staging)" below. |
|
|
| `HTTP_PORT` | `80` | Host port mapping |
|
|
| `DATA_DIR` | `./data` | Host path for persistent data |
|
|
|
|
For advanced configuration, mount a `config.json` into `/app/data/config.json`. See `config.example.json` in the repo.
|
|
|
|
## Updating
|
|
|
|
```bash
|
|
docker compose pull
|
|
docker compose up -d
|
|
```
|
|
|
|
## Data
|
|
|
|
All persistent data lives in `/app/data`:
|
|
- `meshcore.db` — SQLite database (packets, nodes)
|
|
- `config.json` — custom config (optional)
|
|
- `theme.json` — custom theme (optional)
|
|
|
|
**Backup:** `cp data/meshcore.db ~/backup/`
|
|
|
|
## TLS
|
|
|
|
Option A — **External reverse proxy** (recommended): Run with `DISABLE_CADDY=true`, put nginx/traefik/Cloudflare in front.
|
|
|
|
Option B — **Built-in Caddy**: Mount a custom Caddyfile at `/etc/caddy/Caddyfile` and expose ports 80+443.
|
|
|
|
---
|
|
|
|
## Standalone MQTT broker (staging)
|
|
|
|
Starting in v3.7, `docker-compose.staging.yml` assumes a **standalone
|
|
`mqtt-broker` container** (image: `eclipse-mosquitto:2`) already runs
|
|
on the staging VM, out-of-band from this repo. That container:
|
|
|
|
- Owns port `8883` externally (TLS-terminated MQTT for real observers).
|
|
- Is attached to a shared docker network named `meshcore-net`.
|
|
- Is operator-managed state — it is **not** defined in any compose
|
|
file in this repository. Its config, TLS certs, and ACLs live on the
|
|
host, outside git.
|
|
|
|
`corescope-staging-go` reaches it in-network at `mqtt-broker:1883` via
|
|
docker DNS (no host port hop). To make that work, `docker-compose.staging.yml`
|
|
joins the external `meshcore-net` network and defaults `DISABLE_MOSQUITTO=true`
|
|
so the built-in mosquitto stays off.
|
|
|
|
### Prereq — one-time provisioning on the staging host
|
|
|
|
```bash
|
|
docker network create meshcore-net
|
|
# ...then bring up the operator-managed mqtt-broker container on that
|
|
# network (not covered here; that's operator state). THEN:
|
|
docker compose -f docker-compose.staging.yml up -d
|
|
```
|
|
|
|
If `meshcore-net` doesn't exist when compose starts, docker will refuse
|
|
to bring `staging-go` up (`external: true` — compose won't create it).
|
|
|
|
### Reverting to the old single-container behaviour
|
|
|
|
Third-party operators cloning this repo who want the legacy shape
|
|
(in-container mosquitto + `1883:1883` on the host, no external broker)
|
|
should override both the env default and re-add the port mapping.
|
|
|
|
In `.env` (or the shell):
|
|
|
|
```
|
|
DISABLE_MOSQUITTO=false
|
|
```
|
|
|
|
And in `docker-compose.staging.yml`, restore the `1883:1883` mapping
|
|
under `services.staging-go.ports`:
|
|
|
|
```yaml
|
|
ports:
|
|
- "${STAGING_GO_HTTP_PORT:-80}:80"
|
|
- "${STAGING_GO_MQTT_PORT:-1883}:1883" # ← re-added
|
|
- "6060:6060"
|
|
- "6061:6061"
|
|
```
|
|
|
|
That gives you back the pre-v3.7 self-contained staging shape. In that
|
|
mode you do **not** need `meshcore-net`, but note the compose file still
|
|
declares it as `external: true`, so either remove that declaration in
|
|
your fork or ensure the network exists.
|
|
|
|
---
|
|
|
|
## Migrating from manage.sh (existing admins)
|
|
|
|
If you're currently deploying with `manage.sh` (git clone + local build), you have two options going forward:
|
|
|
|
### Option A: Keep using manage.sh (no changes needed)
|
|
|
|
`manage.sh update` continues to work exactly as before — it fetches the latest tag, builds locally, and restarts. Nothing breaks.
|
|
|
|
```bash
|
|
./manage.sh update # latest release
|
|
./manage.sh update v3.5.0 # specific version
|
|
```
|
|
|
|
### Option B: Switch to pre-built images (recommended)
|
|
|
|
Pre-built images skip the build step entirely — faster updates, no Go toolchain needed.
|
|
|
|
**One-time migration:**
|
|
|
|
1. Stop the current deployment:
|
|
```bash
|
|
./manage.sh stop
|
|
```
|
|
|
|
2. Your data is in `~/meshcore-data/` (or whatever `PROD_DATA_DIR` is set to). It's untouched — the database, config, and theme files persist.
|
|
|
|
3. Copy `docker-compose.example.yml` to where you want to run from:
|
|
```bash
|
|
cp docker-compose.example.yml ~/docker-compose.yml
|
|
```
|
|
|
|
4. Start with the pre-built image:
|
|
```bash
|
|
cd ~ && docker compose up -d
|
|
```
|
|
|
|
5. Verify it picked up your existing data:
|
|
```bash
|
|
curl http://localhost/api/stats
|
|
```
|
|
|
|
**Updates after migration:**
|
|
```bash
|
|
docker compose pull && docker compose up -d
|
|
```
|
|
|
|
### What about manage.sh features?
|
|
|
|
| manage.sh command | Pre-built equivalent |
|
|
|---|---|
|
|
| `./manage.sh update` | `docker compose pull && docker compose up -d` |
|
|
| `./manage.sh stop` | `docker compose down` |
|
|
| `./manage.sh start` | `docker compose up -d` |
|
|
| `./manage.sh logs` | `docker compose logs -f` |
|
|
| `./manage.sh status` | `docker compose ps` |
|
|
| `./manage.sh setup` | Copy `docker-compose.example.yml`, edit env vars |
|
|
|
|
`manage.sh` remains available for advanced use cases (building from source, custom patches, development). Pre-built images are recommended for most production deployments.
|
|
|
|
## Staging VM — disk-usage monitor & cleanup (#1684)
|
|
|
|
The staging VM ran out of disk during a hot-patch (#1684). To prevent
|
|
repeats, two scripts live in `scripts/staging/`:
|
|
|
|
- `disk-monitor.sh <mount>` — reads `df -P`, classifies usage against
|
|
`<80 ok / >=80 warn / >=90 error / >=95 alert`, emits to stderr +
|
|
journald (via `logger`). Returns non-zero on `error|alert` so
|
|
systemd surfaces the unit as failed.
|
|
- `disk-cleanup.sh` — removes `/tmp` snapshot files (`*.db`,
|
|
`staging-snap.*`, `cs-*`, `node-compile-cache`) older than 7 days
|
|
and runs `docker builder prune` + `docker image prune` with
|
|
`--filter "until=72h" --filter "label!=keep"`. Set
|
|
`CORESCOPE_CLEANUP_DRY_RUN=1` to log without deleting.
|
|
|
|
### Install on the staging host
|
|
|
|
SSH to `<STAGING_HOST>` as the staging operator user and:
|
|
|
|
```bash
|
|
sudo install -m 0755 scripts/staging/disk-monitor.sh /usr/local/bin/corescope-disk-monitor
|
|
sudo install -m 0755 scripts/staging/disk-cleanup.sh /usr/local/bin/corescope-disk-cleanup
|
|
|
|
# 15-minute monitor
|
|
sudo tee /etc/systemd/system/corescope-disk-monitor.service >/dev/null <<'UNIT'
|
|
[Unit]
|
|
Description=CoreScope staging disk-usage monitor (issue #1684)
|
|
[Service]
|
|
Type=oneshot
|
|
ExecStart=/usr/local/bin/corescope-disk-monitor /
|
|
UNIT
|
|
|
|
sudo tee /etc/systemd/system/corescope-disk-monitor.timer >/dev/null <<'UNIT'
|
|
[Unit]
|
|
Description=Run CoreScope disk-usage monitor every 15 minutes
|
|
[Timer]
|
|
OnBootSec=5min
|
|
OnUnitActiveSec=15min
|
|
Unit=corescope-disk-monitor.service
|
|
[Install]
|
|
WantedBy=timers.target
|
|
UNIT
|
|
|
|
# Daily cleanup at 03:30 local
|
|
sudo tee /etc/systemd/system/corescope-disk-cleanup.service >/dev/null <<'UNIT'
|
|
[Unit]
|
|
Description=CoreScope staging disk cleanup (issue #1684)
|
|
[Service]
|
|
Type=oneshot
|
|
ExecStart=/usr/local/bin/corescope-disk-cleanup
|
|
UNIT
|
|
|
|
sudo tee /etc/systemd/system/corescope-disk-cleanup.timer >/dev/null <<'UNIT'
|
|
[Unit]
|
|
Description=Run CoreScope disk cleanup daily at off-peak
|
|
[Timer]
|
|
OnCalendar=*-*-* 03:30:00
|
|
Persistent=true
|
|
Unit=corescope-disk-cleanup.service
|
|
[Install]
|
|
WantedBy=timers.target
|
|
UNIT
|
|
|
|
sudo systemctl daemon-reload
|
|
sudo systemctl enable --now corescope-disk-monitor.timer corescope-disk-cleanup.timer
|
|
```
|
|
|
|
`<STAGING_HOST>` is the staging VM hostname/IP — operator supplies it,
|
|
not committed to the repo.
|
|
|
|
### Inspecting alerts
|
|
|
|
```bash
|
|
journalctl -t corescope-disk-monitor --since '-1d'
|
|
journalctl -t corescope-disk-cleanup --since '-7d'
|
|
systemctl list-timers | grep corescope-disk
|
|
```
|
|
|
|
`logger` priorities map: `ok→info`, `warn→warning`, `error→err`,
|
|
`alert→alert` (syslog severity 1, the highest level). Wire
|
|
`journalctl -p alert ...` to whatever ops channel the operator
|
|
prefers; use `-p err` to also catch the `error` tier.
|
|
|
|
### Notes on `staging-snap.db` root cause (#1684 phase 3)
|
|
|
|
`grep -rn staging-snap.db cmd/ public/ scripts/` returns **zero**
|
|
hits in the repo. The 4.4 GB orphan was a manual debugging artifact,
|
|
not produced by any committed code. The `disk-cleanup.sh` retention
|
|
rule (anything matching `staging-snap.*` in `/tmp` older than 7 days)
|
|
prevents recurrence without needing source-side TTL changes.
|
|
|
|
If a future feature legitimately needs persistent snapshot DBs, put
|
|
them under `/var/lib/corescope/snapshots/` with explicit rotation —
|
|
not in `/tmp`, which is ephemeral by definition.
|