unoverse 0.1.69 → 0.1.71

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.
@@ -11,17 +11,129 @@
11
11
  # Nothing is softened. Destroys are shouted, the full technical plan is one keystroke
12
12
  # away, and the SAVED plan is what runs, so what was approved is what happens. Identical
13
13
  # for every cloud: the summary reads the plan JSON, which is provider-agnostic.
14
+ # Terraform is one static binary: install it from the official release rather than
15
+ # sending anyone to brew, whose compile chain can demand Xcode Command Line Tools
16
+ # updates for a tool that needs no compiling.
17
+ _ensure_terraform() {
18
+ command -v terraform >/dev/null 2>&1 && return 0
19
+ local REPLY tf_arch tf_os tf_v="1.9.8" tf_dir
20
+ read -r -p " Terraform is needed. Install it now (official binary, ~30 MB)? [Y/n] " REPLY
21
+ [[ "$REPLY" =~ ^[Nn]$ ]] && { fail "terraform is needed to continue"; return 1; }
22
+ tf_arch=$(uname -m)
23
+ case "$tf_arch" in arm64|aarch64) tf_arch=arm64 ;; *) tf_arch=amd64 ;; esac
24
+ [ "$(uname -s)" = "Darwin" ] && tf_os=darwin || tf_os=linux
25
+ tf_dir=$(mktemp -d)
26
+ curl -fsSL -o "$tf_dir/tf.zip" "https://releases.hashicorp.com/terraform/${tf_v}/terraform_${tf_v}_${tf_os}_${tf_arch}.zip" \
27
+ && unzip -o -q "$tf_dir/tf.zip" -d "$tf_dir" || { fail "download failed"; rm -rf "$tf_dir"; return 1; }
28
+ if [ -w /usr/local/bin ]; then mv "$tf_dir/terraform" /usr/local/bin/terraform
29
+ else sudo mv "$tf_dir/terraform" /usr/local/bin/terraform || { fail "could not install to /usr/local/bin"; rm -rf "$tf_dir"; return 1; }
30
+ fi
31
+ rm -rf "$tf_dir"
32
+ ok "terraform $(terraform version | head -1 | awk '{print $2}') installed"
33
+ }
34
+
35
+
36
+ # Everything that must be TRUE before a plan is worth looking at: every value filled,
37
+ # and the database decision made. It lives in a function because deploy has one flow now
38
+ # and this step belongs in it — it used to sit inside the "no .env.production yet" branch,
39
+ # so an already-initialised ground skipped straight to planning and the Postgres question
40
+ # was never asked. Two paths, and the configuration existed in only one of them.
41
+ _ensure_ground_config() {
42
+ local cloud="$1"
43
+ local tfv="$ROOT/infra/$cloud/terraform.tfvars"
44
+ _tf_put() { # key value — safe replacement via node (values may hold sed specials)
45
+ node -e 'const fs=require("fs");const[f,k,v]=process.argv.slice(1);let s=fs.readFileSync(f,"utf8");s=s.replace(new RegExp("^"+k+"(\\s*)=\\s*\"FILL_ME\"","m"),k+"$1= "+JSON.stringify(v));fs.writeFileSync(f,s)' "$tfv" "$1" "$2"
46
+ }
47
+ _tf_fill() { # key envname prompt
48
+ local key="$1" envname="$2" prompt="$3" val
49
+ grep -q "^${key}[[:space:]]*=[[:space:]]*\"FILL_ME\"" "$tfv" 2>/dev/null || return 0
50
+ val=$(grep "^${envname}=" "$ROOT/.env" 2>/dev/null | head -1 | cut -d= -f2-)
51
+ if [ -n "$val" ]; then
52
+ _tf_put "$key" "$val"
53
+ ok "$key ${DIM}from your .env${NC}"
54
+ else
55
+ read -r -p " $prompt: " val
56
+ [ -n "$val" ] && _tf_put "$key" "$val"
57
+ fi
58
+ }
59
+ echo ""
60
+ # POSTGRES: ASK, DO NOT ASSUME. The ground discovers an existing cluster and writes
61
+ # it as a COMMENTED line, so the default silently provisions a SECOND database next
62
+ # to one the account already pays for. A choice with a monthly bill attached is a
63
+ # question, not a line to notice in a file.
64
+ if [ "$cloud" = "digitalocean" ] \
65
+ && grep -q '^#[[:space:]]*existing_pg_cluster_name' "$tfv" 2>/dev/null \
66
+ && ! grep -q '^existing_pg_cluster_name' "$tfv" 2>/dev/null \
67
+ && ! grep -q '^byo_postgres_url' "$tfv" 2>/dev/null; then
68
+ local found
69
+ found=$(grep '^#[[:space:]]*existing_pg_cluster_name' "$tfv" | sed -E 's/.*"([^"]+)".*/\1/')
70
+ if [ -n "$found" ]; then
71
+ echo ""
72
+ echo -e " ${CYAN}${BOLD}You already have a database.${NC} ${DIM}$found${NC}"
73
+ echo ""
74
+ pick_option "Use it" "Create a new one ~\$15/month"
75
+ if [ "$PICKED" = "0" ]; then
76
+ node -e 'const fs=require("fs");const[f,c]=process.argv.slice(1);let s=fs.readFileSync(f,"utf8");s=s.replace(/^#\s*(existing_pg_cluster_name\s*=.*)$/m,"$1");fs.writeFileSync(f,s)' "$tfv" "$found"
77
+ ok "Reusing $found"
78
+ else
79
+ ok "A new database will be created"
80
+ fi
81
+ fi
82
+ fi
83
+
84
+ _tf_fill docr_token DOCR_TOKEN "Registry access token (from your Unoverse admin)"
85
+ _tf_fill openai_api_key OPENAI_API_KEY "OPENAI_API_KEY (powers the platform's AI)"
86
+ if grep -q '^auth_issuer[[:space:]]*=[[:space:]]*"FILL_ME"' "$tfv" 2>/dev/null; then
87
+ local env_auth
88
+ env_auth=$(grep "^AUTH_ISSUER=" "$ROOT/.env" 2>/dev/null | cut -d= -f2-)
89
+ if [ -z "$env_auth" ]; then
90
+ echo ""
91
+ info "A deployed universe requires a login. Local auth-off does not deploy"
92
+ info "${DIM}(Auth0 or any OIDC provider; the issuer looks like https://your-tenant.auth0.com)${NC}"
93
+ fi
94
+ fi
95
+ _tf_fill auth_issuer AUTH_ISSUER "AUTH_ISSUER"
96
+ _tf_fill auth_client_id AUTH_CLIENT_ID "AUTH_CLIENT_ID"
97
+
98
+ # VALUES only: the file's own header comment says the word FILL_ME, and matching
99
+ # it declared a complete file blank.
100
+ if grep -qE '=[[:space:]]*"FILL_ME"' "$tfv" 2>/dev/null; then
101
+ echo ""
102
+ warn "Some values are still blank in ${BOLD}infra/$cloud/terraform.tfvars${NC} — fill them, then:"
103
+ info " cd infra/$cloud && terraform init && terraform apply"
104
+ info " cd ../.. && unoverse deploy"
105
+ echo ""
106
+ exit 1
107
+ fi
108
+ ok "terraform.tfvars is complete"
109
+
110
+ # SIZE IS NOT A ONE-WAY DOOR. small is the POC box, and the plan below is about to
111
+ # quote its monthly cost — the moment someone wonders whether they are committing to
112
+ # it. One line, here, rather than a discovery later.
113
+ local cur_size
114
+ cur_size=$(grep -E '^size[[:space:]]*=' "$tfv" 2>/dev/null | sed -E 's/.*"([^"]+)".*/\1/')
115
+ [ -n "$cur_size" ] && info "Size: ${BOLD}${cur_size}${NC} ${DIM}(small · medium · large — change it in terraform.tfvars and deploy again whenever you outgrow it)${NC}"
116
+
117
+ # STRAIGHT INTO TERRAFORM. Its plan plus its own typed "yes" IS the decision gate
118
+ # for billable infrastructure; a [Y/n] in front of it was asking permission to ask
119
+ # permission. Answering "no" is terraform's "no".
120
+ echo ""
121
+
122
+ }
123
+
14
124
  _ground_apply() {
15
125
  local cloud="$1" tmp planfile rc
16
126
  # SEPARATE STATEMENT. On macOS bash 3.2 a single `local a="$1" b="...$a"` expands $a
17
127
  # EMPTY, so dir became "$ROOT/infra/" and terraform answered "No configuration files".
128
+
129
+
18
130
  # Same trap that once made the release publish the repo root.
19
131
  local dir="$ROOT/infra/$cloud"
20
132
  tmp=$(mktemp -d); planfile="$tmp/plan"
21
133
 
22
134
  terraform -chdir="$dir" init -input=false >/dev/null 2>&1 || { fail "terraform init failed"; rm -rf "$tmp"; return 1; }
23
135
 
24
- info "Working out what needs to change..."
136
+ info "Working out what needs to change ${DIM}($cloud)${NC}..."
25
137
  terraform -chdir="$dir" plan -input=false -detailed-exitcode -out="$planfile" >"$tmp/plan.log" 2>&1
26
138
  rc=$?
27
139
  case "$rc" in
@@ -34,8 +146,11 @@ _ground_apply() {
34
146
  terraform -chdir="$dir" show "$planfile"
35
147
  fi
36
148
 
149
+ # `read -p` does NOT interpret escapes, so ${DIM} printed as literal \033[2m. Colour
150
+ # the hint with echo -e first, then take a plain prompt.
37
151
  local REPLY
38
- read -r -p " Go ahead? [y/N] ${DIM}(d for the full technical plan)${NC} " REPLY
152
+ echo -e " ${DIM}(d shows the full technical plan)${NC}"
153
+ read -r -p " Go ahead? [y/N] " REPLY
39
154
  if [[ "$REPLY" =~ ^[Dd]$ ]]; then
40
155
  terraform -chdir="$dir" show "$planfile"
41
156
  read -r -p " Go ahead? [y/N] " REPLY
@@ -70,178 +185,54 @@ cmd_deploy() {
70
185
  done
71
186
  fi
72
187
 
73
- # INFRA CHANGES SHIP TOO. If the ground has pending changes (edited tfvars, a new
74
- # resource in main.tf from an update), deploy applies them first through
75
- # terraform's own plan-and-yes gate then re-renders the env, since outputs may
76
- # have changed. Without this, deploy only ever shipped images once a server
77
- # existed, and infrastructure edits silently rotted.
78
- local g_applied
79
- if command -v terraform >/dev/null 2>&1; then
80
- local g rc
81
- for g in digitalocean aws; do
82
- [ -d "$ROOT/infra/$g/.terraform" ] || continue
83
- _ground_apply "$g"; rc=$?
84
- [ "$rc" = "1" ] && exit 1
85
- if [ "$rc" = "0" ]; then
86
- terraform -chdir="$ROOT/infra/$g" output -raw env_production > "$env_prod" 2>/dev/null \
87
- && ok "Refreshed .env.production from the ground"
88
- g_applied=1
89
- fi
90
- break
91
- done
92
- fi
93
-
94
- # Missing .env.production is not an error if a ground has been applied — the
95
- # file is just a Terraform output, so render it ourselves. Reading state is
96
- # not wrapping terraform: apply stays the developer's explicit act.
97
- if [ ! -f "$env_prod" ] && command -v terraform >/dev/null 2>&1; then
98
- local g
99
- for g in digitalocean aws; do
100
- [ -d "$ROOT/infra/$g" ] || continue
101
- if terraform -chdir="$ROOT/infra/$g" output -raw env_production > "$env_prod.tmp" 2>/dev/null \
102
- && grep -q '^DEPLOY_HOST=' "$env_prod.tmp"; then
103
- mv "$env_prod.tmp" "$env_prod"
104
- ok "Rendered .env.production from your $g ground"
105
- info "It holds CREDENTIAL_ENCRYPTION_KEY — back it up with the DB, never commit it."
106
- break
107
- fi
108
- rm -f "$env_prod.tmp"
109
- done
110
- fi
188
+ # ── ONE FLOW ────────────────────────────────────────────────────────────────
189
+ # Which ground, is it configured, plan it, apply it, ship it. In that order, every
190
+ # time, whether this is the first deploy or the fiftieth.
191
+ local cloud="" g rc
192
+ for g in digitalocean aws; do
193
+ [ -f "$ROOT/infra/$g/terraform.tfvars" ] && { cloud="$g"; break; }
194
+ done
111
195
 
112
- # NO SERVER YET is not an error, it is the first question. deploy owns the whole
113
- # journey: pick the cloud right here (ground is no longer a command to know about),
114
- # prefill its Terraform, and name the two commands left. `terraform apply` stays the
115
- # developer's own explicit act — it creates billable infrastructure.
116
- if [ ! -f "$env_prod" ]; then
196
+ if [ -z "$cloud" ]; then
117
197
  echo ""
118
198
  echo -e " ${CYAN}${BOLD}Where should this universe live?${NC}"
119
199
  echo ""
120
- local cloud
121
200
  pick_option "DigitalOcean" "AWS"
122
201
  [ "$PICKED" = "1" ] && cloud=aws || cloud=digitalocean
123
202
  echo ""
124
- # An existing tfvars is PROGRESS, not a conflict: the previous run wrote it. Only
125
- # a missing file needs the ground; either way the flow continues below.
126
- if [ -f "$ROOT/infra/$cloud/terraform.tfvars" ]; then
127
- ok "Using your existing infra/$cloud/terraform.tfvars"
128
- else
129
- cmd_ground "$([ "$cloud" = "aws" ] && echo aws || echo do)" || {
130
- echo ""
131
- info "Once that is sorted, run ${BOLD}unoverse deploy${NC} again. It picks up right here."
132
- echo ""
133
- exit 1
134
- }
135
- fi
136
- echo ""
137
- ok "Your $cloud ground is prefilled ${DIM}(infra/$cloud/terraform.tfvars)${NC}"
138
-
139
- # FILL THE BLANKS OURSELVES. Every FILL_ME the universe already knows (.env from
140
- # setup) is copied in silently; only genuine unknowns are asked. Hand-copying
141
- # values between our own files is not a step a developer should ever see.
142
- local tfv="$ROOT/infra/$cloud/terraform.tfvars"
143
- _tf_put() { # key value — safe replacement via node (values may hold sed specials)
144
- node -e 'const fs=require("fs");const[f,k,v]=process.argv.slice(1);let s=fs.readFileSync(f,"utf8");s=s.replace(new RegExp("^"+k+"(\\s*)=\\s*\"FILL_ME\"","m"),k+"$1= "+JSON.stringify(v));fs.writeFileSync(f,s)' "$tfv" "$1" "$2"
145
- }
146
- _tf_fill() { # key envname prompt
147
- local key="$1" envname="$2" prompt="$3" val
148
- grep -q "^${key}[[:space:]]*=[[:space:]]*\"FILL_ME\"" "$tfv" 2>/dev/null || return 0
149
- val=$(grep "^${envname}=" "$ROOT/.env" 2>/dev/null | head -1 | cut -d= -f2-)
150
- if [ -n "$val" ]; then
151
- _tf_put "$key" "$val"
152
- ok "$key ${DIM}from your .env${NC}"
153
- else
154
- read -r -p " $prompt: " val
155
- [ -n "$val" ] && _tf_put "$key" "$val"
156
- fi
157
- }
158
- echo ""
159
- # POSTGRES: ASK, DO NOT ASSUME. The ground discovers an existing cluster and writes
160
- # it as a COMMENTED line, so the default silently provisions a SECOND database next
161
- # to one the account already pays for. A choice with a monthly bill attached is a
162
- # question, not a line to notice in a file.
163
- if [ "$cloud" = "digitalocean" ] \
164
- && grep -q '^#[[:space:]]*existing_pg_cluster_name' "$tfv" 2>/dev/null \
165
- && ! grep -q '^existing_pg_cluster_name' "$tfv" 2>/dev/null \
166
- && ! grep -q '^byo_postgres_url' "$tfv" 2>/dev/null; then
167
- local found
168
- found=$(grep '^#[[:space:]]*existing_pg_cluster_name' "$tfv" | sed -E 's/.*"([^"]+)".*/\1/')
169
- if [ -n "$found" ]; then
170
- echo ""
171
- echo -e " ${CYAN}${BOLD}You already have a database.${NC} ${DIM}$found${NC}"
172
- echo ""
173
- pick_option "Use it" "Create a new one ~\$15/month"
174
- if [ "$PICKED" = "0" ]; then
175
- node -e 'const fs=require("fs");const[f,c]=process.argv.slice(1);let s=fs.readFileSync(f,"utf8");s=s.replace(/^#\s*(existing_pg_cluster_name\s*=.*)$/m,"$1");fs.writeFileSync(f,s)' "$tfv" "$found"
176
- ok "Reusing $found"
177
- else
178
- ok "A new database will be created"
179
- fi
180
- fi
181
- fi
182
-
183
- _tf_fill docr_token DOCR_TOKEN "Registry access token (from your Unoverse admin)"
184
- _tf_fill openai_api_key OPENAI_API_KEY "OPENAI_API_KEY (powers the platform's AI)"
185
- if grep -q '^auth_issuer[[:space:]]*=[[:space:]]*"FILL_ME"' "$tfv" 2>/dev/null; then
186
- local env_auth
187
- env_auth=$(grep "^AUTH_ISSUER=" "$ROOT/.env" 2>/dev/null | cut -d= -f2-)
188
- if [ -z "$env_auth" ]; then
189
- echo ""
190
- info "A deployed universe requires a login. Local auth-off does not deploy"
191
- info "${DIM}(Auth0 or any OIDC provider; the issuer looks like https://your-tenant.auth0.com)${NC}"
192
- fi
193
- fi
194
- _tf_fill auth_issuer AUTH_ISSUER "AUTH_ISSUER"
195
- _tf_fill auth_client_id AUTH_CLIENT_ID "AUTH_CLIENT_ID"
196
-
197
- # VALUES only: the file's own header comment says the word FILL_ME, and matching
198
- # it declared a complete file blank.
199
- if grep -qE '=[[:space:]]*"FILL_ME"' "$tfv" 2>/dev/null; then
203
+ cmd_ground "$([ "$cloud" = "aws" ] && echo aws || echo do)" || {
200
204
  echo ""
201
- warn "Some values are still blank in ${BOLD}infra/$cloud/terraform.tfvars${NC} fill them, then:"
202
- info " cd infra/$cloud && terraform init && terraform apply"
203
- info " cd ../.. && unoverse deploy"
205
+ info "Once that is sorted, run ${BOLD}unoverse deploy${NC} again. It picks up right here."
204
206
  echo ""
205
207
  exit 1
206
- fi
207
- ok "terraform.tfvars is complete"
208
-
209
- # SIZE IS NOT A ONE-WAY DOOR. small is the POC box, and the plan below is about to
210
- # quote its monthly cost the moment someone wonders whether they are committing to
211
- # it. One line, here, rather than a discovery later.
212
- local cur_size
213
- cur_size=$(grep -E '^size[[:space:]]*=' "$tfv" 2>/dev/null | sed -E 's/.*"([^"]+)".*/\1/')
214
- [ -n "$cur_size" ] && info "Size: ${BOLD}${cur_size}${NC} ${DIM}(small · medium · large — change it in terraform.tfvars and deploy again whenever you outgrow it)${NC}"
215
-
216
- # STRAIGHT INTO TERRAFORM. Its plan plus its own typed "yes" IS the decision gate
217
- # for billable infrastructure; a [Y/n] in front of it was asking permission to ask
218
- # permission. Answering "no" is terraform's "no".
219
- echo ""
220
- # Terraform is one static binary: install it directly from the official release
221
- # rather than sending anyone to brew (whose compile chain can demand Xcode Command
222
- # Line Tools updates for a tool that needs no compiling).
223
- if ! command -v terraform >/dev/null 2>&1; then
224
- local REPLY2 tf_arch tf_os tf_v="1.9.8" tf_dir
225
- read -r -p " Terraform is needed. Install it now (official binary, ~30 MB)? [Y/n] " REPLY2
226
- [[ "$REPLY2" =~ ^[Nn]$ ]] && { fail "terraform is needed to continue"; exit 1; }
227
- tf_arch=$(uname -m); [ "$tf_arch" = "arm64" ] || [ "$tf_arch" = "aarch64" ] && tf_arch=arm64 || tf_arch=amd64
228
- [ "$(uname -s)" = "Darwin" ] && tf_os=darwin || tf_os=linux
229
- tf_dir=$(mktemp -d)
230
- curl -fsSL -o "$tf_dir/tf.zip" "https://releases.hashicorp.com/terraform/${tf_v}/terraform_${tf_v}_${tf_os}_${tf_arch}.zip" && unzip -o -q "$tf_dir/tf.zip" -d "$tf_dir" || { fail "download failed"; exit 1; }
231
- if [ -w /usr/local/bin ]; then mv "$tf_dir/terraform" /usr/local/bin/terraform
232
- else sudo mv "$tf_dir/terraform" /usr/local/bin/terraform || { fail "could not install to /usr/local/bin"; exit 1; }
233
- fi
234
- rm -rf "$tf_dir"
235
- ok "terraform $(terraform version | head -1 | awk '{print $2}') installed"
236
- fi
237
- _ground_apply "$cloud" || exit 1
208
+ }
209
+ else
210
+ # SAY WHERE THIS IS GOING, first and unmissably. An existing ground already names
211
+ # the cloud, so re-asking every deploy would be noise but a one-line mention
212
+ # buried above other output reads as never having been asked at all.
213
+ local pretty
214
+ [ "$cloud" = "aws" ] && pretty="AWS" || pretty="DigitalOcean"
238
215
  echo ""
239
- ok "Infrastructure is up. Shipping the platform onto it..."
216
+ echo -e " ${CYAN}${BOLD}⬡ Deploying to $pretty${NC} ${DIM}(infra/$cloud delete that folder's terraform.tfvars to choose again)${NC}"
240
217
  echo ""
241
- cmd_deploy init
242
- return $?
243
218
  fi
244
219
 
220
+ _ensure_ground_config "$cloud" || exit 1
221
+ _ensure_terraform || exit 1
222
+
223
+ _ground_apply "$cloud"; rc=$?
224
+ [ "$rc" = "1" ] && exit 1
225
+ if [ "$rc" = "0" ] || [ ! -f "$env_prod" ]; then
226
+ terraform -chdir="$ROOT/infra/$cloud" output -raw env_production > "$env_prod" 2>/dev/null \
227
+ && ok "Rendered .env.production from your $cloud ground"
228
+ fi
229
+
230
+ if [ ! -f "$env_prod" ]; then
231
+ fail ".env.production could not be rendered — has the ground been applied?"
232
+ exit 1
233
+ fi
234
+
235
+
245
236
  # Read deploy target from .env.production
246
237
  local deploy_host deploy_user
247
238
  deploy_host=$(grep '^DEPLOY_HOST=' "$env_prod" | cut -d= -f2- | tr -d '\r\n' | xargs)
@@ -334,7 +334,7 @@ cmd_init() {
334
334
 
335
335
  # Node vendor keys, OPTIONAL: only the matching nodes need them, nothing platform-level
336
336
  # does. Asked so compose stops warning about them and so they land in .env with names.
337
- local cur_hb cur_sa
337
+ local cur_hb
338
338
  cur_hb=$(_env_cur HYPERBROWSER_API_KEY)
339
339
  if [ -n "$cur_hb" ]; then
340
340
  read -p " HYPERBROWSER_API_KEY [keep current]: " HYPERBROWSER_API_KEY
@@ -342,13 +342,7 @@ cmd_init() {
342
342
  else
343
343
  read -p " HYPERBROWSER_API_KEY (browser nodes, blank to skip): " HYPERBROWSER_API_KEY
344
344
  fi
345
- cur_sa=$(_env_cur SEARCHAPI_KEY)
346
- if [ -n "$cur_sa" ]; then
347
- read -p " SEARCHAPI_KEY [keep current]: " SEARCHAPI_KEY
348
- SEARCHAPI_KEY="${SEARCHAPI_KEY:-$cur_sa}"
349
- else
350
- read -p " SEARCHAPI_KEY (web-search nodes, blank to skip): " SEARCHAPI_KEY
351
- fi
345
+
352
346
 
353
347
  # A silent fact, not a question: the encryption key is GENERATED (a human never types
354
348
  # one) and kept verbatim on re-runs — a new key orphans every stored credential.
@@ -378,7 +372,6 @@ OPENAI_API_KEY=${OPENAI_API_KEY}
378
372
  # back it up with the database. Kept on re-runs.
379
373
  CREDENTIAL_ENCRYPTION_KEY=${CREDENTIAL_ENCRYPTION_KEY}
380
374
  HYPERBROWSER_API_KEY=${HYPERBROWSER_API_KEY}
381
- SEARCHAPI_KEY=${SEARCHAPI_KEY}
382
375
  DOMAIN=
383
376
  ENVEOF
384
377
 
@@ -73,6 +73,23 @@ case "${1:-}" in
73
73
  elif ! docker info >/dev/null 2>&1; then
74
74
  info "Docker is not running. Images refresh on the next unoverse start"
75
75
  else
76
+ # The ground definitions first: they are code, and they update like code.
77
+ # NO `local` HERE: a case branch in the dispatch is not a function body, and bash
78
+ # errors "local: can only be used in a function" straight to the developer's screen.
79
+ _vinfra="$GRAVITY_LIB/../infra"
80
+ if [ -d "$_vinfra" ]; then
81
+ for _g in digitalocean aws; do
82
+ [ -d "$ROOT/infra/$_g" ] && [ -d "$_vinfra/$_g" ] || continue
83
+ _changed=0
84
+ for _f in "$_vinfra/$_g"/*.tf "$_vinfra/$_g"/*.example; do
85
+ [ -f "$_f" ] || continue
86
+ _base=$(basename "$_f")
87
+ cmp -s "$_f" "$ROOT/infra/$_g/$_base" || { cp "$_f" "$ROOT/infra/$_g/$_base"; _changed=1; }
88
+ done
89
+ [ "$_changed" = "1" ] && ok "Refreshed the $_g ground ${DIM}(your terraform.tfvars and state untouched)${NC}"
90
+ done
91
+ fi
92
+
76
93
  info "Checking for newer platform images..."
77
94
  if ! docker compose -f "$ROOT/docker-compose.yml" --env-file "$ROOT/.env" pull; then
78
95
  # A failed pull must not be followed by "everything is up to date". Timeouts
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "unoverse",
3
- "version": "0.1.69",
3
+ "version": "0.1.71",
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",