@tickernelz/wardmote 2026.1111.0 → 2026.1112.0
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/index.js +367 -85
- package/dist/index.js.map +3 -3
- package/package.json +2 -2
package/dist/index.js
CHANGED
|
@@ -1039,7 +1039,7 @@ var init_constants = __esm({
|
|
|
1039
1039
|
"pipelines:write",
|
|
1040
1040
|
"joins:approve"
|
|
1041
1041
|
];
|
|
1042
|
-
TOOL_APPLICATION_TYPES = ["rest_api", "mcp_http", "mcp_stdio", "
|
|
1042
|
+
TOOL_APPLICATION_TYPES = ["rest_api", "mcp_http", "mcp_stdio", "wardmote_plugin", "a2a", "chat"];
|
|
1043
1043
|
TOOL_APPLICATION_STATUSES = ["draft", "active", "disabled", "archived"];
|
|
1044
1044
|
TOOL_CONNECTION_KINDS = ["managed"];
|
|
1045
1045
|
TOOL_CONNECTION_HEALTH_STATUSES = [
|
|
@@ -16605,17 +16605,20 @@ var init_smoke_lab = __esm({
|
|
|
16605
16605
|
});
|
|
16606
16606
|
|
|
16607
16607
|
// ../packages/shared/src/types/instance.ts
|
|
16608
|
-
var DAILY_RETENTION_PRESETS, WEEKLY_RETENTION_PRESETS, MONTHLY_RETENTION_PRESETS, DEFAULT_BACKUP_RETENTION, SPEECH_TO_TEXT_DEFAULTS, ATTACHMENT_MAX_MEGABYTES_MIN, ATTACHMENT_MAX_MEGABYTES_MAX, ATTACHMENT_ORPHAN_AFTER_DAYS_MIN, ATTACHMENT_ORPHAN_AFTER_DAYS_MAX, ATTACHMENT_CLOSED_TASK_AFTER_DAYS_MIN, ATTACHMENT_CLOSED_TASK_AFTER_DAYS_MAX, DEFAULT_ATTACHMENT_RETENTION, DEFAULT_LOCAL_RUN_LIMITS_SETTINGS, DEFAULT_MESSAGE_DELIVERY;
|
|
16608
|
+
var DAILY_RETENTION_PRESETS, WEEKLY_RETENTION_PRESETS, MONTHLY_RETENTION_PRESETS, BACKUP_MAX_FILES_MIN, BACKUP_MAX_FILES_MAX, DEFAULT_BACKUP_RETENTION, SPEECH_TO_TEXT_DEFAULTS, ATTACHMENT_MAX_MEGABYTES_MIN, ATTACHMENT_MAX_MEGABYTES_MAX, ATTACHMENT_ORPHAN_AFTER_DAYS_MIN, ATTACHMENT_ORPHAN_AFTER_DAYS_MAX, ATTACHMENT_CLOSED_TASK_AFTER_DAYS_MIN, ATTACHMENT_CLOSED_TASK_AFTER_DAYS_MAX, DEFAULT_ATTACHMENT_RETENTION, DEFAULT_LOCAL_RUN_LIMITS_SETTINGS, DEFAULT_MESSAGE_DELIVERY;
|
|
16609
16609
|
var init_instance = __esm({
|
|
16610
16610
|
"../packages/shared/src/types/instance.ts"() {
|
|
16611
16611
|
"use strict";
|
|
16612
16612
|
DAILY_RETENTION_PRESETS = [3, 7, 14];
|
|
16613
16613
|
WEEKLY_RETENTION_PRESETS = [1, 2, 4];
|
|
16614
16614
|
MONTHLY_RETENTION_PRESETS = [1, 3, 6];
|
|
16615
|
+
BACKUP_MAX_FILES_MIN = 1;
|
|
16616
|
+
BACKUP_MAX_FILES_MAX = 48;
|
|
16615
16617
|
DEFAULT_BACKUP_RETENTION = {
|
|
16616
16618
|
dailyDays: 7,
|
|
16617
16619
|
weeklyWeeks: 4,
|
|
16618
|
-
monthlyMonths: 1
|
|
16620
|
+
monthlyMonths: 1,
|
|
16621
|
+
maxFiles: 2
|
|
16619
16622
|
};
|
|
16620
16623
|
SPEECH_TO_TEXT_DEFAULTS = {
|
|
16621
16624
|
enabled: false,
|
|
@@ -17214,7 +17217,8 @@ var init_instance2 = __esm({
|
|
|
17214
17217
|
backupRetentionPolicySchema = z40.object({
|
|
17215
17218
|
dailyDays: presetSchema(DAILY_RETENTION_PRESETS, "dailyDays").default(DEFAULT_BACKUP_RETENTION.dailyDays),
|
|
17216
17219
|
weeklyWeeks: presetSchema(WEEKLY_RETENTION_PRESETS, "weeklyWeeks").default(DEFAULT_BACKUP_RETENTION.weeklyWeeks),
|
|
17217
|
-
monthlyMonths: presetSchema(MONTHLY_RETENTION_PRESETS, "monthlyMonths").default(DEFAULT_BACKUP_RETENTION.monthlyMonths)
|
|
17220
|
+
monthlyMonths: presetSchema(MONTHLY_RETENTION_PRESETS, "monthlyMonths").default(DEFAULT_BACKUP_RETENTION.monthlyMonths),
|
|
17221
|
+
maxFiles: z40.number().int().min(BACKUP_MAX_FILES_MIN).max(BACKUP_MAX_FILES_MAX).nullable().default(DEFAULT_BACKUP_RETENTION.maxFiles)
|
|
17218
17222
|
});
|
|
17219
17223
|
speechToTextSettingsSchema = z40.object({
|
|
17220
17224
|
enabled: z40.boolean().default(SPEECH_TO_TEXT_DEFAULTS.enabled),
|
|
@@ -20431,7 +20435,7 @@ function findWardmoteConfigKeyWarnings(config) {
|
|
|
20431
20435
|
collectConfigKeyWarnings(config, wardmoteConfigSchema, "", warnings);
|
|
20432
20436
|
return warnings;
|
|
20433
20437
|
}
|
|
20434
|
-
var configMetaSchema, llmConfigSchema, databaseBackupConfigSchema, databaseConfigSchema, loggingConfigSchema, serverConfigSchema, authConfigSchema, storageLocalDiskConfigSchema, storageS3ConfigSchema, storageConfigSchema, secretsLocalEncryptedConfigSchema, secretsConfigSchema, telemetryConfigSchema, updatesConfigSchema, runLogRetentionConfigSchema, runLogsConfigSchema, wardmoteConfigSchema;
|
|
20438
|
+
var configMetaSchema, llmConfigSchema, databaseBackupConfigSchema, databaseConfigSchema, loggingConfigSchema, serverConfigSchema, authConfigSchema, storageLocalDiskConfigSchema, storageS3ConfigSchema, storageConfigSchema, secretsLocalEncryptedConfigSchema, secretsConfigSchema, telemetryConfigSchema, updatesConfigSchema, runLogRetentionConfigSchema, diskGuardConfigSchema, runLogsConfigSchema, wardmoteConfigSchema;
|
|
20435
20439
|
var init_config_schema = __esm({
|
|
20436
20440
|
"../packages/shared/src/config-schema.ts"() {
|
|
20437
20441
|
"use strict";
|
|
@@ -20450,7 +20454,11 @@ var init_config_schema = __esm({
|
|
|
20450
20454
|
enabled: z70.boolean().default(true),
|
|
20451
20455
|
intervalMinutes: z70.number().int().min(1).max(7 * 24 * 60).default(60),
|
|
20452
20456
|
retentionDays: z70.number().int().min(1).max(3650).default(7),
|
|
20453
|
-
dir: z70.string().default("~/.wardmote/instances/default/data/backups")
|
|
20457
|
+
dir: z70.string().default("~/.wardmote/instances/default/data/backups"),
|
|
20458
|
+
maxFiles: z70.number().int().min(1).max(48).optional(),
|
|
20459
|
+
reservePercent: z70.number().min(0).max(50).optional(),
|
|
20460
|
+
reserveGb: z70.number().min(0).optional(),
|
|
20461
|
+
pinnedMaxAgeDays: z70.number().int().min(0).max(3650).optional()
|
|
20454
20462
|
}).passthrough();
|
|
20455
20463
|
databaseConfigSchema = z70.object({
|
|
20456
20464
|
mode: z70.enum(["embedded-postgres", "postgres"]).default("embedded-postgres"),
|
|
@@ -20530,6 +20538,19 @@ var init_config_schema = __esm({
|
|
|
20530
20538
|
recentTailBytes: z70.number().int().min(0).default(5 * 1024 * 1024),
|
|
20531
20539
|
oldTailBytes: z70.number().int().min(0).default(256 * 1024)
|
|
20532
20540
|
}).passthrough().prefault({});
|
|
20541
|
+
diskGuardConfigSchema = z70.object({
|
|
20542
|
+
enabled: z70.boolean().default(true),
|
|
20543
|
+
intervalMinutes: z70.number().int().min(1).max(24 * 60).default(5),
|
|
20544
|
+
warnFreePercent: z70.number().min(0).max(100).default(20),
|
|
20545
|
+
warnFreeGb: z70.number().min(0).default(100),
|
|
20546
|
+
actionFreePercent: z70.number().min(0).max(100).default(10),
|
|
20547
|
+
emergencyFreePercent: z70.number().min(0).max(100).default(5),
|
|
20548
|
+
ompSessionMaxIdleDays: z70.number().int().min(5).max(3650).default(7),
|
|
20549
|
+
ompSessionDir: z70.string().optional(),
|
|
20550
|
+
ompBlobDir: z70.string().optional(),
|
|
20551
|
+
ompAgentDir: z70.string().optional(),
|
|
20552
|
+
ompSessionSweep: z70.boolean().optional()
|
|
20553
|
+
}).passthrough().prefault({});
|
|
20533
20554
|
runLogsConfigSchema = z70.object({
|
|
20534
20555
|
retention: runLogRetentionConfigSchema
|
|
20535
20556
|
}).passthrough().prefault({});
|
|
@@ -20564,7 +20585,8 @@ var init_config_schema = __esm({
|
|
|
20564
20585
|
keyFilePath: "~/.wardmote/instances/default/secrets/master.key"
|
|
20565
20586
|
}
|
|
20566
20587
|
}),
|
|
20567
|
-
runLogs: runLogsConfigSchema.optional()
|
|
20588
|
+
runLogs: runLogsConfigSchema.optional(),
|
|
20589
|
+
diskGuard: diskGuardConfigSchema.optional()
|
|
20568
20590
|
}).passthrough().superRefine((value, ctx) => {
|
|
20569
20591
|
if (value.server.deploymentMode === "local_trusted" && value.server.exposure !== "private") {
|
|
20570
20592
|
ctx.addIssue({
|
|
@@ -20671,7 +20693,8 @@ var init_notifications = __esm({
|
|
|
20671
20693
|
"assignment",
|
|
20672
20694
|
"review",
|
|
20673
20695
|
"run_failed",
|
|
20674
|
-
"join_request"
|
|
20696
|
+
"join_request",
|
|
20697
|
+
"disk_space"
|
|
20675
20698
|
];
|
|
20676
20699
|
}
|
|
20677
20700
|
});
|
|
@@ -47801,11 +47824,12 @@ var init_test_embedded_postgres = __esm({
|
|
|
47801
47824
|
});
|
|
47802
47825
|
|
|
47803
47826
|
// ../packages/db/src/backup-lib.ts
|
|
47804
|
-
import { createReadStream, createWriteStream, existsSync, mkdirSync, readdirSync, renameSync, statSync, unlinkSync } from "node:fs";
|
|
47805
|
-
import { basename, resolve as resolve2 } from "node:path";
|
|
47827
|
+
import { createReadStream, createWriteStream, existsSync, mkdirSync, readdirSync, renameSync, rmSync, statfsSync, statSync, unlinkSync, writeFileSync } from "node:fs";
|
|
47828
|
+
import { basename, join as join2, resolve as resolve2 } from "node:path";
|
|
47806
47829
|
import { createInterface as createInterface2 } from "node:readline";
|
|
47807
47830
|
import { spawn as spawn4 } from "node:child_process";
|
|
47808
47831
|
import { open as openFile } from "node:fs/promises";
|
|
47832
|
+
import { Writable } from "node:stream";
|
|
47809
47833
|
import { pipeline } from "node:stream/promises";
|
|
47810
47834
|
import { createGunzip, createGzip } from "node:zlib";
|
|
47811
47835
|
import postgres3 from "postgres";
|
|
@@ -47840,9 +47864,169 @@ function monthlyRetentionCutoff(nowMs, monthlyMonths) {
|
|
|
47840
47864
|
const now = new Date(nowMs);
|
|
47841
47865
|
return Date.UTC(now.getUTCFullYear(), now.getUTCMonth() - months, 1);
|
|
47842
47866
|
}
|
|
47867
|
+
function readBackupDiskSpace(dir) {
|
|
47868
|
+
const stats = statfsSync(dir);
|
|
47869
|
+
return { freeBytes: stats.bavail * stats.bsize, totalBytes: stats.blocks * stats.bsize };
|
|
47870
|
+
}
|
|
47871
|
+
function pinnedBackupDir(backupDir) {
|
|
47872
|
+
return join2(backupDir, PINNED_BACKUP_DIRNAME);
|
|
47873
|
+
}
|
|
47874
|
+
function backupIdentity(file2) {
|
|
47875
|
+
const stats = statSync(file2);
|
|
47876
|
+
return `${stats.size}:${stats.mtimeMs}`;
|
|
47877
|
+
}
|
|
47878
|
+
async function verifyGzipBackup(file2) {
|
|
47879
|
+
if (statSync(file2).size === 0) throw new Error(`Backup ${basename(file2)} is empty`);
|
|
47880
|
+
await pipeline(
|
|
47881
|
+
createReadStream(file2, { highWaterMark: 1024 * 1024 }),
|
|
47882
|
+
createGunzip({ chunkSize: 1024 * 1024 }),
|
|
47883
|
+
new Writable({ write(_chunk, _encoding, callback) {
|
|
47884
|
+
callback();
|
|
47885
|
+
} })
|
|
47886
|
+
);
|
|
47887
|
+
}
|
|
47888
|
+
function isArchiveCorruption(error) {
|
|
47889
|
+
const code = error?.code;
|
|
47890
|
+
return typeof code === "string" && code.startsWith("Z_");
|
|
47891
|
+
}
|
|
47892
|
+
async function checkBackup(file2) {
|
|
47893
|
+
const stats = statSync(file2, { throwIfNoEntry: false });
|
|
47894
|
+
if (!stats || stats.size === 0) return "invalid";
|
|
47895
|
+
if (!file2.endsWith(".gz")) return "valid";
|
|
47896
|
+
if (verifiedBackups.get(file2) === `${stats.size}:${stats.mtimeMs}`) return "valid";
|
|
47897
|
+
try {
|
|
47898
|
+
await verifyGzipBackup(file2);
|
|
47899
|
+
} catch (error) {
|
|
47900
|
+
return isArchiveCorruption(error) ? "invalid" : "unreadable";
|
|
47901
|
+
}
|
|
47902
|
+
verifiedBackups.set(file2, backupIdentity(file2));
|
|
47903
|
+
return "valid";
|
|
47904
|
+
}
|
|
47905
|
+
function listRotationBackups(backupDir, filenamePrefix) {
|
|
47906
|
+
if (!existsSync(backupDir)) return [];
|
|
47907
|
+
const entries2 = [];
|
|
47908
|
+
for (const name of readdirSync(backupDir)) {
|
|
47909
|
+
if (!name.startsWith(`${filenamePrefix}-`) || !isBackupFilename(name)) continue;
|
|
47910
|
+
const fullPath = resolve2(backupDir, name);
|
|
47911
|
+
const stats = statSync(fullPath, { throwIfNoEntry: false });
|
|
47912
|
+
if (!stats?.isFile()) continue;
|
|
47913
|
+
entries2.push({ fullPath, sizeBytes: stats.size, mtimeMs: stats.mtimeMs });
|
|
47914
|
+
}
|
|
47915
|
+
return entries2.sort((a, b) => b.mtimeMs - a.mtimeMs);
|
|
47916
|
+
}
|
|
47917
|
+
function removeBackupFile(file2) {
|
|
47918
|
+
rmSync(file2, { force: true });
|
|
47919
|
+
verifiedBackups.delete(file2);
|
|
47920
|
+
}
|
|
47921
|
+
async function pruneToMaxFiles(backupDir, filenamePrefix, maxFiles, newestFile) {
|
|
47922
|
+
let kept = 1;
|
|
47923
|
+
let removed = 0;
|
|
47924
|
+
const unreadable = [];
|
|
47925
|
+
for (const entry of listRotationBackups(backupDir, filenamePrefix)) {
|
|
47926
|
+
if (entry.fullPath === newestFile) continue;
|
|
47927
|
+
const check46 = await checkBackup(entry.fullPath);
|
|
47928
|
+
if (check46 === "unreadable") {
|
|
47929
|
+
unreadable.push(entry.fullPath);
|
|
47930
|
+
continue;
|
|
47931
|
+
}
|
|
47932
|
+
if (kept < maxFiles && check46 === "valid") {
|
|
47933
|
+
kept += 1;
|
|
47934
|
+
continue;
|
|
47935
|
+
}
|
|
47936
|
+
removeBackupFile(entry.fullPath);
|
|
47937
|
+
removed += 1;
|
|
47938
|
+
}
|
|
47939
|
+
return { removed, unreadable };
|
|
47940
|
+
}
|
|
47941
|
+
function prunePinnedBackups(backupDir, maxAgeDays, nowMs = Date.now()) {
|
|
47942
|
+
if (!maxAgeDays || maxAgeDays <= 0) return 0;
|
|
47943
|
+
const dir = pinnedBackupDir(backupDir);
|
|
47944
|
+
if (!existsSync(dir)) return 0;
|
|
47945
|
+
let removed = 0;
|
|
47946
|
+
for (const name of readdirSync(dir)) {
|
|
47947
|
+
if (!isBackupFilename(name)) continue;
|
|
47948
|
+
const fullPath = resolve2(dir, name);
|
|
47949
|
+
const stats = statSync(fullPath, { throwIfNoEntry: false });
|
|
47950
|
+
if (!stats?.isFile() || nowMs - stats.mtimeMs < maxAgeDays * DAY_MS) continue;
|
|
47951
|
+
removeBackupFile(fullPath);
|
|
47952
|
+
removed += 1;
|
|
47953
|
+
}
|
|
47954
|
+
return removed;
|
|
47955
|
+
}
|
|
47956
|
+
function backupReserveBytes(guard, totalBytes) {
|
|
47957
|
+
return Math.max(guard.reserveBytes, Math.ceil(totalBytes * guard.reservePercent / 100));
|
|
47958
|
+
}
|
|
47959
|
+
async function ensureBackupSpace(input2) {
|
|
47960
|
+
const { guard } = input2;
|
|
47961
|
+
const statfs = guard.statfs ?? readBackupDiskSpace;
|
|
47962
|
+
const existing = listRotationBackups(input2.backupDir, input2.filenamePrefix);
|
|
47963
|
+
const lastSize = existing.find((entry) => entry.sizeBytes > 0)?.sizeBytes ?? 0;
|
|
47964
|
+
const space = statfs(input2.targetDir);
|
|
47965
|
+
const requiredBytes = Math.ceil(lastSize * (guard.headroomFactor ?? DEFAULT_BACKUP_HEADROOM_FACTOR)) + backupReserveBytes(guard, space.totalBytes);
|
|
47966
|
+
if (space.freeBytes >= requiredBytes) return 0;
|
|
47967
|
+
if (input2.rotate) {
|
|
47968
|
+
const invalid = [];
|
|
47969
|
+
const valid = [];
|
|
47970
|
+
for (const entry of existing) {
|
|
47971
|
+
const check46 = await checkBackup(entry.fullPath);
|
|
47972
|
+
if (check46 === "valid") valid.push(entry);
|
|
47973
|
+
else if (check46 === "invalid") invalid.push(entry);
|
|
47974
|
+
}
|
|
47975
|
+
const chosen = [];
|
|
47976
|
+
let reclaimed = 0;
|
|
47977
|
+
for (const entry of [...invalid, ...valid.slice(1).reverse()]) {
|
|
47978
|
+
if (space.freeBytes + reclaimed >= requiredBytes) break;
|
|
47979
|
+
chosen.push(entry);
|
|
47980
|
+
reclaimed += entry.sizeBytes;
|
|
47981
|
+
}
|
|
47982
|
+
if (space.freeBytes + reclaimed >= requiredBytes) {
|
|
47983
|
+
for (const entry of chosen) removeBackupFile(entry.fullPath);
|
|
47984
|
+
return chosen.length;
|
|
47985
|
+
}
|
|
47986
|
+
writeFileSync(
|
|
47987
|
+
join2(input2.backupDir, DATABASE_BACKUP_SKIPPED_MARKER),
|
|
47988
|
+
JSON.stringify({
|
|
47989
|
+
skippedAt: (/* @__PURE__ */ new Date()).toISOString(),
|
|
47990
|
+
freeBytes: space.freeBytes,
|
|
47991
|
+
requiredBytes,
|
|
47992
|
+
totalBytes: space.totalBytes
|
|
47993
|
+
})
|
|
47994
|
+
);
|
|
47995
|
+
}
|
|
47996
|
+
throw new DatabaseBackupSkippedError(input2.backupDir, space.freeBytes, requiredBytes);
|
|
47997
|
+
}
|
|
47843
47998
|
function isBackupFilename(name) {
|
|
47844
47999
|
return name.endsWith(".sql") || name.endsWith(".sql.gz");
|
|
47845
48000
|
}
|
|
48001
|
+
function partialBackupPath(finalPath) {
|
|
48002
|
+
return `${finalPath}.${process.pid}.partial`;
|
|
48003
|
+
}
|
|
48004
|
+
function isProcessAlive(pid) {
|
|
48005
|
+
try {
|
|
48006
|
+
process.kill(pid, 0);
|
|
48007
|
+
return true;
|
|
48008
|
+
} catch (error) {
|
|
48009
|
+
return error.code === "EPERM";
|
|
48010
|
+
}
|
|
48011
|
+
}
|
|
48012
|
+
function removeAbandonedPartialBackups(backupDir, nowMs = Date.now()) {
|
|
48013
|
+
if (!existsSync(backupDir)) return 0;
|
|
48014
|
+
let removed = 0;
|
|
48015
|
+
for (const name of readdirSync(backupDir)) {
|
|
48016
|
+
const match = PARTIAL_BACKUP_PATTERN.exec(name);
|
|
48017
|
+
if (!match || !isBackupFilename(name.slice(0, match.index))) continue;
|
|
48018
|
+
const fullPath = resolve2(backupDir, name);
|
|
48019
|
+
const pid = Number(match[1]);
|
|
48020
|
+
const ownerGone = pid === process.pid ? !activePartialBackupFiles.has(fullPath) : !isProcessAlive(pid);
|
|
48021
|
+
if (!ownerGone) {
|
|
48022
|
+
const mtimeMs = statSync(fullPath, { throwIfNoEntry: false })?.mtimeMs;
|
|
48023
|
+
if (mtimeMs === void 0 || nowMs - mtimeMs < ABANDONED_PARTIAL_BACKUP_AGE_MS) continue;
|
|
48024
|
+
}
|
|
48025
|
+
rmSync(fullPath, { force: true });
|
|
48026
|
+
removed += 1;
|
|
48027
|
+
}
|
|
48028
|
+
return removed;
|
|
48029
|
+
}
|
|
47846
48030
|
function adoptRetiredBackupFilenames(backupDir) {
|
|
47847
48031
|
if (!existsSync(backupDir)) return 0;
|
|
47848
48032
|
let renamed = 0;
|
|
@@ -48203,12 +48387,50 @@ async function runDatabaseBackup(opts) {
|
|
|
48203
48387
|
sqlClosed = true;
|
|
48204
48388
|
await sql72.end();
|
|
48205
48389
|
};
|
|
48206
|
-
|
|
48390
|
+
const targetDir = opts.pinned ? pinnedBackupDir(opts.backupDir) : opts.backupDir;
|
|
48391
|
+
mkdirSync(targetDir, { recursive: true });
|
|
48207
48392
|
if (filenamePrefix === DATABASE_BACKUP_FILENAME_PREFIX) adoptRetiredBackupFilenames(opts.backupDir);
|
|
48208
48393
|
const checkpointSuffix = opts.verifyBeforePrune ? `-${randomUUID3()}` : "";
|
|
48209
|
-
|
|
48210
|
-
|
|
48394
|
+
removeAbandonedPartialBackups(targetDir);
|
|
48395
|
+
let freedForSpace = 0;
|
|
48396
|
+
if (opts.spaceGuard) {
|
|
48397
|
+
try {
|
|
48398
|
+
freedForSpace = await ensureBackupSpace({
|
|
48399
|
+
backupDir: opts.backupDir,
|
|
48400
|
+
targetDir,
|
|
48401
|
+
filenamePrefix,
|
|
48402
|
+
guard: opts.spaceGuard,
|
|
48403
|
+
rotate: !opts.pinned
|
|
48404
|
+
});
|
|
48405
|
+
} catch (error) {
|
|
48406
|
+
await closeSql();
|
|
48407
|
+
throw error;
|
|
48408
|
+
}
|
|
48409
|
+
}
|
|
48410
|
+
const finalSqlFile = resolve2(targetDir, `${filenamePrefix}-${timestamp171()}${checkpointSuffix}.sql`);
|
|
48411
|
+
const backupFile = `${finalSqlFile}.gz`;
|
|
48412
|
+
const sqlFile = partialBackupPath(finalSqlFile);
|
|
48413
|
+
const partialBackupFile = partialBackupPath(backupFile);
|
|
48414
|
+
activePartialBackupFiles.add(sqlFile);
|
|
48415
|
+
activePartialBackupFiles.add(partialBackupFile);
|
|
48211
48416
|
const writer = createBufferedTextFileWriter(sqlFile);
|
|
48417
|
+
const finalizeBackup = async () => {
|
|
48418
|
+
await verifyGzipBackup(partialBackupFile);
|
|
48419
|
+
renameSync(partialBackupFile, backupFile);
|
|
48420
|
+
verifiedBackups.set(backupFile, backupIdentity(backupFile));
|
|
48421
|
+
const sizeBytes = statSync(backupFile).size;
|
|
48422
|
+
await opts.verifyBeforePrune?.(backupFile);
|
|
48423
|
+
if (opts.pinned) return { backupFile, sizeBytes, prunedCount: freedForSpace };
|
|
48424
|
+
const rotated = retention.maxFiles && retention.maxFiles > 0 ? await pruneToMaxFiles(opts.backupDir, filenamePrefix, retention.maxFiles, backupFile) : { removed: pruneOldBackups(opts.backupDir, retention, filenamePrefix), unreadable: [] };
|
|
48425
|
+
const prunedCount = freedForSpace + rotated.removed + prunePinnedBackups(opts.backupDir, opts.pinnedMaxAgeDays);
|
|
48426
|
+
rmSync(join2(opts.backupDir, DATABASE_BACKUP_SKIPPED_MARKER), { force: true });
|
|
48427
|
+
return {
|
|
48428
|
+
backupFile,
|
|
48429
|
+
sizeBytes,
|
|
48430
|
+
prunedCount,
|
|
48431
|
+
...rotated.unreadable.length > 0 ? { unreadableBackups: rotated.unreadable } : {}
|
|
48432
|
+
};
|
|
48433
|
+
};
|
|
48212
48434
|
try {
|
|
48213
48435
|
if (backupEngine === "pg_dump" || backupEngine === "auto" && canUsePgDump) {
|
|
48214
48436
|
await sql72`SELECT 1`;
|
|
@@ -48217,14 +48439,14 @@ async function runDatabaseBackup(opts) {
|
|
|
48217
48439
|
await closeSql();
|
|
48218
48440
|
await runPgDumpBackup({
|
|
48219
48441
|
connectionString: opts.connectionString,
|
|
48220
|
-
backupFile,
|
|
48442
|
+
backupFile: partialBackupFile,
|
|
48221
48443
|
connectTimeout
|
|
48222
48444
|
});
|
|
48223
48445
|
dumped = true;
|
|
48224
48446
|
} catch (error) {
|
|
48225
|
-
if (existsSync(
|
|
48447
|
+
if (existsSync(partialBackupFile)) {
|
|
48226
48448
|
try {
|
|
48227
|
-
unlinkSync(
|
|
48449
|
+
unlinkSync(partialBackupFile);
|
|
48228
48450
|
} catch {
|
|
48229
48451
|
}
|
|
48230
48452
|
}
|
|
@@ -48237,10 +48459,7 @@ async function runDatabaseBackup(opts) {
|
|
|
48237
48459
|
}
|
|
48238
48460
|
if (dumped) {
|
|
48239
48461
|
await writer.abort();
|
|
48240
|
-
|
|
48241
|
-
await opts.verifyBeforePrune?.(backupFile);
|
|
48242
|
-
const prunedCount2 = pruneOldBackups(opts.backupDir, retention, filenamePrefix);
|
|
48243
|
-
return { backupFile, sizeBytes: sizeBytes2, prunedCount: prunedCount2 };
|
|
48462
|
+
return await finalizeBackup();
|
|
48244
48463
|
}
|
|
48245
48464
|
}
|
|
48246
48465
|
await sql72`SELECT 1`;
|
|
@@ -48620,33 +48839,24 @@ async function runDatabaseBackup(opts) {
|
|
|
48620
48839
|
emit2("");
|
|
48621
48840
|
await writer.close();
|
|
48622
48841
|
const sqlReadStream = createReadStream(sqlFile);
|
|
48623
|
-
const gzWriteStream = createWriteStream(
|
|
48842
|
+
const gzWriteStream = createWriteStream(partialBackupFile);
|
|
48624
48843
|
await pipeline(sqlReadStream, createGzip(), gzWriteStream);
|
|
48625
48844
|
unlinkSync(sqlFile);
|
|
48626
|
-
|
|
48627
|
-
await opts.verifyBeforePrune?.(backupFile);
|
|
48628
|
-
const prunedCount = pruneOldBackups(opts.backupDir, retention, filenamePrefix);
|
|
48629
|
-
return {
|
|
48630
|
-
backupFile,
|
|
48631
|
-
sizeBytes,
|
|
48632
|
-
prunedCount
|
|
48633
|
-
};
|
|
48845
|
+
return await finalizeBackup();
|
|
48634
48846
|
} catch (error) {
|
|
48635
48847
|
await writer.abort();
|
|
48636
|
-
|
|
48637
|
-
|
|
48638
|
-
|
|
48639
|
-
|
|
48640
|
-
|
|
48641
|
-
|
|
48642
|
-
if (existsSync(sqlFile)) {
|
|
48643
|
-
try {
|
|
48644
|
-
unlinkSync(sqlFile);
|
|
48645
|
-
} catch {
|
|
48848
|
+
for (const file2 of [backupFile, partialBackupFile, sqlFile]) {
|
|
48849
|
+
if (existsSync(file2)) {
|
|
48850
|
+
try {
|
|
48851
|
+
unlinkSync(file2);
|
|
48852
|
+
} catch {
|
|
48853
|
+
}
|
|
48646
48854
|
}
|
|
48647
48855
|
}
|
|
48648
48856
|
throw error;
|
|
48649
48857
|
} finally {
|
|
48858
|
+
activePartialBackupFiles.delete(sqlFile);
|
|
48859
|
+
activePartialBackupFiles.delete(partialBackupFile);
|
|
48650
48860
|
await closeSql();
|
|
48651
48861
|
}
|
|
48652
48862
|
}
|
|
@@ -48683,9 +48893,10 @@ async function runDatabaseRestore(opts) {
|
|
|
48683
48893
|
function formatDatabaseBackupResult(result) {
|
|
48684
48894
|
const size = formatBackupSize(result.sizeBytes);
|
|
48685
48895
|
const pruned = result.prunedCount > 0 ? `; pruned ${result.prunedCount} old backup(s)` : "";
|
|
48686
|
-
|
|
48896
|
+
const unreadable = result.unreadableBackups?.length ? `; kept ${result.unreadableBackups.length} unreadable backup(s)` : "";
|
|
48897
|
+
return `${result.backupFile} (${size}${pruned}${unreadable})`;
|
|
48687
48898
|
}
|
|
48688
|
-
var DEFAULT_BACKUP_WRITE_BUFFER_BYTES, BACKUP_DATA_CURSOR_ROWS, BACKUP_CLI_STDERR_BYTES, BACKUP_BREAKPOINT_DETECT_BYTES, STATEMENT_BREAKPOINT, DATABASE_BACKUP_FILENAME_PREFIX, RETIRED_BACKUP_FILENAME_PREFIX;
|
|
48899
|
+
var DEFAULT_BACKUP_WRITE_BUFFER_BYTES, BACKUP_DATA_CURSOR_ROWS, BACKUP_CLI_STDERR_BYTES, BACKUP_BREAKPOINT_DETECT_BYTES, STATEMENT_BREAKPOINT, DATABASE_BACKUP_FILENAME_PREFIX, PINNED_BACKUP_DIRNAME, DATABASE_BACKUP_SKIPPED_MARKER, DEFAULT_BACKUP_HEADROOM_FACTOR, DAY_MS, DatabaseBackupSkippedError, verifiedBackups, RETIRED_BACKUP_FILENAME_PREFIX, PARTIAL_BACKUP_PATTERN, ABANDONED_PARTIAL_BACKUP_AGE_MS, activePartialBackupFiles;
|
|
48689
48900
|
var init_backup_lib = __esm({
|
|
48690
48901
|
"../packages/db/src/backup-lib.ts"() {
|
|
48691
48902
|
"use strict";
|
|
@@ -48695,7 +48906,30 @@ var init_backup_lib = __esm({
|
|
|
48695
48906
|
BACKUP_BREAKPOINT_DETECT_BYTES = 64 * 1024;
|
|
48696
48907
|
STATEMENT_BREAKPOINT = "-- paperclip statement breakpoint 69f6f3f1-42fd-46a6-bf17-d1d85f8f3900";
|
|
48697
48908
|
DATABASE_BACKUP_FILENAME_PREFIX = "wardmote";
|
|
48909
|
+
PINNED_BACKUP_DIRNAME = "pinned";
|
|
48910
|
+
DATABASE_BACKUP_SKIPPED_MARKER = "backup-skipped.json";
|
|
48911
|
+
DEFAULT_BACKUP_HEADROOM_FACTOR = 1.5;
|
|
48912
|
+
DAY_MS = 24 * 60 * 60 * 1e3;
|
|
48913
|
+
DatabaseBackupSkippedError = class extends Error {
|
|
48914
|
+
constructor(backupDir, freeBytes, requiredBytes) {
|
|
48915
|
+
super(
|
|
48916
|
+
`Database backup skipped: ${formatBackupSize(freeBytes)} free on the backup volume, ${formatBackupSize(requiredBytes)} required (last backup x${DEFAULT_BACKUP_HEADROOM_FACTOR} plus reserve).`
|
|
48917
|
+
);
|
|
48918
|
+
this.backupDir = backupDir;
|
|
48919
|
+
this.freeBytes = freeBytes;
|
|
48920
|
+
this.requiredBytes = requiredBytes;
|
|
48921
|
+
this.name = "DatabaseBackupSkippedError";
|
|
48922
|
+
}
|
|
48923
|
+
backupDir;
|
|
48924
|
+
freeBytes;
|
|
48925
|
+
requiredBytes;
|
|
48926
|
+
code = "database_backup_skipped_low_disk";
|
|
48927
|
+
};
|
|
48928
|
+
verifiedBackups = /* @__PURE__ */ new Map();
|
|
48698
48929
|
RETIRED_BACKUP_FILENAME_PREFIX = "paperclip";
|
|
48930
|
+
PARTIAL_BACKUP_PATTERN = /\.(\d+)\.partial$/;
|
|
48931
|
+
ABANDONED_PARTIAL_BACKUP_AGE_MS = 24 * 60 * 60 * 1e3;
|
|
48932
|
+
activePartialBackupFiles = /* @__PURE__ */ new Set();
|
|
48699
48933
|
}
|
|
48700
48934
|
});
|
|
48701
48935
|
|
|
@@ -48746,10 +48980,13 @@ var src_exports = {};
|
|
|
48746
48980
|
__export(src_exports, {
|
|
48747
48981
|
ADAPTER_AUTH_SESSION_ACTIVE_STATES: () => ADAPTER_AUTH_SESSION_ACTIVE_STATES,
|
|
48748
48982
|
DATABASE_BACKUP_FILENAME_PREFIX: () => DATABASE_BACKUP_FILENAME_PREFIX,
|
|
48983
|
+
DATABASE_BACKUP_SKIPPED_MARKER: () => DATABASE_BACKUP_SKIPPED_MARKER,
|
|
48984
|
+
DatabaseBackupSkippedError: () => DatabaseBackupSkippedError,
|
|
48749
48985
|
EMBEDDED_POSTGRES_DATABASE: () => EMBEDDED_POSTGRES_DATABASE,
|
|
48750
48986
|
EMBEDDED_POSTGRES_PASSWORD: () => EMBEDDED_POSTGRES_PASSWORD,
|
|
48751
48987
|
EMBEDDED_POSTGRES_TEST_TIMEOUT_MS: () => EMBEDDED_POSTGRES_TEST_TIMEOUT_MS,
|
|
48752
48988
|
EMBEDDED_POSTGRES_USER: () => EMBEDDED_POSTGRES_USER,
|
|
48989
|
+
PINNED_BACKUP_DIRNAME: () => PINNED_BACKUP_DIRNAME,
|
|
48753
48990
|
accountingRuntimeBaselines: () => accountingRuntimeBaselines,
|
|
48754
48991
|
activityLog: () => activityLog,
|
|
48755
48992
|
adapterAuthSessions: () => adapterAuthSessions,
|
|
@@ -48977,6 +49214,7 @@ __export(src_exports, {
|
|
|
48977
49214
|
modelCooldowns: () => modelCooldowns,
|
|
48978
49215
|
nativeRunFinalizations: () => nativeRunFinalizations,
|
|
48979
49216
|
nativeRunResults: () => nativeRunResults,
|
|
49217
|
+
pinnedBackupDir: () => pinnedBackupDir,
|
|
48980
49218
|
pipelineAutomationExecutions: () => pipelineAutomationExecutions,
|
|
48981
49219
|
pipelineCaseBlockers: () => pipelineCaseBlockers,
|
|
48982
49220
|
pipelineCaseDocuments: () => pipelineCaseDocuments,
|
|
@@ -49010,6 +49248,7 @@ __export(src_exports, {
|
|
|
49010
49248
|
projects: () => projects,
|
|
49011
49249
|
providerBillingSnapshots: () => providerBillingSnapshots,
|
|
49012
49250
|
providerTraceRecords: () => providerTraceRecords,
|
|
49251
|
+
readBackupDiskSpace: () => readBackupDiskSpace,
|
|
49013
49252
|
reconcileDatabaseWork: () => reconcileDatabaseWork,
|
|
49014
49253
|
reconcilePendingMigrationHistory: () => reconcilePendingMigrationHistory,
|
|
49015
49254
|
remoteAgentProfiles: () => remoteAgentProfiles,
|
|
@@ -49067,6 +49306,7 @@ __export(src_exports, {
|
|
|
49067
49306
|
userSecretDeclarations: () => userSecretDeclarations,
|
|
49068
49307
|
userSecretDefinitions: () => userSecretDefinitions,
|
|
49069
49308
|
userSidebarPreferences: () => userSidebarPreferences,
|
|
49309
|
+
verifyGzipBackup: () => verifyGzipBackup,
|
|
49070
49310
|
webPushSubscriptions: () => webPushSubscriptions,
|
|
49071
49311
|
withDedicatedDbConnection: () => withDedicatedDbConnection,
|
|
49072
49312
|
workAssessments: () => workAssessments,
|
|
@@ -49651,7 +49891,7 @@ var init_client2 = __esm({
|
|
|
49651
49891
|
|
|
49652
49892
|
// ../packages/shared/src/telemetry/state.ts
|
|
49653
49893
|
import { randomUUID as randomUUID4, randomBytes as randomBytes5 } from "node:crypto";
|
|
49654
|
-
import { existsSync as existsSync2, mkdirSync as mkdirSync2, readFileSync, writeFileSync } from "node:fs";
|
|
49894
|
+
import { existsSync as existsSync2, mkdirSync as mkdirSync2, readFileSync, writeFileSync as writeFileSync2 } from "node:fs";
|
|
49655
49895
|
import path15 from "node:path";
|
|
49656
49896
|
function loadOrCreateState(stateDir, version) {
|
|
49657
49897
|
const filePath = path15.join(stateDir, "state.json");
|
|
@@ -49672,7 +49912,7 @@ function loadOrCreateState(stateDir, version) {
|
|
|
49672
49912
|
firstSeenVersion: version
|
|
49673
49913
|
};
|
|
49674
49914
|
mkdirSync2(stateDir, { recursive: true });
|
|
49675
|
-
|
|
49915
|
+
writeFileSync2(filePath, JSON.stringify(state, null, 2) + "\n", "utf-8");
|
|
49676
49916
|
return state;
|
|
49677
49917
|
}
|
|
49678
49918
|
var init_state = __esm({
|
|
@@ -60932,6 +61172,13 @@ function normalizeRetentionDays(value, fallback) {
|
|
|
60932
61172
|
}
|
|
60933
61173
|
return candidate;
|
|
60934
61174
|
}
|
|
61175
|
+
function normalizeMaxFiles(value, fallback) {
|
|
61176
|
+
const candidate = value ?? fallback;
|
|
61177
|
+
if (!Number.isInteger(candidate) || candidate < 0 || candidate > 48) {
|
|
61178
|
+
throw new Error(`Invalid max files '${String(candidate)}'. Use an integer from 0 to 48.`);
|
|
61179
|
+
}
|
|
61180
|
+
return candidate;
|
|
61181
|
+
}
|
|
60935
61182
|
function resolveBackupDir(raw) {
|
|
60936
61183
|
return path29.resolve(expandHomePrefix(raw.trim()));
|
|
60937
61184
|
}
|
|
@@ -60948,19 +61195,28 @@ async function dbBackupCommand(opts) {
|
|
|
60948
61195
|
opts.retentionDays,
|
|
60949
61196
|
config?.database.backup.retentionDays ?? 30
|
|
60950
61197
|
);
|
|
61198
|
+
const maxFiles = normalizeMaxFiles(opts.maxFiles, config?.database.backup.maxFiles ?? 2);
|
|
60951
61199
|
const filenamePrefix = opts.filenamePrefix?.trim() || DATABASE_BACKUP_FILENAME_PREFIX;
|
|
61200
|
+
const pinned = opts.pin === true;
|
|
61201
|
+
const retentionLabel = pinned ? "pinned (outside rotation)" : maxFiles > 0 ? `newest ${maxFiles} verified backup(s)` : `${retentionDays} day(s), tiered`;
|
|
60952
61202
|
p10.log.message(pc7.dim(`Config: ${configPath}`));
|
|
60953
61203
|
p10.log.message(pc7.dim(`Connection source: ${connection.source}`));
|
|
60954
|
-
p10.log.message(pc7.dim(`Backup dir: ${backupDir}`));
|
|
60955
|
-
p10.log.message(pc7.dim(`Retention: ${
|
|
61204
|
+
p10.log.message(pc7.dim(`Backup dir: ${pinned ? pinnedBackupDir(backupDir) : backupDir}`));
|
|
61205
|
+
p10.log.message(pc7.dim(`Retention: ${retentionLabel}`));
|
|
60956
61206
|
const spinner4 = p10.spinner();
|
|
60957
61207
|
spinner4.start("Creating database backup...");
|
|
60958
61208
|
try {
|
|
60959
61209
|
const result = await runDatabaseBackup({
|
|
60960
61210
|
connectionString: connection.value,
|
|
60961
61211
|
backupDir,
|
|
60962
|
-
retention: { dailyDays: retentionDays, weeklyWeeks: 4, monthlyMonths: 1 },
|
|
60963
|
-
filenamePrefix
|
|
61212
|
+
retention: { dailyDays: retentionDays, weeklyWeeks: 4, monthlyMonths: 1, maxFiles: maxFiles > 0 ? maxFiles : null },
|
|
61213
|
+
filenamePrefix,
|
|
61214
|
+
pinned,
|
|
61215
|
+
pinnedMaxAgeDays: config?.database.backup.pinnedMaxAgeDays ?? 30,
|
|
61216
|
+
spaceGuard: {
|
|
61217
|
+
reservePercent: config?.database.backup.reservePercent ?? 5,
|
|
61218
|
+
reserveBytes: Math.round((config?.database.backup.reserveGb ?? 10) * 1024 ** 3)
|
|
61219
|
+
}
|
|
60964
61220
|
});
|
|
60965
61221
|
spinner4.stop(`Backup saved: ${formatDatabaseBackupResult(result)}`);
|
|
60966
61222
|
if (opts.json) {
|
|
@@ -60972,6 +61228,8 @@ async function dbBackupCommand(opts) {
|
|
|
60972
61228
|
prunedCount: result.prunedCount,
|
|
60973
61229
|
backupDir,
|
|
60974
61230
|
retentionDays,
|
|
61231
|
+
maxFiles,
|
|
61232
|
+
pinned,
|
|
60975
61233
|
connectionSource: connection.source
|
|
60976
61234
|
},
|
|
60977
61235
|
null,
|
|
@@ -60996,7 +61254,7 @@ var init_db_backup = __esm({
|
|
|
60996
61254
|
});
|
|
60997
61255
|
|
|
60998
61256
|
// src/services/instance-drain.ts
|
|
60999
|
-
function
|
|
61257
|
+
function isProcessAlive2(pid) {
|
|
61000
61258
|
try {
|
|
61001
61259
|
process.kill(pid, 0);
|
|
61002
61260
|
return true;
|
|
@@ -61011,7 +61269,7 @@ function resolveConfiguredInstanceApiBase(instanceId) {
|
|
|
61011
61269
|
}
|
|
61012
61270
|
function resolveInstanceEndpoint(instanceId, deps = {}) {
|
|
61013
61271
|
const runtime = (deps.readRuntime ?? readRuntimeInfo)(instanceId);
|
|
61014
|
-
const alive = deps.isProcessAlive ??
|
|
61272
|
+
const alive = deps.isProcessAlive ?? isProcessAlive2;
|
|
61015
61273
|
if (runtime && alive(runtime.pid)) {
|
|
61016
61274
|
return { apiBase: buildLocalAppUrl(runtime.host, runtime.port), source: "runtime-info", pid: runtime.pid };
|
|
61017
61275
|
}
|
|
@@ -61224,7 +61482,7 @@ async function withHotRestartLock(instanceId, callback, options = {}) {
|
|
|
61224
61482
|
const token = `${process.pid}:${Date.now()}:${Math.random().toString(16).slice(2)}`;
|
|
61225
61483
|
const deadline = Date.now() + (options.timeoutMs ?? 12e4);
|
|
61226
61484
|
const pollMs = options.pollMs ?? 100;
|
|
61227
|
-
const
|
|
61485
|
+
const isProcessAlive4 = options.isProcessAlive ?? ((pid) => {
|
|
61228
61486
|
try {
|
|
61229
61487
|
process.kill(pid, 0);
|
|
61230
61488
|
return true;
|
|
@@ -61243,7 +61501,7 @@ async function withHotRestartLock(instanceId, callback, options = {}) {
|
|
|
61243
61501
|
try {
|
|
61244
61502
|
const existingToken = (await fs28.readFile(lockPath, "utf8")).trim();
|
|
61245
61503
|
const ownerPid = Number.parseInt(existingToken.split(":", 1)[0] ?? "", 10);
|
|
61246
|
-
if (Number.isInteger(ownerPid) && ownerPid > 0 && !
|
|
61504
|
+
if (Number.isInteger(ownerPid) && ownerPid > 0 && !isProcessAlive4(ownerPid)) {
|
|
61247
61505
|
if ((await fs28.readFile(lockPath, "utf8")).trim() === existingToken) {
|
|
61248
61506
|
await fs28.rm(lockPath, { force: true });
|
|
61249
61507
|
continue;
|
|
@@ -61326,7 +61584,7 @@ async function startManagedService(input2 = {}) {
|
|
|
61326
61584
|
return { status: await detection.manager.status(), health };
|
|
61327
61585
|
}
|
|
61328
61586
|
async function resolveInstanceLiveness(input2) {
|
|
61329
|
-
const alive = input2.isProcessAlive ??
|
|
61587
|
+
const alive = input2.isProcessAlive ?? isProcessAlive2;
|
|
61330
61588
|
const runtime = (input2.readRuntime ?? readRuntimeInfo)(input2.instanceId);
|
|
61331
61589
|
if (runtime && alive(runtime.pid)) {
|
|
61332
61590
|
return { running: true, detail: `server pid ${runtime.pid} is listening on ${runtime.host}:${runtime.port}` };
|
|
@@ -61672,7 +61930,7 @@ async function updateCommand(options, overrides = {}) {
|
|
|
61672
61930
|
const confirmed = await (overrides.confirm ?? defaultConfirm)(`Update from ${manifest.repo}@${manifest.ref} and execute build scripts from commit ${targetSha.slice(0, 12)}?`);
|
|
61673
61931
|
if (!confirmed) throw new Error("Git update cancelled. Re-run with --yes to confirm executing build scripts from the updated commit.");
|
|
61674
61932
|
}
|
|
61675
|
-
if (options.backup !== false) await runPreUpdateBackup(options, overrides.backup ?? (() => dbBackupCommand({})), overrides.hasInstanceData);
|
|
61933
|
+
if (options.backup !== false) await runPreUpdateBackup(options, overrides.backup ?? (() => dbBackupCommand({ pin: true })), overrides.hasInstanceData);
|
|
61676
61934
|
const installed2 = await withInstallStoreLock(async () => {
|
|
61677
61935
|
assertManagedShimWritable(paths);
|
|
61678
61936
|
const payload = await installGitPayload(manifest.repo, targetSha, runCommand2, paths);
|
|
@@ -61755,7 +62013,7 @@ async function updateCommand(options, overrides = {}) {
|
|
|
61755
62013
|
return;
|
|
61756
62014
|
}
|
|
61757
62015
|
await assertReleaseSetPublished(targetVersion, runCommand2);
|
|
61758
|
-
if (options.backup !== false) await runPreUpdateBackup(options, overrides.backup ?? (() => dbBackupCommand({})), overrides.hasInstanceData);
|
|
62016
|
+
if (options.backup !== false) await runPreUpdateBackup(options, overrides.backup ?? (() => dbBackupCommand({ pin: true })), overrides.hasInstanceData);
|
|
61759
62017
|
const installed = await withInstallStoreLock(async () => {
|
|
61760
62018
|
assertManagedShimWritable(paths);
|
|
61761
62019
|
const payload = await installNpmPayload(targetVersion, runCommand2, paths);
|
|
@@ -62219,7 +62477,7 @@ function writeRegistryLockOwner(lockPath, owner) {
|
|
|
62219
62477
|
fs31.renameSync(temporaryPath, ownerPath);
|
|
62220
62478
|
}
|
|
62221
62479
|
}
|
|
62222
|
-
function
|
|
62480
|
+
function isProcessAlive3(pid) {
|
|
62223
62481
|
try {
|
|
62224
62482
|
process.kill(pid, 0);
|
|
62225
62483
|
return true;
|
|
@@ -62283,7 +62541,7 @@ function removeStaleRegistryLock(lockPath) {
|
|
|
62283
62541
|
const ageMs = Date.now() - fs31.statSync(lockPath).mtimeMs;
|
|
62284
62542
|
if (ageMs <= WORKTREE_PORT_REGISTRY_LOCK_STALE_MS) return false;
|
|
62285
62543
|
const owner = readRegistryLockOwner(lockPath);
|
|
62286
|
-
if (owner &&
|
|
62544
|
+
if (owner && isProcessAlive3(owner.pid)) {
|
|
62287
62545
|
if (probeRegistryLockOwner(owner)) return false;
|
|
62288
62546
|
const currentIdentity = readProcessIdentity(owner.pid);
|
|
62289
62547
|
if (owner.processIdentity === currentIdentity) return false;
|
|
@@ -63244,10 +63502,10 @@ import {
|
|
|
63244
63502
|
readFileSync as readFileSync4,
|
|
63245
63503
|
readlinkSync,
|
|
63246
63504
|
renameSync as renameSync2,
|
|
63247
|
-
rmSync,
|
|
63505
|
+
rmSync as rmSync2,
|
|
63248
63506
|
statSync as statSync3,
|
|
63249
63507
|
symlinkSync,
|
|
63250
|
-
writeFileSync as
|
|
63508
|
+
writeFileSync as writeFileSync3
|
|
63251
63509
|
} from "node:fs";
|
|
63252
63510
|
import os10 from "node:os";
|
|
63253
63511
|
import path37 from "node:path";
|
|
@@ -63637,7 +63895,7 @@ function copyDirectoryContents(sourceDir, targetDir) {
|
|
|
63637
63895
|
continue;
|
|
63638
63896
|
}
|
|
63639
63897
|
if (entry.isSymbolicLink()) {
|
|
63640
|
-
|
|
63898
|
+
rmSync2(targetPath, { recursive: true, force: true });
|
|
63641
63899
|
symlinkSync(readlinkSync(sourcePath), targetPath);
|
|
63642
63900
|
copied = true;
|
|
63643
63901
|
continue;
|
|
@@ -63897,7 +64155,7 @@ function copySeededSecretsKey(input2) {
|
|
|
63897
64155
|
const allowProcessEnvFallback = isCurrentSourceConfigPath(input2.sourceConfigPath);
|
|
63898
64156
|
const sourceInlineMasterKey = nonEmpty2(input2.sourceEnvEntries.WARDMOTE_SECRETS_MASTER_KEY) ?? (allowProcessEnvFallback ? nonEmpty2(process.env.WARDMOTE_SECRETS_MASTER_KEY) : null);
|
|
63899
64157
|
if (sourceInlineMasterKey) {
|
|
63900
|
-
|
|
64158
|
+
writeFileSync3(input2.targetKeyFilePath, sourceInlineMasterKey, {
|
|
63901
64159
|
encoding: "utf8",
|
|
63902
64160
|
mode: 384
|
|
63903
64161
|
});
|
|
@@ -63973,7 +64231,7 @@ async function ensureEmbeddedPostgres(dataDir, preferredPort, options = {}) {
|
|
|
63973
64231
|
}
|
|
63974
64232
|
}
|
|
63975
64233
|
if (existsSync4(postmasterPidFile)) {
|
|
63976
|
-
|
|
64234
|
+
rmSync2(postmasterPidFile, { force: true });
|
|
63977
64235
|
}
|
|
63978
64236
|
try {
|
|
63979
64237
|
await instance.start();
|
|
@@ -64466,7 +64724,7 @@ function resolveSeedInstanceId(configPath) {
|
|
|
64466
64724
|
function writeWorktreeSeedManifest(filePath, manifest) {
|
|
64467
64725
|
mkdirSync3(path37.dirname(filePath), { recursive: true });
|
|
64468
64726
|
const temporaryPath = `${filePath}.${process.pid}.${randomUUID6()}.tmp`;
|
|
64469
|
-
|
|
64727
|
+
writeFileSync3(temporaryPath, `${JSON.stringify(manifest, null, 2)}
|
|
64470
64728
|
`, { mode: 384 });
|
|
64471
64729
|
renameSync2(temporaryPath, filePath);
|
|
64472
64730
|
}
|
|
@@ -64514,8 +64772,8 @@ function markWorktreeSeedPending(input2) {
|
|
|
64514
64772
|
...input2.diagnosticMessage ? { message: input2.diagnosticMessage.slice(0, WORKTREE_SEED_DIAGNOSTIC_MESSAGE_LIMIT) } : {}
|
|
64515
64773
|
}]
|
|
64516
64774
|
});
|
|
64517
|
-
|
|
64518
|
-
|
|
64775
|
+
rmSync2(markers.complete, { force: true });
|
|
64776
|
+
rmSync2(markers.pending, { force: true });
|
|
64519
64777
|
}
|
|
64520
64778
|
function updateWorktreeSeedManifest(input2) {
|
|
64521
64779
|
const markers = resolveWorktreeSeedMarkerPaths(input2.configPath);
|
|
@@ -64899,14 +65157,14 @@ async function runWorktreeInit(opts) {
|
|
|
64899
65157
|
);
|
|
64900
65158
|
}
|
|
64901
65159
|
if (opts.force) {
|
|
64902
|
-
|
|
64903
|
-
|
|
65160
|
+
rmSync2(paths.configPath, { force: true });
|
|
65161
|
+
rmSync2(paths.envPath, { force: true });
|
|
64904
65162
|
const seedMarkers = resolveWorktreeSeedMarkerPaths(paths.configPath);
|
|
64905
|
-
|
|
64906
|
-
|
|
64907
|
-
|
|
64908
|
-
|
|
64909
|
-
|
|
65163
|
+
rmSync2(seedMarkers.manifest, { force: true });
|
|
65164
|
+
rmSync2(seedMarkers.pending, { force: true });
|
|
65165
|
+
rmSync2(seedMarkers.complete, { force: true });
|
|
65166
|
+
rmSync2(seedMarkers.empty, { force: true });
|
|
65167
|
+
rmSync2(paths.instanceRoot, { recursive: true, force: true });
|
|
64910
65168
|
}
|
|
64911
65169
|
const { serverPort, databasePort, targetConfig } = await withWorktreePortRegistryLock(
|
|
64912
65170
|
paths.homeDir,
|
|
@@ -64934,7 +65192,7 @@ async function runWorktreeInit(opts) {
|
|
|
64934
65192
|
try {
|
|
64935
65193
|
if (opts.empty) {
|
|
64936
65194
|
mkdirSync3(paths.repoConfigDir, { recursive: true });
|
|
64937
|
-
|
|
65195
|
+
writeFileSync3(resolveWorktreeSeedMarkerPaths(paths.configPath).empty, "Explicitly empty instance; automatic database copying is disabled.\n", { mode: 384 });
|
|
64938
65196
|
}
|
|
64939
65197
|
writeConfig(selectedConfig, paths.configPath);
|
|
64940
65198
|
writeWorktreePortRegistry(paths.homeDir, [
|
|
@@ -64942,8 +65200,8 @@ async function runWorktreeInit(opts) {
|
|
|
64942
65200
|
paths.configPath
|
|
64943
65201
|
]);
|
|
64944
65202
|
} catch (error) {
|
|
64945
|
-
|
|
64946
|
-
if (opts.empty)
|
|
65203
|
+
rmSync2(paths.configPath, { force: true });
|
|
65204
|
+
if (opts.empty) rmSync2(resolveWorktreeSeedMarkerPaths(paths.configPath).empty, { force: true });
|
|
64947
65205
|
throw error;
|
|
64948
65206
|
}
|
|
64949
65207
|
return {
|
|
@@ -65345,7 +65603,7 @@ async function worktreeCleanupCommand(nameArg, opts) {
|
|
|
65345
65603
|
if (existsSync4(targetPath)) {
|
|
65346
65604
|
const spinner4 = p14.spinner();
|
|
65347
65605
|
spinner4.start(`Removing worktree directory ${targetPath}...`);
|
|
65348
|
-
|
|
65606
|
+
rmSync2(targetPath, { recursive: true, force: true });
|
|
65349
65607
|
spinner4.stop(`Removed worktree directory ${targetPath}.`);
|
|
65350
65608
|
}
|
|
65351
65609
|
if (localBranchExists(sourceCwd, name)) {
|
|
@@ -65366,7 +65624,7 @@ async function worktreeCleanupCommand(nameArg, opts) {
|
|
|
65366
65624
|
if (existsSync4(instanceRoot)) {
|
|
65367
65625
|
const spinner4 = p14.spinner();
|
|
65368
65626
|
spinner4.start(`Removing instance data at ${instanceRoot}...`);
|
|
65369
|
-
|
|
65627
|
+
rmSync2(instanceRoot, { recursive: true, force: true });
|
|
65370
65628
|
spinner4.stop(`Removed instance data at ${instanceRoot}.`);
|
|
65371
65629
|
}
|
|
65372
65630
|
p14.outro(pc10.green("Cleanup complete."));
|
|
@@ -66382,7 +66640,7 @@ async function runWorktreeReseed(opts) {
|
|
|
66382
66640
|
expectedCompanyId: nonEmpty2(process.env.WARDMOTE_SEED_EXPECTED_COMPANY_ID) ?? void 0,
|
|
66383
66641
|
seedDatabase: seedWorktreeDatabase
|
|
66384
66642
|
});
|
|
66385
|
-
|
|
66643
|
+
rmSync2(markers.empty, { force: true });
|
|
66386
66644
|
spinner4.stop(`Reseeded ${targetEndpoint.label} (${seedMode}).`);
|
|
66387
66645
|
p14.log.message(pc10.dim(`Source: ${source.configPath}`));
|
|
66388
66646
|
p14.log.message(pc10.dim(`Target: ${targetEndpoint.configPath}`));
|
|
@@ -79356,9 +79614,9 @@ function registerAccessCommands(program2) {
|
|
|
79356
79614
|
}
|
|
79357
79615
|
})
|
|
79358
79616
|
);
|
|
79359
|
-
const
|
|
79617
|
+
const join3 = program2.command("join").description("Join request operations");
|
|
79360
79618
|
addCommonClientOptions(
|
|
79361
|
-
|
|
79619
|
+
join3.command("list").description("List join requests").option("-C, --company-id <id>", "Company ID").option("--status <status>", "Filter by status (pending_approval, approved, rejected; pending alias accepted)").option("--request-type <type>", "Filter by request type").action(async (opts) => {
|
|
79362
79620
|
try {
|
|
79363
79621
|
const ctx = resolveCommandContext(opts, { requireCompany: true });
|
|
79364
79622
|
const params = new URLSearchParams();
|
|
@@ -79373,10 +79631,10 @@ function registerAccessCommands(program2) {
|
|
|
79373
79631
|
}),
|
|
79374
79632
|
{ includeCompany: false }
|
|
79375
79633
|
);
|
|
79376
|
-
addJoinAction(
|
|
79377
|
-
addJoinAction(
|
|
79634
|
+
addJoinAction(join3, "approve");
|
|
79635
|
+
addJoinAction(join3, "reject");
|
|
79378
79636
|
addCommonClientOptions(
|
|
79379
|
-
|
|
79637
|
+
join3.command("claim-key").description("Claim an agent API key for an approved join request").argument("<requestId>", "Join request ID").requiredOption("--claim-secret <secret>", "Claim secret").action(async (requestId, opts) => {
|
|
79380
79638
|
try {
|
|
79381
79639
|
const ctx = resolveCommandContext(opts);
|
|
79382
79640
|
printOutput(await ctx.api.post(apiPath`/api/join-requests/${requestId}/claim-api-key`, { claimSecret: opts.claimSecret }), { json: ctx.json });
|
|
@@ -80564,6 +80822,20 @@ function readIfExists(filePath) {
|
|
|
80564
80822
|
throw error;
|
|
80565
80823
|
}
|
|
80566
80824
|
}
|
|
80825
|
+
function rewriteLegacyStoreMarker(markerPath) {
|
|
80826
|
+
let contents;
|
|
80827
|
+
try {
|
|
80828
|
+
const stat3 = fs37.lstatSync(markerPath);
|
|
80829
|
+
const getuid = process.getuid;
|
|
80830
|
+
if (!stat3.isFile() || stat3.nlink > 1 || typeof getuid === "function" && stat3.uid !== getuid()) return false;
|
|
80831
|
+
contents = fs37.readFileSync(markerPath, "utf8");
|
|
80832
|
+
} catch {
|
|
80833
|
+
return false;
|
|
80834
|
+
}
|
|
80835
|
+
if (contents !== LEGACY_STORE_MARKER) return false;
|
|
80836
|
+
writeFileAtomic(markerPath, MANAGED_STORE_MARKER, 384);
|
|
80837
|
+
return true;
|
|
80838
|
+
}
|
|
80567
80839
|
function isLegacyShim(file2) {
|
|
80568
80840
|
const contents = readIfExists(file2);
|
|
80569
80841
|
return contents !== null && isManagedShimContents(contents, LEGACY_SHIM_MARKER);
|
|
@@ -80607,9 +80879,13 @@ async function migrateHomeCommand(options = {}, overrides = {}) {
|
|
|
80607
80879
|
const unrecognizedShims = [legacyShim, shim].filter((file2) => !legacyShims.includes(file2) && readIfExists(file2)?.includes(`# ${LEGACY_SHIM_MARKER}
|
|
80608
80880
|
`));
|
|
80609
80881
|
const rcFiles = [".bashrc", ".zshrc"].map((name) => path50.join(deps.userHomeDir, name)).filter((file2) => readIfExists(file2)?.includes(LEGACY_PATH_BLOCK_START));
|
|
80610
|
-
const storeHome =
|
|
80882
|
+
const storeHome = target;
|
|
80611
80883
|
const markerPath = path50.join(home === "move" ? source : storeHome, "cli", ".managed-install");
|
|
80612
|
-
const
|
|
80884
|
+
const storeMarker = readIfExists(markerPath);
|
|
80885
|
+
const legacyStore = storeMarker === LEGACY_STORE_MARKER;
|
|
80886
|
+
if (storeMarker !== null && !legacyStore && storeMarker !== MANAGED_STORE_MARKER) {
|
|
80887
|
+
throw new Error(`Refusing to migrate: unrecognized install store marker ${markerPath}.`);
|
|
80888
|
+
}
|
|
80613
80889
|
const steps = [
|
|
80614
80890
|
home === "move" ? `move ${source} to ${target} and link ${source} -> ${target}` : null,
|
|
80615
80891
|
...definitions.map((definition) => `replace ${path50.basename(definition.from)}${fs37.existsSync(`${definition.from}.d`) ? " and its drop-in directory" : ""} with ${path50.basename(definition.to)}`),
|
|
@@ -80682,6 +80958,9 @@ async function migrateHomeCommand(options = {}, overrides = {}) {
|
|
|
80682
80958
|
if (definitions.length > 0) deps.log("Start the service with `wardmote service start`.");
|
|
80683
80959
|
}
|
|
80684
80960
|
|
|
80961
|
+
// src/index.ts
|
|
80962
|
+
init_install_store();
|
|
80963
|
+
|
|
80685
80964
|
// src/commands/migrate-names.ts
|
|
80686
80965
|
init_src2();
|
|
80687
80966
|
init_db_backup();
|
|
@@ -81058,6 +81337,9 @@ program.hook("preAction", async (_thisCommand, actionCommand) => {
|
|
|
81058
81337
|
assertTestDriveDatabaseIsolation(options.config);
|
|
81059
81338
|
}
|
|
81060
81339
|
initTelemetryFromConfigFile(options.config);
|
|
81340
|
+
if (["install", "update", "uninstall"].includes(actionCommand.name())) {
|
|
81341
|
+
rewriteLegacyStoreMarker(resolveInstallStorePaths().markerPath);
|
|
81342
|
+
}
|
|
81061
81343
|
});
|
|
81062
81344
|
registerTestDriveCommand(program);
|
|
81063
81345
|
registerRuntimeCommands(program);
|
|
@@ -81070,7 +81352,7 @@ program.command("channels").description("Show the release channels and which one
|
|
|
81070
81352
|
await channelsCommand(opts);
|
|
81071
81353
|
});
|
|
81072
81354
|
program.command("configure").description("Update configuration sections").option("-c, --config <path>", "Path to config file").option("-d, --data-dir <path>", DATA_DIR_OPTION_HELP).option("-s, --section <section>", "Section to configure (llm, database, logging, server, storage, secrets)").action(configure);
|
|
81073
|
-
program.command("db:backup").description("Create a one-off database backup using current config").option("-c, --config <path>", "Path to config file").option("-d, --data-dir <path>", DATA_DIR_OPTION_HELP).option("--dir <path>", "Backup output directory (overrides config)").option("--retention-days <days>", "
|
|
81355
|
+
program.command("db:backup").description("Create a one-off database backup using current config").option("-c, --config <path>", "Path to config file").option("-d, --data-dir <path>", DATA_DIR_OPTION_HELP).option("--dir <path>", "Backup output directory (overrides config)").option("--max-files <count>", "Keep this many newest verified backups (0 = tiered retention by --retention-days)", (value) => Number(value)).option("--retention-days <days>", "Daily window for tiered retention when --max-files is 0", (value) => Number(value)).option("--pin", "Write a pinned backup under <dir>/pinned, outside rotation (release and rollback dumps)").option("--filename-prefix <prefix>", "Backup filename prefix (default: wardmote)").option("--json", "Print backup metadata as JSON").action(async (opts) => {
|
|
81074
81356
|
await dbBackupCommand(opts);
|
|
81075
81357
|
});
|
|
81076
81358
|
program.command("allowed-hostname").description("Allow a hostname for authenticated/private mode access").argument("<host>", "Hostname to allow (for example dotta-macbook-pro)").option("-c, --config <path>", "Path to config file").option("-d, --data-dir <path>", DATA_DIR_OPTION_HELP).action(addAllowedHostname);
|