@supatype/cli 0.1.0-alpha.6 → 0.1.0-alpha.8
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/.turbo/turbo-build.log +1 -1
- package/.turbo/turbo-test.log +208 -1
- package/.turbo/turbo-typecheck.log +1 -1
- package/dist/app/proxy-dev-app.d.ts +13 -0
- package/dist/app/proxy-dev-app.d.ts.map +1 -0
- package/dist/app/proxy-dev-app.js +53 -0
- package/dist/app/proxy-dev-app.js.map +1 -0
- package/dist/app-config.d.ts +7 -0
- package/dist/app-config.d.ts.map +1 -0
- package/dist/app-config.js +113 -0
- package/dist/app-config.js.map +1 -0
- package/dist/augmentation-generator.d.ts +2 -0
- package/dist/augmentation-generator.d.ts.map +1 -0
- package/dist/augmentation-generator.js +111 -0
- package/dist/augmentation-generator.js.map +1 -0
- package/dist/binary-cache.d.ts +94 -0
- package/dist/binary-cache.d.ts.map +1 -0
- package/dist/binary-cache.js +669 -0
- package/dist/binary-cache.js.map +1 -0
- package/dist/cli.d.ts.map +1 -1
- package/dist/cli.js +13 -7
- package/dist/cli.js.map +1 -1
- package/dist/commands/admin.d.ts.map +1 -1
- package/dist/commands/admin.js +4 -3
- package/dist/commands/admin.js.map +1 -1
- package/dist/commands/app.d.ts.map +1 -1
- package/dist/commands/app.js +56 -209
- package/dist/commands/app.js.map +1 -1
- package/dist/commands/cache.d.ts +6 -0
- package/dist/commands/cache.d.ts.map +1 -0
- package/dist/commands/cache.js +105 -0
- package/dist/commands/cache.js.map +1 -0
- package/dist/commands/cloud.d.ts +20 -0
- package/dist/commands/cloud.d.ts.map +1 -1
- package/dist/commands/cloud.js +50 -52
- package/dist/commands/cloud.js.map +1 -1
- package/dist/commands/db.d.ts.map +1 -1
- package/dist/commands/db.js +47 -54
- package/dist/commands/db.js.map +1 -1
- package/dist/commands/deploy.d.ts +2 -1
- package/dist/commands/deploy.d.ts.map +1 -1
- package/dist/commands/deploy.js +79 -52
- package/dist/commands/deploy.js.map +1 -1
- package/dist/commands/dev.d.ts +11 -0
- package/dist/commands/dev.d.ts.map +1 -1
- package/dist/commands/dev.js +759 -385
- package/dist/commands/dev.js.map +1 -1
- package/dist/commands/diff.d.ts.map +1 -1
- package/dist/commands/diff.js +30 -15
- package/dist/commands/diff.js.map +1 -1
- package/dist/commands/engine.d.ts +1 -3
- package/dist/commands/engine.d.ts.map +1 -1
- package/dist/commands/engine.js +13 -85
- package/dist/commands/engine.js.map +1 -1
- package/dist/commands/functions.d.ts.map +1 -1
- package/dist/commands/functions.js +92 -105
- package/dist/commands/functions.js.map +1 -1
- package/dist/commands/generate.d.ts.map +1 -1
- package/dist/commands/generate.js +22 -12
- package/dist/commands/generate.js.map +1 -1
- package/dist/commands/init.d.ts +1 -1
- package/dist/commands/init.d.ts.map +1 -1
- package/dist/commands/init.js +137 -410
- package/dist/commands/init.js.map +1 -1
- package/dist/commands/migrate-from-v1.d.ts +5 -0
- package/dist/commands/migrate-from-v1.d.ts.map +1 -0
- package/dist/commands/migrate-from-v1.js +125 -0
- package/dist/commands/migrate-from-v1.js.map +1 -0
- package/dist/commands/migrate.d.ts.map +1 -1
- package/dist/commands/migrate.js +27 -23
- package/dist/commands/migrate.js.map +1 -1
- package/dist/commands/pg.d.ts +8 -0
- package/dist/commands/pg.d.ts.map +1 -0
- package/dist/commands/pg.js +102 -0
- package/dist/commands/pg.js.map +1 -0
- package/dist/commands/pull.d.ts.map +1 -1
- package/dist/commands/pull.js +5 -66
- package/dist/commands/pull.js.map +1 -1
- package/dist/commands/push.d.ts.map +1 -1
- package/dist/commands/push.js +128 -38
- package/dist/commands/push.js.map +1 -1
- package/dist/commands/seed.d.ts +2 -0
- package/dist/commands/seed.d.ts.map +1 -1
- package/dist/commands/seed.js +44 -11
- package/dist/commands/seed.js.map +1 -1
- package/dist/commands/self-host.d.ts +7 -1
- package/dist/commands/self-host.d.ts.map +1 -1
- package/dist/commands/self-host.js +272 -758
- package/dist/commands/self-host.js.map +1 -1
- package/dist/commands/self-update.d.ts +9 -0
- package/dist/commands/self-update.d.ts.map +1 -0
- package/dist/commands/self-update.js +33 -0
- package/dist/commands/self-update.js.map +1 -0
- package/dist/commands/status.d.ts.map +1 -1
- package/dist/commands/status.js +4 -3
- package/dist/commands/status.js.map +1 -1
- package/dist/commands/types.d.ts +3 -0
- package/dist/commands/types.d.ts.map +1 -0
- package/dist/commands/types.js +62 -0
- package/dist/commands/types.js.map +1 -0
- package/dist/commands/update.d.ts +7 -0
- package/dist/commands/update.d.ts.map +1 -0
- package/dist/commands/update.js +93 -0
- package/dist/commands/update.js.map +1 -0
- package/dist/components.d.ts +5 -0
- package/dist/components.d.ts.map +1 -0
- package/dist/components.js +3 -0
- package/dist/components.js.map +1 -0
- package/dist/config.d.ts +10 -51
- package/dist/config.d.ts.map +1 -1
- package/dist/config.js +101 -33
- package/dist/config.js.map +1 -1
- package/dist/dev-compose.d.ts +17 -0
- package/dist/dev-compose.d.ts.map +1 -0
- package/dist/dev-compose.js +374 -0
- package/dist/dev-compose.js.map +1 -0
- package/dist/diff-output.d.ts +4 -0
- package/dist/diff-output.d.ts.map +1 -0
- package/dist/diff-output.js +12 -0
- package/dist/diff-output.js.map +1 -0
- package/dist/docker-postgres.d.ts +57 -0
- package/dist/docker-postgres.d.ts.map +1 -0
- package/dist/docker-postgres.js +208 -0
- package/dist/docker-postgres.js.map +1 -0
- package/dist/engine-client.d.ts +69 -0
- package/dist/engine-client.d.ts.map +1 -0
- package/dist/engine-client.js +157 -0
- package/dist/engine-client.js.map +1 -0
- package/dist/ensure-binary.d.ts +7 -0
- package/dist/ensure-binary.d.ts.map +1 -0
- package/dist/ensure-binary.js +17 -0
- package/dist/ensure-binary.js.map +1 -0
- package/dist/functions-router-gen.d.ts +14 -0
- package/dist/functions-router-gen.d.ts.map +1 -0
- package/dist/functions-router-gen.js +199 -0
- package/dist/functions-router-gen.js.map +1 -0
- package/dist/index.d.ts +4 -5
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +2 -3
- package/dist/index.js.map +1 -1
- package/dist/kong-config.d.ts +25 -0
- package/dist/kong-config.d.ts.map +1 -0
- package/dist/kong-config.js +71 -0
- package/dist/kong-config.js.map +1 -0
- package/dist/local-gateway.d.ts +7 -0
- package/dist/local-gateway.d.ts.map +1 -0
- package/dist/local-gateway.js +9 -0
- package/dist/local-gateway.js.map +1 -0
- package/dist/local-storage.d.ts +8 -0
- package/dist/local-storage.d.ts.map +1 -0
- package/dist/local-storage.js +14 -0
- package/dist/local-storage.js.map +1 -0
- package/dist/pgbouncer-userlist.d.ts +5 -0
- package/dist/pgbouncer-userlist.d.ts.map +1 -0
- package/dist/pgbouncer-userlist.js +14 -0
- package/dist/pgbouncer-userlist.js.map +1 -0
- package/dist/postgres-ctl.d.ts +44 -0
- package/dist/postgres-ctl.d.ts.map +1 -0
- package/dist/postgres-ctl.js +137 -0
- package/dist/postgres-ctl.js.map +1 -0
- package/dist/process-manager.d.ts +43 -0
- package/dist/process-manager.d.ts.map +1 -0
- package/dist/process-manager.js +135 -0
- package/dist/process-manager.js.map +1 -0
- package/dist/project-config.d.ts +235 -0
- package/dist/project-config.d.ts.map +1 -0
- package/dist/project-config.js +160 -0
- package/dist/project-config.js.map +1 -0
- package/dist/pull-utils.d.ts +15 -0
- package/dist/pull-utils.d.ts.map +1 -1
- package/dist/pull-utils.js +12 -0
- package/dist/pull-utils.js.map +1 -1
- package/dist/release-pins.d.ts +7 -0
- package/dist/release-pins.d.ts.map +1 -0
- package/dist/release-pins.js +27 -0
- package/dist/release-pins.js.map +1 -0
- package/dist/release-public-key.d.ts +8 -0
- package/dist/release-public-key.d.ts.map +1 -0
- package/dist/release-public-key.js +13 -0
- package/dist/release-public-key.js.map +1 -0
- package/dist/runtime-routes.d.ts +34 -0
- package/dist/runtime-routes.d.ts.map +1 -0
- package/dist/runtime-routes.js +252 -0
- package/dist/runtime-routes.js.map +1 -0
- package/dist/schema-ast-v2.d.ts +127 -0
- package/dist/schema-ast-v2.d.ts.map +1 -0
- package/dist/schema-ast-v2.js +226 -0
- package/dist/schema-ast-v2.js.map +1 -0
- package/dist/scripts/postinstall.d.ts +5 -6
- package/dist/scripts/postinstall.d.ts.map +1 -1
- package/dist/scripts/postinstall.js +36 -20
- package/dist/scripts/postinstall.js.map +1 -1
- package/dist/self-host-compose.d.ts +22 -0
- package/dist/self-host-compose.d.ts.map +1 -0
- package/dist/self-host-compose.js +347 -0
- package/dist/self-host-compose.js.map +1 -0
- package/dist/storage-provision.d.ts +24 -0
- package/dist/storage-provision.d.ts.map +1 -0
- package/dist/storage-provision.js +44 -0
- package/dist/storage-provision.js.map +1 -0
- package/dist/studio-admin-roles.d.ts +7 -0
- package/dist/studio-admin-roles.d.ts.map +1 -0
- package/dist/studio-admin-roles.js +14 -0
- package/dist/studio-admin-roles.js.map +1 -0
- package/dist/studio-dev-server.d.ts +22 -0
- package/dist/studio-dev-server.d.ts.map +1 -0
- package/dist/studio-dev-server.js +28 -0
- package/dist/studio-dev-server.js.map +1 -0
- package/dist/systemd.d.ts +26 -0
- package/dist/systemd.d.ts.map +1 -0
- package/dist/systemd.js +102 -0
- package/dist/systemd.js.map +1 -0
- package/dist/tsx-runner.d.ts.map +1 -1
- package/dist/tsx-runner.js +9 -2
- package/dist/tsx-runner.js.map +1 -1
- package/dist/type-extractor.d.ts +4 -0
- package/dist/type-extractor.d.ts.map +1 -0
- package/dist/type-extractor.js +1213 -0
- package/dist/type-extractor.js.map +1 -0
- package/dist/type-resolver.d.ts +33 -0
- package/dist/type-resolver.d.ts.map +1 -0
- package/dist/type-resolver.js +338 -0
- package/dist/type-resolver.js.map +1 -0
- package/package.json +4 -3
- package/releases/deno/VERSION +1 -0
- package/scripts/mirror-deno-release.sh +76 -0
- package/src/TYPE-RESOLUTION.md +294 -0
- package/src/app/proxy-dev-app.ts +67 -0
- package/src/app-config.ts +128 -0
- package/src/augmentation-generator.ts +126 -0
- package/src/binary-cache.ts +822 -0
- package/src/cli.ts +13 -8
- package/src/commands/admin.ts +4 -3
- package/src/commands/app.ts +67 -231
- package/src/commands/cache.ts +117 -0
- package/src/commands/cloud.ts +63 -64
- package/src/commands/db.ts +54 -63
- package/src/commands/deploy.ts +96 -62
- package/src/commands/dev.ts +933 -405
- package/src/commands/diff.ts +31 -29
- package/src/commands/engine.ts +13 -116
- package/src/commands/functions.ts +97 -115
- package/src/commands/generate.ts +23 -10
- package/src/commands/init.ts +149 -414
- package/src/commands/migrate-from-v1.ts +131 -0
- package/src/commands/migrate.ts +27 -23
- package/src/commands/pg.ts +133 -0
- package/src/commands/pull.ts +6 -85
- package/src/commands/push.ts +161 -56
- package/src/commands/seed.ts +54 -12
- package/src/commands/self-host.ts +312 -880
- package/src/commands/self-update.ts +45 -0
- package/src/commands/status.ts +4 -3
- package/src/commands/types.ts +76 -0
- package/src/commands/update.ts +109 -0
- package/src/components.ts +6 -0
- package/src/config.ts +127 -94
- package/src/dev-compose.ts +455 -0
- package/src/diff-output.ts +12 -0
- package/src/docker-postgres.ts +295 -0
- package/src/engine-client.ts +236 -0
- package/src/ensure-binary.ts +28 -0
- package/src/functions-router-gen.ts +224 -0
- package/src/index.ts +4 -12
- package/src/kong-config.ts +93 -0
- package/src/local-gateway.ts +9 -0
- package/src/local-storage.ts +14 -0
- package/src/pgbouncer-userlist.ts +15 -0
- package/src/postgres-ctl.ts +171 -0
- package/src/process-manager.ts +168 -0
- package/src/project-config.ts +386 -0
- package/src/pull-utils.ts +24 -0
- package/src/release-pins.ts +31 -0
- package/src/release-public-key.ts +12 -0
- package/src/runtime-routes.ts +291 -0
- package/src/schema-ast-v2.ts +324 -0
- package/src/scripts/postinstall.ts +36 -25
- package/src/self-host-compose.ts +389 -0
- package/src/storage-provision.ts +58 -0
- package/src/studio-admin-roles.ts +16 -0
- package/src/studio-dev-server.ts +53 -0
- package/src/systemd.ts +137 -0
- package/src/tsx-runner.ts +11 -1
- package/src/type-extractor.ts +1479 -0
- package/src/type-resolver.ts +457 -0
- package/tests/app-command.test.ts +54 -0
- package/tests/augmentation-generator.test.ts +59 -0
- package/tests/binary-cache-cloud-overrides.test.ts +123 -0
- package/tests/cached-artifact-format.test.ts +84 -0
- package/tests/cli-help.test.ts +40 -14
- package/tests/config.test.ts +171 -37
- package/tests/docker-postgres.test.ts +39 -0
- package/tests/engine-distribution.test.ts +3 -3
- package/tests/ensure-binary.test.ts +59 -0
- package/tests/init.test.ts +28 -86
- package/tests/migrate-from-v1.test.ts +29 -0
- package/tests/normalize-admin-config.test.ts +48 -0
- package/tests/pg-spawn-env.test.ts +18 -0
- package/tests/postgres-archive-tag.test.ts +9 -0
- package/tests/proxy-dev-app.test.ts +33 -0
- package/tests/pull-utils.test.ts +36 -1
- package/tests/release-pins.test.ts +28 -0
- package/tests/runtime-contract.test.ts +351 -0
- package/tests/seed-discover.test.ts +31 -0
- package/tests/studio-admin-roles.test.ts +27 -0
- package/tests/tsconfig.json +9 -0
- package/tests/type-extractor.test.ts +985 -0
- package/tests/type-resolver.test.ts +59 -0
- package/tsconfig.tsbuildinfo +1 -1
- package/vitest.config.ts +12 -0
- package/dist/engine/cache.d.ts +0 -37
- package/dist/engine/cache.d.ts.map +0 -1
- package/dist/engine/cache.js +0 -121
- package/dist/engine/cache.js.map +0 -1
- package/dist/engine/download.d.ts +0 -19
- package/dist/engine/download.d.ts.map +0 -1
- package/dist/engine/download.js +0 -108
- package/dist/engine/download.js.map +0 -1
- package/dist/engine/platform.d.ts +0 -24
- package/dist/engine/platform.d.ts.map +0 -1
- package/dist/engine/platform.js +0 -50
- package/dist/engine/platform.js.map +0 -1
- package/dist/engine/resolve.d.ts +0 -37
- package/dist/engine/resolve.d.ts.map +0 -1
- package/dist/engine/resolve.js +0 -133
- package/dist/engine/resolve.js.map +0 -1
- package/dist/engine/update-notify.d.ts +0 -11
- package/dist/engine/update-notify.d.ts.map +0 -1
- package/dist/engine/update-notify.js +0 -43
- package/dist/engine/update-notify.js.map +0 -1
- package/dist/engine/verify.d.ts +0 -50
- package/dist/engine/verify.d.ts.map +0 -1
- package/dist/engine/verify.js +0 -161
- package/dist/engine/verify.js.map +0 -1
- package/dist/engine-version.d.ts +0 -35
- package/dist/engine-version.d.ts.map +0 -1
- package/dist/engine-version.js +0 -35
- package/dist/engine-version.js.map +0 -1
- package/dist/engine.d.ts +0 -34
- package/dist/engine.d.ts.map +0 -1
- package/dist/engine.js +0 -76
- package/dist/engine.js.map +0 -1
- package/src/engine/cache.ts +0 -135
- package/src/engine/download.ts +0 -143
- package/src/engine/platform.ts +0 -66
- package/src/engine/resolve.ts +0 -197
- package/src/engine/update-notify.ts +0 -50
- package/src/engine/verify.ts +0 -206
- package/src/engine-version.ts +0 -39
- package/src/engine.ts +0 -99
|
@@ -1,932 +1,364 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
} from "
|
|
9
|
-
import {
|
|
10
|
-
import {
|
|
1
|
+
/**
|
|
2
|
+
* self-host commands — manage self-hosted deployments.
|
|
3
|
+
*
|
|
4
|
+
* Compose-based commands are the canonical path.
|
|
5
|
+
* Native/systemd commands are kept temporarily for migration compatibility.
|
|
6
|
+
*/
|
|
7
|
+
|
|
8
|
+
import { Command } from "commander"
|
|
9
|
+
import { existsSync, readFileSync, mkdirSync, copyFileSync, writeFileSync } from "node:fs"
|
|
10
|
+
import { join, resolve } from "node:path"
|
|
11
|
+
import { homedir } from "node:os"
|
|
11
12
|
import { spawnSync } from "node:child_process"
|
|
12
|
-
import {
|
|
13
|
-
import
|
|
13
|
+
import { gzipSync } from "node:zlib"
|
|
14
|
+
import { loadConfig } from "../config.js"
|
|
15
|
+
import { connectionString } from "../project-config.js"
|
|
16
|
+
import { resolveBinary } from "../binary-cache.js"
|
|
17
|
+
import { generateUnits } from "../systemd.js"
|
|
18
|
+
import { readPid } from "../process-manager.js"
|
|
19
|
+
import { localStorageEnv } from "../local-storage.js"
|
|
20
|
+
import { runDockerCompose, writeSelfHostCompose } from "../self-host-compose.js"
|
|
14
21
|
|
|
15
22
|
export function registerSelfHost(program: Command): void {
|
|
16
23
|
const selfHostCmd = program
|
|
17
24
|
.command("self-host")
|
|
18
|
-
.description("Manage self-hosted
|
|
25
|
+
.description("Manage self-hosted deployments (Docker Compose only)")
|
|
19
26
|
|
|
20
|
-
selfHostCmd
|
|
21
|
-
.command("
|
|
22
|
-
.description("
|
|
23
|
-
.option("--domain <domain>", "Production domain (e.g. api.example.com)")
|
|
24
|
-
.option("--app-dockerfile <path>", "Path to your app Dockerfile (omit to skip app service)")
|
|
25
|
-
.option("--app-port <port>", "Port your app listens on", "3000")
|
|
26
|
-
.option("--ssl-email <email>", "Email address for Let's Encrypt registration")
|
|
27
|
-
.action(async (opts: { domain?: string; appDockerfile?: string; appPort: string; sslEmail?: string }) => {
|
|
28
|
-
await setup(process.cwd(), opts)
|
|
29
|
-
})
|
|
27
|
+
const composeCmd = selfHostCmd
|
|
28
|
+
.command("compose")
|
|
29
|
+
.description("Manage compose-based self-host runtime")
|
|
30
30
|
|
|
31
|
-
|
|
32
|
-
.command("
|
|
33
|
-
.description("
|
|
31
|
+
composeCmd
|
|
32
|
+
.command("render")
|
|
33
|
+
.description("Render deterministic self-host compose artifacts")
|
|
34
34
|
.action(() => {
|
|
35
|
-
|
|
35
|
+
const cwd = process.cwd()
|
|
36
|
+
const config = loadConfig(cwd)
|
|
37
|
+
const out = writeSelfHostCompose(cwd, config)
|
|
38
|
+
console.log(`Wrote ${out.composePath}`)
|
|
39
|
+
console.log(`Wrote ${out.kongPath}`)
|
|
36
40
|
})
|
|
37
41
|
|
|
38
|
-
|
|
39
|
-
.command("
|
|
40
|
-
.description("
|
|
41
|
-
.option("--
|
|
42
|
-
.
|
|
43
|
-
|
|
44
|
-
const
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
42
|
+
composeCmd
|
|
43
|
+
.command("up")
|
|
44
|
+
.description("Render and start compose services")
|
|
45
|
+
.option("-d, --detach", "Start in detached mode", true)
|
|
46
|
+
.action((opts: { detach?: boolean }) => {
|
|
47
|
+
const cwd = process.cwd()
|
|
48
|
+
const config = loadConfig(cwd)
|
|
49
|
+
const out = writeSelfHostCompose(cwd, config)
|
|
50
|
+
const status = runDockerCompose(out.composePath, opts.detach ? ["up", "-d"] : ["up"], cwd)
|
|
51
|
+
process.exitCode = status
|
|
48
52
|
})
|
|
49
53
|
|
|
50
|
-
|
|
51
|
-
.command("
|
|
52
|
-
.description("
|
|
53
|
-
.
|
|
54
|
-
|
|
55
|
-
|
|
54
|
+
composeCmd
|
|
55
|
+
.command("down")
|
|
56
|
+
.description("Stop compose services")
|
|
57
|
+
.action(() => {
|
|
58
|
+
const cwd = process.cwd()
|
|
59
|
+
const config = loadConfig(cwd)
|
|
60
|
+
const out = writeSelfHostCompose(cwd, config)
|
|
61
|
+
process.exitCode = runDockerCompose(out.composePath, ["down"], cwd)
|
|
56
62
|
})
|
|
57
63
|
|
|
58
|
-
|
|
59
|
-
.command("
|
|
60
|
-
.description("
|
|
64
|
+
composeCmd
|
|
65
|
+
.command("status")
|
|
66
|
+
.description("Show compose service status")
|
|
61
67
|
.action(() => {
|
|
62
|
-
|
|
68
|
+
const cwd = process.cwd()
|
|
69
|
+
const config = loadConfig(cwd)
|
|
70
|
+
const out = writeSelfHostCompose(cwd, config)
|
|
71
|
+
process.exitCode = runDockerCompose(out.composePath, ["ps"], cwd)
|
|
63
72
|
})
|
|
64
73
|
|
|
65
|
-
|
|
66
|
-
.command("
|
|
67
|
-
.description("
|
|
68
|
-
.option("--
|
|
69
|
-
.option("--
|
|
70
|
-
.action(
|
|
71
|
-
|
|
74
|
+
composeCmd
|
|
75
|
+
.command("logs")
|
|
76
|
+
.description("Tail compose logs")
|
|
77
|
+
.option("--service <name>", "Filter to one service")
|
|
78
|
+
.option("-f, --follow", "Follow log output", true)
|
|
79
|
+
.action((opts: { service?: string; follow?: boolean }) => {
|
|
80
|
+
const cwd = process.cwd()
|
|
81
|
+
const config = loadConfig(cwd)
|
|
82
|
+
const out = writeSelfHostCompose(cwd, config)
|
|
83
|
+
const args = ["logs"]
|
|
84
|
+
if (opts.follow) args.push("-f")
|
|
85
|
+
if (opts.service) args.push(opts.service)
|
|
86
|
+
process.exitCode = runDockerCompose(out.composePath, args, cwd)
|
|
72
87
|
})
|
|
73
|
-
}
|
|
74
|
-
|
|
75
|
-
// ─── Setup ────────────────────────────────────────────────────────────────────
|
|
76
88
|
|
|
77
|
-
|
|
78
|
-
domain?: string
|
|
79
|
-
appDockerfile?: string
|
|
80
|
-
appPort: string
|
|
81
|
-
sslEmail?: string
|
|
82
|
-
}
|
|
89
|
+
// ── Legacy native/systemd helpers (hidden; use compose for self-host) ─────
|
|
83
90
|
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
const res = await fetch(`https://api.github.com/repos/${repo}/releases/latest`, {
|
|
87
|
-
headers: { Accept: "application/vnd.github+json" },
|
|
88
|
-
signal: AbortSignal.timeout(5000),
|
|
89
|
-
})
|
|
90
|
-
if (!res.ok) return fallback
|
|
91
|
-
const data = await res.json() as { tag_name?: string }
|
|
92
|
-
return data.tag_name ?? fallback
|
|
93
|
-
} catch {
|
|
94
|
-
return fallback
|
|
95
|
-
}
|
|
96
|
-
}
|
|
91
|
+
const legacyCmd = new Command("native")
|
|
92
|
+
selfHostCmd.addCommand(legacyCmd, { hidden: true })
|
|
97
93
|
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
console.error(
|
|
103
|
-
"Error: --domain is required (or set selfHost.domain in supatype.config.ts)",
|
|
94
|
+
legacyCmd
|
|
95
|
+
.command(
|
|
96
|
+
"install-service",
|
|
97
|
+
"Generate systemd unit files and (on Linux) install + enable them",
|
|
104
98
|
)
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
write("pgbouncer.ini", productionPgbouncerIni())
|
|
140
|
-
write("userlist.txt", productionUserlist(pgPassword))
|
|
141
|
-
write("deploy.sh", deployScript(domain))
|
|
142
|
-
|
|
143
|
-
// Copy kong.yml if it exists
|
|
144
|
-
const kongSrc = resolve(cwd, ".supatype/kong.yml")
|
|
145
|
-
if (existsSync(kongSrc)) {
|
|
146
|
-
copyFileSync(kongSrc, join(deployDir, "kong.yml"))
|
|
147
|
-
console.log(" copied deploy/kong.yml")
|
|
148
|
-
}
|
|
149
|
-
|
|
150
|
-
// Make deploy.sh executable on Unix
|
|
151
|
-
try {
|
|
152
|
-
spawnSync("chmod", ["+x", join(deployDir, "deploy.sh")])
|
|
153
|
-
} catch { /* non-Unix, ignore */ }
|
|
154
|
-
|
|
155
|
-
console.log(`
|
|
156
|
-
╔══════════════════════════════════════════════════════════════╗
|
|
157
|
-
║ SAVE THESE SECRETS — they will not be shown again! ║
|
|
158
|
-
╚══════════════════════════════════════════════════════════════╝
|
|
159
|
-
|
|
160
|
-
POSTGRES_PASSWORD=${pgPassword}
|
|
161
|
-
JWT_SECRET=${jwtSecret}
|
|
162
|
-
ANON_KEY=${anonKey}
|
|
163
|
-
SERVICE_ROLE_KEY=${serviceKey}
|
|
164
|
-
|
|
165
|
-
These are also written to deploy/.env.production — back it up securely.
|
|
166
|
-
DO NOT commit deploy/.env.production to source control.
|
|
167
|
-
|
|
168
|
-
Next steps:
|
|
169
|
-
1. Copy the deploy/ directory to your VPS
|
|
170
|
-
2. SSH into the VPS and run: bash deploy.sh
|
|
171
|
-
3. Your app will be live at https://${domain}
|
|
172
|
-
`)
|
|
173
|
-
}
|
|
174
|
-
|
|
175
|
-
// ─── Operations ───────────────────────────────────────────────────────────────
|
|
176
|
-
|
|
177
|
-
function runDockerCompose(args: string[], label: string): void {
|
|
178
|
-
const deployDir = resolve(process.cwd(), "deploy")
|
|
179
|
-
if (!existsSync(join(deployDir, "docker-compose.yml"))) {
|
|
180
|
-
console.error("deploy/docker-compose.yml not found. Run: supatype self-host setup")
|
|
181
|
-
process.exit(1)
|
|
182
|
-
}
|
|
183
|
-
const result = spawnSync("docker", ["compose", "-f", join(deployDir, "docker-compose.yml"), ...args], {
|
|
184
|
-
stdio: "inherit",
|
|
185
|
-
cwd: deployDir,
|
|
186
|
-
})
|
|
187
|
-
if (result.status !== 0) process.exit(result.status ?? 1)
|
|
188
|
-
}
|
|
189
|
-
|
|
190
|
-
function backup(cwd: string, outputPath: string): void {
|
|
191
|
-
const deployDir = resolve(cwd, "deploy")
|
|
192
|
-
if (!existsSync(join(deployDir, "docker-compose.yml"))) {
|
|
193
|
-
console.error("deploy/docker-compose.yml not found. Run: supatype self-host setup")
|
|
194
|
-
process.exit(1)
|
|
195
|
-
}
|
|
196
|
-
|
|
197
|
-
const fullOutput = resolve(cwd, outputPath)
|
|
198
|
-
mkdirSync(resolve(fullOutput, ".."), { recursive: true })
|
|
199
|
-
|
|
200
|
-
console.log(`Backing up database to ${outputPath}...`)
|
|
201
|
-
const result = spawnSync(
|
|
202
|
-
"docker",
|
|
203
|
-
[
|
|
204
|
-
"compose",
|
|
205
|
-
"-f", join(deployDir, "docker-compose.yml"),
|
|
206
|
-
"exec", "-T", "db",
|
|
207
|
-
"sh", "-c", "pg_dumpall -U postgres | gzip",
|
|
208
|
-
],
|
|
209
|
-
{ cwd: deployDir, encoding: "buffer" },
|
|
210
|
-
)
|
|
211
|
-
|
|
212
|
-
if (result.status !== 0) {
|
|
213
|
-
console.error("Backup failed:", result.stderr?.toString())
|
|
214
|
-
process.exit(1)
|
|
215
|
-
}
|
|
99
|
+
.option("--output-dir <path>", "Write unit files here instead of /etc/systemd/system/")
|
|
100
|
+
.option("--user <name>", "User to run services as")
|
|
101
|
+
.option("--no-enable", "Generate unit files but do not enable/start them")
|
|
102
|
+
.action(
|
|
103
|
+
async (opts: {
|
|
104
|
+
outputDir?: string
|
|
105
|
+
user?: string
|
|
106
|
+
enable: boolean
|
|
107
|
+
}) => {
|
|
108
|
+
logLegacyWarning("install-service")
|
|
109
|
+
const cwd = process.cwd()
|
|
110
|
+
const config = loadConfig(cwd)
|
|
111
|
+
|
|
112
|
+
const systemdDir = opts.outputDir ?? ".supatype/systemd"
|
|
113
|
+
const absSystemdDir = resolve(cwd, systemdDir)
|
|
114
|
+
|
|
115
|
+
console.log("Generating systemd unit files...")
|
|
116
|
+
const { postgres, server } = generateUnits(config, cwd, {
|
|
117
|
+
outputDir: absSystemdDir,
|
|
118
|
+
...(opts.user !== undefined && { user: opts.user }),
|
|
119
|
+
})
|
|
120
|
+
console.log(` wrote ${postgres}`)
|
|
121
|
+
console.log(` wrote ${server}`)
|
|
122
|
+
|
|
123
|
+
if (!opts.enable) {
|
|
124
|
+
console.log(
|
|
125
|
+
`\nTo install manually:\n` +
|
|
126
|
+
` sudo cp ${postgres} /etc/systemd/system/\n` +
|
|
127
|
+
` sudo cp ${server} /etc/systemd/system/\n` +
|
|
128
|
+
` sudo systemctl daemon-reload\n` +
|
|
129
|
+
` sudo systemctl enable --now supatype-postgres supatype-server`,
|
|
130
|
+
)
|
|
131
|
+
return
|
|
132
|
+
}
|
|
216
133
|
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
134
|
+
if (process.platform !== "linux") {
|
|
135
|
+
console.log(
|
|
136
|
+
"\nNote: systemd unit installation is only supported on Linux.\n" +
|
|
137
|
+
`Unit files are at ${absSystemdDir}/`,
|
|
138
|
+
)
|
|
139
|
+
return
|
|
140
|
+
}
|
|
220
141
|
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
}
|
|
142
|
+
// Install to /etc/systemd/system/
|
|
143
|
+
console.log("\nInstalling to /etc/systemd/system/ (requires sudo)...")
|
|
144
|
+
const units = [
|
|
145
|
+
{ src: postgres, dest: "/etc/systemd/system/supatype-postgres.service" },
|
|
146
|
+
{ src: server, dest: "/etc/systemd/system/supatype-server.service" },
|
|
147
|
+
]
|
|
148
|
+
for (const { src, dest } of units) {
|
|
149
|
+
const cp = spawnSync("sudo", ["cp", src, dest], { stdio: "inherit" })
|
|
150
|
+
if (cp.status !== 0) {
|
|
151
|
+
console.error(`Failed to copy ${src} to ${dest}`)
|
|
152
|
+
process.exit(1)
|
|
153
|
+
}
|
|
154
|
+
}
|
|
235
155
|
|
|
236
|
-
|
|
156
|
+
const daemonReload = spawnSync("sudo", ["systemctl", "daemon-reload"], { stdio: "inherit" })
|
|
157
|
+
if (daemonReload.status !== 0) { process.exit(1) }
|
|
237
158
|
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
}
|
|
159
|
+
const enable = spawnSync(
|
|
160
|
+
"sudo",
|
|
161
|
+
["systemctl", "enable", "--now", "supatype-postgres", "supatype-server"],
|
|
162
|
+
{ stdio: "inherit" },
|
|
163
|
+
)
|
|
164
|
+
if (enable.status !== 0) { process.exit(1) }
|
|
245
165
|
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
{ composeName: "caddy", image: "caddy", repo: null, fallbackTag: "2" },
|
|
252
|
-
{ composeName: "pgbouncer", image: "pgbouncer/pgbouncer", repo: null, fallbackTag: "latest" },
|
|
253
|
-
{ composeName: "functions", image: "denoland/deno", repo: "denoland/deno", fallbackTag: "latest" },
|
|
254
|
-
]
|
|
255
|
-
|
|
256
|
-
function getCurrentImageTag(deployDir: string, serviceName: string): string | null {
|
|
257
|
-
const result = spawnSync(
|
|
258
|
-
"docker",
|
|
259
|
-
["compose", "-f", join(deployDir, "docker-compose.yml"), "images", serviceName, "--format", "json"],
|
|
260
|
-
{ cwd: deployDir, encoding: "utf8" },
|
|
261
|
-
)
|
|
262
|
-
if (result.status !== 0 || !result.stdout.trim()) return null
|
|
263
|
-
try {
|
|
264
|
-
// docker compose images --format json outputs one JSON object per line
|
|
265
|
-
const lines = result.stdout.trim().split("\n")
|
|
266
|
-
for (const line of lines) {
|
|
267
|
-
const data = JSON.parse(line) as { Tag?: string }
|
|
268
|
-
if (data.Tag) return data.Tag
|
|
269
|
-
}
|
|
270
|
-
return null
|
|
271
|
-
} catch {
|
|
272
|
-
return null
|
|
273
|
-
}
|
|
274
|
-
}
|
|
166
|
+
console.log("\nServices installed and started.")
|
|
167
|
+
console.log(" supatype-postgres.service")
|
|
168
|
+
console.log(" supatype-server.service")
|
|
169
|
+
},
|
|
170
|
+
)
|
|
275
171
|
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
172
|
+
// ── serve ──────────────────────────────────────────────────────────────────
|
|
173
|
+
|
|
174
|
+
legacyCmd
|
|
175
|
+
.command("serve", "Start supatype-server in the foreground (for standalone mode)")
|
|
176
|
+
.option("--port <port>", "Override port from config")
|
|
177
|
+
.action(async (opts: { port?: string }) => {
|
|
178
|
+
logLegacyWarning("serve")
|
|
179
|
+
const cwd = process.cwd()
|
|
180
|
+
const config = loadConfig(cwd)
|
|
181
|
+
|
|
182
|
+
const serverBin = await resolveBinary("server", config)
|
|
183
|
+
const port = opts.port ?? String(config.server.port ?? 54321)
|
|
184
|
+
|
|
185
|
+
const args = [
|
|
186
|
+
"--port", port,
|
|
187
|
+
"--mode", config.server.mode,
|
|
188
|
+
...(config.server.domain ? ["--domain", config.server.domain] : []),
|
|
189
|
+
]
|
|
190
|
+
|
|
191
|
+
const stateDir = join(homedir(), ".supatype", "projects", config.project.name)
|
|
192
|
+
const storageEnv = config.storage?.provider !== "s3" ? localStorageEnv(stateDir) : {}
|
|
193
|
+
|
|
194
|
+
console.log(`Starting supatype-server on port ${port}...`)
|
|
195
|
+
const result = spawnSync(serverBin, args, {
|
|
196
|
+
stdio: "inherit",
|
|
197
|
+
cwd,
|
|
198
|
+
env: { ...process.env, ...storageEnv },
|
|
199
|
+
})
|
|
200
|
+
process.exitCode = result.status ?? 1
|
|
281
201
|
})
|
|
282
|
-
if (!res.ok) return null
|
|
283
|
-
const data = await res.json() as { tag_name?: string; body?: string }
|
|
284
|
-
if (!data.tag_name) return null
|
|
285
|
-
return { tag: data.tag_name, body: data.body ?? "" }
|
|
286
|
-
} catch {
|
|
287
|
-
return null
|
|
288
|
-
}
|
|
289
|
-
}
|
|
290
202
|
|
|
291
|
-
|
|
292
|
-
try {
|
|
293
|
-
const { loadConfig } = require("../config.js") as typeof import("../config.js")
|
|
294
|
-
const config = loadConfig(cwd)
|
|
295
|
-
return config.selfHost
|
|
296
|
-
} catch {
|
|
297
|
-
return undefined
|
|
298
|
-
}
|
|
299
|
-
}
|
|
300
|
-
|
|
301
|
-
function isServicePinned(
|
|
302
|
-
selfHostConfig: SelfHostConfig | undefined,
|
|
303
|
-
serviceName: string,
|
|
304
|
-
): ServiceVersionPin | undefined {
|
|
305
|
-
if (!selfHostConfig?.services) return undefined
|
|
306
|
-
return (selfHostConfig.services as Record<string, ServiceVersionPin | undefined>)[serviceName]
|
|
307
|
-
}
|
|
203
|
+
// ── reload ─────────────────────────────────────────────────────────────────
|
|
308
204
|
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
|
|
322
|
-
|
|
323
|
-
|
|
324
|
-
|
|
325
|
-
|
|
326
|
-
|
|
327
|
-
|
|
328
|
-
// Status field sometimes contains "Up ... (healthy)"
|
|
329
|
-
if (data.Status && data.Status.includes("healthy")) return true
|
|
330
|
-
if (data.Status && !data.Status.includes("health") && data.State === "running") return true
|
|
331
|
-
} catch { /* skip bad line */ }
|
|
205
|
+
legacyCmd
|
|
206
|
+
.command("reload", "Reload the running supatype-server (SIGHUP for config reload)")
|
|
207
|
+
.action(() => {
|
|
208
|
+
logLegacyWarning("reload")
|
|
209
|
+
const cwd = process.cwd()
|
|
210
|
+
const config = loadConfig(cwd)
|
|
211
|
+
const stateDir = join(homedir(), ".supatype", "projects", config.project.name)
|
|
212
|
+
const pid = readPid(join(stateDir, "pid"), "server")
|
|
213
|
+
|
|
214
|
+
if (!pid) {
|
|
215
|
+
// Try systemctl if running as a service
|
|
216
|
+
if (process.platform === "linux") {
|
|
217
|
+
const result = spawnSync("systemctl", ["reload", "supatype-server"], { stdio: "inherit" })
|
|
218
|
+
process.exitCode = result.status ?? 1
|
|
219
|
+
return
|
|
220
|
+
}
|
|
221
|
+
console.error("Server does not appear to be running (no PID file found).")
|
|
222
|
+
process.exitCode = 1
|
|
223
|
+
return
|
|
332
224
|
}
|
|
333
|
-
}
|
|
334
|
-
spawnSync("sleep", ["3"])
|
|
335
|
-
}
|
|
336
|
-
return false
|
|
337
|
-
}
|
|
338
225
|
|
|
339
|
-
|
|
340
|
-
|
|
341
|
-
|
|
342
|
-
|
|
343
|
-
|
|
344
|
-
|
|
345
|
-
|
|
346
|
-
|
|
347
|
-
if (pullResult.status !== 0) return false
|
|
348
|
-
|
|
349
|
-
// Restart the service (docker compose will use the image now available)
|
|
350
|
-
// We need to re-tag or use docker compose up with the old image.
|
|
351
|
-
// The simplest reliable approach: stop the service, then start it.
|
|
352
|
-
// Since compose file may have :latest or a tag, we re-pull and restart.
|
|
353
|
-
const upResult = spawnSync(
|
|
354
|
-
"docker",
|
|
355
|
-
["compose", "-f", join(deployDir, "docker-compose.yml"), "up", "-d", "--no-deps", serviceName],
|
|
356
|
-
{ stdio: "inherit", cwd: deployDir },
|
|
357
|
-
)
|
|
358
|
-
return upResult.status === 0
|
|
359
|
-
}
|
|
226
|
+
try {
|
|
227
|
+
process.kill(pid, "SIGHUP")
|
|
228
|
+
console.log(`Sent SIGHUP to supatype-server (pid ${pid}).`)
|
|
229
|
+
} catch (err) {
|
|
230
|
+
console.error(`Failed to signal pid ${pid}:`, (err as Error).message)
|
|
231
|
+
process.exitCode = 1
|
|
232
|
+
}
|
|
233
|
+
})
|
|
360
234
|
|
|
361
|
-
|
|
362
|
-
console.log("\nApplying database migrations...")
|
|
363
|
-
// Check if migrations directory exists
|
|
364
|
-
const migrationsDir = resolve(deployDir, "..", "migrations")
|
|
365
|
-
if (!existsSync(migrationsDir)) {
|
|
366
|
-
console.log(" No migrations directory found, skipping.")
|
|
367
|
-
return true
|
|
368
|
-
}
|
|
369
|
-
|
|
370
|
-
// Run migrations via docker exec into the db container
|
|
371
|
-
const result = spawnSync(
|
|
372
|
-
"docker",
|
|
373
|
-
[
|
|
374
|
-
"compose",
|
|
375
|
-
"-f", join(deployDir, "docker-compose.yml"),
|
|
376
|
-
"exec", "-T", "db",
|
|
377
|
-
"sh", "-c",
|
|
378
|
-
`for f in /migrations/*.sql; do [ -f "$f" ] && psql -U postgres -d supatype -f "$f" && echo "Applied: $f"; done`,
|
|
379
|
-
],
|
|
380
|
-
{
|
|
381
|
-
cwd: deployDir,
|
|
382
|
-
stdio: "inherit",
|
|
383
|
-
// Mount migrations directory
|
|
384
|
-
env: { ...process.env },
|
|
385
|
-
},
|
|
386
|
-
)
|
|
235
|
+
// ── status ─────────────────────────────────────────────────────────────────
|
|
387
236
|
|
|
388
|
-
|
|
389
|
-
|
|
390
|
-
|
|
391
|
-
|
|
392
|
-
|
|
393
|
-
const
|
|
394
|
-
|
|
395
|
-
|
|
396
|
-
|
|
397
|
-
|
|
398
|
-
|
|
399
|
-
|
|
400
|
-
|
|
401
|
-
|
|
402
|
-
|
|
403
|
-
|
|
404
|
-
|
|
405
|
-
{
|
|
406
|
-
)
|
|
407
|
-
if (execResult.status !== 0) {
|
|
408
|
-
console.error(` Failed to apply migration ${file}: ${execResult.stderr}`)
|
|
409
|
-
return false
|
|
237
|
+
legacyCmd
|
|
238
|
+
.command("status", "Show running status of supatype services")
|
|
239
|
+
.action(() => {
|
|
240
|
+
logLegacyWarning("status")
|
|
241
|
+
const cwd = process.cwd()
|
|
242
|
+
const config = loadConfig(cwd)
|
|
243
|
+
const stateDir = join(homedir(), ".supatype", "projects", config.project.name)
|
|
244
|
+
|
|
245
|
+
console.log(`Project: ${config.project.name}\n`)
|
|
246
|
+
|
|
247
|
+
if (process.platform === "linux" && existsSync("/run/systemd/system")) {
|
|
248
|
+
// systemd is active
|
|
249
|
+
for (const svc of ["supatype-postgres", "supatype-server"]) {
|
|
250
|
+
const result = spawnSync("systemctl", ["status", "--no-pager", "--lines=0", svc], {
|
|
251
|
+
encoding: "utf8",
|
|
252
|
+
})
|
|
253
|
+
const active = result.stdout?.includes("active (running)") ? "running" : "stopped"
|
|
254
|
+
console.log(` ${svc}: ${active}`)
|
|
410
255
|
}
|
|
411
|
-
|
|
256
|
+
} else {
|
|
257
|
+
// PID file check
|
|
258
|
+
const serverPid = readPid(join(stateDir, "pid"), "server")
|
|
259
|
+
const pgPid = readPid(join(stateDir, "pid"), "postgres")
|
|
260
|
+
console.log(` postgres: ${pgPid ? `running (pid ${pgPid})` : "stopped"}`)
|
|
261
|
+
console.log(` supatype-server: ${serverPid ? `running (pid ${serverPid})` : "stopped"}`)
|
|
412
262
|
}
|
|
413
|
-
} catch (err) {
|
|
414
|
-
console.error(` Error reading migrations: ${err}`)
|
|
415
|
-
return false
|
|
416
|
-
}
|
|
417
|
-
}
|
|
418
|
-
|
|
419
|
-
console.log(" Migrations complete.")
|
|
420
|
-
return true
|
|
421
|
-
}
|
|
422
|
-
|
|
423
|
-
/** Summarize a release body to a short changelog line. */
|
|
424
|
-
function summarizeChangelog(body: string): string {
|
|
425
|
-
if (!body.trim()) return "(no changelog available)"
|
|
426
|
-
// Take first 3 non-empty lines, strip markdown headers
|
|
427
|
-
const lines = body
|
|
428
|
-
.split("\n")
|
|
429
|
-
.map(l => l.trim())
|
|
430
|
-
.filter(l => l.length > 0)
|
|
431
|
-
.map(l => l.replace(/^#+\s*/, ""))
|
|
432
|
-
.slice(0, 3)
|
|
433
|
-
const summary = lines.join("; ")
|
|
434
|
-
return summary.length > 120 ? summary.slice(0, 117) + "..." : summary
|
|
435
|
-
}
|
|
436
|
-
|
|
437
|
-
interface UpgradeOpts {
|
|
438
|
-
skipBackup?: boolean
|
|
439
|
-
skipMigrations?: boolean
|
|
440
|
-
}
|
|
441
263
|
|
|
442
|
-
|
|
443
|
-
|
|
444
|
-
|
|
445
|
-
console.error("deploy/docker-compose.yml not found. Run: supatype self-host setup")
|
|
446
|
-
process.exit(1)
|
|
447
|
-
}
|
|
448
|
-
|
|
449
|
-
const selfHostConfig = loadSelfHostConfig(cwd)
|
|
450
|
-
|
|
451
|
-
// ── Step 1: Check current vs latest versions ──────────────────────────────
|
|
452
|
-
|
|
453
|
-
console.log("Checking service versions...\n")
|
|
454
|
-
|
|
455
|
-
interface UpgradePlan {
|
|
456
|
-
service: ServiceInfo
|
|
457
|
-
currentTag: string | null
|
|
458
|
-
latestTag: string
|
|
459
|
-
changelog: string
|
|
460
|
-
pinned: boolean
|
|
461
|
-
}
|
|
462
|
-
|
|
463
|
-
const plans: UpgradePlan[] = []
|
|
464
|
-
|
|
465
|
-
for (const svc of MANAGED_SERVICES) {
|
|
466
|
-
const pin = isServicePinned(selfHostConfig, svc.composeName)
|
|
467
|
-
const currentTag = getCurrentImageTag(deployDir, svc.composeName)
|
|
468
|
-
|
|
469
|
-
if (pin) {
|
|
470
|
-
console.log(` ${svc.composeName.padEnd(12)} pinned at ${pin.version} (skipping)`)
|
|
471
|
-
plans.push({
|
|
472
|
-
service: svc,
|
|
473
|
-
currentTag,
|
|
474
|
-
latestTag: pin.version,
|
|
475
|
-
changelog: "",
|
|
476
|
-
pinned: true,
|
|
477
|
-
})
|
|
478
|
-
continue
|
|
479
|
-
}
|
|
480
|
-
|
|
481
|
-
let latestTag = svc.fallbackTag
|
|
482
|
-
let changelog = ""
|
|
483
|
-
|
|
484
|
-
if (svc.repo) {
|
|
485
|
-
const release = await fetchLatestRelease(svc.repo)
|
|
486
|
-
if (release) {
|
|
487
|
-
latestTag = release.tag
|
|
488
|
-
changelog = summarizeChangelog(release.body)
|
|
264
|
+
const logDir = join(stateDir, "logs")
|
|
265
|
+
if (existsSync(logDir)) {
|
|
266
|
+
console.log(`\nLogs: ${logDir}`)
|
|
489
267
|
}
|
|
490
|
-
}
|
|
491
|
-
|
|
492
|
-
const needsUpgrade = currentTag !== latestTag
|
|
493
|
-
const marker = needsUpgrade ? " *" : ""
|
|
494
|
-
console.log(
|
|
495
|
-
` ${svc.composeName.padEnd(12)} ${(currentTag ?? "unknown").padEnd(16)} -> ${latestTag}${marker}`,
|
|
496
|
-
)
|
|
497
|
-
if (changelog && needsUpgrade) {
|
|
498
|
-
console.log(`${"".padEnd(16)}changelog: ${changelog}`)
|
|
499
|
-
}
|
|
500
|
-
|
|
501
|
-
plans.push({
|
|
502
|
-
service: svc,
|
|
503
|
-
currentTag,
|
|
504
|
-
latestTag,
|
|
505
|
-
changelog,
|
|
506
|
-
pinned: false,
|
|
507
268
|
})
|
|
508
|
-
}
|
|
509
|
-
|
|
510
|
-
const upgradeable = plans.filter(p => !p.pinned && p.currentTag !== p.latestTag)
|
|
511
|
-
if (upgradeable.length === 0) {
|
|
512
|
-
console.log("\nAll services are up to date. Nothing to upgrade.")
|
|
513
|
-
return
|
|
514
|
-
}
|
|
515
|
-
|
|
516
|
-
console.log(`\n${upgradeable.length} service(s) will be upgraded.\n`)
|
|
517
269
|
|
|
518
|
-
// ──
|
|
270
|
+
// ── logs ───────────────────────────────────────────────────────────────────
|
|
519
271
|
|
|
520
|
-
|
|
521
|
-
|
|
522
|
-
|
|
523
|
-
|
|
524
|
-
|
|
525
|
-
|
|
526
|
-
|
|
527
|
-
|
|
528
|
-
|
|
529
|
-
|
|
530
|
-
|
|
531
|
-
|
|
532
|
-
|
|
533
|
-
|
|
534
|
-
|
|
535
|
-
|
|
536
|
-
|
|
537
|
-
|
|
538
|
-
|
|
539
|
-
|
|
540
|
-
|
|
541
|
-
// ── Step 4: Rolling restart with health checks and rollback ───────────────
|
|
542
|
-
|
|
543
|
-
console.log("\nStarting rolling upgrade...\n")
|
|
544
|
-
|
|
545
|
-
const failed: string[] = []
|
|
546
|
-
|
|
547
|
-
for (const plan of upgradeable) {
|
|
548
|
-
const svc = plan.service
|
|
549
|
-
const fullImage = `${svc.image}:${plan.latestTag}`
|
|
550
|
-
const previousImage = plan.currentTag ? `${svc.image}:${plan.currentTag}` : null
|
|
551
|
-
|
|
552
|
-
console.log(`Upgrading ${svc.composeName}: ${plan.currentTag ?? "unknown"} -> ${plan.latestTag}`)
|
|
272
|
+
selfHostCmd
|
|
273
|
+
.command("logs", "Tail supatype service logs", { hidden: true })
|
|
274
|
+
.option("--service <name>", "Show logs for: postgres | server")
|
|
275
|
+
.option("--lines <n>", "Number of lines to show", "50")
|
|
276
|
+
.option("-f, --follow", "Follow log output")
|
|
277
|
+
.action((opts: { service?: string; lines: string; follow?: boolean }) => {
|
|
278
|
+
logLegacyWarning("logs")
|
|
279
|
+
const cwd = process.cwd()
|
|
280
|
+
const config = loadConfig(cwd)
|
|
281
|
+
const stateDir = join(homedir(), ".supatype", "projects", config.project.name)
|
|
282
|
+
const logDir = join(stateDir, "logs")
|
|
283
|
+
|
|
284
|
+
if (process.platform === "linux" && existsSync("/run/systemd/system")) {
|
|
285
|
+
const args = ["--no-pager", "--lines", opts.lines]
|
|
286
|
+
if (opts.follow) args.push("--follow")
|
|
287
|
+
if (opts.service) args.push(`-u`, `supatype-${opts.service}`)
|
|
288
|
+
else args.push("-u", "supatype-postgres", "-u", "supatype-server")
|
|
289
|
+
spawnSync("journalctl", args, { stdio: "inherit" })
|
|
290
|
+
return
|
|
291
|
+
}
|
|
553
292
|
|
|
554
|
-
|
|
555
|
-
|
|
556
|
-
|
|
557
|
-
|
|
558
|
-
cwd: deployDir,
|
|
559
|
-
})
|
|
560
|
-
if (pullResult.status !== 0) {
|
|
561
|
-
console.error(` Failed to pull ${fullImage}. Skipping ${svc.composeName}.`)
|
|
562
|
-
failed.push(svc.composeName)
|
|
563
|
-
continue
|
|
564
|
-
}
|
|
565
|
-
|
|
566
|
-
// Restart just this service (zero-downtime: one at a time)
|
|
567
|
-
console.log(` Restarting ${svc.composeName}...`)
|
|
568
|
-
const upResult = spawnSync(
|
|
569
|
-
"docker",
|
|
570
|
-
["compose", "-f", join(deployDir, "docker-compose.yml"), "up", "-d", "--no-deps", svc.composeName],
|
|
571
|
-
{ stdio: "inherit", cwd: deployDir },
|
|
572
|
-
)
|
|
573
|
-
if (upResult.status !== 0) {
|
|
574
|
-
console.error(` Failed to restart ${svc.composeName}.`)
|
|
575
|
-
if (previousImage) {
|
|
576
|
-
rollbackService(deployDir, svc.composeName, previousImage)
|
|
293
|
+
// File-based logs
|
|
294
|
+
const targets: Array<{ label: string; file: string }> = []
|
|
295
|
+
if (!opts.service || opts.service === "postgres") {
|
|
296
|
+
targets.push({ label: "postgres", file: join(logDir, "postgres.log") })
|
|
577
297
|
}
|
|
578
|
-
|
|
579
|
-
|
|
580
|
-
|
|
581
|
-
|
|
582
|
-
|
|
583
|
-
|
|
584
|
-
|
|
585
|
-
|
|
586
|
-
|
|
587
|
-
|
|
588
|
-
|
|
589
|
-
|
|
590
|
-
const healthAfterRollback = checkServiceHealth(deployDir, svc.composeName)
|
|
591
|
-
if (healthAfterRollback) {
|
|
592
|
-
console.log(` Rolled back ${svc.composeName} to ${previousImage} successfully.`)
|
|
593
|
-
} else {
|
|
594
|
-
console.error(` WARNING: ${svc.composeName} is unhealthy even after rollback.`)
|
|
595
|
-
}
|
|
298
|
+
if (!opts.service || opts.service === "server") {
|
|
299
|
+
targets.push({ label: "server", file: join(logDir, "server.log") })
|
|
300
|
+
}
|
|
301
|
+
|
|
302
|
+
for (const { label, file } of targets) {
|
|
303
|
+
if (!existsSync(file)) {
|
|
304
|
+
console.log(`[${label}] log file not found: ${file}`)
|
|
305
|
+
continue
|
|
306
|
+
}
|
|
307
|
+
if (opts.follow) {
|
|
308
|
+
const tail = spawnSync("tail", ["-f", "-n", opts.lines, file], { stdio: "inherit" })
|
|
309
|
+
process.exitCode = tail.status ?? 0
|
|
596
310
|
} else {
|
|
597
|
-
|
|
311
|
+
const n = parseInt(opts.lines, 10)
|
|
312
|
+
const content = readFileSync(file, "utf8")
|
|
313
|
+
const lines = content.split("\n")
|
|
314
|
+
console.log(lines.slice(-n).join("\n"))
|
|
598
315
|
}
|
|
599
316
|
}
|
|
600
|
-
|
|
601
|
-
continue
|
|
602
|
-
}
|
|
603
|
-
|
|
604
|
-
console.log(` ${svc.composeName} upgraded and healthy.\n`)
|
|
605
|
-
}
|
|
606
|
-
|
|
607
|
-
// ── Step 5: Summary ───────────────────────────────────────────────────────
|
|
608
|
-
|
|
609
|
-
if (failed.length === 0) {
|
|
610
|
-
console.log("Upgrade complete. All services are healthy.")
|
|
611
|
-
} else {
|
|
612
|
-
console.error(`\nUpgrade finished with failures in: ${failed.join(", ")}`)
|
|
613
|
-
console.error("\nManual intervention may be needed:")
|
|
614
|
-
console.error(" 1. Check logs: supatype self-host logs --service <name>")
|
|
615
|
-
console.error(" 2. Check status: supatype self-host status")
|
|
616
|
-
console.error(" 3. Restore backup: docker compose exec -T db sh -c 'gunzip | psql -U postgres' < <backup-file>")
|
|
617
|
-
console.error(" 4. Pin a version: Add services.<name>.version in supatype.config.ts selfHost config")
|
|
618
|
-
process.exit(1)
|
|
619
|
-
}
|
|
620
|
-
}
|
|
621
|
-
|
|
622
|
-
// ─── Config helpers ───────────────────────────────────────────────────────────
|
|
623
|
-
|
|
624
|
-
function loadDomainFromConfig(cwd: string): string | undefined {
|
|
625
|
-
try {
|
|
626
|
-
const { loadConfig } = require("../config.js") as typeof import("../config.js")
|
|
627
|
-
const config = loadConfig(cwd)
|
|
628
|
-
return (config as { selfHost?: { domain?: string } }).selfHost?.domain
|
|
629
|
-
} catch {
|
|
630
|
-
return undefined
|
|
631
|
-
}
|
|
632
|
-
}
|
|
633
|
-
|
|
634
|
-
function timestamp(): string {
|
|
635
|
-
return new Date().toISOString().replace(/[:.]/g, "-").slice(0, 19)
|
|
636
|
-
}
|
|
637
|
-
|
|
638
|
-
// ─── Production templates ─────────────────────────────────────────────────────
|
|
639
|
-
|
|
640
|
-
function envProductionTemplate(
|
|
641
|
-
domain: string,
|
|
642
|
-
pgPassword: string,
|
|
643
|
-
jwtSecret: string,
|
|
644
|
-
anonKey: string,
|
|
645
|
-
serviceKey: string,
|
|
646
|
-
): string {
|
|
647
|
-
return `# Production secrets — DO NOT commit this file to source control
|
|
648
|
-
# Generated by: supatype self-host setup
|
|
649
|
-
|
|
650
|
-
DOMAIN=${domain}
|
|
651
|
-
|
|
652
|
-
POSTGRES_PASSWORD=${pgPassword}
|
|
653
|
-
POSTGRES_DB=supatype
|
|
654
|
-
|
|
655
|
-
JWT_SECRET=${jwtSecret}
|
|
656
|
-
ANON_KEY=${anonKey}
|
|
657
|
-
SERVICE_ROLE_KEY=${serviceKey}
|
|
658
|
-
|
|
659
|
-
SITE_URL=https://${domain}
|
|
660
|
-
|
|
661
|
-
# SMTP — required for user email confirmation in production
|
|
662
|
-
SMTP_HOST=
|
|
663
|
-
SMTP_PORT=587
|
|
664
|
-
SMTP_USER=
|
|
665
|
-
SMTP_PASS=
|
|
666
|
-
SMTP_SENDER_NAME=Supatype
|
|
667
|
-
`
|
|
668
|
-
}
|
|
669
|
-
|
|
670
|
-
function productionComposeTemplate(domain: string, opts: SetupOpts, postgresTag: string, authTag: string): string {
|
|
671
|
-
const appService = opts.appDockerfile
|
|
672
|
-
? `
|
|
673
|
-
app:
|
|
674
|
-
build:
|
|
675
|
-
context: ..
|
|
676
|
-
dockerfile: ${opts.appDockerfile}
|
|
677
|
-
environment:
|
|
678
|
-
SUPATYPE_URL: http://kong:8000
|
|
679
|
-
SUPATYPE_ANON_KEY: \${ANON_KEY}
|
|
680
|
-
SUPATYPE_SERVICE_ROLE_KEY: \${SERVICE_ROLE_KEY}
|
|
681
|
-
networks:
|
|
682
|
-
- supatype
|
|
683
|
-
depends_on:
|
|
684
|
-
- kong
|
|
685
|
-
restart: unless-stopped
|
|
686
|
-
`
|
|
687
|
-
: ""
|
|
688
|
-
|
|
689
|
-
return `# Production docker-compose — generated by supatype self-host setup
|
|
690
|
-
# Run with: docker compose up -d (from within the deploy/ directory)
|
|
691
|
-
|
|
692
|
-
services:
|
|
693
|
-
db:
|
|
694
|
-
image: supatype/postgres:${postgresTag}
|
|
695
|
-
environment:
|
|
696
|
-
POSTGRES_PASSWORD: \${POSTGRES_PASSWORD}
|
|
697
|
-
POSTGRES_DB: \${POSTGRES_DB:-supatype}
|
|
698
|
-
volumes:
|
|
699
|
-
- db-data:/var/lib/postgresql/data
|
|
700
|
-
networks:
|
|
701
|
-
- supatype
|
|
702
|
-
healthcheck:
|
|
703
|
-
test: ["CMD-SHELL", "pg_isready -U postgres"]
|
|
704
|
-
interval: 10s
|
|
705
|
-
timeout: 5s
|
|
706
|
-
retries: 20
|
|
707
|
-
restart: unless-stopped
|
|
708
|
-
|
|
709
|
-
pgbouncer:
|
|
710
|
-
image: pgbouncer/pgbouncer:latest
|
|
711
|
-
volumes:
|
|
712
|
-
- ./pgbouncer.ini:/etc/pgbouncer/pgbouncer.ini:ro
|
|
713
|
-
- ./userlist.txt:/etc/pgbouncer/userlist.txt:ro
|
|
714
|
-
networks:
|
|
715
|
-
- supatype
|
|
716
|
-
depends_on:
|
|
717
|
-
db:
|
|
718
|
-
condition: service_healthy
|
|
719
|
-
restart: unless-stopped
|
|
720
|
-
|
|
721
|
-
gotrue:
|
|
722
|
-
image: supatype/auth:${authTag}
|
|
723
|
-
environment:
|
|
724
|
-
GOTRUE_API_HOST: 0.0.0.0
|
|
725
|
-
GOTRUE_API_PORT: 9999
|
|
726
|
-
GOTRUE_DB_DRIVER: postgres
|
|
727
|
-
GOTRUE_DB_DATABASE_URL: "postgres://postgres:\${POSTGRES_PASSWORD}@pgbouncer:6432/\${POSTGRES_DB:-supatype}?search_path=auth"
|
|
728
|
-
GOTRUE_SITE_URL: https://${domain}
|
|
729
|
-
GOTRUE_JWT_SECRET: \${JWT_SECRET}
|
|
730
|
-
GOTRUE_JWT_EXP: 3600
|
|
731
|
-
GOTRUE_JWT_AUD: authenticated
|
|
732
|
-
GOTRUE_JWT_DEFAULT_GROUP_NAME: authenticated
|
|
733
|
-
GOTRUE_JWT_ADMIN_ROLES: service_role
|
|
734
|
-
GOTRUE_MAILER_AUTOCONFIRM: false
|
|
735
|
-
GOTRUE_SMTP_HOST: \${SMTP_HOST}
|
|
736
|
-
GOTRUE_SMTP_PORT: \${SMTP_PORT:-587}
|
|
737
|
-
GOTRUE_SMTP_USER: \${SMTP_USER}
|
|
738
|
-
GOTRUE_SMTP_PASS: \${SMTP_PASS}
|
|
739
|
-
GOTRUE_SMTP_SENDER_NAME: \${SMTP_SENDER_NAME:-Supatype}
|
|
740
|
-
GOTRUE_MAILER_URLPATHS_CONFIRMATION: /auth/v1/verify
|
|
741
|
-
GOTRUE_MAILER_URLPATHS_RECOVERY: /auth/v1/verify
|
|
742
|
-
GOTRUE_MAILER_URLPATHS_EMAIL_CHANGE: /auth/v1/verify
|
|
743
|
-
GOTRUE_MAILER_URLPATHS_INVITE: /auth/v1/verify
|
|
744
|
-
GOTRUE_DISABLE_SIGNUP: false
|
|
745
|
-
networks:
|
|
746
|
-
- supatype
|
|
747
|
-
depends_on:
|
|
748
|
-
pgbouncer:
|
|
749
|
-
condition: service_started
|
|
750
|
-
restart: unless-stopped
|
|
751
|
-
|
|
752
|
-
postgrest:
|
|
753
|
-
image: postgrest/postgrest:v12.2.8
|
|
754
|
-
environment:
|
|
755
|
-
PGRST_DB_URI: postgresql://authenticator:\${POSTGRES_PASSWORD}@pgbouncer:6432/\${POSTGRES_DB:-supatype}
|
|
756
|
-
PGRST_DB_SCHEMA: public
|
|
757
|
-
PGRST_DB_ANON_ROLE: anon
|
|
758
|
-
PGRST_JWT_SECRET: \${JWT_SECRET}
|
|
759
|
-
PGRST_DB_EXTRA_SEARCH_PATH: public,extensions
|
|
760
|
-
PGRST_DB_POOL: 3
|
|
761
|
-
networks:
|
|
762
|
-
- supatype
|
|
763
|
-
depends_on:
|
|
764
|
-
pgbouncer:
|
|
765
|
-
condition: service_started
|
|
766
|
-
restart: unless-stopped
|
|
767
|
-
|
|
768
|
-
kong:
|
|
769
|
-
image: kong:3.6
|
|
770
|
-
environment:
|
|
771
|
-
KONG_DATABASE: "off"
|
|
772
|
-
KONG_DECLARATIVE_CONFIG: /etc/kong/kong.yml
|
|
773
|
-
KONG_PROXY_ACCESS_LOG: /dev/stdout
|
|
774
|
-
KONG_ADMIN_ACCESS_LOG: /dev/stdout
|
|
775
|
-
KONG_PROXY_ERROR_LOG: /dev/stderr
|
|
776
|
-
KONG_ADMIN_ERROR_LOG: /dev/stderr
|
|
777
|
-
volumes:
|
|
778
|
-
- ./kong.yml:/etc/kong/kong.yml:ro
|
|
779
|
-
networks:
|
|
780
|
-
- supatype
|
|
781
|
-
depends_on:
|
|
782
|
-
- postgrest
|
|
783
|
-
- gotrue
|
|
784
|
-
restart: unless-stopped
|
|
785
|
-
${appService}
|
|
786
|
-
functions:
|
|
787
|
-
image: denoland/deno:latest
|
|
788
|
-
environment:
|
|
789
|
-
SUPATYPE_URL: http://kong:8000
|
|
790
|
-
SUPATYPE_ANON_KEY: \${ANON_KEY}
|
|
791
|
-
SUPATYPE_SERVICE_ROLE_KEY: \${SERVICE_ROLE_KEY}
|
|
792
|
-
FUNCTIONS_DIR: /functions
|
|
793
|
-
volumes:
|
|
794
|
-
- ../supatype/functions:/functions:ro
|
|
795
|
-
networks:
|
|
796
|
-
- supatype
|
|
797
|
-
depends_on:
|
|
798
|
-
- kong
|
|
799
|
-
mem_limit: 512m
|
|
800
|
-
cpus: 1.0
|
|
801
|
-
restart: unless-stopped
|
|
802
|
-
|
|
803
|
-
caddy:
|
|
804
|
-
image: caddy:2
|
|
805
|
-
ports:
|
|
806
|
-
- "80:80"
|
|
807
|
-
- "443:443"
|
|
808
|
-
volumes:
|
|
809
|
-
- ./Caddyfile:/etc/caddy/Caddyfile:ro
|
|
810
|
-
- caddy-data:/data
|
|
811
|
-
- caddy-config:/config
|
|
812
|
-
networks:
|
|
813
|
-
- supatype
|
|
814
|
-
depends_on:
|
|
815
|
-
- kong
|
|
816
|
-
restart: unless-stopped
|
|
817
|
-
|
|
818
|
-
networks:
|
|
819
|
-
supatype:
|
|
820
|
-
driver: bridge
|
|
821
|
-
|
|
822
|
-
volumes:
|
|
823
|
-
db-data:
|
|
824
|
-
caddy-data:
|
|
825
|
-
caddy-config:
|
|
826
|
-
`
|
|
827
|
-
}
|
|
828
|
-
|
|
829
|
-
function caddyfileTemplate(domain: string, sslEmail?: string): string {
|
|
830
|
-
const emailLine = sslEmail ? `\n\ttls ${sslEmail}\n` : ""
|
|
831
|
-
return `${domain} {${emailLine}
|
|
832
|
-
\treverse_proxy kong:8000
|
|
833
|
-
|
|
834
|
-
\theader {
|
|
835
|
-
\t\tStrict-Transport-Security "max-age=31536000; includeSubDomains"
|
|
836
|
-
\t\tX-Frame-Options "SAMEORIGIN"
|
|
837
|
-
\t\tX-Content-Type-Options "nosniff"
|
|
838
|
-
\t}
|
|
839
|
-
}
|
|
840
|
-
`
|
|
841
|
-
}
|
|
317
|
+
})
|
|
842
318
|
|
|
843
|
-
|
|
844
|
-
|
|
845
|
-
|
|
846
|
-
|
|
847
|
-
|
|
848
|
-
|
|
849
|
-
|
|
850
|
-
|
|
851
|
-
|
|
852
|
-
|
|
853
|
-
|
|
854
|
-
|
|
855
|
-
|
|
856
|
-
|
|
857
|
-
|
|
858
|
-
|
|
859
|
-
|
|
319
|
+
// ── backup ─────────────────────────────────────────────────────────────────
|
|
320
|
+
|
|
321
|
+
legacyCmd
|
|
322
|
+
.command("backup", "Create a Postgres dump of the project database")
|
|
323
|
+
.option("--output <path>", "Output file path (default: ./backups/backup-<timestamp>.sql.gz)")
|
|
324
|
+
.option("--connection <url>", "Database connection URL (overrides config)")
|
|
325
|
+
.action((opts: { output?: string; connection?: string }) => {
|
|
326
|
+
logLegacyWarning("backup")
|
|
327
|
+
const cwd = process.cwd()
|
|
328
|
+
const config = loadConfig(cwd)
|
|
329
|
+
const conn = opts.connection ?? connectionString(config)
|
|
330
|
+
const outFile = opts.output ?? resolve(
|
|
331
|
+
cwd,
|
|
332
|
+
"backups",
|
|
333
|
+
`backup-${new Date().toISOString().replace(/[:.]/g, "-")}.sql.gz`,
|
|
334
|
+
)
|
|
335
|
+
|
|
336
|
+
mkdirSync(resolve(outFile, ".."), { recursive: true })
|
|
337
|
+
|
|
338
|
+
console.log(`Backing up database to ${outFile}...`)
|
|
339
|
+
try {
|
|
340
|
+
// Avoid shell interpolation of user-supplied values.
|
|
341
|
+
const pgDump = spawnSync("pg_dump", [conn], {
|
|
342
|
+
stdio: ["ignore", "pipe", "pipe"],
|
|
343
|
+
})
|
|
344
|
+
if (pgDump.status !== 0) {
|
|
345
|
+
const stderr = pgDump.stderr?.toString("utf8") ?? ""
|
|
346
|
+
throw new Error(stderr.trim() || "pg_dump failed")
|
|
347
|
+
}
|
|
860
348
|
|
|
861
|
-
|
|
862
|
-
|
|
863
|
-
|
|
864
|
-
|
|
865
|
-
|
|
866
|
-
|
|
867
|
-
|
|
868
|
-
|
|
869
|
-
|
|
870
|
-
return `# PgBouncer userlist — generated by supatype self-host setup
|
|
871
|
-
# Regenerate by running: supatype self-host setup
|
|
872
|
-
"postgres" "${postgresHash}"
|
|
873
|
-
"authenticator" "${authenticatorHash}"
|
|
874
|
-
`
|
|
349
|
+
const compressed = gzipSync(pgDump.stdout)
|
|
350
|
+
writeFileSync(outFile, compressed)
|
|
351
|
+
console.log("Backup complete.")
|
|
352
|
+
} catch (err) {
|
|
353
|
+
console.error("Backup failed:", (err as Error).message)
|
|
354
|
+
process.exit(1)
|
|
355
|
+
}
|
|
356
|
+
})
|
|
875
357
|
}
|
|
876
358
|
|
|
877
|
-
function
|
|
878
|
-
|
|
879
|
-
|
|
880
|
-
|
|
881
|
-
|
|
882
|
-
|
|
883
|
-
DOMAIN="${domain}"
|
|
884
|
-
|
|
885
|
-
echo "Checking prerequisites..."
|
|
886
|
-
|
|
887
|
-
# Check Docker
|
|
888
|
-
if ! command -v docker &>/dev/null; then
|
|
889
|
-
echo "Docker not found. Installing..."
|
|
890
|
-
curl -fsSL https://get.docker.com | sh
|
|
891
|
-
usermod -aG docker "$USER"
|
|
892
|
-
newgrp docker
|
|
893
|
-
fi
|
|
894
|
-
|
|
895
|
-
# Check ports 80 and 443 are available
|
|
896
|
-
for port in 80 443; do
|
|
897
|
-
if ss -tlnp 2>/dev/null | grep -q ":$port " ; then
|
|
898
|
-
echo "Error: Port $port is already in use. Free it before running deploy.sh."
|
|
899
|
-
exit 1
|
|
900
|
-
fi
|
|
901
|
-
done
|
|
902
|
-
|
|
903
|
-
echo "Loading environment..."
|
|
904
|
-
if [ ! -f .env.production ]; then
|
|
905
|
-
echo "Error: .env.production not found in $(pwd)"
|
|
906
|
-
exit 1
|
|
907
|
-
fi
|
|
908
|
-
|
|
909
|
-
# Export env vars from .env.production
|
|
910
|
-
set -a; source .env.production; set +a
|
|
911
|
-
|
|
912
|
-
echo "Starting services..."
|
|
913
|
-
docker compose up -d --wait
|
|
914
|
-
|
|
915
|
-
echo "Waiting for health checks..."
|
|
916
|
-
timeout=120
|
|
917
|
-
elapsed=0
|
|
918
|
-
while ! docker compose ps --format json 2>/dev/null | grep -q '"Health":"healthy"'; do
|
|
919
|
-
sleep 5
|
|
920
|
-
elapsed=$((elapsed + 5))
|
|
921
|
-
if [ $elapsed -ge $timeout ]; then
|
|
922
|
-
echo "Timeout waiting for services to become healthy."
|
|
923
|
-
docker compose ps
|
|
924
|
-
exit 1
|
|
925
|
-
fi
|
|
926
|
-
done
|
|
927
|
-
|
|
928
|
-
echo ""
|
|
929
|
-
echo "Deployment complete!"
|
|
930
|
-
echo "Your app is live at: https://$DOMAIN"
|
|
931
|
-
`
|
|
359
|
+
function logLegacyWarning(cmd: string): void {
|
|
360
|
+
console.warn(
|
|
361
|
+
`[supatype] self-host native ${cmd} is deprecated. ` +
|
|
362
|
+
"Use `supatype self-host compose` commands instead.",
|
|
363
|
+
)
|
|
932
364
|
}
|