openvibers/openvibe.host host.example.json

host.example.json

main 121.8 KB · 4192 lines · json

1{
2 "_about": "Example ovhost inventory for the single OpenVibe host (openvibe-oregon). Copy to /etc/openvibe/host.json (root:root, 0640) and correct anything that differs on the machine; the real file is never committed. Paths, units, ports and env file locations come from OpenVibe.Network docs/roadmap-baseline/data/services.json and prod-snapshot.json (22 Sep 2026) and each repository's deploy/ directory. env.required lists NAMES only.",
3 "_databases": "Every databases entry names a PostgreSQL database in the cluster: { \"name\": \"trade\", \"engine\": \"postgresql\", \"database\": \"ov_trade\" }. A drill maps it to { \"url\": \"DATABASE_URL\", \"directUrl\": \"DATABASE_DIRECT_URL\" } and restores a scratch database and role.",
4 "host": "openvibe-oregon",
5 "stateDir": "/var/lib/openvibe-host",
6 "backupDir": "/var/backups/openvibe",
7 "backupStagingDir": "/var/backups/openvibe.staging",
8 "drillDir": "/var/lib/openvibe-drills",
9 "_drill": "Restore drills (ovhost drill <service>): each service with a database has a drill block. The drill port is the service port + 10000 on 127.0.0.1. http://127.0.0.1:9 is a closed port, used where an empty value would fall back to a production service on loopback. An empty OV_OAUTH_CLIENT_SECRET stops a service from minting service tokens, so it cannot call other services with its identity. Loopback stays reachable from the drill sandbox (services need Network's public keys), so these overrides are what keep a drill away from production. A PostgreSQL drill restores the dump into a scratch database under a freshly created role, then sets DATABASE_URL and DATABASE_DIRECT_URL to it and VALKEY_URL to the closed port; it refuses to start if any of the three would keep its production value, because the drilled release would otherwise run its migrations against production (DATABASE_DIRECT_URL) and write into the production Valkey keyspace (VALKEY_URL). _notes explain what is left on and why.",
10 "_lifecycle": "Lifecycle (ovhost validate, WS-P task 1): each service's lifecycle comes from its manifest in openvibe-contracts (the service's `manifest` id, default its key), from openvibe-contracts 0.55.0 on. A `lifecycle` block in a service entry replaces the manifest's on this host; none is needed today. validate compares shutdown.deadlineSeconds with each unit's effective TimeoutStopSec and KillSignal, and the checkout's installed openvibe-contracts with contracts.range.",
11 "nginx": {
12 "sitesAvailable": "/etc/nginx/sites-available",
13 "sitesEnabled": "/etc/nginx/sites-enabled",
14 "bin": "nginx"
15 },
16 "defaults": {
17 "owner": "ubuntu",
18 "runAs": "ubuntu",
19 "remote": "origin",
20 "branch": "main"
21 },
22 "_events": "Release notifications (ovhost deploy/rollback/announce, docs/release-notifications.md): the env file with Host's service principal credentials (OV_OAUTH_CLIENT_ID, OV_OAUTH_CLIENT_SECRET, EVENTS_URL, OV_NETWORK_INTERNAL_URL). A service entry may set \"origin\" (https) to override the manifest's publicOrigin.",
23 "events": {
24 "envFile": "/etc/openvibe/host.env"
25 },
26 "services": {
27 "network": {
28 "repo": "/opt/openvibe.network",
29 "units": [
30 "openvibe-network.service"
31 ],
32 "unitSources": {
33 "openvibe-network.service": "deploy/systemd/openvibe-network.service"
34 },
35 "installUnits": true,
36 "envFile": "/etc/openvibe/network.env",
37 "env": {
38 "required": [
39 "BASE_URL",
40 "JWT_PRIVATE_KEY",
41 "JWT_PUBLIC_KEY",
42 "DATABASE_URL",
43 "DATABASE_DIRECT_URL"
44 ]
45 },
46 "port": 4000,
47 "ready": {
48 "url": "http://127.0.0.1:4000/api/ready",
49 "timeoutSeconds": 30
50 },
51 "noRestartPaths": [
52 "docs/",
53 "test/",
54 "**/*.md"
55 ],
56 "databases": [
57 {
58 "name": "network",
59 "engine": "postgresql",
60 "database": "ov_network"
61 }
62 ],
63 "nginx": {
64 "vhost": "openvibe.network.conf",
65 "repoVhost": "deploy/nginx/openvibe.network.conf",
66 "variant": "http"
67 },
68 "drill": {
69 "port": 14000,
70 "ready": "/api/ready",
71 "databases": {
72 "network": {
73 "url": "DATABASE_URL",
74 "directUrl": "DATABASE_DIRECT_URL"
75 }
76 },
77 "env": {
78 "PORT": "{port}",
79 "HOST": "127.0.0.1",
80 "OV_EVENTS_INTERNAL_URL": "",
81 "INTERNAL_API_KEY": "",
82 "OV_TOOLS_INTERNAL_URL": "http://127.0.0.1:9",
83 "AVATAR_PATH": "{tmp}/avatars",
84 "VALKEY_URL": "redis://127.0.0.1:9/0"
85 },
86 "dirs": [
87 "{tmp}/avatars"
88 ],
89 "compare": [
90 "/api/health",
91 "/api/.well-known/jwks",
92 "/api/brand",
93 "/api/themes?limit=500",
94 "/api/domains",
95 "/api/v1/registry/capabilities"
96 ],
97 "counts": [
98 {
99 "db": "network",
100 "table": "users"
101 },
102 {
103 "db": "network",
104 "table": "oauth_clients"
105 },
106 {
107 "db": "network",
108 "table": "wallets"
109 },
110 {
111 "db": "network",
112 "table": "coin_transactions"
113 }
114 ]
115 }
116 },
117 "live": {
118 "repo": "/opt/openvibe.live",
119 "owner": "root",
120 "runAs": "ubuntu",
121 "strategy": "release-layout",
122 "release": {
123 "git": "repo",
124 "releases": "releases",
125 "current": "current",
126 "id": "time-sha8",
127 "links": {
128 "data": "../../shared/data"
129 },
130 "keep": 5,
131 "settleSeconds": 3
132 },
133 "_strategyNote": "Live deploy/scripts/deploy.sh (release layout) as ovhost deploy live: a new release is prepared in releases/<UTC time>-<sha8> while current serves (node_modules hard-linked from the current release unless package-lock.json or dependency fields changed, then npm ci into the new release; every dependency resolves; node --check on every changed .js, JSON.parse on every changed .json), data -> ../../shared/data in each release, current switched by an atomic rename, the release's deploy/systemd/release/* unit files installed. public/, docs/ and the other noRestartPaths switch without a restart. The socket unit must be held by pid 1 on 127.0.0.1:3000: when its unit file changed or systemd does not hold the port, the service is stopped, the socket restarted and the service started (the only socket restart ovhost makes). --rollback is ovhost rollback live.",
134 "units": [
135 "openvibe-live.service"
136 ],
137 "socketUnit": "openvibe-live.socket",
138 "unitSources": {
139 "openvibe-live.service": "deploy/systemd/release/openvibe-live.service",
140 "openvibe-live.socket": "deploy/systemd/release/openvibe-live.socket",
141 "openvibe-live.service.d/socket.conf": "deploy/systemd/release/openvibe-live.service.d/socket.conf"
142 },
143 "envFile": "/etc/openvibe/live.env",
144 "env": {
145 "required": [
146 "BASE_URL",
147 "JWT_SECRET",
148 "OV_NETWORK_URL",
149 "OV_OAUTH_CLIENT_ID",
150 "OV_OAUTH_CLIENT_SECRET",
151 "MEDIA_URL",
152 "MEDIA_API_KEY",
153 "MEDIA_WEBHOOK_SECRET",
154 "DATABASE_URL",
155 "DATABASE_DIRECT_URL"
156 ]
157 },
158 "port": 3000,
159 "ready": {
160 "url": "http://127.0.0.1:3000/api/ready",
161 "timeoutSeconds": 90,
162 "release": true
163 },
164 "noRestartPaths": [
165 "public/",
166 "docs/",
167 "test/",
168 "scripts/",
169 "hardware/",
170 "deploy/nginx/",
171 "deploy/cloudflare/",
172 "deploy/fail2ban/",
173 "**/*.md"
174 ],
175 "protected": {
176 "kind": "http-json-count",
177 "url": "http://127.0.0.1:3000/api/streams",
178 "array": "streams",
179 "where": {
180 "is_live": true
181 },
182 "label": "live streams"
183 },
184 "drain": {
185 "policy": "refuse",
186 "waitMaxSeconds": 28800,
187 "pollSeconds": 60,
188 "quietChecks": 2
189 },
190 "databases": [
191 {
192 "name": "live",
193 "engine": "postgresql",
194 "database": "ov_live"
195 }
196 ],
197 "_databasesNote": "analytics.db (1.7 GB, append-only pageviews) is left out of routine backups on purpose; add it here to include it.",
198 "backupOnChange": [
199 "server/db/migrations.js",
200 "server/db/schema.sql"
201 ],
202 "preflight": {
203 "syntaxCheck": true
204 },
205 "nginx": {
206 "vhost": "openvibe.live.conf",
207 "repoVhost": "deploy/nginx/openvibe.live.conf",
208 "variant": "websocket",
209 "wsPaths": [
210 "/ws/"
211 ],
212 "maxBody": "3m"
213 },
214 "drill": {
215 "port": 13000,
216 "databases": {
217 "live": {
218 "url": "DATABASE_URL",
219 "directUrl": "DATABASE_DIRECT_URL"
220 }
221 },
222 "env": {
223 "PORT": "{port}",
224 "HOST": "127.0.0.1",
225 "LIVE_DRILL": "1",
226 "DATA_DIR": "{tmp}/data",
227 "EVENTS_URL": "",
228 "OV_OAUTH_CLIENT_SECRET": "",
229 "MEDIA_URL": "http://127.0.0.1:9",
230 "VALKEY_URL": "redis://127.0.0.1:9/0"
231 },
232 "dirs": [
233 "{tmp}/data"
234 ],
235 "requires": [
236 {
237 "file": "server/drill.js",
238 "contains": "LIVE_DRILL"
239 },
240 {
241 "file": "server/paths.js",
242 "contains": "DATA_DIR"
243 }
244 ],
245 "compare": [
246 "/api/themes",
247 "/api/emotes/global",
248 {
249 "path": "/api/streams",
250 "ignore": [
251 "viewer_count",
252 "peak_viewers",
253 "external_viewer_count",
254 "total_viewer_count",
255 "last_heartbeat",
256 "thumbnail_url"
257 ]
258 },
259 {
260 "path": "/api/streams/recently-online?limit=20",
261 "ignore": [
262 "vod_thumbnail"
263 ]
264 },
265 {
266 "path": "/api/streams/channel/japaneseoldguy/live",
267 "ignore": [
268 "viewer_count",
269 "peak_viewers",
270 "last_heartbeat",
271 "thumbnail_url",
272 "server_clip",
273 "endpoint"
274 ]
275 }
276 ],
277 "counts": [
278 {
279 "db": "live",
280 "table": "users"
281 },
282 {
283 "db": "live",
284 "table": "channels"
285 },
286 {
287 "db": "live",
288 "table": "managed_streams"
289 },
290 {
291 "db": "live",
292 "table": "streams"
293 },
294 {
295 "db": "live",
296 "table": "follows"
297 },
298 {
299 "db": "live",
300 "table": "chat_messages"
301 }
302 ]
303 }
304 },
305 "media": {
306 "repo": "/opt/openvibe.media",
307 "units": [
308 "openvibe-media.service"
309 ],
310 "unitSources": {
311 "openvibe-media.service": "deploy/systemd/openvibe-media.service"
312 },
313 "envFile": "/etc/openvibe/media.env",
314 "env": {
315 "required": [
316 "VOD_PATH",
317 "MEDIA_B2_ENDPOINT",
318 "MEDIA_B2_BUCKET",
319 "MEDIA_B2_KEY_ID",
320 "MEDIA_B2_APP_KEY",
321 "MEDIA_R2_ENDPOINT",
322 "MEDIA_R2_BUCKET",
323 "MEDIA_R2_ACCESS_KEY_ID",
324 "MEDIA_R2_SECRET_ACCESS_KEY"
325 ]
326 },
327 "port": 4100,
328 "ready": {
329 "url": "http://127.0.0.1:4100/api/ready",
330 "timeoutSeconds": 60
331 },
332 "noRestartPaths": [
333 "docs/",
334 "test/",
335 "**/*.md"
336 ],
337 "protected": {
338 "kind": "http-json-count",
339 "url": "http://127.0.0.1:4100/api/ready",
340 "field": "recordings_in_progress",
341 "addFields": [
342 "chunked_recordings_in_progress",
343 "recordings_untracked_in_progress",
344 "uploads_in_progress"
345 ],
346 "label": "recordings and uploads in progress"
347 },
348 "drain": {
349 "policy": "refuse",
350 "waitMaxSeconds": 43200,
351 "pollSeconds": 60,
352 "quietChecks": 2
353 },
354 "databases": [
355 {
356 "name": "media",
357 "engine": "postgresql",
358 "database": "ov_media"
359 }
360 ],
361 "nginx": {
362 "vhost": "openvibe.media.conf",
363 "repoVhost": "deploy/nginx/openvibe.media.conf",
364 "variant": "http"
365 },
366 "drill": {
367 "_notes": "Needs OpenVibe.Media 6e74eb0 or later (MEDIA_DRILL, server/drill.js; requires checks the deployed checkout). MEDIA_DRILL=1 refuses to start unless its database is outside the checkout and /opt/openvibe.media, HOST is loopback and PORT is not 4100. It then serves HTTP only: no app seeding or JWKS refresh, no tiering sweep, health job, junk sweep, clip re-cuts, copy verification, jobs worker, invariant scans, disk guardian, thumbnail cleanup, object purge, backfill, orphan-recording finalize or Events relay, and no webhook (the apps table's URLs are production's). It writes no file and creates no directory, and never opens a file path from the database: every byte route (/v and /c bytes, /t, /a, /f, /o, paste screenshots, live frames) answers 503; watch pages and /browse render from the copy. Every method but GET/HEAD/OPTIONS answers 403, /auth/* 503; net, fetch, child_process (git only: no ffmpeg or ffprobe), dgram and listen are guarded. /api/ready drops the storage and remote-tier checks (mode: drill). The storage paths from media.env move under {tmp}/storage (never created) as a second switch; THUMBNAILS_PATH stays production's, read-only in the sandbox, because /browse counts the thumbnail directory in its tab bar (the drill lists it and serves none of it). /browse caches its tab counts for 60 s in production: take the backup right before the drill. EVENTS_URL and the client secret are blanked too. Not run on the host yet.",
368 "port": 14100,
369 "databases": {
370 "media": {
371 "url": "DATABASE_URL",
372 "directUrl": "DATABASE_DIRECT_URL"
373 }
374 },
375 "env": {
376 "PORT": "{port}",
377 "HOST": "127.0.0.1",
378 "MEDIA_DRILL": "1",
379 "VOD_PATH": "{tmp}/storage/vods",
380 "CLIPS_PATH": "{tmp}/storage/clips",
381 "PASTES_PATH": "{tmp}/storage/pastes",
382 "ASSETS_PATH": "{tmp}/storage/assets",
383 "FILES_PATH": "{tmp}/storage/files",
384 "OBJECTS_PATH": "{tmp}/storage/objects",
385 "EVENTS_URL": "",
386 "OV_OAUTH_CLIENT_SECRET": "",
387 "VALKEY_URL": "redis://127.0.0.1:9/0"
388 },
389 "requires": [
390 {
391 "file": "server/drill.js",
392 "contains": "MEDIA_DRILL"
393 },
394 {
395 "file": "server/drill.js",
396 "contains": "DATABASE_URL"
397 },
398 {
399 "file": "server/public/routes.js",
400 "contains": "refuseBytes"
401 }
402 ],
403 "compare": [
404 "/browse?tab=videos",
405 "/browse?tab=clips"
406 ],
407 "counts": [
408 {
409 "db": "media",
410 "table": "media_objects"
411 },
412 {
413 "db": "media",
414 "table": "vods"
415 },
416 {
417 "db": "media",
418 "table": "clips"
419 },
420 {
421 "db": "media",
422 "table": "apps"
423 }
424 ]
425 }
426 },
427 "tools": {
428 "repo": "/opt/openvibe.tools",
429 "strategy": "multi-app",
430 "_strategyNote": "Tools deploy/scripts/deploy.sh as ovhost deploy tools: untracked apps/*/package-lock.json files the release tracks are removed before the merge; apps/_* are packages, not apps (skipPackages, the multi-app default); npm install where an app's lockfile or dependencies changed or node_modules is missing; every dependency resolves (reinstalled once); each app's data/ is made as ubuntu (the units' ReadWritePaths); the jobs runtime loads in img, audio and docs and the guard in every app; every openvibe-tools*.service unit file restarts (unitsMatch; one the inventory does not list is named); ready when the gateway answers /api/ready, its /release.json names the new sha and every unit is active; otherwise the checkout goes back and everything restarts again.",
431 "packages": [
432 "apps/*"
433 ],
434 "units": [
435 "openvibe-tools.service",
436 "openvibe-tools-maps.service",
437 "openvibe-tools-food.service",
438 "openvibe-tools-img.service",
439 "openvibe-tools-yt.service",
440 "openvibe-tools-audio.service",
441 "openvibe-tools-text.service",
442 "openvibe-tools-docs.service"
443 ],
444 "unitsMatch": "openvibe-tools*.service",
445 "envFile": "/etc/openvibe/tools.env",
446 "env": {
447 "required": [
448 "OV_NETWORK_URL",
449 "OV_OAUTH_CLIENT_ID",
450 "OV_OAUTH_CLIENT_SECRET",
451 "MEDIA_URL",
452 "DATABASE_URL",
453 "DATABASE_DIRECT_URL"
454 ]
455 },
456 "metrics": {
457 "extra": [
458 {
459 "name": "maps",
460 "port": 4010
461 },
462 {
463 "name": "food",
464 "port": 4011
465 },
466 {
467 "name": "img",
468 "port": 4012
469 },
470 {
471 "name": "yt",
472 "port": 4013
473 },
474 {
475 "name": "audio",
476 "port": 4014
477 },
478 {
479 "name": "text",
480 "port": 4015
481 },
482 {
483 "name": "docs",
484 "port": 4016
485 }
486 ]
487 },
488 "port": 4001,
489 "ready": {
490 "url": "http://127.0.0.1:4001/api/ready",
491 "headers": {
492 "Host": "openvibe.tools"
493 },
494 "timeoutSeconds": 60,
495 "release": true,
496 "allUnits": true
497 },
498 "noRestartPaths": [
499 "**/*.md"
500 ],
501 "preflight": {
502 "dirs": [
503 "data"
504 ],
505 "checks": [
506 {
507 "label": "jobs runtime loads",
508 "packages": [
509 "apps/img",
510 "apps/audio",
511 "apps/docs"
512 ],
513 "argv": [
514 "node",
515 "-e",
516 "require('openvibe-contracts'); require('openvibe-sdk'); require('../_shared/jobs')"
517 ]
518 },
519 {
520 "label": "guard loads",
521 "packages": "*",
522 "argv": [
523 "node",
524 "-e",
525 "require('../_shared/guard')"
526 ]
527 }
528 ]
529 },
530 "databases": [
531 {
532 "name": "tools",
533 "engine": "postgresql",
534 "database": "ov_tools"
535 }
536 ],
537 "drill": {
538 "port": 14016,
539 "unit": "openvibe-tools-docs.service",
540 "productionPort": 4016,
541 "databases": {
542 "tools": {
543 "url": "DATABASE_URL",
544 "directUrl": "DATABASE_DIRECT_URL"
545 }
546 },
547 "env": {
548 "PORT": "{port}",
549 "HOST": "127.0.0.1",
550 "UPLOADS_DIR": "{tmp}/uploads",
551 "OUTPUT_DIR": "{tmp}/output",
552 "TOOLS_JOBS_CONCURRENCY": "0",
553 "TOOLS_JOB_RESULTS": "local",
554 "EVENTS_PUBLISH": "off",
555 "EVENTS_URL": "",
556 "OV_OAUTH_CLIENT_SECRET": "",
557 "MEDIA_URL": "http://127.0.0.1:9",
558 "VALKEY_URL": "redis://127.0.0.1:9/0"
559 },
560 "dirs": [
561 "{tmp}/uploads",
562 "{tmp}/output"
563 ],
564 "requires": [
565 {
566 "file": "apps/docs/server/config.js",
567 "contains": "DATA_DIR"
568 },
569 {
570 "file": "apps/_shared/jobs/index.js",
571 "contains": "TOOLS_JOB_RESULTS"
572 },
573 {
574 "file": "apps/_shared/jobs/events.js",
575 "contains": "EVENTS_PUBLISH"
576 }
577 ],
578 "ready": "/api/ready",
579 "compare": [
580 {
581 "path": "/release.json",
582 "ignore": [
583 "booted_at"
584 ]
585 }
586 ],
587 "counts": [
588 {
589 "db": "tools",
590 "table": "tool_jobs"
591 }
592 ]
593 },
594 "protected": {
595 "kind": "postgresql-count",
596 "database": "ov_tools",
597 "sql": "SELECT count(*) AS n FROM tool_jobs WHERE state = 'running'",
598 "label": "running tool jobs"
599 },
600 "drain": {
601 "policy": "report"
602 }
603 },
604 "community": {
605 "repo": "/opt/openvibe.community",
606 "units": [
607 "openvibe-community.service"
608 ],
609 "unitSources": {
610 "openvibe-community.service": "deploy/systemd/openvibe-community.service"
611 },
612 "envFile": "/etc/openvibe/community.env",
613 "env": {
614 "required": [
615 "BASE_URL",
616 "OV_NETWORK_URL",
617 "OV_OAUTH_CLIENT_ID",
618 "OV_OAUTH_CLIENT_SECRET",
619 "OV_OAUTH_REDIRECT_URI"
620 ]
621 },
622 "port": 4200,
623 "ready": {
624 "url": "http://127.0.0.1:4200/api/ready",
625 "timeoutSeconds": 30
626 },
627 "noRestartPaths": [
628 "docs/",
629 "test/",
630 "**/*.md"
631 ],
632 "databases": [
633 {
634 "name": "community",
635 "engine": "postgresql",
636 "database": "ov_community"
637 }
638 ],
639 "nginx": {
640 "vhost": "openvibe.community.conf",
641 "repoVhost": "deploy/nginx/openvibe.community.conf",
642 "variant": "http"
643 },
644 "drill": {
645 "port": 14200,
646 "databases": {
647 "community": {
648 "url": "DATABASE_URL",
649 "directUrl": "DATABASE_DIRECT_URL"
650 }
651 },
652 "env": {
653 "PORT": "{port}",
654 "HOST": "127.0.0.1",
655 "DISCORD_RELAY_ENABLED": "0",
656 "OV_OAUTH_CLIENT_SECRET": "",
657 "OV_LIVE_INTERNAL_URL": "http://127.0.0.1:9",
658 "OV_MEDIA_INTERNAL_URL": "http://127.0.0.1:9",
659 "VALKEY_URL": "redis://127.0.0.1:9/0"
660 },
661 "compare": [
662 "/api/pastes?limit=5",
663 "/api/v1/spaces",
664 "/api/v1/pulse?limit=5"
665 ],
666 "counts": [
667 {
668 "db": "community",
669 "table": "pastes"
670 },
671 {
672 "db": "community",
673 "table": "spaces"
674 },
675 {
676 "db": "community",
677 "table": "threads"
678 },
679 {
680 "db": "community",
681 "table": "posts"
682 }
683 ]
684 }
685 },
686 "events": {
687 "_note": "Not deployed as of the 22 Sep 2026 production snapshot (no events.env on the host).",
688 "repo": "/opt/openvibe.events",
689 "units": [
690 "openvibe-events.service"
691 ],
692 "unitSources": {
693 "openvibe-events.service": "deploy/systemd/openvibe-events.service"
694 },
695 "envFile": "/etc/openvibe/events.env",
696 "env": {
697 "required": [
698 "OV_NETWORK_URL",
699 "OV_NETWORK_INTERNAL_URL"
700 ]
701 },
702 "port": 4300,
703 "ready": {
704 "url": "http://127.0.0.1:4300/api/ready",
705 "timeoutSeconds": 30
706 },
707 "noRestartPaths": [
708 "docs/",
709 "test/",
710 "**/*.md"
711 ],
712 "protected": {
713 "kind": "http-json-count",
714 "url": "http://127.0.0.1:4300/api/ready",
715 "field": "realtime_connections",
716 "label": "SSE connections"
717 },
718 "drain": {
719 "policy": "report"
720 },
721 "databases": [
722 {
723 "name": "events",
724 "engine": "postgresql",
725 "database": "ov_events"
726 }
727 ],
728 "nginx": {
729 "vhost": "events.openvibe.network.conf",
730 "repoVhost": "deploy/nginx/events.openvibe.network.conf",
731 "variant": "sse",
732 "ssePaths": [
733 "/realtime/stream"
734 ],
735 "certName": "openvibe.network"
736 },
737 "drill": {
738 "port": 14300,
739 "databases": {
740 "events": {
741 "url": "DATABASE_URL",
742 "directUrl": "DATABASE_DIRECT_URL"
743 }
744 },
745 "env": {
746 "PORT": "{port}",
747 "HOST": "127.0.0.1",
748 "EVENTS_WORKER": "off",
749 "VALKEY_URL": "redis://127.0.0.1:9/0"
750 },
751 "counts": [
752 {
753 "db": "events",
754 "table": "events"
755 },
756 {
757 "db": "events",
758 "table": "subscriptions"
759 },
760 {
761 "db": "events",
762 "table": "deliveries"
763 },
764 {
765 "db": "events",
766 "table": "consumer_checkpoints"
767 }
768 ],
769 "_notes": "EVENTS_WORKER=off stops signed deliveries to subscribers. /api/ready requires Network JWKS (read-only GET on loopback). The retention prune interval always runs but only touches the copy. No public read endpoint is stable enough to compare; row counts only."
770 }
771 },
772 "games": {
773 "repo": "/opt/openvibe.games",
774 "strategy": "pnpm-build",
775 "_strategyNote": "The production procedure (git pull; pnpm install --frozen-lockfile; pnpm build; systemctl restart openvibe-games) as ovhost deploy games, with git as the checkout owner (not sudo git), every workspace package's dependencies verified, pg and openvibe-sdk/db loaded under this Node before the restart, /api/ready polled, and on failure the checkout restored, reinstalled and rebuilt (the build writes apps/client/dist, which is served) and the unit restarted again. Players online are reported (drain report) and reconnect; --wait-idle holds until nobody plays. Games is on PostgreSQL (ov_games) since its ADR-0007 M1; nothing is tracked build output any more.",
776 "units": [
777 "openvibe-games.service"
778 ],
779 "envFile": "/etc/openvibe/games.env",
780 "port": 8000,
781 "ready": {
782 "url": "http://127.0.0.1:8000/api/ready",
783 "timeoutSeconds": 60
784 },
785 "preflight": {
786 "checks": [
787 {
788 "label": "pg and openvibe-sdk/db load under this Node",
789 "packages": [
790 "apps/server"
791 ],
792 "argv": [
793 "node",
794 "-e",
795 "require('pg'); require('openvibe-sdk/db')"
796 ]
797 }
798 ]
799 },
800 "databases": [
801 {
802 "name": "world",
803 "engine": "postgresql",
804 "database": "ov_games"
805 }
806 ],
807 "drill": {
808 "_notes": "Games M1 (ADR-0007): PostgreSQL ov_games. One HTTP listener (PORT/HOST; /ws and /editor-ws are upgrades on it), tsx runs src/main.ts with no build. The drill restores into a scratch database and sets DATABASE_URL/DATABASE_DIRECT_URL to it and VALKEY_URL to a closed port. EVENTS_PUBLISH=off stops the outbox relay, MEDIA_MIRROR=off the map-asset mirror, an empty OV_OAUTH_CLIENT_SECRET every service call. The 30 Hz tick keeps running (readiness needs it) and its write-behind flusher writes into the scratch copy only; map.json stays production's file, read-only in the sandbox. Count only tables the simulation does not write. Not run on the host since the PostgreSQL move.",
809 "port": 18000,
810 "databases": {
811 "world": {
812 "url": "DATABASE_URL",
813 "directUrl": "DATABASE_DIRECT_URL"
814 }
815 },
816 "env": {
817 "PORT": "{port}",
818 "HOST": "127.0.0.1",
819 "OV_OAUTH_CLIENT_SECRET": "",
820 "EVENTS_PUBLISH": "off",
821 "EVENTS_URL": "",
822 "MEDIA_MIRROR": "off",
823 "TMPDIR": "{tmp}/tmp",
824 "VALKEY_URL": "redis://127.0.0.1:9/0"
825 },
826 "dirs": [
827 "{tmp}/tmp"
828 ],
829 "requires": [
830 {
831 "file": "apps/server/src/config.ts",
832 "contains": "EVENTS_PUBLISH"
833 },
834 {
835 "file": "apps/server/src/config.ts",
836 "contains": "MEDIA_MIRROR"
837 }
838 ],
839 "compare": [
840 "/map.json",
841 "/api/v1/mods"
842 ],
843 "counts": [
844 {
845 "db": "world",
846 "table": "characters"
847 },
848 {
849 "db": "world",
850 "table": "mods"
851 }
852 ]
853 },
854 "protected": {
855 "kind": "http-json-count",
856 "url": "http://127.0.0.1:8000/api/ready",
857 "field": "checks.sessions.detail.online",
858 "label": "players online"
859 },
860 "drain": {
861 "policy": "report"
862 }
863 },
864 "host": {
865 "_note": "OpenVibe.Host hosting comparison catalog on port 4910; PostgreSQL database ov_host.",
866 "repo": "/opt/openvibe.host",
867 "units": [
868 "openvibe-host.service"
869 ],
870 "unitSources": {
871 "openvibe-host.service": "deploy/systemd/openvibe-host.service",
872 "openvibe-.service.d/10-database-order.conf": "deploy/systemd/openvibe-.service.d/10-database-order.conf",
873 "openre-.service.d/10-database-order.conf": "deploy/systemd/openre-.service.d/10-database-order.conf",
874 "pgbouncer.service.d/10-after-postgresql.conf": "deploy/systemd/pgbouncer.service.d/10-after-postgresql.conf"
875 },
876 "envFile": "/etc/openvibe/host.env",
877 "env": {
878 "required": [
879 "BASE_URL",
880 "OV_NETWORK_URL",
881 "OV_OAUTH_CLIENT_ID",
882 "OV_OAUTH_CLIENT_SECRET",
883 "HOST_FORM_SECRET"
884 ]
885 },
886 "port": 4910,
887 "nginx": {
888 "vhost": "openvibe.host.conf",
889 "repoVhost": "deploy/nginx/openvibe.host.conf",
890 "variant": "http"
891 },
892 "ready": {
893 "url": "http://127.0.0.1:4910/api/ready",
894 "timeoutSeconds": 30
895 },
896 "noRestartPaths": [
897 "docs/",
898 "test/",
899 "**/*.md"
900 ],
901 "databases": [
902 {
903 "name": "host",
904 "engine": "postgresql",
905 "database": "ov_host"
906 }
907 ],
908 "drill": {
909 "port": 14910,
910 "databases": {
911 "host": {
912 "url": "DATABASE_URL",
913 "directUrl": "DATABASE_DIRECT_URL"
914 }
915 },
916 "env": {
917 "PORT": "{port}",
918 "HOST": "127.0.0.1",
919 "HOST_WORKER": "off",
920 "EVENTS_URL": "",
921 "OV_OAUTH_CLIENT_SECRET": "",
922 "VALKEY_URL": "redis://127.0.0.1:9/0"
923 },
924 "counts": [
925 {
926 "db": "host",
927 "table": "catalog_providers"
928 },
929 {
930 "db": "host",
931 "table": "catalog_plans"
932 },
933 {
934 "db": "host",
935 "table": "catalog_promos"
936 }
937 ],
938 "_notes": "Restores the ov_host catalog database and checks provider, plan and promotion counts."
939 }
940 },
941 "billing": {
942 "_note": "Not deployed as of the 22 Sep 2026 production snapshot; unit, env file, port and database from the repository's deploy/ directory.",
943 "repo": "/opt/openvibe.billing",
944 "units": [
945 "openvibe-billing.service"
946 ],
947 "unitSources": {
948 "openvibe-billing.service": "deploy/systemd/openvibe-billing.service"
949 },
950 "envFile": "/etc/openvibe/billing.env",
951 "env": {
952 "required": "from-example"
953 },
954 "port": 4600,
955 "ready": {
956 "url": "http://127.0.0.1:4600/api/ready",
957 "timeoutSeconds": 30
958 },
959 "noRestartPaths": [
960 "docs/",
961 "test/",
962 "**/*.md"
963 ],
964 "databases": [
965 {
966 "name": "billing",
967 "engine": "postgresql",
968 "database": "ov_billing"
969 }
970 ],
971 "drill": {
972 "port": 14600,
973 "databases": {
974 "billing": {
975 "url": "DATABASE_URL",
976 "directUrl": "DATABASE_DIRECT_URL"
977 }
978 },
979 "env": {
980 "PORT": "{port}",
981 "HOST": "127.0.0.1",
982 "BILLING_JOBS": "off",
983 "EVENTS_URL": "",
984 "OV_OAUTH_CLIENT_SECRET": "",
985 "VALKEY_URL": "redis://127.0.0.1:9/0"
986 },
987 "compare": [
988 "/api/v1/rates"
989 ],
990 "counts": [
991 {
992 "db": "billing",
993 "table": "accounts"
994 },
995 {
996 "db": "billing",
997 "table": "transactions"
998 },
999 {
1000 "db": "billing",
1001 "table": "ledger_entries"
1002 },
1003 {
1004 "db": "billing",
1005 "table": "subscriptions"
1006 },
1007 {
1008 "db": "billing",
1009 "table": "payment_intents"
1010 }
1011 ],
1012 "_notes": "BILLING_JOBS=off stops provider-event settlement, the renewal sweep and the outbox relay. Payment providers are called only on incoming requests (and are remote, which the sandbox blocks). /api/health is not compared: it reports the relay state, which the drill turns off."
1013 }
1014 },
1015 "chat": {
1016 "_note": "Not deployed as of the 22 Sep 2026 production snapshot; unit, env file, port and database path from the repository's deploy/ directory.",
1017 "repo": "/opt/openvibe.chat",
1018 "units": [
1019 "openvibe-chat.service"
1020 ],
1021 "unitSources": {
1022 "openvibe-chat.service": "deploy/systemd/openvibe-chat.service"
1023 },
1024 "envFile": "/etc/openvibe/chat.env",
1025 "env": {
1026 "required": "from-example"
1027 },
1028 "port": 4400,
1029 "ready": {
1030 "url": "http://127.0.0.1:4400/ready",
1031 "timeoutSeconds": 30
1032 },
1033 "noRestartPaths": [
1034 "docs/",
1035 "test/",
1036 "**/*.md"
1037 ],
1038 "databases": [
1039 {
1040 "name": "chat",
1041 "engine": "postgresql",
1042 "database": "ov_chat"
1043 }
1044 ],
1045 "drill": {
1046 "port": 14400,
1047 "databases": {
1048 "chat": {
1049 "url": "DATABASE_URL",
1050 "directUrl": "DATABASE_DIRECT_URL"
1051 }
1052 },
1053 "env": {
1054 "PORT": "{port}",
1055 "HOST": "127.0.0.1",
1056 "EVENTS_URL": "",
1057 "LIVE_MIRROR": "0",
1058 "OV_OAUTH_CLIENT_SECRET": "",
1059 "OV_LIVE_INTERNAL_URL": "http://127.0.0.1:9",
1060 "CHAT_CACHE_DIR": "{tmp}/cache",
1061 "SOUNDS_PATH": "{tmp}/sounds",
1062 "VALKEY_URL": "redis://127.0.0.1:9/0"
1063 },
1064 "dirs": [
1065 "{tmp}/cache",
1066 "{tmp}/sounds"
1067 ],
1068 "compare": [
1069 "/api/chat/filters/friendly"
1070 ],
1071 "counts": [
1072 {
1073 "db": "chat",
1074 "table": "dm_conversations"
1075 },
1076 {
1077 "db": "chat",
1078 "table": "dm_messages"
1079 },
1080 {
1081 "db": "chat",
1082 "table": "emotes"
1083 },
1084 {
1085 "db": "chat",
1086 "table": "moderation_actions"
1087 }
1088 ]
1089 },
1090 "protected": {
1091 "kind": "http-json-count",
1092 "url": "http://127.0.0.1:4400/ready",
1093 "field": "connections",
1094 "label": "chat sockets"
1095 },
1096 "drain": {
1097 "policy": "report"
1098 }
1099 },
1100 "search": {
1101 "_note": "Not deployed as of the 22 Sep 2026 production snapshot; unit, env file, port and database from the repository's deploy/ directory.",
1102 "repo": "/opt/openvibe.search",
1103 "units": [
1104 "openvibe-search.service"
1105 ],
1106 "unitSources": {
1107 "openvibe-search.service": "deploy/systemd/openvibe-search.service"
1108 },
1109 "envFile": "/etc/openvibe/search.env",
1110 "env": {
1111 "required": "from-example"
1112 },
1113 "port": 4710,
1114 "ready": {
1115 "url": "http://127.0.0.1:4710/api/ready",
1116 "timeoutSeconds": 30
1117 },
1118 "noRestartPaths": [
1119 "docs/",
1120 "test/",
1121 "**/*.md"
1122 ],
1123 "databases": [
1124 {
1125 "name": "search",
1126 "engine": "postgresql",
1127 "database": "ov_search"
1128 }
1129 ],
1130 "drill": {
1131 "port": 14710,
1132 "databases": {
1133 "search": {
1134 "url": "DATABASE_URL",
1135 "directUrl": "DATABASE_DIRECT_URL"
1136 }
1137 },
1138 "env": {
1139 "PORT": "{port}",
1140 "HOST": "127.0.0.1",
1141 "EVENTS_URL": "",
1142 "SEARCH_EVENTS_SECRET": "",
1143 "OV_OAUTH_CLIENT_SECRET": "",
1144 "VALKEY_URL": "redis://127.0.0.1:9/0"
1145 },
1146 "compare": [
1147 "/api/v1/search?q=openvibe&limit=10",
1148 "/api/v1/suggest?q=open&limit=10"
1149 ],
1150 "counts": [
1151 {
1152 "db": "search",
1153 "table": "documents"
1154 },
1155 {
1156 "db": "search",
1157 "table": "doc_acl"
1158 },
1159 {
1160 "db": "search",
1161 "table": "doc_facets"
1162 }
1163 ],
1164 "_notes": "/api/ready needs the Network public key (read-only JWKS GET on loopback). No switch: the 6-hourly outbox prune (copy only)."
1165 }
1166 },
1167 "sources": {
1168 "_note": "Not deployed as of the 22 Sep 2026 production snapshot; unit, env file, port and database from the repository's deploy/ directory.",
1169 "repo": "/opt/openvibe.sources",
1170 "units": [
1171 "openvibe-sources.service"
1172 ],
1173 "unitSources": {
1174 "openvibe-sources.service": "deploy/systemd/openvibe-sources.service"
1175 },
1176 "envFile": "/etc/openvibe/sources.env",
1177 "env": {
1178 "required": "from-example"
1179 },
1180 "port": 4720,
1181 "ready": {
1182 "url": "http://127.0.0.1:4720/api/ready",
1183 "timeoutSeconds": 30
1184 },
1185 "noRestartPaths": [
1186 "docs/",
1187 "test/",
1188 "**/*.md"
1189 ],
1190 "databases": [
1191 {
1192 "name": "sources",
1193 "engine": "postgresql",
1194 "database": "ov_sources"
1195 }
1196 ],
1197 "drill": {
1198 "port": 14720,
1199 "databases": {
1200 "sources": {
1201 "url": "DATABASE_URL",
1202 "directUrl": "DATABASE_DIRECT_URL"
1203 }
1204 },
1205 "env": {
1206 "PORT": "{port}",
1207 "HOST": "127.0.0.1",
1208 "SOURCES_WORKER": "off",
1209 "EVENTS_URL": "",
1210 "OV_OAUTH_CLIENT_SECRET": "",
1211 "VALKEY_URL": "redis://127.0.0.1:9/0"
1212 },
1213 "counts": [
1214 {
1215 "db": "sources",
1216 "table": "sources"
1217 },
1218 {
1219 "db": "sources",
1220 "table": "items"
1221 },
1222 {
1223 "db": "sources",
1224 "table": "item_revisions"
1225 },
1226 {
1227 "db": "sources",
1228 "table": "fetch_runs"
1229 }
1230 ],
1231 "_notes": "SOURCES_WORKER=off stops the crawler (feeds, sitemaps, robots.txt; production may allow loopback sources). Every /api/v1 read needs a service token, so row counts only."
1232 }
1233 },
1234 "wiki": {
1235 "_note": "Not deployed as of the 22 Sep 2026 production snapshot; unit, env file, port and database from the repository's deploy/ directory.",
1236 "repo": "/opt/openvibe.wiki",
1237 "units": [
1238 "openvibe-wiki.service"
1239 ],
1240 "unitSources": {
1241 "openvibe-wiki.service": "deploy/systemd/openvibe-wiki.service"
1242 },
1243 "envFile": "/etc/openvibe/wiki.env",
1244 "env": {
1245 "required": "from-example"
1246 },
1247 "port": 4800,
1248 "ready": {
1249 "url": "http://127.0.0.1:4800/api/ready",
1250 "timeoutSeconds": 30
1251 },
1252 "noRestartPaths": [
1253 "docs/",
1254 "test/",
1255 "**/*.md"
1256 ],
1257 "databases": [
1258 {
1259 "name": "wiki",
1260 "engine": "postgresql",
1261 "database": "ov_wiki"
1262 }
1263 ],
1264 "drill": {
1265 "port": 14800,
1266 "databases": {
1267 "wiki": {
1268 "url": "DATABASE_URL",
1269 "directUrl": "DATABASE_DIRECT_URL"
1270 }
1271 },
1272 "env": {
1273 "PORT": "{port}",
1274 "HOST": "127.0.0.1",
1275 "EVENTS_URL": "",
1276 "OV_OAUTH_CLIENT_SECRET": "",
1277 "OV_MEDIA_INTERNAL_URL": "",
1278 "OV_SOURCES_INTERNAL_URL": "",
1279 "WIKI_SCHEDULE_INTERVAL_MS": "2147483647",
1280 "VALKEY_URL": "redis://127.0.0.1:9/0"
1281 },
1282 "compare": [
1283 "/api/v1/spaces",
1284 "/feed.json"
1285 ],
1286 "counts": [
1287 {
1288 "db": "wiki",
1289 "table": "wiki_spaces"
1290 },
1291 {
1292 "db": "wiki",
1293 "table": "wiki_pages"
1294 },
1295 {
1296 "db": "wiki",
1297 "table": "wiki_page_revisions"
1298 }
1299 ],
1300 "_notes": "The schedule worker has no off switch; the interval is set to the largest value Node accepts (anything larger becomes 1 ms). An empty client secret also stops Community thread creation on page views."
1301 }
1302 },
1303 "blog": {
1304 "_note": "Not deployed as of the 22 Sep 2026 production snapshot; unit, env file, port and database from the repository's deploy/ directory.",
1305 "repo": "/opt/openvibe.blog",
1306 "units": [
1307 "openvibe-blog.service"
1308 ],
1309 "unitSources": {
1310 "openvibe-blog.service": "deploy/systemd/openvibe-blog.service"
1311 },
1312 "envFile": "/etc/openvibe/blog.env",
1313 "env": {
1314 "required": "from-example"
1315 },
1316 "port": 4810,
1317 "ready": {
1318 "url": "http://127.0.0.1:4810/api/ready",
1319 "timeoutSeconds": 30
1320 },
1321 "noRestartPaths": [
1322 "docs/",
1323 "test/",
1324 "**/*.md"
1325 ],
1326 "databases": [
1327 {
1328 "name": "blog",
1329 "engine": "postgresql",
1330 "database": "ov_blog"
1331 }
1332 ],
1333 "drill": {
1334 "port": 14810,
1335 "databases": {
1336 "blog": {
1337 "url": "DATABASE_URL",
1338 "directUrl": "DATABASE_DIRECT_URL"
1339 }
1340 },
1341 "env": {
1342 "PORT": "{port}",
1343 "HOST": "127.0.0.1",
1344 "BLOG_WORKER": "off",
1345 "EVENTS_URL": "",
1346 "OV_OAUTH_CLIENT_SECRET": "",
1347 "OV_MEDIA_INTERNAL_URL": "http://127.0.0.1:9",
1348 "OV_COMMUNITY_INTERNAL_URL": "http://127.0.0.1:9",
1349 "VALKEY_URL": "redis://127.0.0.1:9/0"
1350 },
1351 "compare": [
1352 "/api/v1/blogs/openvibe/posts?limit=10",
1353 "/feed.json"
1354 ],
1355 "counts": [
1356 {
1357 "db": "blog",
1358 "table": "blogs"
1359 },
1360 {
1361 "db": "blog",
1362 "table": "blog_posts"
1363 },
1364 {
1365 "db": "blog",
1366 "table": "blog_post_revisions"
1367 }
1368 ],
1369 "_notes": "An empty client secret stops Community thread creation and Network identity lookups on page reads; author names then come from the subject_projections cache in the copy."
1370 }
1371 },
1372 "tips": {
1373 "_note": "Not deployed as of the 22 Sep 2026 production snapshot; unit, env file, port and database from the repository's deploy/ directory.",
1374 "repo": "/opt/openvibe.tips",
1375 "units": [
1376 "openvibe-tips.service"
1377 ],
1378 "unitSources": {
1379 "openvibe-tips.service": "deploy/systemd/openvibe-tips.service"
1380 },
1381 "envFile": "/etc/openvibe/tips.env",
1382 "env": {
1383 "required": "from-example"
1384 },
1385 "port": 4610,
1386 "ready": {
1387 "url": "http://127.0.0.1:4610/api/ready",
1388 "timeoutSeconds": 30
1389 },
1390 "noRestartPaths": [
1391 "docs/",
1392 "test/",
1393 "**/*.md"
1394 ],
1395 "databases": [
1396 {
1397 "name": "tips",
1398 "engine": "postgresql",
1399 "database": "ov_tips"
1400 }
1401 ],
1402 "drill": {
1403 "port": 14610,
1404 "databases": {
1405 "tips": {
1406 "url": "DATABASE_URL",
1407 "directUrl": "DATABASE_DIRECT_URL"
1408 }
1409 },
1410 "env": {
1411 "PORT": "{port}",
1412 "HOST": "127.0.0.1",
1413 "TIPS_JOBS": "off",
1414 "EVENTS_URL": "",
1415 "TIPS_EVENTS_SECRET": "",
1416 "TIPS_CHAT_ADAPTER": "none",
1417 "OV_OAUTH_CLIENT_SECRET": "",
1418 "BILLING_URL": "http://127.0.0.1:9",
1419 "LIVE_INTERNAL_URL": "http://127.0.0.1:9",
1420 "VALKEY_URL": "redis://127.0.0.1:9/0"
1421 },
1422 "compare": [
1423 "/sitemap.xml"
1424 ],
1425 "counts": [
1426 {
1427 "db": "tips",
1428 "table": "creator_tip_profiles"
1429 },
1430 {
1431 "db": "tips",
1432 "table": "tip_interactions"
1433 },
1434 {
1435 "db": "tips",
1436 "table": "tip_goals"
1437 }
1438 ],
1439 "_notes": "Money: TIPS_JOBS=off stops due Billing transfers and chat deliveries, BILLING_URL points at a closed port (an empty value falls back to production Billing), TIPS_EVENTS_SECRET empty refuses funding webhooks. /api/ready needs the Network public key (read-only JWKS GET on loopback)."
1440 }
1441 },
1442 "vip": {
1443 "_note": "Not deployed as of the 22 Sep 2026 production snapshot; unit, env file, port and database from the repository's deploy/ directory.",
1444 "repo": "/opt/openvibe.vip",
1445 "units": [
1446 "openvibe-vip.service"
1447 ],
1448 "unitSources": {
1449 "openvibe-vip.service": "deploy/systemd/openvibe-vip.service"
1450 },
1451 "envFile": "/etc/openvibe/vip.env",
1452 "env": {
1453 "required": "from-example"
1454 },
1455 "port": 4620,
1456 "ready": {
1457 "url": "http://127.0.0.1:4620/api/ready",
1458 "timeoutSeconds": 30
1459 },
1460 "noRestartPaths": [
1461 "docs/",
1462 "test/",
1463 "**/*.md"
1464 ],
1465 "databases": [
1466 {
1467 "name": "vip",
1468 "engine": "postgresql",
1469 "database": "ov_vip"
1470 }
1471 ],
1472 "drill": {
1473 "port": 14620,
1474 "databases": {
1475 "vip": {
1476 "url": "DATABASE_URL",
1477 "directUrl": "DATABASE_DIRECT_URL"
1478 }
1479 },
1480 "env": {
1481 "PORT": "{port}",
1482 "HOST": "127.0.0.1",
1483 "VIP_JOBS": "off",
1484 "EVENTS_URL": "",
1485 "VIP_EVENTS_SECRET": "",
1486 "OV_OAUTH_CLIENT_SECRET": "",
1487 "BILLING_URL": "http://127.0.0.1:9",
1488 "VIP_CHECKOUT_PROVIDERS": "",
1489 "VALKEY_URL": "redis://127.0.0.1:9/0"
1490 },
1491 "compare": [
1492 "/api/v1/creators/network",
1493 "/api/v1/plans?creator=network",
1494 "/api/v1/perks?creator=network"
1495 ],
1496 "counts": [
1497 {
1498 "db": "vip",
1499 "table": "vip_creators"
1500 },
1501 {
1502 "db": "vip",
1503 "table": "vip_plans"
1504 },
1505 {
1506 "db": "vip",
1507 "table": "vip_memberships"
1508 }
1509 ],
1510 "_notes": "VIP_FORM_SECRET must stay as production sets it (the service exits without it). BILLING_URL points at a closed port: the Billing client has no off switch and an empty value falls back to production Billing."
1511 }
1512 },
1513 "news": {
1514 "_note": "Not deployed as of the 22 Sep 2026 production snapshot; unit, env file, port and database from the repository's deploy/ directory.",
1515 "repo": "/opt/openvibe.news",
1516 "units": [
1517 "openvibe-news.service"
1518 ],
1519 "unitSources": {
1520 "openvibe-news.service": "deploy/systemd/openvibe-news.service"
1521 },
1522 "envFile": "/etc/openvibe/news.env",
1523 "env": {
1524 "required": "from-example"
1525 },
1526 "port": 4820,
1527 "ready": {
1528 "url": "http://127.0.0.1:4820/api/ready",
1529 "timeoutSeconds": 30
1530 },
1531 "noRestartPaths": [
1532 "docs/",
1533 "test/",
1534 "**/*.md"
1535 ],
1536 "databases": [
1537 {
1538 "name": "news",
1539 "engine": "postgresql",
1540 "database": "ov_news"
1541 }
1542 ],
1543 "drill": {
1544 "port": 14820,
1545 "databases": {
1546 "news": {
1547 "url": "DATABASE_URL",
1548 "directUrl": "DATABASE_DIRECT_URL"
1549 }
1550 },
1551 "env": {
1552 "PORT": "{port}",
1553 "HOST": "127.0.0.1",
1554 "NEWS_WORKER": "off",
1555 "EVENTS_URL": "",
1556 "NEWS_EVENTS_SECRET": "",
1557 "OV_AI_INTERNAL_URL": "",
1558 "OV_OAUTH_CLIENT_SECRET": "",
1559 "OV_SOURCES_INTERNAL_URL": "http://127.0.0.1:9",
1560 "VALKEY_URL": "redis://127.0.0.1:9/0"
1561 },
1562 "compare": [
1563 "/api/v1/stories?limit=10",
1564 "/api/v1/topics"
1565 ],
1566 "counts": [
1567 {
1568 "db": "news",
1569 "table": "news_topics"
1570 },
1571 {
1572 "db": "news",
1573 "table": "news_stories"
1574 },
1575 {
1576 "db": "news",
1577 "table": "news_source_items"
1578 }
1579 ]
1580 }
1581 },
1582 "reviews": {
1583 "_note": "Not deployed as of the 22 Sep 2026 production snapshot; unit, env file, port and database from the repository's deploy/ directory.",
1584 "repo": "/opt/openvibe.reviews",
1585 "units": [
1586 "openvibe-reviews.service"
1587 ],
1588 "unitSources": {
1589 "openvibe-reviews.service": "deploy/systemd/openvibe-reviews.service"
1590 },
1591 "envFile": "/etc/openvibe/reviews.env",
1592 "env": {
1593 "required": "from-example"
1594 },
1595 "port": 4830,
1596 "ready": {
1597 "url": "http://127.0.0.1:4830/api/ready",
1598 "timeoutSeconds": 30
1599 },
1600 "noRestartPaths": [
1601 "docs/",
1602 "test/",
1603 "**/*.md"
1604 ],
1605 "databases": [
1606 {
1607 "name": "reviews",
1608 "engine": "postgresql",
1609 "database": "ov_reviews"
1610 }
1611 ],
1612 "drill": {
1613 "port": 14830,
1614 "databases": {
1615 "reviews": {
1616 "url": "DATABASE_URL",
1617 "directUrl": "DATABASE_DIRECT_URL"
1618 }
1619 },
1620 "env": {
1621 "PORT": "{port}",
1622 "HOST": "127.0.0.1",
1623 "REVIEWS_SOURCES_SYNC": "false",
1624 "EVENTS_URL": "",
1625 "REVIEWS_EVENTS_SECRET": "",
1626 "OV_OAUTH_CLIENT_SECRET": "",
1627 "OV_SOURCES_INTERNAL_URL": "",
1628 "VALKEY_URL": "redis://127.0.0.1:9/0"
1629 },
1630 "compare": [
1631 "/api/v1/entities?limit=10",
1632 "/feed.json"
1633 ],
1634 "counts": [
1635 {
1636 "db": "reviews",
1637 "table": "review_entities"
1638 },
1639 {
1640 "db": "reviews",
1641 "table": "review_signals"
1642 },
1643 {
1644 "db": "reviews",
1645 "table": "review_summaries"
1646 }
1647 ],
1648 "_notes": "reconcileIndex() at boot may write index revisions and outbox rows into the copy (no switch; copy only)."
1649 }
1650 },
1651 "deals": {
1652 "_note": "Not deployed as of the 22 Sep 2026 production snapshot; unit, env file, port and database from the repository's deploy/ directory.",
1653 "repo": "/opt/openvibe.deals",
1654 "units": [
1655 "openvibe-deals.service"
1656 ],
1657 "unitSources": {
1658 "openvibe-deals.service": "deploy/systemd/openvibe-deals.service"
1659 },
1660 "envFile": "/etc/openvibe/deals.env",
1661 "env": {
1662 "required": "from-example"
1663 },
1664 "port": 4840,
1665 "ready": {
1666 "url": "http://127.0.0.1:4840/api/ready",
1667 "timeoutSeconds": 30
1668 },
1669 "noRestartPaths": [
1670 "docs/",
1671 "test/",
1672 "**/*.md"
1673 ],
1674 "databases": [
1675 {
1676 "name": "deals",
1677 "engine": "postgresql",
1678 "database": "ov_deals"
1679 }
1680 ],
1681 "drill": {
1682 "port": 14840,
1683 "databases": {
1684 "deals": {
1685 "url": "DATABASE_URL",
1686 "directUrl": "DATABASE_DIRECT_URL"
1687 }
1688 },
1689 "env": {
1690 "PORT": "{port}",
1691 "HOST": "127.0.0.1",
1692 "DEALS_WORKER": "off",
1693 "DEALS_IMPORT": "off",
1694 "EVENTS_URL": "",
1695 "DEALS_EVENTS_SECRET": "",
1696 "OV_OAUTH_CLIENT_SECRET": "",
1697 "OV_COMMUNITY_INTERNAL_URL": "http://127.0.0.1:9",
1698 "OV_SOURCES_INTERNAL_URL": "http://127.0.0.1:9",
1699 "VALKEY_URL": "redis://127.0.0.1:9/0"
1700 },
1701 "compare": [
1702 {
1703 "path": "/api/v1/offers?sort=new&page=1",
1704 "ignore": [
1705 "age_ms"
1706 ]
1707 }
1708 ],
1709 "counts": [
1710 {
1711 "db": "deals",
1712 "table": "deal_offers"
1713 },
1714 {
1715 "db": "deals",
1716 "table": "deal_products"
1717 },
1718 {
1719 "db": "deals",
1720 "table": "deal_votes"
1721 }
1722 ],
1723 "_notes": "The client secret MUST be empty: with it, an anonymous GET /d/:slug can create a thread in production Community. Offers carry freshness.age_ms (now - observed_at), ignored in the comparison."
1724 }
1725 },
1726 "coupons": {
1727 "_note": "Not deployed as of the 22 Sep 2026 production snapshot; unit, env file, port and database from the repository's deploy/ directory.",
1728 "repo": "/opt/openvibe.coupons",
1729 "units": [
1730 "openvibe-coupons.service"
1731 ],
1732 "unitSources": {
1733 "openvibe-coupons.service": "deploy/systemd/openvibe-coupons.service"
1734 },
1735 "envFile": "/etc/openvibe/coupons.env",
1736 "env": {
1737 "required": "from-example"
1738 },
1739 "port": 4850,
1740 "ready": {
1741 "url": "http://127.0.0.1:4850/api/ready",
1742 "timeoutSeconds": 30
1743 },
1744 "noRestartPaths": [
1745 "docs/",
1746 "test/",
1747 "**/*.md"
1748 ],
1749 "databases": [
1750 {
1751 "name": "coupons",
1752 "engine": "postgresql",
1753 "database": "ov_coupons"
1754 }
1755 ],
1756 "drill": {
1757 "port": 14850,
1758 "databases": {
1759 "coupons": {
1760 "url": "DATABASE_URL",
1761 "directUrl": "DATABASE_DIRECT_URL"
1762 }
1763 },
1764 "env": {
1765 "PORT": "{port}",
1766 "HOST": "127.0.0.1",
1767 "COUPONS_WORKER": "off",
1768 "EVENTS_URL": "",
1769 "OV_OAUTH_CLIENT_SECRET": "",
1770 "OV_SOURCES_INTERNAL_URL": "",
1771 "VALKEY_URL": "redis://127.0.0.1:9/0"
1772 },
1773 "compare": [
1774 "/sitemaps/merchants.xml",
1775 "/feed.json"
1776 ],
1777 "counts": [
1778 {
1779 "db": "coupons",
1780 "table": "coupons"
1781 },
1782 {
1783 "db": "coupons",
1784 "table": "coupon_merchants"
1785 },
1786 {
1787 "db": "coupons",
1788 "table": "coupon_merchant_domains"
1789 }
1790 ]
1791 }
1792 },
1793 "trade": {
1794 "_note": "Not deployed as of the 22 Sep 2026 production snapshot; unit, env file, port and database from the repository's deploy/ directory.",
1795 "repo": "/opt/openvibe.trade",
1796 "units": [
1797 "openvibe-trade.service"
1798 ],
1799 "unitSources": {
1800 "openvibe-trade.service": "deploy/systemd/openvibe-trade.service"
1801 },
1802 "envFile": "/etc/openvibe/trade.env",
1803 "env": {
1804 "required": "from-example"
1805 },
1806 "port": 4860,
1807 "ready": {
1808 "url": "http://127.0.0.1:4860/api/ready",
1809 "timeoutSeconds": 30
1810 },
1811 "noRestartPaths": [
1812 "docs/",
1813 "test/",
1814 "**/*.md"
1815 ],
1816 "databases": [
1817 {
1818 "name": "trade",
1819 "engine": "postgresql",
1820 "database": "ov_trade"
1821 }
1822 ],
1823 "drill": {
1824 "port": 14860,
1825 "databases": {
1826 "trade": {
1827 "url": "DATABASE_URL",
1828 "directUrl": "DATABASE_DIRECT_URL"
1829 }
1830 },
1831 "env": {
1832 "PORT": "{port}",
1833 "HOST": "127.0.0.1",
1834 "TRADE_WORKER": "off",
1835 "EVENTS_URL": "",
1836 "TRADE_EVENTS_WEBHOOK_SECRET": "",
1837 "OV_OAUTH_CLIENT_SECRET": "",
1838 "OV_SOURCES_INTERNAL_URL": "http://127.0.0.1:9",
1839 "VALKEY_URL": "redis://127.0.0.1:9/0"
1840 },
1841 "compare": [
1842 "/api/v1/instruments?limit=10",
1843 "/feed.json"
1844 ],
1845 "counts": [
1846 {
1847 "db": "trade",
1848 "table": "trade_instruments"
1849 },
1850 {
1851 "db": "trade",
1852 "table": "trade_market_observations"
1853 },
1854 {
1855 "db": "trade",
1856 "table": "trade_source_documents"
1857 }
1858 ]
1859 }
1860 },
1861 "codes": {
1862 "_note": "Not deployed as of the 22 Sep 2026 production snapshot; unit, env file, port and database from the repository's deploy/ directory.",
1863 "repo": "/opt/openvibe.codes",
1864 "units": [
1865 "openvibe-codes.service"
1866 ],
1867 "unitSources": {
1868 "openvibe-codes.service": "deploy/systemd/openvibe-codes.service"
1869 },
1870 "envFile": "/etc/openvibe/codes.env",
1871 "env": {
1872 "required": "from-example"
1873 },
1874 "port": 4900,
1875 "ready": {
1876 "url": "http://127.0.0.1:4900/api/ready",
1877 "timeoutSeconds": 30
1878 },
1879 "noRestartPaths": [
1880 "docs/",
1881 "test/",
1882 "**/*.md"
1883 ],
1884 "databases": [
1885 {
1886 "name": "codes",
1887 "engine": "postgresql",
1888 "database": "ov_codes"
1889 }
1890 ],
1891 "drill": {
1892 "port": 14900,
1893 "databases": {
1894 "codes": {
1895 "url": "DATABASE_URL",
1896 "directUrl": "DATABASE_DIRECT_URL"
1897 }
1898 },
1899 "env": {
1900 "PORT": "{port}",
1901 "HOST": "127.0.0.1",
1902 "EVENTS_URL": "",
1903 "OV_OAUTH_CLIENT_SECRET": "",
1904 "VALKEY_URL": "redis://127.0.0.1:9/0"
1905 },
1906 "compare": [
1907 "/sitemap.xml"
1908 ],
1909 "counts": [
1910 {
1911 "db": "codes",
1912 "table": "manifests"
1913 },
1914 {
1915 "db": "codes",
1916 "table": "releases"
1917 },
1918 {
1919 "db": "codes",
1920 "table": "trust"
1921 }
1922 ],
1923 "_notes": "Left on (read-only): JWKS and registry GETs to Network on loopback. The docs pages carry their boot time, so they are not compared."
1924 }
1925 },
1926 "ai": {
1927 "repo": "/opt/openvibe.ai",
1928 "units": [
1929 "openvibe-ai.service"
1930 ],
1931 "unitSources": {
1932 "openvibe-ai.service": "deploy/systemd/openvibe-ai.service"
1933 },
1934 "envFile": "/etc/openvibe/ai.env",
1935 "env": {
1936 "required": "from-example"
1937 },
1938 "port": 4700,
1939 "ready": {
1940 "url": "http://127.0.0.1:4700/api/ready",
1941 "timeoutSeconds": 30
1942 },
1943 "noRestartPaths": [
1944 "docs/",
1945 "test/",
1946 "**/*.md"
1947 ],
1948 "databases": [
1949 {
1950 "name": "ai",
1951 "engine": "postgresql",
1952 "database": "ov_ai"
1953 }
1954 ],
1955 "drill": {
1956 "_notes": "Every provider key, base URL and the HTTP seam are blanked and AI_ENABLED=0, so no run can reach a paid provider; OV_OAUTH_CLIENT_SECRET and EVENTS_URL are blanked (no service calls, no events); WHISPER_BIN is blanked (no transcription). Boot marks interrupted runs failed in the restored copy only. Counts cover configuration and citations only: runs, requests and usage grow by the minute, so the dump is checked by pg_restore --list; these rows are not compared by count. Passed on the host 2026-09-24 05:40 UTC.",
1957 "port": 14700,
1958 "databases": {
1959 "ai": {
1960 "url": "DATABASE_URL",
1961 "directUrl": "DATABASE_DIRECT_URL"
1962 }
1963 },
1964 "env": {
1965 "PORT": "{port}",
1966 "HOST": "127.0.0.1",
1967 "BASE_URL": "http://127.0.0.1:{port}",
1968 "AI_ENABLED": "0",
1969 "AI_PROVIDER": "",
1970 "AI_API_KEY": "",
1971 "AI_BASE_URL": "",
1972 "AI_MODEL": "",
1973 "AI_FALLBACK_PROVIDER": "",
1974 "AI_FALLBACK_API_KEY": "",
1975 "AI_FALLBACK_BASE_URL": "",
1976 "AI_HTTP_SEAM_URL": "",
1977 "WHISPER_BIN": "",
1978 "OV_OAUTH_CLIENT_SECRET": "",
1979 "EVENTS_URL": "",
1980 "AI_STUB_FALLBACK": "1",
1981 "VALKEY_URL": "redis://127.0.0.1:9/0"
1982 },
1983 "compare": [
1984 "/api/health"
1985 ],
1986 "counts": [
1987 {
1988 "db": "ai",
1989 "table": "providers"
1990 },
1991 {
1992 "db": "ai",
1993 "table": "models"
1994 },
1995 {
1996 "db": "ai",
1997 "table": "routes"
1998 },
1999 {
2000 "db": "ai",
2001 "table": "templates"
2002 },
2003 {
2004 "db": "ai",
2005 "table": "workflows"
2006 },
2007 {
2008 "db": "ai",
2009 "table": "quotas"
2010 },
2011 {
2012 "db": "ai",
2013 "table": "citations"
2014 }
2015 ]
2016 }
2017 },
2018 "bot": {
2019 "_note": "Deployed; migrations 0001 and 0002 applied. Bot owns openvibe.bot.conf: deploy bot installs its proxy vhost after Sites skips that filename. BOT_WHIP_BASE (in /etc/openvibe/bot.env) points Bot's video at https://ingest.openre.stream/whip. Use ovhost deploy bot --restart if the checkout is already current so it installs and enables the service unit. Keep Sites on its ovhost deploy path so it installs every vhost.",
2020 "repo": "/opt/openvibe.bot",
2021 "installUnits": true,
2022 "enableUnits": true,
2023 "units": [
2024 "openvibe-bot.service"
2025 ],
2026 "unitSources": {
2027 "openvibe-bot.service": "deploy/systemd/openvibe-bot.service"
2028 },
2029 "envFile": "/etc/openvibe/bot.env",
2030 "env": {
2031 "required": "from-example"
2032 },
2033 "port": 4630,
2034 "nginx": {
2035 "vhost": "openvibe.bot.conf",
2036 "repoVhost": "deploy/nginx/openvibe.bot.conf",
2037 "installOnDeploy": true
2038 },
2039 "ready": {
2040 "url": "http://127.0.0.1:4630/api/ready",
2041 "timeoutSeconds": 30
2042 },
2043 "protected": {
2044 "kind": "http-json-count",
2045 "url": "http://127.0.0.1:4630/api/ready",
2046 "field": "protected_sessions",
2047 "label": "robot control sessions"
2048 },
2049 "drain": {
2050 "policy": "wait",
2051 "waitMaxSeconds": 600,
2052 "pollSeconds": 10,
2053 "quietChecks": 2,
2054 "timeoutPolicy": "continue"
2055 },
2056 "noRestartPaths": [
2057 "docs/",
2058 "test/",
2059 "**/*.md"
2060 ],
2061 "databases": [
2062 {
2063 "name": "bot",
2064 "engine": "postgresql",
2065 "database": "ov_bot"
2066 }
2067 ],
2068 "drill": {
2069 "port": 14630,
2070 "databases": {
2071 "bot": {
2072 "url": "DATABASE_URL",
2073 "directUrl": "DATABASE_DIRECT_URL"
2074 }
2075 },
2076 "env": {
2077 "PORT": "{port}",
2078 "HOST": "127.0.0.1",
2079 "EVENTS_URL": "",
2080 "OV_OAUTH_CLIENT_SECRET": "",
2081 "VALKEY_URL": "redis://127.0.0.1:9/0"
2082 },
2083 "compare": [],
2084 "counts": [
2085 {
2086 "db": "bot",
2087 "table": "robots"
2088 },
2089 {
2090 "db": "bot",
2091 "table": "devices"
2092 }
2093 ]
2094 }
2095 },
2096 "openre": {
2097 "_note": "Release layout: /opt/openre.stream/repo is the git clone, releases/<sha12> the releases (npm ci into each, chowned to ubuntu), `current` the link the API and the coordinator run from (deploy.sh release + api as ovhost deploy openre; ovhost rollback openre). units are the API and the session coordinator. workerUnits are the transport workers: one instance per release (openre-rtmp-ingest@<release>, openre-jsmpeg@<release>, openre-webrtc@<release>), started by deploy.sh workers (still the repository's script), drained by the coordinator when a newer generation is ready, and exiting on their own. ovhost lists them in status/validate, never starts, stops or restarts them (the inventory refuses a worker unit in units), and never prunes a release a worker instance runs from. deploy.sh workers starts openre-webrtc@<release> once the WHIP vhost (ingest.openre.stream) and MEDIASOUP_ANNOUNCED_IP are set.",
2098 "repo": "/opt/openre.stream",
2099 "strategy": "release-layout",
2100 "owner": "root",
2101 "runAs": "ubuntu",
2102 "release": {
2103 "git": "repo",
2104 "id": "sha12",
2105 "reuseModules": false,
2106 "chown": "ubuntu:ubuntu",
2107 "keep": 5
2108 },
2109 "units": [
2110 "openre-api.service",
2111 "openre-session-coordinator.service"
2112 ],
2113 "workerUnits": [
2114 "[email protected]",
2115 "[email protected]",
2116 "[email protected]",
2117 "[email protected]"
2118 ],
2119 "envFile": "/etc/openvibe/openre.env",
2120 "env": {
2121 "required": "from-example"
2122 },
2123 "port": 4500,
2124 "ready": {
2125 "url": "http://127.0.0.1:4500/api/ready",
2126 "timeoutSeconds": 60
2127 },
2128 "noRestartPaths": [
2129 "docs/",
2130 "test/",
2131 "**/*.md"
2132 ],
2133 "databases": [
2134 {
2135 "name": "openre",
2136 "engine": "postgresql",
2137 "database": "ov_openre"
2138 }
2139 ],
2140 "drill": {
2141 "port": 14500,
2142 "command": [
2143 "/usr/bin/node",
2144 "server/index.js"
2145 ],
2146 "databases": {
2147 "openre": {
2148 "url": "DATABASE_URL",
2149 "directUrl": "DATABASE_DIRECT_URL"
2150 }
2151 },
2152 "env": {
2153 "PORT": "{port}",
2154 "HOST": "127.0.0.1",
2155 "OPENRE_DRILL": "1",
2156 "EVENTS_URL": "",
2157 "OV_OAUTH_CLIENT_SECRET": "",
2158 "MEDIA_URL": "http://127.0.0.1:9",
2159 "MEDIA_API_KEY": "",
2160 "OPENRE_RECORDING": "off",
2161 "VALKEY_URL": "redis://127.0.0.1:9/0"
2162 },
2163 "compare": [
2164 "/api/health",
2165 "/robots.txt"
2166 ],
2167 "counts": [
2168 {
2169 "db": "openre",
2170 "table": "stream_definitions"
2171 },
2172 {
2173 "db": "openre",
2174 "table": "ingest_keys"
2175 },
2176 {
2177 "db": "openre",
2178 "table": "destinations"
2179 },
2180 {
2181 "db": "openre",
2182 "table": "migration_map"
2183 }
2184 ],
2185 "_notes": "Only openre-api starts (command; the working directory and the non-secret Environment= come from openre-api.service). It never runs a transport, a coordinator or the event relay in any mode. OPENRE_DRILL=1 (OpenRestream 5e86ea7 and later) also makes it read-only: writes, /play/ (which would pull from production's worker on loopback) and sign-in (which would redeem codes at Network) answer 503. The coordinator and the workers refuse to start. The event relay and Media calls are off. EVENTS_URL and OV_OAUTH_CLIENT_SECRET empty and MEDIA_URL on the closed port are the same switches for releases before 5e86ea7. Left on (read-only): the Network JWKS GET on loopback. Not compared: ingest_sessions and workers (they change whenever someone streams or a generation rolls over) and /api/ready (it reports live worker heartbeats)."
2186 },
2187 "protected": {
2188 "kind": "postgresql-count",
2189 "database": "ov_openre",
2190 "sql": "SELECT count(*) AS n FROM ingest_sessions WHERE state IN ('starting', 'live', 'ending')",
2191 "label": "ingest sessions"
2192 },
2193 "drain": {
2194 "policy": "refuse"
2195 }
2196 },
2197 "actor": {
2198 "_note": "Added 2026-10-08 (plan T17): OpenVibe.Actor, the task router, at openvibe.actor on loopback :4950.",
2199 "repo": "/opt/openvibe.actor",
2200 "units": [
2201 "openvibe-actor.service"
2202 ],
2203 "unitSources": {
2204 "openvibe-actor.service": "deploy/systemd/openvibe-actor.service"
2205 },
2206 "envFile": "/etc/openvibe/actor.env",
2207 "env": {
2208 "required": "from-example"
2209 },
2210 "port": 4950,
2211 "ready": {
2212 "url": "http://127.0.0.1:4950/api/ready",
2213 "timeoutSeconds": 30
2214 },
2215 "noRestartPaths": [
2216 "docs/",
2217 "test/",
2218 "**/*.md"
2219 ],
2220 "databases": [
2221 {
2222 "name": "actor",
2223 "engine": "postgresql",
2224 "database": "ov_actor"
2225 }
2226 ],
2227 "drill": {
2228 "port": 14950,
2229 "databases": {
2230 "actor": {
2231 "url": "DATABASE_URL",
2232 "directUrl": "DATABASE_DIRECT_URL"
2233 }
2234 },
2235 "env": {
2236 "PORT": "{port}",
2237 "HOST": "127.0.0.1",
2238 "VALKEY_URL": "redis://127.0.0.1:9/0",
2239 "OV_OAUTH_CLIENT_SECRET": "",
2240 "ACTOR_EVENTS_URL": "",
2241 "ACTOR_EVENTS_SECRET": "",
2242 "ACTOR_DEEPSEEK_API_KEY": "",
2243 "ACTOR_OPENAI_API_KEY": "",
2244 "ACTOR_LOCAL_LLM_URL": "http://127.0.0.1:9/v1",
2245 "ACTOR_SEARCH_URL": "http://127.0.0.1:9",
2246 "ACTOR_TOOLS_URL": "http://127.0.0.1:9",
2247 "ACTOR_MAX_RUNNING": "0",
2248 "ACTOR_AGENTS_TICK_MAX": "0",
2249 "ACTOR_WEBHOOK_INTERVAL_MS": "86400000"
2250 },
2251 "acceptance": [
2252 {
2253 "path": "/api/ready",
2254 "status": 200,
2255 "keys": []
2256 }
2257 ],
2258 "counts": [
2259 {
2260 "db": "actor",
2261 "table": "tasks"
2262 },
2263 {
2264 "db": "actor",
2265 "table": "actor_agents"
2266 },
2267 {
2268 "db": "actor",
2269 "table": "actor_approvals"
2270 }
2271 ],
2272 "countsTolerance": 20,
2273 "_notes": "Drill 2026-10-15: the restored copy boots with no service credentials (no tokens, no event relay or subscriptions), Valkey on a closed port and every outbound service URL on a closed port; counts and readiness. No model keys and no running slots, so a queued task is never resumed against a provider. Readiness needs one agent system: the local model URL is a closed port."
2274 }
2275 },
2276 "food": {
2277 "_note": "Added 2026-10-08 (plan T19): OpenVibe.Food, food near you, meal plans and a pantry, at openvibe.food on loopback :4970.",
2278 "repo": "/opt/openvibe.food",
2279 "units": [
2280 "openvibe-food.service"
2281 ],
2282 "unitSources": {
2283 "openvibe-food.service": "deploy/systemd/openvibe-food.service"
2284 },
2285 "envFile": "/etc/openvibe/food.env",
2286 "env": {
2287 "required": "from-example"
2288 },
2289 "port": 4970,
2290 "ready": {
2291 "url": "http://127.0.0.1:4970/api/ready",
2292 "timeoutSeconds": 30
2293 },
2294 "noRestartPaths": [
2295 "docs/",
2296 "test/",
2297 "**/*.md"
2298 ],
2299 "databases": [
2300 {
2301 "name": "food",
2302 "engine": "postgresql",
2303 "database": "ov_food"
2304 }
2305 ],
2306 "drill": {
2307 "port": 14960,
2308 "databases": {
2309 "food": {
2310 "url": "DATABASE_URL",
2311 "directUrl": "DATABASE_DIRECT_URL"
2312 }
2313 },
2314 "env": {
2315 "PORT": "{port}",
2316 "HOST": "127.0.0.1",
2317 "VALKEY_URL": "redis://127.0.0.1:9/0",
2318 "OV_OAUTH_CLIENT_SECRET": "",
2319 "FOOD_EVENTS_URL": "",
2320 "FOOD_EVENTS_SECRET": "",
2321 "OV_AI_INTERNAL_URL": "",
2322 "FOOD_NOMINATIM_URL": "http://127.0.0.1:9",
2323 "FOOD_OVERPASS_URL": "http://127.0.0.1:9"
2324 },
2325 "acceptance": [
2326 {
2327 "path": "/api/ready",
2328 "status": 200,
2329 "keys": []
2330 }
2331 ],
2332 "counts": [
2333 {
2334 "db": "food",
2335 "table": "food_plans"
2336 },
2337 {
2338 "db": "food",
2339 "table": "food_pantry"
2340 }
2341 ],
2342 "countsTolerance": 5,
2343 "_notes": "Drill 2026-10-15: the restored copy boots with no service credentials (no tokens, no event relay or subscriptions), Valkey on a closed port and every outbound service URL on a closed port; counts and readiness."
2344 }
2345 },
2346 "help": {
2347 "_note": "Added 2026-10-08 (plan T19): OpenVibe.Help, the help centre and support tickets, at openvibe.help on loopback :5020.",
2348 "repo": "/opt/openvibe.help",
2349 "units": [
2350 "openvibe-help.service"
2351 ],
2352 "unitSources": {
2353 "openvibe-help.service": "deploy/systemd/openvibe-help.service"
2354 },
2355 "envFile": "/etc/openvibe/help.env",
2356 "env": {
2357 "required": "from-example"
2358 },
2359 "port": 5020,
2360 "ready": {
2361 "url": "http://127.0.0.1:5020/api/ready",
2362 "timeoutSeconds": 30
2363 },
2364 "noRestartPaths": [
2365 "docs/",
2366 "test/",
2367 "**/*.md"
2368 ],
2369 "databases": [
2370 {
2371 "name": "help",
2372 "engine": "postgresql",
2373 "database": "ov_help"
2374 }
2375 ],
2376 "drill": {
2377 "port": 14970,
2378 "databases": {
2379 "help": {
2380 "url": "DATABASE_URL",
2381 "directUrl": "DATABASE_DIRECT_URL"
2382 }
2383 },
2384 "env": {
2385 "PORT": "{port}",
2386 "HOST": "127.0.0.1",
2387 "VALKEY_URL": "redis://127.0.0.1:9/0",
2388 "OV_OAUTH_CLIENT_SECRET": "",
2389 "HELP_EVENTS_URL": "",
2390 "HELP_EVENTS_SECRET": "",
2391 "HELP_SEARCH_URL": "http://127.0.0.1:9"
2392 },
2393 "acceptance": [
2394 {
2395 "path": "/api/ready",
2396 "status": 200,
2397 "keys": []
2398 }
2399 ],
2400 "counts": [
2401 {
2402 "db": "help",
2403 "table": "help_tickets"
2404 },
2405 {
2406 "db": "help",
2407 "table": "help_messages"
2408 }
2409 ],
2410 "countsTolerance": 5,
2411 "_notes": "Drill 2026-10-15: the restored copy boots with no service credentials (no tokens, no event relay or subscriptions), Valkey on a closed port and every outbound service URL on a closed port; counts and readiness."
2412 }
2413 },
2414 "work": {
2415 "_note": "Added 2026-10-08 (plan T19): OpenVibe.Work, job listings from open boards and saved searches, at openvibe.work on loopback :4960.",
2416 "repo": "/opt/openvibe.work",
2417 "units": [
2418 "openvibe-work.service"
2419 ],
2420 "unitSources": {
2421 "openvibe-work.service": "deploy/systemd/openvibe-work.service"
2422 },
2423 "envFile": "/etc/openvibe/work.env",
2424 "env": {
2425 "required": "from-example"
2426 },
2427 "port": 4960,
2428 "ready": {
2429 "url": "http://127.0.0.1:4960/api/ready",
2430 "timeoutSeconds": 30
2431 },
2432 "noRestartPaths": [
2433 "docs/",
2434 "test/",
2435 "**/*.md"
2436 ],
2437 "databases": [
2438 {
2439 "name": "work",
2440 "engine": "postgresql",
2441 "database": "ov_work"
2442 }
2443 ],
2444 "drill": {
2445 "port": 14980,
2446 "databases": {
2447 "work": {
2448 "url": "DATABASE_URL",
2449 "directUrl": "DATABASE_DIRECT_URL"
2450 }
2451 },
2452 "env": {
2453 "PORT": "{port}",
2454 "HOST": "127.0.0.1",
2455 "VALKEY_URL": "redis://127.0.0.1:9/0",
2456 "OV_OAUTH_CLIENT_SECRET": "",
2457 "WORK_EVENTS_URL": "",
2458 "WORK_EVENTS_SECRET": "",
2459 "WORK_INGEST": "off",
2460 "WORK_ACTOR_URL": "http://127.0.0.1:9"
2461 },
2462 "acceptance": [
2463 {
2464 "path": "/api/ready",
2465 "status": 200,
2466 "keys": []
2467 }
2468 ],
2469 "counts": [
2470 {
2471 "db": "work",
2472 "table": "work_listings"
2473 },
2474 {
2475 "db": "work",
2476 "table": "work_saved_searches"
2477 },
2478 {
2479 "db": "work",
2480 "table": "work_applications"
2481 }
2482 ],
2483 "countsTolerance": 400,
2484 "_notes": "Drill 2026-10-15: the restored copy boots with no service credentials (no tokens, no event relay or subscriptions), Valkey on a closed port and every outbound service URL on a closed port; counts and readiness. work_listings moves with every six-hourly ingest (new and expired listings): tolerance 400."
2485 }
2486 },
2487 "quest": {
2488 "_note": "Added 2026-10-08 (plan T19): OpenVibe.Quest, the shared quest log, at openvibe.quest on loopback :4980.",
2489 "repo": "/opt/openvibe.quest",
2490 "units": [
2491 "openvibe-quest.service"
2492 ],
2493 "unitSources": {
2494 "openvibe-quest.service": "deploy/systemd/openvibe-quest.service"
2495 },
2496 "envFile": "/etc/openvibe/quest.env",
2497 "env": {
2498 "required": "from-example"
2499 },
2500 "port": 4980,
2501 "ready": {
2502 "url": "http://127.0.0.1:4980/api/ready",
2503 "timeoutSeconds": 30
2504 },
2505 "noRestartPaths": [
2506 "docs/",
2507 "test/",
2508 "**/*.md"
2509 ],
2510 "databases": [
2511 {
2512 "name": "quest",
2513 "engine": "postgresql",
2514 "database": "ov_quest"
2515 }
2516 ],
2517 "drill": {
2518 "port": 14990,
2519 "databases": {
2520 "quest": {
2521 "url": "DATABASE_URL",
2522 "directUrl": "DATABASE_DIRECT_URL"
2523 }
2524 },
2525 "env": {
2526 "PORT": "{port}",
2527 "HOST": "127.0.0.1",
2528 "VALKEY_URL": "redis://127.0.0.1:9/0",
2529 "OV_OAUTH_CLIENT_SECRET": "",
2530 "QUEST_EVENTS_URL": "",
2531 "QUEST_EVENTS_SECRET": "",
2532 "QUEST_COINS": "off",
2533 "QUEST_ITEMS": "off",
2534 "OV_INVENTORY_INTERNAL_URL": "http://127.0.0.1:9"
2535 },
2536 "acceptance": [
2537 {
2538 "path": "/api/ready",
2539 "status": 200,
2540 "keys": []
2541 }
2542 ],
2543 "counts": [
2544 {
2545 "db": "quest",
2546 "table": "quest_progress"
2547 },
2548 {
2549 "db": "quest",
2550 "table": "quest_completions"
2551 },
2552 {
2553 "db": "quest",
2554 "table": "quest_badges"
2555 }
2556 ],
2557 "countsTolerance": 20,
2558 "_notes": "Drill 2026-10-15: the restored copy boots with no service credentials (no tokens, no event relay or subscriptions), Valkey on a closed port and every outbound service URL on a closed port; counts and readiness."
2559 }
2560 },
2561 "rent": {
2562 "_note": "Added 2026-10-08 (plan T19): OpenVibe.Rent, listings people post, at openvibe.rent on loopback :5010.",
2563 "repo": "/opt/openvibe.rent",
2564 "units": [
2565 "openvibe-rent.service"
2566 ],
2567 "unitSources": {
2568 "openvibe-rent.service": "deploy/systemd/openvibe-rent.service"
2569 },
2570 "envFile": "/etc/openvibe/rent.env",
2571 "env": {
2572 "required": "from-example"
2573 },
2574 "port": 5010,
2575 "ready": {
2576 "url": "http://127.0.0.1:5010/api/ready",
2577 "timeoutSeconds": 30
2578 },
2579 "noRestartPaths": [
2580 "docs/",
2581 "test/",
2582 "**/*.md"
2583 ],
2584 "databases": [
2585 {
2586 "name": "rent",
2587 "engine": "postgresql",
2588 "database": "ov_rent"
2589 }
2590 ],
2591 "drill": {
2592 "port": 15000,
2593 "databases": {
2594 "rent": {
2595 "url": "DATABASE_URL",
2596 "directUrl": "DATABASE_DIRECT_URL"
2597 }
2598 },
2599 "env": {
2600 "PORT": "{port}",
2601 "HOST": "127.0.0.1",
2602 "VALKEY_URL": "redis://127.0.0.1:9/0",
2603 "OV_OAUTH_CLIENT_SECRET": "",
2604 "RENT_EVENTS_URL": "",
2605 "RENT_EVENTS_SECRET": ""
2606 },
2607 "acceptance": [
2608 {
2609 "path": "/api/ready",
2610 "status": 200,
2611 "keys": []
2612 }
2613 ],
2614 "counts": [
2615 {
2616 "db": "rent",
2617 "table": "rent_listings"
2618 },
2619 {
2620 "db": "rent",
2621 "table": "rent_saved_searches"
2622 },
2623 {
2624 "db": "rent",
2625 "table": "area_price_index"
2626 }
2627 ],
2628 "countsTolerance": 5,
2629 "_notes": "Drill 2026-10-15: the restored copy boots with no service credentials (no tokens, no event relay or subscriptions), Valkey on a closed port and every outbound service URL on a closed port; counts and readiness."
2630 }
2631 },
2632 "media-hub": {
2633 "_note": "Added 2026-10-08 (plan T10/T19): OpenVibe.MediaHub, openvibe.download as a private drive (pics and video coming), loopback :4990.",
2634 "repo": "/opt/openvibe.download",
2635 "units": [
2636 "openvibe-media-hub.service"
2637 ],
2638 "unitSources": {
2639 "openvibe-media-hub.service": "deploy/systemd/openvibe-media-hub.service"
2640 },
2641 "envFile": "/etc/openvibe/media-hub.env",
2642 "env": {
2643 "required": "from-example"
2644 },
2645 "port": 4990,
2646 "ready": {
2647 "url": "http://127.0.0.1:4990/api/ready",
2648 "timeoutSeconds": 30
2649 },
2650 "noRestartPaths": [
2651 "docs/",
2652 "test/",
2653 "**/*.md"
2654 ],
2655 "databases": [
2656 {
2657 "name": "media-hub",
2658 "engine": "postgresql",
2659 "database": "ov_media_hub"
2660 }
2661 ],
2662 "drill": {
2663 "port": 15010,
2664 "databases": {
2665 "media-hub": {
2666 "url": "DATABASE_URL",
2667 "directUrl": "DATABASE_DIRECT_URL"
2668 }
2669 },
2670 "env": {
2671 "PORT": "{port}",
2672 "HOST": "127.0.0.1",
2673 "VALKEY_URL": "redis://127.0.0.1:9/0",
2674 "OV_OAUTH_CLIENT_SECRET": "",
2675 "MEDIAHUB_EVENTS_URL": "",
2676 "MEDIAHUB_EVENTS_SECRET": "",
2677 "MEDIAHUB_MEDIA_URL": "http://127.0.0.1:9",
2678 "MEDIAHUB_LIVE_URL": "http://127.0.0.1:9",
2679 "MEDIAHUB_CHAT_URL": "http://127.0.0.1:9",
2680 "MEDIAHUB_TMP_DIR": "{tmp}/tmp"
2681 },
2682 "acceptance": [
2683 {
2684 "path": "/api/ready",
2685 "status": 200,
2686 "keys": []
2687 }
2688 ],
2689 "counts": [
2690 {
2691 "db": "media-hub",
2692 "table": "mh_files"
2693 },
2694 {
2695 "db": "media-hub",
2696 "table": "mh_folders"
2697 },
2698 {
2699 "db": "media-hub",
2700 "table": "mh_shares"
2701 },
2702 {
2703 "db": "media-hub",
2704 "table": "video_playlists"
2705 }
2706 ],
2707 "countsTolerance": 5,
2708 "_notes": "Drill 2026-10-15: the restored copy boots with no service credentials (no tokens, no event relay or subscriptions), Valkey on a closed port and every outbound service URL on a closed port; counts and readiness."
2709 }
2710 },
2711 "inventory": {
2712 "_note": "Added 2026-10-09 (plan T21, ADR-054): OpenVibe.Inventory, the network-wide inventory at inventory.openvibe.network, loopback :5030.",
2713 "repo": "/opt/inventory.openvibe.network",
2714 "units": [
2715 "openvibe-inventory.service"
2716 ],
2717 "unitSources": {
2718 "openvibe-inventory.service": "deploy/systemd/openvibe-inventory.service"
2719 },
2720 "envFile": "/etc/openvibe/inventory.env",
2721 "env": {
2722 "required": "from-example"
2723 },
2724 "port": 5030,
2725 "ready": {
2726 "url": "http://127.0.0.1:5030/api/ready",
2727 "timeoutSeconds": 30
2728 },
2729 "noRestartPaths": [
2730 "docs/",
2731 "test/",
2732 "**/*.md"
2733 ],
2734 "databases": [
2735 {
2736 "name": "inventory",
2737 "engine": "postgresql",
2738 "database": "ov_inventory"
2739 }
2740 ],
2741 "drill": {
2742 "port": 15020,
2743 "databases": {
2744 "inventory": {
2745 "url": "DATABASE_URL",
2746 "directUrl": "DATABASE_DIRECT_URL"
2747 }
2748 },
2749 "env": {
2750 "PORT": "{port}",
2751 "HOST": "127.0.0.1",
2752 "VALKEY_URL": "redis://127.0.0.1:9/0",
2753 "OV_OAUTH_CLIENT_SECRET": "",
2754 "EVENTS_URL": "",
2755 "INVENTORY_EVENTS_SECRET": "",
2756 "OV_MEDIA_INTERNAL_URL": "http://127.0.0.1:9"
2757 },
2758 "acceptance": [
2759 {
2760 "path": "/api/ready",
2761 "status": 200,
2762 "keys": []
2763 }
2764 ],
2765 "counts": [
2766 {
2767 "db": "inventory",
2768 "table": "inventory_definitions"
2769 },
2770 {
2771 "db": "inventory",
2772 "table": "inventory_instances"
2773 },
2774 {
2775 "db": "inventory",
2776 "table": "inventory_ledger"
2777 }
2778 ],
2779 "countsTolerance": 50,
2780 "_notes": "Drill 2026-10-15: the restored copy boots with no service credentials (no tokens, no event relay or subscriptions), Valkey on a closed port and every outbound service URL on a closed port; counts and readiness."
2781 }
2782 },
2783 "play": {
2784 "_note": "Added 2026-10-14 (Games ADR-0007 decision 10, contracts 0.172.0): OpenVibe.Play, achievements and leaderboards at play.openvibe.network, loopback :5040.",
2785 "repo": "/opt/play.openvibe.network",
2786 "units": [
2787 "openvibe-play.service"
2788 ],
2789 "unitSources": {
2790 "openvibe-play.service": "deploy/systemd/openvibe-play.service"
2791 },
2792 "envFile": "/etc/openvibe/play.env",
2793 "env": {
2794 "required": "from-example"
2795 },
2796 "port": 5040,
2797 "ready": {
2798 "url": "http://127.0.0.1:5040/api/ready",
2799 "timeoutSeconds": 30
2800 },
2801 "noRestartPaths": [
2802 "docs/",
2803 "test/",
2804 "**/*.md"
2805 ],
2806 "databases": [
2807 {
2808 "name": "play",
2809 "engine": "postgresql",
2810 "database": "ov_play"
2811 }
2812 ],
2813 "drill": {
2814 "port": 15030,
2815 "databases": {
2816 "play": {
2817 "url": "DATABASE_URL",
2818 "directUrl": "DATABASE_DIRECT_URL"
2819 }
2820 },
2821 "env": {
2822 "PORT": "{port}",
2823 "HOST": "127.0.0.1",
2824 "VALKEY_URL": "redis://127.0.0.1:9/0",
2825 "OV_OAUTH_CLIENT_SECRET": "",
2826 "EVENTS_URL": "",
2827 "PLAY_EVENTS_SECRET": ""
2828 },
2829 "acceptance": [
2830 {
2831 "path": "/api/ready",
2832 "status": 200,
2833 "keys": []
2834 }
2835 ],
2836 "counts": [
2837 {
2838 "db": "play",
2839 "table": "play_titles"
2840 },
2841 {
2842 "db": "play",
2843 "table": "play_achievements"
2844 },
2845 {
2846 "db": "play",
2847 "table": "play_unlocks"
2848 },
2849 {
2850 "db": "play",
2851 "table": "play_scores"
2852 }
2853 ],
2854 "countsTolerance": 50,
2855 "_notes": "Drill 2026-10-15: the restored copy boots with no service credentials (no tokens, no event relay or subscriptions), Valkey on a closed port and every outbound service URL on a closed port; counts and readiness."
2856 }
2857 },
2858 "zone": {
2859 "_note": "Added 2026-10-14 (ADR-031 amendment, contracts 0.173.0): OpenVibe.Zone, private S3-compatible storage zones at openvibe.zone, loopback :5050. Media serves the bytes (s3.openvibe.media / s3.openvibe.network).",
2860 "repo": "/opt/openvibe.zone",
2861 "units": [
2862 "openvibe-zone.service"
2863 ],
2864 "unitSources": {
2865 "openvibe-zone.service": "deploy/systemd/openvibe-zone.service"
2866 },
2867 "envFile": "/etc/openvibe/zone.env",
2868 "env": {
2869 "required": "from-example"
2870 },
2871 "port": 5050,
2872 "ready": {
2873 "url": "http://127.0.0.1:5050/api/ready",
2874 "timeoutSeconds": 30
2875 },
2876 "noRestartPaths": [
2877 "docs/",
2878 "test/",
2879 "**/*.md"
2880 ],
2881 "databases": [
2882 {
2883 "name": "zone",
2884 "engine": "postgresql",
2885 "database": "ov_zone"
2886 }
2887 ],
2888 "drill": {
2889 "port": 15040,
2890 "databases": {
2891 "zone": {
2892 "url": "DATABASE_URL",
2893 "directUrl": "DATABASE_DIRECT_URL"
2894 }
2895 },
2896 "env": {
2897 "PORT": "{port}",
2898 "HOST": "127.0.0.1",
2899 "VALKEY_URL": "redis://127.0.0.1:9/0",
2900 "OV_OAUTH_CLIENT_SECRET": "",
2901 "EVENTS_URL": "",
2902 "ZONE_EVENTS_SECRET": "",
2903 "MEDIA_INTERNAL_URL": "http://127.0.0.1:9"
2904 },
2905 "acceptance": [
2906 {
2907 "path": "/api/ready",
2908 "status": 200,
2909 "keys": []
2910 }
2911 ],
2912 "counts": [
2913 {
2914 "db": "zone",
2915 "table": "zone_zones"
2916 },
2917 {
2918 "db": "zone",
2919 "table": "zone_usage"
2920 }
2921 ],
2922 "countsTolerance": 5,
2923 "_notes": "Drill 2026-10-15: the restored copy boots with no service credentials (no tokens, no event relay or subscriptions), Valkey on a closed port and every outbound service URL on a closed port; counts and readiness."
2924 }
2925 },
2926 "watch": {
2927 "_note": "Added 2026-10-07 (plan T18): OpenVibe.Watch on loopback :4730; no public vhost until its product surface ships.",
2928 "repo": "/opt/openvibe.watch",
2929 "units": [
2930 "openvibe-watch.service"
2931 ],
2932 "unitSources": {
2933 "openvibe-watch.service": "deploy/systemd/openvibe-watch.service"
2934 },
2935 "envFile": "/etc/openvibe/watch.env",
2936 "env": {
2937 "required": "from-example"
2938 },
2939 "port": 4730,
2940 "ready": {
2941 "url": "http://127.0.0.1:4730/api/ready",
2942 "timeoutSeconds": 30
2943 },
2944 "noRestartPaths": [
2945 "docs/",
2946 "test/",
2947 "**/*.md"
2948 ],
2949 "databases": [
2950 {
2951 "name": "watch",
2952 "engine": "postgresql",
2953 "database": "ov_watch"
2954 }
2955 ],
2956 "drill": {
2957 "port": 14730,
2958 "databases": {
2959 "watch": {
2960 "url": "DATABASE_URL",
2961 "directUrl": "DATABASE_DIRECT_URL"
2962 }
2963 },
2964 "env": {
2965 "PORT": "{port}",
2966 "HOST": "127.0.0.1",
2967 "WATCH_WORKER": "off",
2968 "EVENTS_URL": "",
2969 "OV_OAUTH_CLIENT_SECRET": "",
2970 "VALKEY_URL": "redis://127.0.0.1:9/0"
2971 },
2972 "counts": [
2973 {
2974 "db": "watch",
2975 "table": "watches"
2976 },
2977 {
2978 "db": "watch",
2979 "table": "observations"
2980 },
2981 {
2982 "db": "watch",
2983 "table": "check_runs"
2984 }
2985 ],
2986 "acceptance": [
2987 {
2988 "path": "/api/ready",
2989 "keys": [
2990 "ready",
2991 "checks"
2992 ]
2993 },
2994 {
2995 "path": "/api/health"
2996 }
2997 ]
2998 }
2999 },
3000 "run": {
3001 "_note": "Added 2026-10-07 (plan T14): OpenVibe.Run on loopback :4920; jobs dispatch through Bot's internal jobs API.",
3002 "repo": "/opt/openvibe.run",
3003 "units": [
3004 "openvibe-run.service"
3005 ],
3006 "unitSources": {
3007 "openvibe-run.service": "deploy/systemd/openvibe-run.service"
3008 },
3009 "envFile": "/etc/openvibe/run.env",
3010 "env": {
3011 "required": "from-example"
3012 },
3013 "port": 4920,
3014 "ready": {
3015 "url": "http://127.0.0.1:4920/api/ready",
3016 "timeoutSeconds": 30
3017 },
3018 "noRestartPaths": [
3019 "docs/",
3020 "test/",
3021 "**/*.md"
3022 ],
3023 "databases": [
3024 {
3025 "name": "run",
3026 "engine": "postgresql",
3027 "database": "ov_run"
3028 }
3029 ],
3030 "drill": {
3031 "port": 14920,
3032 "databases": {
3033 "run": {
3034 "url": "DATABASE_URL",
3035 "directUrl": "DATABASE_DIRECT_URL"
3036 }
3037 },
3038 "env": {
3039 "PORT": "{port}",
3040 "HOST": "127.0.0.1",
3041 "RUN_POLL_MS": "0",
3042 "EVENTS_URL": "",
3043 "OV_OAUTH_CLIENT_SECRET": "",
3044 "VALKEY_URL": "redis://127.0.0.1:9/0"
3045 },
3046 "counts": [
3047 {
3048 "db": "run",
3049 "table": "run_jobs"
3050 }
3051 ],
3052 "acceptance": [
3053 {
3054 "path": "/api/ready",
3055 "keys": [
3056 "ready",
3057 "checks"
3058 ]
3059 },
3060 {
3061 "path": "/api/health"
3062 }
3063 ]
3064 }
3065 },
3066 "computer": {
3067 "_note": "Added 2026-11-08: OpenVibe.Computer is the thin front door over Run; it keeps no session state of its own.",
3068 "repo": "/opt/openvibe.computer",
3069 "units": [
3070 "openvibe-computer.service"
3071 ],
3072 "unitSources": {
3073 "openvibe-computer.service": "deploy/systemd/openvibe-computer.service"
3074 },
3075 "envFile": "/etc/openvibe/computer.env",
3076 "env": {
3077 "required": "from-example"
3078 },
3079 "port": 5135,
3080 "ready": {
3081 "url": "http://127.0.0.1:5135/api/health",
3082 "timeoutSeconds": 15
3083 },
3084 "noRestartPaths": [
3085 "docs/",
3086 "test/",
3087 "**/*.md"
3088 ],
3089 "origin": "https://openvibe.computer",
3090 "nginx": {
3091 "vhost": "openvibe.computer.conf",
3092 "repoVhost": "deploy/nginx/openvibe.computer.conf",
3093 "variant": "http"
3094 }
3095 },
3096 "services": {
3097 "_note": "Added 2026-10-07 (plan T13): OpenVibe.Services on loopback :4930.",
3098 "repo": "/opt/openvibe.services",
3099 "units": [
3100 "openvibe-services.service"
3101 ],
3102 "unitSources": {
3103 "openvibe-services.service": "deploy/systemd/openvibe-services.service"
3104 },
3105 "envFile": "/etc/openvibe/services.env",
3106 "env": {
3107 "required": "from-example"
3108 },
3109 "port": 4930,
3110 "ready": {
3111 "url": "http://127.0.0.1:4930/api/ready",
3112 "timeoutSeconds": 30
3113 },
3114 "noRestartPaths": [
3115 "docs/",
3116 "test/",
3117 "**/*.md"
3118 ],
3119 "databases": [
3120 {
3121 "name": "services",
3122 "engine": "postgresql",
3123 "database": "ov_services"
3124 }
3125 ],
3126 "drill": {
3127 "port": 14930,
3128 "databases": {
3129 "services": {
3130 "url": "DATABASE_URL",
3131 "directUrl": "DATABASE_DIRECT_URL"
3132 }
3133 },
3134 "env": {
3135 "PORT": "{port}",
3136 "HOST": "127.0.0.1",
3137 "EVENTS_URL": "",
3138 "OV_OAUTH_CLIENT_SECRET": "",
3139 "VALKEY_URL": "redis://127.0.0.1:9/0"
3140 },
3141 "counts": [],
3142 "acceptance": [
3143 {
3144 "path": "/api/ready",
3145 "keys": [
3146 "ready",
3147 "checks"
3148 ]
3149 },
3150 {
3151 "path": "/api/health"
3152 }
3153 ]
3154 }
3155 },
3156 "space": {
3157 "_note": "Added 2026-10-07 (plan T10 step 3): OpenVibe.Space, the forum moved off OpenVibe.Community; loopback :4940 behind openvibe.space.",
3158 "repo": "/opt/openvibe.space",
3159 "units": [
3160 "openvibe-space.service"
3161 ],
3162 "unitSources": {
3163 "openvibe-space.service": "deploy/systemd/openvibe-space.service"
3164 },
3165 "envFile": "/etc/openvibe/space.env",
3166 "env": {
3167 "required": "from-example"
3168 },
3169 "port": 4940,
3170 "ready": {
3171 "url": "http://127.0.0.1:4940/api/ready",
3172 "timeoutSeconds": 30
3173 },
3174 "noRestartPaths": [
3175 "docs/",
3176 "test/",
3177 "**/*.md"
3178 ],
3179 "databases": [
3180 {
3181 "name": "space",
3182 "engine": "postgresql",
3183 "database": "ov_space"
3184 }
3185 ],
3186 "drill": {
3187 "port": 14940,
3188 "databases": {
3189 "space": {
3190 "url": "DATABASE_URL",
3191 "directUrl": "DATABASE_DIRECT_URL"
3192 }
3193 },
3194 "env": {
3195 "PORT": "{port}",
3196 "HOST": "127.0.0.1",
3197 "EVENTS_URL": "",
3198 "OV_OAUTH_CLIENT_SECRET": "",
3199 "VALKEY_URL": "redis://127.0.0.1:9/0"
3200 },
3201 "counts": [
3202 {
3203 "db": "space",
3204 "table": "spaces"
3205 },
3206 {
3207 "db": "space",
3208 "table": "threads"
3209 },
3210 {
3211 "db": "space",
3212 "table": "posts"
3213 }
3214 ],
3215 "acceptance": [
3216 {
3217 "path": "/api/ready",
3218 "keys": [
3219 "ready",
3220 "checks"
3221 ]
3222 },
3223 {
3224 "path": "/api/health"
3225 }
3226 ]
3227 },
3228 "nginx": {
3229 "vhost": "openvibe.space.conf",
3230 "repoVhost": "deploy/nginx/openvibe.space.conf",
3231 "variant": "http"
3232 }
3233 },
3234 "design": {
3235 "_note": "OpenVibe.Design: the theme studio (plan 2026-10-28 section 5.2); stateless front over Network's theme API, OAuth client design; database ov_design",
3236 "repo": "/opt/openvibe.design",
3237 "units": [
3238 "openvibe-design.service"
3239 ],
3240 "unitSources": {
3241 "openvibe-design.service": "deploy/systemd/openvibe-design.service"
3242 },
3243 "envFile": "/etc/openvibe/design.env",
3244 "env": {
3245 "required": "from-example"
3246 },
3247 "port": 5090,
3248 "ready": {
3249 "url": "http://127.0.0.1:5090/api/ready",
3250 "timeoutSeconds": 30
3251 },
3252 "noRestartPaths": [
3253 "docs/",
3254 "test/",
3255 "**/*.md"
3256 ],
3257 "databases": [
3258 {
3259 "name": "design",
3260 "engine": "postgresql",
3261 "database": "ov_design"
3262 }
3263 ],
3264 "drill": {
3265 "port": 15090,
3266 "databases": {
3267 "design": {
3268 "url": "DATABASE_URL",
3269 "directUrl": "DATABASE_DIRECT_URL"
3270 }
3271 },
3272 "env": {
3273 "PORT": "{port}",
3274 "HOST": "127.0.0.1",
3275 "VALKEY_URL": "redis://127.0.0.1:9/0",
3276 "OV_OAUTH_CLIENT_SECRET": "",
3277 "DESIGN_DRILL": "1",
3278 "DESIGN_EVENTS_URL": "",
3279 "DESIGN_EVENTS_SECRET": "",
3280 "OV_LIVE_INTERNAL_URL": "http://127.0.0.1:9"
3281 },
3282 "acceptance": [
3283 {
3284 "path": "/api/ready",
3285 "status": 200,
3286 "keys": []
3287 }
3288 ],
3289 "counts": [
3290 {
3291 "db": "design",
3292 "table": "arena_rounds"
3293 }
3294 ],
3295 "countsTolerance": 5,
3296 "_notes": "The restored copy boots with no service credentials (no tokens, no outbox publishing or subscriptions), Valkey on a closed port and every outbound service URL on a closed port; counts and readiness."
3297 },
3298 "socketUnit": "openvibe-design.socket"
3299 },
3300 "exchange": {
3301 "_note": "OpenVibe.Exchange: the Exchange room (plan 2026-10-28 section 5.11); listings with permission passports, sandbox, install/revoke, free and swap; database ov_exchange",
3302 "repo": "/opt/openvibe.exchange",
3303 "units": [
3304 "openvibe-exchange.service"
3305 ],
3306 "unitSources": {
3307 "openvibe-exchange.service": "deploy/systemd/openvibe-exchange.service"
3308 },
3309 "envFile": "/etc/openvibe/exchange.env",
3310 "env": {
3311 "required": "from-example"
3312 },
3313 "port": 5150,
3314 "ready": {
3315 "url": "http://127.0.0.1:5150/api/ready",
3316 "timeoutSeconds": 30
3317 },
3318 "noRestartPaths": [
3319 "docs/",
3320 "test/",
3321 "**/*.md"
3322 ],
3323 "databases": [
3324 {
3325 "name": "exchange",
3326 "engine": "postgresql",
3327 "database": "ov_exchange"
3328 }
3329 ],
3330 "drill": {
3331 "port": 15150,
3332 "databases": {
3333 "exchange": {
3334 "url": "DATABASE_URL",
3335 "directUrl": "DATABASE_DIRECT_URL"
3336 }
3337 },
3338 "env": {
3339 "PORT": "{port}",
3340 "HOST": "127.0.0.1",
3341 "VALKEY_URL": "redis://127.0.0.1:9/0",
3342 "OV_OAUTH_CLIENT_SECRET": "",
3343 "EXCHANGE_DRILL": "1",
3344 "EXCHANGE_EVENTS_URL": "",
3345 "EXCHANGE_EVENTS_SECRET": "",
3346 "OV_LIVE_INTERNAL_URL": "http://127.0.0.1:9"
3347 },
3348 "acceptance": [
3349 {
3350 "path": "/api/ready",
3351 "status": 200,
3352 "keys": []
3353 }
3354 ],
3355 "counts": [
3356 {
3357 "db": "exchange",
3358 "table": "exchange_listings"
3359 }
3360 ],
3361 "countsTolerance": 5,
3362 "_notes": "The restored copy boots with no service credentials (no tokens, no outbox publishing or subscriptions), Valkey on a closed port and every outbound service URL on a closed port; counts and readiness."
3363 },
3364 "socketUnit": "openvibe-exchange.socket"
3365 },
3366 "report": {
3367 "_note": "Added 2026-10-28 (plan 2026-10-28 §5.4): OpenVibe.Report v1, the Report room at openvibe.report on loopback :5080. Stateless: open stats from Network, The Rundown and after-show reports from Live.; database ov_report",
3368 "repo": "/opt/openvibe.report",
3369 "units": [
3370 "openvibe-report.service"
3371 ],
3372 "unitSources": {
3373 "openvibe-report.service": "deploy/systemd/openvibe-report.service"
3374 },
3375 "envFile": "/etc/openvibe/report.env",
3376 "env": {
3377 "required": "from-example"
3378 },
3379 "port": 5080,
3380 "ready": {
3381 "url": "http://127.0.0.1:5080/api/ready",
3382 "timeoutSeconds": 30
3383 },
3384 "noRestartPaths": [
3385 "docs/",
3386 "test/",
3387 "**/*.md"
3388 ],
3389 "databases": [
3390 {
3391 "name": "report",
3392 "engine": "postgresql",
3393 "database": "ov_report"
3394 }
3395 ],
3396 "drill": {
3397 "port": 15180,
3398 "databases": {
3399 "report": {
3400 "url": "DATABASE_URL",
3401 "directUrl": "DATABASE_DIRECT_URL"
3402 }
3403 },
3404 "env": {
3405 "PORT": "{port}",
3406 "HOST": "127.0.0.1",
3407 "VALKEY_URL": "redis://127.0.0.1:9/0",
3408 "OV_OAUTH_CLIENT_SECRET": "",
3409 "REPORT_DRILL": "1",
3410 "REPORT_EVENTS_URL": "",
3411 "REPORT_EVENTS_SECRET": "",
3412 "OV_LIVE_INTERNAL_URL": "http://127.0.0.1:9"
3413 },
3414 "acceptance": [
3415 {
3416 "path": "/api/ready",
3417 "status": 200,
3418 "keys": []
3419 }
3420 ],
3421 "counts": [
3422 {
3423 "db": "report",
3424 "table": "report_inbox"
3425 }
3426 ],
3427 "countsTolerance": 5,
3428 "_notes": "The restored copy boots with no service credentials (no tokens, no outbox publishing or subscriptions), Valkey on a closed port and every outbound service URL on a closed port; counts and readiness."
3429 },
3430 "socketUnit": "openvibe-report.socket"
3431 },
3432 "vision": {
3433 "_note": "OpenVibe.Vision: the Vision room (plan 2026-10-28 section 5.8); ask a picture, local OCR, compare, UI check; database ov_vision",
3434 "repo": "/opt/openvibe.vision",
3435 "units": [
3436 "openvibe-vision.service"
3437 ],
3438 "unitSources": {
3439 "openvibe-vision.service": "deploy/systemd/openvibe-vision.service"
3440 },
3441 "envFile": "/etc/openvibe/vision.env",
3442 "env": {
3443 "required": "from-example"
3444 },
3445 "port": 5130,
3446 "ready": {
3447 "url": "http://127.0.0.1:5130/api/ready",
3448 "timeoutSeconds": 30
3449 },
3450 "noRestartPaths": [
3451 "docs/",
3452 "test/",
3453 "**/*.md"
3454 ],
3455 "databases": [
3456 {
3457 "name": "vision",
3458 "engine": "postgresql",
3459 "database": "ov_vision"
3460 }
3461 ],
3462 "drill": {
3463 "port": 15130,
3464 "databases": {
3465 "vision": {
3466 "url": "DATABASE_URL",
3467 "directUrl": "DATABASE_DIRECT_URL"
3468 }
3469 },
3470 "env": {
3471 "PORT": "{port}",
3472 "HOST": "127.0.0.1",
3473 "VALKEY_URL": "redis://127.0.0.1:9/0",
3474 "OV_OAUTH_CLIENT_SECRET": "",
3475 "VISION_DRILL": "1",
3476 "VISION_EVENTS_URL": "",
3477 "VISION_EVENTS_SECRET": "",
3478 "OV_LIVE_INTERNAL_URL": "http://127.0.0.1:9"
3479 },
3480 "acceptance": [
3481 {
3482 "path": "/api/ready",
3483 "status": 200,
3484 "keys": []
3485 }
3486 ],
3487 "counts": [
3488 {
3489 "db": "vision",
3490 "table": "vision_answers"
3491 }
3492 ],
3493 "countsTolerance": 5,
3494 "_notes": "The restored copy boots with no service credentials (no tokens, no outbox publishing or subscriptions), Valkey on a closed port and every outbound service URL on a closed port; counts and readiness."
3495 },
3496 "socketUnit": "openvibe-vision.socket"
3497 },
3498 "fun": {
3499 "_note": "OpenVibe.Fun: the Fun room (plan 2026-10-28 section 5.10); IRL quests on Live, a local guide from OpenStreetMap, group plans; database ov_fun",
3500 "repo": "/opt/openvibe.fun",
3501 "units": [
3502 "openvibe-fun.service"
3503 ],
3504 "unitSources": {
3505 "openvibe-fun.service": "deploy/systemd/openvibe-fun.service"
3506 },
3507 "envFile": "/etc/openvibe/fun.env",
3508 "env": {
3509 "required": "from-example"
3510 },
3511 "port": 5140,
3512 "ready": {
3513 "url": "http://127.0.0.1:5140/api/ready",
3514 "timeoutSeconds": 30
3515 },
3516 "noRestartPaths": [
3517 "docs/",
3518 "test/",
3519 "**/*.md"
3520 ],
3521 "databases": [
3522 {
3523 "name": "fun",
3524 "engine": "postgresql",
3525 "database": "ov_fun"
3526 }
3527 ],
3528 "drill": {
3529 "port": 15140,
3530 "databases": {
3531 "fun": {
3532 "url": "DATABASE_URL",
3533 "directUrl": "DATABASE_DIRECT_URL"
3534 }
3535 },
3536 "env": {
3537 "PORT": "{port}",
3538 "HOST": "127.0.0.1",
3539 "VALKEY_URL": "redis://127.0.0.1:9/0",
3540 "OV_OAUTH_CLIENT_SECRET": "",
3541 "FUN_DRILL": "1",
3542 "FUN_EVENTS_URL": "",
3543 "FUN_EVENTS_SECRET": "",
3544 "OV_LIVE_INTERNAL_URL": "http://127.0.0.1:9"
3545 },
3546 "acceptance": [
3547 {
3548 "path": "/api/ready",
3549 "status": 200,
3550 "keys": []
3551 }
3552 ],
3553 "counts": [
3554 {
3555 "db": "fun",
3556 "table": "fun_quest_boards"
3557 }
3558 ],
3559 "countsTolerance": 5,
3560 "_notes": "The restored copy boots with no service credentials (no tokens, no outbox publishing or subscriptions), Valkey on a closed port and every outbound service URL on a closed port; counts and readiness."
3561 },
3562 "socketUnit": "openvibe-fun.socket"
3563 },
3564 "shoes": {
3565 "_note": "OpenVibe.Shoes: the Shoes room (plan 2026-10-28 section 5.6) at open.shoes; offers from Deals with price history, a cited drops calendar, watches; database ov_shoes",
3566 "repo": "/opt/openvibe.shoes",
3567 "units": [
3568 "openvibe-shoes.service"
3569 ],
3570 "unitSources": {
3571 "openvibe-shoes.service": "deploy/systemd/openvibe-shoes.service"
3572 },
3573 "envFile": "/etc/openvibe/shoes.env",
3574 "env": {
3575 "required": "from-example"
3576 },
3577 "port": 5180,
3578 "ready": {
3579 "url": "http://127.0.0.1:5180/api/ready",
3580 "timeoutSeconds": 30
3581 },
3582 "noRestartPaths": [
3583 "docs/",
3584 "test/",
3585 "**/*.md"
3586 ],
3587 "databases": [
3588 {
3589 "name": "shoes",
3590 "engine": "postgresql",
3591 "database": "ov_shoes"
3592 }
3593 ],
3594 "drill": {
3595 "port": 15190,
3596 "databases": {
3597 "shoes": {
3598 "url": "DATABASE_URL",
3599 "directUrl": "DATABASE_DIRECT_URL"
3600 }
3601 },
3602 "env": {
3603 "PORT": "{port}",
3604 "HOST": "127.0.0.1",
3605 "VALKEY_URL": "redis://127.0.0.1:9/0",
3606 "OV_OAUTH_CLIENT_SECRET": "",
3607 "SHOES_DRILL": "1",
3608 "SHOES_EVENTS_URL": "",
3609 "SHOES_EVENTS_SECRET": "",
3610 "OV_LIVE_INTERNAL_URL": "http://127.0.0.1:9"
3611 },
3612 "acceptance": [
3613 {
3614 "path": "/api/ready",
3615 "status": 200,
3616 "keys": []
3617 }
3618 ],
3619 "counts": [
3620 {
3621 "db": "shoes",
3622 "table": "shoe_models"
3623 }
3624 ],
3625 "countsTolerance": 5,
3626 "_notes": "The restored copy boots with no service credentials (no tokens, no outbox publishing or subscriptions), Valkey on a closed port and every outbound service URL on a closed port; counts and readiness."
3627 }
3628 },
3629 "bench": {
3630 "_note": "OpenVibe.Bench: one AI benchmark and comparison engine for every room (plan 2026-10-28 section 4.12); database ov_bench",
3631 "repo": "/opt/openvibe.bench",
3632 "units": [
3633 "openvibe-bench.service"
3634 ],
3635 "unitSources": {
3636 "openvibe-bench.service": "deploy/systemd/openvibe-bench.service"
3637 },
3638 "envFile": "/etc/openvibe/bench.env",
3639 "env": {
3640 "required": "from-example"
3641 },
3642 "port": 5230,
3643 "ready": {
3644 "url": "http://127.0.0.1:5230/api/ready",
3645 "timeoutSeconds": 30
3646 },
3647 "noRestartPaths": [
3648 "docs/",
3649 "test/",
3650 "**/*.md"
3651 ],
3652 "databases": [
3653 {
3654 "name": "bench",
3655 "engine": "postgresql",
3656 "database": "ov_bench"
3657 }
3658 ],
3659 "drill": {
3660 "port": 15230,
3661 "databases": {
3662 "bench": {
3663 "url": "DATABASE_URL",
3664 "directUrl": "DATABASE_DIRECT_URL"
3665 }
3666 },
3667 "env": {
3668 "PORT": "{port}",
3669 "HOST": "127.0.0.1",
3670 "VALKEY_URL": "redis://127.0.0.1:9/0",
3671 "OV_OAUTH_CLIENT_SECRET": "",
3672 "BENCH_DRILL": "1",
3673 "BENCH_EVENTS_URL": "",
3674 "BENCH_EVENTS_SECRET": "",
3675 "OV_LIVE_INTERNAL_URL": "http://127.0.0.1:9"
3676 },
3677 "acceptance": [
3678 {
3679 "path": "/api/ready",
3680 "status": 200,
3681 "keys": []
3682 }
3683 ],
3684 "counts": [
3685 {
3686 "db": "bench",
3687 "table": "bench_kinds"
3688 }
3689 ],
3690 "countsTolerance": 5,
3691 "_notes": "The restored copy boots with no service credentials (no tokens, no outbox publishing or subscriptions), Valkey on a closed port and every outbound service URL on a closed port; counts and readiness."
3692 },
3693 "socketUnit": "openvibe-bench.socket"
3694 },
3695 "cam": {
3696 "_note": "OpenVibe.Cam: the camera app, my cameras with viewer grants, and stills for the Live cam button (plan 2026-10-28 section 5.13); database ov_cam",
3697 "repo": "/opt/openvibe.cam",
3698 "units": [
3699 "openvibe-cam.service"
3700 ],
3701 "unitSources": {
3702 "openvibe-cam.service": "deploy/systemd/openvibe-cam.service"
3703 },
3704 "envFile": "/etc/openvibe/cam.env",
3705 "env": {
3706 "required": "from-example"
3707 },
3708 "port": 5160,
3709 "ready": {
3710 "url": "http://127.0.0.1:5160/api/ready",
3711 "timeoutSeconds": 30
3712 },
3713 "noRestartPaths": [
3714 "docs/",
3715 "test/",
3716 "**/*.md"
3717 ],
3718 "databases": [
3719 {
3720 "name": "cam",
3721 "engine": "postgresql",
3722 "database": "ov_cam"
3723 }
3724 ],
3725 "drill": {
3726 "port": 15160,
3727 "databases": {
3728 "cam": {
3729 "url": "DATABASE_URL",
3730 "directUrl": "DATABASE_DIRECT_URL"
3731 }
3732 },
3733 "env": {
3734 "PORT": "{port}",
3735 "HOST": "127.0.0.1",
3736 "VALKEY_URL": "redis://127.0.0.1:9/0",
3737 "OV_OAUTH_CLIENT_SECRET": "",
3738 "CAM_DRILL": "1",
3739 "CAM_EVENTS_URL": "",
3740 "CAM_EVENTS_SECRET": "",
3741 "OV_LIVE_INTERNAL_URL": "http://127.0.0.1:9"
3742 },
3743 "acceptance": [
3744 {
3745 "path": "/api/ready",
3746 "status": 200,
3747 "keys": []
3748 }
3749 ],
3750 "counts": [
3751 {
3752 "db": "cam",
3753 "table": "cam_cameras"
3754 }
3755 ],
3756 "countsTolerance": 5,
3757 "_notes": "The restored copy boots with no service credentials (no tokens, no outbox publishing or subscriptions), Valkey on a closed port and every outbound service URL on a closed port; counts and readiness."
3758 },
3759 "socketUnit": "openvibe-cam.socket"
3760 },
3761 "domains": {
3762 "_note": "Added 2026-10-28 (plan 2026-10-28 §5.1): OpenVibe.Domains v1, the Domains room at openvibe.domains on loopback :5070. Stateless: prices from Host, history from Tools.; v2 control panel: database ov_domains (proposals, connections), OAuth client domains",
3763 "repo": "/opt/openvibe.domains",
3764 "units": [
3765 "openvibe-domains.service"
3766 ],
3767 "unitSources": {
3768 "openvibe-domains.service": "deploy/systemd/openvibe-domains.service"
3769 },
3770 "envFile": "/etc/openvibe/domains.env",
3771 "env": {
3772 "required": "from-example"
3773 },
3774 "port": 5070,
3775 "ready": {
3776 "url": "http://127.0.0.1:5070/api/ready",
3777 "timeoutSeconds": 30
3778 },
3779 "noRestartPaths": [
3780 "docs/",
3781 "test/",
3782 "**/*.md"
3783 ],
3784 "databases": [
3785 {
3786 "name": "domains",
3787 "engine": "postgresql",
3788 "database": "ov_domains"
3789 }
3790 ],
3791 "drill": {
3792 "port": 15070,
3793 "databases": {
3794 "domains": {
3795 "url": "DATABASE_URL",
3796 "directUrl": "DATABASE_DIRECT_URL"
3797 }
3798 },
3799 "env": {
3800 "PORT": "{port}",
3801 "HOST": "127.0.0.1",
3802 "VALKEY_URL": "redis://127.0.0.1:9/0",
3803 "OV_OAUTH_CLIENT_SECRET": "",
3804 "DOMAINS_DRILL": "1",
3805 "DOMAINS_EVENTS_URL": "",
3806 "DOMAINS_EVENTS_SECRET": "",
3807 "OV_LIVE_INTERNAL_URL": "http://127.0.0.1:9"
3808 },
3809 "acceptance": [
3810 {
3811 "path": "/api/ready",
3812 "status": 200,
3813 "keys": []
3814 }
3815 ],
3816 "counts": [
3817 {
3818 "db": "domains",
3819 "table": "domains_proposals"
3820 }
3821 ],
3822 "countsTolerance": 5,
3823 "_notes": "The restored copy boots with no service credentials (no tokens, no outbox publishing or subscriptions), Valkey on a closed port and every outbound service URL on a closed port; counts and readiness."
3824 },
3825 "socketUnit": "openvibe-domains.socket"
3826 },
3827 "meme": {
3828 "_note": "OpenVibe.Meme: the Meme room (plan 2026-10-28 section 5.7); entries cited to the moment, meme of the week, captions; database ov_meme",
3829 "repo": "/opt/openvibe.meme",
3830 "units": [
3831 "openvibe-meme.service"
3832 ],
3833 "unitSources": {
3834 "openvibe-meme.service": "deploy/systemd/openvibe-meme.service"
3835 },
3836 "envFile": "/etc/openvibe/meme.env",
3837 "env": {
3838 "required": "from-example"
3839 },
3840 "port": 5120,
3841 "ready": {
3842 "url": "http://127.0.0.1:5120/api/ready",
3843 "timeoutSeconds": 30
3844 },
3845 "noRestartPaths": [
3846 "docs/",
3847 "test/",
3848 "**/*.md"
3849 ],
3850 "databases": [
3851 {
3852 "name": "meme",
3853 "engine": "postgresql",
3854 "database": "ov_meme"
3855 }
3856 ],
3857 "drill": {
3858 "port": 15120,
3859 "databases": {
3860 "meme": {
3861 "url": "DATABASE_URL",
3862 "directUrl": "DATABASE_DIRECT_URL"
3863 }
3864 },
3865 "env": {
3866 "PORT": "{port}",
3867 "HOST": "127.0.0.1",
3868 "VALKEY_URL": "redis://127.0.0.1:9/0",
3869 "OV_OAUTH_CLIENT_SECRET": "",
3870 "MEME_DRILL": "1",
3871 "MEME_EVENTS_URL": "",
3872 "MEME_EVENTS_SECRET": "",
3873 "OV_LIVE_INTERNAL_URL": "http://127.0.0.1:9"
3874 },
3875 "acceptance": [
3876 {
3877 "path": "/api/ready",
3878 "status": 200,
3879 "keys": []
3880 }
3881 ],
3882 "counts": [
3883 {
3884 "db": "meme",
3885 "table": "meme_entries"
3886 }
3887 ],
3888 "countsTolerance": 5,
3889 "_notes": "The restored copy boots with no service credentials (no tokens, no outbox publishing or subscriptions), Valkey on a closed port and every outbound service URL on a closed port; counts and readiness."
3890 },
3891 "socketUnit": "openvibe-meme.socket"
3892 },
3893 "surf": {
3894 "_note": "OpenVibe.Surf: the Surf room (plan 2026-10-28 section 5.6); Ride, Web and Watch; stateless front, OAuth client surf",
3895 "repo": "/opt/openvibe.surf",
3896 "units": [
3897 "openvibe-surf.service"
3898 ],
3899 "unitSources": {
3900 "openvibe-surf.service": "deploy/systemd/openvibe-surf.service"
3901 },
3902 "envFile": "/etc/openvibe/surf.env",
3903 "env": {
3904 "required": "from-example"
3905 },
3906 "port": 5110,
3907 "ready": {
3908 "url": "http://127.0.0.1:5110/api/ready",
3909 "timeoutSeconds": 30
3910 },
3911 "noRestartPaths": [
3912 "docs/",
3913 "test/",
3914 "**/*.md"
3915 ],
3916 "databases": [],
3917 "socketUnit": "openvibe-surf.socket"
3918 },
3919 "today": {
3920 "_note": "OpenVibe.Today: the Today room (plan 2026-10-28 section 5.3); schedules, reminders, calendar feeds, the brief; database ov_today",
3921 "repo": "/opt/openvibe.today",
3922 "units": [
3923 "openvibe-today.service"
3924 ],
3925 "unitSources": {
3926 "openvibe-today.service": "deploy/systemd/openvibe-today.service"
3927 },
3928 "envFile": "/etc/openvibe/today.env",
3929 "env": {
3930 "required": "from-example"
3931 },
3932 "port": 5100,
3933 "ready": {
3934 "url": "http://127.0.0.1:5100/api/ready",
3935 "timeoutSeconds": 30
3936 },
3937 "noRestartPaths": [
3938 "docs/",
3939 "test/",
3940 "**/*.md"
3941 ],
3942 "databases": [
3943 {
3944 "name": "today",
3945 "engine": "postgresql",
3946 "database": "ov_today"
3947 }
3948 ],
3949 "drill": {
3950 "port": 15050,
3951 "databases": {
3952 "today": {
3953 "url": "DATABASE_URL",
3954 "directUrl": "DATABASE_DIRECT_URL"
3955 }
3956 },
3957 "env": {
3958 "PORT": "{port}",
3959 "HOST": "127.0.0.1",
3960 "VALKEY_URL": "redis://127.0.0.1:9/0",
3961 "OV_OAUTH_CLIENT_SECRET": "",
3962 "TODAY_DRILL": "1",
3963 "TODAY_EVENTS_URL": "",
3964 "TODAY_EVENTS_SECRET": "",
3965 "OV_LIVE_INTERNAL_URL": "http://127.0.0.1:9"
3966 },
3967 "acceptance": [
3968 {
3969 "path": "/api/ready",
3970 "status": 200,
3971 "keys": []
3972 }
3973 ],
3974 "counts": [
3975 {
3976 "db": "today",
3977 "table": "today_events"
3978 }
3979 ],
3980 "countsTolerance": 5,
3981 "_notes": "The restored copy boots with no service credentials (no tokens, no outbox publishing or subscriptions), Valkey on a closed port and every outbound service URL on a closed port; counts and readiness."
3982 },
3983 "socketUnit": "openvibe-today.socket"
3984 },
3985 "tours": {
3986 "_note": "OpenVibe.Tours: the Tours room (plan 2026-10-28 section 5.12); cities, places and routes from open data, IRL streams, stays, trips; database ov_tours",
3987 "repo": "/opt/openvibe.tours",
3988 "units": [
3989 "openvibe-tours.service"
3990 ],
3991 "unitSources": {
3992 "openvibe-tours.service": "deploy/systemd/openvibe-tours.service"
3993 },
3994 "envFile": "/etc/openvibe/tours.env",
3995 "env": {
3996 "required": "from-example"
3997 },
3998 "port": 5170,
3999 "ready": {
4000 "url": "http://127.0.0.1:5170/api/ready",
4001 "timeoutSeconds": 30
4002 },
4003 "noRestartPaths": [
4004 "docs/",
4005 "test/",
4006 "**/*.md"
4007 ],
4008 "databases": [
4009 {
4010 "name": "tours",
4011 "engine": "postgresql",
4012 "database": "ov_tours"
4013 }
4014 ],
4015 "drill": {
4016 "port": 15170,
4017 "databases": {
4018 "tours": {
4019 "url": "DATABASE_URL",
4020 "directUrl": "DATABASE_DIRECT_URL"
4021 }
4022 },
4023 "env": {
4024 "PORT": "{port}",
4025 "HOST": "127.0.0.1",
4026 "VALKEY_URL": "redis://127.0.0.1:9/0",
4027 "OV_OAUTH_CLIENT_SECRET": "",
4028 "TOURS_DRILL": "1",
4029 "TOURS_EVENTS_URL": "",
4030 "TOURS_EVENTS_SECRET": "",
4031 "OV_LIVE_INTERNAL_URL": "http://127.0.0.1:9"
4032 },
4033 "acceptance": [
4034 {
4035 "path": "/api/ready",
4036 "status": 200,
4037 "keys": []
4038 }
4039 ],
4040 "counts": [
4041 {
4042 "db": "tours",
4043 "table": "tours_cities"
4044 }
4045 ],
4046 "countsTolerance": 5,
4047 "_notes": "The restored copy boots with no service credentials (no tokens, no outbox publishing or subscriptions), Valkey on a closed port and every outbound service URL on a closed port; counts and readiness."
4048 }
4049 },
4050 "mail": {
4051 "_note": "OpenVibe.Mail (M1): receive-only Stalwart on inbound TCP 25, masks, burners and agent inboxes; database ov_mail; public door openvibe.email. openvibe-stalwart.service and its slice are auxiliary: installed from this checkout and listed by status/validate, but a Mail deploy restarts them only when deploy/stalwart/config.toml or one of their unit files changes. The binary is installed by hand from scripts/fetch-stalwart.js (pinned sha256) — ovhost never builds or downloads it. Message blobs at /var/lib/openvibe-stalwart/blobs are compressed and named by content hash, so they are archived by backup with verify:false and not sha256-checked. Remove openvibe.email from /etc/nginx/sites-enabled/parked-domains.conf before the vhost can win the name. Ports 587 and 465 stay closed.",
4052 "repo": "/opt/openvibe.mail",
4053 "units": [
4054 "openvibe-mail.service"
4055 ],
4056 "auxiliaryUnits": [
4057 {
4058 "unit": "openvibe-stalwart.service",
4059 "restartOn": [
4060 "deploy/systemd/openvibe-stalwart.service",
4061 "deploy/systemd/openvibe-stalwart.slice"
4062 ],
4063 "applyOn": [
4064 "deploy/stalwart/"
4065 ],
4066 "applyHint": "render and apply it as OpenVibe.Mail docs/INSTALL.md says (render.js, then apply.js with the plan); a restart alone keeps the old configuration"
4067 }
4068 ],
4069 "unitSources": {
4070 "openvibe-mail.service": "deploy/systemd/openvibe-mail.service",
4071 "openvibe-stalwart.service": "deploy/systemd/openvibe-stalwart.service",
4072 "openvibe-stalwart.slice": "deploy/systemd/openvibe-stalwart.slice"
4073 },
4074 "installUnits": true,
4075 "enableUnits": true,
4076 "envFile": "/etc/openvibe/mail.env",
4077 "env": {
4078 "required": [
4079 "NODE_ENV",
4080 "PORT",
4081 "HOST",
4082 "BASE_URL",
4083 "TRUST_PROXY",
4084 "DATABASE_URL",
4085 "DATABASE_DIRECT_URL",
4086 "VALKEY_URL",
4087 "VALKEY_PREFIX",
4088 "OV_NETWORK_URL",
4089 "OV_NETWORK_INTERNAL_URL",
4090 "OV_OAUTH_CLIENT_ID",
4091 "OV_OAUTH_CLIENT_SECRET",
4092 "MAIL_BASE_DOMAIN",
4093 "MAIL_MASK_DOMAIN",
4094 "BURNER_DOMAINS",
4095 "STALWART_URL",
4096 "STALWART_ADMIN_USER",
4097 "STALWART_ADMIN_PASSWORD"
4098 ]
4099 },
4100 "port": 5250,
4101 "health": {
4102 "url": "http://127.0.0.1:5250/api/health"
4103 },
4104 "ready": {
4105 "url": "http://127.0.0.1:5250/api/ready",
4106 "timeoutSeconds": 30
4107 },
4108 "noRestartPaths": [
4109 "docs/",
4110 "test/",
4111 "**/*.md",
4112 "deploy/stalwart/",
4113 "deploy/systemd/openvibe-stalwart.service",
4114 "deploy/systemd/openvibe-stalwart.slice"
4115 ],
4116 "databases": [
4117 {
4118 "name": "mail",
4119 "engine": "postgresql",
4120 "database": "ov_mail",
4121 "role": "ov_mail"
4122 }
4123 ],
4124 "objects": [
4125 {
4126 "name": "stalwart-blobs",
4127 "path": "/var/lib/openvibe-stalwart/blobs",
4128 "verify": false
4129 }
4130 ],
4131 "drill": {
4132 "supported": false,
4133 "reason": "M1 has no drill switch: readiness depends on the live Stalwart directory and the process writes the directory view at boot, so a second instance is not side-effect free."
4134 },
4135 "nginx": {
4136 "vhost": "openvibe.email.conf",
4137 "repoVhost": "deploy/nginx/openvibe.email.conf",
4138 "installOnDeploy": true
4139 },
4140 "lifecycle": {
4141 "liveness": {
4142 "endpoint": "/api/health",
4143 "means": "the process answers HTTP; nothing else is checked"
4144 },
4145 "shutdown": {
4146 "signal": "SIGTERM",
4147 "deadlineSeconds": 10,
4148 "drains": [
4149 "the HTTP server closes",
4150 "the purge job stops",
4151 "the database connection closes"
4152 ]
4153 },
4154 "startupRecovery": {
4155 "resumes": [
4156 {
4157 "kind": "jobs",
4158 "what": "due mask expiries and burner purges run on the next interval"
4159 },
4160 {
4161 "kind": "outbox",
4162 "what": "unsent mail.received and mail.mask.created events stay in the outbox until the relay starts"
4163 }
4164 ]
4165 },
4166 "rollback": {
4167 "conditions": [
4168 "automatic: not ready within ready.timeoutSeconds"
4169 ],
4170 "window": "while ovhost waits for readiness",
4171 "blockers": {
4172 "none": "the service's migrations only add; Stalwart's tables live in the same database and are not touched by a Mail release"
4173 }
4174 },
4175 "contracts": {
4176 "range": ">=0.329.0 <1.0.0"
4177 },
4178 "leases": {
4179 "claims": [
4180 {
4181 "what": "the purge job lease",
4182 "holder": "the one mail process",
4183 "expires": "MAIL_PURGE_INTERVAL_MS (60 s)",
4184 "fencing": "a conditional UPDATE on mail_settings.updated_at"
4185 }
4186 ]
4187 }
4188 }
4189 }
4190 }
4191}
4192