openvibers/openvibe.host Commits

Register Directory public service on port 5281 (#382)

OP OpenVibers committed 2 days ago · 9b91dfe061 · 3 files, +79 −0

Changes

ddiff --git a/docs/directory-launch.md b/docs/directory-launch.md
nnew file mode 100644
iindex 0000000..4a47423
---- /dev/null
++++ b/docs/directory-launch.md
@@@ -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.
ddiff --git a/scripts/register-directory.py b/scripts/register-directory.py
nnew file mode 100644
iindex 0000000..06b9db3
---- /dev/null
++++ b/scripts/register-directory.py
@@@ -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')
ddiff --git a/test/directory-registration.test.js b/test/directory-registration.test.js
nnew file mode 100644
iindex 0000000..8b46b60
---- /dev/null
++++ b/test/directory-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(),'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});}