unoverse 0.1.83 → 0.1.85
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
|
|
@@ -96,9 +127,33 @@
|
|
|
96
127
|
register: migrate_result
|
|
97
128
|
ignore_errors: yes
|
|
98
129
|
|
|
130
|
+
# THE WHOLE FILE IS NOT THE ANSWER TO "DID IT WORK". This printed every line of the
|
|
131
|
+
# baseline — several hundred lines of CREATE TABLE nobody asked to read, burying the one
|
|
132
|
+
# line that says whether anything ran. Keep the names and the verdict.
|
|
99
133
|
- name: "[4/5] Migration output"
|
|
100
134
|
debug:
|
|
101
|
-
msg:
|
|
135
|
+
msg: >-
|
|
136
|
+
{{ (migrate_result.stdout_lines | default([]) | select('match', '^> - ') | list)
|
|
137
|
+
+ (migrate_result.stdout_lines | default([]) | select('search', 'Migrations complete|No migrations') | list)
|
|
138
|
+
| default(['No output'], true) }}
|
|
139
|
+
|
|
140
|
+
# SERVICES BOOTED BEFORE THIS SCHEMA EXISTED. install.yml has to start the containers
|
|
141
|
+
# first — migrations run THROUGH the unoverse container, so there is nothing to run them
|
|
142
|
+
# in until it is up. The cost is that every service that touches the database at boot
|
|
143
|
+
# does so against an empty one: memory logged "permission denied for schema public" and
|
|
144
|
+
# then 'relation "memories" does not exist', and stayed in that state until something
|
|
145
|
+
# restarted it. Restarting here is the missing half of that ordering, and it only ever
|
|
146
|
+
# happens on a first setup.
|
|
147
|
+
- name: "[4b/5] Restart services against the migrated schema"
|
|
148
|
+
command: docker compose restart
|
|
149
|
+
args:
|
|
150
|
+
chdir: /opt/gravity
|
|
151
|
+
when: migrate_result.rc | default(1) == 0
|
|
152
|
+
|
|
153
|
+
- name: "[4b/5] Settle"
|
|
154
|
+
pause:
|
|
155
|
+
seconds: 15
|
|
156
|
+
when: migrate_result.rc | default(1) == 0
|
|
102
157
|
|
|
103
158
|
- name: "[5/5] Verify database connectivity"
|
|
104
159
|
uri:
|
|
@@ -134,36 +134,38 @@
|
|
|
134
134
|
register: api_write_check
|
|
135
135
|
ignore_errors: yes
|
|
136
136
|
|
|
137
|
-
|
|
137
|
+
# AN EMPTY CATALOGUE IS NOT A BROKEN ONE. Nodes arrive as rows — installed from the
|
|
138
|
+
# marketplace or published from Studio — so a universe on its first day has none, and
|
|
139
|
+
# that is the correct state, not a fault. This used to fail outright on zero and advise
|
|
140
|
+
# "run deploy.sh rebuild", a command that does not exist, which turned every first
|
|
141
|
+
# deploy into a red line pointing at nothing.
|
|
142
|
+
#
|
|
143
|
+
# What IS worth failing on is the runtime not answering at all: that means the catalogue
|
|
144
|
+
# could not be read, which is a different fact from it being empty.
|
|
145
|
+
- name: "[10/13] Check the node catalogue is readable"
|
|
138
146
|
shell: |
|
|
139
147
|
cd /opt/gravity
|
|
140
148
|
# The internal :4106 runtime is Docker-network-only (never published), and the
|
|
141
149
|
# public :4105 routes are JWT-gated — so read the catalog from INSIDE the
|
|
142
150
|
# container. node:20-slim has no curl; use node fetch.
|
|
143
151
|
NODE_COUNT=$(docker compose exec -T unoverse node -e \
|
|
144
|
-
"fetch('http://127.0.0.1:4106/nodes').then(r=>r.json()).then(d=>console.log((d.nodes||[]).length)).catch(()=>console.log(
|
|
152
|
+
"fetch('http://127.0.0.1:4106/nodes').then(r=>r.json()).then(d=>console.log((d.nodes||[]).length)).catch(()=>console.log('unreachable'))" 2>/dev/null | tr -d ' \r')
|
|
153
|
+
if [ "$NODE_COUNT" = "unreachable" ]; then
|
|
154
|
+
echo "nodes=unreachable"
|
|
155
|
+
echo "The runtime did not answer on :4106 — the catalogue could not be read"
|
|
156
|
+
exit 1
|
|
157
|
+
fi
|
|
145
158
|
echo "nodes=${NODE_COUNT}"
|
|
146
159
|
if [ "${NODE_COUNT:-0}" -eq 0 ] 2>/dev/null; then
|
|
147
|
-
echo "
|
|
148
|
-
exit 1
|
|
160
|
+
echo "(none installed yet — install from the marketplace or publish from Studio)"
|
|
149
161
|
fi
|
|
150
162
|
register: plugin_check
|
|
151
163
|
ignore_errors: yes
|
|
152
164
|
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
PKG_COUNT=$(docker compose exec -T unoverse ls /app/host_packages 2>/dev/null | wc -l | tr -d ' ')
|
|
158
|
-
echo "packages_mounted=${PKG_COUNT}"
|
|
159
|
-
[ "$PKG_COUNT" -gt 0 ]
|
|
160
|
-
else
|
|
161
|
-
echo "packages_mounted=0"
|
|
162
|
-
echo "WARNING: No packages directory — run deploy.sh or unoverse update"
|
|
163
|
-
exit 1
|
|
164
|
-
fi
|
|
165
|
-
register: packages_check
|
|
166
|
-
ignore_errors: yes
|
|
165
|
+
# NO PACKAGES CHECK. It looked for /app/host_packages, a bind mount that was retired
|
|
166
|
+
# when nodes moved to npm at runtime plus the marketplace. Nothing declares
|
|
167
|
+
# PACKAGES_PATH any more and docker-compose.yml has no such volume, so the check could
|
|
168
|
+
# only ever report 0 and tell the operator to run a command that would not fix it.
|
|
167
169
|
|
|
168
170
|
- name: "[12/14] Check for errors in service logs"
|
|
169
171
|
shell: |
|
|
@@ -245,11 +247,9 @@
|
|
|
245
247
|
── Prompt Blocks (internal :4106) ──
|
|
246
248
|
{{ prompt_blocks_check.stdout_lines[0] | default('(skipped)') }}
|
|
247
249
|
|
|
248
|
-
──
|
|
249
|
-
|
|
250
|
+
── Node catalogue ──
|
|
251
|
+
{{ plugin_check.stdout_lines[0] | default('(skipped)') }}
|
|
250
252
|
{{ plugin_check.stdout_lines[1] | default('') }}
|
|
251
|
-
Packages: {{ packages_check.stdout_lines[0] | default('(skipped)') }}
|
|
252
|
-
{{ packages_check.stdout_lines[1] | default('') }}
|
|
253
253
|
|
|
254
254
|
── Recent Errors in Logs ──
|
|
255
255
|
{{ log_errors.stdout | default('(skipped)') }}
|
|
@@ -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"
|
package/operator/lib/deploy.sh
CHANGED
|
@@ -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