@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/utils/paths.js
CHANGED
|
@@ -1,50 +1,69 @@
|
|
|
1
1
|
'use strict';
|
|
2
2
|
|
|
3
3
|
const fs = require('fs');
|
|
4
|
+
const os = require('os');
|
|
4
5
|
const path = require('path');
|
|
5
6
|
const { readConfig, CONFIG_DIR } = require('./config');
|
|
7
|
+
const { getActiveInstall, setActiveInstall } = require('./global-config');
|
|
6
8
|
|
|
7
|
-
|
|
9
|
+
/** Default app install dir (npm-CLI style home install). */
|
|
10
|
+
function defaultInstallDir() {
|
|
11
|
+
return path.join(os.homedir(), 'ragsuite-test');
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
function looksLikeGitCheckout(dir) {
|
|
8
15
|
const start = path.join(dir, 'scripts', 'docker-start.sh');
|
|
9
16
|
const pkg = path.join(dir, 'package.json');
|
|
10
17
|
return fs.existsSync(start) && fs.existsSync(pkg);
|
|
11
18
|
}
|
|
12
19
|
|
|
13
|
-
function
|
|
14
|
-
if (!looksLikeRepoRoot(dir)) return false;
|
|
20
|
+
function looksLikeImagesInstall(dir) {
|
|
15
21
|
const compose = path.join(dir, 'docker-compose.yml');
|
|
16
|
-
|
|
22
|
+
const start = path.join(dir, 'scripts', 'docker-start-images.sh');
|
|
23
|
+
if (!fs.existsSync(compose) || !fs.existsSync(start)) return false;
|
|
24
|
+
const cfgPath = path.join(dir, CONFIG_DIR, 'config.json');
|
|
25
|
+
if (fs.existsSync(cfgPath)) {
|
|
26
|
+
try {
|
|
27
|
+
const cfg = JSON.parse(fs.readFileSync(cfgPath, 'utf8'));
|
|
28
|
+
if (cfg.source === 'images') return true;
|
|
29
|
+
} catch {
|
|
30
|
+
/* fall through */
|
|
31
|
+
}
|
|
32
|
+
}
|
|
33
|
+
return !fs.existsSync(path.join(dir, 'scripts', 'docker-start.sh'));
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
function looksLikeRepoRoot(dir) {
|
|
37
|
+
return looksLikeGitCheckout(dir) || looksLikeImagesInstall(dir);
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
function looksLikeFullBundleRoot(dir) {
|
|
41
|
+
if (!looksLikeGitCheckout(dir)) return false;
|
|
42
|
+
return fs.existsSync(path.join(dir, 'docker-compose.yml'));
|
|
17
43
|
}
|
|
18
44
|
|
|
19
45
|
function walkUpForRepoRoot(startDir) {
|
|
20
46
|
let current = path.resolve(startDir);
|
|
21
47
|
for (;;) {
|
|
22
|
-
if (looksLikeRepoRoot(current))
|
|
23
|
-
return current;
|
|
24
|
-
}
|
|
48
|
+
if (looksLikeRepoRoot(current)) return current;
|
|
25
49
|
const parent = path.dirname(current);
|
|
26
|
-
if (parent === current)
|
|
27
|
-
return null;
|
|
28
|
-
}
|
|
50
|
+
if (parent === current) return null;
|
|
29
51
|
current = parent;
|
|
30
52
|
}
|
|
31
53
|
}
|
|
32
54
|
|
|
33
55
|
function envRepoRoot(env = process.env) {
|
|
34
|
-
const
|
|
35
|
-
for (const raw of candidates) {
|
|
56
|
+
for (const raw of [env.RAGSUITE_REPO_ROOT, env.RAGSUITE_INSTALL_DIR]) {
|
|
36
57
|
if (!raw || !String(raw).trim()) continue;
|
|
37
58
|
const root = path.resolve(String(raw).trim());
|
|
38
|
-
if (looksLikeRepoRoot(root))
|
|
39
|
-
return root;
|
|
40
|
-
}
|
|
59
|
+
if (looksLikeRepoRoot(root)) return root;
|
|
41
60
|
}
|
|
42
61
|
return null;
|
|
43
62
|
}
|
|
44
63
|
|
|
45
64
|
/**
|
|
46
|
-
* Resolve
|
|
47
|
-
* --repo-root →
|
|
65
|
+
* Resolve install root (npm-CLI style):
|
|
66
|
+
* --repo-root → env → cwd walk-up → ~/.ragsuite-test active install → ~/ragsuite-test
|
|
48
67
|
*/
|
|
49
68
|
function resolveRepoRoot({
|
|
50
69
|
cwd = process.cwd(),
|
|
@@ -56,16 +75,18 @@ function resolveRepoRoot({
|
|
|
56
75
|
const root = path.resolve(repoRootFlag);
|
|
57
76
|
if (!looksLikeRepoRoot(root)) {
|
|
58
77
|
const err = new Error(
|
|
59
|
-
`Not a RAGSuite
|
|
78
|
+
`Not a RAGSuite install: ${root}. Run: ragsuite-test init`,
|
|
60
79
|
);
|
|
61
80
|
err.code = 'NOT_REPO_ROOT';
|
|
62
81
|
throw err;
|
|
63
82
|
}
|
|
83
|
+
setActiveInstall(root);
|
|
64
84
|
return root;
|
|
65
85
|
}
|
|
66
86
|
|
|
67
87
|
const fromEnv = envRepoRoot(env);
|
|
68
88
|
if (fromEnv) {
|
|
89
|
+
setActiveInstall(fromEnv);
|
|
69
90
|
return fromEnv;
|
|
70
91
|
}
|
|
71
92
|
|
|
@@ -77,21 +98,41 @@ function resolveRepoRoot({
|
|
|
77
98
|
for (const key of ['repoRoot', 'installDir']) {
|
|
78
99
|
const candidate = cfg[key];
|
|
79
100
|
if (candidate && looksLikeRepoRoot(candidate)) {
|
|
80
|
-
|
|
101
|
+
const resolved = path.resolve(candidate);
|
|
102
|
+
setActiveInstall(resolved);
|
|
103
|
+
return resolved;
|
|
81
104
|
}
|
|
82
105
|
}
|
|
83
106
|
}
|
|
107
|
+
setActiveInstall(walked);
|
|
84
108
|
return walked;
|
|
85
109
|
}
|
|
86
110
|
}
|
|
87
111
|
|
|
88
112
|
const walked = walkUpForRepoRoot(cwd);
|
|
89
113
|
if (walked) {
|
|
114
|
+
setActiveInstall(walked);
|
|
90
115
|
return walked;
|
|
91
116
|
}
|
|
92
117
|
|
|
118
|
+
const active = getActiveInstall();
|
|
119
|
+
if (active && looksLikeRepoRoot(active)) {
|
|
120
|
+
return active;
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
const fallback = defaultInstallDir();
|
|
124
|
+
if (looksLikeRepoRoot(fallback)) {
|
|
125
|
+
setActiveInstall(fallback);
|
|
126
|
+
return fallback;
|
|
127
|
+
}
|
|
128
|
+
|
|
93
129
|
const err = new Error(
|
|
94
|
-
|
|
130
|
+
[
|
|
131
|
+
'No RAGSuite install found.',
|
|
132
|
+
' First time: ragsuite-test init',
|
|
133
|
+
' Or images: ragsuite-test init --from-images',
|
|
134
|
+
' Then: ragsuite-test start',
|
|
135
|
+
].join('\n'),
|
|
95
136
|
);
|
|
96
137
|
err.code = 'NOT_REPO_ROOT';
|
|
97
138
|
throw err;
|
|
@@ -107,13 +148,26 @@ function ensureRagsuiteDir(repoRoot) {
|
|
|
107
148
|
return dir;
|
|
108
149
|
}
|
|
109
150
|
|
|
110
|
-
/** Native PID/logs stay under .ragsuite/native (Phase 4 scripts). */
|
|
111
151
|
function nativeLogDir(repoRoot) {
|
|
112
152
|
return path.join(repoRoot, '.ragsuite', 'native');
|
|
113
153
|
}
|
|
114
154
|
|
|
155
|
+
function isImagesInstall(repoRoot) {
|
|
156
|
+
const cfg = readConfig(repoRoot);
|
|
157
|
+
if (cfg && cfg.source === 'images') return true;
|
|
158
|
+
return looksLikeImagesInstall(repoRoot) && !looksLikeGitCheckout(repoRoot);
|
|
159
|
+
}
|
|
160
|
+
|
|
161
|
+
function isDirEmptyOrMissing(dir) {
|
|
162
|
+
if (!fs.existsSync(dir)) return true;
|
|
163
|
+
return fs.readdirSync(dir).filter((n) => n !== '.DS_Store').length === 0;
|
|
164
|
+
}
|
|
165
|
+
|
|
115
166
|
module.exports = {
|
|
167
|
+
defaultInstallDir,
|
|
116
168
|
looksLikeRepoRoot,
|
|
169
|
+
looksLikeGitCheckout,
|
|
170
|
+
looksLikeImagesInstall,
|
|
117
171
|
looksLikeFullBundleRoot,
|
|
118
172
|
walkUpForRepoRoot,
|
|
119
173
|
envRepoRoot,
|
|
@@ -121,4 +175,6 @@ module.exports = {
|
|
|
121
175
|
scriptPath,
|
|
122
176
|
ensureRagsuiteDir,
|
|
123
177
|
nativeLogDir,
|
|
178
|
+
isImagesInstall,
|
|
179
|
+
isDirEmptyOrMissing,
|
|
124
180
|
};
|
package/src/utils/port.js
CHANGED
|
@@ -66,7 +66,7 @@ async function assertPortsFree(ports = DEFAULT_INSTALL_PORTS) {
|
|
|
66
66
|
'Free them, then retry. This CLI will not kill foreign processes.',
|
|
67
67
|
'',
|
|
68
68
|
'If a previous RAGSuite install is running:',
|
|
69
|
-
' ragsuite-test stop
|
|
69
|
+
' ragsuite-test stop',
|
|
70
70
|
' # or from a monorepo checkout: cd <checkout> && npm run down',
|
|
71
71
|
'',
|
|
72
72
|
'See what owns the ports (macOS/Linux):',
|
|
@@ -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
|