4runr-os 2.10.69 → 2.10.70
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/apps/gateway/dist/apps/gateway/src/db/docker-manager.d.ts +4 -0
- package/apps/gateway/dist/apps/gateway/src/db/docker-manager.d.ts.map +1 -1
- package/apps/gateway/dist/apps/gateway/src/db/docker-manager.js +55 -45
- package/apps/gateway/dist/apps/gateway/src/db/docker-manager.js.map +1 -1
- package/apps/gateway/dist/apps/gateway/src/db/init.d.ts.map +1 -1
- package/apps/gateway/dist/apps/gateway/src/db/init.js +4 -11
- package/apps/gateway/dist/apps/gateway/src/db/init.js.map +1 -1
- package/apps/gateway/docker-compose.local.yml +2 -0
- package/apps/gateway/src/db/docker-manager.ts +98 -62
- package/apps/gateway/src/db/init.ts +9 -15
- package/dist/tui-handlers.js +129 -23
- package/dist/tui-handlers.js.map +1 -1
- package/dist/watchdog.js +2 -2
- package/dist/watchdog.js.map +1 -1
- package/package.json +2 -2
package/dist/tui-handlers.js
CHANGED
|
@@ -1606,7 +1606,7 @@ async function handleGatewayConnect(ctx) {
|
|
|
1606
1606
|
level: 'info',
|
|
1607
1607
|
message: 'Attempting to restart Docker containers...',
|
|
1608
1608
|
});
|
|
1609
|
-
const redisUrl = tryTuiAutostartDockerComposeStack(bundleCheck.path, activityLog, getCurrentTime);
|
|
1609
|
+
const redisUrl = await tryTuiAutostartDockerComposeStack(bundleCheck.path, activityLog, getCurrentTime);
|
|
1610
1610
|
if (redisUrl) {
|
|
1611
1611
|
await new Promise((r) => setTimeout(r, 3000));
|
|
1612
1612
|
// Re-verify
|
|
@@ -2267,6 +2267,8 @@ async function isGatewayHealthy(url) {
|
|
|
2267
2267
|
}
|
|
2268
2268
|
}
|
|
2269
2269
|
const TUI_LOCAL_REDIS_DEFAULT = 'redis://127.0.0.1:6379';
|
|
2270
|
+
const TUI_DB_CONTAINERS = ['4runr-postgres', '4runr-redis'];
|
|
2271
|
+
const TUI_COMPOSE_PROJECT = '4runr';
|
|
2270
2272
|
function tuiAutostartDockerStackDisabledByEnv() {
|
|
2271
2273
|
if (process.env['FOURRUNR_NO_AUTO_REDIS'] === '1' || process.env['FOURRUNR_NO_AUTO_REDIS'] === 'true') {
|
|
2272
2274
|
return true;
|
|
@@ -2279,24 +2281,90 @@ function tuiAutostartDockerStackDisabledByEnv() {
|
|
|
2279
2281
|
}
|
|
2280
2282
|
return false;
|
|
2281
2283
|
}
|
|
2282
|
-
|
|
2283
|
-
|
|
2284
|
-
|
|
2285
|
-
|
|
2286
|
-
|
|
2287
|
-
|
|
2284
|
+
function tuiDockerShell() {
|
|
2285
|
+
return process.platform === 'win32' ? (process.env['ComSpec'] || 'cmd.exe') : '/bin/sh';
|
|
2286
|
+
}
|
|
2287
|
+
function tuiIsContainerRunning(containerName) {
|
|
2288
|
+
try {
|
|
2289
|
+
const state = execSync(`docker inspect --format="{{.State.Running}}" ${containerName}`, {
|
|
2290
|
+
encoding: 'utf-8',
|
|
2291
|
+
stdio: 'pipe',
|
|
2292
|
+
timeout: 5000,
|
|
2293
|
+
windowsHide: true,
|
|
2294
|
+
shell: tuiDockerShell(),
|
|
2295
|
+
}).trim();
|
|
2296
|
+
return state === 'true';
|
|
2297
|
+
}
|
|
2298
|
+
catch {
|
|
2299
|
+
return false;
|
|
2300
|
+
}
|
|
2301
|
+
}
|
|
2302
|
+
function tuiStartStoppedContainers(activityLog, getCurrentTimeFn) {
|
|
2303
|
+
const stopped = TUI_DB_CONTAINERS.filter((name) => !tuiIsContainerRunning(name));
|
|
2304
|
+
if (stopped.length === 0) {
|
|
2305
|
+
return;
|
|
2306
|
+
}
|
|
2307
|
+
try {
|
|
2308
|
+
execSync(`docker start ${stopped.join(' ')}`, {
|
|
2309
|
+
stdio: 'ignore',
|
|
2310
|
+
windowsHide: true,
|
|
2311
|
+
timeout: 60000,
|
|
2312
|
+
shell: tuiDockerShell(),
|
|
2313
|
+
});
|
|
2288
2314
|
activityLog.push({
|
|
2289
2315
|
timestamp: getCurrentTimeFn(),
|
|
2290
2316
|
level: 'info',
|
|
2291
|
-
message:
|
|
2317
|
+
message: `Docker: started stopped container(s): ${stopped.join(', ')}`,
|
|
2292
2318
|
});
|
|
2293
|
-
return undefined;
|
|
2294
2319
|
}
|
|
2295
|
-
|
|
2320
|
+
catch {
|
|
2321
|
+
// May not exist yet — compose up will create them
|
|
2322
|
+
}
|
|
2323
|
+
}
|
|
2324
|
+
async function tuiWaitForContainerHealthy(containerName, timeoutMs) {
|
|
2325
|
+
const start = Date.now();
|
|
2326
|
+
while (Date.now() - start < timeoutMs) {
|
|
2327
|
+
try {
|
|
2328
|
+
const health = execSync(`docker inspect --format="{{.State.Health.Status}}" ${containerName}`, {
|
|
2329
|
+
encoding: 'utf-8',
|
|
2330
|
+
stdio: 'pipe',
|
|
2331
|
+
timeout: 5000,
|
|
2332
|
+
windowsHide: true,
|
|
2333
|
+
shell: tuiDockerShell(),
|
|
2334
|
+
}).trim();
|
|
2335
|
+
if (health === 'healthy') {
|
|
2336
|
+
return true;
|
|
2337
|
+
}
|
|
2338
|
+
if (health === '' || health === '<no value>') {
|
|
2339
|
+
const running = execSync(`docker inspect --format="{{.State.Running}}" ${containerName}`, {
|
|
2340
|
+
encoding: 'utf-8',
|
|
2341
|
+
stdio: 'pipe',
|
|
2342
|
+
timeout: 5000,
|
|
2343
|
+
windowsHide: true,
|
|
2344
|
+
shell: tuiDockerShell(),
|
|
2345
|
+
}).trim();
|
|
2346
|
+
if (running === 'true') {
|
|
2347
|
+
return true;
|
|
2348
|
+
}
|
|
2349
|
+
}
|
|
2350
|
+
}
|
|
2351
|
+
catch {
|
|
2352
|
+
// keep waiting
|
|
2353
|
+
}
|
|
2354
|
+
await new Promise((r) => setTimeout(r, 1000));
|
|
2355
|
+
}
|
|
2356
|
+
return false;
|
|
2357
|
+
}
|
|
2358
|
+
/**
|
|
2359
|
+
* Start Postgres + Redis together via the same compose file the Gateway uses.
|
|
2360
|
+
* Watchdog stops both when 4r exits — always ensure both are back up on connect.
|
|
2361
|
+
*/
|
|
2362
|
+
async function tryTuiAutostartDockerComposeStack(bundlePath, activityLog, getCurrentTimeFn) {
|
|
2363
|
+
if (tuiAutostartDockerStackDisabledByEnv()) {
|
|
2296
2364
|
activityLog.push({
|
|
2297
2365
|
timestamp: getCurrentTimeFn(),
|
|
2298
2366
|
level: 'info',
|
|
2299
|
-
message: 'Autostart Docker stack: skipped (
|
|
2367
|
+
message: 'Autostart Docker stack: skipped (FOURRUNR_NO_AUTO_DOCKER / FOURRUNR_NO_AUTO_REDIS / FOURRUNR_SPAWN_REDIS=0).',
|
|
2300
2368
|
});
|
|
2301
2369
|
return undefined;
|
|
2302
2370
|
}
|
|
@@ -2309,21 +2377,59 @@ function tryTuiAutostartDockerComposeStack(bundlePath, activityLog, getCurrentTi
|
|
|
2309
2377
|
});
|
|
2310
2378
|
return undefined;
|
|
2311
2379
|
}
|
|
2312
|
-
const dockerShell =
|
|
2380
|
+
const dockerShell = tuiDockerShell();
|
|
2381
|
+
const allRunning = TUI_DB_CONTAINERS.every((name) => tuiIsContainerRunning(name));
|
|
2382
|
+
if (allRunning) {
|
|
2383
|
+
activityLog.push({
|
|
2384
|
+
timestamp: getCurrentTimeFn(),
|
|
2385
|
+
level: 'info',
|
|
2386
|
+
message: 'Docker: Postgres + Redis already running (4runr-postgres :5432, 4runr-redis :6379).',
|
|
2387
|
+
});
|
|
2388
|
+
return String(process.env['REDIS_URL'] || '').trim() || TUI_LOCAL_REDIS_DEFAULT;
|
|
2389
|
+
}
|
|
2390
|
+
if (String(process.env['REDIS_URL'] || '').trim() !== '') {
|
|
2391
|
+
activityLog.push({
|
|
2392
|
+
timestamp: getCurrentTimeFn(),
|
|
2393
|
+
level: 'info',
|
|
2394
|
+
message: 'REDIS_URL is set in environment — still starting Postgres + Redis Docker stack for persistence.',
|
|
2395
|
+
});
|
|
2396
|
+
}
|
|
2313
2397
|
try {
|
|
2314
|
-
|
|
2315
|
-
|
|
2316
|
-
|
|
2317
|
-
|
|
2318
|
-
|
|
2319
|
-
|
|
2398
|
+
tuiStartStoppedContainers(activityLog, getCurrentTimeFn);
|
|
2399
|
+
if (!TUI_DB_CONTAINERS.every((name) => tuiIsContainerRunning(name))) {
|
|
2400
|
+
execSync(`docker compose -p ${TUI_COMPOSE_PROJECT} -f "${composeFile}" up -d`, {
|
|
2401
|
+
cwd: bundlePath,
|
|
2402
|
+
stdio: 'ignore',
|
|
2403
|
+
windowsHide: true,
|
|
2404
|
+
timeout: 120000,
|
|
2405
|
+
shell: dockerShell,
|
|
2406
|
+
});
|
|
2407
|
+
}
|
|
2408
|
+
activityLog.push({
|
|
2409
|
+
timestamp: getCurrentTimeFn(),
|
|
2410
|
+
level: 'info',
|
|
2411
|
+
message: 'Waiting for Postgres + Redis to become healthy...',
|
|
2320
2412
|
});
|
|
2413
|
+
// Postgres cold start after watchdog stop can take 30–60s
|
|
2414
|
+
const pgOk = await tuiWaitForContainerHealthy('4runr-postgres', 60000);
|
|
2415
|
+
const redisOk = await tuiWaitForContainerHealthy('4runr-redis', 15000);
|
|
2416
|
+
if (!pgOk || !redisOk) {
|
|
2417
|
+
const down = [!pgOk ? '4runr-postgres' : null, !redisOk ? '4runr-redis' : null]
|
|
2418
|
+
.filter(Boolean)
|
|
2419
|
+
.join(', ');
|
|
2420
|
+
activityLog.push({
|
|
2421
|
+
timestamp: getCurrentTimeFn(),
|
|
2422
|
+
level: 'warning',
|
|
2423
|
+
message: `Autostart Docker stack: ${down} did not become healthy in time. Gateway may fall back to memory DB.`,
|
|
2424
|
+
});
|
|
2425
|
+
return undefined;
|
|
2426
|
+
}
|
|
2321
2427
|
activityLog.push({
|
|
2322
2428
|
timestamp: getCurrentTimeFn(),
|
|
2323
2429
|
level: 'success',
|
|
2324
2430
|
message: 'Docker: Postgres + Redis started via docker-compose.local.yml (4runr-postgres :5432, 4runr-redis :6379).',
|
|
2325
2431
|
});
|
|
2326
|
-
return TUI_LOCAL_REDIS_DEFAULT;
|
|
2432
|
+
return String(process.env['REDIS_URL'] || '').trim() || TUI_LOCAL_REDIS_DEFAULT;
|
|
2327
2433
|
}
|
|
2328
2434
|
catch (e) {
|
|
2329
2435
|
activityLog.push({
|
|
@@ -2387,10 +2493,7 @@ async function startLocalGatewayAndWait(bundlePath, url, activityLog, getCurrent
|
|
|
2387
2493
|
});
|
|
2388
2494
|
shutdownAutostartedGateway(portNum);
|
|
2389
2495
|
await new Promise((r) => setTimeout(r, 400));
|
|
2390
|
-
const tuiRedisUrl = tryTuiAutostartDockerComposeStack(bundlePath, activityLog, getCurrentTimeFn);
|
|
2391
|
-
if (tuiRedisUrl) {
|
|
2392
|
-
await new Promise((r) => setTimeout(r, 1500));
|
|
2393
|
-
}
|
|
2496
|
+
const tuiRedisUrl = await tryTuiAutostartDockerComposeStack(bundlePath, activityLog, getCurrentTimeFn);
|
|
2394
2497
|
const passThroughRedis = process.env['REDIS_URL'];
|
|
2395
2498
|
if (!tuiRedisUrl && !String(passThroughRedis || '').trim()) {
|
|
2396
2499
|
activityLog.push({
|
|
@@ -2414,6 +2517,9 @@ async function startLocalGatewayAndWait(bundlePath, url, activityLog, getCurrent
|
|
|
2414
2517
|
};
|
|
2415
2518
|
if (tuiRedisUrl) {
|
|
2416
2519
|
childEnv['REDIS_URL'] = tuiRedisUrl;
|
|
2520
|
+
childEnv['DATABASE_URL'] =
|
|
2521
|
+
process.env['DATABASE_URL'] ||
|
|
2522
|
+
'postgresql://4runr:4runr_dev_pass@localhost:5432/4runr_local';
|
|
2417
2523
|
}
|
|
2418
2524
|
const commonOpts = {
|
|
2419
2525
|
cwd: bundlePath,
|