jarvis-launcher 0.1.0-alpha.43
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/README.md +451 -0
- package/dist/cli.d.ts +3 -0
- package/dist/cli.js +153 -0
- package/dist/cli.js.map +1 -0
- package/dist/commands/backup.d.ts +53 -0
- package/dist/commands/backup.js +113 -0
- package/dist/commands/backup.js.map +1 -0
- package/dist/commands/enable.d.ts +24 -0
- package/dist/commands/enable.js +169 -0
- package/dist/commands/enable.js.map +1 -0
- package/dist/commands/init.d.ts +40 -0
- package/dist/commands/init.js +131 -0
- package/dist/commands/init.js.map +1 -0
- package/dist/commands/login.d.ts +42 -0
- package/dist/commands/login.js +62 -0
- package/dist/commands/login.js.map +1 -0
- package/dist/commands/registry-login.d.ts +20 -0
- package/dist/commands/registry-login.js +48 -0
- package/dist/commands/registry-login.js.map +1 -0
- package/dist/commands/restore.d.ts +53 -0
- package/dist/commands/restore.js +120 -0
- package/dist/commands/restore.js.map +1 -0
- package/dist/commands/start.d.ts +42 -0
- package/dist/commands/start.js +508 -0
- package/dist/commands/start.js.map +1 -0
- package/dist/commands/stop.d.ts +38 -0
- package/dist/commands/stop.js +56 -0
- package/dist/commands/stop.js.map +1 -0
- package/dist/commands/update.d.ts +34 -0
- package/dist/commands/update.js +83 -0
- package/dist/commands/update.js.map +1 -0
- package/dist/homepage/home.d.ts +94 -0
- package/dist/homepage/home.js +145 -0
- package/dist/homepage/home.js.map +1 -0
- package/dist/homepage/logo.d.ts +68 -0
- package/dist/homepage/logo.js +208 -0
- package/dist/homepage/logo.js.map +1 -0
- package/dist/homepage/menu.d.ts +123 -0
- package/dist/homepage/menu.js +222 -0
- package/dist/homepage/menu.js.map +1 -0
- package/dist/homepage/prompts.d.ts +196 -0
- package/dist/homepage/prompts.js +332 -0
- package/dist/homepage/prompts.js.map +1 -0
- package/dist/homepage/status-view.d.ts +15 -0
- package/dist/homepage/status-view.js +191 -0
- package/dist/homepage/status-view.js.map +1 -0
- package/dist/lib/arch.d.ts +50 -0
- package/dist/lib/arch.js +57 -0
- package/dist/lib/arch.js.map +1 -0
- package/dist/lib/backup.d.ts +101 -0
- package/dist/lib/backup.js +219 -0
- package/dist/lib/backup.js.map +1 -0
- package/dist/lib/compose.d.ts +70 -0
- package/dist/lib/compose.js +207 -0
- package/dist/lib/compose.js.map +1 -0
- package/dist/lib/config.d.ts +70 -0
- package/dist/lib/config.js +141 -0
- package/dist/lib/config.js.map +1 -0
- package/dist/lib/docker.d.ts +206 -0
- package/dist/lib/docker.js +272 -0
- package/dist/lib/docker.js.map +1 -0
- package/dist/lib/exec-error.d.ts +27 -0
- package/dist/lib/exec-error.js +58 -0
- package/dist/lib/exec-error.js.map +1 -0
- package/dist/lib/external-services.d.ts +46 -0
- package/dist/lib/external-services.js +201 -0
- package/dist/lib/external-services.js.map +1 -0
- package/dist/lib/health.d.ts +102 -0
- package/dist/lib/health.js +165 -0
- package/dist/lib/health.js.map +1 -0
- package/dist/lib/mongo.d.ts +124 -0
- package/dist/lib/mongo.js +134 -0
- package/dist/lib/mongo.js.map +1 -0
- package/dist/lib/ports.d.ts +92 -0
- package/dist/lib/ports.js +234 -0
- package/dist/lib/ports.js.map +1 -0
- package/dist/lib/profiles.d.ts +39 -0
- package/dist/lib/profiles.js +44 -0
- package/dist/lib/profiles.js.map +1 -0
- package/dist/lib/progress.d.ts +66 -0
- package/dist/lib/progress.js +79 -0
- package/dist/lib/progress.js.map +1 -0
- package/dist/lib/proxy-merge.d.ts +15 -0
- package/dist/lib/proxy-merge.js +282 -0
- package/dist/lib/proxy-merge.js.map +1 -0
- package/dist/lib/registry.d.ts +106 -0
- package/dist/lib/registry.js +179 -0
- package/dist/lib/registry.js.map +1 -0
- package/dist/lib/restore.d.ts +288 -0
- package/dist/lib/restore.js +534 -0
- package/dist/lib/restore.js.map +1 -0
- package/dist/lib/scenario.d.ts +83 -0
- package/dist/lib/scenario.js +150 -0
- package/dist/lib/scenario.js.map +1 -0
- package/dist/lib/seed-plan.d.ts +67 -0
- package/dist/lib/seed-plan.js +36 -0
- package/dist/lib/seed-plan.js.map +1 -0
- package/dist/lib/sql.d.ts +182 -0
- package/dist/lib/sql.js +286 -0
- package/dist/lib/sql.js.map +1 -0
- package/dist/lib/state.d.ts +148 -0
- package/dist/lib/state.js +178 -0
- package/dist/lib/state.js.map +1 -0
- package/dist/lib/status.d.ts +98 -0
- package/dist/lib/status.js +100 -0
- package/dist/lib/status.js.map +1 -0
- package/package.json +36 -0
- package/templates/docker-compose.test.ts +259 -0
- package/templates/docker-compose.yml +193 -0
package/README.md
ADDED
|
@@ -0,0 +1,451 @@
|
|
|
1
|
+
# jarvis-launcher
|
|
2
|
+
|
|
3
|
+
`jarvis-launcher` is a cross-platform command-line tool that prepares a **scenario folder** and runs the full Jarvis stack around the published all-in-one image. It is a thin wrapper over `docker` / `docker compose` (plus a short-lived `mongo` container used only for restores) — it never re-implements what those tools already do.
|
|
4
|
+
|
|
5
|
+
Your only inputs are a **folder** and a **command**. The launcher authors every compose / `.env` file for you, picks free host ports, provisions the base configuration out of the image, and (when a prepared dump is present) restores it before Jarvis starts serving.
|
|
6
|
+
|
|
7
|
+
---
|
|
8
|
+
|
|
9
|
+
## Prerequisites
|
|
10
|
+
|
|
11
|
+
You need exactly three things on the host:
|
|
12
|
+
|
|
13
|
+
1. **Docker** with **Compose v2** (`docker compose`, the plugin — not the old `docker-compose`). Docker Desktop on macOS/Windows or Docker Engine on Linux both work.
|
|
14
|
+
2. **Node.js ≥ 20 (LTS)** — required to install and run the CLI.
|
|
15
|
+
3. An **ACR repository-scoped token** for the integrator image registry `integratori-ggeybpb3azacdpfw.azurecr.io`. The token comes as a pair:
|
|
16
|
+
- the **token name** is the Docker login **username**;
|
|
17
|
+
- the **token password** is the **password**.
|
|
18
|
+
|
|
19
|
+
That is the whole floor. In particular you do **not** need:
|
|
20
|
+
|
|
21
|
+
- **No** host-side MongoDB tools (`mongosh`, `mongorestore`, …) — every Mongo operation runs inside a container.
|
|
22
|
+
- **No** Azure CLI — authentication is a plain `docker login` against the registry (see [`registry-login`](#registry-login)).
|
|
23
|
+
|
|
24
|
+
> **Apple Silicon (arm64):** the optional **SQL Server** service (`enable sql` / `start --sql`) runs only as an **x64** image — Microsoft ships no arm64 build of SQL Server — so on an Apple Silicon Mac it runs under emulation. Enable **"Use Rosetta for x86/amd64 emulation"** in Docker Desktop → *Settings → General* for it to start reliably; without it the QEMU fallback is slow and SQL Server can crash. The always-on core stack (Jarvis / Mongo / ES) runs natively on arm64 and is unaffected.
|
|
25
|
+
|
|
26
|
+
> **Note:** MongoDB, Elasticsearch and SQL Server data live on Docker **named volumes** (`<project>_mongo_data` / `<project>_es_data` / `<project>_sql_data`), not host bind-mounts. A macOS/Windows Docker Desktop bind-mount (grpcfuse) makes WiredTiger index builds fail intermittently with `Operation not permitted` (EPERM), which crashes Automation and wedges the stack; named volumes live on the Docker VM's native filesystem and avoid this. They also sidestep the raw-Linux non-root-UID bind-mount permission issue entirely (Docker initializes volume ownership for the container). Because the volumes survive `stop` (which never passes `-v`), a restored SQL database persists across a `stop`/`start` cycle — the seed itself only re-runs on a first start, a stale-volume replace, or an explicit `--sql` enable, never on a routine restart.
|
|
27
|
+
|
|
28
|
+
---
|
|
29
|
+
|
|
30
|
+
## Install
|
|
31
|
+
|
|
32
|
+
From a published package:
|
|
33
|
+
|
|
34
|
+
```bash
|
|
35
|
+
npm install -g jarvis-launcher
|
|
36
|
+
```
|
|
37
|
+
|
|
38
|
+
Or run it directly without installing, via `npx`:
|
|
39
|
+
|
|
40
|
+
```bash
|
|
41
|
+
npx jarvis-launcher <command>
|
|
42
|
+
```
|
|
43
|
+
|
|
44
|
+
Or, running from a checkout of this repository:
|
|
45
|
+
|
|
46
|
+
```bash
|
|
47
|
+
npm install
|
|
48
|
+
npm run build
|
|
49
|
+
```
|
|
50
|
+
|
|
51
|
+
This produces the `jarvis-launcher` executable (its bin is `dist/cli.js`). The examples below assume it is on your `PATH`; when running from source you can substitute `npm run dev --` for `jarvis-launcher`.
|
|
52
|
+
|
|
53
|
+
---
|
|
54
|
+
|
|
55
|
+
## Authenticate once: `registry-login`
|
|
56
|
+
|
|
57
|
+
The all-in-one image lives in a private ACR, so Docker must be authenticated before the launcher can pull it. Run this **once per machine**:
|
|
58
|
+
|
|
59
|
+
```bash
|
|
60
|
+
jarvis-launcher registry-login
|
|
61
|
+
```
|
|
62
|
+
|
|
63
|
+
You are prompted (masked) for the **token name** (username) and **token password**. The credential is stored by **Docker's own credential store** (`~/.docker/config.json` / your OS keychain) — the launcher persists no secret itself — so you are asked **only once per machine**. Subsequent commands that pull the image reuse the stored credential silently; if the credential is ever missing or rejected, `start`/`update` re-prompt automatically and retry once.
|
|
64
|
+
|
|
65
|
+
You can supply the credential non-interactively (e.g. in CI):
|
|
66
|
+
|
|
67
|
+
```bash
|
|
68
|
+
# via flags
|
|
69
|
+
jarvis-launcher registry-login --user <token-name> --password <token-password>
|
|
70
|
+
|
|
71
|
+
# or via environment variables
|
|
72
|
+
export JARVIS_REGISTRY_USER=<token-name>
|
|
73
|
+
export JARVIS_REGISTRY_PASSWORD=<token-password>
|
|
74
|
+
jarvis-launcher registry-login
|
|
75
|
+
```
|
|
76
|
+
|
|
77
|
+
The credential-source precedence is **flags → environment variables → masked prompt**. `--registry <host>` overrides the default registry host if needed.
|
|
78
|
+
|
|
79
|
+
> **`registry-login` is Docker registry authentication.** It is **not** the same as the Jarvis dev-[`login`](#login) command, which opens a browser impersonation session into a running Jarvis. Keep the two distinct.
|
|
80
|
+
|
|
81
|
+
---
|
|
82
|
+
|
|
83
|
+
## The scenario folder
|
|
84
|
+
|
|
85
|
+
A **scenario folder** is the single root the launcher operates on. It is **both** the source of the three base config files **and** the host data root for Jarvis-owned files (document store, etc.) via the image's `/scenario` bind-mount. MongoDB and Elasticsearch data, by contrast, live on Docker **named volumes** (see the note above). Everything **survives teardown and recreate**: `stop`/`update` remove containers but never the host data or the named volumes (only `docker compose down -v` would wipe the volumes).
|
|
86
|
+
|
|
87
|
+
The launcher creates the host data subfolders **idempotently** and **never wipes** existing content. Layout:
|
|
88
|
+
|
|
89
|
+
```
|
|
90
|
+
<scenario>/
|
|
91
|
+
jarvis-host-config.json # base config — provisioned by init/start (copied from the image)
|
|
92
|
+
jarvis.proxy.json # "
|
|
93
|
+
jarvis.automation.json # "
|
|
94
|
+
data/ # Jarvis-owned data (mounted as /scenario/data)
|
|
95
|
+
seed/ # restore artifact: an inner dump/ (mongo bson.gz) + document-store files
|
|
96
|
+
state.json # optional: pre-declared initial state — see "Pre-seeding the initial state"
|
|
97
|
+
dsdata/ # document-store tree (original/ artifacts/ avatars/ …) -> data/ds/*
|
|
98
|
+
# (legacy alternative: dstore/ -> data/ds/original)
|
|
99
|
+
automation/ # optional: Elsa automation assets -> data/automation
|
|
100
|
+
# (merged; a same-named sub-folder is replaced entirely)
|
|
101
|
+
sqlbackup/ # optional SQL Server .bak file(s)
|
|
102
|
+
externalservice_*/ # optional: your own companion service(s) — see "External services" below
|
|
103
|
+
.orchestrator/ # launcher-owned: state.json (persisted ports + project name) + generated .env
|
|
104
|
+
|
|
105
|
+
# Mongo, Elasticsearch & SQL Server data are NOT here — they live on Docker named volumes:
|
|
106
|
+
# <project>_mongo_data -> /data/db
|
|
107
|
+
# <project>_es_data -> /usr/share/elasticsearch/data
|
|
108
|
+
# <project>_sql_data -> /var/opt/mssql (only when the `sql` profile is active)
|
|
109
|
+
```
|
|
110
|
+
|
|
111
|
+
Notes:
|
|
112
|
+
|
|
113
|
+
- The three `jarvis-*.json` configs are **lifted out of the image** (`/jarvis/reference/`); there is no bundled offline fallback. If the image is unreachable, provisioning fails fast with a clear error.
|
|
114
|
+
- A **missing** config triggers auto-provisioning. A config that is **present but invalid** is **never overwritten** — the launcher fails fast so you can fix or remove it.
|
|
115
|
+
- There is **no `automationrepo/`** folder — git-backed automation repos are not wired; automation uses the in-container default.
|
|
116
|
+
- `.orchestrator/` is launcher-owned, regenerable state. Deleting it only means new host ports get chosen on the next `start`.
|
|
117
|
+
|
|
118
|
+
### Pre-seeding the initial state (`seed/state.json`)
|
|
119
|
+
|
|
120
|
+
An integrator packaging a scenario can ship a **`seed/state.json`** file to pre-declare the environment's **initial** state, so the first `init`/`start` picks up the right project name, optional profiles, and image tag without anyone having to remember the flags. It is read **only when the scenario has no `.orchestrator/state.json` yet** (a brand-new environment); once the scenario has been started, its own `.orchestrator/state.json` drives everything and the seed is ignored.
|
|
121
|
+
|
|
122
|
+
Only three properties are honored — anything else in the file (notably a `ports` block) is ignored, so you can copy a working environment's `.orchestrator/state.json` verbatim and only the portable bits are applied (fresh host ports are always re-allocated for the new machine):
|
|
123
|
+
|
|
124
|
+
```json
|
|
125
|
+
{
|
|
126
|
+
"projectName": "cmms",
|
|
127
|
+
"enabledProfiles": ["sql"],
|
|
128
|
+
"tag": "1.2.3"
|
|
129
|
+
}
|
|
130
|
+
```
|
|
131
|
+
|
|
132
|
+
| Property | Meaning | Default when omitted |
|
|
133
|
+
|---|---|---|
|
|
134
|
+
| `projectName` | the `docker compose -p` project name | derived from the folder name |
|
|
135
|
+
| `enabledProfiles` | optional profiles to turn on (`sql`, `mail`) | none |
|
|
136
|
+
| `tag` | base image tag to pin the scenario to | `latest` |
|
|
137
|
+
|
|
138
|
+
Explicit CLI flags always win over the seed: `--tag` overrides `tag`, and `--sql`/`--mail` are **added to** the seed's `enabledProfiles`.
|
|
139
|
+
|
|
140
|
+
In the interactive homepage, choosing **Start** on a brand-new scenario that ships a `seed/state.json` skips the initial questions (folder / tag / SQL / mail / force) and starts straight away — the seed already pre-declares that initial state, so there is nothing to ask.
|
|
141
|
+
|
|
142
|
+
---
|
|
143
|
+
|
|
144
|
+
## Quick start (brand-new scenario)
|
|
145
|
+
|
|
146
|
+
```bash
|
|
147
|
+
mkdir my-scenario && cd my-scenario
|
|
148
|
+
jarvis-launcher registry-login # once per machine, if not already done
|
|
149
|
+
jarvis-launcher start
|
|
150
|
+
```
|
|
151
|
+
|
|
152
|
+
`start` from an empty folder does everything for you:
|
|
153
|
+
|
|
154
|
+
1. scaffolds the data subfolders and **auto-provisions** the three base configs from the image;
|
|
155
|
+
2. **allocates a free, clustered block of host ports** and persists them (so the URL stays stable across restarts);
|
|
156
|
+
3. brings up `mongo` + `es`, waits until they are healthy;
|
|
157
|
+
4. **auto-restores** the prepared dump **if** one is present under `seed/` **and** the database is empty (an empty `seed/` or no seed at all is fine — Jarvis simply comes up with an empty database; a populated DB is left untouched — restore only on `--force`);
|
|
158
|
+
5. starts `jarvis`, waits for the proxy to answer, then prints the access URLs and the dev-login links.
|
|
159
|
+
|
|
160
|
+
After a restore, Elasticsearch repopulates in the background; the launcher prints a banner and best-effort indexing progress so you know search may be briefly incomplete.
|
|
161
|
+
|
|
162
|
+
---
|
|
163
|
+
|
|
164
|
+
## Commands
|
|
165
|
+
|
|
166
|
+
`<folder>` is an **optional** positional on every command — it defaults to the **current working directory**, so `cd`-ing into the scenario folder and omitting it is the common case.
|
|
167
|
+
|
|
168
|
+
| Command | Synopsis | What it does |
|
|
169
|
+
|---|---|---|
|
|
170
|
+
| [`init`](#init) | `init [folder]` | Provision the three base config files into an empty/config-less folder. |
|
|
171
|
+
| [`registry-login`](#registry-login) | `registry-login [--registry <host>] [--user <name>] [--password <pwd>]` | Authenticate Docker against the image registry (once per machine). |
|
|
172
|
+
| [`start`](#start) | `start [folder] [--sql] [--mail] [--force]` | Bring up the full stack (auto-init + auto-restore) and report the URLs. |
|
|
173
|
+
| [`stop`](#stop) | `stop [folder]` | Stop the stack (remove containers) while keeping all on-disk data. |
|
|
174
|
+
| [`restore`](#restore) | `restore [folder] [--force]` | Restore the prepared artifact into the running stack (gated by `--force`). |
|
|
175
|
+
| [`update`](#update) | `update [folder] --tag <T>` | Pull a new image tag and recreate the stack on it, preserving data. |
|
|
176
|
+
| [`enable`](#enable) | `enable [folder] <sql\|mail>` | Attach an optional service (SQL Server or mail catcher) to the stack. |
|
|
177
|
+
| [`login`](#login) | `login [folder] [--admin\|--user]` | Open the browser at the Jarvis dev-login (impersonation) URL. |
|
|
178
|
+
|
|
179
|
+
### `init`
|
|
180
|
+
|
|
181
|
+
```bash
|
|
182
|
+
jarvis-launcher init [folder]
|
|
183
|
+
```
|
|
184
|
+
|
|
185
|
+
Resolves the folder (default: CWD), scaffolds the data subfolders, and copies any **absent** base config out of the image's `/jarvis/reference/`. It is only-if-absent: when all three configs already exist it is a no-op and the image is not even pulled. A present-but-invalid config is never overwritten — it fails fast instead.
|
|
186
|
+
|
|
187
|
+
You rarely need `init` on its own — `start` performs the same provisioning automatically — but it is useful to lay down (and review) the configs before the first `start`.
|
|
188
|
+
|
|
189
|
+
### `registry-login`
|
|
190
|
+
|
|
191
|
+
```bash
|
|
192
|
+
jarvis-launcher registry-login [--registry <host>] [--user <token-name>] [--password <token-password>]
|
|
193
|
+
```
|
|
194
|
+
|
|
195
|
+
Forces a fresh `docker login` against the integrator registry so you can pre-seed or refresh the stored credential. See [Authenticate once](#authenticate-once-registry-login) above for the credential sources and the once-per-machine behavior.
|
|
196
|
+
|
|
197
|
+
### `start`
|
|
198
|
+
|
|
199
|
+
```bash
|
|
200
|
+
jarvis-launcher start [folder] [--sql] [--mail] [--force]
|
|
201
|
+
```
|
|
202
|
+
|
|
203
|
+
Brings up the full, logged-in environment in one command. See [Quick start](#quick-start-brand-new-scenario) for the brand-new-folder flow.
|
|
204
|
+
|
|
205
|
+
| Flag | Effect |
|
|
206
|
+
|---|---|
|
|
207
|
+
| `--sql` | Also start the optional **SQL Server** service. If a `.bak` is present under `<folder>/seed/sqlbackup`, it is restored after the SQL container is healthy. |
|
|
208
|
+
| `--mail` | Also start the optional **mail catcher** (catch-all SMTP + web UI). |
|
|
209
|
+
| `--force` | Run the restore **even if the database already has data** (drops the existing data first). Without it, a populated DB is left as-is. |
|
|
210
|
+
|
|
211
|
+
On success it prints the Web UI / API URL, both dev-login links, and the Mongo / ES (and optional SQL / mail) host ports. Enabled optional profiles are remembered, so a later plain `start` keeps them on.
|
|
212
|
+
|
|
213
|
+
### `stop`
|
|
214
|
+
|
|
215
|
+
```bash
|
|
216
|
+
jarvis-launcher stop [folder]
|
|
217
|
+
```
|
|
218
|
+
|
|
219
|
+
Removes the scenario's containers (including any optional `sql`/`mail` ones) while **keeping all on-disk data** — it never passes `-v`/`--volumes`. The next `start` sees the data and performs no restore. If the scenario was never started, it reports there is nothing to stop and exits non-zero.
|
|
220
|
+
|
|
221
|
+
### `restore`
|
|
222
|
+
|
|
223
|
+
```bash
|
|
224
|
+
jarvis-launcher restore [folder] [--force]
|
|
225
|
+
```
|
|
226
|
+
|
|
227
|
+
Explicitly reseeds Mongo / document-store / ES from the prepared artifact under `<folder>/seed`. It brings `mongo`+`es` up healthy first, then applies the gate:
|
|
228
|
+
|
|
229
|
+
- **`--force`** → restore regardless of DB contents (drops non-system DBs first, then replaces the data).
|
|
230
|
+
- **no `--force`, DB already populated** → **refuses** and changes nothing.
|
|
231
|
+
- **no `--force`, DB empty** → restores.
|
|
232
|
+
|
|
233
|
+
The artifact must live at `<folder>/seed/` and contain an inner `dump/` (the `mongorestore --gzip` payload). The document-store files are picked up from either `seed/dsdata/` — a full `data/ds` tree (`original/`, `artifacts/`, `avatars/`, …), copied subfolder-by-subfolder into `data/ds/*` — or, for backward compatibility, the legacy `seed/dstore/` (moved into `data/ds/original`). A missing/incomplete `dump/` aborts cleanly with a clear message; if neither document-store folder is present the restore proceeds but warns that stored blobs will be missing their files. If the artifact carries a `seed/automation/` folder, its contents are moved into `data/automation` (the container's `/scenario/data/automation`, Elsa's `AssetsLocation`): a top-level merge that overwrites same-named files, replaces a same-named sub-folder entirely, and leaves unrelated existing assets in place.
|
|
234
|
+
|
|
235
|
+
### `update`
|
|
236
|
+
|
|
237
|
+
```bash
|
|
238
|
+
jarvis-launcher update [folder] --tag <tag>
|
|
239
|
+
```
|
|
240
|
+
|
|
241
|
+
Moves the stack to a new image tag, **preserving all data and without restoring**. It is **pull-first** so you are never left with no runnable environment:
|
|
242
|
+
|
|
243
|
+
1. resolves the architecture-correct image variant (a `-arm` suffix is added automatically on ARM hosts) and **pulls it first** (re-authenticating + retrying once if needed);
|
|
244
|
+
2. **only if the pull succeeds**, recreates the stack on the new image (down → up, data kept via the `/scenario` host mount and the Mongo/ES named volumes) — **no restore**, the existing data is reused;
|
|
245
|
+
3. if the pull **fails** (typo, offline, unpublished tag, rejected credentials), it reports the error and **leaves the running stack untouched**.
|
|
246
|
+
|
|
247
|
+
`--tag` is required (e.g. `--tag latest` or `--tag 1.2.3`).
|
|
248
|
+
|
|
249
|
+
### `enable`
|
|
250
|
+
|
|
251
|
+
```bash
|
|
252
|
+
jarvis-launcher enable [folder] <sql|mail>
|
|
253
|
+
```
|
|
254
|
+
|
|
255
|
+
Attaches one optional service to the scenario and remembers it for future starts:
|
|
256
|
+
|
|
257
|
+
- **`sql`** — starts the SQL Server service. When `<folder>/seed/sqlbackup` contains a `.bak`, it is restored into the database; with no `.bak`, the container is started and reachable but not seeded.
|
|
258
|
+
- **`mail`** — starts a catch-all mail catcher (Mailpit); all outgoing mail is captured and viewable in its web UI. No seed step is needed.
|
|
259
|
+
|
|
260
|
+
`enable` attaches **only** the requested service — it does not recreate the always-on `jarvis`/`mongo`/`es` containers. If the stack is not running yet, the profile is persisted and activates on the next `start`. (Enabling a service is equivalent to passing `--sql`/`--mail` to `start`.)
|
|
261
|
+
|
|
262
|
+
### `login`
|
|
263
|
+
|
|
264
|
+
```bash
|
|
265
|
+
jarvis-launcher login [folder] [--admin|--user]
|
|
266
|
+
```
|
|
267
|
+
|
|
268
|
+
Opens your browser at the Jarvis **dev-login (impersonation)** URL through the scenario's persisted proxy port, so you can jump straight into a session:
|
|
269
|
+
|
|
270
|
+
- `--admin` (default) — log in as the admin account;
|
|
271
|
+
- `--user` — log in as the standard user account.
|
|
272
|
+
|
|
273
|
+
This works because the image is built as a non-production / dev environment with forced-login enabled. It is a Jarvis application convenience and is **distinct from [`registry-login`](#registry-login)** (Docker registry authentication). The stack must already be started (otherwise there is no persisted port and the command fails with a clear message).
|
|
274
|
+
|
|
275
|
+
---
|
|
276
|
+
|
|
277
|
+
## Optional services
|
|
278
|
+
|
|
279
|
+
Both optional services are off by default and can be turned on either at start time (`start --sql` / `start --mail`) or afterwards (`enable sql` / `enable mail`):
|
|
280
|
+
|
|
281
|
+
- **SQL Server (`sql`)** — for scenarios that need the relational side. Drop a prepared `.bak` into `<folder>/seed/sqlbackup` and it is restored automatically the first time SQL is brought up, when the service is enabled with `--sql`, or on a brand-new folder (only a seed, no `.orchestrator/state.json`) — in that last case the restore **replaces** any leftover data from a reused `<project>_sql_data` volume, without needing `--force`, mirroring the Mongo auto-restore. A routine restart never re-seeds, so the persisted database is left untouched. Without a `.bak` the server is simply available and empty.
|
|
282
|
+
- **Mail catcher (`mail`)** — a catch-all SMTP server with a web inbox (Mailpit). All mail Jarvis would send is intercepted there, so you can inspect notifications without a real mail server.
|
|
283
|
+
|
|
284
|
+
---
|
|
285
|
+
|
|
286
|
+
## External services
|
|
287
|
+
|
|
288
|
+
Any folder under `<folder>/seed/` whose name starts with `externalservice_`
|
|
289
|
+
is picked up automatically and brought up as an extra container alongside
|
|
290
|
+
the Jarvis stack — no compose file to hand-edit. This is for your own
|
|
291
|
+
companion services (a CMMS API, a mock third-party integration, …) that
|
|
292
|
+
Jarvis or its automation should be able to reach on the compose network.
|
|
293
|
+
It lives under `seed/`, grouped with the other integrator-supplied scenario
|
|
294
|
+
resources (`dsdata/`, `automation/`, `sqlbackup/`) — a top-level folder is
|
|
295
|
+
**not** picked up.
|
|
296
|
+
|
|
297
|
+
```
|
|
298
|
+
<scenario>/
|
|
299
|
+
seed/
|
|
300
|
+
externalservice_cmms.api/
|
|
301
|
+
Dockerfile # your own — built as-is, context = this folder
|
|
302
|
+
jarvis-service.json # tells the launcher how to wire the service in
|
|
303
|
+
... # the service's own source
|
|
304
|
+
```
|
|
305
|
+
|
|
306
|
+
`jarvis-service.json`:
|
|
307
|
+
|
|
308
|
+
```json
|
|
309
|
+
{
|
|
310
|
+
"name": "cmms-api",
|
|
311
|
+
"dockerfile": "Dockerfile",
|
|
312
|
+
"ports": ["8080:80"],
|
|
313
|
+
"environment": {
|
|
314
|
+
"ASPNETCORE_ENVIRONMENT": "Development"
|
|
315
|
+
}
|
|
316
|
+
}
|
|
317
|
+
```
|
|
318
|
+
|
|
319
|
+
`Dockerfile` — entirely yours, built as-is (the launcher only sets `build:
|
|
320
|
+
{ context: <folder>, dockerfile: <this file> }`, nothing else). A typical
|
|
321
|
+
.NET example:
|
|
322
|
+
|
|
323
|
+
```dockerfile
|
|
324
|
+
# Build stage
|
|
325
|
+
FROM mcr.microsoft.com/dotnet/sdk:10.0 AS build
|
|
326
|
+
WORKDIR /externalservice_cmms.api/src
|
|
327
|
+
|
|
328
|
+
COPY . .
|
|
329
|
+
RUN dotnet publish -c Release -r linux-x64 --self-contained false -o /externalservice_cmms.api/app/publish
|
|
330
|
+
|
|
331
|
+
# Runtime stage
|
|
332
|
+
FROM mcr.microsoft.com/dotnet/aspnet:10.0
|
|
333
|
+
WORKDIR /externalservice_cmms.api/app
|
|
334
|
+
|
|
335
|
+
COPY --from=build /externalservice_cmms.api/app/publish .
|
|
336
|
+
|
|
337
|
+
ENTRYPOINT ["dotnet", "Cmms.Api.dll"]
|
|
338
|
+
```
|
|
339
|
+
|
|
340
|
+
All fields are optional:
|
|
341
|
+
|
|
342
|
+
- **`name`** — the compose service name. Defaults to the folder name with
|
|
343
|
+
the `externalservice_` prefix stripped and sanitized (e.g.
|
|
344
|
+
`externalservice_cmms.api` → `cmms.api`).
|
|
345
|
+
- **`dockerfile`** — path relative to the folder. Defaults to `Dockerfile`.
|
|
346
|
+
- **`ports`** — `"host:container"` strings, published exactly as given
|
|
347
|
+
(host ports are **not** auto-allocated the way `sql`/`mail` are — pick a
|
|
348
|
+
port that's free on your machine). Omit for a service that's only reached
|
|
349
|
+
from inside the compose network, by service name.
|
|
350
|
+
- **`environment`** — a flat map of environment variables passed to the
|
|
351
|
+
container as-is.
|
|
352
|
+
|
|
353
|
+
### Exposing an external service through the Jarvis proxy
|
|
354
|
+
|
|
355
|
+
An external service is reachable from `jarvis` over the compose network at
|
|
356
|
+
`http://<service-name>:<container-port>/` (the compose service `name` from
|
|
357
|
+
`jarvis-service.json`, and the port your app listens on **inside** the
|
|
358
|
+
container — never the host-published one, and never `localhost`, which
|
|
359
|
+
inside the `jarvis` container means the `jarvis` container itself).
|
|
360
|
+
|
|
361
|
+
To route Jarvis's reverse proxy at that address, drop a `jarvis.proxy.json`
|
|
362
|
+
**fragment** next to `Dockerfile`/`jarvis-service.json` in the same
|
|
363
|
+
`externalservice_*` folder:
|
|
364
|
+
|
|
365
|
+
```
|
|
366
|
+
<scenario>/
|
|
367
|
+
seed/
|
|
368
|
+
externalservice_cmms.api/
|
|
369
|
+
Dockerfile
|
|
370
|
+
jarvis-service.json
|
|
371
|
+
jarvis.proxy.json # NEW — proxy wiring fragment (optional)
|
|
372
|
+
```
|
|
373
|
+
|
|
374
|
+
```json
|
|
375
|
+
{
|
|
376
|
+
"ReverseProxy": {
|
|
377
|
+
"Clusters": {
|
|
378
|
+
"jarvis-cmms": {
|
|
379
|
+
"Destinations": {
|
|
380
|
+
"jarvis-cmms/destination1": {
|
|
381
|
+
"Address": "http://cmms-api:5200/"
|
|
382
|
+
}
|
|
383
|
+
}
|
|
384
|
+
}
|
|
385
|
+
}
|
|
386
|
+
},
|
|
387
|
+
"ReverseProxyDynamicRoutes": {
|
|
388
|
+
"Routes": {
|
|
389
|
+
"cmms": {
|
|
390
|
+
"ClusterId": "jarvis-cmms",
|
|
391
|
+
"AuthorizationPolicy": "JarvisProxy",
|
|
392
|
+
"Match": { "Path": "/jarvis-cmms/{**catchall}" },
|
|
393
|
+
"Transforms": [{ "PathPattern": "/{**catchall}" }]
|
|
394
|
+
}
|
|
395
|
+
}
|
|
396
|
+
}
|
|
397
|
+
}
|
|
398
|
+
```
|
|
399
|
+
|
|
400
|
+
The launcher merges this into the scenario's live `<folder>/jarvis.proxy.json`
|
|
401
|
+
on every `start`/`update` (`lib/proxy-merge.ts`). Only three dict paths are
|
|
402
|
+
ever touched: `ReverseProxy.Routes`, `ReverseProxy.Clusters`,
|
|
403
|
+
`ReverseProxyDynamicRoutes.Routes` — everything else in that file (the base
|
|
404
|
+
routes/clusters, anything you added by hand) is left alone. Rules:
|
|
405
|
+
|
|
406
|
+
- an entry is only **added** the first time it appears — it's never
|
|
407
|
+
overwritten if something with the same id already exists there with
|
|
408
|
+
different content (you get a warning instead, so a name clash never
|
|
409
|
+
silently clobbers your own edits);
|
|
410
|
+
- removing (or renaming) the `externalservice_*` folder removes the entries
|
|
411
|
+
it added on the next `start`/`update` — but only if you haven't hand-edited
|
|
412
|
+
them since (otherwise they're left in place with a warning);
|
|
413
|
+
- `jarvis.proxy.json` at the scenario root stays the file you provisioned
|
|
414
|
+
once and can still edit directly — this only ever *adds to* it, and only
|
|
415
|
+
through the fragment above.
|
|
416
|
+
|
|
417
|
+
The launcher scans for the compose overlay and (re)generates
|
|
418
|
+
`.orchestrator/external-services.yml` on every `start`/`stop`/`update` — like
|
|
419
|
+
`.orchestrator/state.json` and the `.env`, this file is launcher-owned and
|
|
420
|
+
regenerable; never hand-edit it. A folder that's missing its `Dockerfile`,
|
|
421
|
+
missing/malformed `jarvis-service.json`, or whose resolved name collides with
|
|
422
|
+
another service (including the core `jarvis`/`mongo`/`es`/`sql`/`mail`
|
|
423
|
+
names) is **skipped with a warning** — it never blocks the rest of the stack
|
|
424
|
+
from starting. Removing or renaming the folder and running `start`/`stop`
|
|
425
|
+
again tears its container down.
|
|
426
|
+
|
|
427
|
+
Images are built the first time the service comes up; a later code change to
|
|
428
|
+
the service is **not** picked up automatically on the next `start` — rebuild
|
|
429
|
+
explicitly (e.g. `docker compose -p <project> build <service>`) or remove the
|
|
430
|
+
built image first.
|
|
431
|
+
|
|
432
|
+
---
|
|
433
|
+
|
|
434
|
+
## Build and test (developing the launcher)
|
|
435
|
+
|
|
436
|
+
```bash
|
|
437
|
+
npm install # install dependencies
|
|
438
|
+
npm run build # tsc → dist/ (this is the "does it compile" gate)
|
|
439
|
+
npm test # run the vitest unit suite
|
|
440
|
+
```
|
|
441
|
+
|
|
442
|
+
Docker/Mongo/ES-driven behavior (real `compose up/down`, copy-from-image, live restore, real `docker login`) is covered by integration/manual checks that require Docker and the published image; the unit suite covers the pure logic.
|
|
443
|
+
|
|
444
|
+
---
|
|
445
|
+
|
|
446
|
+
## Troubleshooting
|
|
447
|
+
|
|
448
|
+
- **`unauthorized` / `401` when pulling** — your Docker credential for the registry is missing or expired. Run `jarvis-launcher registry-login` again (or supply `--user`/`--password`).
|
|
449
|
+
- **"Refusing to restore: data already exists"** — the database is already populated. Re-run `restore` with `--force` to drop and reload, or leave it as-is.
|
|
450
|
+
- **Port already in use** — the launcher re-probes persisted ports on every `start` and re-allocates a fresh block if one is taken, reporting the new proxy URL. There is a tiny window between picking a port and `compose up`; if a true conflict is hit, you get a clear bind-conflict error rather than a hang.
|
|
451
|
+
- **"Nothing to stop / no started stack"** — that scenario was never started (no `.orchestrator/state.json`). Run `start` first.
|
package/dist/cli.d.ts
ADDED
package/dist/cli.js
ADDED
|
@@ -0,0 +1,153 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
import { realpathSync } from 'node:fs';
|
|
3
|
+
import { createRequire } from 'node:module';
|
|
4
|
+
import { fileURLToPath } from 'node:url';
|
|
5
|
+
import { Command, CommanderError } from 'commander';
|
|
6
|
+
import { registerInit } from './commands/init.js';
|
|
7
|
+
import { registerRegistryLoginCommand } from './commands/registry-login.js';
|
|
8
|
+
import { registerStartCommand } from './commands/start.js';
|
|
9
|
+
import { registerStopCommand } from './commands/stop.js';
|
|
10
|
+
import { registerRestoreCommand } from './commands/restore.js';
|
|
11
|
+
import { registerBackupCommand } from './commands/backup.js';
|
|
12
|
+
import { loginCommand } from './commands/login.js';
|
|
13
|
+
import { update } from './commands/update.js';
|
|
14
|
+
import { enable } from './commands/enable.js';
|
|
15
|
+
import { OPTIONAL_PROFILES, isOptionalProfile } from './lib/profiles.js';
|
|
16
|
+
import { runHomepage } from './homepage/home.js';
|
|
17
|
+
// Read version from package.json at runtime. Using createRequire keeps this
|
|
18
|
+
// compatible with NodeNext ESM without relying on a JSON import assertion
|
|
19
|
+
// (which would also pull package.json into the emitted dist/ tree via rootDir).
|
|
20
|
+
const require = createRequire(import.meta.url);
|
|
21
|
+
const pkg = require('../package.json');
|
|
22
|
+
const program = new Command();
|
|
23
|
+
program
|
|
24
|
+
.name('jarvis-launcher')
|
|
25
|
+
.description('Prepare a scenario folder and run the full Jarvis all-in-one stack via docker compose.')
|
|
26
|
+
.version(pkg.version);
|
|
27
|
+
// --- subcommand registration (T-2-23) ---------------------------------------
|
|
28
|
+
//
|
|
29
|
+
// The command modules were built in parallel and expose intentionally
|
|
30
|
+
// different shapes. `cli.ts` is the single place that knows about all of them and
|
|
31
|
+
// reconciles those shapes onto one program (commands never import each other):
|
|
32
|
+
//
|
|
33
|
+
// * `register…Command(program)` builders register themselves and return nothing
|
|
34
|
+
// useful to us — call them for their side effect: init, registry-login, start,
|
|
35
|
+
// stop, restore, backup.
|
|
36
|
+
// * `loginCommand()` returns a ready-made `Command` instance → `addCommand`.
|
|
37
|
+
// * `update` / `enable` export only an action function (no builder), so we
|
|
38
|
+
// declare the `program.command(...)` surface here and call the action.
|
|
39
|
+
registerInit(program);
|
|
40
|
+
registerRegistryLoginCommand(program);
|
|
41
|
+
registerStartCommand(program);
|
|
42
|
+
registerStopCommand(program);
|
|
43
|
+
registerRestoreCommand(program);
|
|
44
|
+
registerBackupCommand(program);
|
|
45
|
+
program.addCommand(loginCommand());
|
|
46
|
+
// `update [folder] --tag <tag>` — the action fn lives in commands/update.ts. The
|
|
47
|
+
// tag is required (the update is meaningless without a target image tag).
|
|
48
|
+
program
|
|
49
|
+
.command('update')
|
|
50
|
+
.description('Pull a new image tag and recreate the stack on it, preserving all data.')
|
|
51
|
+
.argument('[folder]', 'scenario folder (defaults to the current directory)')
|
|
52
|
+
.requiredOption('--tag <tag>', 'the image tag to update to (e.g. latest, 1.2.3)')
|
|
53
|
+
.action(async (folder, opts) => {
|
|
54
|
+
await update({ folder, tag: opts.tag });
|
|
55
|
+
});
|
|
56
|
+
// `enable [folder] <service>` — `service` is `sql` | `mail`. The action fn lives
|
|
57
|
+
// in commands/enable.ts; we validate the positional here so an unknown service
|
|
58
|
+
// yields a clear message + non-zero exit (mapped by the dispatcher below).
|
|
59
|
+
program
|
|
60
|
+
.command('enable')
|
|
61
|
+
.description('Attach an optional service (sql | mail) to a scenario stack.')
|
|
62
|
+
.argument('[folder]', 'scenario folder (defaults to the current directory)')
|
|
63
|
+
.argument('<service>', 'optional service to enable: sql or mail')
|
|
64
|
+
.action(async (folder, service) => {
|
|
65
|
+
if (!isOptionalProfile(service)) {
|
|
66
|
+
throw new Error(`Unknown service "${service}". Expected one of: ${OPTIONAL_PROFILES.join(', ')}.`);
|
|
67
|
+
}
|
|
68
|
+
await enable({ folder, service });
|
|
69
|
+
});
|
|
70
|
+
// `help [command]` — print general usage, or the usage of a single command. The
|
|
71
|
+
// argument is optional; with no argument it behaves like the top-level help.
|
|
72
|
+
program
|
|
73
|
+
.command('help [command]')
|
|
74
|
+
.description('Display help for the CLI or for a specific command.')
|
|
75
|
+
.action((commandName) => {
|
|
76
|
+
if (!commandName) {
|
|
77
|
+
program.outputHelp();
|
|
78
|
+
return;
|
|
79
|
+
}
|
|
80
|
+
const sub = program.commands.find((c) => c.name() === commandName);
|
|
81
|
+
if (!sub) {
|
|
82
|
+
throw new Error(`Unknown command "${commandName}". Run "jarvis-launcher help" to list commands.`);
|
|
83
|
+
}
|
|
84
|
+
sub.outputHelp();
|
|
85
|
+
});
|
|
86
|
+
// --- no-args TTY dispatch (FR-HOME-001/002/003, SC-HOME-001/007) -------------
|
|
87
|
+
//
|
|
88
|
+
// "No subcommand" = only the node binary + this script path (`argv.length <= 2`).
|
|
89
|
+
// The interactive homepage may only launch when BOTH stdin and stdout are real
|
|
90
|
+
// TTYs (R-HOME5): a stdout-piped-but-stdin-a-TTY invocation must NOT hang waiting
|
|
91
|
+
// on a keypress, so requiring both keeps the non-interactive path on the
|
|
92
|
+
// help+exit branch below. Pure and exported so the three branches are unit-tested
|
|
93
|
+
// with no TTY (see cli.test.ts).
|
|
94
|
+
export function shouldRunHomepage(argv, stdinIsTTY, stdoutIsTTY) {
|
|
95
|
+
return argv.length <= 2 && Boolean(stdinIsTTY && stdoutIsTTY);
|
|
96
|
+
}
|
|
97
|
+
export function isEntrypoint(modulePath, argv1, canonicalize = realpathSync.native) {
|
|
98
|
+
if (!argv1) {
|
|
99
|
+
return false;
|
|
100
|
+
}
|
|
101
|
+
try {
|
|
102
|
+
return canonicalize(modulePath) === canonicalize(argv1);
|
|
103
|
+
}
|
|
104
|
+
catch {
|
|
105
|
+
return modulePath === argv1;
|
|
106
|
+
}
|
|
107
|
+
}
|
|
108
|
+
// --- error → message + non-zero exit (FR-ORC-034/035/046) -------------------
|
|
109
|
+
//
|
|
110
|
+
// Every command's action is async; commander surfaces a rejected action promise
|
|
111
|
+
// out of `parseAsync`. We catch here and print only the error's `.message` —
|
|
112
|
+
// which the libs build to carry surfaced execa stderr verbatim — never a raw
|
|
113
|
+
// stack trace or an execa object dump. A `CommanderError` (e.g. a missing
|
|
114
|
+
// required option, or `--version`/`--help` exit) is commander's own controlled
|
|
115
|
+
// flow: it has already printed its message and chosen an exit code, so we honor
|
|
116
|
+
// that exit code instead of re-printing.
|
|
117
|
+
async function main() {
|
|
118
|
+
// No subcommand + a real interactive terminal ⇒ the always-on homepage
|
|
119
|
+
// (FR-HOME-001). Done BEFORE parseAsync so commander never sees the empty argv.
|
|
120
|
+
if (shouldRunHomepage(process.argv, Boolean(process.stdin.isTTY), Boolean(process.stdout.isTTY))) {
|
|
121
|
+
await runHomepage();
|
|
122
|
+
return;
|
|
123
|
+
}
|
|
124
|
+
// No subcommand but NOT interactive (piped / CI / non-TTY) ⇒ print help to
|
|
125
|
+
// stderr and exit non-zero, EXPLICITLY (FR-HOME-002, SC-HOME-007). Handled
|
|
126
|
+
// here rather than relying on commander's default no-args behavior so the
|
|
127
|
+
// help+non-zero guarantee can't regress with a commander version bump.
|
|
128
|
+
if (process.argv.length <= 2) {
|
|
129
|
+
program.outputHelp({ error: true });
|
|
130
|
+
process.exit(1);
|
|
131
|
+
}
|
|
132
|
+
// An explicit command ⇒ the existing dispatch path, completely unchanged
|
|
133
|
+
// (FR-HOME-003, FR-HOME-027), including the CommanderError → exit-code mapping.
|
|
134
|
+
try {
|
|
135
|
+
await program.parseAsync(process.argv);
|
|
136
|
+
}
|
|
137
|
+
catch (err) {
|
|
138
|
+
if (err instanceof CommanderError) {
|
|
139
|
+
process.exit(err.exitCode);
|
|
140
|
+
}
|
|
141
|
+
const message = err instanceof Error ? err.message : String(err);
|
|
142
|
+
console.error(`Error: ${message}`);
|
|
143
|
+
process.exit(1);
|
|
144
|
+
}
|
|
145
|
+
}
|
|
146
|
+
// Run only when executed as the program entry point (not when imported by a
|
|
147
|
+
// test). Comparing this module's file path to the invoked script (argv[1]) keeps
|
|
148
|
+
// `import { shouldRunHomepage }` from cli.test.ts side-effect-free, so importing
|
|
149
|
+
// the module never spawns the homepage or touches process.argv/exit.
|
|
150
|
+
if (isEntrypoint(fileURLToPath(import.meta.url), process.argv[1])) {
|
|
151
|
+
void main();
|
|
152
|
+
}
|
|
153
|
+
//# sourceMappingURL=cli.js.map
|
package/dist/cli.js.map
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"cli.js","sourceRoot":"","sources":["../src/cli.ts"],"names":[],"mappings":"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"}
|