@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/cli.d.ts +2 -2
- package/dist/cli.js +25 -1
- package/dist/cloud-steps/backup.d.ts +12 -10
- package/dist/cloud-steps/backup.js +92 -16
- package/dist/cloud-steps/coolify.d.ts +6 -3
- package/dist/cloud-steps/coolify.js +10 -3
- package/dist/cloud-steps/deploy.d.ts +38 -1
- package/dist/cloud-steps/deploy.js +95 -31
- package/dist/config.d.ts +1 -1
- package/dist/config.js +5 -0
- package/dist/database.d.ts +20 -0
- package/dist/database.js +9 -10
- package/dist/deploy-app.d.ts +32 -0
- package/dist/deploy-app.js +81 -0
- package/dist/doctor.js +10 -3
- package/dist/index.d.ts +2 -2
- package/dist/index.js +2 -2
- package/dist/new-cloud.d.ts +4 -3
- package/dist/new-cloud.js +5 -3
- package/dist/providers/coolify.d.ts +56 -0
- package/dist/providers/coolify.js +38 -0
- package/dist/restore-check.d.ts +33 -8
- package/dist/restore-check.js +78 -34
- package/dist/runner.d.ts +9 -1
- package/dist/runner.js +26 -5
- package/package.json +6 -6
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
|
|
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
|
|
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
|
-
|
|
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";
|
package/dist/new-cloud.d.ts
CHANGED
|
@@ -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,
|
|
81
|
-
*
|
|
82
|
-
*
|
|
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,
|
|
112
|
-
*
|
|
113
|
-
*
|
|
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
|
*
|
package/dist/restore-check.d.ts
CHANGED
|
@@ -36,20 +36,26 @@ export interface RestoreCheckOptions {
|
|
|
36
36
|
app: string;
|
|
37
37
|
state: AppStateStore;
|
|
38
38
|
source: BackupSource;
|
|
39
|
-
/** Where `
|
|
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
|
|
44
|
+
* The Postgres container `pg_restore` runs inside.
|
|
45
45
|
*
|
|
46
|
-
*
|
|
47
|
-
*
|
|
48
|
-
*
|
|
49
|
-
|
|
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
|
|
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
|
-
/**
|
|
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 {
|