openvibers/openvibe.host Commits

Register Compare public service on port 5280 (#378)

OP OpenVibers committed 3 days ago · 7ec13b5bc2 · 3 files, +78 −0

Changes

ddiff --git a/docs/compare-launch.md b/docs/compare-launch.md
nnew file mode 100644
iindex 0000000..6c53571
---- /dev/null
++++ b/docs/compare-launch.md
@@@ -0,0 +1,7 @@
++# Compare launch
++
++Compare is a stateless public read service at openvibe.compare, port 5280. Its only local state is a ten-minute cache. No database, account principal or provider credentials are needed. The service/site manifest and compare.comparison@1 are proposed separately in Contracts; the inventory lifecycle override permits launch before that library is released.
++
++After preparing `/opt/openvibe.compare` from OpenVibers/OpenVibe.Compare and setting PORT=5280, HOST=127.0.0.1 and BASE_URL=https://openvibe.compare with ov-svc-env, run `sudo python3 scripts/register-compare.py`. This checks port collisions and refuses to overwrite a differing existing service. Ship the ready Compare PR through merge-deploy.sh; install its nginx vhost with ov-vhost-install after the domain certificate exists. The vhost replaces the temporary redirect. Verify the home, `/comparisons/ai-models`, `/api/v1/comparisons`, `/sitemap.xml` and the Markdown response.
++
++Upstreams are public HTTPS reads: Exchange market, Bench chat.arena board and codes.patch runs, Codes harnesses, and Billing public plans. A failed upstream is shown as unavailable, never a fabricated or zero measurement. Billing's public plans endpoint exposes only checkout rate definitions; customer and ledger routes remain restricted.
ddiff --git a/scripts/register-compare.py b/scripts/register-compare.py
nnew file mode 100644
iindex 0000000..2364dc1
---- /dev/null
++++ b/scripts/register-compare.py
@@@ -0,0 +1,62 @@
++#!/usr/bin/env python3
++"""Register the stateless Compare service. Run as root; preserves existing inventory entries."""
++import json
++import os
++import pathlib
++import tempfile
++import sys
++
++
++def entry():
++ return {
++ '_note': 'Compare v1: stateless, sourced public comparisons; no database or service credentials.',
++ 'repo': '/opt/openvibe.compare',
++ 'origin': 'https://openvibe.compare',
++ 'units': ['openvibe-compare.service'],
++ 'unitSources': {'openvibe-compare.service': 'deploy/systemd/openvibe-compare.service'},
++ 'installUnits': True,
++ 'envFile': '/etc/openvibe/compare.env',
++ 'env': {'required': ['PORT', 'HOST', 'BASE_URL']},
++ 'port': 5280,
++ 'ready': {'url': 'http://127.0.0.1:5280/api/ready', 'timeoutSeconds': 30},
++ 'databases': [],
++ 'nginx': {'vhost': 'openvibe.compare.conf', 'repoVhost': 'deploy/nginx/openvibe.compare.conf', 'variant': 'http'},
++ 'lifecycle': {
++ 'liveness': {'endpoint': '/api/health', 'means': 'HTTP process answers'},
++ 'shutdown': {'signal': 'SIGTERM', 'deadlineSeconds': 10, 'drains': ['HTTP server closed']},
++ 'startupRecovery': {'resumes': [{'kind': 'state', 'what': 'Refetch public sources after ten-minute cache reset'}]},
++ 'rollback': {'conditions': ['Ready probe fails'], 'window': 'Any time', 'blockers': {'none': 'Stateless; no migrations'}},
++ 'leases': {'none': 'No workers or writes'},
++ },
++ }
++
++
++def register(target):
++ target = pathlib.Path(target)
++ data = json.loads(target.read_text())
++ if 'compare' in data['services']:
++ if data['services']['compare'] != entry():
++ raise SystemExit('compare already registered with different settings; refusing overwrite')
++ print('compare already registered')
++ return
++ for name, service in data['services'].items():
++ if service.get('port') == 5280 or service.get('drill', {}).get('port') == 5280:
++ raise SystemExit(f'Port 5280 already belongs to {name}')
++ data['services']['compare'] = entry()
++ st = target.stat()
++ fd, tmp = tempfile.mkstemp(prefix='.compare-', dir=target.parent)
++ try:
++ with os.fdopen(fd, 'w') as out:
++ json.dump(data, out, indent=2)
++ out.write('\n')
++ os.chmod(tmp, st.st_mode & 0o777)
++ os.chown(tmp, st.st_uid, st.st_gid)
++ os.replace(tmp, target)
++ finally:
++ if os.path.exists(tmp):
++ os.unlink(tmp)
++ print('compare registered on port 5280; no database')
++
++
++if __name__ == '__main__':
++ register(sys.argv[1] if len(sys.argv) > 1 else '/etc/openvibe/host.json')
ddiff --git a/test/compare-registration.test.js b/test/compare-registration.test.js
nnew file mode 100644
iindex 0000000..85c5f67
---- /dev/null
++++ b/test/compare-registration.test.js
@@@ -0,0 +1,9 @@
++'use strict';
++const assert=require('node:assert/strict'),fs=require('node:fs'),os=require('node:os'),path=require('node:path'),{spawnSync}=require('node:child_process');
++const dir=fs.mkdtempSync(path.join(os.tmpdir(),'compare-inventory-')),file=path.join(dir,'host.json');
++try {
++ const run=()=>spawnSync('python3',[path.join(__dirname,'../scripts/register-compare.py'),file],{encoding:'utf8'});
++ fs.writeFileSync(file,JSON.stringify({services:{existing:{port:5000}}}));assert.equal(run().status,0);let d=JSON.parse(fs.readFileSync(file));assert.equal(d.services.existing.port,5000);assert.equal(d.services.compare.port,5280);assert.deepEqual(d.services.compare.databases,[]);assert.equal(run().status,0);
++ fs.writeFileSync(file,JSON.stringify({services:{collision:{port:5280}}}));assert.notEqual(run().status,0);assert(!JSON.parse(fs.readFileSync(file)).services.compare);
++ console.log('compare registration: preserves inventory, idempotent, rejects port collision');
++} finally {fs.rmSync(dir,{recursive:true,force:true});}