@coffre/cli 0.1.11 → 0.1.12
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/{connect-fkn_APVy.js → connect-BXDkiWOL.js} +1 -1
- package/dist/{connect-CpN0VYGE.js → connect-tekAM4hC.js} +6 -80
- package/dist/main.js +2 -191
- package/dist/migrations/postgres/0001_remove_syncs.sql +13 -0
- package/dist/migrations/postgres/meta/0001_snapshot.json +1707 -0
- package/dist/migrations/postgres/meta/_journal.json +7 -0
- package/dist/{node-postgres-_y-EeSHQ.js → node-postgres-BHgGDfvg.js} +1 -1
- package/dist/{node-postgres-BlNvYfq_.js → node-postgres-BWEOOdc6.js} +1 -1
- package/dist/{portable-CPh2thNN.js → portable-9wWdi5e1.js} +4 -24
- package/dist/{relations-CJCGPRst.js → relations-BuTStNq-.js} +1 -5
- package/dist/{schema-sqlite-Byp7B3yI.js → schema-sqlite-BaFfy9lC.js} +5 -93
- package/dist/{setup-ClLcofuy.js → setup-BMmFAR3N.js} +3 -3
- package/dist/templates/node/package.json +3 -3
- package/dist/templates/workers/app/wrangler.jsonc +1 -1
- package/dist/templates/workers/package.json +4 -4
- package/package.json +4 -4
|
@@ -1,2 +1,2 @@
|
|
|
1
|
-
import { n as openDatabase, r as postgresConnection, t as engineOfUrl } from "./connect-
|
|
1
|
+
import { n as openDatabase, r as postgresConnection, t as engineOfUrl } from "./connect-tekAM4hC.js";
|
|
2
2
|
export { engineOfUrl, openDatabase, postgresConnection };
|
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
import { d as esm_default$1, m as __toESM, p as require_pg_connection_string } from "./esm-Bts-esAl.js";
|
|
2
2
|
import { n as ROLE_NAMES, r as assignableToEnvironment } from "./access-CO1kGjWW.js";
|
|
3
|
-
import {
|
|
4
|
-
import { r as drizzle } from "./node-postgres-
|
|
5
|
-
import { a as trackCommits, i as relationsOf, n as asPostgresDatabase, r as ACTIVE_SUBJECT } from "./portable-
|
|
3
|
+
import { D as customType, O as boolean, V as SQL, W as sql, b as text, ct as is, et as IndexedColumn, h as uuid, k as bigint, nt as unique, p as pgTable, rt as foreignKey, st as entityKind, v as timestamp, w as integer } from "./relations-BuTStNq-.js";
|
|
4
|
+
import { r as drizzle } from "./node-postgres-BHgGDfvg.js";
|
|
5
|
+
import { a as trackCommits, i as relationsOf, n as asPostgresDatabase, r as ACTIVE_SUBJECT } from "./portable-9wWdi5e1.js";
|
|
6
6
|
import { resolve } from "node:path";
|
|
7
7
|
import { AsyncLocalStorage } from "node:async_hooks";
|
|
8
8
|
import { checkServerIdentity } from "node:tls";
|
|
@@ -151,10 +151,6 @@ var schema_exports = /* @__PURE__ */ __exportAll({
|
|
|
151
151
|
secretVersions: () => secretVersions,
|
|
152
152
|
secrets: () => secrets,
|
|
153
153
|
secretsRelations: () => secretsRelations,
|
|
154
|
-
syncKeys: () => syncKeys,
|
|
155
|
-
syncKeysRelations: () => syncKeysRelations,
|
|
156
|
-
syncs: () => syncs,
|
|
157
|
-
syncsRelations: () => syncsRelations,
|
|
158
154
|
vaultGrants: () => vaultGrants,
|
|
159
155
|
vaultMembers: () => vaultMembers
|
|
160
156
|
});
|
|
@@ -503,81 +499,11 @@ const deviceAuthorizations = pgTable("device_authorizations", {
|
|
|
503
499
|
foreignColumns: [vaultMembers.principal]
|
|
504
500
|
}).onDelete("restrict")
|
|
505
501
|
]);
|
|
506
|
-
|
|
507
|
-
* An environment kept in step with a third-party service: GitHub Actions
|
|
508
|
-
* secrets, Vercel or Railway variables, Worker secrets.
|
|
509
|
-
*
|
|
510
|
-
* The destination's API token is itself a coffre secret, referenced by id, so
|
|
511
|
-
* it is encrypted, versioned and audited like everything else and never sits
|
|
512
|
-
* in this table.
|
|
513
|
-
*/
|
|
514
|
-
const syncs = pgTable("syncs", {
|
|
515
|
-
id: uuid().primaryKey().defaultRandom(),
|
|
516
|
-
projectId: uuid("project_id").notNull(),
|
|
517
|
-
environmentId: uuid("environment_id").notNull(),
|
|
518
|
-
provider: text().notNull(),
|
|
519
|
-
config: text().notNull(),
|
|
520
|
-
credentialSecretId: uuid("credential_secret_id").notNull(),
|
|
521
|
-
createdAt: createdAt(),
|
|
522
|
-
createdBy: text("created_by").notNull(),
|
|
523
|
-
pausedAt: timestamp("paused_at", { withTimezone: true }),
|
|
524
|
-
archivedAt: timestamp("archived_at", { withTimezone: true }),
|
|
525
|
-
leaseUntil: timestamp("lease_until", { withTimezone: true }),
|
|
526
|
-
lastRunAt: timestamp("last_run_at", { withTimezone: true }),
|
|
527
|
-
lastStatus: text("last_status"),
|
|
528
|
-
lastError: text("last_error")
|
|
529
|
-
}, (table) => [
|
|
530
|
-
check("syncs_config_check", sql`${table.config}::jsonb IS NOT NULL`),
|
|
531
|
-
check("syncs_last_status_check", sql`${table.lastStatus} IS NULL OR ${table.lastStatus} IN ('ok', 'partial', 'failed')`),
|
|
532
|
-
foreignKey({
|
|
533
|
-
name: "syncs_environment_in_project",
|
|
534
|
-
columns: [table.environmentId, table.projectId],
|
|
535
|
-
foreignColumns: [environments.id, environments.projectId]
|
|
536
|
-
}).onDelete("restrict"),
|
|
537
|
-
foreignKey({
|
|
538
|
-
name: "syncs_credential_secret_id_fkey",
|
|
539
|
-
columns: [table.credentialSecretId],
|
|
540
|
-
foreignColumns: [secrets.id]
|
|
541
|
-
}).onDelete("restrict"),
|
|
542
|
-
index("syncs_environment_idx").on(table.environmentId)
|
|
543
|
-
]);
|
|
544
|
-
/**
|
|
545
|
-
* What a sync last pushed, one row per key.
|
|
546
|
-
*
|
|
547
|
-
* Most destinations are write-only, so coffre cannot diff against them. It
|
|
548
|
-
* diffs against this instead: a key is stale when its secret has moved past
|
|
549
|
-
* the version recorded here. It is also the list of keys coffre may delete at
|
|
550
|
-
* the destination; a key it never pushed is never removed.
|
|
551
|
-
*/
|
|
552
|
-
const syncKeys = pgTable("sync_keys", {
|
|
553
|
-
syncId: uuid("sync_id").notNull(),
|
|
554
|
-
key: text().notNull(),
|
|
555
|
-
secretVersionId: uuid("secret_version_id"),
|
|
556
|
-
pushedAt: timestamp("pushed_at", { withTimezone: true }).notNull().defaultNow(),
|
|
557
|
-
removedAt: timestamp("removed_at", { withTimezone: true })
|
|
558
|
-
}, (table) => [
|
|
559
|
-
primaryKey({
|
|
560
|
-
name: "sync_keys_pkey",
|
|
561
|
-
columns: [table.syncId, table.key]
|
|
562
|
-
}),
|
|
563
|
-
foreignKey({
|
|
564
|
-
name: "sync_keys_sync_id_fkey",
|
|
565
|
-
columns: [table.syncId],
|
|
566
|
-
foreignColumns: [syncs.id]
|
|
567
|
-
}).onDelete("restrict"),
|
|
568
|
-
foreignKey({
|
|
569
|
-
name: "sync_keys_secret_version_id_fkey",
|
|
570
|
-
columns: [table.secretVersionId],
|
|
571
|
-
foreignColumns: [secretVersions.id]
|
|
572
|
-
}).onDelete("restrict")
|
|
573
|
-
]);
|
|
574
|
-
const { environmentsRelations, secretsRelations, syncsRelations, syncKeysRelations } = relationsOf({
|
|
502
|
+
const { environmentsRelations, secretsRelations } = relationsOf({
|
|
575
503
|
projects,
|
|
576
504
|
environments,
|
|
577
505
|
secrets,
|
|
578
|
-
secretVersions
|
|
579
|
-
syncs,
|
|
580
|
-
syncKeys
|
|
506
|
+
secretVersions
|
|
581
507
|
});
|
|
582
508
|
//#endregion
|
|
583
509
|
//#region ../db/dist/index.js
|
|
@@ -694,7 +620,7 @@ async function openDatabase(url) {
|
|
|
694
620
|
const [{ createClient }, { drizzle }, schema] = await Promise.all([
|
|
695
621
|
import("@libsql/client"),
|
|
696
622
|
import("drizzle-orm/libsql"),
|
|
697
|
-
import("./schema-sqlite-
|
|
623
|
+
import("./schema-sqlite-BaFfy9lC.js")
|
|
698
624
|
]);
|
|
699
625
|
const client = createClient({
|
|
700
626
|
url,
|
package/dist/main.js
CHANGED
|
@@ -8,30 +8,6 @@ import { homedir, hostname } from "node:os";
|
|
|
8
8
|
import { dirname, join, resolve } from "node:path";
|
|
9
9
|
import { createCipheriv, createDecipheriv, createHash, hkdfSync, randomBytes, timingSafeEqual } from "node:crypto";
|
|
10
10
|
//#region ../client/dist/index.js
|
|
11
|
-
/**
|
|
12
|
-
* The config from `name=value` arguments, as the CLI takes them. A field with
|
|
13
|
-
* several options takes them comma-separated, and one left out gets what the
|
|
14
|
-
* form preselects. Text goes as typed, for the server to check.
|
|
15
|
-
*/
|
|
16
|
-
function configFromArguments(provider, assignments) {
|
|
17
|
-
const config = {};
|
|
18
|
-
for (const assignment of assignments) {
|
|
19
|
-
const equals = assignment.indexOf("=");
|
|
20
|
-
const name = equals === -1 ? assignment : assignment.slice(0, equals);
|
|
21
|
-
const field = provider.fields.find((candidate) => candidate.name === name);
|
|
22
|
-
if (equals === -1 || field === void 0) {
|
|
23
|
-
const names = provider.fields.map((candidate) => candidate.name).join(", ");
|
|
24
|
-
throw new Error(`${provider.label} takes ${names} as name=value; got ${JSON.stringify(assignment)}`);
|
|
25
|
-
}
|
|
26
|
-
if (Object.hasOwn(config, name)) throw new Error(`${name} is given twice`);
|
|
27
|
-
const value = assignment.slice(equals + 1);
|
|
28
|
-
if (field.type === "text") config[name] = value;
|
|
29
|
-
else if (field.multiple) config[name] = value.split(",").map((item) => item.trim()).filter((item) => item !== "");
|
|
30
|
-
else config[name] = value;
|
|
31
|
-
}
|
|
32
|
-
for (const field of provider.fields) if (field.type === "options" && !Object.hasOwn(config, field.name)) config[field.name] = field.multiple ? field.initial : field.initial[0];
|
|
33
|
-
return config;
|
|
34
|
-
}
|
|
35
11
|
/** The API's error shape, `{ error, message }`, with the HTTP status. */
|
|
36
12
|
const COFFRE_ERROR = Symbol.for("@coffre/client:CoffreError");
|
|
37
13
|
var CoffreError = class extends Error {
|
|
@@ -174,15 +150,6 @@ set: (member, access) => call("PATCH /access/:member", { member }, access) },
|
|
|
174
150
|
get: (code) => call("GET /device-logins/:code", { code }),
|
|
175
151
|
decide: (code, approve) => call("POST /device-logins/:code", { code }, { approve })
|
|
176
152
|
},
|
|
177
|
-
syncs: {
|
|
178
|
-
/** Where a sync can push on this instance, and what each asks for. */
|
|
179
|
-
providers: () => call("GET /syncs/providers", {}),
|
|
180
|
-
list: (path) => call("GET /syncs/:project/:environment", place(path)),
|
|
181
|
-
add: (path, input) => call("POST /syncs/:project/:environment", place(path), input),
|
|
182
|
-
update: (id, patch) => call("PATCH /syncs/by-id/:id", { id }, patch),
|
|
183
|
-
remove: (id) => call("DELETE /syncs/by-id/:id", { id }),
|
|
184
|
-
run: (id) => call("POST /syncs/by-id/:id/runs", { id })
|
|
185
|
-
},
|
|
186
153
|
audit: {
|
|
187
154
|
list: (query = {}) => call("GET /audit", {}, query),
|
|
188
155
|
verify: () => call("GET /audit/verification", {}),
|
|
@@ -521,8 +488,6 @@ function getCalls({ places, secrets, members, services }) {
|
|
|
521
488
|
"GET /sessions": [{ params: {} }],
|
|
522
489
|
"GET /identities": [{ params: {} }],
|
|
523
490
|
"GET /device-logins/:code": [{ params: { code: "BCDF-GHJK" } }],
|
|
524
|
-
"GET /syncs/providers": [{ params: {} }],
|
|
525
|
-
"GET /syncs/:project/:environment": places.map((params) => ({ params })),
|
|
526
491
|
"GET /audit": [{
|
|
527
492
|
params: {},
|
|
528
493
|
input: { limit: 500 }
|
|
@@ -574,12 +539,6 @@ const EVERY_ROUTE = {
|
|
|
574
539
|
"DELETE /identities/:id": true,
|
|
575
540
|
"GET /device-logins/:code": true,
|
|
576
541
|
"POST /device-logins/:code": true,
|
|
577
|
-
"GET /syncs/providers": true,
|
|
578
|
-
"GET /syncs/:project/:environment": true,
|
|
579
|
-
"POST /syncs/:project/:environment": true,
|
|
580
|
-
"PATCH /syncs/by-id/:id": true,
|
|
581
|
-
"DELETE /syncs/by-id/:id": true,
|
|
582
|
-
"POST /syncs/by-id/:id/runs": true,
|
|
583
542
|
"GET /audit": true,
|
|
584
543
|
"GET /audit/verification": true,
|
|
585
544
|
"GET /audit/keys": true
|
|
@@ -939,8 +898,7 @@ async function tokenScope(api, canary) {
|
|
|
939
898
|
for (const path of [...others, nowhere]) {
|
|
940
899
|
await refused(`the token listed ${path}`, api.secrets.list(path));
|
|
941
900
|
await refused(`the token revealed ${path}`, api.secrets.reveal(path));
|
|
942
|
-
|
|
943
|
-
refusals += 3;
|
|
901
|
+
refusals += 2;
|
|
944
902
|
}
|
|
945
903
|
await refused(`the token read ${nowhere}'s audit`, api.audit.list({
|
|
946
904
|
path: nowhere,
|
|
@@ -2325,13 +2283,6 @@ async function offboard(args) {
|
|
|
2325
2283
|
report.exposed.forEach((entry, index) => {
|
|
2326
2284
|
process.stdout.write(` ${names[index].padEnd(width)} v${String(entry.version).padEnd(4)} ${entry.how} ${entry.at.slice(0, 10)}\n`);
|
|
2327
2285
|
});
|
|
2328
|
-
if (report.syncs.length > 0) {
|
|
2329
|
-
process.stdout.write(`\nSyncs ${they} set up, which keep pushing\n`);
|
|
2330
|
-
for (const entry of report.syncs) {
|
|
2331
|
-
const paused = entry.paused ? " (paused)" : "";
|
|
2332
|
-
process.stdout.write(` ${entry.project}/${entry.environment} -> ${entry.providerLabel} ${entry.destination}${paused}\n`);
|
|
2333
|
-
}
|
|
2334
|
-
}
|
|
2335
2286
|
if (report.issuedTokens.length > 0) {
|
|
2336
2287
|
process.stdout.write(`\nService tokens ${they} issued, which still work\n`);
|
|
2337
2288
|
for (const token of report.issuedTokens) {
|
|
@@ -2421,138 +2372,6 @@ async function verifyLog() {
|
|
|
2421
2372
|
process.stderr.write(`audit log BROKEN${at}, found by the ${result.author}'s check (${through}): ${result.reason}\n`);
|
|
2422
2373
|
process.exit(2);
|
|
2423
2374
|
}
|
|
2424
|
-
const SYNC_USAGE = `usage:
|
|
2425
|
-
coffre sync providers
|
|
2426
|
-
coffre sync list <project>/<environment>
|
|
2427
|
-
coffre sync add <project>/<environment> <provider> name=value… --credential <project>/<environment>/<KEY>
|
|
2428
|
-
coffre sync run <project>/<environment> <sync>
|
|
2429
|
-
coffre sync pause <project>/<environment> <sync>
|
|
2430
|
-
coffre sync resume <project>/<environment> <sync>
|
|
2431
|
-
coffre sync remove <project>/<environment> <sync>
|
|
2432
|
-
|
|
2433
|
-
\`coffre sync providers\` lists where this instance can sync to, and what each
|
|
2434
|
-
provider takes. <sync> is the start of an id from \`coffre sync list\`, or the
|
|
2435
|
-
provider's id when the environment syncs there only once.
|
|
2436
|
-
`;
|
|
2437
|
-
/** Where this instance can sync to, one provider to a paragraph. */
|
|
2438
|
-
function printProviders(providers) {
|
|
2439
|
-
if (providers.length === 0) {
|
|
2440
|
-
process.stdout.write("This instance offers no sync providers.\n");
|
|
2441
|
-
return;
|
|
2442
|
-
}
|
|
2443
|
-
process.stdout.write("What is in brackets can be left out. A choice left out is what comes\nbefore the first |, and several choices go comma-separated.\n");
|
|
2444
|
-
for (const provider of providers) process.stdout.write(`\n${provider.id} ${provider.label}\n ${[...provider.fields.map(fieldUsage), `--credential ${provider.credential.placeholder}`].join(" ")}\n${indented(provider.credential.hint)}\n`);
|
|
2445
|
-
}
|
|
2446
|
-
/** Prose under a usage line, indented and wrapped to a terminal's 80 columns. */
|
|
2447
|
-
function indented(text) {
|
|
2448
|
-
const lines = [];
|
|
2449
|
-
for (const word of text.split(/\s+/)) {
|
|
2450
|
-
const last = lines.length - 1;
|
|
2451
|
-
if (last >= 0 && lines[last].length + 1 + word.length <= 76) lines[last] += ` ${word}`;
|
|
2452
|
-
else lines.push(word);
|
|
2453
|
-
}
|
|
2454
|
-
return lines.map((line) => ` ${line}`).join("\n");
|
|
2455
|
-
}
|
|
2456
|
-
function fieldUsage(field) {
|
|
2457
|
-
if (field.type === "text") return field.optional ? `[${field.name}=…]` : `${field.name}=…`;
|
|
2458
|
-
const others = field.options.map((option) => option.value).filter((value) => !field.initial.includes(value));
|
|
2459
|
-
return `[${field.name}=${[field.initial.join(","), ...others].join("|")}]`;
|
|
2460
|
-
}
|
|
2461
|
-
/**
|
|
2462
|
-
* Push an environment somewhere else and keep it current. The server does
|
|
2463
|
-
* the work and checks every field; this names things and reports back.
|
|
2464
|
-
*/
|
|
2465
|
-
async function sync(args) {
|
|
2466
|
-
const [verb, path, ...rest] = args;
|
|
2467
|
-
if (verb === "providers") {
|
|
2468
|
-
const { providers } = await client().syncs.providers();
|
|
2469
|
-
printProviders(providers);
|
|
2470
|
-
return;
|
|
2471
|
-
}
|
|
2472
|
-
if (verb === void 0 || verb === "--help" || path === void 0) {
|
|
2473
|
-
process.stdout.write(SYNC_USAGE);
|
|
2474
|
-
process.exit(verb === void 0 || verb === "--help" ? 0 : 1);
|
|
2475
|
-
}
|
|
2476
|
-
const { project, environment, key } = parsePath(path);
|
|
2477
|
-
if (key !== void 0) fail(`syncs belong to an environment: use ${project}/${environment}`);
|
|
2478
|
-
const place = `${project}/${environment}`;
|
|
2479
|
-
const coffre = client();
|
|
2480
|
-
if (verb === "list") {
|
|
2481
|
-
const { syncs } = await coffre.syncs.list(place);
|
|
2482
|
-
if (syncs.length === 0) process.stdout.write(`${project}/${environment} is not synced anywhere\n`);
|
|
2483
|
-
for (const entry of syncs) printSync(entry);
|
|
2484
|
-
return;
|
|
2485
|
-
}
|
|
2486
|
-
if (verb === "add") {
|
|
2487
|
-
const { values, positionals } = parseArgs({
|
|
2488
|
-
args: rest,
|
|
2489
|
-
options: { credential: { type: "string" } },
|
|
2490
|
-
allowPositionals: true
|
|
2491
|
-
});
|
|
2492
|
-
const [id, ...assignments] = positionals;
|
|
2493
|
-
const { providers } = await coffre.syncs.providers();
|
|
2494
|
-
const provider = providers.find((candidate) => candidate.id === id);
|
|
2495
|
-
if (provider === void 0) fail(`name a provider this instance offers (${providers.length === 0 ? "none" : providers.map((candidate) => candidate.id).join(", ")}); \`coffre sync providers\` shows what each takes`);
|
|
2496
|
-
if (values.credential === void 0) fail(`--credential names the secret holding the ${provider.label} token, such as ${provider.credential.placeholder}`);
|
|
2497
|
-
const config = attempt(() => configFromArguments(provider, assignments));
|
|
2498
|
-
const created = await coffre.syncs.add(place, {
|
|
2499
|
-
provider: provider.id,
|
|
2500
|
-
config,
|
|
2501
|
-
credential: values.credential
|
|
2502
|
-
});
|
|
2503
|
-
process.stdout.write(`Syncing ${project}/${environment} to ${created.destination} (${created.providerLabel}), id ${created.id.slice(0, 8)}.\nThe first push has started; \`coffre sync list ${project}/${environment}\` shows how it went.\n`);
|
|
2504
|
-
return;
|
|
2505
|
-
}
|
|
2506
|
-
if (![
|
|
2507
|
-
"run",
|
|
2508
|
-
"pause",
|
|
2509
|
-
"resume",
|
|
2510
|
-
"remove"
|
|
2511
|
-
].includes(verb)) fail(`unknown sync command "${verb}"; see \`coffre sync --help\``);
|
|
2512
|
-
const reference = rest[0];
|
|
2513
|
-
if (reference === void 0) fail(`usage: coffre sync ${verb} ${project}/${environment} <sync>`);
|
|
2514
|
-
const { syncs } = await coffre.syncs.list(place);
|
|
2515
|
-
const chosen = attempt(() => pickSync(syncs, reference));
|
|
2516
|
-
if (verb === "run") {
|
|
2517
|
-
if (chosen.paused) fail(`syncing to ${chosen.destination} is paused; resume it first`);
|
|
2518
|
-
const { sync: after, outcome } = await coffre.syncs.run(chosen.id);
|
|
2519
|
-
if (outcome.status === "busy") fail(`a run to ${chosen.destination} is already under way`);
|
|
2520
|
-
for (const failure of outcome.failed) process.stderr.write(` could not ${failure.operation === "upsert" ? "push" : "remove"} ${failure.key}: ${failure.message}\n`);
|
|
2521
|
-
if (outcome.status === "failed") fail(`could not sync to ${after.destination}: ${outcome.error ?? "the run failed"}`);
|
|
2522
|
-
const changed = outcome.upserted.length + outcome.deleted.length;
|
|
2523
|
-
process.stdout.write(changed === 0 && outcome.failed.length === 0 ? `${after.destination} was already current\n` : `Pushed ${outcome.upserted.length}, removed ${outcome.deleted.length} at ${after.destination}\n`);
|
|
2524
|
-
if (outcome.status === "partial") process.exit(1);
|
|
2525
|
-
return;
|
|
2526
|
-
}
|
|
2527
|
-
if (verb === "remove") {
|
|
2528
|
-
await coffre.syncs.remove(chosen.id);
|
|
2529
|
-
process.stdout.write(`No longer syncing to ${chosen.destination}. Keys coffre pushed there stay; remove them at ${chosen.providerLabel} if they should go too.\n`);
|
|
2530
|
-
return;
|
|
2531
|
-
}
|
|
2532
|
-
const paused = verb === "pause";
|
|
2533
|
-
await coffre.syncs.update(chosen.id, { paused });
|
|
2534
|
-
process.stdout.write(paused ? `Paused syncing to ${chosen.destination}\n` : `Resumed syncing to ${chosen.destination}; changes since the pause go out on the next run\n`);
|
|
2535
|
-
}
|
|
2536
|
-
/** By id prefix, or by provider when only one sync here goes there. */
|
|
2537
|
-
function pickSync(syncs, reference) {
|
|
2538
|
-
const byProvider = syncs.filter((entry) => entry.provider === reference);
|
|
2539
|
-
if (byProvider.length === 1) return byProvider[0];
|
|
2540
|
-
if (byProvider.length > 1) throw new Error(`more than one ${reference} sync here; name it by id (coffre sync list)`);
|
|
2541
|
-
if (reference.length < 4) throw new Error("give at least 4 characters of a sync id, or a provider id");
|
|
2542
|
-
const byId = syncs.filter((entry) => entry.id.startsWith(reference.toLowerCase()));
|
|
2543
|
-
if (byId.length === 1) return byId[0];
|
|
2544
|
-
if (byId.length > 1) throw new Error(`"${reference}" starts more than one sync id; give more of it`);
|
|
2545
|
-
throw new Error(`no sync "${reference}" here; \`coffre sync list\` shows them`);
|
|
2546
|
-
}
|
|
2547
|
-
function printSync(entry) {
|
|
2548
|
-
const state = entry.running ? "pushing" : entry.paused ? "paused" : entry.lastStatus === "failed" ? "failed" : entry.lastStatus === "partial" ? "partial" : entry.lastRunAt === null ? "not run yet" : entry.pending > 0 ? "behind" : "in sync";
|
|
2549
|
-
const counts = [`${entry.synced} synced`, ...entry.pending > 0 ? [`${entry.pending} pending`] : []];
|
|
2550
|
-
if (entry.lastRunAt !== null) counts.push(`last run ${entry.lastRunAt.slice(0, 16).replace("T", " ")}`);
|
|
2551
|
-
process.stdout.write(`${entry.id.slice(0, 8)} ${entry.providerLabel.padEnd(18)} ${entry.destination}\n`);
|
|
2552
|
-
process.stdout.write(` ${state}: ${counts.join(", ")}; token from ${entry.credential}\n`);
|
|
2553
|
-
if (entry.lastError !== null && !entry.running) process.stdout.write(` ${entry.lastError}\n`);
|
|
2554
|
-
for (const skipped of entry.skipped) process.stdout.write(` skips ${skipped.key}: ${skipped.reason}\n`);
|
|
2555
|
-
}
|
|
2556
2375
|
/** `coffre init --workers|--node [<dir>]`: a new deployment of coffre. */
|
|
2557
2376
|
function initProject(args) {
|
|
2558
2377
|
const { values, positionals } = parseArgs({
|
|
@@ -2609,11 +2428,6 @@ const USAGE = `coffre - secrets, with an audit log
|
|
|
2609
2428
|
coffre offboard <principal> [--service] [--apply]
|
|
2610
2429
|
what removing them revokes, and what to rotate
|
|
2611
2430
|
|
|
2612
|
-
Syncs (coffre sync providers for where to, and what each takes)
|
|
2613
|
-
coffre sync list <project>/<environment>
|
|
2614
|
-
coffre sync add <project>/<environment> <provider> name=value…
|
|
2615
|
-
--credential <project>/<environment>/<KEY>
|
|
2616
|
-
coffre sync run|pause|resume|remove <project>/<environment> <sync>
|
|
2617
2431
|
|
|
2618
2432
|
Audit
|
|
2619
2433
|
coffre audit [--limit N] [--actor <id>] [--denied] [--detail]
|
|
@@ -2639,7 +2453,7 @@ switch (command) {
|
|
|
2639
2453
|
await keys(rest);
|
|
2640
2454
|
break;
|
|
2641
2455
|
case "setup":
|
|
2642
|
-
await (await import("./setup-
|
|
2456
|
+
await (await import("./setup-BMmFAR3N.js")).setup(rest);
|
|
2643
2457
|
break;
|
|
2644
2458
|
case "login":
|
|
2645
2459
|
await login(rest);
|
|
@@ -2692,9 +2506,6 @@ switch (command) {
|
|
|
2692
2506
|
case "offboard":
|
|
2693
2507
|
await offboard(rest);
|
|
2694
2508
|
break;
|
|
2695
|
-
case "sync":
|
|
2696
|
-
await sync(rest);
|
|
2697
|
-
break;
|
|
2698
2509
|
case "audit":
|
|
2699
2510
|
await audit(rest);
|
|
2700
2511
|
break;
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
-- Stop old app instances before upgrading. Never silently discard a configured
|
|
2
|
+
-- destination, including archived ones. Keep the audit and sealed legacy rows.
|
|
3
|
+
LOCK TABLE syncs, sync_keys IN ACCESS EXCLUSIVE MODE;
|
|
4
|
+
--> statement-breakpoint
|
|
5
|
+
DO $$ BEGIN
|
|
6
|
+
IF EXISTS (SELECT 1 FROM syncs) OR EXISTS (SELECT 1 FROM sync_keys) THEN
|
|
7
|
+
RAISE EXCEPTION 'syncs are removed: migrate destinations to service tokens, back up and clear syncs and sync_keys before upgrading';
|
|
8
|
+
END IF;
|
|
9
|
+
END $$;
|
|
10
|
+
--> statement-breakpoint
|
|
11
|
+
DROP TABLE sync_keys;
|
|
12
|
+
--> statement-breakpoint
|
|
13
|
+
DROP TABLE syncs;
|