openvibers/openvibe.host Commits
Register Directory public service on port 5281 (#382)
Changes
nnew file mode 100644@@@ -0,0 +1,7 @@++# Directory launch++++Directory is a public read service at openvibe.directory, port 5281. It refreshes public source feeds at startup and every six hours. The local-business pilot reads a bounded OpenStreetMap map area. No database, account principal, or provider credentials are needed for this public first release.++++Prepare `/opt/openvibe.directory` from OpenVibers/OpenVibe.Directory with PORT=5281, HOST=127.0.0.1, and BASE_URL=https://openvibe.directory. Run `sudo python3 scripts/register-directory.py` from the current Host checkout; it checks port collisions and refuses to replace a differing entry. Deploy the ready Directory PR through merge-deploy.sh, install the nginx vhost with ov-vhost-install, and verify the home, `/api/v1/kinds`, `/api/v1/entries`, `/sitemap.xml`, and `/llms.txt`.++++The service has no private-person search or reverse phone lookup. A later opt-in claim flow needs a signed-in identity and persistent storage before it can publish any person entry.nnew file mode 100644@@@ -0,0 +1,63 @@++#!/usr/bin/env python3++"""Register the stateless Directory service. Run as root; preserves existing inventory entries."""++import json++import os++import pathlib++import tempfile++import sys++++++def entry():++ return {++ '_note': 'Directory v1: stateless, source-linked public directories; no database or service credentials.',++ 'repo': '/opt/openvibe.directory',++ 'origin': 'https://openvibe.directory',++ 'units': ['openvibe-directory.service'],++ 'unitSources': {'openvibe-directory.service': 'deploy/systemd/openvibe-directory.service'},++ 'installUnits': True,++ 'envFile': '/etc/openvibe/directory.env',++ 'env': {'required': ['PORT', 'HOST', 'BASE_URL']},++ 'port': 5281,++ 'ready': {'url': 'http://127.0.0.1:5281/api/ready', 'timeoutSeconds': 30},++ 'databases': [],++ 'nginx': {'vhost': 'openvibe.directory.conf', 'repoVhost': 'deploy/nginx/openvibe.directory.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 six-hour cache reset'}]},++ 'rollback': {'conditions': ['Ready probe fails'], 'window': 'Any time', 'blockers': {'none': 'Stateless; no migrations'}},++ 'contracts': {'none': 'Stateless public reads; no runtime Contracts dependency. Directory schema release is held separately.'},++ 'leases': {'none': 'No workers or writes'},++ },++ }++++++def register(target):++ target = pathlib.Path(target)++ data = json.loads(target.read_text())++ if 'directory' in data['services']:++ if data['services']['directory'] != entry():++ raise SystemExit('directory already registered with different settings; refusing overwrite')++ print('directory already registered')++ return++ for name, service in data['services'].items():++ if service.get('port') == 5281 or service.get('drill', {}).get('port') == 5281:++ raise SystemExit(f'Port 5281 already belongs to {name}')++ data['services']['directory'] = entry()++ st = target.stat()++ fd, tmp = tempfile.mkstemp(prefix='.directory-', 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('directory registered on port 5281; no database')++++++if __name__ == '__main__':++ register(sys.argv[1] if len(sys.argv) > 1 else '/etc/openvibe/host.json')nnew file mode 100644@@@ -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(),'directory-inventory-')),file=path.join(dir,'host.json');++try {++ const run=()=>spawnSync('python3',[path.join(__dirname,'../scripts/register-directory.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.directory.port,5281);assert.deepEqual(d.services.directory.databases,[]);assert.deepEqual(require('../lib/lifecycle').check(d.services.directory.lifecycle,{runs:true}),[]);assert.equal(run().status,0);++ fs.writeFileSync(file,JSON.stringify({services:{collision:{port:5281}}}));assert.notEqual(run().status,0);assert(!JSON.parse(fs.readFileSync(file)).services.directory);++ console.log('directory registration: preserves inventory, idempotent, rejects port collision');++} finally {fs.rmSync(dir,{recursive:true,force:true});}
new file mode 100644
new file mode 100644
@@ -0,0 +1,7 @@
@@ -0,0 +1,7 @@
+# Directory launch
+
+Directory is a public read service at openvibe.directory, port 5281. It refreshes public source feeds at startup and every six hours. The local-business pilot reads a bounded OpenStreetMap map area. No database, account principal, or provider credentials are needed for this public first release.
+
+Prepare `/opt/openvibe.directory` from OpenVibers/OpenVibe.Directory with PORT=5281, HOST=127.0.0.1, and BASE_URL=https://openvibe.directory. Run `sudo python3 scripts/register-directory.py` from the current Host checkout; it checks port collisions and refuses to replace a differing entry. Deploy the ready Directory PR through merge-deploy.sh, install the nginx vhost with ov-vhost-install, and verify the home, `/api/v1/kinds`, `/api/v1/entries`, `/sitemap.xml`, and `/llms.txt`.
+
+The service has no private-person search or reverse phone lookup. A later opt-in claim flow needs a signed-in identity and persistent storage before it can publish any person entry.
new file mode 100644
new file mode 100644
@@ -0,0 +1,63 @@
@@ -0,0 +1,63 @@
+#!/usr/bin/env python3
+"""Register the stateless Directory service. Run as root; preserves existing inventory entries."""
+import json
+import os
+import pathlib
+import tempfile
+import sys
+
+
+def entry():
+ return {
+ '_note': 'Directory v1: stateless, source-linked public directories; no database or service credentials.',
+ 'repo': '/opt/openvibe.directory',
+ 'origin': 'https://openvibe.directory',
+ 'units': ['openvibe-directory.service'],
+ 'unitSources': {'openvibe-directory.service': 'deploy/systemd/openvibe-directory.service'},
+ 'installUnits': True,
+ 'envFile': '/etc/openvibe/directory.env',
+ 'env': {'required': ['PORT', 'HOST', 'BASE_URL']},
+ 'port': 5281,
+ 'ready': {'url': 'http://127.0.0.1:5281/api/ready', 'timeoutSeconds': 30},
+ 'databases': [],
+ 'nginx': {'vhost': 'openvibe.directory.conf', 'repoVhost': 'deploy/nginx/openvibe.directory.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 six-hour cache reset'}]},
+ 'rollback': {'conditions': ['Ready probe fails'], 'window': 'Any time', 'blockers': {'none': 'Stateless; no migrations'}},
+ 'contracts': {'none': 'Stateless public reads; no runtime Contracts dependency. Directory schema release is held separately.'},
+ 'leases': {'none': 'No workers or writes'},
+ },
+ }
+
+
+def register(target):
+ target = pathlib.Path(target)
+ data = json.loads(target.read_text())
+ if 'directory' in data['services']:
+ if data['services']['directory'] != entry():
+ raise SystemExit('directory already registered with different settings; refusing overwrite')
+ print('directory already registered')
+ return
+ for name, service in data['services'].items():
+ if service.get('port') == 5281 or service.get('drill', {}).get('port') == 5281:
+ raise SystemExit(f'Port 5281 already belongs to {name}')
+ data['services']['directory'] = entry()
+ st = target.stat()
+ fd, tmp = tempfile.mkstemp(prefix='.directory-', 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('directory registered on port 5281; no database')
+
+
+if __name__ == '__main__':
+ register(sys.argv[1] if len(sys.argv) > 1 else '/etc/openvibe/host.json')
new file mode 100644
new file mode 100644
@@ -0,0 +1,9 @@
@@ -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(),'directory-inventory-')),file=path.join(dir,'host.json');
+try {
+ const run=()=>spawnSync('python3',[path.join(__dirname,'../scripts/register-directory.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.directory.port,5281);assert.deepEqual(d.services.directory.databases,[]);assert.deepEqual(require('../lib/lifecycle').check(d.services.directory.lifecycle,{runs:true}),[]);assert.equal(run().status,0);
+ fs.writeFileSync(file,JSON.stringify({services:{collision:{port:5281}}}));assert.notEqual(run().status,0);assert(!JSON.parse(fs.readFileSync(file)).services.directory);
+ console.log('directory registration: preserves inventory, idempotent, rejects port collision');
+} finally {fs.rmSync(dir,{recursive:true,force:true});}