@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 +64 -47
- package/package.json +3 -2
- package/src/commands/doctor.js +17 -13
- package/src/commands/init.js +66 -37
- package/src/commands/start.js +25 -17
- package/src/commands/stop.js +23 -13
- package/src/index.js +3 -2
- package/src/utils/args.js +6 -0
- package/src/utils/config.js +11 -5
- package/src/utils/distribution.js +10 -8
- package/src/utils/images-install.js +100 -0
- package/src/utils/paths.js +34 -9
- package/templates/images/.env.example +76 -0
- package/templates/images/docker-compose.yml +165 -0
- package/templates/images/scripts/docker-doctor-images.sh +48 -0
- package/templates/images/scripts/docker-start-images.sh +107 -0
- package/templates/images/scripts/docker-stop-images.sh +22 -0
package/README.md
CHANGED
|
@@ -1,83 +1,100 @@
|
|
|
1
1
|
# RAGSuite test CLI (`ragsuite-test`)
|
|
2
2
|
|
|
3
|
-
> **TESTING PACKAGE** — temporary
|
|
3
|
+
> **TESTING PACKAGE** — temporary name before the final `ragsuite` CLI.
|
|
4
4
|
|
|
5
|
-
|
|
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
|
-
|
|
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
|
-
|
|
36
|
+
### Native (no Docker) — git install only
|
|
14
37
|
|
|
15
|
-
|
|
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
|
|
41
|
+
ragsuite-test init --mode native
|
|
42
|
+
ragsuite-test start --repo-root ~/ragsuite-test
|
|
21
43
|
```
|
|
22
44
|
|
|
23
|
-
|
|
45
|
+
---
|
|
24
46
|
|
|
25
|
-
|
|
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
|
-
|
|
49
|
+
**Maintainer (once):** publish images — Actions → **Publish images (GHCR)**
|
|
50
|
+
(packages: `ghcr.io/nitsan-ai/ragsuite-backend`, `…/ragsuite-frontend`)
|
|
32
51
|
|
|
33
|
-
|
|
52
|
+
**User:**
|
|
34
53
|
|
|
35
54
|
```bash
|
|
36
|
-
|
|
37
|
-
ragsuite-test init --
|
|
38
|
-
ragsuite-test
|
|
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
|
-
|
|
41
|
-
ragsuite-test init --install-dir ~/my-ragsuite
|
|
62
|
+
Optional pin:
|
|
42
63
|
|
|
43
|
-
|
|
44
|
-
ragsuite-test
|
|
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
|
-
|
|
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
|
-
|
|
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.
|
|
3
|
+
"version": "0.1.3",
|
|
4
4
|
"private": false,
|
|
5
|
-
"description": "RAGSuite testing CLI
|
|
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"
|
package/src/commands/doctor.js
CHANGED
|
@@ -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
|
|
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
|
-
|
|
15
|
-
|
|
14
|
+
Git install: scripts/doctor.sh
|
|
15
|
+
Images install: scripts/docker-doctor-images.sh
|
|
16
16
|
|
|
17
17
|
Options:
|
|
18
|
-
--mode <docker|native>
|
|
19
|
-
--repo-root <path>
|
|
20
|
-
--dry-run
|
|
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
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
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
|
});
|
package/src/commands/init.js
CHANGED
|
@@ -2,13 +2,18 @@
|
|
|
2
2
|
|
|
3
3
|
const path = require('path');
|
|
4
4
|
const { writeConfig, CONFIG_DIR } = require('../utils/config');
|
|
5
|
-
const {
|
|
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 = '
|
|
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 (
|
|
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
|
-
|
|
44
|
-
|
|
45
|
-
|
|
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
|
-
|
|
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
|
-
|
|
64
|
-
|
|
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
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
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
|
|
73
|
-
--llm-api-key / --smtp-* Optional overrides
|
|
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
|
|
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
|
-
|
|
123
|
-
if (fromGit === null && !repoRootFlag) {
|
|
133
|
+
if (!fromImages && fromGit === null && !repoRootFlag) {
|
|
124
134
|
fromGit = '';
|
|
125
|
-
info(`Install: clone
|
|
126
|
-
info(`
|
|
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
|
-
|
|
130
|
-
|
|
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 (
|
|
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
|
|
package/src/commands/start.js
CHANGED
|
@@ -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
|
|
11
|
+
const summary = 'Start stack (git scripts or images pull-only)';
|
|
12
12
|
|
|
13
13
|
function help() {
|
|
14
|
-
return `Usage: ragsuite-test start [options]
|
|
14
|
+
return `Usage: ragsuite-test start [options]
|
|
15
15
|
|
|
16
|
-
|
|
17
|
-
|
|
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
|
|
21
|
-
--repo-root <path>
|
|
22
|
-
--dry-run Print
|
|
23
|
-
--detach, -d
|
|
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
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
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,
|
|
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
|
});
|
package/src/commands/stop.js
CHANGED
|
@@ -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
|
|
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
|
-
|
|
15
|
-
|
|
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
|
|
19
|
-
--repo-root <path>
|
|
20
|
-
--dry-run Print
|
|
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
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
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
|
|
35
|
-
' --
|
|
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];
|
package/src/utils/config.js
CHANGED
|
@@ -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
|
|
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:
|
|
7
|
-
* -
|
|
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
|
-
'
|
|
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
|
-
'
|
|
27
|
-
'
|
|
28
|
-
' ragsuite-test init',
|
|
29
|
-
'
|
|
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
|
+
};
|
package/src/utils/paths.js
CHANGED
|
@@ -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
|
|
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 (!
|
|
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
|
|
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
|
|
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
|