Files
greatbear/scripts/run-all.mjs
T
tanshu 757ec8aee2 Rebuild ansible on the project deploy template
- playbook.yml (hosts: monoco) + vars/default.yml, requirements.yml
- roles: network (shared docker network), greatbear (registry pull,
  bind mount /var/lib/greatbear/pb_data, .env upload, health wait),
  caddy (blockinfile snippet into the shared Caddyfile + docker exec reload)
- image honors PB_DATA_DIR and runs as root to match the bind-mount pattern
- frontend on www.greatbear.in (+apex) with /api/* and /_* proxied to
  PocketBase; admin.greatbear.in redirects to the admin UI
2026-09-03 10:14:11 +05:30

117 lines
3.4 KiB
JavaScript
Executable File

#!/usr/bin/env node
/**
* Container entrypoint supervisor.
*
* Runs PocketBase and the TanStack Start node server side by side:
* 1. starts PocketBase (data in /data, migrations from ./pb_migrations)
* 2. waits for its health endpoint
* 3. bootstraps the superuser account (idempotent upsert)
* 4. seeds initial content (idempotent, create-if-missing)
* 5. starts the site server on PORT (default 3000)
*
* If either process dies the container exits non-zero so Docker restarts it.
*/
import { spawn, spawnSync } from 'node:child_process'
const PORT = process.env.PORT ?? '3000'
const PB_HTTP = process.env.PB_HTTP ?? '0.0.0.0:8090'
// PB_DATA_DIR is the canonical variable (set in the deploy .env); PB_DATA kept
// as a legacy alias, /data as the image default.
const PB_DATA = process.env.PB_DATA_DIR ?? process.env.PB_DATA ?? '/data'
let stopping = false
const children = []
function start(name, command, args, extraEnv = {}) {
const child = spawn(command, args, {
stdio: 'inherit',
env: { ...process.env, ...extraEnv },
})
child.name = name
children.push(child)
child.on('exit', (code) => {
if (stopping) return
console.error(`[supervisor] ${name} exited with code ${code} — shutting down`)
shutdown(code ?? 1)
})
return child
}
function run(name, command, args, extraEnv = {}) {
const result = spawnSync(command, args, {
stdio: 'inherit',
env: { ...process.env, ...extraEnv },
})
if (result.status !== 0) {
console.warn(`[supervisor] ${name} finished with status ${result.status}`)
}
return result.status ?? 0
}
function shutdown(exitCode = 0) {
if (stopping) return
stopping = true
for (const child of children) {
if (child.exitCode === null && child.signalCode === null) child.kill('SIGTERM')
}
setTimeout(() => process.exit(exitCode), 2000)
}
process.on('SIGINT', () => shutdown(0))
process.on('SIGTERM', () => shutdown(0))
async function waitForHealth(url, attempts = 120) {
for (let i = 0; i < attempts; i++) {
try {
const res = await fetch(url)
if (res.ok) return
} catch {
// not up yet
}
await new Promise((resolve) => setTimeout(resolve, 500))
}
throw new Error(`health check at ${url} timed out`)
}
async function main() {
console.log('[supervisor] starting PocketBase…')
start('pocketbase', './pocketbase', [
'serve',
`--http=${PB_HTTP}`,
`--dir=${PB_DATA}`,
'--migrationsDir=./pb_migrations',
'--publicDir=./pb_public',
])
await waitForHealth('http://127.0.0.1:8090/api/health')
console.log('[supervisor] PocketBase is healthy')
if (process.env.PB_SUPERUSER_EMAIL && process.env.PB_SUPERUSER_PASSWORD) {
run('superuser upsert', './pocketbase', [
'superuser',
'upsert',
process.env.PB_SUPERUSER_EMAIL,
process.env.PB_SUPERUSER_PASSWORD,
`--dir=${PB_DATA}`,
'--migrationsDir=./pb_migrations',
])
console.log('[supervisor] superuser ensured')
run('seed', 'node', ['scripts/seed.mjs'])
} else {
console.warn('[supervisor] PB_SUPERUSER_EMAIL/PASSWORD not set — skipping superuser + seed')
}
console.log(`[supervisor] starting site server on :${PORT}…`)
start('site', 'node', ['.output/server/index.mjs'], {
PORT,
PB_URL: process.env.PB_URL ?? 'http://127.0.0.1:8090',
NODE_ENV: 'production',
})
}
main().catch((error) => {
console.error(`[supervisor] fatal: ${error.message}`)
shutdown(1)
})