unoverse 0.1.91 → 0.1.93

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.
@@ -126,6 +126,9 @@ output "env_production" {
126
126
  OPENAI_API_KEY=${var.openai_api_key}
127
127
  HYPERBROWSER_API_KEY=${var.hyperbrowser_api_key}
128
128
 
129
+ # Marketplace catalogue (MARKETPLACE.md §5). Empty = local items only.
130
+ UNOVERSE_MARKETPLACE_URL=${var.marketplace_url}
131
+
129
132
  # Ingress. With a domain: DOMAIN drives every URL (compose derives
130
133
  # https://api.<domain>). Without one (POC): DOMAIN stays empty and the
131
134
  # explicit URLs below point at the ALB's DNS name over plain HTTP.
@@ -53,3 +53,7 @@ roles = [
53
53
  # that locks every credential users save in the platform. If it's lost, no backup
54
54
  # can bring them back. Never commit it; keep a safe copy with your database backups.
55
55
  # To change any value: edit this file and re-render. Never hand-edit the output.
56
+
57
+ # Marketplace catalogue to install from (docs/architecture/MARKETPLACE.md).
58
+ # Leave unset for local items only.
59
+ # marketplace_url = "https://marketplace.example.com"
@@ -91,3 +91,14 @@ variable "hyperbrowser_api_key" {
91
91
  default = ""
92
92
  sensitive = true
93
93
  }
94
+
95
+ # The catalogue this universe installs from (MARKETPLACE.md §5). NO DEFAULT, deliberately:
96
+ # a URL is never hardcoded and never a fallback, and absent config means no remote
97
+ # catalogue — the universe serves what it has on disk and in its rows, which is the correct
98
+ # state for development and for air-gapped estates. It reaches the marketplace at install
99
+ # time and at no other time.
100
+ variable "marketplace_url" {
101
+ description = "Base URL of the marketplace catalogue. Empty = local items only."
102
+ type = string
103
+ default = ""
104
+ }
@@ -118,6 +118,9 @@ output "env_production" {
118
118
  OPENAI_API_KEY=${var.openai_api_key}
119
119
  HYPERBROWSER_API_KEY=${var.hyperbrowser_api_key}
120
120
 
121
+ # Marketplace catalogue (MARKETPLACE.md §5). Empty = local items only.
122
+ UNOVERSE_MARKETPLACE_URL=${var.marketplace_url}
123
+
121
124
  # Ingress. With a domain: DOMAIN drives every URL (compose derives
122
125
  # https://api.<domain>). Without one (POC): DOMAIN stays empty and the
123
126
  # explicit URLs below point at the load balancer's IP over plain HTTP.
@@ -50,3 +50,7 @@ openai_api_key = "sk-..." # memory server + OpenAI nodes
50
50
  # that locks every credential users save in the platform. If it's lost, no backup
51
51
  # can bring them back. Never commit it; keep a safe copy with your database backups.
52
52
  # To change any value: edit this file and re-render. Never hand-edit the output.
53
+
54
+ # Marketplace catalogue to install from (docs/architecture/MARKETPLACE.md).
55
+ # Leave unset for local items only.
56
+ # marketplace_url = "https://marketplace.example.com"
@@ -125,3 +125,14 @@ variable "hyperbrowser_api_key" {
125
125
  default = ""
126
126
  sensitive = true
127
127
  }
128
+
129
+ # The catalogue this universe installs from (MARKETPLACE.md §5). NO DEFAULT, deliberately:
130
+ # a URL is never hardcoded and never a fallback, and absent config means no remote
131
+ # catalogue — the universe serves what it has on disk and in its rows, which is the correct
132
+ # state for development and for air-gapped estates. It reaches the marketplace at install
133
+ # time and at no other time.
134
+ variable "marketplace_url" {
135
+ description = "Base URL of the marketplace catalogue. Empty = local items only."
136
+ type = string
137
+ default = ""
138
+ }
@@ -102,14 +102,11 @@ NODE
102
102
  # password afterwards. This opens a door in the network layer; it does not open the
103
103
  # database.
104
104
  cmd_db_allow() {
105
- local cloud="" g
106
- for g in digitalocean aws; do
107
- [ -f "$ROOT/infra/$g/terraform.tfvars" ] && { cloud="$g"; break; }
108
- done
109
- if [ -z "$cloud" ]; then
110
- fail "No ground here. There is no database to reach"
105
+ local cloud
106
+ SELF_CMD="unoverse db-allow" cloud=$(_pick_ground "${1:-}") || {
107
+ [ -z "${1:-}" ] && [ ! -d "$ROOT/infra" ] && fail "No ground here. There is no database to reach"
111
108
  return 1
112
- fi
109
+ }
113
110
 
114
111
  local cluster
115
112
  cluster=$(grep -E '^existing_pg_cluster_name[[:space:]]*=' "$ROOT/infra/$cloud/terraform.tfvars" 2>/dev/null | sed -E 's/.*"([^"]+)".*/\1/')
@@ -188,6 +185,51 @@ NODE
188
185
  # `unoverse destroy` ran with no credential at all and every API call came back 401 —
189
186
  # the same class of bug as the Postgres question living in one branch of two. Anything
190
187
  # that talks to a ground calls this first.
188
+ # WHICH GROUND THIS COMMAND MEANS.
189
+ # _pick_ground [name] → echoes the ground, or fails with a reason
190
+ #
191
+ # Every command used `for g in digitalocean aws; do ... break; done`: first match wins, and
192
+ # digitalocean is first in the list. With both grounds configured there was no way to reach
193
+ # the AWS one — and `unoverse destroy` would have torn down DigitalOcean while the operator
194
+ # meant AWS, silently, because the word "aws" had nowhere to go.
195
+ #
196
+ # So a ground can be named. With one configured, naming it is optional and the answer is
197
+ # obvious. With two, the name is REQUIRED rather than guessed: a wrong guess here destroys
198
+ # the wrong cloud.
199
+ _pick_ground() {
200
+ local want="${1:-}" g found=""
201
+ case "$want" in
202
+ do|digitalocean) want="digitalocean" ;;
203
+ aws|amazon) want="aws" ;;
204
+ "") want="" ;;
205
+ *) fail "Unknown ground '$want'. Use ${BOLD}digitalocean${NC} or ${BOLD}aws${NC}" >&2; return 1 ;;
206
+ esac
207
+
208
+ local configured=()
209
+ for g in digitalocean aws; do
210
+ [ -f "$ROOT/infra/$g/terraform.tfvars" ] && configured+=("$g")
211
+ done
212
+
213
+ if [ -n "$want" ]; then
214
+ for g in "${configured[@]}"; do
215
+ [ "$g" = "$want" ] && { printf '%s' "$want"; return 0; }
216
+ done
217
+ fail "No $want ground here. Run ${BOLD}unoverse deploy $want${NC} to create one" >&2
218
+ return 1
219
+ fi
220
+
221
+ case "${#configured[@]}" in
222
+ 0) return 1 ;; # caller offers to create one
223
+ 1) printf '%s' "${configured[0]}"; return 0 ;;
224
+ *)
225
+ # Two grounds and no name. Refuse rather than pick: this decides which cloud a
226
+ # destroy lands on.
227
+ fail "Two grounds are configured. Say which: ${BOLD}${SELF_CMD:-unoverse deploy} digitalocean${NC} or ${BOLD}${SELF_CMD:-unoverse deploy} aws${NC}" >&2
228
+ return 1
229
+ ;;
230
+ esac
231
+ }
232
+
191
233
  _ground_credentials() {
192
234
  [ -n "${DIGITALOCEAN_TOKEN:-}" ] && return 0
193
235
  local _docfg
@@ -472,6 +514,13 @@ _ground_apply() {
472
514
  }
473
515
 
474
516
  cmd_deploy() {
517
+ # A LEADING GROUND NAME IS NOT A SUBCOMMAND. `unoverse deploy aws` names the cloud;
518
+ # `unoverse deploy init` names the step; `unoverse deploy aws init` does both. Take the
519
+ # ground off the front when it is there, and leave everything else exactly as it was.
520
+ local GROUND_ARG=""
521
+ case "${1:-}" in
522
+ do|digitalocean|aws|amazon) GROUND_ARG="$1"; shift ;;
523
+ esac
475
524
 
476
525
  # .env.production IS NOT A FILE IN THE UNIVERSE. It is `terraform output -raw
477
526
  # env_production`: derived, complete, and regenerated in full on every deploy. Writing it
@@ -492,10 +541,14 @@ cmd_deploy() {
492
541
  # ── ONE FLOW ────────────────────────────────────────────────────────────────
493
542
  # Which ground, is it configured, plan it, apply it, ship it. In that order, every
494
543
  # time, whether this is the first deploy or the fiftieth.
495
- local cloud="" g rc
496
- for g in digitalocean aws; do
497
- [ -f "$ROOT/infra/$g/terraform.tfvars" ] && { cloud="$g"; break; }
498
- done
544
+ # A named ground wins; one configured ground is implied; two without a name is refused
545
+ # (see _pick_ground). Nothing configured at all falls through to creating one below,
546
+ # which is the first-run path and the only case where an empty answer is not a mistake.
547
+ local cloud="" rc have=0
548
+ [ -f "$ROOT/infra/digitalocean/terraform.tfvars" ] || [ -f "$ROOT/infra/aws/terraform.tfvars" ] && have=1
549
+ if [ "$have" = "1" ]; then
550
+ cloud=$(SELF_CMD="unoverse deploy" _pick_ground "${GROUND_ARG:-}") || exit 1
551
+ fi
499
552
 
500
553
  if [ -z "$cloud" ]; then
501
554
  echo ""
@@ -19,14 +19,17 @@
19
19
  # cloud resources this ground owns.
20
20
 
21
21
  cmd_destroy() {
22
- local cloud="" g
23
- for g in digitalocean aws; do
24
- [ -f "$ROOT/infra/$g/terraform.tfvars" ] && { cloud="$g"; break; }
25
- done
26
- if [ -z "$cloud" ]; then
27
- fail "No ground here. There is nothing deployed to take down"
22
+ # NAME THE GROUND. This took the first configured one, and digitalocean is first in the
23
+ # list so an operator with both grounds who typed `unoverse destroy` meaning AWS would
24
+ # have torn down DigitalOcean instead, with the confirmation prompt happily naming the
25
+ # universe they thought they were destroying. _pick_ground refuses to guess when there is
26
+ # more than one.
27
+ local cloud
28
+ cloud=$(SELF_CMD="unoverse destroy" _pick_ground "${1:-}") || {
29
+ [ -f "$ROOT/infra/digitalocean/terraform.tfvars" ] || [ -f "$ROOT/infra/aws/terraform.tfvars" ] \
30
+ || fail "No ground here. There is nothing deployed to take down"
28
31
  return 1
29
- fi
32
+ }
30
33
 
31
34
  local dir="$ROOT/infra/$cloud"
32
35
  if [ ! -d "$dir/.terraform" ]; then
@@ -24,6 +24,7 @@ cmd_help() {
24
24
  echo -e " ${GREEN}logs${NC} What is it doing ${DIM}(unoverse logs <service> for one)${NC}"
25
25
  echo -e " ${GREEN}deploy${NC} Ship it to a server ${DIM}(first run asks which cloud)${NC}"
26
26
  echo -e " ${GREEN}destroy${NC} Take the deployment down ${DIM}(shows what goes, and what stays)${NC}"
27
+ echo -e " ${DIM}With two grounds, name one: deploy aws · destroy digitalocean${NC}"
27
28
  echo -e " ${GREEN}db-allow${NC} Let this machine reach the database ${DIM}(run it when you change network)${NC}"
28
29
  echo ""
29
30
  # Owner-only lane. Printed ONLY when publish.sh is present, so a starter kit never
@@ -65,8 +65,8 @@ case "${1:-}" in
65
65
  check) cmd_check && cmd_db_verify && cmd_doctor ;;
66
66
  dev) cmd_dev ;;
67
67
  ground) shift; cmd_ground "$@" ;;
68
- destroy) cmd_destroy ;;
69
- db-allow) cmd_db_allow ;;
68
+ destroy) cmd_destroy "${2:-}" ;;
69
+ db-allow) cmd_db_allow "${2:-}" ;;
70
70
  refresh-images)
71
71
  # internal: `unoverse update` runs this after updating the CLI. Pull newer images
72
72
  # if the registry has them, and recreate only what is already running — an update
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "unoverse",
3
- "version": "0.1.91",
3
+ "version": "0.1.93",
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",