@nitsan-ai/ragsuite-test 0.1.2 → 0.1.4
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 +42 -59
- package/package.json +3 -2
- package/src/commands/doctor.js +17 -13
- package/src/commands/init.js +130 -60
- package/src/commands/start.js +25 -17
- package/src/commands/stop.js +23 -13
- package/src/commands/update.js +84 -51
- package/src/index.js +5 -3
- package/src/utils/args.js +6 -0
- package/src/utils/config.js +11 -5
- package/src/utils/distribution.js +10 -8
- package/src/utils/git-install.js +12 -7
- package/src/utils/global-config.js +61 -0
- package/src/utils/images-install.js +106 -0
- package/src/utils/paths.js +77 -21
- package/src/utils/port.js +1 -1
- 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/src/commands/update.js
CHANGED
|
@@ -2,86 +2,119 @@
|
|
|
2
2
|
|
|
3
3
|
const fs = require('fs');
|
|
4
4
|
const path = require('path');
|
|
5
|
-
const { resolveRepoRoot } = require('../utils/paths');
|
|
5
|
+
const { resolveRepoRoot, isImagesInstall, looksLikeGitCheckout } = require('../utils/paths');
|
|
6
6
|
const { readConfig } = require('../utils/config');
|
|
7
|
-
const {
|
|
8
|
-
const {
|
|
7
|
+
const { setActiveInstall } = require('../utils/global-config');
|
|
8
|
+
const { captureCommand, runScript } = require('../utils/spawn');
|
|
9
|
+
const { info, warn, error } = require('../utils/log');
|
|
9
10
|
|
|
10
11
|
const name = 'update';
|
|
11
|
-
const summary = '
|
|
12
|
+
const summary = 'Update app in place (git pull or image pull) — keeps data & .env';
|
|
12
13
|
|
|
13
14
|
function help() {
|
|
14
|
-
return `Usage: ragsuite-test update
|
|
15
|
+
return `Usage: ragsuite-test update
|
|
15
16
|
|
|
16
|
-
|
|
17
|
-
Git
|
|
17
|
+
Upgrades the active install without wiping data:
|
|
18
|
+
• Git install → git pull (keeps .env + Docker volumes)
|
|
19
|
+
• Images install → docker compose pull (keeps .env + volumes)
|
|
18
20
|
|
|
19
21
|
Never runs docker compose down -v.
|
|
20
22
|
|
|
23
|
+
Typical flow after a new release:
|
|
24
|
+
npm install -g @nitsan-ai/ragsuite-test@latest
|
|
25
|
+
ragsuite-test update
|
|
26
|
+
ragsuite-test start
|
|
27
|
+
|
|
21
28
|
Options:
|
|
22
|
-
--repo-root <path>
|
|
29
|
+
--repo-root <path> Override active install
|
|
30
|
+
--dry-run Print actions only
|
|
23
31
|
`;
|
|
24
32
|
}
|
|
25
33
|
|
|
26
34
|
function run(ctx) {
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
35
|
+
let repoRoot;
|
|
36
|
+
try {
|
|
37
|
+
repoRoot = resolveRepoRoot({
|
|
38
|
+
cwd: ctx.cwd,
|
|
39
|
+
repoRootFlag: ctx.globals.repoRoot,
|
|
40
|
+
env: ctx.env,
|
|
41
|
+
});
|
|
42
|
+
} catch (err) {
|
|
43
|
+
error(err.message);
|
|
44
|
+
return 1;
|
|
45
|
+
}
|
|
32
46
|
|
|
47
|
+
setActiveInstall(repoRoot);
|
|
33
48
|
const cfg = readConfig(repoRoot);
|
|
34
|
-
const source = (cfg && cfg.source) || '
|
|
49
|
+
const source = (cfg && cfg.source) || (isImagesInstall(repoRoot) ? 'images' : 'git');
|
|
50
|
+
const images = source === 'images' || isImagesInstall(repoRoot);
|
|
35
51
|
|
|
36
|
-
info(`
|
|
52
|
+
info(`Updating install: ${repoRoot}`);
|
|
37
53
|
info(` source=${source}`);
|
|
54
|
+
info(' (.env and Docker volumes are kept — never wiped)');
|
|
38
55
|
info('');
|
|
39
56
|
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
info(' 2. Update sources (git pull)');
|
|
43
|
-
info(' 3. Docker: npm run rebuild or docker compose build');
|
|
44
|
-
info(' 4. Native: re-run backend/scripts/setup.sh if Python deps changed');
|
|
45
|
-
info(' 5. Start again (ragsuite-test start --repo-root …)');
|
|
46
|
-
info(' 6. Run doctor after upgrades');
|
|
47
|
-
info('');
|
|
48
|
-
info('Never: docker compose down -v (destroys Postgres/Chroma data).');
|
|
49
|
-
info('');
|
|
50
|
-
|
|
51
|
-
const gitDir = path.join(repoRoot, '.git');
|
|
52
|
-
if (!fs.existsSync(gitDir)) {
|
|
53
|
-
warn('No .git directory — skip fetch/status. Re-init with --from-git if needed.');
|
|
57
|
+
if (ctx.globals.dryRun) {
|
|
58
|
+
info(`[dry-run] would update ${images ? 'images (compose pull)' : 'git (pull)'}`);
|
|
54
59
|
return 0;
|
|
55
60
|
}
|
|
56
61
|
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
62
|
+
if (images) {
|
|
63
|
+
info('Pulling newer Docker images…');
|
|
64
|
+
const pull = captureCommand(
|
|
65
|
+
'docker',
|
|
66
|
+
['compose', '-f', 'docker-compose.yml', 'pull'],
|
|
67
|
+
{ cwd: repoRoot, env: ctx.env },
|
|
68
|
+
);
|
|
69
|
+
if (pull.status !== 0) {
|
|
70
|
+
error(`Image pull failed (exit ${pull.status}).`);
|
|
71
|
+
if (pull.stderr) error(String(pull.stderr).trimEnd());
|
|
72
|
+
error('Publish GHCR images / check IMAGE_TAG in .env, then retry.');
|
|
73
|
+
return 1;
|
|
74
|
+
}
|
|
75
|
+
info('Images pulled.');
|
|
76
|
+
info('');
|
|
77
|
+
info('Apply update:');
|
|
78
|
+
info(' ragsuite-test stop');
|
|
79
|
+
info(' ragsuite-test start');
|
|
80
|
+
info('(or: ragsuite-test start — compose recreate with new images)');
|
|
81
|
+
// Recreate containers with new images without wiping volumes
|
|
82
|
+
info('');
|
|
83
|
+
info('Recreating containers with new images (volumes preserved)…');
|
|
84
|
+
return runScript(repoRoot, path.join('scripts', 'docker-start-images.sh'), ['--detach'], {
|
|
85
|
+
dryRun: false,
|
|
86
|
+
env: { ...ctx.env, RAGSUITE_MODE: 'docker' },
|
|
87
|
+
});
|
|
61
88
|
}
|
|
62
89
|
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
90
|
+
// Git / checkout install
|
|
91
|
+
if (!looksLikeGitCheckout(repoRoot) && !fs.existsSync(path.join(repoRoot, '.git'))) {
|
|
92
|
+
error('This install has no .git directory. Re-run: ragsuite-test init --force');
|
|
93
|
+
return 1;
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
info('Fetching and pulling latest from git (non-destructive)…');
|
|
97
|
+
const pull = captureCommand('git', ['pull', '--ff-only'], {
|
|
68
98
|
cwd: repoRoot,
|
|
69
99
|
env: ctx.env,
|
|
70
100
|
});
|
|
71
|
-
if (
|
|
72
|
-
warn(
|
|
73
|
-
|
|
74
|
-
);
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
const status = captureCommand('git', ['status', '-sb'], { cwd: repoRoot, env: ctx.env });
|
|
81
|
-
if (status.stdout) {
|
|
82
|
-
info('');
|
|
83
|
-
info(String(status.stdout).trimEnd());
|
|
101
|
+
if (pull.status !== 0) {
|
|
102
|
+
warn(`git pull --ff-only failed (exit ${pull.status}). Trying fetch + status…`);
|
|
103
|
+
if (pull.stderr) warn(String(pull.stderr).trimEnd());
|
|
104
|
+
captureCommand('git', ['fetch', '--all', '--prune'], { cwd: repoRoot, env: ctx.env });
|
|
105
|
+
const st = captureCommand('git', ['status', '-sb'], { cwd: repoRoot, env: ctx.env });
|
|
106
|
+
if (st.stdout) info(String(st.stdout).trimEnd());
|
|
107
|
+
error('Could not fast-forward. Fix local commits or stash, then: cd install && git pull');
|
|
108
|
+
return 1;
|
|
84
109
|
}
|
|
110
|
+
if (pull.stdout) info(String(pull.stdout).trimEnd());
|
|
111
|
+
info('git pull OK.');
|
|
112
|
+
info('');
|
|
113
|
+
info('Restart to apply:');
|
|
114
|
+
info(' ragsuite-test stop');
|
|
115
|
+
info(' ragsuite-test start');
|
|
116
|
+
info('');
|
|
117
|
+
info('Never: docker compose down -v (would delete Postgres/Chroma data)');
|
|
85
118
|
return 0;
|
|
86
119
|
}
|
|
87
120
|
|
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',
|
|
@@ -41,8 +42,9 @@ function globalHelp() {
|
|
|
41
42
|
' --force init: overwrite non-empty install dir / .env',
|
|
42
43
|
' --yes, -y Non-interactive (same defaults as plain init)',
|
|
43
44
|
' --mode <docker|native> Override config (Docker optional)',
|
|
44
|
-
' --dry-run Print actions without running (start/stop/doctor/logs)',
|
|
45
|
+
' --dry-run Print actions without running (start/stop/doctor/logs/update)',
|
|
45
46
|
'',
|
|
47
|
+
'Active install: ~/.ragsuite-test/config.json (set by init; used by start/update/stop)',
|
|
46
48
|
'Env: RAGSUITE_REPO_ROOT or RAGSUITE_INSTALL_DIR',
|
|
47
49
|
' RAGSUITE_TEST_LLM_API_KEY, RAGSUITE_TEST_SMTP_* , RAGSUITE_TEST_EMAIL_FROM',
|
|
48
50
|
'',
|
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
|
|
29
|
-
' ragsuite-test
|
|
30
|
-
'Docker is optional — init picks native automatically if Docker is not running.',
|
|
28
|
+
'Day-to-day:',
|
|
29
|
+
' ragsuite-test start',
|
|
30
|
+
' ragsuite-test update',
|
|
31
|
+
' ragsuite-test stop',
|
|
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,
|
package/src/utils/git-install.js
CHANGED
|
@@ -1,18 +1,17 @@
|
|
|
1
1
|
'use strict';
|
|
2
2
|
|
|
3
3
|
const fs = require('fs');
|
|
4
|
-
const os = require('os');
|
|
5
4
|
const path = require('path');
|
|
6
5
|
const { spawnSync } = require('child_process');
|
|
7
|
-
const {
|
|
6
|
+
const {
|
|
7
|
+
defaultInstallDir,
|
|
8
|
+
looksLikeFullBundleRoot,
|
|
9
|
+
looksLikeRepoRoot,
|
|
10
|
+
} = require('./paths');
|
|
8
11
|
|
|
9
12
|
/** Default public monorepo (when the GitHub repo is public). */
|
|
10
13
|
const DEFAULT_GIT_URL = 'https://github.com/nitsan-ai/RAGSUITE.git';
|
|
11
14
|
|
|
12
|
-
function defaultInstallDir() {
|
|
13
|
-
return path.join(os.homedir(), 'ragsuite-test');
|
|
14
|
-
}
|
|
15
|
-
|
|
16
15
|
function requireGit() {
|
|
17
16
|
const which = spawnSync('which', ['git'], { encoding: 'utf8' });
|
|
18
17
|
if (which.status !== 0) {
|
|
@@ -49,7 +48,13 @@ function cloneGitToInstallDir(gitUrl, options = {}) {
|
|
|
49
48
|
if (!isDirEmptyOrMissing(target)) {
|
|
50
49
|
if (!force) {
|
|
51
50
|
const err = new Error(
|
|
52
|
-
|
|
51
|
+
[
|
|
52
|
+
`Install directory is not empty: ${target}`,
|
|
53
|
+
' If already installed: ragsuite-test start',
|
|
54
|
+
' To upgrade in place: ragsuite-test update',
|
|
55
|
+
' To wipe & reinstall: ragsuite-test init --force',
|
|
56
|
+
' Or use another path: ragsuite-test init --install-dir <path>',
|
|
57
|
+
].join('\n'),
|
|
53
58
|
);
|
|
54
59
|
err.code = 'INSTALL_DIR_NONEMPTY';
|
|
55
60
|
throw err;
|
|
@@ -0,0 +1,61 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
const fs = require('fs');
|
|
4
|
+
const os = require('os');
|
|
5
|
+
const path = require('path');
|
|
6
|
+
|
|
7
|
+
/** User-level CLI state (not inside the app install). Like other npm CLIs. */
|
|
8
|
+
function globalConfigDir() {
|
|
9
|
+
return path.join(os.homedir(), '.ragsuite-test');
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
function globalConfigPath() {
|
|
13
|
+
return path.join(globalConfigDir(), 'config.json');
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
function readGlobalConfig() {
|
|
17
|
+
const file = globalConfigPath();
|
|
18
|
+
if (!fs.existsSync(file)) return null;
|
|
19
|
+
try {
|
|
20
|
+
const raw = JSON.parse(fs.readFileSync(file, 'utf8'));
|
|
21
|
+
if (!raw || typeof raw !== 'object') return null;
|
|
22
|
+
return {
|
|
23
|
+
installDir: raw.installDir ? path.resolve(String(raw.installDir)) : null,
|
|
24
|
+
updatedAt: raw.updatedAt || null,
|
|
25
|
+
};
|
|
26
|
+
} catch {
|
|
27
|
+
return null;
|
|
28
|
+
}
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
function writeGlobalConfig(partial = {}) {
|
|
32
|
+
const dir = globalConfigDir();
|
|
33
|
+
fs.mkdirSync(dir, { recursive: true });
|
|
34
|
+
const existing = readGlobalConfig() || {};
|
|
35
|
+
const next = {
|
|
36
|
+
...existing,
|
|
37
|
+
...partial,
|
|
38
|
+
updatedAt: new Date().toISOString(),
|
|
39
|
+
};
|
|
40
|
+
if (next.installDir) next.installDir = path.resolve(String(next.installDir));
|
|
41
|
+
fs.writeFileSync(globalConfigPath(), `${JSON.stringify(next, null, 2)}\n`, 'utf8');
|
|
42
|
+
return next;
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
function setActiveInstall(installDir) {
|
|
46
|
+
return writeGlobalConfig({ installDir: path.resolve(installDir) });
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
function getActiveInstall() {
|
|
50
|
+
const g = readGlobalConfig();
|
|
51
|
+
return g && g.installDir ? g.installDir : null;
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
module.exports = {
|
|
55
|
+
globalConfigDir,
|
|
56
|
+
globalConfigPath,
|
|
57
|
+
readGlobalConfig,
|
|
58
|
+
writeGlobalConfig,
|
|
59
|
+
setActiveInstall,
|
|
60
|
+
getActiveInstall,
|
|
61
|
+
};
|
|
@@ -0,0 +1,106 @@
|
|
|
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
|
+
[
|
|
42
|
+
`Install directory is not empty: ${target}`,
|
|
43
|
+
' If already installed: ragsuite-test start',
|
|
44
|
+
' To upgrade in place: ragsuite-test update',
|
|
45
|
+
' To wipe & reinstall: ragsuite-test init --from-images --force',
|
|
46
|
+
' Or use another path: ragsuite-test init --from-images --install-dir <path>',
|
|
47
|
+
].join('\n'),
|
|
48
|
+
);
|
|
49
|
+
err.code = 'INSTALL_DIR_NONEMPTY';
|
|
50
|
+
throw err;
|
|
51
|
+
}
|
|
52
|
+
fs.rmSync(target, { recursive: true, force: true });
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
fs.mkdirSync(target, { recursive: true });
|
|
56
|
+
copyFile(
|
|
57
|
+
path.join(srcRoot, 'docker-compose.yml'),
|
|
58
|
+
path.join(target, 'docker-compose.yml'),
|
|
59
|
+
);
|
|
60
|
+
copyFile(
|
|
61
|
+
path.join(srcRoot, '.env.example'),
|
|
62
|
+
path.join(target, '.env.example'),
|
|
63
|
+
);
|
|
64
|
+
copyFile(
|
|
65
|
+
path.join(srcRoot, 'scripts', 'docker-start-images.sh'),
|
|
66
|
+
path.join(target, 'scripts', 'docker-start-images.sh'),
|
|
67
|
+
);
|
|
68
|
+
copyFile(
|
|
69
|
+
path.join(srcRoot, 'scripts', 'docker-stop-images.sh'),
|
|
70
|
+
path.join(target, 'scripts', 'docker-stop-images.sh'),
|
|
71
|
+
);
|
|
72
|
+
copyFile(
|
|
73
|
+
path.join(srcRoot, 'scripts', 'docker-doctor-images.sh'),
|
|
74
|
+
path.join(target, 'scripts', 'docker-doctor-images.sh'),
|
|
75
|
+
);
|
|
76
|
+
try {
|
|
77
|
+
fs.chmodSync(path.join(target, 'scripts', 'docker-start-images.sh'), 0o755);
|
|
78
|
+
fs.chmodSync(path.join(target, 'scripts', 'docker-stop-images.sh'), 0o755);
|
|
79
|
+
fs.chmodSync(path.join(target, 'scripts', 'docker-doctor-images.sh'), 0o755);
|
|
80
|
+
} catch {
|
|
81
|
+
/* windows */
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
// Minimal package.json so tooling can identify the install dir
|
|
85
|
+
fs.writeFileSync(
|
|
86
|
+
path.join(target, 'package.json'),
|
|
87
|
+
`${JSON.stringify(
|
|
88
|
+
{
|
|
89
|
+
name: 'ragsuite-images-install',
|
|
90
|
+
private: true,
|
|
91
|
+
description: 'RAGSuite pull-only images install (no source tree)',
|
|
92
|
+
},
|
|
93
|
+
null,
|
|
94
|
+
2,
|
|
95
|
+
)}\n`,
|
|
96
|
+
'utf8',
|
|
97
|
+
);
|
|
98
|
+
|
|
99
|
+
return { repoRoot: target };
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
module.exports = {
|
|
103
|
+
templatesRoot,
|
|
104
|
+
materializeImagesInstall,
|
|
105
|
+
defaultInstallDir,
|
|
106
|
+
};
|