unoverse 0.1.83 → 0.1.84

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -80,6 +80,37 @@
80
80
  See docs/runbooks/02-database.md for details.
81
81
  when: ext_result.rc != 0
82
82
 
83
+ # BEFORE MIGRATIONS, OR THEY CANNOT CREATE THEIR OWN BOOKKEEPING TABLE. PostgreSQL 15+
84
+ # dropped the implicit CREATE on schema public, and DigitalOcean creates every database
85
+ # owned by its admin user, so the universe user connects fine and then fails on the
86
+ # first CREATE TABLE. Granting is idempotent and only ever widens this one user's rights
87
+ # inside its own database — it touches nothing else on a shared cluster.
88
+ #
89
+ # no_log because the admin connection string is an argument here. It arrives from
90
+ # terraform at deploy time and is never written to the server.
91
+ - name: "[3b/5] Grant the universe user rights on its own schema"
92
+ command: >
93
+ docker compose exec -T -e NODE_TLS_REJECT_UNAUTHORIZED=0 -e ADMIN_URL={{ pg_admin_url }} unoverse node -e
94
+ "const{Client}=require('pg');
95
+ const c=new Client({connectionString:process.env.ADMIN_URL,ssl:{rejectUnauthorized:false}});
96
+ (async()=>{
97
+ await c.connect();
98
+ await c.query('GRANT ALL ON SCHEMA public TO \"universe\"');
99
+ await c.query('GRANT ALL ON ALL TABLES IN SCHEMA public TO \"universe\"');
100
+ await c.query('GRANT ALL ON ALL SEQUENCES IN SCHEMA public TO \"universe\"');
101
+ await c.end();
102
+ console.log('granted');
103
+ })().catch(e=>{console.error(e.message);process.exit(1)})"
104
+ args:
105
+ chdir: /opt/gravity
106
+ when: (pg_admin_url | default('')) | length > 0
107
+ no_log: true
108
+ register: grant_result
109
+
110
+ - name: "[3b/5] Schema rights"
111
+ debug:
112
+ msg: "{{ 'universe may create objects in its database' if (pg_admin_url | default('')) | length > 0 else 'skipped — bring-your-own database, permissions are yours' }}"
113
+
83
114
  # Apply the .sql migrations with node-pg-migrate — same as `./unoverse db-setup`
84
115
  # (scripts/lib/db-setup.sh). The old programmatic `require('./dist/db')` table
85
116
  # setup is retired: the engine no longer ships a dist, and .sql migrations under
@@ -12,6 +12,19 @@ locals {
12
12
  pg_pooled = local.pg_managed ? "postgresql://${digitalocean_database_user.universe[0].name}:${digitalocean_database_user.universe[0].password}@${digitalocean_database_connection_pool.universe[0].private_host}:${digitalocean_database_connection_pool.universe[0].port}/${digitalocean_database_connection_pool.universe[0].name}?sslmode=require&pgbouncer=true" : var.byo_postgres_url
13
13
  pg_direct = local.pg_managed ? "postgresql://${digitalocean_database_user.universe[0].name}:${digitalocean_database_user.universe[0].password}@${local.pg_cluster_host}:${local.pg_cluster_port}/${digitalocean_database_db.universe[0].name}?sslmode=require" : var.byo_postgres_url
14
14
 
15
+ # THE ADMIN URL EXISTS FOR EXACTLY ONE STATEMENT. PostgreSQL 15+ no longer grants CREATE
16
+ # on schema public to PUBLIC, and DigitalOcean owns every database it creates as doadmin.
17
+ # So the universe user can connect and can create nothing: the first migration dies on
18
+ # "permission denied for schema public" after the extensions step reports OK, which reads
19
+ # like the database is fine. Only the cluster's admin can hand over those rights.
20
+ #
21
+ # It is a separate output and NOT part of env_production on purpose. The server holds the
22
+ # universe user's credentials and must never hold the cluster admin's — deploy reads this,
23
+ # spends it on one GRANT, and it goes no further.
24
+ pg_admin_user = local.pg_adopt ? data.digitalocean_database_cluster.existing_pg[0].user : (local.provision_pg ? digitalocean_database_cluster.pg[0].user : "")
25
+ pg_admin_pass = local.pg_adopt ? data.digitalocean_database_cluster.existing_pg[0].password : (local.provision_pg ? digitalocean_database_cluster.pg[0].password : "")
26
+ pg_admin_url = local.pg_managed ? "postgresql://${local.pg_admin_user}:${local.pg_admin_pass}@${local.pg_cluster_host}:${local.pg_cluster_port}/${digitalocean_database_db.universe[0].name}?sslmode=require" : ""
27
+
15
28
  # Redis is always ours — provisioned above, no BYO branch.
16
29
  redis_host = digitalocean_database_cluster.redis.private_host
17
30
  redis_port = digitalocean_database_cluster.redis.port
@@ -39,6 +52,14 @@ output "api_url" {
39
52
  value = local.has_domain ? "https://${local.api_host}" : "http://${digitalocean_loadbalancer.public.ip}"
40
53
  }
41
54
 
55
+ # Read by deploy for the one-time schema grant, never written to the server. Empty when
56
+ # the database is BYO: somebody else's cluster, whose permissions are theirs to run.
57
+ output "pg_admin_url" {
58
+ value = local.pg_admin_url
59
+ description = "Cluster admin connection to this universe's database — used once, to grant the universe user rights on schema public"
60
+ sensitive = true
61
+ }
62
+
42
63
  output "env_production" {
43
64
  sensitive = true
44
65
  description = "Rendered .env.production — complete, write to a file and deploy"
@@ -363,6 +363,13 @@ cmd_deploy() {
363
363
 
364
364
  _adopted_db_access "$cloud" grant
365
365
 
366
+ # The cluster admin connection, held in this shell and nowhere else. db-setup spends it
367
+ # on a single GRANT so the universe user can create objects in its own database; it is
368
+ # never written to the server, and an empty value (BYO database, or a ground without the
369
+ # output) simply skips that step.
370
+ local pg_admin_url
371
+ pg_admin_url=$(terraform -chdir="$ROOT/infra/$cloud" output -raw pg_admin_url 2>/dev/null)
372
+
366
373
 
367
374
  # Read the deploy target from the rendered settings
368
375
  local deploy_host deploy_user
@@ -476,6 +483,7 @@ EOF
476
483
  -i "$tmp_inventory" \
477
484
  "$ansible_dir/playbooks/db-setup.yml" \
478
485
  -e "universe_root=$ROOT" \
486
+ -e "pg_admin_url=$pg_admin_url" \
479
487
  -e "env_file=$env_prod" || { rm -f "$tmp_inventory"; fail "db setup failed — fix and re-run: unoverse deploy db"; exit 1; }
480
488
  echo ""
481
489
  info "[3/3] Verifying..."
@@ -498,6 +506,7 @@ EOF
498
506
  -i "$tmp_inventory" \
499
507
  "$ansible_dir/playbooks/db-setup.yml" \
500
508
  -e "universe_root=$ROOT" \
509
+ -e "pg_admin_url=$pg_admin_url" \
501
510
  -e "env_file=$env_prod"
502
511
  ;;
503
512
  test|check)
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "unoverse",
3
- "version": "0.1.83",
3
+ "version": "0.1.84",
4
4
  "description": "The Unoverse front door — create a Studio project, a universe, or a client app, and launch Studio.",
5
5
  "license": "SEE LICENSE IN README.md",
6
6
  "type": "module",