openvibers/openvibe.host Commits

Provision Directory opt-in listing storage and OAuth (#383)

OP OpenVibers committed 2 days ago · 46b4db2c89 · 3 files, +24 −8

Changes

ddiff --git a/docs/directory-launch.md b/docs/directory-launch.md
iindex 4a47423..8a8e956 100644
---- a/docs/directory-launch.md
++++ b/docs/directory-launch.md
@@@ -1,7 +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.
++Directory serves source-linked public listings at openvibe.directory, port 5281. It refreshes public feeds at startup and every six hours. The local-business pilot reads a bounded OpenStreetMap map area. Signed-in, opt-in people listings use the dedicated `ov_directory` PostgreSQL database and a Network OAuth client.
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.
++After the people-listing PRs ship, rerun the registration script to upgrade the existing inventory entry. Run `sudo ovhost data provision directory`; use Network's service-principal script to rotate the seeded `directory` OAuth client into `/etc/openvibe/directory.env` without printing its secret. Deploy Directory, then check `/auth/health` and that `/people/me` directs an anonymous visitor to sign-in. The service has no imported people, private-person search, or reverse phone lookup.
ddiff --git a/scripts/register-directory.py b/scripts/register-directory.py
iindex 06b9db3..adedca7 100644
---- a/scripts/register-directory.py
++++ b/scripts/register-directory.py
@@@ -7,7 +7,7 @@ import tempfile
import sys
--def entry():
++def old_entry():
return {
'_note': 'Directory v1: stateless, source-linked public directories; no database or service credentials.',
'repo': '/opt/openvibe.directory',
@@@ -32,16 +32,30 @@ def entry():
}
++def entry():
++ service = old_entry()
++ service['_note'] = 'Directory: sourced public reads plus opt-in people listings in PostgreSQL.'
++ service['databases'] = [{'name': 'directory', 'engine': 'postgresql', 'database': 'ov_directory'}]
++ service['env'] = {'required': ['PORT', 'HOST', 'BASE_URL', 'DATABASE_URL', 'OV_OAUTH_CLIENT_ID', 'OV_OAUTH_CLIENT_SECRET']}
++ service['lifecycle']['startupRecovery']['resumes'].append({'kind': 'state', 'what': 'Reload opt-in people listings from PostgreSQL.'})
++ service['lifecycle']['rollback']['blockers'] = {'none': 'People migration is expand-only and the previous version ignores the new table.'}
++ service['lifecycle']['contracts'] = {'none': 'Directory uses signed Network user tokens and its own PostgreSQL table; the service manifest release is separate.'}
++ service['lifecycle']['leases'] = {'none': 'No background writer lease; public source refresh stays in process.'}
++ return service
++
++
def register(target):
target = pathlib.Path(target)
data = json.loads(target.read_text())
if 'directory' in data['services']:
-- if data['services']['directory'] != entry():
++ if data['services']['directory'] == entry():
++ print('directory already registered')
++ return
++ if data['services']['directory'] != old_entry():
raise SystemExit('directory already registered with different settings; refusing overwrite')
-- print('directory already registered')
-- return
++ data['services']['directory'] = entry()
for name, service in data['services'].items():
-- if service.get('port') == 5281 or service.get('drill', {}).get('port') == 5281:
++ if name != 'directory' and (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()
ddiff --git a/test/directory-registration.test.js b/test/directory-registration.test.js
iindex 8b46b60..c0971d6 100644
---- a/test/directory-registration.test.js
++++ b/test/directory-registration.test.js
@@@ -3,7 +3,9 @@ const assert=require('node:assert/strict'),fs=require('node:fs'),os=require('nod
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:{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.equal(d.services.directory.databases[0].database,'ov_directory');assert.deepEqual(require('../lib/lifecycle').check(d.services.directory.lifecycle,{runs:true}),[]);assert.equal(run().status,0);
++ const legacy=spawnSync('python3',['-c',`import runpy,json;print(json.dumps(runpy.run_path('${path.join(__dirname,'../scripts/register-directory.py')}')['old_entry']()))`],{encoding:'utf8'});
++ fs.writeFileSync(file,JSON.stringify({services:{directory:JSON.parse(legacy.stdout)}}));assert.equal(run().status,0);d=JSON.parse(fs.readFileSync(file));assert.equal(d.services.directory.databases[0].database,'ov_directory');
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});}