Deploying WebSensor on MacLustr
Since 2026-09-13 production runs on the OVH server BHS128 (
ssh BHS128, ubuntu@51.161.112.69, Ubuntu 24.04, 12 threads / 128 GB), outsidemld: code in~/apps/websensor, Postgres 17 native (db/rolewebsensor/websensor, C.UTF-8 like the Mac source — with en_US.UTF-8 the generatedevents.searchtsvector rejected a 5 836-byte keyword), Redis native, blobs in~/websensor-data/blobs, PM2 under systemd (pm2-ubuntu,LimitNOFILE1 048 576 for the 48 fetch workers) with~/apps/pm2.websensor.config.cjsgenerated from the mld manifest by~/apps/.manifests/gen-pm2.py(secrets, 0600). Tunnel route:mlt add www.websensor.io BHS128:8260(BHS64 Caddy → wg1 10.67.0.60); firewallufw allow in on wg1 from 10.67.0.1 to any port 8260 proto tcp. The move was triggered by the LAN uplink (Bell) collapsing under the ~6 000 outbound connections of the engine + factory (NAT table). Release on BHS128:rsync -az --exclude node_modules --exclude .git --exclude 'apps/web/.next' --exclude data --exclude 'deploy/*.mld.json' . BHS128:apps/websensor/thenssh BHS128 'cd apps/websensor && pnpm install --frozen-lockfile && DATABASE_URL=postgres://websensor:websensor@127.0.0.1:5432/websensor pnpm db:migrate && DATABASE_URL=postgres://websensor:websensor@127.0.0.1:5432/websensor node node_modules/tsx/dist/cli.mjs apps/engine/src/cli.ts sync && API_URL=http://127.0.0.1:8260 NEXT_PUBLIC_SITE_URL=https://www.websensor.io pnpm --filter @websensor/web build && pm2 restart websensor-engine websensor-factory websensor-api websensor-web && pm2 save'. The M4M64b copy (code, blobs, Postgres database, Redis keys) was deleted on 2026-09-14: BHS128 now holds the only production data — schedule an off-host backup (pg_dump websensor+~/websensor-data/blobs). Themldprocedure below is kept for reference (manifest backup: laptop~/Desktop/Cluster/secrets/mld-manifests-retired-20260914/websensor.json).
WebSensor formerly ran as four PM2 processes on one node (M4M64b), published through the MacLustr Tunnel (WireGuard +
Caddy on the OVH gateway BHS64, www.websensor.io; ngrok was retired on 2026-09-10), orchestrated by mld from the
M1M32 gateway.
Internet → www.websensor.io → BHS64 (Caddy TLS) → wg1 → websensor-api (:8260, Fastify)
├── /api/v1/* REST + WebSocket /api/v1/live
└── /* reverse proxy → websensor-web (Next.js :8261, loopback)
websensor-engine (scheduler + pipeline, Prometheus :8262) → PostgreSQL 17 `websensor` + Redis (loopback)
websensor-factory (Source Factory: coverage seeds → deep discovery → shadow sensors → acceptance; heartbeat Redis ws:factory:status)
Blob store: ~/websensor-data/blobs (content-addressed, zstd) — never synced by mld (excluded `data/`).First deployment
- Copy
deploy/websensor.mld.json.exampletoM1M32:~/dispatch/apps/websensor.jsonand fill the secrets (ANTHROPIC_API_KEY,SCRAPFLY_API_KEY, andWS_ADMIN_TOKENon the API process — it enables/api/v1/admin/*and the/opsdashboard; a copy of the token lives in~/.websensor/admin-tokenon the laptop). The manifest is0600on the gateway; a gitignored local copy may live indeploy/websensor.mld.json. ~/Desktop/cluster-skill/mld stage ~/Desktop/Projets/apps-web/websensor websensor~/Desktop/cluster-skill/mld deploy websensor --node M4M64bPost-sync hooks:pnpm install,createdb+ migrations, registry sync,next build. Health:/api/ready(database + engine activity in the last 15 minutes), publichttps://www.websensor.io/api/ready.- Verify:
mld status --live,curl https://www.websensor.io/api/v1/stats, and a WebSocket handshake (npx wscat -c wss://www.websensor.io/api/v1/live).
DNS / tunnel
www.websensor.io→ A record to BHS64 (51.161.112.61);mld deploy/moverepoints the Caddy route to the current node automatically (tunnel.domainin the manifest). The apexwebsensor.ioshould redirect towww.- WebSocket upgrades traverse Caddy without configuration; the API sends a heartbeat every 25 s and clients
replay missed events with
{"since": lastSid}after a reconnection.
Source Factory (0.3)
- Seeds:
config/coverage/*.yaml(universes) andconfig/factory/seeds/*.yaml; the post-sync hook runscli.ts factory seed --mode=hinted(members without a monitored domain + members with hints).--mode=all --requeuere-discovers everything;factory requeuere-queues blocked/errored seeds. - Process:
pm2 logs websensor-factory;cli.ts factory stats;/ops→ Source Factory panel (funnel, sectors, candidates, manual accept/reject);GET /api/v1/admin/factory,/admin/factory/seeds,/admin/factory/candidates,POST /admin/factory/seeds(bulk),POST /admin/factory/shadow/:sensorId/accept|reject. - Tuning (env on the factory process):
WS_FACTORY_CONCURRENCY(6),WS_FACTORY_BUDGET(90 requests/org),WS_FACTORY_MIN_SCORE(0.35),WS_FACTORY_SHADOW_MIN_CHECKS(5),WS_FACTORY_SHADOW_MIN_HOURS(24),WS_FACTORY_SHADOW_MAX_HOURS(120).GITHUB_TOKEN(optional) improves repository ranking. - Shadow sensors are polled by the engine at priority 3; raise
WS_FETCH_CONCURRENCYon the engine when the shadow population grows (32 at 0.3).cli.ts factory evaluate [--force]runs the decision pass by hand;cli.ts factory export --sector=x > config/sources.d/6x-factory-x.yamlgraduates accepted sensors into the registry. - Coverage:
GET /api/v1/coverage,/coveragepage; universes validated withapps/engine/src/coverage-validate.ts --dns.
Operations
- Logs:
mld logs websensororssh M4M64b pm2 logs websensor-engine. - Registry edits: change
config/sources.yaml, redeploy (sync runs in post-sync) orssh M4M64b "cd ~/apps/websensor && node node_modules/tsx/dist/cli.mjs apps/engine/src/cli.ts sync". Seed sensors removed from the YAML are disabled automatically; discovery-created sensors are kept. - Discovery:
cli.ts discover [sourceId…];cli.ts probe <domain>for a dry run. - Metrics: engine
http://127.0.0.1:8262/metrics, API/api/metrics(Prometheus text). Public health dashboard:/healthon the site,/api/v1/health/connectors. - Budgets:
WS_LLM_DAILY_CALL_BUDGET(Claude calls/day),WS_SCRAPFLY_DAILY_BUDGET(Scrapfly calls/day, ~30 credits each with anti-bot). Both counters reset at UTC midnight and are visible inmetrics_daily. - Backups:
pg_dump websensor+~/websensor-data/blobs(content-addressed; safe to rsync incrementally). - Retention:
sensor_runsunchanged/304 rows are pruned after 14 days, all runs after 60 days. Raw bodies of snapshots that never produced an event are pruned afterWS_RETENTION_RAW_DAYS(21) — canonical forms, hashes, changes and events are kept; event snapshots are never pruned.cli.ts prune-blobsruns it by hand. - After a deploy that changes alias or scoring rules:
cli.ts relink-entities 7(re-resolves mentioned entities) andcli.ts refresh-clusters(breaking / developing / confirmed states). - Operations dashboard:
/ops(token) andGET /api/v1/admin/ops; connector dry-runPOST /api/v1/admin/sensors/test; bulk importPOST /api/v1/admin/sources/import?dry_run=1(JSON or YAML) — imported sensors start as PENDING and become ACTIVE after their first successful check.