diff --git a/README.md b/README.md index 8266c24..79b2db9 100644 --- a/README.md +++ b/README.md @@ -912,6 +912,10 @@ fixtures also use the production candidate. These fixtures do not replace a fresh verified backup of every production database or a scoped rollout and rollback plan for the shared service. +## Fashion catalog databases + +The application bootstraps are scoped to Scraping, the Iceberg catalog and the Vestiaire developer platform. They do not redeploy or change the shared PostgreSQL runtime. Use certificate-verified TLS and protected application credentials. Existing credentials and ownership must be preserved; drift requires separate review. + ## Jotwink Backup And Restore Jotwink has an independent backup service and health timestamp so a failed @@ -959,7 +963,7 @@ Jotwink host backup is opt-in through the `jotwink` Compose profile. Provision a BetaCrew has an independent six-hour backup service for `betacrew` and `keycloak_betacrew`. Dumps are validated, encrypted with OpenSSL CMS AES-256-GCM before persistent storage, checksummed, and retained for 35 days. -Provision `/var/lib/makepad/postgres-backups/betacrew` for uid 70 and install +Provision `/var/lib/makepad/postgres-backups/betacrew` for uid 999 and install the public recipient certificate at `/etc/makepad/postgres-backup/betacrew-recipient.pem`; keep its private key offline. Replicate completed backup directories to independently administered diff --git a/bootstrap/vestiaire-developer-platform.sql b/bootstrap/vestiaire-developer-platform.sql new file mode 100644 index 0000000..f28dcfc --- /dev/null +++ b/bootstrap/vestiaire-developer-platform.sql @@ -0,0 +1,35 @@ +\set ON_ERROR_STOP on +SELECT pg_advisory_lock(hashtext('makepad-postgres'),hashtext('vestiaire_developer-bootstrap')); +SELECT NOT EXISTS(SELECT 1 FROM pg_roles WHERE rolname='vestiaire_developer_app') AND NOT EXISTS(SELECT 1 FROM pg_database WHERE datname='vestiaire_developer') AS fresh_install \gset +\if :fresh_install +\if :{?vestiaire_developer_app_password} +\else + \echo 'missing vestiaire_developer_app_password' + DO $$ BEGIN RAISE EXCEPTION 'Vestiaire bootstrap precondition failed'; END $$; +\endif +SELECT length(:'vestiaire_developer_app_password') >= 32 AS strong_password \gset +\if :strong_password +\else + \echo 'vestiaire_developer_app_password must contain at least 32 characters' + DO $$ BEGIN RAISE EXCEPTION 'Vestiaire bootstrap precondition failed'; END $$; +\endif +CREATE ROLE vestiaire_developer_app LOGIN NOSUPERUSER NOCREATEDB NOCREATEROLE NOREPLICATION NOBYPASSRLS PASSWORD :'vestiaire_developer_app_password'; +CREATE DATABASE vestiaire_developer OWNER vestiaire_developer_app; +REVOKE ALL ON DATABASE vestiaire_developer FROM PUBLIC; +GRANT CONNECT ON DATABASE vestiaire_developer TO vestiaire_developer_app; +\else + SELECT EXISTS( + SELECT 1 FROM pg_database d JOIN pg_roles r ON d.datdba=r.oid + WHERE d.datname='vestiaire_developer' AND r.rolname='vestiaire_developer_app' + AND r.rolcanlogin AND NOT r.rolsuper AND NOT r.rolcreatedb + AND NOT r.rolcreaterole AND NOT r.rolreplication AND NOT r.rolbypassrls + AND NOT EXISTS(SELECT 1 FROM pg_auth_members m WHERE m.member=r.oid OR m.roleid=r.oid) + AND NOT EXISTS(SELECT 1 FROM aclexplode(COALESCE(d.datacl,acldefault('d',d.datdba))) a WHERE a.grantee<>d.datdba) + ) AS existing_is_scoped \gset + \if :existing_is_scoped + \echo 'Existing Vestiaire ownership and isolation verified; credentials unchanged.' + \else + DO $$ BEGIN RAISE EXCEPTION 'Existing Vestiaire installation is incomplete or unsafe'; END $$; + \endif +\endif +SELECT pg_advisory_unlock(hashtext('makepad-postgres'),hashtext('vestiaire_developer-bootstrap'));