openvibers/openvibe.events Commits

Events: events.openvibe.network answers 308 to openvibe.events (plan T7 layer 3) (#22)

OP OpenVibers committed 7 Oct 2026 · b50f5b7a99 · 4 files, +14 −98

Changes

ddiff --git a/README.md b/README.md
iindex 46fbd64..0a5e09e 100644
---- a/README.md
++++ b/README.md
@@@ -3,7 +3,7 @@
> Durable events, subscriptions, delivery, retry, dead letters and replay for the network.
**Status:** alpha (roadmap Wave 3), deployed on `openvibe-ovh` (unit `openvibe-events`, 127.0.0.1:4300, public at `https://openvibe.events`). Live (`live.stream.*`), Media, Chat, Games, Network and OpenRe publish to it in production, and every consumer webhook requires signature v2. Media's completion events reach Live through Events (`/internal/media-events`; Live keeps the older `/internal/media-webhook` during the transition), and the shared notification bell can follow `network.notification.created` over the realtime stream with a Network ticket.
--**Domain:** `openvibe.events` is the API and product origin (plan T7): the JSON API, the realtime gateway (ADR-005 put Realtime inside Events; `realtime.openvibe.network` has no runtime of its own), the home page and its `robots.txt`/`sitemap.xml`/`llms.txt`. `events.openvibe.network` answers the same surface until every client moves, then redirects (308).
++**Domain:** `openvibe.events` is the API and product origin (plan T7): the JSON API, the realtime gateway (ADR-005 put Realtime inside Events; `realtime.openvibe.network` has no runtime of its own), the home page and its `robots.txt`/`sitemap.xml`/`llms.txt`. `events.openvibe.network` answers 308 to the same path on openvibe.events.
**Plan:** OpenVibe End-to-End Realignment & Implementation Plan, revision 3 (20 Sep 2026), §6 and §6.3.
**License:** AGPL-3.0 (same as every OpenVibe service).
@@@ -357,9 +357,8 @@ fast-forward `/opt/openvibe.events`, install on a lockfile change, restart, wait
The unit is `openvibe-events.service` on `127.0.0.1:4300`, the env file `/etc/openvibe/events.env`. The database is
`ov_events` on the host's data role (`sudo /opt/openvibe.host/roles/data/add-service.sh events` writes its settings); the
release migrates it at boot. nginx serves `openvibe.events`, the API and product origin, from
--[deploy/nginx/openvibe.events.conf](deploy/nginx/openvibe.events.conf); `events.openvibe.network` answers the same
--surface from [deploy/nginx/events.openvibe.network.conf](deploy/nginx/events.openvibe.network.conf) until every client
--moves, then redirects (308). ovhost treats open
++[deploy/nginx/openvibe.events.conf](deploy/nginx/openvibe.events.conf); `events.openvibe.network` answers 308 to the
++same path there ([deploy/nginx/events.openvibe.network.conf](deploy/nginx/events.openvibe.network.conf)). ovhost treats open
realtime connections as a report-only drain, so `--wait-idle` waits for them.
Rollback: ovhost puts the previous sha back by itself when `/api/ready` does not answer 2xx after the
restart; afterwards `sudo ovhost rollback events --to <sha>`. Migrations only add tables and columns.
ddiff --git a/deploy/nginx/events.openvibe.network.conf b/deploy/nginx/events.openvibe.network.conf
iindex 2ec6c3e..0cf1c24 100644
---- a/deploy/nginx/events.openvibe.network.conf
++++ b/deploy/nginx/events.openvibe.network.conf
@@@ -4,32 +4,21 @@
## Deployed to /etc/nginx/sites-available/events.openvibe.network.conf — keep them in sync,
## and reload nginx after changing it.
##
--## The Node service (port 4300, systemd unit openvibe-events) serves:
--## /realtime/stream browser Server-Sent Events (long-lived; must not be buffered)
--## /api/health, /api/ready, /release.json
--## /metrics Prometheus text, answered only to direct loopback callers (never proxied)
--## /api/v1/events, /api/v1/subscriptions, /api/v1/checkpoints
--## public, token-guarded: developer apps (events.app.* capabilities, ADR-014)
--## and off-host services. Every request needs a Network token.
--## /api/v1/deliveries operators only (events.delivery.admin): host-local, refused here.
--## Services on this host call 127.0.0.1:4300 directly.
++## OpenVibe.Events' API, realtime stream and product home are on https://openvibe.events
++## (deploy/nginx/openvibe.events.conf, plan T7). This old name answers 308 to the same path and query
++## there: a 308 keeps the method and the body, so a POST to /api/v1/events is replayed as a POST.
++## Services on this host never come through here: they call 127.0.0.1:4300 directly.
## ═══════════════════════════════════════════════════════════════
--limit_req_zone $binary_remote_addr zone=ovevents_connect:10m rate=2r/s;
--limit_conn_zone $binary_remote_addr zone=ovevents_streams:10m;
--limit_req_zone $binary_remote_addr zone=ovevents_api:10m rate=20r/s;
--
--# ── HTTP → HTTPS redirect ──────────────────────────────────
server {
listen 80;
listen [::]:80;
server_name events.openvibe.network;
location /.well-known/acme-challenge/ { root /var/www/certbot; }
-- location / { return 301 https://events.openvibe.network$request_uri; }
++ location / { return 308 https://openvibe.events$request_uri; }
}
--# ── HTTPS server ───────────────────────────────────────────
server {
listen 443 ssl;
listen [::]:443 ssl;
@@@ -43,83 +32,7 @@ server {
access_log /var/log/nginx/events.openvibe.network.access.log;
error_log /var/log/nginx/events.openvibe.network.error.log;
-- client_max_body_size 2m;
--
-- add_header X-Content-Type-Options nosniff always;
-- add_header Referrer-Policy strict-origin-when-cross-origin always;
add_header Strict-Transport-Security "max-age=31536000; includeSubDomains" always;
-- # ── Realtime (SSE): no buffering, no compression, long reads ──
-- # The service sends a heartbeat comment every 25 s, well inside these timeouts and
-- # Cloudflare's 100 s idle limit. CORS is answered by the service (https://*.openvibe.* only).
-- location = /realtime/stream {
-- access_log /var/log/nginx/events.openvibe.network.access.log ov_events_noquery; # never the ?ticket= (log_format_events_noquery.conf)
-- limit_req zone=ovevents_connect burst=20 nodelay;
-- limit_conn ovevents_streams 20;
-- proxy_pass http://127.0.0.1:4300;
-- proxy_http_version 1.1;
-- proxy_set_header Connection "";
-- proxy_set_header Host $host;
-- proxy_set_header X-Real-IP $remote_addr;
-- proxy_set_header X-Forwarded-For $remote_addr;
-- proxy_set_header CF-Connecting-IP $remote_addr;
-- proxy_set_header X-Forwarded-Proto $scheme;
-- proxy_set_header Last-Event-ID $http_last_event_id;
-- proxy_buffering off;
-- proxy_cache off;
-- gzip off;
-- chunked_transfer_encoding on;
-- proxy_read_timeout 1h;
-- proxy_send_timeout 1h;
-- send_timeout 1h;
-- }
--
-- # X-Forwarded-For on every proxied location: the service treats a request without it from
-- # 127.0.0.1 as a local caller (GET /metrics answers only those).
-- location = /api/health { proxy_pass http://127.0.0.1:4300; proxy_set_header Host $host; proxy_set_header X-Real-IP $remote_addr; proxy_set_header X-Forwarded-For $remote_addr; proxy_set_header CF-Connecting-IP $remote_addr; }
-- location = /api/ready { proxy_pass http://127.0.0.1:4300; proxy_set_header Host $host; proxy_set_header X-Real-IP $remote_addr; proxy_set_header X-Forwarded-For $remote_addr; proxy_set_header CF-Connecting-IP $remote_addr; }
-- location = /release.json { proxy_pass http://127.0.0.1:4300; proxy_set_header Host $host; proxy_set_header X-Real-IP $remote_addr; proxy_set_header X-Forwarded-For $remote_addr; proxy_set_header CF-Connecting-IP $remote_addr; }
-- location = /limits.json { proxy_pass http://127.0.0.1:4300; proxy_set_header Host $host; proxy_set_header X-Real-IP $remote_addr; proxy_set_header X-Forwarded-For $remote_addr; proxy_set_header CF-Connecting-IP $remote_addr; }
--
-- # Metrics are for host-local scrapers only (http://127.0.0.1:4300/metrics).
-- location = /metrics { return 404; }
--
-- # ── Token-guarded API for developer apps (see header) ──
-- # Per-project quotas are enforced by the service; this only caps per-IP request rates.
-- location ~ ^/api/v1/(events|subscriptions|checkpoints)(/|$) {
-- limit_req zone=ovevents_api burst=40 nodelay;
-- proxy_pass http://127.0.0.1:4300;
-- proxy_http_version 1.1;
-- proxy_set_header Connection "";
-- proxy_set_header Host $host;
-- proxy_set_header X-Real-IP $remote_addr;
-- proxy_set_header X-Forwarded-For $remote_addr;
-- proxy_set_header CF-Connecting-IP $remote_addr;
-- proxy_set_header X-Forwarded-Proto $scheme;
-- }
--
-- # ── Everything else under /api/: host-local only (see header) ──
-- location /api/ {
-- return 403;
-- # To expose it (token-guarded) to off-host services instead:
-- # proxy_pass http://127.0.0.1:4300;
-- # proxy_http_version 1.1;
-- # proxy_set_header Host $host;
-- # proxy_set_header X-Real-IP $remote_addr; # client address from $remote_addr only (realip),
-- # proxy_set_header X-Forwarded-For $remote_addr; # never appended to or passed through
-- # proxy_set_header CF-Connecting-IP $remote_addr;
-- # proxy_set_header X-Forwarded-Proto $scheme;
-- }
--
-- location = / {
-- proxy_pass http://127.0.0.1:4300;
-- proxy_set_header Host $host;
-- proxy_set_header X-Real-IP $remote_addr;
-- proxy_set_header X-Forwarded-For $remote_addr;
-- proxy_set_header CF-Connecting-IP $remote_addr;
-- }
--
-- location / {
-- return 404;
-- }
++ location / { return 308 https://openvibe.events$request_uri; }
}
ddiff --git a/test/home.test.js b/test/home.test.js
iindex 0bcb1ea..079ea13 100644
---- a/test/home.test.js
++++ b/test/home.test.js
@@@ -136,6 +136,10 @@ t('deploy/nginx/openvibe.events.conf is the API and product origin, with no redi
const refusals = [conf.indexOf('location = /metrics { return 404; }'), conf.indexOf('location /internal/ { return 404; }')];
assert.ok(refusals.every((i) => i >= 0), 'metrics and /internal/ are refused');
assert.ok(!conf.includes('events.openvibe.network$request_uri'), 'no catch-all redirect to the old API host');
++ const old = fs.readFileSync(path.join(__dirname, '..', 'deploy', 'nginx', 'events.openvibe.network.conf'), 'utf8')
++ .split('\n').filter((l) => !/^\s*#/.test(l)).join('\n');
++ assert.ok(!old.includes('proxy_pass'), 'events.openvibe.network serves nothing itself');
++ assert.strictEqual((old.match(/return 308 https:\/\/openvibe\.events\$request_uri;/g) || []).length, 2, 'HTTP and HTTPS answer 308 to openvibe.events');
assert.ok(conf.includes('return 301 https://openvibe.events$request_uri;'), ':80 and www reach the apex');
});
ddiff --git a/test/readme-examples.test.js b/test/readme-examples.test.js
iindex 979929c..f1aef7b 100644
---- a/test/readme-examples.test.js
++++ b/test/readme-examples.test.js
@@@ -29,7 +29,7 @@ t('realtime example gets a new ticket and resumes from the last cursor', async (
assert.strictEqual(options.headers.Authorization, 'Bearer test-user-token');
return { ok: true, json: async () => ({
ticket: `ticket-${requests}`,
-- stream_url: 'https://events.openvibe.network/realtime/stream',
++ stream_url: 'https://openvibe.events/realtime/stream',
topics: ['network.notification.*'],
}) };
},