Compare commits
7 Commits
fix/gatewa
...
chore/bump
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
2357602f50 | ||
| 1230f6b984 | |||
| 14b775f1b9 | |||
|
|
c7691d9807 | ||
| 9a53d55678 | |||
|
|
31008ef7ff | ||
| 621ab260c0 |
@@ -35,13 +35,42 @@ steps:
|
|||||||
- |
|
- |
|
||||||
echo "//git.mosaicstack.dev/api/packages/mosaicstack/npm/:_authToken=$NPM_TOKEN" > ~/.npmrc
|
echo "//git.mosaicstack.dev/api/packages/mosaicstack/npm/:_authToken=$NPM_TOKEN" > ~/.npmrc
|
||||||
echo "@mosaicstack:registry=https://git.mosaicstack.dev/api/packages/mosaicstack/npm/" >> ~/.npmrc
|
echo "@mosaicstack:registry=https://git.mosaicstack.dev/api/packages/mosaicstack/npm/" >> ~/.npmrc
|
||||||
# Publish non-private packages to Gitea (--no-git-checks skips dirty/branch checks in CI)
|
# Publish non-private packages to Gitea.
|
||||||
# --filter excludes web (private)
|
#
|
||||||
- >
|
# The only publish failure we tolerate is "version already exists" —
|
||||||
pnpm --filter "@mosaicstack/*"
|
# that legitimately happens when only some packages were bumped in
|
||||||
--filter "!@mosaicstack/web"
|
# the merge. Any other failure (registry 404, auth error, network
|
||||||
publish --no-git-checks --access public
|
# error) MUST fail the pipeline loudly: the previous
|
||||||
|| echo "[publish] Some packages may already exist at this version — continuing"
|
# `|| echo "... continuing"` fallback silently hid a 404 from the
|
||||||
|
# Gitea org rename and caused every @mosaicstack/* publish to fall
|
||||||
|
# on the floor while CI still reported green.
|
||||||
|
- |
|
||||||
|
# Portable sh (Alpine ash) — avoid bashisms like PIPESTATUS.
|
||||||
|
set +e
|
||||||
|
pnpm --filter "@mosaicstack/*" --filter "!@mosaicstack/web" publish --no-git-checks --access public >/tmp/publish.log 2>&1
|
||||||
|
EXIT=$?
|
||||||
|
set -e
|
||||||
|
cat /tmp/publish.log
|
||||||
|
if [ "$EXIT" -eq 0 ]; then
|
||||||
|
echo "[publish] all packages published successfully"
|
||||||
|
exit 0
|
||||||
|
fi
|
||||||
|
# Hard registry / auth / network errors → fatal. Match npm's own
|
||||||
|
# error lines specifically to avoid false positives on arbitrary
|
||||||
|
# log text that happens to contain "E404" etc.
|
||||||
|
if grep -qE "npm (error|ERR!) code (E404|E401|ENEEDAUTH|ECONNREFUSED|ETIMEDOUT|ENOTFOUND)" /tmp/publish.log; then
|
||||||
|
echo "[publish] FATAL: registry/auth/network error detected — failing pipeline" >&2
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
# Only tolerate the explicit "version already published" case.
|
||||||
|
# npm returns this as E403 with body "You cannot publish over..."
|
||||||
|
# or EPUBLISHCONFLICT depending on version.
|
||||||
|
if grep -qE "EPUBLISHCONFLICT|You cannot publish over|previously published" /tmp/publish.log; then
|
||||||
|
echo "[publish] some packages already at this version — continuing (non-fatal)"
|
||||||
|
exit 0
|
||||||
|
fi
|
||||||
|
echo "[publish] FATAL: publish failed with unrecognized error — failing pipeline" >&2
|
||||||
|
exit 1
|
||||||
depends_on:
|
depends_on:
|
||||||
- build
|
- build
|
||||||
|
|
||||||
@@ -74,12 +103,12 @@ steps:
|
|||||||
- mkdir -p /kaniko/.docker
|
- mkdir -p /kaniko/.docker
|
||||||
- echo "{\"auths\":{\"git.mosaicstack.dev\":{\"username\":\"$REGISTRY_USER\",\"password\":\"$REGISTRY_PASS\"}}}" > /kaniko/.docker/config.json
|
- echo "{\"auths\":{\"git.mosaicstack.dev\":{\"username\":\"$REGISTRY_USER\",\"password\":\"$REGISTRY_PASS\"}}}" > /kaniko/.docker/config.json
|
||||||
- |
|
- |
|
||||||
DESTINATIONS="--destination git.mosaicstack.dev/mosaic/mosaic-stack/gateway:sha-${CI_COMMIT_SHA:0:7}"
|
DESTINATIONS="--destination git.mosaicstack.dev/mosaicstack/mosaic-stack/gateway:sha-${CI_COMMIT_SHA:0:7}"
|
||||||
if [ "$CI_COMMIT_BRANCH" = "main" ]; then
|
if [ "$CI_COMMIT_BRANCH" = "main" ]; then
|
||||||
DESTINATIONS="$DESTINATIONS --destination git.mosaicstack.dev/mosaic/mosaic-stack/gateway:latest"
|
DESTINATIONS="$DESTINATIONS --destination git.mosaicstack.dev/mosaicstack/mosaic-stack/gateway:latest"
|
||||||
fi
|
fi
|
||||||
if [ -n "$CI_COMMIT_TAG" ]; then
|
if [ -n "$CI_COMMIT_TAG" ]; then
|
||||||
DESTINATIONS="$DESTINATIONS --destination git.mosaicstack.dev/mosaic/mosaic-stack/gateway:$CI_COMMIT_TAG"
|
DESTINATIONS="$DESTINATIONS --destination git.mosaicstack.dev/mosaicstack/mosaic-stack/gateway:$CI_COMMIT_TAG"
|
||||||
fi
|
fi
|
||||||
/kaniko/executor --context . --dockerfile docker/gateway.Dockerfile $DESTINATIONS
|
/kaniko/executor --context . --dockerfile docker/gateway.Dockerfile $DESTINATIONS
|
||||||
depends_on:
|
depends_on:
|
||||||
@@ -99,12 +128,12 @@ steps:
|
|||||||
- mkdir -p /kaniko/.docker
|
- mkdir -p /kaniko/.docker
|
||||||
- echo "{\"auths\":{\"git.mosaicstack.dev\":{\"username\":\"$REGISTRY_USER\",\"password\":\"$REGISTRY_PASS\"}}}" > /kaniko/.docker/config.json
|
- echo "{\"auths\":{\"git.mosaicstack.dev\":{\"username\":\"$REGISTRY_USER\",\"password\":\"$REGISTRY_PASS\"}}}" > /kaniko/.docker/config.json
|
||||||
- |
|
- |
|
||||||
DESTINATIONS="--destination git.mosaicstack.dev/mosaic/mosaic-stack/web:sha-${CI_COMMIT_SHA:0:7}"
|
DESTINATIONS="--destination git.mosaicstack.dev/mosaicstack/mosaic-stack/web:sha-${CI_COMMIT_SHA:0:7}"
|
||||||
if [ "$CI_COMMIT_BRANCH" = "main" ]; then
|
if [ "$CI_COMMIT_BRANCH" = "main" ]; then
|
||||||
DESTINATIONS="$DESTINATIONS --destination git.mosaicstack.dev/mosaic/mosaic-stack/web:latest"
|
DESTINATIONS="$DESTINATIONS --destination git.mosaicstack.dev/mosaicstack/mosaic-stack/web:latest"
|
||||||
fi
|
fi
|
||||||
if [ -n "$CI_COMMIT_TAG" ]; then
|
if [ -n "$CI_COMMIT_TAG" ]; then
|
||||||
DESTINATIONS="$DESTINATIONS --destination git.mosaicstack.dev/mosaic/mosaic-stack/web:$CI_COMMIT_TAG"
|
DESTINATIONS="$DESTINATIONS --destination git.mosaicstack.dev/mosaicstack/mosaic-stack/web:$CI_COMMIT_TAG"
|
||||||
fi
|
fi
|
||||||
/kaniko/executor --context . --dockerfile docker/web.Dockerfile $DESTINATIONS
|
/kaniko/executor --context . --dockerfile docker/web.Dockerfile $DESTINATIONS
|
||||||
depends_on:
|
depends_on:
|
||||||
|
|||||||
@@ -3,7 +3,7 @@
|
|||||||
"version": "0.0.6",
|
"version": "0.0.6",
|
||||||
"repository": {
|
"repository": {
|
||||||
"type": "git",
|
"type": "git",
|
||||||
"url": "https://git.mosaicstack.dev/mosaic/mosaic-stack.git",
|
"url": "https://git.mosaicstack.dev/mosaicstack/mosaic-stack.git",
|
||||||
"directory": "apps/gateway"
|
"directory": "apps/gateway"
|
||||||
},
|
},
|
||||||
"type": "module",
|
"type": "module",
|
||||||
@@ -15,7 +15,7 @@
|
|||||||
"dist"
|
"dist"
|
||||||
],
|
],
|
||||||
"publishConfig": {
|
"publishConfig": {
|
||||||
"registry": "https://git.mosaicstack.dev/api/packages/mosaic/npm/",
|
"registry": "https://git.mosaicstack.dev/api/packages/mosaicstack/npm/",
|
||||||
"access": "public"
|
"access": "public"
|
||||||
},
|
},
|
||||||
"scripts": {
|
"scripts": {
|
||||||
|
|||||||
47
docs/scratchpads/gateway-install-ux-20260404.md
Normal file
47
docs/scratchpads/gateway-install-ux-20260404.md
Normal file
@@ -0,0 +1,47 @@
|
|||||||
|
# Gateway Install UX Fixes — 2026-04-04
|
||||||
|
|
||||||
|
## Context
|
||||||
|
|
||||||
|
User hit two publish-drift bugs on `mosaic gateway install` (fixed in #386 and
|
||||||
|
#389). On top of those, the install UX itself has three concrete problems:
|
||||||
|
|
||||||
|
1. Admin API token is generated by the bootstrap step and saved to
|
||||||
|
`~/.config/mosaic/gateway/meta.json`, but never shown to the user. There is
|
||||||
|
no way to retrieve it without reading the file or running `mosaic gateway
|
||||||
|
config` afterward.
|
||||||
|
2. When install crashes mid-way (e.g. daemon fails to become healthy), the
|
||||||
|
next run of `mosaic gateway install` prompts "Reinstall? [y/N]". Saying N
|
||||||
|
aborts, leaving the user with a half-configured install and no clear path
|
||||||
|
forward. There is no resume path.
|
||||||
|
3. Health-check failure only prints "Check logs: mosaic gateway logs" — forcing
|
||||||
|
the user to run another command to see the actual error.
|
||||||
|
|
||||||
|
## Goal
|
||||||
|
|
||||||
|
Make `mosaic gateway install` a single end-to-end, resumable command that shows
|
||||||
|
the admin token on creation and surfaces errors inline.
|
||||||
|
|
||||||
|
## Plan
|
||||||
|
|
||||||
|
- `install.ts`: detect partial state (meta exists, no admin token, daemon not
|
||||||
|
running) and skip already-completed phases instead of aborting.
|
||||||
|
- `install.ts`: add a prominent "Admin API Token" banner printed right after
|
||||||
|
bootstrap succeeds. Include copy-now warning.
|
||||||
|
- `install.ts`: on health-check timeout, read the tail of `LOG_FILE` and print
|
||||||
|
the last ~30 non-empty lines before returning.
|
||||||
|
- Keep `mosaic gateway config` as-is (view/edit env vars); this is not the
|
||||||
|
setup wizard.
|
||||||
|
- No new commands. No new flags yet.
|
||||||
|
|
||||||
|
## Out of scope
|
||||||
|
|
||||||
|
- Readline/stdin piping fragility (pre-existing, not related to user complaint).
|
||||||
|
- Refactor to a phased state machine (overkill for three targeted fixes).
|
||||||
|
|
||||||
|
## Verification
|
||||||
|
|
||||||
|
- Typecheck, lint, format (mandatory gates).
|
||||||
|
- Manual end-to-end: fresh install → confirm token displayed.
|
||||||
|
- Manual resume: delete daemon.pid mid-install → re-run → confirm it resumes.
|
||||||
|
- Manual failure: point ENV_FILE port to a used port → re-run → confirm log
|
||||||
|
tail is printed.
|
||||||
@@ -3,7 +3,7 @@
|
|||||||
"version": "0.0.2",
|
"version": "0.0.2",
|
||||||
"repository": {
|
"repository": {
|
||||||
"type": "git",
|
"type": "git",
|
||||||
"url": "https://git.mosaicstack.dev/mosaic/mosaic-stack.git",
|
"url": "https://git.mosaicstack.dev/mosaicstack/mosaic-stack.git",
|
||||||
"directory": "packages/agent"
|
"directory": "packages/agent"
|
||||||
},
|
},
|
||||||
"main": "dist/index.js",
|
"main": "dist/index.js",
|
||||||
@@ -28,7 +28,7 @@
|
|||||||
"vitest": "^2.0.0"
|
"vitest": "^2.0.0"
|
||||||
},
|
},
|
||||||
"publishConfig": {
|
"publishConfig": {
|
||||||
"registry": "https://git.mosaicstack.dev/api/packages/mosaic/npm/",
|
"registry": "https://git.mosaicstack.dev/api/packages/mosaicstack/npm/",
|
||||||
"access": "public"
|
"access": "public"
|
||||||
},
|
},
|
||||||
"files": [
|
"files": [
|
||||||
|
|||||||
@@ -3,7 +3,7 @@
|
|||||||
"version": "0.0.2",
|
"version": "0.0.2",
|
||||||
"repository": {
|
"repository": {
|
||||||
"type": "git",
|
"type": "git",
|
||||||
"url": "https://git.mosaicstack.dev/mosaic/mosaic-stack.git",
|
"url": "https://git.mosaicstack.dev/mosaicstack/mosaic-stack.git",
|
||||||
"directory": "packages/auth"
|
"directory": "packages/auth"
|
||||||
},
|
},
|
||||||
"type": "module",
|
"type": "module",
|
||||||
@@ -32,7 +32,7 @@
|
|||||||
"better-auth": "^1.5.5"
|
"better-auth": "^1.5.5"
|
||||||
},
|
},
|
||||||
"publishConfig": {
|
"publishConfig": {
|
||||||
"registry": "https://git.mosaicstack.dev/api/packages/mosaic/npm/",
|
"registry": "https://git.mosaicstack.dev/api/packages/mosaicstack/npm/",
|
||||||
"access": "public"
|
"access": "public"
|
||||||
},
|
},
|
||||||
"files": [
|
"files": [
|
||||||
|
|||||||
@@ -3,7 +3,7 @@
|
|||||||
"version": "0.0.2",
|
"version": "0.0.2",
|
||||||
"repository": {
|
"repository": {
|
||||||
"type": "git",
|
"type": "git",
|
||||||
"url": "https://git.mosaicstack.dev/mosaic/mosaic-stack.git",
|
"url": "https://git.mosaicstack.dev/mosaicstack/mosaic-stack.git",
|
||||||
"directory": "packages/brain"
|
"directory": "packages/brain"
|
||||||
},
|
},
|
||||||
"main": "dist/index.js",
|
"main": "dist/index.js",
|
||||||
@@ -29,7 +29,7 @@
|
|||||||
"vitest": "^2.0.0"
|
"vitest": "^2.0.0"
|
||||||
},
|
},
|
||||||
"publishConfig": {
|
"publishConfig": {
|
||||||
"registry": "https://git.mosaicstack.dev/api/packages/mosaic/npm/",
|
"registry": "https://git.mosaicstack.dev/api/packages/mosaicstack/npm/",
|
||||||
"access": "public"
|
"access": "public"
|
||||||
},
|
},
|
||||||
"files": [
|
"files": [
|
||||||
|
|||||||
@@ -3,7 +3,7 @@
|
|||||||
"version": "0.0.17",
|
"version": "0.0.17",
|
||||||
"repository": {
|
"repository": {
|
||||||
"type": "git",
|
"type": "git",
|
||||||
"url": "https://git.mosaicstack.dev/mosaic/mosaic-stack.git",
|
"url": "https://git.mosaicstack.dev/mosaicstack/mosaic-stack.git",
|
||||||
"directory": "packages/cli"
|
"directory": "packages/cli"
|
||||||
},
|
},
|
||||||
"type": "module",
|
"type": "module",
|
||||||
@@ -47,7 +47,7 @@
|
|||||||
"vitest": "^2.0.0"
|
"vitest": "^2.0.0"
|
||||||
},
|
},
|
||||||
"publishConfig": {
|
"publishConfig": {
|
||||||
"registry": "https://git.mosaicstack.dev/api/packages/mosaic/npm/",
|
"registry": "https://git.mosaicstack.dev/api/packages/mosaicstack/npm/",
|
||||||
"access": "public"
|
"access": "public"
|
||||||
},
|
},
|
||||||
"files": [
|
"files": [
|
||||||
|
|||||||
@@ -3,7 +3,7 @@
|
|||||||
"version": "0.0.2",
|
"version": "0.0.2",
|
||||||
"repository": {
|
"repository": {
|
||||||
"type": "git",
|
"type": "git",
|
||||||
"url": "https://git.mosaicstack.dev/mosaic/mosaic-stack.git",
|
"url": "https://git.mosaicstack.dev/mosaicstack/mosaic-stack.git",
|
||||||
"directory": "packages/config"
|
"directory": "packages/config"
|
||||||
},
|
},
|
||||||
"type": "module",
|
"type": "module",
|
||||||
@@ -32,7 +32,7 @@
|
|||||||
"vitest": "^2.0.0"
|
"vitest": "^2.0.0"
|
||||||
},
|
},
|
||||||
"publishConfig": {
|
"publishConfig": {
|
||||||
"registry": "https://git.mosaicstack.dev/api/packages/mosaic/npm/",
|
"registry": "https://git.mosaicstack.dev/api/packages/mosaicstack/npm/",
|
||||||
"access": "public"
|
"access": "public"
|
||||||
},
|
},
|
||||||
"files": [
|
"files": [
|
||||||
|
|||||||
@@ -3,7 +3,7 @@
|
|||||||
"version": "0.0.2",
|
"version": "0.0.2",
|
||||||
"repository": {
|
"repository": {
|
||||||
"type": "git",
|
"type": "git",
|
||||||
"url": "https://git.mosaicstack.dev/mosaic/mosaic-stack.git",
|
"url": "https://git.mosaicstack.dev/mosaicstack/mosaic-stack.git",
|
||||||
"directory": "packages/coord"
|
"directory": "packages/coord"
|
||||||
},
|
},
|
||||||
"main": "dist/index.js",
|
"main": "dist/index.js",
|
||||||
@@ -29,7 +29,7 @@
|
|||||||
"vitest": "^2.0.0"
|
"vitest": "^2.0.0"
|
||||||
},
|
},
|
||||||
"publishConfig": {
|
"publishConfig": {
|
||||||
"registry": "https://git.mosaicstack.dev/api/packages/mosaic/npm/",
|
"registry": "https://git.mosaicstack.dev/api/packages/mosaicstack/npm/",
|
||||||
"access": "public"
|
"access": "public"
|
||||||
},
|
},
|
||||||
"files": [
|
"files": [
|
||||||
|
|||||||
@@ -3,7 +3,7 @@
|
|||||||
"version": "0.0.3",
|
"version": "0.0.3",
|
||||||
"repository": {
|
"repository": {
|
||||||
"type": "git",
|
"type": "git",
|
||||||
"url": "https://git.mosaicstack.dev/mosaic/mosaic-stack.git",
|
"url": "https://git.mosaicstack.dev/mosaicstack/mosaic-stack.git",
|
||||||
"directory": "packages/db"
|
"directory": "packages/db"
|
||||||
},
|
},
|
||||||
"type": "module",
|
"type": "module",
|
||||||
@@ -38,7 +38,7 @@
|
|||||||
"postgres": "^3.4.8"
|
"postgres": "^3.4.8"
|
||||||
},
|
},
|
||||||
"publishConfig": {
|
"publishConfig": {
|
||||||
"registry": "https://git.mosaicstack.dev/api/packages/mosaic/npm/",
|
"registry": "https://git.mosaicstack.dev/api/packages/mosaicstack/npm/",
|
||||||
"access": "public"
|
"access": "public"
|
||||||
},
|
},
|
||||||
"files": [
|
"files": [
|
||||||
|
|||||||
@@ -3,7 +3,7 @@
|
|||||||
"version": "0.0.2",
|
"version": "0.0.2",
|
||||||
"repository": {
|
"repository": {
|
||||||
"type": "git",
|
"type": "git",
|
||||||
"url": "https://git.mosaicstack.dev/mosaic/mosaic-stack.git",
|
"url": "https://git.mosaicstack.dev/mosaicstack/mosaic-stack.git",
|
||||||
"directory": "packages/design-tokens"
|
"directory": "packages/design-tokens"
|
||||||
},
|
},
|
||||||
"type": "module",
|
"type": "module",
|
||||||
@@ -26,7 +26,7 @@
|
|||||||
"vitest": "^2.0.0"
|
"vitest": "^2.0.0"
|
||||||
},
|
},
|
||||||
"publishConfig": {
|
"publishConfig": {
|
||||||
"registry": "https://git.mosaicstack.dev/api/packages/mosaic/npm/",
|
"registry": "https://git.mosaicstack.dev/api/packages/mosaicstack/npm/",
|
||||||
"access": "public"
|
"access": "public"
|
||||||
},
|
},
|
||||||
"files": [
|
"files": [
|
||||||
|
|||||||
@@ -3,7 +3,7 @@
|
|||||||
"version": "0.0.2",
|
"version": "0.0.2",
|
||||||
"repository": {
|
"repository": {
|
||||||
"type": "git",
|
"type": "git",
|
||||||
"url": "https://git.mosaicstack.dev/mosaic/mosaic-stack.git",
|
"url": "https://git.mosaicstack.dev/mosaicstack/mosaic-stack.git",
|
||||||
"directory": "packages/forge"
|
"directory": "packages/forge"
|
||||||
},
|
},
|
||||||
"type": "module",
|
"type": "module",
|
||||||
@@ -35,7 +35,7 @@
|
|||||||
"vitest": "^2.0.0"
|
"vitest": "^2.0.0"
|
||||||
},
|
},
|
||||||
"publishConfig": {
|
"publishConfig": {
|
||||||
"registry": "https://git.mosaicstack.dev/api/packages/mosaic/npm/",
|
"registry": "https://git.mosaicstack.dev/api/packages/mosaicstack/npm/",
|
||||||
"access": "public"
|
"access": "public"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -3,7 +3,7 @@
|
|||||||
"version": "0.0.2",
|
"version": "0.0.2",
|
||||||
"repository": {
|
"repository": {
|
||||||
"type": "git",
|
"type": "git",
|
||||||
"url": "https://git.mosaicstack.dev/mosaic/mosaic-stack.git",
|
"url": "https://git.mosaicstack.dev/mosaicstack/mosaic-stack.git",
|
||||||
"directory": "packages/log"
|
"directory": "packages/log"
|
||||||
},
|
},
|
||||||
"type": "module",
|
"type": "module",
|
||||||
@@ -30,7 +30,7 @@
|
|||||||
"vitest": "^2.0.0"
|
"vitest": "^2.0.0"
|
||||||
},
|
},
|
||||||
"publishConfig": {
|
"publishConfig": {
|
||||||
"registry": "https://git.mosaicstack.dev/api/packages/mosaic/npm/",
|
"registry": "https://git.mosaicstack.dev/api/packages/mosaicstack/npm/",
|
||||||
"access": "public"
|
"access": "public"
|
||||||
},
|
},
|
||||||
"files": [
|
"files": [
|
||||||
|
|||||||
@@ -3,7 +3,7 @@
|
|||||||
"version": "0.0.2",
|
"version": "0.0.2",
|
||||||
"repository": {
|
"repository": {
|
||||||
"type": "git",
|
"type": "git",
|
||||||
"url": "https://git.mosaicstack.dev/mosaic/mosaic-stack.git",
|
"url": "https://git.mosaicstack.dev/mosaicstack/mosaic-stack.git",
|
||||||
"directory": "packages/macp"
|
"directory": "packages/macp"
|
||||||
},
|
},
|
||||||
"type": "module",
|
"type": "module",
|
||||||
@@ -28,7 +28,7 @@
|
|||||||
"vitest": "^2.0.0"
|
"vitest": "^2.0.0"
|
||||||
},
|
},
|
||||||
"publishConfig": {
|
"publishConfig": {
|
||||||
"registry": "https://git.mosaicstack.dev/api/packages/mosaic/npm/",
|
"registry": "https://git.mosaicstack.dev/api/packages/mosaicstack/npm/",
|
||||||
"access": "public"
|
"access": "public"
|
||||||
},
|
},
|
||||||
"files": [
|
"files": [
|
||||||
|
|||||||
@@ -3,7 +3,7 @@
|
|||||||
"version": "0.0.3",
|
"version": "0.0.3",
|
||||||
"repository": {
|
"repository": {
|
||||||
"type": "git",
|
"type": "git",
|
||||||
"url": "https://git.mosaicstack.dev/mosaic/mosaic-stack.git",
|
"url": "https://git.mosaicstack.dev/mosaicstack/mosaic-stack.git",
|
||||||
"directory": "packages/memory"
|
"directory": "packages/memory"
|
||||||
},
|
},
|
||||||
"type": "module",
|
"type": "module",
|
||||||
@@ -32,7 +32,7 @@
|
|||||||
"vitest": "^2.0.0"
|
"vitest": "^2.0.0"
|
||||||
},
|
},
|
||||||
"publishConfig": {
|
"publishConfig": {
|
||||||
"registry": "https://git.mosaicstack.dev/api/packages/mosaic/npm/",
|
"registry": "https://git.mosaicstack.dev/api/packages/mosaicstack/npm/",
|
||||||
"access": "public"
|
"access": "public"
|
||||||
},
|
},
|
||||||
"files": [
|
"files": [
|
||||||
|
|||||||
@@ -1,9 +1,9 @@
|
|||||||
{
|
{
|
||||||
"name": "@mosaicstack/mosaic",
|
"name": "@mosaicstack/mosaic",
|
||||||
"version": "0.0.19",
|
"version": "0.0.21",
|
||||||
"repository": {
|
"repository": {
|
||||||
"type": "git",
|
"type": "git",
|
||||||
"url": "https://git.mosaicstack.dev/mosaic/mosaic-stack.git",
|
"url": "https://git.mosaicstack.dev/mosaicstack/mosaic-stack.git",
|
||||||
"directory": "packages/mosaic"
|
"directory": "packages/mosaic"
|
||||||
},
|
},
|
||||||
"description": "Mosaic agent framework — installation wizard and meta package",
|
"description": "Mosaic agent framework — installation wizard and meta package",
|
||||||
@@ -52,7 +52,7 @@
|
|||||||
"vitest": "^2.0.0"
|
"vitest": "^2.0.0"
|
||||||
},
|
},
|
||||||
"publishConfig": {
|
"publishConfig": {
|
||||||
"registry": "https://git.mosaicstack.dev/api/packages/mosaic/npm/",
|
"registry": "https://git.mosaicstack.dev/api/packages/mosaicstack/npm/",
|
||||||
"access": "public"
|
"access": "public"
|
||||||
},
|
},
|
||||||
"files": [
|
"files": [
|
||||||
|
|||||||
@@ -1,21 +1,26 @@
|
|||||||
import { randomBytes } from 'node:crypto';
|
import { randomBytes } from 'node:crypto';
|
||||||
import { writeFileSync } from 'node:fs';
|
import { existsSync, readFileSync, writeFileSync } from 'node:fs';
|
||||||
import { join } from 'node:path';
|
import { join } from 'node:path';
|
||||||
import { createInterface } from 'node:readline';
|
import { createInterface } from 'node:readline';
|
||||||
import type { GatewayMeta } from './daemon.js';
|
import type { GatewayMeta } from './daemon.js';
|
||||||
import {
|
import {
|
||||||
ENV_FILE,
|
ENV_FILE,
|
||||||
GATEWAY_HOME,
|
GATEWAY_HOME,
|
||||||
|
LOG_FILE,
|
||||||
ensureDirs,
|
ensureDirs,
|
||||||
|
getDaemonPid,
|
||||||
installGatewayPackage,
|
installGatewayPackage,
|
||||||
readMeta,
|
readMeta,
|
||||||
resolveGatewayEntry,
|
resolveGatewayEntry,
|
||||||
startDaemon,
|
startDaemon,
|
||||||
|
stopDaemon,
|
||||||
waitForHealth,
|
waitForHealth,
|
||||||
writeMeta,
|
writeMeta,
|
||||||
getInstalledGatewayVersion,
|
getInstalledGatewayVersion,
|
||||||
} from './daemon.js';
|
} from './daemon.js';
|
||||||
|
|
||||||
|
const MOSAIC_CONFIG_FILE = join(GATEWAY_HOME, 'mosaic.config.json');
|
||||||
|
|
||||||
interface InstallOpts {
|
interface InstallOpts {
|
||||||
host: string;
|
host: string;
|
||||||
port: number;
|
port: number;
|
||||||
@@ -36,30 +41,198 @@ export async function runInstall(opts: InstallOpts): Promise<void> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
async function doInstall(rl: ReturnType<typeof createInterface>, opts: InstallOpts): Promise<void> {
|
async function doInstall(rl: ReturnType<typeof createInterface>, opts: InstallOpts): Promise<void> {
|
||||||
// Check existing installation
|
|
||||||
const existing = readMeta();
|
const existing = readMeta();
|
||||||
if (existing) {
|
const envExists = existsSync(ENV_FILE);
|
||||||
const answer = await prompt(
|
const mosaicConfigExists = existsSync(MOSAIC_CONFIG_FILE);
|
||||||
rl,
|
let hasConfig = envExists && mosaicConfigExists;
|
||||||
`Gateway already installed (v${existing.version}). Reinstall? [y/N] `,
|
let daemonRunning = getDaemonPid() !== null;
|
||||||
|
const hasAdminToken = Boolean(existing?.adminToken);
|
||||||
|
// `opts.host` already incorporates meta fallback via the parent command
|
||||||
|
// in gateway.ts (resolveOpts). Using it directly also lets a user pass
|
||||||
|
// `--host X` to recover from a previous install that stored a broken
|
||||||
|
// host. We intentionally do not prefer `existing.host` over `opts.host`.
|
||||||
|
const host = opts.host;
|
||||||
|
|
||||||
|
// Corrupt partial state: exactly one of the two config files survived.
|
||||||
|
// This happens when an earlier install was interrupted between writing
|
||||||
|
// .env and mosaic.config.json. Rewriting the missing one would silently
|
||||||
|
// rotate BETTER_AUTH_SECRET or clobber saved DB/Valkey URLs. Refuse to
|
||||||
|
// guess — tell the user how to recover. Check file presence only; do
|
||||||
|
// NOT gate on `existing`, because the installer writes config before
|
||||||
|
// meta, so an interrupted first install has no meta yet.
|
||||||
|
if ((envExists || mosaicConfigExists) && !hasConfig) {
|
||||||
|
console.error('Gateway install is in a corrupt partial state:');
|
||||||
|
console.error(` .env file: ${envExists ? 'present' : 'MISSING'} (${ENV_FILE})`);
|
||||||
|
console.error(
|
||||||
|
` mosaic.config.json: ${mosaicConfigExists ? 'present' : 'MISSING'} (${MOSAIC_CONFIG_FILE})`,
|
||||||
);
|
);
|
||||||
if (answer.toLowerCase() !== 'y') {
|
console.error('\nRun `mosaic gateway uninstall` to clean up, then re-run install.');
|
||||||
console.log('Aborted.');
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Fully set up already — offer to re-run the config wizard and restart.
|
||||||
|
// The wizard allows changing storage tier / DB URLs, so this can move
|
||||||
|
// the install onto a different data store. We do NOT wipe persisted
|
||||||
|
// local data here — for a true scratch wipe run `mosaic gateway
|
||||||
|
// uninstall` first.
|
||||||
|
let explicitReinstall = false;
|
||||||
|
if (existing && hasConfig && daemonRunning && hasAdminToken) {
|
||||||
|
console.log(`Gateway is already installed and running (v${existing.version}).`);
|
||||||
|
console.log(` Endpoint: http://${existing.host}:${existing.port.toString()}`);
|
||||||
|
console.log(` Status: mosaic gateway status`);
|
||||||
|
console.log();
|
||||||
|
console.log('Re-running the config wizard will:');
|
||||||
|
console.log(' - regenerate .env and mosaic.config.json');
|
||||||
|
console.log(' - restart the daemon');
|
||||||
|
console.log(' - preserve BETTER_AUTH_SECRET (sessions stay valid)');
|
||||||
|
console.log(' - clear the stored admin token (you will re-bootstrap an admin user)');
|
||||||
|
console.log(' - allow changing storage tier / DB URLs (may point at a different data store)');
|
||||||
|
console.log('To wipe persisted data, run `mosaic gateway uninstall` first.');
|
||||||
|
const answer = await prompt(rl, 'Re-run config wizard? [y/N] ');
|
||||||
|
if (answer.trim().toLowerCase() !== 'y') {
|
||||||
|
console.log('Nothing to do.');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
// Fall through. The daemon stop below triggers because hasConfig=false
|
||||||
|
// forces the wizard to re-run.
|
||||||
|
hasConfig = false;
|
||||||
|
explicitReinstall = true;
|
||||||
|
} else if (existing && (hasConfig || daemonRunning)) {
|
||||||
|
// Partial install detected — resume instead of re-prompting the user.
|
||||||
|
console.log('Detected a partial gateway installation — resuming setup.\n');
|
||||||
|
}
|
||||||
|
|
||||||
|
// If we are going to (re)write config, the running daemon would end up
|
||||||
|
// serving the old config while health checks and meta point at the new
|
||||||
|
// one. Always stop the daemon before writing config.
|
||||||
|
if (!hasConfig && daemonRunning) {
|
||||||
|
console.log('Stopping gateway daemon before writing new config...');
|
||||||
|
try {
|
||||||
|
await stopDaemon();
|
||||||
|
} catch (err) {
|
||||||
|
const msg = err instanceof Error ? err.message : String(err);
|
||||||
|
if (/not running/i.test(msg)) {
|
||||||
|
// Raced with daemon exit — fine, proceed.
|
||||||
|
} else {
|
||||||
|
console.error(`Failed to stop running daemon: ${msg}`);
|
||||||
|
console.error('Refusing to rewrite config while an unknown-state daemon is running.');
|
||||||
|
console.error('Stop it manually (mosaic gateway stop) and re-run install.');
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
// Re-check — stop may have succeeded but we want to be sure before
|
||||||
|
// writing new config files and starting a fresh process.
|
||||||
|
if (getDaemonPid() !== null) {
|
||||||
|
console.error('Gateway daemon is still running after stop attempt. Aborting.');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
daemonRunning = false;
|
||||||
|
}
|
||||||
|
|
||||||
// Step 1: Install npm package
|
// Step 1: Install npm package. Always run on first install and on any
|
||||||
if (!opts.skipInstall) {
|
// resume where the daemon is NOT already running — a prior failure may
|
||||||
|
// have been caused by a broken package version, and the retry should
|
||||||
|
// pick up the latest release. Skip only when resuming while the daemon
|
||||||
|
// is already alive (package must be working to have started).
|
||||||
|
if (!opts.skipInstall && !daemonRunning) {
|
||||||
installGatewayPackage();
|
installGatewayPackage();
|
||||||
}
|
}
|
||||||
|
|
||||||
ensureDirs();
|
ensureDirs();
|
||||||
|
|
||||||
// Step 2: Collect configuration
|
// Step 2: Collect configuration (skip if both files already exist).
|
||||||
|
// On resume, treat the .env file as authoritative for port — but let a
|
||||||
|
// user-supplied non-default `--port` override it so they can recover
|
||||||
|
// from a conflicting saved port the same way `--host` lets them
|
||||||
|
// recover from a bad saved host. `opts.port === 14242` is commander's
|
||||||
|
// default (not explicit user input), so we prefer .env in that case.
|
||||||
|
let port: number;
|
||||||
|
const regeneratedConfig = !hasConfig;
|
||||||
|
if (hasConfig) {
|
||||||
|
const envPort = readPortFromEnv();
|
||||||
|
port = opts.port !== 14242 ? opts.port : (envPort ?? existing?.port ?? opts.port);
|
||||||
|
console.log(`Using existing config at ${ENV_FILE} (port ${port.toString()})`);
|
||||||
|
} else {
|
||||||
|
port = await runConfigWizard(rl, opts);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Step 3: Write meta.json. Prefer host from existing meta when resuming.
|
||||||
|
let entryPoint: string;
|
||||||
|
try {
|
||||||
|
entryPoint = resolveGatewayEntry();
|
||||||
|
} catch {
|
||||||
|
console.error('Error: Gateway package not found after install.');
|
||||||
|
console.error('Check that @mosaicstack/gateway installed correctly.');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const version = getInstalledGatewayVersion() ?? 'unknown';
|
||||||
|
// Preserve the admin token only on a pure resume (no config regeneration).
|
||||||
|
// Any time we regenerated config, the wizard may have pointed at a
|
||||||
|
// different storage tier / DB URL, so the old token is unverifiable —
|
||||||
|
// drop it and require re-bootstrap.
|
||||||
|
const preserveToken = !regeneratedConfig && Boolean(existing?.adminToken);
|
||||||
|
const meta: GatewayMeta = {
|
||||||
|
version,
|
||||||
|
installedAt: explicitReinstall
|
||||||
|
? new Date().toISOString()
|
||||||
|
: (existing?.installedAt ?? new Date().toISOString()),
|
||||||
|
entryPoint,
|
||||||
|
host,
|
||||||
|
port,
|
||||||
|
...(preserveToken && existing?.adminToken ? { adminToken: existing.adminToken } : {}),
|
||||||
|
};
|
||||||
|
writeMeta(meta);
|
||||||
|
|
||||||
|
// Step 4: Start the daemon (idempotent — skip if already running).
|
||||||
|
if (!daemonRunning) {
|
||||||
|
console.log('\nStarting gateway daemon...');
|
||||||
|
try {
|
||||||
|
const pid = startDaemon();
|
||||||
|
console.log(`Gateway started (PID ${pid.toString()})`);
|
||||||
|
} catch (err) {
|
||||||
|
console.error(`Failed to start: ${err instanceof Error ? err.message : String(err)}`);
|
||||||
|
printLogTail();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
console.log('\nGateway daemon is already running.');
|
||||||
|
}
|
||||||
|
|
||||||
|
// Step 5: Wait for health
|
||||||
|
console.log('Waiting for gateway to become healthy...');
|
||||||
|
const healthy = await waitForHealth(host, port, 30_000);
|
||||||
|
if (!healthy) {
|
||||||
|
console.error('\nGateway did not become healthy within 30 seconds.');
|
||||||
|
printLogTail();
|
||||||
|
console.error('\nFix the underlying error above, then re-run `mosaic gateway install`.');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
console.log('Gateway is healthy.\n');
|
||||||
|
|
||||||
|
// Step 6: Bootstrap — first admin user.
|
||||||
|
await bootstrapFirstUser(rl, host, port, meta);
|
||||||
|
|
||||||
|
console.log('\n─── Installation Complete ───');
|
||||||
|
console.log(` Endpoint: http://${host}:${port.toString()}`);
|
||||||
|
console.log(` Config: ${GATEWAY_HOME}`);
|
||||||
|
console.log(` Logs: mosaic gateway logs`);
|
||||||
|
console.log(` Status: mosaic gateway status`);
|
||||||
|
}
|
||||||
|
|
||||||
|
async function runConfigWizard(
|
||||||
|
rl: ReturnType<typeof createInterface>,
|
||||||
|
opts: InstallOpts,
|
||||||
|
): Promise<number> {
|
||||||
console.log('\n─── Gateway Configuration ───\n');
|
console.log('\n─── Gateway Configuration ───\n');
|
||||||
|
|
||||||
// Tier selection
|
// If a previous .env exists on disk, reuse its BETTER_AUTH_SECRET so
|
||||||
|
// regenerating config does not silently log out existing users.
|
||||||
|
const preservedAuthSecret = readEnvVarFromFile('BETTER_AUTH_SECRET');
|
||||||
|
if (preservedAuthSecret) {
|
||||||
|
console.log('(Preserving existing BETTER_AUTH_SECRET — auth sessions will remain valid.)\n');
|
||||||
|
}
|
||||||
|
|
||||||
console.log('Storage tier:');
|
console.log('Storage tier:');
|
||||||
console.log(' 1. Local (embedded database, no dependencies)');
|
console.log(' 1. Local (embedded database, no dependencies)');
|
||||||
console.log(' 2. Team (PostgreSQL + Valkey required)');
|
console.log(' 2. Team (PostgreSQL + Valkey required)');
|
||||||
@@ -91,10 +264,8 @@ async function doInstall(rl: ReturnType<typeof createInterface>, opts: InstallOp
|
|||||||
const corsOrigin =
|
const corsOrigin =
|
||||||
(await prompt(rl, 'CORS origin [http://localhost:3000]: ')) || 'http://localhost:3000';
|
(await prompt(rl, 'CORS origin [http://localhost:3000]: ')) || 'http://localhost:3000';
|
||||||
|
|
||||||
// Generate auth secret
|
const authSecret = preservedAuthSecret ?? randomBytes(32).toString('hex');
|
||||||
const authSecret = randomBytes(32).toString('hex');
|
|
||||||
|
|
||||||
// Step 3: Write .env
|
|
||||||
const envLines = [
|
const envLines = [
|
||||||
`GATEWAY_PORT=${port.toString()}`,
|
`GATEWAY_PORT=${port.toString()}`,
|
||||||
`BETTER_AUTH_SECRET=${authSecret}`,
|
`BETTER_AUTH_SECRET=${authSecret}`,
|
||||||
@@ -116,7 +287,6 @@ async function doInstall(rl: ReturnType<typeof createInterface>, opts: InstallOp
|
|||||||
writeFileSync(ENV_FILE, envLines.join('\n') + '\n', { mode: 0o600 });
|
writeFileSync(ENV_FILE, envLines.join('\n') + '\n', { mode: 0o600 });
|
||||||
console.log(`\nConfig written to ${ENV_FILE}`);
|
console.log(`\nConfig written to ${ENV_FILE}`);
|
||||||
|
|
||||||
// Step 3b: Write mosaic.config.json
|
|
||||||
const mosaicConfig =
|
const mosaicConfig =
|
||||||
tier === 'local'
|
tier === 'local'
|
||||||
? {
|
? {
|
||||||
@@ -132,66 +302,81 @@ async function doInstall(rl: ReturnType<typeof createInterface>, opts: InstallOp
|
|||||||
memory: { type: 'pgvector' },
|
memory: { type: 'pgvector' },
|
||||||
};
|
};
|
||||||
|
|
||||||
const configFile = join(GATEWAY_HOME, 'mosaic.config.json');
|
writeFileSync(MOSAIC_CONFIG_FILE, JSON.stringify(mosaicConfig, null, 2) + '\n', { mode: 0o600 });
|
||||||
writeFileSync(configFile, JSON.stringify(mosaicConfig, null, 2) + '\n', { mode: 0o600 });
|
console.log(`Config written to ${MOSAIC_CONFIG_FILE}`);
|
||||||
console.log(`Config written to ${configFile}`);
|
|
||||||
|
|
||||||
// Step 4: Write meta.json
|
return port;
|
||||||
let entryPoint: string;
|
}
|
||||||
|
|
||||||
|
function readEnvVarFromFile(key: string): string | null {
|
||||||
|
if (!existsSync(ENV_FILE)) return null;
|
||||||
try {
|
try {
|
||||||
entryPoint = resolveGatewayEntry();
|
for (const line of readFileSync(ENV_FILE, 'utf-8').split('\n')) {
|
||||||
|
const trimmed = line.trim();
|
||||||
|
if (!trimmed || trimmed.startsWith('#')) continue;
|
||||||
|
const eqIdx = trimmed.indexOf('=');
|
||||||
|
if (eqIdx <= 0) continue;
|
||||||
|
if (trimmed.slice(0, eqIdx) !== key) continue;
|
||||||
|
return trimmed.slice(eqIdx + 1);
|
||||||
|
}
|
||||||
} catch {
|
} catch {
|
||||||
console.error('Error: Gateway package not found after install.');
|
return null;
|
||||||
console.error('Check that @mosaicstack/gateway installed correctly.');
|
}
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
function readPortFromEnv(): number | null {
|
||||||
|
const raw = readEnvVarFromFile('GATEWAY_PORT');
|
||||||
|
if (raw === null) return null;
|
||||||
|
const parsed = parseInt(raw, 10);
|
||||||
|
return Number.isNaN(parsed) ? null : parsed;
|
||||||
|
}
|
||||||
|
|
||||||
|
function printLogTail(maxLines = 30): void {
|
||||||
|
if (!existsSync(LOG_FILE)) {
|
||||||
|
console.error(`(no log file at ${LOG_FILE})`);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
const version = getInstalledGatewayVersion() ?? 'unknown';
|
|
||||||
|
|
||||||
const meta = {
|
|
||||||
version,
|
|
||||||
installedAt: new Date().toISOString(),
|
|
||||||
entryPoint,
|
|
||||||
host: opts.host,
|
|
||||||
port,
|
|
||||||
};
|
|
||||||
writeMeta(meta);
|
|
||||||
|
|
||||||
// Step 5: Start the daemon
|
|
||||||
console.log('\nStarting gateway daemon...');
|
|
||||||
try {
|
try {
|
||||||
const pid = startDaemon();
|
const lines = readFileSync(LOG_FILE, 'utf-8')
|
||||||
console.log(`Gateway started (PID ${pid.toString()})`);
|
.split('\n')
|
||||||
|
.filter((l) => l.trim().length > 0);
|
||||||
|
const tail = lines.slice(-maxLines);
|
||||||
|
if (tail.length === 0) {
|
||||||
|
console.error('(log file is empty)');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
console.error(`\n─── Last ${tail.length.toString()} log lines (${LOG_FILE}) ───`);
|
||||||
|
for (const line of tail) console.error(line);
|
||||||
|
console.error('─────────────────────────────────────────────');
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
console.error(`Failed to start: ${err instanceof Error ? err.message : String(err)}`);
|
console.error(`Could not read log file: ${err instanceof Error ? err.message : String(err)}`);
|
||||||
return;
|
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// Step 6: Wait for health
|
function printAdminTokenBanner(token: string): void {
|
||||||
console.log('Waiting for gateway to become healthy...');
|
const border = '═'.repeat(68);
|
||||||
const healthy = await waitForHealth(opts.host, port, 30_000);
|
console.log();
|
||||||
if (!healthy) {
|
console.log(border);
|
||||||
console.error('Gateway did not become healthy within 30 seconds.');
|
console.log(' Admin API Token');
|
||||||
console.error(`Check logs: mosaic gateway logs`);
|
console.log(border);
|
||||||
return;
|
console.log();
|
||||||
}
|
console.log(` ${token}`);
|
||||||
console.log('Gateway is healthy.\n');
|
console.log();
|
||||||
|
console.log(' Save this token now — it will not be shown again in full.');
|
||||||
// Step 7: Bootstrap — first user setup
|
console.log(' It is stored (read-only) at:');
|
||||||
await bootstrapFirstUser(rl, opts.host, port, meta);
|
console.log(` ${join(GATEWAY_HOME, 'meta.json')}`);
|
||||||
|
console.log();
|
||||||
console.log('\n─── Installation Complete ───');
|
console.log(' Use it with admin endpoints, e.g.:');
|
||||||
console.log(` Endpoint: http://${opts.host}:${port.toString()}`);
|
console.log(` mosaic gateway --token <token> status`);
|
||||||
console.log(` Config: ${GATEWAY_HOME}`);
|
console.log(border);
|
||||||
console.log(` Logs: mosaic gateway logs`);
|
|
||||||
console.log(` Status: mosaic gateway status`);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
async function bootstrapFirstUser(
|
async function bootstrapFirstUser(
|
||||||
rl: ReturnType<typeof createInterface>,
|
rl: ReturnType<typeof createInterface>,
|
||||||
host: string,
|
host: string,
|
||||||
port: number,
|
port: number,
|
||||||
meta: Omit<GatewayMeta, 'adminToken'> & { adminToken?: string },
|
meta: GatewayMeta,
|
||||||
): Promise<void> {
|
): Promise<void> {
|
||||||
const baseUrl = `http://${host}:${port.toString()}`;
|
const baseUrl = `http://${host}:${port.toString()}`;
|
||||||
|
|
||||||
@@ -201,7 +386,12 @@ async function bootstrapFirstUser(
|
|||||||
|
|
||||||
const status = (await statusRes.json()) as { needsSetup: boolean };
|
const status = (await statusRes.json()) as { needsSetup: boolean };
|
||||||
if (!status.needsSetup) {
|
if (!status.needsSetup) {
|
||||||
|
if (meta.adminToken) {
|
||||||
|
console.log('Admin user already exists (token on file).');
|
||||||
|
} else {
|
||||||
console.log('Admin user already exists — skipping setup.');
|
console.log('Admin user already exists — skipping setup.');
|
||||||
|
console.log('(No admin token on file — sign in via the web UI to manage tokens.)');
|
||||||
|
}
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
} catch {
|
} catch {
|
||||||
@@ -247,12 +437,12 @@ async function bootstrapFirstUser(
|
|||||||
token: { plaintext: string };
|
token: { plaintext: string };
|
||||||
};
|
};
|
||||||
|
|
||||||
// Save admin token to meta
|
// Persist the token so future CLI calls can authenticate automatically.
|
||||||
meta.adminToken = result.token.plaintext;
|
meta.adminToken = result.token.plaintext;
|
||||||
writeMeta(meta as GatewayMeta);
|
writeMeta(meta);
|
||||||
|
|
||||||
console.log(`\nAdmin user created: ${result.user.email}`);
|
console.log(`\nAdmin user created: ${result.user.email}`);
|
||||||
console.log('Admin API token saved to gateway config.');
|
printAdminTokenBanner(result.token.plaintext);
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
console.error(`Bootstrap error: ${err instanceof Error ? err.message : String(err)}`);
|
console.error(`Bootstrap error: ${err instanceof Error ? err.message : String(err)}`);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -127,10 +127,28 @@ export function semverLt(a: string, b: string): boolean {
|
|||||||
// ─── Known packages for checkForAllUpdates() ──────────────────────────────
|
// ─── Known packages for checkForAllUpdates() ──────────────────────────────
|
||||||
|
|
||||||
const KNOWN_PACKAGES = [
|
const KNOWN_PACKAGES = [
|
||||||
'@mosaicstack/mosaic',
|
'@mosaicstack/agent',
|
||||||
'@mosaicstack/cli',
|
'@mosaicstack/auth',
|
||||||
|
'@mosaicstack/brain',
|
||||||
|
'@mosaicstack/config',
|
||||||
|
'@mosaicstack/coord',
|
||||||
|
'@mosaicstack/db',
|
||||||
|
'@mosaicstack/design-tokens',
|
||||||
|
'@mosaicstack/discord-plugin',
|
||||||
|
'@mosaicstack/forge',
|
||||||
'@mosaicstack/gateway',
|
'@mosaicstack/gateway',
|
||||||
|
'@mosaicstack/log',
|
||||||
|
'@mosaicstack/macp',
|
||||||
|
'@mosaicstack/memory',
|
||||||
|
'@mosaicstack/mosaic',
|
||||||
|
'@mosaicstack/oc-framework-plugin',
|
||||||
|
'@mosaicstack/oc-macp-plugin',
|
||||||
|
'@mosaicstack/prdy',
|
||||||
'@mosaicstack/quality-rails',
|
'@mosaicstack/quality-rails',
|
||||||
|
'@mosaicstack/queue',
|
||||||
|
'@mosaicstack/storage',
|
||||||
|
'@mosaicstack/telegram-plugin',
|
||||||
|
'@mosaicstack/types',
|
||||||
];
|
];
|
||||||
|
|
||||||
// ─── Multi-package types ──────────────────────────────────────────────────
|
// ─── Multi-package types ──────────────────────────────────────────────────
|
||||||
|
|||||||
@@ -3,7 +3,7 @@
|
|||||||
"version": "0.0.2",
|
"version": "0.0.2",
|
||||||
"repository": {
|
"repository": {
|
||||||
"type": "git",
|
"type": "git",
|
||||||
"url": "https://git.mosaicstack.dev/mosaic/mosaic-stack.git",
|
"url": "https://git.mosaicstack.dev/mosaicstack/mosaic-stack.git",
|
||||||
"directory": "packages/prdy"
|
"directory": "packages/prdy"
|
||||||
},
|
},
|
||||||
"main": "dist/index.js",
|
"main": "dist/index.js",
|
||||||
@@ -33,7 +33,7 @@
|
|||||||
"vitest": "^2.0.0"
|
"vitest": "^2.0.0"
|
||||||
},
|
},
|
||||||
"publishConfig": {
|
"publishConfig": {
|
||||||
"registry": "https://git.mosaicstack.dev/api/packages/mosaic/npm/",
|
"registry": "https://git.mosaicstack.dev/api/packages/mosaicstack/npm/",
|
||||||
"access": "public"
|
"access": "public"
|
||||||
},
|
},
|
||||||
"files": [
|
"files": [
|
||||||
|
|||||||
@@ -3,7 +3,7 @@
|
|||||||
"version": "0.0.3",
|
"version": "0.0.3",
|
||||||
"repository": {
|
"repository": {
|
||||||
"type": "git",
|
"type": "git",
|
||||||
"url": "https://git.mosaicstack.dev/mosaic/mosaic-stack.git",
|
"url": "https://git.mosaicstack.dev/mosaicstack/mosaic-stack.git",
|
||||||
"directory": "packages/quality-rails"
|
"directory": "packages/quality-rails"
|
||||||
},
|
},
|
||||||
"type": "module",
|
"type": "module",
|
||||||
@@ -30,7 +30,7 @@
|
|||||||
"vitest": "^2.0.0"
|
"vitest": "^2.0.0"
|
||||||
},
|
},
|
||||||
"publishConfig": {
|
"publishConfig": {
|
||||||
"registry": "https://git.mosaicstack.dev/api/packages/mosaic/npm/",
|
"registry": "https://git.mosaicstack.dev/api/packages/mosaicstack/npm/",
|
||||||
"access": "public"
|
"access": "public"
|
||||||
},
|
},
|
||||||
"files": [
|
"files": [
|
||||||
|
|||||||
@@ -3,7 +3,7 @@
|
|||||||
"version": "0.0.3",
|
"version": "0.0.3",
|
||||||
"repository": {
|
"repository": {
|
||||||
"type": "git",
|
"type": "git",
|
||||||
"url": "https://git.mosaicstack.dev/mosaic/mosaic-stack.git",
|
"url": "https://git.mosaicstack.dev/mosaicstack/mosaic-stack.git",
|
||||||
"directory": "packages/queue"
|
"directory": "packages/queue"
|
||||||
},
|
},
|
||||||
"main": "dist/index.js",
|
"main": "dist/index.js",
|
||||||
@@ -29,7 +29,7 @@
|
|||||||
"vitest": "^2.0.0"
|
"vitest": "^2.0.0"
|
||||||
},
|
},
|
||||||
"publishConfig": {
|
"publishConfig": {
|
||||||
"registry": "https://git.mosaicstack.dev/api/packages/mosaic/npm/",
|
"registry": "https://git.mosaicstack.dev/api/packages/mosaicstack/npm/",
|
||||||
"access": "public"
|
"access": "public"
|
||||||
},
|
},
|
||||||
"files": [
|
"files": [
|
||||||
|
|||||||
@@ -3,7 +3,7 @@
|
|||||||
"version": "0.0.3",
|
"version": "0.0.3",
|
||||||
"repository": {
|
"repository": {
|
||||||
"type": "git",
|
"type": "git",
|
||||||
"url": "https://git.mosaicstack.dev/mosaic/mosaic-stack.git",
|
"url": "https://git.mosaicstack.dev/mosaicstack/mosaic-stack.git",
|
||||||
"directory": "packages/storage"
|
"directory": "packages/storage"
|
||||||
},
|
},
|
||||||
"main": "dist/index.js",
|
"main": "dist/index.js",
|
||||||
@@ -30,7 +30,7 @@
|
|||||||
"vitest": "^2.0.0"
|
"vitest": "^2.0.0"
|
||||||
},
|
},
|
||||||
"publishConfig": {
|
"publishConfig": {
|
||||||
"registry": "https://git.mosaicstack.dev/api/packages/mosaic/npm/",
|
"registry": "https://git.mosaicstack.dev/api/packages/mosaicstack/npm/",
|
||||||
"access": "public"
|
"access": "public"
|
||||||
},
|
},
|
||||||
"files": [
|
"files": [
|
||||||
|
|||||||
@@ -3,7 +3,7 @@
|
|||||||
"version": "0.0.2",
|
"version": "0.0.2",
|
||||||
"repository": {
|
"repository": {
|
||||||
"type": "git",
|
"type": "git",
|
||||||
"url": "https://git.mosaicstack.dev/mosaic/mosaic-stack.git",
|
"url": "https://git.mosaicstack.dev/mosaicstack/mosaic-stack.git",
|
||||||
"directory": "packages/types"
|
"directory": "packages/types"
|
||||||
},
|
},
|
||||||
"main": "dist/index.js",
|
"main": "dist/index.js",
|
||||||
@@ -29,7 +29,7 @@
|
|||||||
"class-validator": "^0.15.1"
|
"class-validator": "^0.15.1"
|
||||||
},
|
},
|
||||||
"publishConfig": {
|
"publishConfig": {
|
||||||
"registry": "https://git.mosaicstack.dev/api/packages/mosaic/npm/",
|
"registry": "https://git.mosaicstack.dev/api/packages/mosaicstack/npm/",
|
||||||
"access": "public"
|
"access": "public"
|
||||||
},
|
},
|
||||||
"files": [
|
"files": [
|
||||||
|
|||||||
@@ -3,7 +3,7 @@
|
|||||||
"version": "0.0.2",
|
"version": "0.0.2",
|
||||||
"repository": {
|
"repository": {
|
||||||
"type": "git",
|
"type": "git",
|
||||||
"url": "https://git.mosaicstack.dev/mosaic/mosaic-stack.git",
|
"url": "https://git.mosaicstack.dev/mosaicstack/mosaic-stack.git",
|
||||||
"directory": "plugins/discord"
|
"directory": "plugins/discord"
|
||||||
},
|
},
|
||||||
"main": "dist/index.js",
|
"main": "dist/index.js",
|
||||||
@@ -31,7 +31,7 @@
|
|||||||
"vitest": "^2.0.0"
|
"vitest": "^2.0.0"
|
||||||
},
|
},
|
||||||
"publishConfig": {
|
"publishConfig": {
|
||||||
"registry": "https://git.mosaicstack.dev/api/packages/mosaic/npm/",
|
"registry": "https://git.mosaicstack.dev/api/packages/mosaicstack/npm/",
|
||||||
"access": "public"
|
"access": "public"
|
||||||
},
|
},
|
||||||
"files": [
|
"files": [
|
||||||
|
|||||||
@@ -3,7 +3,7 @@
|
|||||||
"version": "0.0.2",
|
"version": "0.0.2",
|
||||||
"repository": {
|
"repository": {
|
||||||
"type": "git",
|
"type": "git",
|
||||||
"url": "https://git.mosaicstack.dev/mosaic/mosaic-stack.git",
|
"url": "https://git.mosaicstack.dev/mosaicstack/mosaic-stack.git",
|
||||||
"directory": "plugins/macp"
|
"directory": "plugins/macp"
|
||||||
},
|
},
|
||||||
"type": "module",
|
"type": "module",
|
||||||
@@ -23,7 +23,7 @@
|
|||||||
"openclaw": "*"
|
"openclaw": "*"
|
||||||
},
|
},
|
||||||
"publishConfig": {
|
"publishConfig": {
|
||||||
"registry": "https://git.mosaicstack.dev/api/packages/mosaic/npm/",
|
"registry": "https://git.mosaicstack.dev/api/packages/mosaicstack/npm/",
|
||||||
"access": "public"
|
"access": "public"
|
||||||
},
|
},
|
||||||
"files": [
|
"files": [
|
||||||
|
|||||||
@@ -3,7 +3,7 @@
|
|||||||
"version": "0.0.2",
|
"version": "0.0.2",
|
||||||
"repository": {
|
"repository": {
|
||||||
"type": "git",
|
"type": "git",
|
||||||
"url": "https://git.mosaicstack.dev/mosaic/mosaic-stack.git",
|
"url": "https://git.mosaicstack.dev/mosaicstack/mosaic-stack.git",
|
||||||
"directory": "plugins/mosaic-framework"
|
"directory": "plugins/mosaic-framework"
|
||||||
},
|
},
|
||||||
"type": "module",
|
"type": "module",
|
||||||
@@ -18,7 +18,7 @@
|
|||||||
"openclaw": "*"
|
"openclaw": "*"
|
||||||
},
|
},
|
||||||
"publishConfig": {
|
"publishConfig": {
|
||||||
"registry": "https://git.mosaicstack.dev/api/packages/mosaic/npm/",
|
"registry": "https://git.mosaicstack.dev/api/packages/mosaicstack/npm/",
|
||||||
"access": "public"
|
"access": "public"
|
||||||
},
|
},
|
||||||
"files": [
|
"files": [
|
||||||
|
|||||||
@@ -3,7 +3,7 @@
|
|||||||
"version": "0.0.2",
|
"version": "0.0.2",
|
||||||
"repository": {
|
"repository": {
|
||||||
"type": "git",
|
"type": "git",
|
||||||
"url": "https://git.mosaicstack.dev/mosaic/mosaic-stack.git",
|
"url": "https://git.mosaicstack.dev/mosaicstack/mosaic-stack.git",
|
||||||
"directory": "plugins/telegram"
|
"directory": "plugins/telegram"
|
||||||
},
|
},
|
||||||
"main": "dist/index.js",
|
"main": "dist/index.js",
|
||||||
@@ -29,7 +29,7 @@
|
|||||||
"telegraf": "^4.16.3"
|
"telegraf": "^4.16.3"
|
||||||
},
|
},
|
||||||
"publishConfig": {
|
"publishConfig": {
|
||||||
"registry": "https://git.mosaicstack.dev/api/packages/mosaic/npm/",
|
"registry": "https://git.mosaicstack.dev/api/packages/mosaicstack/npm/",
|
||||||
"access": "public"
|
"access": "public"
|
||||||
},
|
},
|
||||||
"files": [
|
"files": [
|
||||||
|
|||||||
@@ -6,10 +6,10 @@
|
|||||||
# 2. @mosaicstack/mosaic (npm) → ~/.npm-global/ (CLI, TUI, gateway client, wizard)
|
# 2. @mosaicstack/mosaic (npm) → ~/.npm-global/ (CLI, TUI, gateway client, wizard)
|
||||||
#
|
#
|
||||||
# Remote install (recommended):
|
# Remote install (recommended):
|
||||||
# bash <(curl -fsSL https://git.mosaicstack.dev/mosaic/mosaic-stack/raw/branch/main/tools/install.sh)
|
# bash <(curl -fsSL https://git.mosaicstack.dev/mosaicstack/mosaic-stack/raw/branch/main/tools/install.sh)
|
||||||
#
|
#
|
||||||
# Remote install (alternative — use -s -- to pass flags):
|
# Remote install (alternative — use -s -- to pass flags):
|
||||||
# curl -fsSL https://git.mosaicstack.dev/mosaic/mosaic-stack/raw/branch/main/tools/install.sh | bash -s --
|
# curl -fsSL https://git.mosaicstack.dev/mosaicstack/mosaic-stack/raw/branch/main/tools/install.sh | bash -s --
|
||||||
#
|
#
|
||||||
# Flags:
|
# Flags:
|
||||||
# --check Version check only, no install
|
# --check Version check only, no install
|
||||||
@@ -50,11 +50,11 @@ done
|
|||||||
|
|
||||||
# ─── constants ────────────────────────────────────────────────────────────────
|
# ─── constants ────────────────────────────────────────────────────────────────
|
||||||
MOSAIC_HOME="${MOSAIC_HOME:-$HOME/.config/mosaic}"
|
MOSAIC_HOME="${MOSAIC_HOME:-$HOME/.config/mosaic}"
|
||||||
REGISTRY="${MOSAIC_REGISTRY:-https://git.mosaicstack.dev/api/packages/mosaic/npm/}"
|
REGISTRY="${MOSAIC_REGISTRY:-https://git.mosaicstack.dev/api/packages/mosaicstack/npm/}"
|
||||||
SCOPE="${MOSAIC_SCOPE:-@mosaicstack}"
|
SCOPE="${MOSAIC_SCOPE:-@mosaicstack}"
|
||||||
PREFIX="${MOSAIC_PREFIX:-$HOME/.npm-global}"
|
PREFIX="${MOSAIC_PREFIX:-$HOME/.npm-global}"
|
||||||
CLI_PKG="${SCOPE}/mosaic"
|
CLI_PKG="${SCOPE}/mosaic"
|
||||||
REPO_BASE="https://git.mosaicstack.dev/mosaic/mosaic-stack"
|
REPO_BASE="https://git.mosaicstack.dev/mosaicstack/mosaic-stack"
|
||||||
ARCHIVE_URL="${REPO_BASE}/archive/${GIT_REF}.tar.gz"
|
ARCHIVE_URL="${REPO_BASE}/archive/${GIT_REF}.tar.gz"
|
||||||
|
|
||||||
# ─── colours ──────────────────────────────────────────────────────────────────
|
# ─── colours ──────────────────────────────────────────────────────────────────
|
||||||
|
|||||||
Reference in New Issue
Block a user