unoverse 0.1.93 → 0.1.95
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.
- package/bin/unoverse.mjs +1 -1
- package/operator/lib/doctor.sh +2 -2
- package/operator/lib/help.sh +5 -3
- package/operator/lib/start.sh +15 -6
- package/operator/operator.sh +0 -2
- package/package.json +1 -1
- package/operator/lib/init.sh +0 -430
package/bin/unoverse.mjs
CHANGED
|
@@ -60,7 +60,7 @@ const UNIVERSE = findUniverse();
|
|
|
60
60
|
const OPERATOR_COMMANDS = new Set([
|
|
61
61
|
"start", "stop", "check", "logs", "deploy", "destroy", "db-allow",
|
|
62
62
|
// kept working, not advertised
|
|
63
|
-
"ground", "dev", "build", "publish",
|
|
63
|
+
"ground", "dev", "build", "publish",
|
|
64
64
|
]);
|
|
65
65
|
|
|
66
66
|
const [, , cmd, ...args] = process.argv;
|
package/operator/lib/doctor.sh
CHANGED
|
@@ -40,7 +40,7 @@ cmd_doctor() {
|
|
|
40
40
|
if [ -f "$ROOT/.env" ]; then
|
|
41
41
|
ok ".env file exists"
|
|
42
42
|
else
|
|
43
|
-
fail ".env file missing. Run ${BOLD}unoverse
|
|
43
|
+
fail ".env file missing. Run ${BOLD}unoverse create${NC} in an empty folder"
|
|
44
44
|
issues=$((issues + 1))
|
|
45
45
|
print_summary $issues
|
|
46
46
|
return
|
|
@@ -82,7 +82,7 @@ cmd_doctor() {
|
|
|
82
82
|
if [ -f "$docker_config" ] && grep -q "$DOCR_REGISTRY" "$docker_config" 2>/dev/null; then
|
|
83
83
|
ok "Logged in to DigitalOcean Container Registry"
|
|
84
84
|
else
|
|
85
|
-
fail "Not logged in to
|
|
85
|
+
fail "Not logged in to the registry. Run ${BOLD}unoverse start${NC}, which logs in from your .env"
|
|
86
86
|
issues=$((issues + 1))
|
|
87
87
|
fi
|
|
88
88
|
|
package/operator/lib/help.sh
CHANGED
|
@@ -22,9 +22,11 @@ cmd_help() {
|
|
|
22
22
|
echo -e " ${GREEN}stop${NC} Stop it"
|
|
23
23
|
echo -e " ${GREEN}check${NC} Is it healthy ${DIM}(services, schema, environment)${NC}"
|
|
24
24
|
echo -e " ${GREEN}logs${NC} What is it doing ${DIM}(unoverse logs <service> for one)${NC}"
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
25
|
+
# THE ARGUMENT IS ON THE LINE, not in a footnote. It read as a special case for people
|
|
26
|
+
# with two clouds; it is simply how the command is shaped. A cloud is optional when only
|
|
27
|
+
# one ground exists and required when two do.
|
|
28
|
+
echo -e " ${GREEN}deploy${NC} Ship it to a server ${DIM}· aws or digitalocean${NC}"
|
|
29
|
+
echo -e " ${GREEN}destroy${NC} Take it down ${DIM}· aws or digitalocean, shows what goes and what stays${NC}"
|
|
28
30
|
echo -e " ${GREEN}db-allow${NC} Let this machine reach the database ${DIM}(run it when you change network)${NC}"
|
|
29
31
|
echo ""
|
|
30
32
|
# Owner-only lane. Printed ONLY when publish.sh is present, so a starter kit never
|
package/operator/lib/start.sh
CHANGED
|
@@ -25,7 +25,7 @@ check_docker_file_sharing() {
|
|
|
25
25
|
echo -e " This will cause services to stay in ${BOLD}Created${NC} state and not start."
|
|
26
26
|
echo ""
|
|
27
27
|
echo -e " ${BOLD}Fix (choose one):${NC}"
|
|
28
|
-
echo -e " 1. Move project to ${BOLD}~/dev/${NC} or ${BOLD}~/projects/${NC} and re-run ${BOLD}unoverse
|
|
28
|
+
echo -e " 1. Move project to ${BOLD}~/dev/${NC} or ${BOLD}~/projects/${NC} and re-run ${BOLD}unoverse start${NC}"
|
|
29
29
|
echo -e " 2. Docker Desktop → Settings → Resources → File Sharing → add ${BOLD}$HOME/$location${NC}"
|
|
30
30
|
echo ""
|
|
31
31
|
return 1
|
|
@@ -79,7 +79,16 @@ cmd_start() {
|
|
|
79
79
|
fi
|
|
80
80
|
|
|
81
81
|
check_docker_file_sharing || exit 1
|
|
82
|
-
|
|
82
|
+
# NO SETUP WIZARD. `unoverse create` writes the .env; there is nothing for start to
|
|
83
|
+
# interview about, and an interactive wizard on the way to "start the platform" was a
|
|
84
|
+
# command inside a command. Say what is missing and name the one that fixes it.
|
|
85
|
+
if [ ! -f "$ROOT/.env" ]; then
|
|
86
|
+
echo ""
|
|
87
|
+
fail "No .env here. This folder is not a universe"
|
|
88
|
+
info "Run ${BOLD}unoverse create${NC} in an empty folder to make one"
|
|
89
|
+
echo ""
|
|
90
|
+
exit 1
|
|
91
|
+
fi
|
|
83
92
|
banner "Starting Unoverse Platform"
|
|
84
93
|
timer_start
|
|
85
94
|
|
|
@@ -126,7 +135,7 @@ cmd_start() {
|
|
|
126
135
|
echo -e " ${DIM}───────────────────────────────${NC}"
|
|
127
136
|
echo ""
|
|
128
137
|
echo -e " ${YELLOW}Common fixes:${NC}"
|
|
129
|
-
echo -e " • Not logged into registry? ${GREEN}
|
|
138
|
+
echo -e " • Not logged into registry? check DOCR_TOKEN in ${GREEN}.env${NC}"
|
|
130
139
|
echo -e " • Missing .env? ${GREEN}cp .env.example .env${NC} and fill in values"
|
|
131
140
|
echo -e " • Images not pulled? ${GREEN}docker compose pull${NC}"
|
|
132
141
|
echo ""
|
|
@@ -167,8 +176,8 @@ cmd_start() {
|
|
|
167
176
|
build_component_nodes
|
|
168
177
|
# Migrations, now that the unoverse service is up (they run from inside its
|
|
169
178
|
# container). This is what makes start the resume point after any interrupted
|
|
170
|
-
# setup: pull, boot, migrate, no third command to know. Subshell
|
|
171
|
-
#
|
|
179
|
+
# setup: pull, boot, migrate, no third command to know. Subshell because cmd_db_setup exits
|
|
180
|
+
# rather than returns on failure.
|
|
172
181
|
if ! (cmd_db_setup) >/dev/null 2>&1; then
|
|
173
182
|
warn "Migrations did not apply. Run ${BOLD}unoverse check${NC} to see why"
|
|
174
183
|
fi
|
|
@@ -180,7 +189,7 @@ cmd_start() {
|
|
|
180
189
|
echo ""
|
|
181
190
|
echo -e " ${YELLOW}No containers were created. Possible causes:${NC}"
|
|
182
191
|
echo -e " • Images not pulled yet? ${GREEN}docker compose pull${NC}"
|
|
183
|
-
echo -e " • Not logged into registry? ${GREEN}
|
|
192
|
+
echo -e " • Not logged into registry? check DOCR_TOKEN in ${GREEN}.env${NC}"
|
|
184
193
|
echo ""
|
|
185
194
|
else
|
|
186
195
|
printf "\r ${YELLOW}!${NC} ${DIM}$running/$total services running${NC}\n"
|
package/operator/operator.sh
CHANGED
|
@@ -33,7 +33,6 @@ fi
|
|
|
33
33
|
# ── Source all modules ──────────────────────────────────────────────
|
|
34
34
|
source "$GRAVITY_LIB/find-root.sh"
|
|
35
35
|
source "$GRAVITY_LIB/common.sh"
|
|
36
|
-
source "$GRAVITY_LIB/init.sh"
|
|
37
36
|
source "$GRAVITY_LIB/start.sh"
|
|
38
37
|
source "$GRAVITY_LIB/stop.sh"
|
|
39
38
|
source "$GRAVITY_LIB/logs.sh"
|
|
@@ -55,7 +54,6 @@ source "$GRAVITY_LIB/ground.sh"
|
|
|
55
54
|
# (The old "studio mode" gate is gone — 2026-07-28. This CLI has ONE job:
|
|
56
55
|
# operate a universe. Studio is a separate app; authoring happens there.)
|
|
57
56
|
case "${1:-}" in
|
|
58
|
-
init) cmd_init ;; # not advertised: create configures, start resumes
|
|
59
57
|
start) shift; cmd_start "$@" ;;
|
|
60
58
|
stop) cmd_stop ;;
|
|
61
59
|
logs) cmd_logs "${2:-}" ;;
|
package/package.json
CHANGED
package/operator/lib/init.sh
DELETED
|
@@ -1,430 +0,0 @@
|
|
|
1
|
-
#!/usr/bin/env bash
|
|
2
|
-
# unoverse init, login, install-to-path, first-run check
|
|
3
|
-
|
|
4
|
-
# install_to_path REMOVED 2026-07-31. The global command is the npm package `unoverse`
|
|
5
|
-
# (`npm i -g unoverse`); symlinking a per-project bash script into /usr/local/bin was the
|
|
6
|
-
# second CLI we set out to delete.
|
|
7
|
-
|
|
8
|
-
# ── The image download, as an experience ─────────────────────────────────────
|
|
9
|
-
#
|
|
10
|
-
# The pull starts the moment the token is accepted, so the minute spent answering
|
|
11
|
-
# questions is also the minute the images arrive. NOTHING writes to the terminal
|
|
12
|
-
# asynchronously — a progress bar racing a readline prompt corrupts what the developer
|
|
13
|
-
# is typing. The background half writes one line of STATE to a temp file; the wizard
|
|
14
|
-
# prints a status line synchronously between question groups, where output composes
|
|
15
|
-
# with input; the end of setup attaches with docker's own bars for whatever remains.
|
|
16
|
-
PULL_STATE=""
|
|
17
|
-
start_background_pull() {
|
|
18
|
-
PULL_STATE=$(mktemp)
|
|
19
|
-
(
|
|
20
|
-
imgs=$(docker compose -f "$ROOT/docker-compose.yml" config --images 2>/dev/null | sort -u)
|
|
21
|
-
total=$(printf '%s\n' "$imgs" | grep -c .)
|
|
22
|
-
n=0
|
|
23
|
-
for img in $imgs; do
|
|
24
|
-
short="${img##*/}"; short="${short%%:*}"
|
|
25
|
-
if docker image inspect "$img" >/dev/null 2>&1; then
|
|
26
|
-
n=$((n+1)); echo "$n $total ready $short" > "$PULL_STATE"; continue
|
|
27
|
-
fi
|
|
28
|
-
echo "$n $total pulling $short" > "$PULL_STATE"
|
|
29
|
-
docker pull "$img" >/dev/null 2>&1
|
|
30
|
-
n=$((n+1)); echo "$n $total pulled $short" > "$PULL_STATE"
|
|
31
|
-
done
|
|
32
|
-
echo "$total $total done -" > "$PULL_STATE"
|
|
33
|
-
) &
|
|
34
|
-
}
|
|
35
|
-
|
|
36
|
-
# One dim line, printed only when the wizard is printing anyway. Never a redraw.
|
|
37
|
-
pull_status_line() {
|
|
38
|
-
[ -n "$PULL_STATE" ] && [ -s "$PULL_STATE" ] || return 0
|
|
39
|
-
local n total verb what
|
|
40
|
-
read -r n total verb what < "$PULL_STATE" 2>/dev/null || return 0
|
|
41
|
-
if [ "$verb" = "done" ]; then
|
|
42
|
-
echo -e " ${GREEN}⬇${NC} ${DIM}platform images: all $total downloaded${NC}"
|
|
43
|
-
elif [ "$verb" = "pulling" ]; then
|
|
44
|
-
echo -e " ${CYAN}⬇${NC} ${DIM}platform images: $n of $total · pulling $what…${NC}"
|
|
45
|
-
else
|
|
46
|
-
echo -e " ${CYAN}⬇${NC} ${DIM}platform images: $n of $total${NC}"
|
|
47
|
-
fi
|
|
48
|
-
echo ""
|
|
49
|
-
}
|
|
50
|
-
|
|
51
|
-
# First-run detection
|
|
52
|
-
check_first_run() {
|
|
53
|
-
if [ ! -f "$ROOT/.env" ]; then
|
|
54
|
-
echo ""
|
|
55
|
-
echo -e " ${YELLOW}${BOLD}Welcome to Unoverse!${NC}"
|
|
56
|
-
echo ""
|
|
57
|
-
echo -e " Looks like this is your first time. Let's get you set up."
|
|
58
|
-
echo -e " You'll need credentials from your Unoverse admin."
|
|
59
|
-
echo ""
|
|
60
|
-
read -r -p " Ready to start setup? [Y/n] " REPLY
|
|
61
|
-
echo ""
|
|
62
|
-
if [[ ! "$REPLY" =~ ^[Nn]$ ]]; then
|
|
63
|
-
cmd_init
|
|
64
|
-
else
|
|
65
|
-
echo ""
|
|
66
|
-
info "Run ${BOLD}unoverse start${NC} when you're ready. It picks setup back up."
|
|
67
|
-
echo ""
|
|
68
|
-
fi
|
|
69
|
-
exit 0
|
|
70
|
-
fi
|
|
71
|
-
}
|
|
72
|
-
|
|
73
|
-
cmd_login() {
|
|
74
|
-
# Check if already logged in
|
|
75
|
-
local docker_config="$HOME/.docker/config.json"
|
|
76
|
-
if [ -f "$docker_config" ]; then
|
|
77
|
-
if grep -q "$DOCR_REGISTRY" "$docker_config" 2>/dev/null; then
|
|
78
|
-
ok "Already logged in to DOCR"
|
|
79
|
-
return
|
|
80
|
-
fi
|
|
81
|
-
fi
|
|
82
|
-
|
|
83
|
-
read -p " DOCR Token: " TOKEN
|
|
84
|
-
if echo "$TOKEN" | docker login "$DOCR_REGISTRY" -u "$TOKEN" --password-stdin &>/dev/null; then
|
|
85
|
-
ok "Logged in to DOCR"
|
|
86
|
-
else
|
|
87
|
-
fail "Login failed"
|
|
88
|
-
exit 1
|
|
89
|
-
fi
|
|
90
|
-
}
|
|
91
|
-
|
|
92
|
-
cmd_init() {
|
|
93
|
-
echo ""
|
|
94
|
-
echo -e " ${BOLD}${CYAN}⬡ Unoverse Setup${NC}"
|
|
95
|
-
echo -e " ${DIM}─────────────────────────────────${NC}"
|
|
96
|
-
echo ""
|
|
97
|
-
|
|
98
|
-
# (The studio/platform mode interview was removed 2026-07-28 — Studio is a
|
|
99
|
-
# separate app, and this CLI only sets up the platform.)
|
|
100
|
-
timer_start
|
|
101
|
-
|
|
102
|
-
# DOCKER IS NEEDED TO START, NOT TO CONFIGURE. This used to exit here, which threw
|
|
103
|
-
# away a scaffold and a validated token because Docker Desktop happened to be closed.
|
|
104
|
-
# Writing .env needs nothing running, so record the state and carry on; the steps that
|
|
105
|
-
# genuinely need a daemon skip themselves, and `start` is where it becomes an error.
|
|
106
|
-
DOCKER_OK=0
|
|
107
|
-
if ! command -v docker &>/dev/null; then
|
|
108
|
-
warn "Docker is not installed. Configuration will finish; install it before ${BOLD}unoverse start${NC}"
|
|
109
|
-
info "Install: https://docs.docker.com/get-docker/"
|
|
110
|
-
elif ! docker info &>/dev/null; then
|
|
111
|
-
warn "Docker is not running. Configuration will finish; start Docker Desktop before ${BOLD}unoverse start${NC}"
|
|
112
|
-
else
|
|
113
|
-
DOCKER_OK=1
|
|
114
|
-
ok "Docker is installed and running"
|
|
115
|
-
fi
|
|
116
|
-
|
|
117
|
-
# Apple Silicon check
|
|
118
|
-
if [ "$(uname -m)" = "arm64" ]; then
|
|
119
|
-
ok "Apple Silicon detected: multi-arch images will run natively"
|
|
120
|
-
echo ""
|
|
121
|
-
fi
|
|
122
|
-
|
|
123
|
-
# RE-RUNNING EDITS. There is no "overwrite? [y/N]" gate any more: every existing value
|
|
124
|
-
# becomes its question's default, so Enter keeps a setting and typing replaces it.
|
|
125
|
-
# Walking through changes only what you change — which makes this the way to change
|
|
126
|
-
# one env var, not a destructive restart.
|
|
127
|
-
_env_cur() { grep "^$1=" "$ROOT/.env" 2>/dev/null | head -1 | cut -d= -f2-; }
|
|
128
|
-
|
|
129
|
-
echo ""
|
|
130
|
-
echo -e " ${BOLD}Configure your environment:${NC}"
|
|
131
|
-
if [ -f "$ROOT/.env" ]; then
|
|
132
|
-
echo -e " ${DIM}(Existing .env found. Enter keeps each current value)${NC}"
|
|
133
|
-
else
|
|
134
|
-
echo -e " ${DIM}(Press Enter to use defaults)${NC}"
|
|
135
|
-
fi
|
|
136
|
-
echo ""
|
|
137
|
-
|
|
138
|
-
# DOCR Token. `unoverse create` has already asked for this and VALIDATED it against
|
|
139
|
-
# the registry, so it hands it over rather than making you type the same credential
|
|
140
|
-
# twice minutes apart. Typed here only when init is run on its own.
|
|
141
|
-
if [ -n "${UNOVERSE_DOCR_TOKEN:-}" ]; then
|
|
142
|
-
DOCR_TOKEN="$UNOVERSE_DOCR_TOKEN"
|
|
143
|
-
DOCR_USER="${UNOVERSE_DOCR_USER:-$UNOVERSE_DOCR_TOKEN}"
|
|
144
|
-
ok "Registry token carried over from create"
|
|
145
|
-
else
|
|
146
|
-
local cur_token
|
|
147
|
-
cur_token=$(_env_cur DOCR_TOKEN)
|
|
148
|
-
DOCR_USER=$(_env_cur DOCR_USER)
|
|
149
|
-
while true; do
|
|
150
|
-
if [ -n "$cur_token" ]; then
|
|
151
|
-
read -p " DOCR Token [keep current]: " DOCR_TOKEN || { fail "no input (end of stream). Run unoverse init interactively"; exit 1; }
|
|
152
|
-
DOCR_TOKEN="${DOCR_TOKEN:-$cur_token}"
|
|
153
|
-
else
|
|
154
|
-
read -p " DOCR Token (from your Unoverse admin): " DOCR_TOKEN || { fail "no input (end of stream). Run unoverse init interactively"; exit 1; }
|
|
155
|
-
fi
|
|
156
|
-
# Accept the credential in any shape, AS A PAIR. The downloaded Docker
|
|
157
|
-
# credentials wrap base64("email:token") — the username is the email, and
|
|
158
|
-
# logging in token-as-username gets "unauthorized" for exactly that shape.
|
|
159
|
-
DOCR_USER=""
|
|
160
|
-
if [[ "$DOCR_TOKEN" != dop_v1_* ]]; then
|
|
161
|
-
local decoded
|
|
162
|
-
decoded=$(printf '%s' "$DOCR_TOKEN" | sed -E 's/.*"auth"[^"]*"([A-Za-z0-9+\/=]+)".*/\1/' | base64 -d 2>/dev/null | tr -d '\0')
|
|
163
|
-
[ -n "$decoded" ] || decoded=$(printf '%s' "$DOCR_TOKEN" | base64 -d 2>/dev/null | tr -d '\0')
|
|
164
|
-
case "$decoded" in
|
|
165
|
-
*:dop_v1_*) DOCR_USER="${decoded%%:*}"; DOCR_TOKEN="dop_v1_${decoded##*dop_v1_}";;
|
|
166
|
-
esac
|
|
167
|
-
fi
|
|
168
|
-
DOCR_USER="${DOCR_USER:-$DOCR_TOKEN}"
|
|
169
|
-
if [[ "$DOCR_TOKEN" == dop_v1_* ]]; then
|
|
170
|
-
break
|
|
171
|
-
fi
|
|
172
|
-
fail "That does not look like a registry credential. Paste it exactly as it was sent"
|
|
173
|
-
done
|
|
174
|
-
fi
|
|
175
|
-
|
|
176
|
-
# THE DOWNLOAD STARTS NOW (see the block at the top of this file).
|
|
177
|
-
if [ "$DOCKER_OK" = "1" ]; then
|
|
178
|
-
local login_err
|
|
179
|
-
if login_err=$(echo "$DOCR_TOKEN" | docker login "$DOCR_REGISTRY" -u "${DOCR_USER:-$DOCR_TOKEN}" --password-stdin 2>&1 >/dev/null); then
|
|
180
|
-
start_background_pull
|
|
181
|
-
echo ""
|
|
182
|
-
echo -e " ${CYAN}⬇${NC} ${DIM}Platform images are downloading in the background while you configure${NC}"
|
|
183
|
-
echo ""
|
|
184
|
-
else
|
|
185
|
-
# The REASON, not just the fact: a swallowed docker error left "login failed"
|
|
186
|
-
# undiagnosable when create had just validated the same credential.
|
|
187
|
-
warn "Registry login failed. Images will not pull until it is fixed"
|
|
188
|
-
echo "$login_err" | grep -vi "warning" | head -3 | sed 's/^/ /'
|
|
189
|
-
fi
|
|
190
|
-
fi
|
|
191
|
-
|
|
192
|
-
# A DEFAULT, because "from your admin" is meaningless when you are the admin. The
|
|
193
|
-
# platform ships no database (docker-compose has no postgres), so this points at one
|
|
194
|
-
# you run. Enter takes the conventional local one.
|
|
195
|
-
DB_DEFAULT=$(_env_cur DATABASE_URL)
|
|
196
|
-
DB_DEFAULT="${DB_DEFAULT:-postgres://postgres:postgres@localhost:5432/unoverse}"
|
|
197
|
-
while true; do
|
|
198
|
-
read -p " DATABASE_URL [${DB_DEFAULT}]: " DATABASE_URL || { fail "no input (end of stream). Run unoverse init interactively"; exit 1; }
|
|
199
|
-
DATABASE_URL="${DATABASE_URL:-$DB_DEFAULT}"
|
|
200
|
-
if [ -n "$DATABASE_URL" ] && [[ "$DATABASE_URL" != *"user:password"* ]]; then
|
|
201
|
-
break
|
|
202
|
-
fi
|
|
203
|
-
fail "DATABASE_URL is required. Get it from your Unoverse admin"
|
|
204
|
-
done
|
|
205
|
-
|
|
206
|
-
# Auto-add SSL params if missing
|
|
207
|
-
if [[ "$DATABASE_URL" != *"sslmode="* ]] && [[ "$DATABASE_URL" != *"ssl="* ]]; then
|
|
208
|
-
local sep="?"
|
|
209
|
-
[[ "$DATABASE_URL" == *"?"* ]] && sep="&"
|
|
210
|
-
if [[ "$DATABASE_URL" == *"localhost"* ]] || \
|
|
211
|
-
[[ "$DATABASE_URL" == *"127.0.0.1"* ]] || \
|
|
212
|
-
[[ "$DATABASE_URL" == *"host.docker.internal"* ]]; then
|
|
213
|
-
DATABASE_URL="${DATABASE_URL}${sep}sslmode=disable"
|
|
214
|
-
ok "Local database detected. Added sslmode=disable"
|
|
215
|
-
else
|
|
216
|
-
DATABASE_URL="${DATABASE_URL}${sep}sslmode=require"
|
|
217
|
-
ok "Managed database detected. Added sslmode=require"
|
|
218
|
-
fi
|
|
219
|
-
fi
|
|
220
|
-
|
|
221
|
-
pull_status_line
|
|
222
|
-
# Redis, current values as defaults
|
|
223
|
-
local rd
|
|
224
|
-
rd=$(_env_cur REDIS_HOST); rd="${rd:-host.docker.internal}"
|
|
225
|
-
read -p " REDIS_HOST [$rd]: " REDIS_HOST
|
|
226
|
-
REDIS_HOST="${REDIS_HOST:-$rd}"
|
|
227
|
-
|
|
228
|
-
rd=$(_env_cur REDIS_PORT); rd="${rd:-6379}"
|
|
229
|
-
read -p " REDIS_PORT [$rd]: " REDIS_PORT
|
|
230
|
-
REDIS_PORT="${REDIS_PORT:-$rd}"
|
|
231
|
-
|
|
232
|
-
rd=$(_env_cur REDIS_PASSWORD)
|
|
233
|
-
if [ -n "$rd" ]; then
|
|
234
|
-
read -p " REDIS_PASSWORD [keep current]: " REDIS_PASSWORD
|
|
235
|
-
REDIS_PASSWORD="${REDIS_PASSWORD:-$rd}"
|
|
236
|
-
else
|
|
237
|
-
read -p " REDIS_PASSWORD (blank for none): " REDIS_PASSWORD
|
|
238
|
-
fi
|
|
239
|
-
|
|
240
|
-
rd=$(_env_cur REDIS_TLS); rd="${rd:-false}"
|
|
241
|
-
read -p " REDIS_TLS [$rd]: " REDIS_TLS
|
|
242
|
-
REDIS_TLS="${REDIS_TLS:-$rd}"
|
|
243
|
-
|
|
244
|
-
# Auth (required — from admin)
|
|
245
|
-
# ASK BEFORE DEMANDING. A developer trying the platform locally has no identity
|
|
246
|
-
# provider yet, and AUTH_ISSUER was required, so init could not be completed at all.
|
|
247
|
-
#
|
|
248
|
-
# This is a LOCAL switch only. INFRASTRUCTURE.md is explicit that auth is always on for
|
|
249
|
-
# a deployed universe ("there is no auth-off deployment"), and the platform enforces it
|
|
250
|
-
# rather than trusting this wizard: authConfig.ts refuses to start with auth off when
|
|
251
|
-
# NODE_ENV=production. So answering "no" here cannot produce an unprotected deployment.
|
|
252
|
-
pull_status_line
|
|
253
|
-
local cur_auth idp_prompt
|
|
254
|
-
cur_auth=$(_env_cur AUTH_ENABLED)
|
|
255
|
-
idp_prompt="[y/N]"
|
|
256
|
-
[ "$cur_auth" = "true" ] && idp_prompt="[Y/n]"
|
|
257
|
-
read -r -p " Do you have an identity provider (Auth0/OIDC) to connect? $idp_prompt " HAS_IDP
|
|
258
|
-
echo ""
|
|
259
|
-
if [ -z "$HAS_IDP" ] && [ "$cur_auth" = "true" ]; then HAS_IDP=y; fi
|
|
260
|
-
|
|
261
|
-
if [[ "$HAS_IDP" =~ ^[Yy]$ ]]; then
|
|
262
|
-
AUTH_ENABLED=true
|
|
263
|
-
local cur_iss cur_cid cur_aud
|
|
264
|
-
cur_iss=$(_env_cur AUTH_ISSUER)
|
|
265
|
-
cur_cid=$(_env_cur AUTH_CLIENT_ID)
|
|
266
|
-
cur_aud=$(_env_cur AUTH_AUDIENCE)
|
|
267
|
-
|
|
268
|
-
while true; do
|
|
269
|
-
if [ -n "$cur_iss" ]; then
|
|
270
|
-
read -p " AUTH_ISSUER [$cur_iss]: " AUTH_ISSUER || { fail "no input (end of stream). Run unoverse init interactively"; exit 1; }
|
|
271
|
-
AUTH_ISSUER="${AUTH_ISSUER:-$cur_iss}"
|
|
272
|
-
else
|
|
273
|
-
read -p " AUTH_ISSUER (e.g. https://your-tenant.auth0.com): " AUTH_ISSUER || { fail "no input (end of stream). Run unoverse init interactively"; exit 1; }
|
|
274
|
-
fi
|
|
275
|
-
if [ -n "$AUTH_ISSUER" ] && [[ "$AUTH_ISSUER" == https://* ]]; then
|
|
276
|
-
break
|
|
277
|
-
fi
|
|
278
|
-
fail "AUTH_ISSUER must be an https:// URL from your identity provider"
|
|
279
|
-
done
|
|
280
|
-
|
|
281
|
-
while true; do
|
|
282
|
-
if [ -n "$cur_cid" ]; then
|
|
283
|
-
read -p " AUTH_CLIENT_ID [$cur_cid]: " AUTH_CLIENT_ID || { fail "no input (end of stream). Run unoverse init interactively"; exit 1; }
|
|
284
|
-
AUTH_CLIENT_ID="${AUTH_CLIENT_ID:-$cur_cid}"
|
|
285
|
-
else
|
|
286
|
-
read -p " AUTH_CLIENT_ID: " AUTH_CLIENT_ID || { fail "no input (end of stream). Run unoverse init interactively"; exit 1; }
|
|
287
|
-
fi
|
|
288
|
-
if [ -n "$AUTH_CLIENT_ID" ] && [[ "$AUTH_CLIENT_ID" != *"your-"* ]]; then
|
|
289
|
-
break
|
|
290
|
-
fi
|
|
291
|
-
fail "AUTH_CLIENT_ID is required"
|
|
292
|
-
done
|
|
293
|
-
|
|
294
|
-
cur_aud="${cur_aud:-gravity-api}"
|
|
295
|
-
read -p " AUTH_AUDIENCE [$cur_aud]: " AUTH_AUDIENCE
|
|
296
|
-
AUTH_AUDIENCE="${AUTH_AUDIENCE:-$cur_aud}"
|
|
297
|
-
else
|
|
298
|
-
AUTH_ENABLED=false
|
|
299
|
-
AUTH_ISSUER=""
|
|
300
|
-
AUTH_CLIENT_ID=""
|
|
301
|
-
AUTH_AUDIENCE="gravity-api"
|
|
302
|
-
warn "Auth is OFF for local development"
|
|
303
|
-
info "Deploying needs it: ${BOLD}unoverse deploy${NC} runs with auth on, and the server refuses to start without it in production"
|
|
304
|
-
fi
|
|
305
|
-
|
|
306
|
-
# NOT ASKED. 4105 is the port docker-compose publishes, so locally this is a fact the
|
|
307
|
-
# CLI already knows, and confirming it is not a question. A DEPLOYED universe gets its
|
|
308
|
-
# own API_URL rendered into .env.production by Terraform, which never comes through here.
|
|
309
|
-
API_URL="http://localhost:4105"
|
|
310
|
-
|
|
311
|
-
# DERIVED FROM THE FOLDER, not hardcoded. Every key the memory server writes is
|
|
312
|
-
# prefixed with this, so two universes sharing one Redis and the same namespace mix
|
|
313
|
-
# their streams and caches. It used to be written as a literal `gravity` for every
|
|
314
|
-
# local universe, while Terraform rendered `universe` for deployed ones: same Redis,
|
|
315
|
-
# same prefix, silently interleaved.
|
|
316
|
-
REDIS_NAMESPACE=$(basename "$ROOT" | tr '[:upper:]' '[:lower:]' | tr -c 'a-z0-9-' '-' | sed 's/-*$//')
|
|
317
|
-
REDIS_NAMESPACE="${REDIS_NAMESPACE:-universe}"
|
|
318
|
-
ok "Redis namespace: ${BOLD}${REDIS_NAMESPACE}${NC}"
|
|
319
|
-
|
|
320
|
-
# OpenAI (for Memory Server)
|
|
321
|
-
pull_status_line
|
|
322
|
-
local cur_oai
|
|
323
|
-
cur_oai=$(_env_cur OPENAI_API_KEY)
|
|
324
|
-
# NOT just the memory server: compose hands this to the unoverse service itself —
|
|
325
|
-
# agents, embeddings and memory all run on it. A universe boots without it, but its AI
|
|
326
|
-
# does not, so skipping gets a warning rather than silence.
|
|
327
|
-
if [ -n "$cur_oai" ]; then
|
|
328
|
-
read -p " OPENAI_API_KEY [keep current]: " OPENAI_API_KEY
|
|
329
|
-
OPENAI_API_KEY="${OPENAI_API_KEY:-$cur_oai}"
|
|
330
|
-
else
|
|
331
|
-
read -p " OPENAI_API_KEY (powers the platform's AI): " OPENAI_API_KEY
|
|
332
|
-
[ -z "$OPENAI_API_KEY" ] && warn "No OpenAI key: the universe starts, but agents, embeddings and memory will not work until one is in .env"
|
|
333
|
-
fi
|
|
334
|
-
|
|
335
|
-
# Node vendor keys, OPTIONAL: only the matching nodes need them, nothing platform-level
|
|
336
|
-
# does. Asked so compose stops warning about them and so they land in .env with names.
|
|
337
|
-
local cur_hb
|
|
338
|
-
cur_hb=$(_env_cur HYPERBROWSER_API_KEY)
|
|
339
|
-
if [ -n "$cur_hb" ]; then
|
|
340
|
-
read -p " HYPERBROWSER_API_KEY [keep current]: " HYPERBROWSER_API_KEY
|
|
341
|
-
HYPERBROWSER_API_KEY="${HYPERBROWSER_API_KEY:-$cur_hb}"
|
|
342
|
-
else
|
|
343
|
-
read -p " HYPERBROWSER_API_KEY (browser nodes, blank to skip): " HYPERBROWSER_API_KEY
|
|
344
|
-
fi
|
|
345
|
-
|
|
346
|
-
|
|
347
|
-
# A silent fact, not a question: the encryption key is GENERATED (a human never types
|
|
348
|
-
# one) and kept verbatim on re-runs — a new key orphans every stored credential.
|
|
349
|
-
CREDENTIAL_ENCRYPTION_KEY=$(_env_cur CREDENTIAL_ENCRYPTION_KEY)
|
|
350
|
-
if [ -z "$CREDENTIAL_ENCRYPTION_KEY" ]; then
|
|
351
|
-
CREDENTIAL_ENCRYPTION_KEY=$(openssl rand -hex 32 2>/dev/null || head -c32 /dev/urandom | xxd -p -c64)
|
|
352
|
-
ok "Credential encryption key generated"
|
|
353
|
-
fi
|
|
354
|
-
# Write .env
|
|
355
|
-
cat > "$ROOT/.env" << ENVEOF
|
|
356
|
-
# Generated by unoverse init
|
|
357
|
-
DOCR_TOKEN=${DOCR_TOKEN}
|
|
358
|
-
DOCR_USER=${DOCR_USER:-${DOCR_TOKEN}}
|
|
359
|
-
DATABASE_URL=${DATABASE_URL}
|
|
360
|
-
REDIS_HOST=${REDIS_HOST}
|
|
361
|
-
REDIS_PORT=${REDIS_PORT}
|
|
362
|
-
REDIS_PASSWORD=${REDIS_PASSWORD}
|
|
363
|
-
REDIS_TLS=${REDIS_TLS}
|
|
364
|
-
REDIS_NAMESPACE=${REDIS_NAMESPACE}
|
|
365
|
-
AUTH_ENABLED=${AUTH_ENABLED}
|
|
366
|
-
AUTH_ISSUER=${AUTH_ISSUER}
|
|
367
|
-
AUTH_CLIENT_ID=${AUTH_CLIENT_ID}
|
|
368
|
-
AUTH_AUDIENCE=${AUTH_AUDIENCE}
|
|
369
|
-
API_URL=${API_URL}
|
|
370
|
-
OPENAI_API_KEY=${OPENAI_API_KEY}
|
|
371
|
-
# Generated at setup: encrypts credentials stored by nodes. Losing it orphans them —
|
|
372
|
-
# back it up with the database. Kept on re-runs.
|
|
373
|
-
CREDENTIAL_ENCRYPTION_KEY=${CREDENTIAL_ENCRYPTION_KEY}
|
|
374
|
-
HYPERBROWSER_API_KEY=${HYPERBROWSER_API_KEY}
|
|
375
|
-
DOMAIN=
|
|
376
|
-
ENVEOF
|
|
377
|
-
|
|
378
|
-
ok ".env created"
|
|
379
|
-
|
|
380
|
-
# Attach to the background pull started when the token was accepted: layers already
|
|
381
|
-
# down show as complete, the rest stream docker's progress bars. Without a daemon the
|
|
382
|
-
# token is in .env, and `unoverse start` pulls on its first run.
|
|
383
|
-
if [ "$DOCKER_OK" = "1" ]; then
|
|
384
|
-
pull_missing_images
|
|
385
|
-
[ -n "$PULL_STATE" ] && rm -f "$PULL_STATE"
|
|
386
|
-
else
|
|
387
|
-
echo ""
|
|
388
|
-
info "Skipped the image download. ${BOLD}unoverse start${NC} does it once Docker is up"
|
|
389
|
-
fi
|
|
390
|
-
|
|
391
|
-
# Install to PATH
|
|
392
|
-
|
|
393
|
-
# Done
|
|
394
|
-
echo ""
|
|
395
|
-
echo -e " ${GREEN}${BOLD}━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━${NC}"
|
|
396
|
-
echo -e " ${GREEN}${BOLD} ✓ Setup Complete!${NC} ${DIM}($(timer_elapsed))${NC}"
|
|
397
|
-
echo -e " ${GREEN}${BOLD}━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━${NC}"
|
|
398
|
-
# MIGRATIONS RUN HERE, so `db-setup` is not a command anyone has to know about. Deploy
|
|
399
|
-
# already ran them on the server (playbooks/db-setup.yml); this is the local half.
|
|
400
|
-
# A database that is not reachable yet is not a failed setup — .env is written and
|
|
401
|
-
# correct, so say so and move on rather than unwinding everything.
|
|
402
|
-
echo ""
|
|
403
|
-
# SUBSHELL, deliberately: cmd_db_setup exits on "services not up yet", and an exit in a
|
|
404
|
-
# sourced function would take all of init with it — setup then reported failure for a
|
|
405
|
-
# situation that just means "migrations run on start". Which they now do (start.sh).
|
|
406
|
-
if grep -q '^DATABASE_URL=' "$ROOT/.env" 2>/dev/null; then
|
|
407
|
-
# Quietly: db-setup narrates its own advice ("start services first, then re-run"),
|
|
408
|
-
# which contradicts and duplicates the one line that is true here.
|
|
409
|
-
if (cmd_db_setup) >/dev/null 2>&1; then
|
|
410
|
-
ok "Database schema is up to date"
|
|
411
|
-
else
|
|
412
|
-
info "Migrations wait for the platform: ${BOLD}unoverse start${NC} applies them once services are up"
|
|
413
|
-
fi
|
|
414
|
-
fi
|
|
415
|
-
|
|
416
|
-
echo ""
|
|
417
|
-
echo -e " ${BOLD}Next steps:${NC}"
|
|
418
|
-
echo ""
|
|
419
|
-
if [ "$DOCKER_OK" = "1" ]; then
|
|
420
|
-
echo -e " ${GREEN}unoverse start${NC} Start the platform"
|
|
421
|
-
else
|
|
422
|
-
echo -e " ${DIM}1.${NC} Start Docker Desktop"
|
|
423
|
-
echo -e " ${DIM}2.${NC} ${GREEN}unoverse start${NC}"
|
|
424
|
-
fi
|
|
425
|
-
echo -e " ${GREEN}unoverse where${NC} Links to your Canvas and API"
|
|
426
|
-
echo ""
|
|
427
|
-
info "Run ${BOLD}unoverse check${NC} anytime to see if it is healthy"
|
|
428
|
-
info "Change a setting any time: edit ${BOLD}.env${NC} directly, or re-run ${BOLD}unoverse create${NC} (Enter keeps each current value)"
|
|
429
|
-
echo ""
|
|
430
|
-
}
|