@nitsan-ai/ragsuite-test 0.1.2 → 0.1.3

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 CHANGED
@@ -1,83 +1,100 @@
1
1
  # RAGSuite test CLI (`ragsuite-test`)
2
2
 
3
- > **TESTING PACKAGE** — temporary. Final public CLI will be `ragsuite` later.
3
+ > **TESTING PACKAGE** — temporary name before the final `ragsuite` CLI.
4
4
 
5
- **One-command public install** (GitHub repo must be public):
5
+ Two ways to run. Pick one:
6
+
7
+ | | **A) Git source** | **B) Docker images only** |
8
+ |--|-------------------|---------------------------|
9
+ | Command | `ragsuite-test init` | `ragsuite-test init --from-images` |
10
+ | On disk | Full monorepo clone | Tiny compose bundle only |
11
+ | App code visible? | Yes | **No** (runs in containers) |
12
+ | Needs | git + Node; Docker *or* native Postgres/Redis | Node + **Docker** |
13
+ | Best for | Dev, debugging, native mode, reading code | Testers / “just run the app” |
14
+ | Pros | Native mode; full scripts; offline rebuild from source | No source leak; faster to start; smaller disk |
15
+ | Cons | Source on disk; larger checkout | Needs published GHCR images; Docker required; no native mode |
16
+
17
+ ---
18
+
19
+ ## A) Git source (clone)
6
20
 
7
21
  ```bash
8
22
  npm install -g @nitsan-ai/ragsuite-test
9
23
  ragsuite-test init
10
- ragsuite-test start --repo-root ~/ragsuite-test --detach
24
+ # clones https://github.com/nitsan-ai/RAGSUITE.git ~/ragsuite-test
25
+ # auto: docker if daemon up, else native
26
+
27
+ ragsuite-test start --repo-root ~/ragsuite-test --detach # docker
28
+ # or after init --mode native:
29
+ # ragsuite-test start --repo-root ~/ragsuite-test
30
+
31
+ # API http://localhost:9090
32
+ # Web http://localhost:9091 (docker) or :9191 (native)
33
+ ragsuite-test stop --repo-root ~/ragsuite-test
11
34
  ```
12
35
 
13
- That clones `https://github.com/nitsan-ai/RAGSUITE.git` into `~/ragsuite-test`, writes `.env` (no SMTP/LLM questions), and picks **Docker** if the daemon is up, otherwise **native**.
36
+ ### Native (no Docker) git install only
14
37
 
15
- - API: http://localhost:9090
16
- - Web (Docker): http://localhost:9091
17
- - Web (native): http://localhost:9191
38
+ Needs Postgres `:5436` (`ragsuite_v3`) and Redis `:6382`:
18
39
 
19
40
  ```bash
20
- ragsuite-test stop --repo-root ~/ragsuite-test
41
+ ragsuite-test init --mode native
42
+ ragsuite-test start --repo-root ~/ragsuite-test
21
43
  ```
22
44
 
23
- ## Prerequisites
45
+ ---
24
46
 
25
- | Need | Required? | How |
26
- |------|-----------|-----|
27
- | Node.js 18+ | Yes | https://nodejs.org · `brew install node` |
28
- | git | Yes | `xcode-select --install` · `brew install git` |
29
- | Docker | No | Optional; without it init uses native (needs Postgres `:5436` + Redis `:6382`) |
47
+ ## B) Docker images (no source on disk)
30
48
 
31
- Real SMTP belongs only in local `.env` (gitignored). Init never asks for it.
49
+ **Maintainer (once):** publish images Actions **Publish images (GHCR)**
50
+ (packages: `ghcr.io/nitsan-ai/ragsuite-backend`, `…/ragsuite-frontend`)
32
51
 
33
- ## Advanced (optional)
52
+ **User:**
34
53
 
35
54
  ```bash
36
- # Force native / docker
37
- ragsuite-test init --mode native
38
- ragsuite-test init --mode docker
55
+ npm install -g @nitsan-ai/ragsuite-test
56
+ ragsuite-test init --from-images
57
+ ragsuite-test start --repo-root ~/ragsuite-test --detach
58
+ # pulls images — does NOT clone app source
59
+ ragsuite-test stop --repo-root ~/ragsuite-test
60
+ ```
39
61
 
40
- # Custom install dir
41
- ragsuite-test init --install-dir ~/my-ragsuite
62
+ Optional pin:
42
63
 
43
- # Existing checkout (CI / developers)
44
- ragsuite-test init --repo-root /path/to/RAGSUITE
64
+ ```bash
65
+ # in ~/ragsuite-test/.env
66
+ IMAGE_TAG=1.0.0
67
+ IMAGE_REGISTRY=ghcr.io/nitsan-ai
45
68
  ```
46
69
 
70
+ If images are private on GHCR, make packages public or `docker login ghcr.io`.
71
+
72
+ ---
73
+
74
+ ## Prerequisites
75
+
76
+ | Need | Git install | Images install |
77
+ |------|-------------|----------------|
78
+ | Node 18+ | Yes | Yes |
79
+ | git | Yes | No |
80
+ | Docker | Optional | **Required** |
81
+ | Postgres/Redis on host | Only for `--mode native` | No (in compose) |
82
+
83
+ SMTP/LLM: no init prompts. Real SMTP only in local `.env` (gitignored).
84
+
47
85
  ## Ports
48
86
 
49
87
  | Service | Port |
50
88
  |---------|------|
51
89
  | API | 9090 |
52
- | Web (Docker) | 9091 |
90
+ | Web (Docker/images) | 9091 |
53
91
  | Web (native Expo) | 9191 |
54
92
  | Postgres | 5436 |
55
93
  | Redis | 6382 |
56
94
 
57
- Busy ports: `ragsuite-test stop --repo-root ~/ragsuite-test` · `lsof -nP -iTCP:9090 -sTCP:LISTEN`
58
-
59
- ## Commands
60
-
61
- | Command | Behavior |
62
- |---------|----------|
63
- | `init` | Clone public repo → `.env` + config (default) |
64
- | `start` / `stop` | Docker or native scripts |
65
- | `doctor` | Health / prereq hints |
66
- | `logs` / `update` / `version` | As before |
67
-
68
- ## Publish
69
-
70
- See [PUBLISH.md](./PUBLISH.md).
71
-
72
- ```bash
73
- cd cli && npm run prepublish:check
74
- # Only when approved:
75
- RAGSUITE_TEST_ALLOW_PUBLISH=1 npm publish --access public
76
- ```
77
-
78
- ## Windows
95
+ ## Publish CLI
79
96
 
80
- Use WSL or Git Bash.
97
+ See [PUBLISH.md](./PUBLISH.md). Version bump then Actions → **CLI publish**.
81
98
 
82
99
  ## Tests
83
100
 
