@hyperfixation/cli 0.1.4 → 0.1.6

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/dist/database.js CHANGED
@@ -61,8 +61,8 @@ export async function openDatabase(runner, options) {
61
61
  throw new DatabaseTransportError("tunnel", `could not reach Postgres on ${TUNNEL_LOOPBACK}:${String(remotePort)} on the box, and no ` +
62
62
  "container was named to discover an address on the docker network", { cause: loopback.failure });
63
63
  }
64
- const found = await containerAddress(runner, candidates);
65
- const direct = await tryTunnel(runner, options.admin, remotePort, found.address);
64
+ const found = await findPostgresContainer(runner, candidates);
65
+ const direct = await tryTunnel(runner, options.admin, remotePort, found.address, found.container);
66
66
  if ("database" in direct)
67
67
  return direct.database;
68
68
  if (options.dockerExec !== true) {
@@ -74,16 +74,13 @@ export async function openDatabase(runner, options) {
74
74
  }
75
75
  /** The cluster at a URL this process can already dial — a test's Postgres, or a live tunnel. */
76
76
  export function openDatabaseUrl(adminUrl) {
77
- return tunnelDatabase(adminUrl, undefined, undefined);
77
+ return tunnelDatabase(adminUrl, undefined, undefined, undefined);
78
78
  }
79
- async function tryTunnel(runner, admin, remotePort, remoteHost) {
79
+ async function tryTunnel(runner, admin, remotePort, remoteHost, container) {
80
80
  let tunnel;
81
81
  try {
82
82
  tunnel = await runner.tunnel(remotePort, remoteHost);
83
- const database = tunnelDatabase(adminUrlOf(admin, tunnel.localPort), tunnel, {
84
- host: remoteHost,
85
- port: remotePort,
86
- });
83
+ const database = tunnelDatabase(adminUrlOf(admin, tunnel.localPort), tunnel, { host: remoteHost, port: remotePort }, container);
87
84
  await database.query("SELECT 1");
88
85
  return { database };
89
86
  }
@@ -100,7 +97,7 @@ async function tryTunnel(runner, admin, remotePort, remoteHost) {
100
97
  * network something else. Every candidate that failed is reported, because which name a database
101
98
  * got is a fact about how it was created and the operator is the one who knows it.
102
99
  */
103
- async function containerAddress(runner, containers) {
100
+ export async function findPostgresContainer(runner, containers) {
104
101
  const problems = [];
105
102
  for (const container of containers) {
106
103
  const command = ["docker", "inspect", "-f", DOCKER_NETWORKS_FORMAT, container];
@@ -135,7 +132,7 @@ function coolifyAddress(stdout) {
135
132
  .filter((entry) => isIPv4(entry.address));
136
133
  return (networks.find((entry) => entry.network === COOLIFY_NETWORK) ?? networks[0])?.address;
137
134
  }
138
- function tunnelDatabase(adminUrl, tunnel, boxAddress) {
135
+ function tunnelDatabase(adminUrl, tunnel, boxAddress, container) {
139
136
  const clients = new Map();
140
137
  const clientFor = async (databaseName) => {
141
138
  const url = withDatabase(adminUrl, databaseName);
@@ -150,6 +147,7 @@ function tunnelDatabase(adminUrl, tunnel, boxAddress) {
150
147
  return {
151
148
  kind: "tunnel",
152
149
  boxAddress,
150
+ container,
153
151
  adminUrl: (databaseName) => withDatabase(adminUrl, databaseName),
154
152
  query: async (sql, queryOptions) => {
155
153
  const client = await clientFor(queryOptions?.database);
@@ -175,6 +173,7 @@ function tunnelDatabase(adminUrl, tunnel, boxAddress) {
175
173
  function dockerExecDatabase(runner, container, admin) {
176
174
  return {
177
175
  kind: "docker-exec",
176
+ container,
178
177
  adminUrl: () => undefined,
179
178
  query: async (sql, queryOptions) => {
180
179
  // `-f -`: the statement goes down stdin, so it never appears in the box's process list
@@ -0,0 +1,32 @@
1
+ import { type StepExec, type StepOut } from "./cloud-steps/index.js";
2
+ import { type OperatorConfig } from "./config.js";
3
+ import type { FetchLike } from "./providers/http.js";
4
+ export interface DeployAppOptions {
5
+ /** The app as `hf new` named it, which is also its state file's name. */
6
+ app: string;
7
+ /** The commit to deploy. Defaults to `main`'s on the app's own repository. */
8
+ sha?: string;
9
+ io: StepOut;
10
+ config?: OperatorConfig;
11
+ /** Where the per-app state files are. Defaults to `stateDir()`. */
12
+ stateDir?: string;
13
+ env?: NodeJS.ProcessEnv;
14
+ fetch?: FetchLike;
15
+ exec?: StepExec;
16
+ now?: () => number;
17
+ sleep?: (ms: number) => Promise<void>;
18
+ }
19
+ export interface DeployAppResult {
20
+ app: string;
21
+ /** The commit the app answered `/api/status` with before this returned. */
22
+ sha: string;
23
+ url: string;
24
+ }
25
+ /**
26
+ * `hf deploy <name>` — point the app at a commit and wait until it says it is serving it.
27
+ *
28
+ * The same path as `hf new`'s tenth step, and the reason there is a command at all: auto-deploy
29
+ * is off, so a merge to main changes nothing on the box until this runs. Everything it needs is
30
+ * in the state cache the provisioning run wrote; nothing here is interactive.
31
+ */
32
+ export declare function deployApp(options: DeployAppOptions): Promise<DeployAppResult>;
@@ -0,0 +1,81 @@
1
+ import { deployCommit } from "./cloud-steps/deploy.js";
2
+ import { spawnStepExec, StepFailed } from "./cloud-steps/index.js";
3
+ import { gitAuthEnv } from "./cloud-steps/repo.js";
4
+ import { loadOperatorConfig, requireOperatorConfig } from "./config.js";
5
+ import { deriveNames } from "./names.js";
6
+ import { CoolifyClient } from "./providers/coolify.js";
7
+ import { openAppState } from "./state.js";
8
+ /** What a commit looks like once `git` has resolved it; `/api/status` reports the same form. */
9
+ const FULL_SHA = /^[0-9a-f]{40}$/;
10
+ /**
11
+ * `hf deploy <name>` — point the app at a commit and wait until it says it is serving it.
12
+ *
13
+ * The same path as `hf new`'s tenth step, and the reason there is a command at all: auto-deploy
14
+ * is off, so a merge to main changes nothing on the box until this runs. Everything it needs is
15
+ * in the state cache the provisioning run wrote; nothing here is interactive.
16
+ */
17
+ export async function deployApp(options) {
18
+ const env = options.env ?? process.env;
19
+ const config = options.config ?? (await loadOperatorConfig({ env }));
20
+ const names = deriveNames(options.app);
21
+ const required = requireOperatorConfig(config, ["HF_COOLIFY_URL", "HF_COOLIFY_TOKEN", "HF_BASE_DOMAIN", "HF_GITHUB_TOKEN"], { env });
22
+ const store = await openAppState(names.given, { dir: options.stateDir, env });
23
+ const { coolify, statusTokens, repo } = store.state;
24
+ const appUuid = coolify?.appUuid;
25
+ const readToken = statusTokens?.read;
26
+ if (appUuid === undefined || readToken === undefined) {
27
+ const missing = appUuid === undefined ? "Coolify application uuid" : "read status token";
28
+ throw new StepFailed(`${store.file} has no ${missing}: hf new has not finished provisioning ${names.given}`);
29
+ }
30
+ const sha = options.sha === undefined
31
+ ? await mainSha(options, repo, required.HF_GITHUB_TOKEN)
32
+ : options.sha;
33
+ if (!FULL_SHA.test(sha)) {
34
+ throw new StepFailed(`${sha} is not a commit sha: --sha takes the full forty hex characters, because that is ` +
35
+ "what /api/status reports back");
36
+ }
37
+ const fqdn = `${names.given}.${required.HF_BASE_DOMAIN}`;
38
+ await deployCommit({
39
+ name: names.given,
40
+ appUuid,
41
+ fqdn,
42
+ sha,
43
+ readToken,
44
+ coolify: new CoolifyClient({
45
+ url: required.HF_COOLIFY_URL,
46
+ token: required.HF_COOLIFY_TOKEN,
47
+ fetch: options.fetch,
48
+ }),
49
+ io: options.io,
50
+ now: options.now ?? (() => Date.now()),
51
+ sleep: options.sleep ?? (async (ms) => await new Promise((resolve) => setTimeout(resolve, ms))),
52
+ fetch: options.fetch,
53
+ });
54
+ await store.patch({ lastDeployedSha: sha });
55
+ return { app: names.given, sha, url: `https://${fqdn}` };
56
+ }
57
+ /**
58
+ * `main`'s sha on the app's repository, read with `git ls-remote` and no checkout.
59
+ *
60
+ * The remote rather than a local clone: the operator running this has just merged a pull request,
61
+ * and whatever is in a directory on the laptop is not what the box would build.
62
+ */
63
+ async function mainSha(options, repo, token) {
64
+ if (repo === undefined) {
65
+ throw new StepFailed(`no owner/name repository in ${options.app}'s state cache: nothing can be asked what main ` +
66
+ "is. Pass --sha <sha>.");
67
+ }
68
+ const url = `https://github.com/${repo}.git`;
69
+ const exec = options.exec ?? spawnStepExec;
70
+ const outcome = await exec("git", ["ls-remote", url, "refs/heads/main"], {
71
+ cwd: process.cwd(),
72
+ capture: true,
73
+ env: gitAuthEnv(token),
74
+ });
75
+ const sha = /^([0-9a-f]{40})\s/.exec(outcome.stdout.trim())?.[1];
76
+ if (outcome.code !== 0 || sha === undefined) {
77
+ throw new StepFailed(`git ls-remote ${url} refs/heads/main named no commit: the repository may be gone, the ` +
78
+ "branch unborn, or HF_GITHUB_TOKEN unable to read it. Pass --sha <sha>.");
79
+ }
80
+ return sha;
81
+ }
package/dist/doctor.js CHANGED
@@ -142,7 +142,7 @@ async function doctorApp(context, name) {
142
142
  }
143
143
  }
144
144
  const report = await statusFindings(context, name, state, add);
145
- versionFinding(report?.applicationVersion, mainSha, mainShaProblem, add);
145
+ versionFinding(name, report?.applicationVersion, mainSha, mainShaProblem, add);
146
146
  if (report?.budget !== undefined) {
147
147
  budgetFinding(report.budget.current, "current", add);
148
148
  budgetFinding(report.budget.previous, "previous", add);
@@ -232,7 +232,14 @@ function amount(value) {
232
232
  const parsed = Number(value);
233
233
  return Number.isFinite(parsed) ? parsed : undefined;
234
234
  }
235
- function versionFinding(deployed, mainSha, mainShaProblem, add) {
235
+ /**
236
+ * What the app answers against what its repository's main holds.
237
+ *
238
+ * The mismatch names `hf deploy` because nothing else closes it: Coolify's push auto-deploy is
239
+ * disabled on every application `hf new` creates, so a merged pull request sits unpublished until
240
+ * an operator says so.
241
+ */
242
+ function versionFinding(name, deployed, mainSha, mainShaProblem, add) {
236
243
  if (mainShaProblem !== undefined) {
237
244
  add("version", "fail", mainShaProblem);
238
245
  return;
@@ -245,7 +252,7 @@ function versionFinding(deployed, mainSha, mainShaProblem, add) {
245
252
  }
246
253
  add("version", deployed === mainSha ? "ok" : "warn", deployed === mainSha
247
254
  ? `applicationVersion ${short(mainSha)} is main`
248
- : `applicationVersion ${short(deployed)} is not main ${short(mainSha)}`);
255
+ : `applicationVersion ${short(deployed)} is not main ${short(mainSha)} — run hf deploy ${name}`);
249
256
  }
250
257
  function budgetFinding(period, which, add) {
251
258
  if (period === null) {
package/dist/index.d.ts CHANGED
@@ -15,7 +15,7 @@ export { generate, GENERATOR_BIN, GENERATOR_CONFIG, NoGenerators, type GenerateO
15
15
  export { dev, devBuildSha, DEV_COMPOSE_FILE, type DevOptions, type DevResult } from "./dev.js";
16
16
  export { run, CommandFailed, type RunOptions } from "./spawn.js";
17
17
  export { createLocalRunner, createSshRunner, shellQuote, sshExecArgv, sshTunnelArgv, RunnerError, DEFAULT_TUNNEL_READY_TIMEOUT_MS, type ExecOptions, type ExecResult, type LocalRunner, type LocalRunnerOptions, type Runner, type SshRunnerOptions, type Tunnel, } from "./runner.js";
18
- export { openDatabase, openDatabaseUrl, redactPasswords, DatabaseTransportError, DEFAULT_POSTGRES_PORT, type AdminCredentials, type Database, type DatabaseTransport, type OpenDatabaseOptions, type QueryOptions, type QueryResult, } from "./database.js";
18
+ export { findPostgresContainer, openDatabase, openDatabaseUrl, redactPasswords, DatabaseTransportError, DEFAULT_POSTGRES_PORT, type AdminCredentials, type Database, type DatabaseTransport, type OpenDatabaseOptions, type QueryOptions, type QueryResult, } from "./database.js";
19
19
  export { createLocalDirectoryBackupSource, createS3BackupSource, BackupSourceError, COOLIFY_BACKUP_DIR, type BackupDump, type BackupSource, type BackupSourceKind, type LocalDirectoryBackupSourceOptions, } from "./backup-source.js";
20
- export { formatRestoreCheck, pgRestoreArgv, restoreCheck, restoreCheckApp, RestoreCheckError, SCRATCH_SUFFIX, STALE_DUMP_HOURS, type RestoreCheckAppOptions, type RestoreCheckOptions, type RestoreCheckResult, type RestoreCheckRow, type RestoreVerdict, } from "./restore-check.js";
20
+ export { formatRestoreCheck, pgRestoreArgv, pgRestoreInContainerArgv, restoreCheck, restoreCheckApp, RestoreCheckError, SCRATCH_SUFFIX, STALE_DUMP_HOURS, type RestoreCheckAppOptions, type RestoreCheckOptions, type RestoreCheckResult, type RestoreCheckRow, type RestoreVerdict, } from "./restore-check.js";
21
21
  export { provisionDatabase, ProvisionDatabaseError, REQUIRED_EXTENSIONS, type ProvisionDatabaseOptions, type ProvisionDatabaseResult, } from "./provision-database.js";
package/dist/index.js CHANGED
@@ -15,7 +15,7 @@ export { generate, GENERATOR_BIN, GENERATOR_CONFIG, NoGenerators, } from "./gen.
15
15
  export { dev, devBuildSha, DEV_COMPOSE_FILE } from "./dev.js";
16
16
  export { run, CommandFailed } from "./spawn.js";
17
17
  export { createLocalRunner, createSshRunner, shellQuote, sshExecArgv, sshTunnelArgv, RunnerError, DEFAULT_TUNNEL_READY_TIMEOUT_MS, } from "./runner.js";
18
- export { openDatabase, openDatabaseUrl, redactPasswords, DatabaseTransportError, DEFAULT_POSTGRES_PORT, } from "./database.js";
18
+ export { findPostgresContainer, openDatabase, openDatabaseUrl, redactPasswords, DatabaseTransportError, DEFAULT_POSTGRES_PORT, } from "./database.js";
19
19
  export { createLocalDirectoryBackupSource, createS3BackupSource, BackupSourceError, COOLIFY_BACKUP_DIR, } from "./backup-source.js";
20
- export { formatRestoreCheck, pgRestoreArgv, restoreCheck, restoreCheckApp, RestoreCheckError, SCRATCH_SUFFIX, STALE_DUMP_HOURS, } from "./restore-check.js";
20
+ export { formatRestoreCheck, pgRestoreArgv, pgRestoreInContainerArgv, restoreCheck, restoreCheckApp, RestoreCheckError, SCRATCH_SUFFIX, STALE_DUMP_HOURS, } from "./restore-check.js";
21
21
  export { provisionDatabase, ProvisionDatabaseError, REQUIRED_EXTENSIONS, } from "./provision-database.js";
@@ -77,9 +77,10 @@ export declare function invalidateStaleSecretSteps(state: AppStateStore): Promis
77
77
  export declare function runSteps<Context extends CloudContext>(steps: readonly Step<Context>[], context: Context): Promise<RunStepsResult>;
78
78
  /**
79
79
  * The keys a cloud `hf new` runs without: three have a default derived from another key, the two
80
- * provider keys are what the checklist warns about when they are unset, and the three Langfuse
81
- * keys are three ways of configuring one step — an org key, a project key pair, or neither, which
82
- * the step degrades to a warning and a checklist line.
80
+ * provider keys are what the checklist warns about when they are unset, the three Langfuse keys
81
+ * are three ways of configuring one step — an org key, a project key pair, or neither, which the
82
+ * step degrades to a warning and a checklist line — and the S3 storage is one the backup step
83
+ * discovers when the box has exactly one, and warns about when it cannot.
83
84
  */
84
85
  export declare const OPTIONAL_CLOUD_CONFIG: readonly ConfigKey[];
85
86
  /**
package/dist/new-cloud.js CHANGED
@@ -108,11 +108,13 @@ export async function runSteps(steps, context) {
108
108
  }
109
109
  /**
110
110
  * The keys a cloud `hf new` runs without: three have a default derived from another key, the two
111
- * provider keys are what the checklist warns about when they are unset, and the three Langfuse
112
- * keys are three ways of configuring one step — an org key, a project key pair, or neither, which
113
- * the step degrades to a warning and a checklist line.
111
+ * provider keys are what the checklist warns about when they are unset, the three Langfuse keys
112
+ * are three ways of configuring one step — an org key, a project key pair, or neither, which the
113
+ * step degrades to a warning and a checklist line — and the S3 storage is one the backup step
114
+ * discovers when the box has exactly one, and warns about when it cannot.
114
115
  */
115
116
  export const OPTIONAL_CLOUD_CONFIG = [
117
+ "HF_COOLIFY_S3_STORAGE_UUID",
116
118
  "HF_DB_HOST_INTERNAL",
117
119
  "HF_DB_CONTAINER",
118
120
  "HF_PG_ADMIN_USER",
@@ -80,6 +80,23 @@ export interface CoolifyEnvironmentVariable {
80
80
  is_literal?: boolean;
81
81
  is_multiline?: boolean;
82
82
  is_shown_once?: boolean;
83
+ /**
84
+ * Whether the builder interpolates the variable, and whether the containers get it.
85
+ *
86
+ * Both default true in Coolify's UI and neither is in the document's *request* schemas —
87
+ * only in its `EnvironmentVariable` model — but the box accepts and stores them (verified
88
+ * against 4.3.21). `SOURCE_COMMIT` needs both: it is the compose image tag and build arg as
89
+ * well as the `HF_BUILD_SHA` three services read.
90
+ */
91
+ is_buildtime?: boolean;
92
+ is_runtime?: boolean;
93
+ }
94
+ /** One entry of `GET /applications/{uuid}/envs`, narrowed to what an upsert has to match on. */
95
+ export interface CoolifyEnvEntry {
96
+ uuid: string;
97
+ key: string;
98
+ value?: string;
99
+ is_preview?: boolean;
83
100
  }
84
101
  export interface CoolifyDeploymentRequest {
85
102
  deployments: {
@@ -108,10 +125,33 @@ export interface CoolifyBackupRequest {
108
125
  database_backup_retention_amount_locally?: number;
109
126
  database_backup_retention_days_locally?: number;
110
127
  }
128
+ /**
129
+ * What `PATCH /databases/{uuid}/backups/{scheduled_backup_uuid}` accepts of the fields E3 sets.
130
+ *
131
+ * Every field is optional upstream and only the ones a rerun reconciles are here: the schedule it
132
+ * finds was registered by an earlier `hf new`, and rewriting fields nobody set would undo whatever
133
+ * the operator changed in Coolify's own UI.
134
+ */
135
+ export interface CoolifyBackupUpdate {
136
+ frequency?: string;
137
+ enabled?: boolean;
138
+ save_s3?: boolean;
139
+ s3_storage_uuid?: string;
140
+ databases_to_backup?: string;
141
+ dump_all?: boolean;
142
+ }
111
143
  export interface CoolifyBackup {
112
144
  uuid: string;
113
145
  message?: string;
114
146
  }
147
+ /** An S3 storage as `GET /s3-storages` lists it; `is_usable` is Coolify's own validation verdict. */
148
+ export interface CoolifyS3Storage {
149
+ uuid: string;
150
+ name: string;
151
+ bucket?: string;
152
+ region?: string;
153
+ is_usable?: boolean;
154
+ }
115
155
  export interface CoolifyClientOptions {
116
156
  /** `HF_COOLIFY_URL` — the instance's origin, without `/api/v1`. */
117
157
  url: string;
@@ -148,11 +188,27 @@ export declare class CoolifyClient {
148
188
  tag?: string;
149
189
  }): Promise<CoolifyApplicationSummary[]>;
150
190
  updateEnvsBulk(appUuid: string, data: readonly CoolifyEnvironmentVariable[]): Promise<unknown>;
191
+ /** Every environment variable on the application, preview and non-preview alike. */
192
+ listEnvs(appUuid: string): Promise<CoolifyEnvEntry[]>;
193
+ createEnv(appUuid: string, variable: CoolifyEnvironmentVariable): Promise<{
194
+ uuid: string;
195
+ }>;
196
+ /** Keyed by `key` — and by `is_preview`, which is why an upsert sends the entry's own flag. */
197
+ updateEnv(appUuid: string, variable: CoolifyEnvironmentVariable): Promise<unknown>;
151
198
  deploy(uuid: string, options?: {
152
199
  force?: boolean;
153
200
  }): Promise<CoolifyDeploymentRequest>;
154
201
  getDeployment(deploymentUuid: string): Promise<CoolifyDeployment>;
155
202
  createDatabaseBackup(databaseUuid: string, body: CoolifyBackupRequest): Promise<CoolifyBackup>;
203
+ updateDatabaseBackup(databaseUuid: string, backupUuid: string, body: CoolifyBackupUpdate): Promise<unknown>;
204
+ /**
205
+ * Every S3 storage the token's team has configured.
206
+ *
207
+ * A backup registered with `save_s3` and no `s3_storage_uuid` is accepted, then runs with
208
+ * `S3 storage configuration is missing` in its log and keeps the dump on the box alone — so the
209
+ * uuid has to come from somewhere, and this is the only place the API offers one.
210
+ */
211
+ listS3Storages(): Promise<CoolifyS3Storage[]>;
156
212
  /**
157
213
  * The database's scheduled backups.
158
214
  *
@@ -55,6 +55,27 @@ export class CoolifyClient {
55
55
  secrets: data.map((variable) => variable.value),
56
56
  });
57
57
  }
58
+ /** Every environment variable on the application, preview and non-preview alike. */
59
+ async listEnvs(appUuid) {
60
+ return await this.request({ method: "GET", path: `/applications/${segment(appUuid)}/envs` });
61
+ }
62
+ async createEnv(appUuid, variable) {
63
+ return await this.request({
64
+ method: "POST",
65
+ path: `/applications/${segment(appUuid)}/envs`,
66
+ body: variable,
67
+ secrets: [variable.value],
68
+ });
69
+ }
70
+ /** Keyed by `key` — and by `is_preview`, which is why an upsert sends the entry's own flag. */
71
+ async updateEnv(appUuid, variable) {
72
+ return await this.request({
73
+ method: "PATCH",
74
+ path: `/applications/${segment(appUuid)}/envs`,
75
+ body: variable,
76
+ secrets: [variable.value],
77
+ });
78
+ }
58
79
  async deploy(uuid, options = {}) {
59
80
  return await this.request({
60
81
  method: "POST",
@@ -75,6 +96,23 @@ export class CoolifyClient {
75
96
  body,
76
97
  });
77
98
  }
99
+ async updateDatabaseBackup(databaseUuid, backupUuid, body) {
100
+ return await this.request({
101
+ method: "PATCH",
102
+ path: `/databases/${segment(databaseUuid)}/backups/${segment(backupUuid)}`,
103
+ body,
104
+ });
105
+ }
106
+ /**
107
+ * Every S3 storage the token's team has configured.
108
+ *
109
+ * A backup registered with `save_s3` and no `s3_storage_uuid` is accepted, then runs with
110
+ * `S3 storage configuration is missing` in its log and keeps the dump on the box alone — so the
111
+ * uuid has to come from somewhere, and this is the only place the API offers one.
112
+ */
113
+ async listS3Storages() {
114
+ return await this.request({ method: "GET", path: "/s3-storages" });
115
+ }
78
116
  /**
79
117
  * The database's scheduled backups.
80
118
  *
@@ -36,20 +36,26 @@ export interface RestoreCheckOptions {
36
36
  app: string;
37
37
  state: AppStateStore;
38
38
  source: BackupSource;
39
- /** Where `pg_restore` runs: the box, or this machine in a test. */
39
+ /** Where the dump file is and where `docker` is run: the box, or this machine in a test. */
40
40
  runner: Runner;
41
41
  /** How the scratch database is created and both sides are counted. */
42
42
  database: Database | string;
43
43
  /**
44
- * The cluster's admin URL **as the runner sees it**, for `pg_restore`.
44
+ * The Postgres container `pg_restore` runs inside.
45
45
  *
46
- * Not the same address as `database`: the laptop reaches the cluster through an `ssh -L`
47
- * forward onto a local port, and a `pg_restore` running on the far side of that forward has to
48
- * dial the box's own loopback. Defaults to `database`'s URL, which is what a test wants when
49
- * both sides are the same machine.
46
+ * The box host has no Postgres client tools Postgres runs only in Coolify's container — so a
47
+ * `pg_restore` on the host exits 127. The dump is the host's and is not mounted into the
48
+ * container, so it goes down `docker exec -i`'s stdin.
49
+ */
50
+ container: string;
51
+ /** The superuser inside the container; `HF_PG_ADMIN_USER`, default `postgres`. */
52
+ adminUser?: string;
53
+ /**
54
+ * @deprecated Unused: `pg_restore` no longer dials an address, it runs beside the server inside
55
+ * `container`. Removed in 0.2.0.
50
56
  */
51
57
  restoreAdminUrl?: string;
52
- /** The `pg_restore` binary on the runner. */
58
+ /** The `pg_restore` binary inside the container. */
53
59
  pgRestorePath?: string;
54
60
  now?: Date;
55
61
  }
@@ -64,13 +70,32 @@ export interface RestoreCheckOptions {
64
70
  * mismatch — has to leave the warning standing until a check actually passes.
65
71
  */
66
72
  export declare function restoreCheck(options: RestoreCheckOptions): Promise<RestoreCheckResult>;
67
- /** The argv `restoreCheck` runs — the array the test asserts against. */
73
+ /**
74
+ * The argv `restoreCheck` runs — the array the test asserts against.
75
+ *
76
+ * @deprecated The box host has no `pg_restore`; the restore runs inside the Postgres container.
77
+ * Use `pgRestoreInContainerArgv`. Removed in 0.2.0.
78
+ */
68
79
  export declare function pgRestoreArgv(options: {
69
80
  url: string;
70
81
  role: string;
71
82
  file: string;
72
83
  pgRestorePath?: string;
73
84
  }): string[];
85
+ /**
86
+ * The argv `restoreCheck` runs — the array the test asserts against.
87
+ *
88
+ * No `--dbname` URL: inside the container the server is on the local socket, so the admin user
89
+ * and the database name are all it takes and no password crosses an argv. The dump arrives on
90
+ * stdin, which is why there is no file argument either.
91
+ */
92
+ export declare function pgRestoreInContainerArgv(options: {
93
+ container: string;
94
+ adminUser: string;
95
+ database: string;
96
+ role: string;
97
+ pgRestorePath?: string;
98
+ }): string[];
74
99
  /** The table `hf restore-check` prints, and the two lines around it. */
75
100
  export declare function formatRestoreCheck(result: RestoreCheckResult): string[];
76
101
  export interface RestoreCheckAppOptions {