unoverse 0.1.36 → 0.1.38
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/lib/create.mjs +38 -21
- package/operator/lib/db-verify.sh +1 -1
- package/operator/lib/init.sh +18 -2
- package/operator/lib/start.sh +3 -1
- package/package.json +1 -1
package/lib/create.mjs
CHANGED
|
@@ -33,27 +33,44 @@ async function ask(rl, q) {
|
|
|
33
33
|
* credential gets refused, so detect it and pass it through untouched. */
|
|
34
34
|
/** The raw dop_v1 token, whichever shape was pasted. docker login needs the raw form,
|
|
35
35
|
* so the base64 user:token blob is decoded before anything downstream sees it. */
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
36
|
+
/** The credential AS A PAIR. The downloaded Docker credentials file carries
|
|
37
|
+
* base64("email:token") — the username is the ACCOUNT EMAIL, and logging in with the
|
|
38
|
+
* token as username (right for API tokens) gets "unauthorized" for it. So nothing here
|
|
39
|
+
* reduces to a bare token any more: whatever shape is pasted, the exact user:password
|
|
40
|
+
* pair comes out and is used verbatim.
|
|
41
|
+
* raw dop_v1 → token:token
|
|
42
|
+
* base64("u:p") → u:p (email:token, or :token → token:token)
|
|
43
|
+
* the whole JSON file → its auth value, decoded the same way
|
|
44
|
+
*/
|
|
45
|
+
function parseCredential(input) {
|
|
46
|
+
const t = input.trim().replace(/^["']|["']$/g, "");
|
|
47
|
+
const pair = (d) => {
|
|
48
|
+
const m = /^([\x20-\x7e]*):([\x20-\x7e]+)$/.exec(d);
|
|
49
|
+
return m ? { user: m[1] || m[2], pass: m[2] } : null;
|
|
50
|
+
};
|
|
51
|
+
const auth = /"auth"\s*:\s*"([A-Za-z0-9+/=]+)"/.exec(t);
|
|
52
|
+
if (auth) {
|
|
53
|
+
const p = pair(Buffer.from(auth[1], "base64").toString("utf8"));
|
|
54
|
+
if (p) return p;
|
|
55
|
+
}
|
|
56
|
+
if (/^[A-Za-z0-9+/]+=*$/.test(t)) {
|
|
57
|
+
const p = pair(Buffer.from(t, "base64").toString("utf8"));
|
|
58
|
+
if (p) return p;
|
|
41
59
|
}
|
|
42
|
-
|
|
60
|
+
const inline = /dop_v1_[0-9a-f]{64}/.exec(t);
|
|
61
|
+
if (inline) return { user: inline[0], pass: inline[0] };
|
|
62
|
+
return { user: t, pass: t };
|
|
43
63
|
}
|
|
44
64
|
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
if (/^[\x20-\x7e]+:[\x20-\x7e]+$/.test(decoded)) return token;
|
|
49
|
-
}
|
|
50
|
-
return Buffer.from(`${token}:${token}`).toString("base64");
|
|
65
|
+
|
|
66
|
+
function registryAuth(cred) {
|
|
67
|
+
return Buffer.from(`${cred.user}:${cred.pass}`).toString("base64");
|
|
51
68
|
}
|
|
52
69
|
|
|
53
70
|
/** The gate: prove the token opens the registry before handing over the kit.
|
|
54
71
|
* Docker registries use the bearer-realm flow: /v2/ answers 401 and names the
|
|
55
72
|
* auth realm; presenting the token there yields 200 (valid) or 401 (refused). */
|
|
56
|
-
async function validateRegistryToken(
|
|
73
|
+
async function validateRegistryToken(cred) {
|
|
57
74
|
try {
|
|
58
75
|
const probe = await fetch(`https://${REGISTRY_HOST}/v2/`);
|
|
59
76
|
const wa = probe.headers.get("www-authenticate") ?? "";
|
|
@@ -62,7 +79,7 @@ async function validateRegistryToken(token) {
|
|
|
62
79
|
if (!realm) return probe.status === 200;
|
|
63
80
|
const res = await fetch(
|
|
64
81
|
`${realm}?service=${encodeURIComponent(service)}&scope=registry:catalog:*`,
|
|
65
|
-
{ headers: { Authorization: `Basic ${registryAuth(
|
|
82
|
+
{ headers: { Authorization: `Basic ${registryAuth(cred)}` } },
|
|
66
83
|
);
|
|
67
84
|
return res.ok;
|
|
68
85
|
} catch {
|
|
@@ -113,9 +130,9 @@ function dockerUp() {
|
|
|
113
130
|
* the same credential two minutes later — the tool approving a token with a weaker
|
|
114
131
|
* check than the one that counts. When Docker is up, validation IS docker login (and
|
|
115
132
|
* the credential lands in the keychain, so nothing downstream logs in again). */
|
|
116
|
-
function dockerLogin(
|
|
117
|
-
const r = spawnSync("docker", ["login", REGISTRY_HOST, "-u",
|
|
118
|
-
input:
|
|
133
|
+
function dockerLogin(cred) {
|
|
134
|
+
const r = spawnSync("docker", ["login", REGISTRY_HOST, "-u", cred.user, "--password-stdin"], {
|
|
135
|
+
input: cred.pass,
|
|
119
136
|
encoding: "utf8",
|
|
120
137
|
});
|
|
121
138
|
const err = (r.stderr || "").split("\n").filter((l) => l && !/^WARNING/i.test(l)).pop() || "";
|
|
@@ -271,8 +288,8 @@ export async function create(nameArg) {
|
|
|
271
288
|
// A refused token re-asks RIGHT HERE. Being thrown out of the wizard to run
|
|
272
289
|
// create again is its own bug.
|
|
273
290
|
for (;;) {
|
|
274
|
-
token =
|
|
275
|
-
if (!token) {
|
|
291
|
+
token = parseCredential(await ask(rl, "Registry access token: "));
|
|
292
|
+
if (!token.pass) {
|
|
276
293
|
fail("no token. The universe kit is available to licensed operators");
|
|
277
294
|
process.exit(1);
|
|
278
295
|
}
|
|
@@ -283,7 +300,7 @@ export async function create(nameArg) {
|
|
|
283
300
|
if (res.ok) { ok("token accepted"); break; }
|
|
284
301
|
fail("the registry refused it:");
|
|
285
302
|
console.log(` ${dim(res.err)}`);
|
|
286
|
-
console.log(` ${dim("Paste the
|
|
303
|
+
console.log(` ${dim("Paste the credential exactly as your admin sent it (any shape works), or Ctrl-C to stop.")}\n`);
|
|
287
304
|
} else {
|
|
288
305
|
const valid = await validateRegistryToken(token);
|
|
289
306
|
console.log("");
|
|
@@ -321,7 +338,7 @@ export async function create(nameArg) {
|
|
|
321
338
|
const r = spawnSync("bash", [operatorScript, "init"], {
|
|
322
339
|
stdio: "inherit",
|
|
323
340
|
cwd: name,
|
|
324
|
-
env: { ...process.env, UNOVERSE_DOCR_TOKEN:
|
|
341
|
+
env: { ...process.env, UNOVERSE_DOCR_TOKEN: token.pass, UNOVERSE_DOCR_USER: token.user },
|
|
325
342
|
});
|
|
326
343
|
if (r.status !== 0) {
|
|
327
344
|
fail(`setup did not finish. Run 'unoverse start'${name === "." ? "" : ` in ./${name}`} to pick it back up`);
|
|
@@ -35,7 +35,7 @@ cmd_db_verify() {
|
|
|
35
35
|
workflows: [
|
|
36
36
|
"id", "name", "description", "nodes", "edges", "active",
|
|
37
37
|
"execution_mode", "test_inputs", "umap_settings", "viewport",
|
|
38
|
-
"mcp_schema", "memory_config", "created_at", "updated_at"
|
|
38
|
+
"mcp_schema", "memory_config", "content_taxonomy", "created_at", "updated_at"
|
|
39
39
|
],
|
|
40
40
|
workflow_executions: [
|
|
41
41
|
"execution_id", "workflow_id", "status", "start_time", "end_time",
|
package/operator/lib/init.sh
CHANGED
|
@@ -140,10 +140,12 @@ cmd_init() {
|
|
|
140
140
|
# twice minutes apart. Typed here only when init is run on its own.
|
|
141
141
|
if [ -n "${UNOVERSE_DOCR_TOKEN:-}" ]; then
|
|
142
142
|
DOCR_TOKEN="$UNOVERSE_DOCR_TOKEN"
|
|
143
|
+
DOCR_USER="${UNOVERSE_DOCR_USER:-$UNOVERSE_DOCR_TOKEN}"
|
|
143
144
|
ok "Registry token carried over from create"
|
|
144
145
|
else
|
|
145
146
|
local cur_token
|
|
146
147
|
cur_token=$(_env_cur DOCR_TOKEN)
|
|
148
|
+
DOCR_USER=$(_env_cur DOCR_USER)
|
|
147
149
|
while true; do
|
|
148
150
|
if [ -n "$cur_token" ]; then
|
|
149
151
|
read -p " DOCR Token [keep current]: " DOCR_TOKEN || { fail "no input (end of stream). Run unoverse init interactively"; exit 1; }
|
|
@@ -151,17 +153,30 @@ cmd_init() {
|
|
|
151
153
|
else
|
|
152
154
|
read -p " DOCR Token (from your Unoverse admin): " DOCR_TOKEN || { fail "no input (end of stream). Run unoverse init interactively"; exit 1; }
|
|
153
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}"
|
|
154
169
|
if [[ "$DOCR_TOKEN" == dop_v1_* ]]; then
|
|
155
170
|
break
|
|
156
171
|
fi
|
|
157
|
-
fail "
|
|
172
|
+
fail "That does not look like a registry credential. Paste it exactly as it was sent"
|
|
158
173
|
done
|
|
159
174
|
fi
|
|
160
175
|
|
|
161
176
|
# THE DOWNLOAD STARTS NOW (see the block at the top of this file).
|
|
162
177
|
if [ "$DOCKER_OK" = "1" ]; then
|
|
163
178
|
local login_err
|
|
164
|
-
if login_err=$(echo "$DOCR_TOKEN" | docker login "$DOCR_REGISTRY" -u "$DOCR_TOKEN" --password-stdin 2>&1 >/dev/null); then
|
|
179
|
+
if login_err=$(echo "$DOCR_TOKEN" | docker login "$DOCR_REGISTRY" -u "${DOCR_USER:-$DOCR_TOKEN}" --password-stdin 2>&1 >/dev/null); then
|
|
165
180
|
start_background_pull
|
|
166
181
|
echo ""
|
|
167
182
|
echo -e " ${CYAN}⬇${NC} ${DIM}Platform images are downloading in the background while you configure${NC}"
|
|
@@ -339,6 +354,7 @@ cmd_init() {
|
|
|
339
354
|
cat > "$ROOT/.env" << ENVEOF
|
|
340
355
|
# Generated by unoverse init
|
|
341
356
|
DOCR_TOKEN=${DOCR_TOKEN}
|
|
357
|
+
DOCR_USER=${DOCR_USER:-${DOCR_TOKEN}}
|
|
342
358
|
DATABASE_URL=${DATABASE_URL}
|
|
343
359
|
REDIS_HOST=${REDIS_HOST}
|
|
344
360
|
REDIS_PORT=${REDIS_PORT}
|
package/operator/lib/start.sh
CHANGED
|
@@ -55,8 +55,10 @@ cmd_start() {
|
|
|
55
55
|
# Login to registry if DOCR_TOKEN is set
|
|
56
56
|
local docr_token
|
|
57
57
|
docr_token=$(grep "^DOCR_TOKEN=" "$ROOT/.env" 2>/dev/null | cut -d'=' -f2-)
|
|
58
|
+
local docr_user
|
|
59
|
+
docr_user=$(grep "^DOCR_USER=" "$ROOT/.env" 2>/dev/null | cut -d'=' -f2-)
|
|
58
60
|
if [ -n "$docr_token" ]; then
|
|
59
|
-
echo "$docr_token" | docker login registry.digitalocean.com -u "$docr_token" --password-stdin >/dev/null 2>&1 || true
|
|
61
|
+
echo "$docr_token" | docker login registry.digitalocean.com -u "${docr_user:-$docr_token}" --password-stdin >/dev/null 2>&1 || true
|
|
60
62
|
fi
|
|
61
63
|
|
|
62
64
|
# `start --pull` refreshes the images first. This is where refreshing a LOCAL universe
|
package/package.json
CHANGED