package/package.json CHANGED
@@ -1,8 +1,8 @@
1
1
  {
2
2
  "name": "@nitsan-ai/ragsuite-test",
3
- "version": "0.1.2",
3
+ "version": "0.1.3",
4
4
  "private": false,
5
- "description": "RAGSuite testing CLI (ragsuite-test) public install: npm i -g && ragsuite-test init (clones GitHub). Temporary name before public ragsuite release.",
5
+ "description": "RAGSuite testing CLI — init (git source) or init --from-images (pull-only, no source).",
6
6
  "main": "src/index.js",
7
7
  "bin": {
8
8
  "ragsuite-test": "./bin/ragsuite-test.js"
@@ -10,6 +10,7 @@
10
10
  "files": [
11
11
  "bin/",
12
12
  "src/",
13
+ "templates/",
13
14
  "package.json",
14
15
  "README.md",
15
16
  "LICENSE"
@@ -2,22 +2,22 @@
2
2
 
3
3
  const path = require('path');
4
4
  const { resolveMode } = require('../utils/config');
5
- const { resolveRepoRoot } = require('../utils/paths');
5
+ const { resolveRepoRoot, isImagesInstall } = require('../utils/paths');
6
6
  const { runScript } = require('../utils/spawn');
7
7
 
8
8
  const name = 'doctor';
9
- const summary = 'Run scripts/doctor.sh prerequisite checks';
9
+ const summary = 'Run doctor checks (git or images install)';
10
10
 
11
11
  function help() {
12
12
  return `Usage: ragsuite-test doctor [options]
13
13
 
14
- Runs scripts/doctor.sh. Mode selects which checklist gates the exit code
15
- (RAGSUITE_MODE / config / --mode). Both mode sections are still printed.
14
+ Git install: scripts/doctor.sh
15
+ Images install: scripts/docker-doctor-images.sh
16
16
 
17
17
  Options:
18
- --mode <docker|native> Gate exit on this mode (default: docker)
19
- --repo-root <path> Monorepo root
20
- --dry-run Print the script path and exit 0
18
+ --mode <docker|native> Git install gate
19
+ --repo-root <path>
20
+ --dry-run
21
21
  `;
22
22
  }
23
23
 
@@ -26,12 +26,16 @@ function run(ctx) {
26
26
  cwd: ctx.cwd,
27
27
  repoRootFlag: ctx.globals.repoRoot,
28
28
  });
29
- const mode = resolveMode({
30
- flagMode: ctx.globals.mode,
31
- repoRoot,
32
- env: ctx.env,
33
- });
34
- return runScript(repoRoot, path.join('scripts', 'doctor.sh'), [], {
29
+ const images = isImagesInstall(repoRoot);
30
+ const mode = images
31
+ ? 'docker'
32
+ : resolveMode({
33
+ flagMode: ctx.globals.mode,
34
+ repoRoot,
35
+ env: ctx.env,
36
+ });
37
+ const script = images ? 'docker-doctor-images.sh' : 'doctor.sh';
38
+ return runScript(repoRoot, path.join('scripts', script), [], {
35
39
  dryRun: ctx.globals.dryRun,
36
40
  env: { ...ctx.env, RAGSUITE_MODE: mode },
37
41
  });
@@ -2,13 +2,18 @@
2
2
 
3
3
  const path = require('path');
4
4
  const { writeConfig, CONFIG_DIR } = require('../utils/config');
5
- const { ensureRagsuiteDir, resolveRepoRoot } = require('../utils/paths');
5
+ const {
6
+ ensureRagsuiteDir,
7
+ resolveRepoRoot,
8
+ isImagesInstall,
9
+ } = require('../utils/paths');
6
10
  const { assertSupportedInstall, installHint } = require('../utils/distribution');
7
11
  const {
8
12
  DEFAULT_GIT_URL,
9
13
  defaultInstallDir,
10
14
  cloneGitToInstallDir,
11
15
  } = require('../utils/git-install');
16
+ const { materializeImagesInstall } = require('../utils/images-install');
12
17
  const { detectOs } = require('../utils/os-detect');
13
18
  const {
14
19
  printPrereqs,
@@ -23,29 +28,29 @@ const { assertPortsFree } = require('../utils/port');
23
28
  const { info, error } = require('../utils/log');
24
29
 
25
30
  const name = 'init';
26
- const summary = 'One-command install: clone public GitHub repo .env + config';
31
+ const summary = 'Install: git clone (source) or pull-only Docker images';
27
32
 
28
- function printSuccess(repoRoot, mode) {
33
+ function printSuccess(repoRoot, mode, source) {
29
34
  info('');
30
35
  info('=== Install complete ===');
31
36
  info(` Install dir : ${repoRoot}`);
37
+ info(` Source : ${source}`);
32
38
  info(` Mode : ${mode}`);
33
39
  info(` Config : ${path.join(repoRoot, CONFIG_DIR, 'config.json')}`);
34
40
  info('');
35
41
  info(' API http://localhost:9090');
36
- if (mode === 'native') {
42
+ if (source === 'images') {
43
+ info(' Web http://localhost:9091 (images / Docker nginx)');
44
+ } else if (mode === 'native') {
37
45
  info(' Web http://localhost:9191 (Expo web — native mode)');
38
46
  } else {
39
47
  info(' Web http://localhost:9091 (Docker nginx)');
40
48
  }
41
49
  info('');
42
50
  info('Next steps:');
43
- info(` 1. ragsuite-test start --repo-root ${repoRoot}${mode === 'docker' ? ' --detach' : ''}`);
44
- if (mode === 'native') {
45
- info(' 2. Open http://localhost:9191 and bootstrap the first admin');
46
- } else {
47
- info(' 2. Open http://localhost:9091 and bootstrap the first admin');
48
- }
51
+ const detach = mode === 'docker' || source === 'images' ? ' --detach' : '';
52
+ info(` 1. ragsuite-test start --repo-root ${repoRoot}${detach}`);
53
+ info(' 2. Open the Web URL above and bootstrap the first admin');
49
54
  info(` 3. ragsuite-test doctor --repo-root ${repoRoot}`);
50
55
  info('');
51
56
  info(installHint());
@@ -54,23 +59,27 @@ function printSuccess(repoRoot, mode) {
54
59
  function help() {
55
60
  return `Usage: ragsuite-test init [options]
56
61
 
57
- Simple public install (default — no questions):
58
- clones ${DEFAULT_GIT_URL}
59
- into ~/ragsuite-test
60
- picks docker mode if Docker is running, otherwise native
61
- writes .env (JWT auto; SMTP/LLM from .env.example — no prompts)
62
+ Two install styles:
62
63
 
63
- ragsuite-test init
64
- ragsuite-test start --repo-root ~/ragsuite-test --detach
64
+ A) Source on disk (git) — default
65
+ ragsuite-test init
66
+ Clones ${DEFAULT_GIT_URL} → ~/ragsuite-test
67
+ Supports --mode docker|native
65
68
 
66
- Advanced (optional):
67
- --from-git [url] Clone this URL instead of the default
68
- --repo-root <path> Use an existing checkout (no clone)
69
- --install-dir <path> Clone target (default: ~/ragsuite-test)
70
- --mode <docker|native> Force run mode
69
+ B) No source (Docker images only)
70
+ ragsuite-test init --from-images
71
+ Copies a tiny compose bundle → ~/ragsuite-test
72
+ Pulls ghcr.io/nitsan-ai/ragsuite-backend|frontend (no git clone of app code)
73
+
74
+ Options:
75
+ --from-images Pull-only images install (no monorepo source)
76
+ --from-git [url] Clone this URL (default public repo)
77
+ --repo-root <path> Configure an existing git checkout
78
+ --install-dir <path> Target dir (default: ~/ragsuite-test)
79
+ --mode <docker|native> Git install only (images install is always docker)
71
80
  --force Overwrite non-empty install dir / existing .env
72
- --yes, -y Same defaults; for scripts/CI
73
- --llm-api-key / --smtp-* Optional overrides (usually not needed)
81
+ --yes, -y Non-interactive
82
+ --llm-api-key / --smtp-* Optional overrides
74
83
  `;
75
84
  }
76
85
 
@@ -91,9 +100,10 @@ function resolveLlmOverride(g, env) {
91
100
  return null;
92
101
  }
93
102
 
94
- function countSources(fromGit, repoRootFlag) {
103
+ function countSources(fromGit, fromImages, repoRootFlag) {
95
104
  let n = 0;
96
105
  if (fromGit !== null && fromGit !== undefined) n += 1;
106
+ if (fromImages) n += 1;
97
107
  if (repoRootFlag) n += 1;
98
108
  return n;
99
109
  }
@@ -103,12 +113,12 @@ async function run(ctx) {
103
113
  const g = ctx.globals;
104
114
 
105
115
  if (g.fromRelease) {
106
- error('--from-release is not supported. Use default init, --from-git, or --repo-root.');
116
+ error('--from-release is not supported. Use init, --from-images, --from-git, or --repo-root.');
107
117
  return 1;
108
118
  }
109
119
 
110
- if (countSources(g.fromGit, g.repoRoot) > 1) {
111
- error('Use only one of --from-git or --repo-root');
120
+ if (countSources(g.fromGit, g.fromImages, g.repoRoot) > 1) {
121
+ error('Use only one of --from-images, --from-git, or --repo-root');
112
122
  return 1;
113
123
  }
114
124
 
@@ -116,20 +126,30 @@ async function run(ctx) {
116
126
  info(`OS: ${osInfo.label} (${osInfo.platform})`);
117
127
 
118
128
  let fromGit = g.fromGit;
129
+ let fromImages = Boolean(g.fromImages);
119
130
  let repoRootFlag = g.repoRoot;
120
131
  const installDir = g.installDir || defaultInstallDir();
121
132
 
122
- // Default public path: no menus, no path prompts — clone GitHub → ~/ragsuite-test
123
- if (fromGit === null && !repoRootFlag) {
133
+ if (!fromImages && fromGit === null && !repoRootFlag) {
124
134
  fromGit = '';
125
- info(`Install: clone ${DEFAULT_GIT_URL}`);
126
- info(`Target: ${path.resolve(installDir)}`);
135
+ info(`Install style: git clone (source on disk)`);
136
+ info(` ${DEFAULT_GIT_URL} → ${path.resolve(installDir)}`);
137
+ }
138
+
139
+ if (fromImages) {
140
+ info('Install style: Docker images only (no app source on disk)');
141
+ info(` Target: ${path.resolve(installDir)}`);
127
142
  }
128
143
 
129
- const mode = g.mode || resolvePreferredMode(null);
130
- info(`Run mode: ${mode} (Docker optional — pass --mode docker|native to force)`);
144
+ let mode;
145
+ if (fromImages) {
146
+ mode = 'docker';
147
+ } else {
148
+ mode = g.mode || resolvePreferredMode(null);
149
+ }
150
+ info(`Run mode: ${mode}`);
131
151
 
132
- const needGit = fromGit !== null && fromGit !== undefined;
152
+ const needGit = !fromImages && fromGit !== null && fromGit !== undefined;
133
153
  if (!printPrereqs(mode, { needGit })) {
134
154
  error('Fix prerequisites above and re-run init');
135
155
  return 1;
@@ -155,7 +175,15 @@ async function run(ctx) {
155
175
  let source = 'checkout';
156
176
 
157
177
  try {
158
- if (fromGit !== null && fromGit !== undefined) {
178
+ if (fromImages) {
179
+ const result = materializeImagesInstall({
180
+ installDir,
181
+ force: g.force,
182
+ });
183
+ repoRoot = result.repoRoot;
184
+ source = 'images';
185
+ info(`Images bundle installed at ${repoRoot}`);
186
+ } else if (fromGit !== null && fromGit !== undefined) {
159
187
  const url = fromGit === '' ? DEFAULT_GIT_URL : fromGit;
160
188
  info(`Cloning ${url} → ${path.resolve(installDir)}`);
161
189
  const result = cloneGitToInstallDir(url, {
@@ -171,6 +199,7 @@ async function run(ctx) {
171
199
  repoRootFlag,
172
200
  env: ctx.env,
173
201
  });
202
+ if (isImagesInstall(repoRoot)) source = 'images';
174
203
  }
175
204
  } catch (err) {
176
205
  error(err.message || String(err));
@@ -203,7 +232,7 @@ async function run(ctx) {
203
232
 
204
233
  info(`Wrote ${path.join(repoRoot, CONFIG_DIR, 'config.json')}`);
205
234
  info(` mode=${cfg.mode} source=${cfg.source}`);
206
- printSuccess(repoRoot, mode);
235
+ printSuccess(repoRoot, mode, source);
207
236
  return 0;
208
237
  }
209
238
 
@@ -2,25 +2,25 @@
2
2
 
3
3
  const path = require('path');
4
4
  const { resolveMode } = require('../utils/config');
5
- const { resolveRepoRoot } = require('../utils/paths');
5
+ const { resolveRepoRoot, isImagesInstall } = require('../utils/paths');
6
6
  const { runScript } = require('../utils/spawn');
7
7
  const { assertApiPortFree } = require('../utils/port');
8
8
  const { error } = require('../utils/log');
9
9
 
10
10
  const name = 'start';
11
- const summary = 'Start stack via docker-start.sh or native-start.sh';
11
+ const summary = 'Start stack (git scripts or images pull-only)';
12
12
 
13
13
  function help() {
14
- return `Usage: ragsuite-test start [options] [-- script-args...]
14
+ return `Usage: ragsuite-test start [options]
15
15
 
16
- Calls scripts/docker-start.sh (mode=docker) or scripts/native-start.sh (mode=native).
17
- Does not reimplement compose logic. Extra args after -- are forwarded (e.g. --detach).
16
+ Git install: scripts/docker-start.sh or native-start.sh
17
+ Images install (--from-images): scripts/docker-start-images.sh (pull, no build)
18
18
 
19
19
  Options:
20
- --mode <docker|native> Override config / RAGSUITE_MODE
21
- --repo-root <path> Monorepo root
22
- --dry-run Print the script path and exit 0
23
- --detach, -d Forwarded to docker-start.sh (also accepted without --)
20
+ --mode <docker|native> Override (git install; images always docker)
21
+ --repo-root <path> Install root
22
+ --dry-run Print script path and exit 0
23
+ --detach, -d Detached start
24
24
  `;
25
25
  }
26
26
 
@@ -29,14 +29,22 @@ async function run(ctx) {
29
29
  cwd: ctx.cwd,
30
30
  repoRootFlag: ctx.globals.repoRoot,
31
31
  });
32
- const mode = resolveMode({
33
- flagMode: ctx.globals.mode,
34
- repoRoot,
35
- env: ctx.env,
36
- });
32
+ const images = isImagesInstall(repoRoot);
33
+ const mode = images
34
+ ? 'docker'
35
+ : resolveMode({
36
+ flagMode: ctx.globals.mode,
37
+ repoRoot,
38
+ env: ctx.env,
39
+ });
40
+
41
+ let scriptName;
42
+ if (images) {
43
+ scriptName = 'docker-start-images.sh';
44
+ } else {
45
+ scriptName = mode === 'native' ? 'native-start.sh' : 'docker-start.sh';
46
+ }
37
47
 
38
- const scriptName = mode === 'native' ? 'native-start.sh' : 'docker-start.sh';
39
- const rel = path.join('scripts', scriptName);
40
48
  const forward = normalizeStartArgs(ctx.commandArgs);
41
49
 
42
50
  if (!ctx.globals.dryRun) {
@@ -48,7 +56,7 @@ async function run(ctx) {
48
56
  }
49
57
  }
50
58
 
51
- return runScript(repoRoot, rel, forward, {
59
+ return runScript(repoRoot, path.join('scripts', scriptName), forward, {
52
60
  dryRun: ctx.globals.dryRun,
53
61
  env: { ...ctx.env, RAGSUITE_MODE: mode },
54
62
  });
@@ -2,22 +2,23 @@
2
2
 
3
3
  const path = require('path');
4
4
  const { resolveMode } = require('../utils/config');
5
- const { resolveRepoRoot } = require('../utils/paths');
5
+ const { resolveRepoRoot, isImagesInstall } = require('../utils/paths');
6
6
  const { runScript } = require('../utils/spawn');
7
7
 
8
8
  const name = 'stop';
9
- const summary = 'Stop stack via docker-stop.sh or native-stop.sh';
9
+ const summary = 'Stop stack (git scripts or images pull-only)';
10
10
 
11
11
  function help() {
12
12
  return `Usage: ragsuite-test stop [options]
13
13
 
14
- Calls scripts/docker-stop.sh or scripts/native-stop.sh based on mode.
15
- Never wipes Docker volumes (same as docker-stop.sh).
14
+ Git install: docker-stop.sh / native-stop.sh
15
+ Images install: docker-stop-images.sh
16
+ Never wipes Docker volumes.
16
17
 
17
18
  Options:
18
- --mode <docker|native> Override config / RAGSUITE_MODE
19
- --repo-root <path> Monorepo root
20
- --dry-run Print the script path and exit 0
19
+ --mode <docker|native> Override (git install)
20
+ --repo-root <path> Install root
21
+ --dry-run Print script path and exit 0
21
22
  `;
22
23
  }
23
24
 
@@ -26,12 +27,21 @@ function run(ctx) {
26
27
  cwd: ctx.cwd,
27
28
  repoRootFlag: ctx.globals.repoRoot,
28
29
  });
29
- const mode = resolveMode({
30
- flagMode: ctx.globals.mode,
31
- repoRoot,
32
- env: ctx.env,
33
- });
34
- const scriptName = mode === 'native' ? 'native-stop.sh' : 'docker-stop.sh';
30
+ const images = isImagesInstall(repoRoot);
31
+ const mode = images
32
+ ? 'docker'
33
+ : resolveMode({
34
+ flagMode: ctx.globals.mode,
35
+ repoRoot,
36
+ env: ctx.env,
37
+ });
38
+
39
+ const scriptName = images
40
+ ? 'docker-stop-images.sh'
41
+ : mode === 'native'
42
+ ? 'native-stop.sh'
43
+ : 'docker-stop.sh';
44
+
35
45
  return runScript(repoRoot, path.join('scripts', scriptName), [], {
36
46
  dryRun: ctx.globals.dryRun,
37
47
  env: { ...ctx.env, RAGSUITE_MODE: mode },
package/src/index.js CHANGED
@@ -31,8 +31,9 @@ function globalHelp() {
31
31
  'Global options:',
32
32
  ' --help, -h Show help',
33
33
  ' --repo-root <path> Monorepo / install root',
34
- ' --from-git [url] init: clone this URL (default: public nitsan-ai/RAGSUITE)',
35
- ' --install-dir <path> init: clone target (default ~/ragsuite-test)',
34
+ ' --from-git [url] init: clone source (default public repo)',
35
+ ' --from-images init: Docker images only (no app source on disk)',
36
+ ' --install-dir <path> init: install target (default ~/ragsuite-test)',
36
37
  ' --llm-api-key <key> init: optional override for CUSTOM_LLM_INTERNAL_API_KEY',
37
38
  ' --smtp-host <host> init: optional SMTP override',
38
39
  ' --smtp-user <user> init: optional SMTP override',
package/src/utils/args.js CHANGED
@@ -11,6 +11,7 @@ function parseArgv(argv) {
11
11
  dryRun: false,
12
12
  mode: null,
13
13
  fromGit: null,
14
+ fromImages: false,
14
15
  fromRelease: null,
15
16
  installDir: null,
16
17
  force: false,
@@ -79,6 +80,11 @@ function parseArgv(argv) {
79
80
  continue;
80
81
  }
81
82
 
83
+ if (t === '--from-images') {
84
+ globals.fromImages = true;
85
+ continue;
86
+ }
87
+
82
88
  if (t === '--from-git' || t.startsWith('--from-git=')) {
83
89
  if (t === '--from-git') {
84
90
  const next = tokens[i + 1];
@@ -21,6 +21,12 @@ function defaultConfig(repoRoot, mode = 'docker') {
21
21
  };
22
22
  }
23
23
 
24
+ function normalizeSource(raw) {
25
+ const s = String(raw || '').trim();
26
+ if (s === 'images' || s === 'git' || s === 'zip' || s === 'checkout') return s;
27
+ return 'checkout';
28
+ }
29
+
24
30
  function readConfig(repoRoot) {
25
31
  const file = configPath(repoRoot);
26
32
  if (!fs.existsSync(file)) {
@@ -31,11 +37,10 @@ function readConfig(repoRoot) {
31
37
  if (!raw || typeof raw !== 'object') {
32
38
  return null;
33
39
  }
34
- const source = raw.source === 'zip' ? 'zip' : 'checkout';
35
40
  return {
36
41
  version: Number(raw.version) || CONFIG_VERSION,
37
42
  mode: raw.mode === 'native' ? 'native' : 'docker',
38
- source,
43
+ source: normalizeSource(raw.source),
39
44
  repoRoot: path.resolve(String(raw.repoRoot || repoRoot)),
40
45
  installDir: path.resolve(String(raw.installDir || raw.repoRoot || repoRoot)),
41
46
  };
@@ -48,10 +53,11 @@ function writeConfig(repoRoot, partial = {}) {
48
53
  const dir = path.join(repoRoot, CONFIG_DIR);
49
54
  fs.mkdirSync(dir, { recursive: true });
50
55
  const existing = readConfig(repoRoot) || defaultConfig(repoRoot);
51
- const source =
52
- partial.source === 'zip' || partial.source === 'checkout'
56
+ const source = normalizeSource(
57
+ partial.source !== undefined && partial.source !== null
53
58
  ? partial.source
54
- : existing.source || 'checkout';
59
+ : existing.source || 'checkout',
60
+ );
55
61
  const next = {
56
62
  ...existing,
57
63
  ...partial,
@@ -3,19 +3,21 @@
3
3
  /**
4
4
  * Distribution modes for the testing CLI.
5
5
  *
6
- * - git-clone: default `ragsuite-test init` (public GitHub)
7
- * - local-checkout: --repo-root (advanced / CI)
6
+ * - git-clone: `ragsuite-test init` (source on disk)
7
+ * - images: `ragsuite-test init --from-images` (pull GHCR, no app source)
8
+ * - local-checkout: --repo-root
8
9
  */
9
10
 
10
11
  const MODE_LOCAL = 'local-checkout';
11
12
  const MODE_GIT = 'git-clone';
13
+ const MODE_IMAGES = 'images';
12
14
  const MODE_REGISTRY = 'registry-compose';
13
15
 
14
16
  function assertSupportedInstall() {}
15
17
 
16
18
  function resolveComposeBundle() {
17
19
  const err = new Error(
18
- 'Registry/compose (GHCR) install is not available yet. Run: ragsuite-test init',
20
+ 'Use: ragsuite-test init --from-images (pull-only) OR ragsuite-test init (git source)',
19
21
  );
20
22
  err.code = 'DIST_NOT_READY';
21
23
  throw err;
@@ -23,17 +25,17 @@ function resolveComposeBundle() {
23
25
 
24
26
  function installHint() {
25
27
  return [
26
- 'Simple deploy:',
27
- ' npm install -g @nitsan-ai/ragsuite-test',
28
- ' ragsuite-test init',
29
- ' ragsuite-test start --repo-root ~/ragsuite-test --detach',
30
- 'Docker is optional — init picks native automatically if Docker is not running.',
28
+ 'Choose an install style:',
29
+ ' Source on disk: ragsuite-test init',
30
+ ' No source: ragsuite-test init --from-images',
31
+ 'Then: ragsuite-test start --repo-root ~/ragsuite-test --detach',
31
32
  ].join('\n');
32
33
  }
33
34
 
34
35
  module.exports = {
35
36
  MODE_LOCAL,
36
37
  MODE_GIT,
38
+ MODE_IMAGES,
37
39
  MODE_REGISTRY,
38
40
  mode: MODE_GIT,
39
41
  assertSupportedInstall,
@@ -0,0 +1,100 @@
1
+ 'use strict';
2
+
3
+ const fs = require('fs');
4
+ const path = require('path');
5
+ const { defaultInstallDir } = require('./git-install');
6
+
7
+ function templatesRoot() {
8
+ return path.join(__dirname, '..', '..', 'templates', 'images');
9
+ }
10
+
11
+ function isDirEmptyOrMissing(dir) {
12
+ if (!fs.existsSync(dir)) return true;
13
+ const items = fs.readdirSync(dir).filter((n) => n !== '.DS_Store');
14
+ return items.length === 0;
15
+ }
16
+
17
+ function copyFile(src, dest) {
18
+ fs.mkdirSync(path.dirname(dest), { recursive: true });
19
+ fs.copyFileSync(src, dest);
20
+ }
21
+
22
+ /**
23
+ * Materialize a pull-only install dir (compose + scripts + .env.example).
24
+ * No monorepo source.
25
+ */
26
+ function materializeImagesInstall(options = {}) {
27
+ const { installDir = defaultInstallDir(), force = false } = options;
28
+ const srcRoot = templatesRoot();
29
+ if (!fs.existsSync(path.join(srcRoot, 'docker-compose.yml'))) {
30
+ const err = new Error(
31
+ `Images templates missing in CLI package (${srcRoot}). Reinstall @nitsan-ai/ragsuite-test.`,
32
+ );
33
+ err.code = 'IMAGES_TEMPLATE_MISSING';
34
+ throw err;
35
+ }
36
+
37
+ const target = path.resolve(installDir);
38
+ if (!isDirEmptyOrMissing(target)) {
39
+ if (!force) {
40
+ const err = new Error(
41
+ `Install directory is not empty: ${target}. Pass --force to replace, or choose another --install-dir.`,
42
+ );
43
+ err.code = 'INSTALL_DIR_NONEMPTY';
44
+ throw err;
45
+ }
46
+ fs.rmSync(target, { recursive: true, force: true });
47
+ }
48
+
49
+ fs.mkdirSync(target, { recursive: true });
50
+ copyFile(
51
+ path.join(srcRoot, 'docker-compose.yml'),
52
+ path.join(target, 'docker-compose.yml'),
53
+ );
54
+ copyFile(
55
+ path.join(srcRoot, '.env.example'),
56
+ path.join(target, '.env.example'),
57
+ );
58
+ copyFile(
59
+ path.join(srcRoot, 'scripts', 'docker-start-images.sh'),
60
+ path.join(target, 'scripts', 'docker-start-images.sh'),
61
+ );
62
+ copyFile(
63
+ path.join(srcRoot, 'scripts', 'docker-stop-images.sh'),
64
+ path.join(target, 'scripts', 'docker-stop-images.sh'),
65
+ );
66
+ copyFile(
67
+ path.join(srcRoot, 'scripts', 'docker-doctor-images.sh'),
68
+ path.join(target, 'scripts', 'docker-doctor-images.sh'),
69
+ );
70
+ try {
71
+ fs.chmodSync(path.join(target, 'scripts', 'docker-start-images.sh'), 0o755);
72
+ fs.chmodSync(path.join(target, 'scripts', 'docker-stop-images.sh'), 0o755);
73
+ fs.chmodSync(path.join(target, 'scripts', 'docker-doctor-images.sh'), 0o755);
74
+ } catch {
75
+ /* windows */
76
+ }
77
+
78
+ // Minimal package.json so tooling can identify the install dir
79
+ fs.writeFileSync(
80
+ path.join(target, 'package.json'),
81
+ `${JSON.stringify(
82
+ {
83
+ name: 'ragsuite-images-install',
84
+ private: true,
85
+ description: 'RAGSuite pull-only images install (no source tree)',
86
+ },
87
+ null,
88
+ 2,
89
+ )}\n`,
90
+ 'utf8',
91
+ );
92
+
93
+ return { repoRoot: target };
94
+ }
95
+
96
+ module.exports = {
97
+ templatesRoot,
98
+ materializeImagesInstall,
99
+ defaultInstallDir,
100
+ };
@@ -4,14 +4,35 @@ const fs = require('fs');
4
4
  const path = require('path');
5
5
  const { readConfig, CONFIG_DIR } = require('./config');
6
6
 
7
- function looksLikeRepoRoot(dir) {
7
+ function looksLikeGitCheckout(dir) {
8
8
  const start = path.join(dir, 'scripts', 'docker-start.sh');
9
9
  const pkg = path.join(dir, 'package.json');
10
10
  return fs.existsSync(start) && fs.existsSync(pkg);
11
11
  }
12
12
 
13
+ function looksLikeImagesInstall(dir) {
14
+ const compose = path.join(dir, 'docker-compose.yml');
15
+ const start = path.join(dir, 'scripts', 'docker-start-images.sh');
16
+ if (!fs.existsSync(compose) || !fs.existsSync(start)) return false;
17
+ const cfgPath = path.join(dir, CONFIG_DIR, 'config.json');
18
+ if (fs.existsSync(cfgPath)) {
19
+ try {
20
+ const cfg = JSON.parse(fs.readFileSync(cfgPath, 'utf8'));
21
+ if (cfg.source === 'images') return true;
22
+ } catch {
23
+ /* fall through */
24
+ }
25
+ }
26
+ // Before config is written, or force-detect by scripts present without docker-start.sh
27
+ return !fs.existsSync(path.join(dir, 'scripts', 'docker-start.sh'));
28
+ }
29
+
30
+ function looksLikeRepoRoot(dir) {
31
+ return looksLikeGitCheckout(dir) || looksLikeImagesInstall(dir);
32
+ }
33
+
13
34
  function looksLikeFullBundleRoot(dir) {
14
- if (!looksLikeRepoRoot(dir)) return false;
35
+ if (!looksLikeGitCheckout(dir)) return false;
15
36
  const compose = path.join(dir, 'docker-compose.yml');
16
37
  return fs.existsSync(compose);
17
38
  }
@@ -42,10 +63,6 @@ function envRepoRoot(env = process.env) {
42
63
  return null;
43
64
  }
44
65
 
45
- /**
46
- * Resolve monorepo root:
47
- * --repo-root → RAGSUITE_REPO_ROOT / RAGSUITE_INSTALL_DIR → walk-up / config.
48
- */
49
66
  function resolveRepoRoot({
50
67
  cwd = process.cwd(),
51
68
  repoRootFlag,
@@ -56,7 +73,7 @@ function resolveRepoRoot({
56
73
  const root = path.resolve(repoRootFlag);
57
74
  if (!looksLikeRepoRoot(root)) {
58
75
  const err = new Error(
59
- `Not a RAGSuite repo root (missing scripts/docker-start.sh): ${root}`,
76
+ `Not a RAGSuite install root: ${root} (need git checkout or images install)`,
60
77
  );
61
78
  err.code = 'NOT_REPO_ROOT';
62
79
  throw err;
@@ -91,7 +108,7 @@ function resolveRepoRoot({
91
108
  }
92
109
 
93
110
  const err = new Error(
94
- 'Could not find RAGSuite repo root. Pass --repo-root <path>, set RAGSUITE_REPO_ROOT, or run: ragsuite-test init --from-git',
111
+ 'Could not find RAGSuite install. Run: ragsuite-test init OR ragsuite-test init --from-images',
95
112
  );
96
113
  err.code = 'NOT_REPO_ROOT';
97
114
  throw err;
@@ -107,13 +124,20 @@ function ensureRagsuiteDir(repoRoot) {
107
124
  return dir;
108
125
  }
109
126
 
110
- /** Native PID/logs stay under .ragsuite/native (Phase 4 scripts). */
111
127
  function nativeLogDir(repoRoot) {
112
128
  return path.join(repoRoot, '.ragsuite', 'native');
113
129
  }
114
130
 
131
+ function isImagesInstall(repoRoot) {
132
+ const cfg = readConfig(repoRoot);
133
+ if (cfg && cfg.source === 'images') return true;
134
+ return looksLikeImagesInstall(repoRoot) && !looksLikeGitCheckout(repoRoot);
135
+ }
136
+
115
137
  module.exports = {
116
138
  looksLikeRepoRoot,
139
+ looksLikeGitCheckout,
140
+ looksLikeImagesInstall,
117
141
  looksLikeFullBundleRoot,
118
142
  walkUpForRepoRoot,
119
143
  envRepoRoot,
@@ -121,4 +145,5 @@ module.exports = {
121
145
  scriptPath,
122
146
  ensureRagsuiteDir,
123
147
  nativeLogDir,
148
+ isImagesInstall,
124
149
  };
@@ -0,0 +1,76 @@
1
+ # RAGSuite Server — copy to .env: cp .env.example .env
2
+ # Never commit .env.
3
+ #
4
+ # Used by docker compose (backend + worker env_file) and native-start (host process).
5
+ # Compose injects DATABASE_URL, REDIS_HOST/PORT, CHROMA_* for containers — those override
6
+ # anything duplicated here when using Docker mode.
7
+ # For native mode (npm run start:native), set the Native block below (or rely on script defaults).
8
+
9
+ # --- Required secrets ---
10
+ # JWT is regenerated by `ragsuite-test init` (do not put real secrets here — use .env).
11
+ JWT_SECRET_KEY=change-me-use-a-long-random-secret-in-production
12
+ # First-run default (not a cloud vendor key). Override in .env anytime.
13
+ CUSTOM_LLM_INTERNAL_API_KEY=ragsuite-default-llm-internal-key
14
+
15
+ # --- Transactional email (invites, 2FA, password reset) ---
16
+ # Template only — real SMTP_USER / SMTP_PASSWORD / EMAIL_FROM belong in .env (gitignored).
17
+ # `ragsuite-test init` copies this file to .env and does NOT ask for SMTP.
18
+ SMTP_HOST=smtp.gmail.com
19
+ SMTP_PORT=587
20
+ SMTP_USER=your-smtp-user@example.com
21
+ SMTP_PASSWORD=change-me-app-password-or-smtp-secret
22
+ SMTP_USE_TLS=true
23
+ EMAIL_FROM=your-smtp-user@example.com
24
+
25
+ # --- Frontend Docker build (baked into env.json at image build) ---
26
+ FRONTEND_API_URL=http://localhost:9090
27
+ # Optional: public origin that serves /widget assets (defaults to FRONTEND_API_URL in Dockerfile)
28
+ # WIDGET_ASSET_BASE=
29
+
30
+ # --- App policy ---
31
+ HF_HUB_OFFLINE=1
32
+ SSO_ENABLED=false
33
+ SSO_REQUIRE_REDIS=true
34
+
35
+ # --- RAG / ingest (optional) ---
36
+ ENABLE_ASYNC_DOCUMENT_INGEST=true
37
+ EMBEDDING_PREFERRED_SOURCE=chat
38
+ COMPARE_MODEL_CONFIG_SOURCE=chat
39
+
40
+ # --- Optional: Docker host-port overrides (defaults match npm start) ---
41
+ # API_PORT=9090
42
+ # WEB_PORT=9091
43
+ # POSTGRES_HOST_PORT=5436
44
+ # REDIS_HOST_PORT=6382
45
+
46
+ # --- Optional: Compose Postgres credentials (defaults: ragsuite / ragsuite / ragsuite_v3) ---
47
+ # POSTGRES_USER=ragsuite
48
+ # POSTGRES_PASSWORD=ragsuite
49
+ # POSTGRES_DB=ragsuite_v3
50
+
51
+ # --- Optional: App URL / CORS (compose sets local defaults for Docker) ---
52
+ # CORS_ORIGINS=http://localhost:9091
53
+ # FRONTEND_BASE_URL=http://localhost:9091
54
+ # PUBLIC_API_BASE_URL=http://localhost:9090/api/v1
55
+ # SSO_CALLBACK_BASE_URL=http://localhost:9090/api/v1/auth/sso/callback
56
+
57
+ # --- Native mode (npm run start:native) — host Postgres/Redis/Chroma ---
58
+ # Do not use Docker service DNS names (postgres/redis/chromadb) on the host.
59
+ # DATABASE_URL=postgresql://ragsuite:ragsuite@localhost:5436/ragsuite_v3
60
+ # REDIS_HOST=localhost
61
+ # REDIS_PORT=6382
62
+ # CHROMA_MODE=http
63
+ # CHROMA_HOST=127.0.0.1
64
+ # CHROMA_PORT=8004
65
+ # CHROMA_PERSIST_PATH=data/chroma_db
66
+ # CORS_ORIGINS=http://localhost:9191,http://127.0.0.1:9191,http://localhost:9091
67
+ # FRONTEND_BASE_URL=http://localhost:9191
68
+ # PUBLIC_API_BASE_URL=http://localhost:9090/api/v1
69
+ # EXPO_DEV_SERVER_PORT=9191
70
+ # RAGSUITE_MODE=native
71
+
72
+ # Advanced tuning: backend/docs/backend/configuration.md
73
+
74
+ # --- Images install (init --from-images) ---
75
+ # IMAGE_REGISTRY=ghcr.io/nitsan-ai
76
+ # IMAGE_TAG=latest
@@ -0,0 +1,165 @@
1
+ # Pull-only stack (no monorepo source / no local build).
2
+ # Used by: ragsuite-test init --from-images
3
+ # Images: ghcr.io/nitsan-ai/ragsuite-backend and ragsuite-frontend
4
+ #
5
+ # Override tag: IMAGE_TAG=1.0.0 (default: latest)
6
+ # Registry: IMAGE_REGISTRY=ghcr.io/nitsan-ai (default)
7
+
8
+ name: ragsuite-server
9
+
10
+ networks:
11
+ ragsuite-server-net:
12
+ name: ragsuite-server-net
13
+
14
+ x-backend-image: &backend_image
15
+ image: ${IMAGE_REGISTRY:-ghcr.io/nitsan-ai}/ragsuite-backend:${IMAGE_TAG:-latest}
16
+ pull_policy: ${PULL_POLICY:-always}
17
+
18
+ services:
19
+ postgres:
20
+ image: postgres:15-alpine
21
+ environment:
22
+ POSTGRES_USER: ${POSTGRES_USER:-ragsuite}
23
+ POSTGRES_PASSWORD: ${POSTGRES_PASSWORD:-ragsuite}
24
+ POSTGRES_DB: ${POSTGRES_DB:-ragsuite_v3}
25
+ ports:
26
+ - "${POSTGRES_HOST_PORT:-5436}:5432"
27
+ volumes:
28
+ - postgres_data:/var/lib/postgresql/data
29
+ healthcheck:
30
+ test: ["CMD-SHELL", "pg_isready -U $${POSTGRES_USER} -d $${POSTGRES_DB}"]
31
+ interval: 5s
32
+ timeout: 5s
33
+ retries: 10
34
+ networks:
35
+ - ragsuite-server-net
36
+ restart: unless-stopped
37
+
38
+ redis:
39
+ image: redis:7-alpine
40
+ ports:
41
+ - "${REDIS_HOST_PORT:-6382}:6379"
42
+ healthcheck:
43
+ test: ["CMD", "redis-cli", "ping"]
44
+ interval: 5s
45
+ timeout: 3s
46
+ retries: 10
47
+ networks:
48
+ - ragsuite-server-net
49
+ restart: unless-stopped
50
+
51
+ chromadb:
52
+ <<: *backend_image
53
+ entrypoint: []
54
+ command:
55
+ - chroma
56
+ - run
57
+ - --host
58
+ - "0.0.0.0"
59
+ - --port
60
+ - "8000"
61
+ - --path
62
+ - /data/chroma
63
+ volumes:
64
+ - chroma_data:/data/chroma
65
+ healthcheck:
66
+ test: ["CMD", "curl", "-sf", "http://localhost:8000/api/v2/heartbeat"]
67
+ interval: 10s
68
+ timeout: 5s
69
+ retries: 10
70
+ start_period: 30s
71
+ networks:
72
+ - ragsuite-server-net
73
+ restart: unless-stopped
74
+
75
+ backend:
76
+ <<: *backend_image
77
+ env_file:
78
+ - .env
79
+ environment:
80
+ DEBUG: "True"
81
+ CORS_ORIGINS: "http://localhost:${WEB_PORT:-9091}"
82
+ ENABLE_SCHEDULER: "true"
83
+ ENABLE_DURABLE_JOBS: "true"
84
+ DATABASE_URL: postgresql://${POSTGRES_USER:-ragsuite}:${POSTGRES_PASSWORD:-ragsuite}@postgres:5432/${POSTGRES_DB:-ragsuite_v3}
85
+ REDIS_HOST: redis
86
+ REDIS_PORT: "6379"
87
+ CHROMA_MODE: http
88
+ CHROMA_HOST: chromadb
89
+ CHROMA_PORT: "8000"
90
+ CHROMA_PERSIST_PATH: /data/chroma
91
+ DOCUMENT_STAGING_DIR: /data/staging
92
+ RUN_INLINE_WORKER: "false"
93
+ WEB_CONCURRENCY: "2"
94
+ FRONTEND_BASE_URL: http://localhost:${WEB_PORT:-9091}
95
+ PUBLIC_API_BASE_URL: http://localhost:${API_PORT:-9090}/api/v1
96
+ SSO_CALLBACK_BASE_URL: http://localhost:${API_PORT:-9090}/api/v1/auth/sso/callback
97
+ ports:
98
+ - "${API_PORT:-9090}:8000"
99
+ depends_on:
100
+ postgres:
101
+ condition: service_healthy
102
+ redis:
103
+ condition: service_healthy
104
+ chromadb:
105
+ condition: service_healthy
106
+ healthcheck:
107
+ test: ["CMD", "curl", "-sf", "http://localhost:8000/api/v1/health/ready"]
108
+ interval: 30s
109
+ timeout: 10s
110
+ retries: 5
111
+ start_period: 120s
112
+ volumes:
113
+ - staging_data:/data/staging
114
+ networks:
115
+ - ragsuite-server-net
116
+ restart: unless-stopped
117
+
118
+ worker:
119
+ <<: *backend_image
120
+ entrypoint: []
121
+ command: ["python", "-m", "app.worker"]
122
+ env_file:
123
+ - .env
124
+ environment:
125
+ DEBUG: "False"
126
+ CORS_ORIGINS: "http://localhost:${WEB_PORT:-9091}"
127
+ ENABLE_SCHEDULER: "true"
128
+ ENABLE_DURABLE_JOBS: "true"
129
+ DATABASE_URL: postgresql://${POSTGRES_USER:-ragsuite}:${POSTGRES_PASSWORD:-ragsuite}@postgres:5432/${POSTGRES_DB:-ragsuite_v3}
130
+ REDIS_HOST: redis
131
+ REDIS_PORT: "6379"
132
+ CHROMA_MODE: http
133
+ CHROMA_HOST: chromadb
134
+ CHROMA_PORT: "8000"
135
+ CHROMA_PERSIST_PATH: /data/chroma
136
+ DOCUMENT_STAGING_DIR: /data/staging
137
+ RUN_INLINE_WORKER: "false"
138
+ depends_on:
139
+ backend:
140
+ condition: service_healthy
141
+ volumes:
142
+ - staging_data:/data/staging
143
+ networks:
144
+ - ragsuite-server-net
145
+ restart: unless-stopped
146
+
147
+ frontend:
148
+ image: ${IMAGE_REGISTRY:-ghcr.io/nitsan-ai}/ragsuite-frontend:${IMAGE_TAG:-latest}
149
+ pull_policy: ${PULL_POLICY:-always}
150
+ ports:
151
+ - "${WEB_PORT:-9091}:80"
152
+ healthcheck:
153
+ test: ["CMD", "wget", "-q", "--spider", "http://localhost/healthz"]
154
+ interval: 15s
155
+ timeout: 5s
156
+ retries: 5
157
+ start_period: 10s
158
+ networks:
159
+ - ragsuite-server-net
160
+ restart: unless-stopped
161
+
162
+ volumes:
163
+ postgres_data:
164
+ chroma_data:
165
+ staging_data:
@@ -0,0 +1,48 @@
1
+ #!/usr/bin/env bash
2
+ # Lightweight doctor for images-only installs.
3
+ set -euo pipefail
4
+
5
+ SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
6
+ ROOT="$(cd "$SCRIPT_DIR/.." && pwd)"
7
+ cd "$ROOT"
8
+
9
+ ok=0
10
+ fail=0
11
+
12
+ check() {
13
+ local label="$1"
14
+ shift
15
+ if "$@"; then
16
+ echo " ✓ $label"
17
+ ok=$((ok + 1))
18
+ else
19
+ echo " ✗ $label"
20
+ fail=$((fail + 1))
21
+ fi
22
+ }
23
+
24
+ echo "Doctor (images install):"
25
+ check "docker CLI" command -v docker >/dev/null
26
+ check "docker daemon" docker info >/dev/null 2>&1
27
+ check "docker compose" docker compose version >/dev/null 2>&1
28
+ check ".env present" test -f .env
29
+ check "compose file" test -f docker-compose.yml
30
+
31
+ API_PORT="${API_PORT:-9090}"
32
+ if curl -sf "http://127.0.0.1:${API_PORT}/api/v1/health/ping" >/dev/null 2>&1 \
33
+ || curl -sf "http://127.0.0.1:${API_PORT}/api/v1/crawl/auth/public-config" >/dev/null 2>&1; then
34
+ echo " ✓ API reachable :${API_PORT}"
35
+ ok=$((ok + 1))
36
+ else
37
+ echo " ○ API not up yet on :${API_PORT} (start the stack if needed)"
38
+ fi
39
+
40
+ echo "Summary: ${ok} ok, ${fail} failed"
41
+ if [[ "$fail" -gt 0 ]]; then
42
+ echo "How to fix:"
43
+ echo " → Install Docker Desktop: https://www.docker.com/products/docker-desktop/"
44
+ echo " → Publish images: GitHub Actions → Publish images (GHCR)"
45
+ echo " → Start: ragsuite-test start --repo-root $ROOT --detach"
46
+ exit 1
47
+ fi
48
+ exit 0
@@ -0,0 +1,107 @@
1
+ #!/usr/bin/env bash
2
+ # Start pull-only images stack (no source build).
3
+ # Exit: 0 ok, 1 prereq, 2 env, 3 compose, 4 health
4
+ set -euo pipefail
5
+
6
+ SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
7
+ ROOT="$(cd "$SCRIPT_DIR/.." && pwd)"
8
+ cd "$ROOT"
9
+
10
+ DETACH=1
11
+ for arg in "$@"; do
12
+ case "$arg" in
13
+ -d|--detach) DETACH=1 ;;
14
+ -h|--help)
15
+ echo "Usage: $0 [--detach|-d]"
16
+ echo " docker compose up -d (pull images, no --build)"
17
+ exit 0
18
+ ;;
19
+ esac
20
+ done
21
+
22
+ if ! command -v docker >/dev/null 2>&1; then
23
+ echo "error: docker is required for images install" >&2
24
+ exit 1
25
+ fi
26
+ if ! docker info >/dev/null 2>&1; then
27
+ echo "error: Docker daemon is not running" >&2
28
+ exit 1
29
+ fi
30
+ if ! docker compose version >/dev/null 2>&1; then
31
+ echo "error: docker compose plugin is required" >&2
32
+ exit 1
33
+ fi
34
+
35
+ if [[ ! -f .env ]]; then
36
+ if [[ -f .env.example ]]; then
37
+ cp .env.example .env
38
+ echo "warn: created .env from .env.example — edit secrets for production"
39
+ else
40
+ echo "error: missing .env" >&2
41
+ exit 2
42
+ fi
43
+ fi
44
+
45
+ # shellcheck disable=SC1091
46
+ set -a
47
+ # shellcheck source=/dev/null
48
+ source .env
49
+ set +a
50
+
51
+ if [[ -z "${JWT_SECRET_KEY:-}" || "${JWT_SECRET_KEY}" == change-me* ]]; then
52
+ echo "error: JWT_SECRET_KEY must be set (non placeholder) in .env" >&2
53
+ exit 2
54
+ fi
55
+ if [[ -z "${CUSTOM_LLM_INTERNAL_API_KEY:-}" || "${CUSTOM_LLM_INTERNAL_API_KEY}" == change-me* ]]; then
56
+ echo "error: CUSTOM_LLM_INTERNAL_API_KEY must be set (non placeholder) in .env" >&2
57
+ exit 2
58
+ fi
59
+
60
+ export IMAGE_REGISTRY="${IMAGE_REGISTRY:-ghcr.io/nitsan-ai}"
61
+ export IMAGE_TAG="${IMAGE_TAG:-latest}"
62
+ export PULL_POLICY="${PULL_POLICY:-always}"
63
+
64
+ COMPOSE_FILE="${COMPOSE_FILE:-docker-compose.yml}"
65
+ if [[ ! -f "$COMPOSE_FILE" ]]; then
66
+ echo "error: missing $COMPOSE_FILE (images install)" >&2
67
+ exit 1
68
+ fi
69
+
70
+ echo "==> Pulling / starting images (no build) registry=${IMAGE_REGISTRY} tag=${IMAGE_TAG}"
71
+ set +e
72
+ docker compose -f "$COMPOSE_FILE" pull
73
+ docker compose -f "$COMPOSE_FILE" up -d --pull always
74
+ rc=$?
75
+ set -e
76
+ if [[ "$rc" -ne 0 ]]; then
77
+ echo "error: docker compose failed (exit $rc). Are GHCR images published and public/readable?" >&2
78
+ echo " Expected: ${IMAGE_REGISTRY}/ragsuite-backend:${IMAGE_TAG}" >&2
79
+ echo " Expected: ${IMAGE_REGISTRY}/ragsuite-frontend:${IMAGE_TAG}" >&2
80
+ exit 3
81
+ fi
82
+
83
+ API_PORT="${API_PORT:-9090}"
84
+ echo "==> Waiting for API :${API_PORT}"
85
+ ok=0
86
+ for _ in $(seq 1 60); do
87
+ if curl -sf "http://127.0.0.1:${API_PORT}/api/v1/crawl/auth/public-config" >/dev/null 2>&1 \
88
+ || curl -sf "http://127.0.0.1:${API_PORT}/api/v1/health/ping" >/dev/null 2>&1; then
89
+ ok=1
90
+ break
91
+ fi
92
+ sleep 2
93
+ done
94
+ if [[ "$ok" -ne 1 ]]; then
95
+ echo "error: API health timed out" >&2
96
+ docker compose -f "$COMPOSE_FILE" ps -a || true
97
+ docker compose -f "$COMPOSE_FILE" logs backend --tail 80 || true
98
+ exit 4
99
+ fi
100
+
101
+ echo ""
102
+ echo "Stack URLs:"
103
+ echo " API http://localhost:${API_PORT}"
104
+ echo " Web UI http://localhost:${WEB_PORT:-9091}"
105
+ echo ""
106
+ echo "Images stack is up (no source tree required)."
107
+ exit 0
@@ -0,0 +1,22 @@
1
+ #!/usr/bin/env bash
2
+ # Stop pull-only images stack (volumes preserved).
3
+ set -euo pipefail
4
+
5
+ SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
6
+ ROOT="$(cd "$SCRIPT_DIR/.." && pwd)"
7
+ cd "$ROOT"
8
+
9
+ if ! command -v docker >/dev/null 2>&1; then
10
+ echo "error: docker required" >&2
11
+ exit 1
12
+ fi
13
+ if ! docker info >/dev/null 2>&1; then
14
+ echo "error: Docker daemon is not running" >&2
15
+ exit 1
16
+ fi
17
+
18
+ COMPOSE_FILE="${COMPOSE_FILE:-docker-compose.yml}"
19
+ echo "==> Stopping images stack (volumes preserved)…"
20
+ docker compose -f "$COMPOSE_FILE" down
21
+ echo "Containers stopped. Volumes kept."
22
+ exit 0