paratix 0.16.0 → 0.16.1
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/{chunk-56SN6NQH.js → chunk-KC7XBI77.js} +64 -38
- package/dist/cli.js +2 -2
- package/dist/{index-N3n0FmKh.d.ts → index-jbXRUlJ6.d.ts} +1 -1
- package/dist/index.d.ts +2 -2
- package/dist/index.js +1 -1
- package/dist/modules/index.d.ts +1 -1
- package/dist/modules/index.js +1 -1
- package/llm-guide.md +9 -0
- package/package.json +1 -1
|
@@ -2519,6 +2519,13 @@ async function validateExistingExtractDestination(conn, parameters) {
|
|
|
2519
2519
|
// src/modules/fileMetadataHelpers.ts
|
|
2520
2520
|
var EXEC_OPTS2 = { ignoreExitCode: true, silent: true };
|
|
2521
2521
|
var DEFAULT_FILE_WRITE_MODE = "0644";
|
|
2522
|
+
var EMPTY_OWNERSHIP = {
|
|
2523
|
+
group: "",
|
|
2524
|
+
groupId: "",
|
|
2525
|
+
mode: "",
|
|
2526
|
+
owner: "",
|
|
2527
|
+
ownerId: ""
|
|
2528
|
+
};
|
|
2522
2529
|
function assertValidChownOwnershipSpec(ownerSpec) {
|
|
2523
2530
|
if (ownerSpec === "") {
|
|
2524
2531
|
throw new Error("chown ownership spec must not be empty");
|
|
@@ -2581,20 +2588,23 @@ function renderGuardedMetadataCommand(kind, remotePath, value) {
|
|
|
2581
2588
|
].join("\n");
|
|
2582
2589
|
}
|
|
2583
2590
|
async function readOwnership(ssh2, remotePath) {
|
|
2584
|
-
const result = await ssh2.exec(`stat -c '%a %U %G' ${shellQuote(remotePath)}`, EXEC_OPTS2);
|
|
2585
|
-
if (result.code !== 0) return {
|
|
2586
|
-
const [mode = "", owner = "", group2 = ""] = result.stdout.trim().split(new RegExp("\\s+", "v"));
|
|
2587
|
-
return { group: group2, mode, owner };
|
|
2591
|
+
const result = await ssh2.exec(`stat -c '%a %U %G %u %g' ${shellQuote(remotePath)}`, EXEC_OPTS2);
|
|
2592
|
+
if (result.code !== 0) return { ...EMPTY_OWNERSHIP };
|
|
2593
|
+
const [mode = "", owner = "", group2 = "", ownerId = "", groupId = ""] = result.stdout.trim().split(new RegExp("\\s+", "v"));
|
|
2594
|
+
return { group: group2, groupId, mode, owner, ownerId };
|
|
2588
2595
|
}
|
|
2589
2596
|
function normalizeMode(mode) {
|
|
2590
2597
|
return mode.startsWith("0") ? mode : `0${mode}`;
|
|
2591
2598
|
}
|
|
2592
|
-
|
|
2593
|
-
|
|
2599
|
+
var NUMERIC_ID_PATTERN = new RegExp("^\\d+$", "v");
|
|
2600
|
+
function isNumericId(value) {
|
|
2601
|
+
return NUMERIC_ID_PATTERN.test(value);
|
|
2594
2602
|
}
|
|
2595
|
-
function
|
|
2596
|
-
if (
|
|
2597
|
-
|
|
2603
|
+
function ownershipComponentMatches(expected, actual, actualId) {
|
|
2604
|
+
if (expected === "") return true;
|
|
2605
|
+
if (actual === expected || actualId === expected) return true;
|
|
2606
|
+
if (!isNumericId(expected) || !isNumericId(actualId)) return false;
|
|
2607
|
+
return Number(expected) === Number(actualId);
|
|
2598
2608
|
}
|
|
2599
2609
|
async function resolveWriteMode(ssh2, remotePath, explicitMode) {
|
|
2600
2610
|
if (explicitMode != null) return explicitMode;
|
|
@@ -2635,10 +2645,10 @@ function ownershipMatches(current, options) {
|
|
|
2635
2645
|
if (options?.mode != null && current.mode !== options.mode.replace(new RegExp("^0+", "v"), "")) return false;
|
|
2636
2646
|
if (options?.owner == null) return true;
|
|
2637
2647
|
const expectsGroup = options.owner.includes(":");
|
|
2638
|
-
const [expectedOwner, expectedGroup = ""] = options.owner.split(":", 2);
|
|
2639
|
-
if (!ownershipComponentMatches(expectedOwner, current.owner)) return false;
|
|
2640
|
-
if (!
|
|
2641
|
-
return
|
|
2648
|
+
const [expectedOwner = "", expectedGroup = ""] = options.owner.split(":", 2);
|
|
2649
|
+
if (!ownershipComponentMatches(expectedOwner, current.owner, current.ownerId)) return false;
|
|
2650
|
+
if (!expectsGroup) return true;
|
|
2651
|
+
return ownershipComponentMatches(expectedGroup, current.group, current.groupId);
|
|
2642
2652
|
}
|
|
2643
2653
|
function createMetadataModule(kind, remotePath, value) {
|
|
2644
2654
|
const name = `file.${kind}: ${remotePath}`;
|
|
@@ -2676,6 +2686,7 @@ var SILENT = { silent: true };
|
|
|
2676
2686
|
var FLAGS_DIR = "/var/lib/paratix/flags";
|
|
2677
2687
|
var ARCHIVE_MARKER_MODE = "0644";
|
|
2678
2688
|
var ARCHIVE_OWNER_MEMBER_CONCURRENCY = 8;
|
|
2689
|
+
var ARCHIVE_STAT_OWNERSHIP_FIELDS = 4;
|
|
2679
2690
|
async function mapWithConcurrencyLimit2(items, limit, mapper) {
|
|
2680
2691
|
if (items.length === 0) return [];
|
|
2681
2692
|
const results = [];
|
|
@@ -3055,11 +3066,9 @@ async function applyExtract(conn, parameters) {
|
|
|
3055
3066
|
}
|
|
3056
3067
|
}
|
|
3057
3068
|
function ownerMatchesStat(stdout, owner) {
|
|
3058
|
-
const [actualUser = "", actualGroup = ""] = stdout.trim().split(new RegExp("\\s+", "v"),
|
|
3069
|
+
const [actualUser = "", actualGroup = "", actualUserId = "", actualGroupId = ""] = stdout.trim().split(new RegExp("\\s+", "v"), ARCHIVE_STAT_OWNERSHIP_FIELDS);
|
|
3059
3070
|
const [expectedUser = "", expectedGroup = ""] = owner.split(":", 2);
|
|
3060
|
-
|
|
3061
|
-
if (expectedGroup !== "" && actualGroup !== expectedGroup) return false;
|
|
3062
|
-
return true;
|
|
3071
|
+
return ownershipComponentMatches(expectedUser, actualUser, actualUserId) && ownershipComponentMatches(expectedGroup, actualGroup, actualGroupId);
|
|
3063
3072
|
}
|
|
3064
3073
|
function isStringArray(value) {
|
|
3065
3074
|
return Array.isArray(value) && value.every((item) => typeof item === "string");
|
|
@@ -3071,7 +3080,7 @@ async function extractedMemberOwnerMatches(conn, parameters) {
|
|
|
3071
3080
|
EXEC_OPTS3
|
|
3072
3081
|
);
|
|
3073
3082
|
if (exists.code !== 0) return false;
|
|
3074
|
-
const stat5 = await conn.exec(`stat -c '%U %G' -- ${shellQuote(path3)}`, EXEC_OPTS3);
|
|
3083
|
+
const stat5 = await conn.exec(`stat -c '%U %G %u %g' -- ${shellQuote(path3)}`, EXEC_OPTS3);
|
|
3075
3084
|
if (stat5.code !== 0) return false;
|
|
3076
3085
|
return ownerMatchesStat(stat5.stdout, owner);
|
|
3077
3086
|
}
|
|
@@ -5354,18 +5363,22 @@ function rejectSensitiveHeadersOverHttp(parameters) {
|
|
|
5354
5363
|
);
|
|
5355
5364
|
}
|
|
5356
5365
|
async function readDownloadOwnership(conn, destination) {
|
|
5357
|
-
const result = await conn.exec(`stat -c '%a %U %G' ${shellQuote(destination)}`, {
|
|
5366
|
+
const result = await conn.exec(`stat -c '%a %U %G %u %g' ${shellQuote(destination)}`, {
|
|
5358
5367
|
ignoreExitCode: true,
|
|
5359
5368
|
silent: true
|
|
5360
5369
|
});
|
|
5361
|
-
if (result.code !== 0) return { group: "", mode: "", owner: "" };
|
|
5362
|
-
const [mode = "", owner = "", group2 = ""] = result.stdout.trim().split(new RegExp("\\s+", "v"));
|
|
5363
|
-
return { group: group2, mode, owner };
|
|
5370
|
+
if (result.code !== 0) return { group: "", groupId: "", mode: "", owner: "", ownerId: "" };
|
|
5371
|
+
const [mode = "", owner = "", group2 = "", ownerId = "", groupId = ""] = result.stdout.trim().split(new RegExp("\\s+", "v"));
|
|
5372
|
+
return { group: group2, groupId, mode, owner, ownerId };
|
|
5373
|
+
}
|
|
5374
|
+
function downloadComponentMatches(expected, actual, actualId) {
|
|
5375
|
+
if (expected == null) return true;
|
|
5376
|
+
return ownershipComponentMatches(expected, actual, actualId);
|
|
5364
5377
|
}
|
|
5365
5378
|
function downloadOwnershipMatches(current, options) {
|
|
5366
5379
|
if (options.mode != null && current.mode !== options.mode.replace(new RegExp("^0+", "v"), "")) return false;
|
|
5367
|
-
if (options.owner
|
|
5368
|
-
if (options.group
|
|
5380
|
+
if (!downloadComponentMatches(options.owner, current.owner, current.ownerId)) return false;
|
|
5381
|
+
if (!downloadComponentMatches(options.group, current.group, current.groupId)) return false;
|
|
5369
5382
|
return true;
|
|
5370
5383
|
}
|
|
5371
5384
|
async function metadataMatches(conn, destination, options) {
|
|
@@ -5564,7 +5577,7 @@ function downloadModeDrifted(current, options) {
|
|
|
5564
5577
|
return options.mode != null && current.mode !== options.mode.replace(new RegExp("^0+", "v"), "");
|
|
5565
5578
|
}
|
|
5566
5579
|
function downloadOwnerDrifted(current, options) {
|
|
5567
|
-
return options.owner
|
|
5580
|
+
return !downloadComponentMatches(options.owner, current.owner, current.ownerId) || !downloadComponentMatches(options.group, current.group, current.groupId);
|
|
5568
5581
|
}
|
|
5569
5582
|
async function healModeDrift(conn, parameters, current) {
|
|
5570
5583
|
if (parameters.mode == null || !downloadModeDrifted(current, parameters)) {
|
|
@@ -6922,18 +6935,27 @@ ${endMarker}`
|
|
|
6922
6935
|
};
|
|
6923
6936
|
}
|
|
6924
6937
|
async function readPropertiesState(ssh2, remotePath) {
|
|
6925
|
-
const result = await ssh2.exec(`stat -c '%a %U %G' ${shellQuote(remotePath)}`, EXEC_OPTS6);
|
|
6926
|
-
if (result.code !== 0) return { group: "", mode: "", owner: "" };
|
|
6927
|
-
const [mode = "", owner = "", group2 = ""] = result.stdout.trim().split(new RegExp("\\s+", "v"));
|
|
6928
|
-
return { group: group2, mode, owner };
|
|
6938
|
+
const result = await ssh2.exec(`stat -c '%a %U %G %u %g' ${shellQuote(remotePath)}`, EXEC_OPTS6);
|
|
6939
|
+
if (result.code !== 0) return { group: "", groupId: "", mode: "", owner: "", ownerId: "" };
|
|
6940
|
+
const [mode = "", owner = "", group2 = "", ownerId = "", groupId = ""] = result.stdout.trim().split(new RegExp("\\s+", "v"));
|
|
6941
|
+
return { group: group2, groupId, mode, owner, ownerId };
|
|
6929
6942
|
}
|
|
6930
6943
|
function modeMatches(current, desired) {
|
|
6931
6944
|
return current === desired.replace(new RegExp("^0+", "v"), "");
|
|
6932
6945
|
}
|
|
6946
|
+
function assertValidOwnershipComponent(value, kind) {
|
|
6947
|
+
if (isNumericId(value)) return;
|
|
6948
|
+
if (kind === "user") assertValidUserName(value);
|
|
6949
|
+
else assertValidGroupName(value);
|
|
6950
|
+
}
|
|
6951
|
+
function propertiesComponentMatches(expected, actual, actualId) {
|
|
6952
|
+
if (expected == null) return true;
|
|
6953
|
+
return ownershipComponentMatches(expected, actual, actualId);
|
|
6954
|
+
}
|
|
6933
6955
|
function assertValidPropertiesOptions(options) {
|
|
6934
6956
|
if (options.mode != null) validateMode(options.mode);
|
|
6935
|
-
if (options.owner != null)
|
|
6936
|
-
if (options.group != null)
|
|
6957
|
+
if (options.owner != null) assertValidOwnershipComponent(options.owner, "user");
|
|
6958
|
+
if (options.group != null) assertValidOwnershipComponent(options.group, "group");
|
|
6937
6959
|
}
|
|
6938
6960
|
function isDriftFailure(result) {
|
|
6939
6961
|
return typeof result !== "boolean";
|
|
@@ -6949,8 +6971,8 @@ async function applyModeDrift(context) {
|
|
|
6949
6971
|
}
|
|
6950
6972
|
async function maybeApplyCombinedChown(context) {
|
|
6951
6973
|
const { current, options, remotePath, ssh: ssh2 } = context;
|
|
6952
|
-
const ownerNeedsUpdate = options.owner != null && current.owner
|
|
6953
|
-
const groupNeedsUpdate = options.group != null && current.group
|
|
6974
|
+
const ownerNeedsUpdate = options.owner != null && !propertiesComponentMatches(options.owner, current.owner, current.ownerId);
|
|
6975
|
+
const groupNeedsUpdate = options.group != null && !propertiesComponentMatches(options.group, current.group, current.groupId);
|
|
6954
6976
|
if (!ownerNeedsUpdate || !groupNeedsUpdate || options.owner == null || options.group == null) {
|
|
6955
6977
|
return false;
|
|
6956
6978
|
}
|
|
@@ -6963,7 +6985,9 @@ async function maybeApplyCombinedChown(context) {
|
|
|
6963
6985
|
}
|
|
6964
6986
|
async function maybeApplySingleChown(context) {
|
|
6965
6987
|
const { current, options, remotePath, ssh: ssh2 } = context;
|
|
6966
|
-
if (options.owner == null || current.owner
|
|
6988
|
+
if (options.owner == null || propertiesComponentMatches(options.owner, current.owner, current.ownerId)) {
|
|
6989
|
+
return false;
|
|
6990
|
+
}
|
|
6967
6991
|
const result = await ssh2.exec(renderGuardedChownCommand(options.owner, remotePath), EXEC_OPTS6);
|
|
6968
6992
|
if (result.code !== 0) {
|
|
6969
6993
|
return failedCommand(`[file.properties: ${remotePath}] chown failed`, result);
|
|
@@ -6972,7 +6996,9 @@ async function maybeApplySingleChown(context) {
|
|
|
6972
6996
|
}
|
|
6973
6997
|
async function maybeApplySingleChgrp(context) {
|
|
6974
6998
|
const { current, options, remotePath, ssh: ssh2 } = context;
|
|
6975
|
-
if (options.group == null || current.group
|
|
6999
|
+
if (options.group == null || propertiesComponentMatches(options.group, current.group, current.groupId)) {
|
|
7000
|
+
return false;
|
|
7001
|
+
}
|
|
6976
7002
|
const result = await ssh2.exec(renderGuardedChgrpCommand(options.group, remotePath), EXEC_OPTS6);
|
|
6977
7003
|
if (result.code !== 0) {
|
|
6978
7004
|
return failedCommand(`[file.properties: ${remotePath}] chgrp failed`, result);
|
|
@@ -7011,10 +7037,10 @@ function properties(remotePath, options) {
|
|
|
7011
7037
|
async check(ssh2) {
|
|
7012
7038
|
if (!ssh2) return NEEDS_APPLY;
|
|
7013
7039
|
if (await isSymlink(ssh2, remotePath)) return NEEDS_APPLY;
|
|
7014
|
-
const { group: group2, mode, owner } = await readPropertiesState(ssh2, remotePath);
|
|
7040
|
+
const { group: group2, groupId, mode, owner, ownerId } = await readPropertiesState(ssh2, remotePath);
|
|
7015
7041
|
if (options.mode != null && !modeMatches(mode, options.mode)) return NEEDS_APPLY;
|
|
7016
|
-
if (options.owner
|
|
7017
|
-
if (options.group
|
|
7042
|
+
if (!propertiesComponentMatches(options.owner, owner, ownerId)) return NEEDS_APPLY;
|
|
7043
|
+
if (!propertiesComponentMatches(options.group, group2, groupId)) return NEEDS_APPLY;
|
|
7018
7044
|
return "ok";
|
|
7019
7045
|
},
|
|
7020
7046
|
name: `file.properties: ${remotePath}`
|
package/dist/cli.js
CHANGED
|
@@ -6262,7 +6262,7 @@ async function runApplyCommand(file, options, run = runPlaybook) {
|
|
|
6262
6262
|
if (options.diff && !options.dryRun) {
|
|
6263
6263
|
throw new CliUsageError("--diff requires --dry-run");
|
|
6264
6264
|
}
|
|
6265
|
-
printCliHeader("0.16.
|
|
6265
|
+
printCliHeader("0.16.1-728d4e1");
|
|
6266
6266
|
const environmentOverrides = applyCliEnvironmentOverrides(options.env, {
|
|
6267
6267
|
firstRun: options.firstRun
|
|
6268
6268
|
});
|
|
@@ -6300,7 +6300,7 @@ function renderSubcommandOptionsHelp(command) {
|
|
|
6300
6300
|
return ["", `Options for "paratix ${command.name()} <file>":`, ...rows].join("\n");
|
|
6301
6301
|
}
|
|
6302
6302
|
var program = new Command();
|
|
6303
|
-
program.name("paratix").description("Idempotent VPS setup tool in TypeScript").version("0.16.
|
|
6303
|
+
program.name("paratix").description("Idempotent VPS setup tool in TypeScript").version("0.16.1-728d4e1");
|
|
6304
6304
|
var applyCommand = program.command("apply <file>").description("Apply a server definition").option(
|
|
6305
6305
|
"--diff",
|
|
6306
6306
|
"When combined with --dry-run, show a unified diff per module that would change.",
|
|
@@ -1017,7 +1017,7 @@ type PropertiesOptions = {
|
|
|
1017
1017
|
/**
|
|
1018
1018
|
* Set file or directory ownership and permissions on the remote host.
|
|
1019
1019
|
* Only the attributes specified in `options` are checked and applied. Drift
|
|
1020
|
-
* is detected via `stat -c '%a %U %G'` so `apply` only invokes the relevant
|
|
1020
|
+
* is detected via `stat -c '%a %U %G %u %g'` so `apply` only invokes the relevant
|
|
1021
1021
|
* `chmod`/`chown`/`chgrp` for fields that actually differ from the desired
|
|
1022
1022
|
* state. When all desired fields already match, `apply` returns `status: "ok"`
|
|
1023
1023
|
* instead of falsely reporting a change.
|
package/dist/index.d.ts
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
import { E as Environment, M as Module, a as ModuleMetaEntry, b as MetaEnvironmentValue, c as EnvironmentMetaEntry, S as SshdPortMetaEntry, d as SystemHostMetaEntry, e as SystemRebootMetaEntry, f as ModuleResult, g as ExecResult, h as ModuleStatus, i as SshConnection, O as OrchestrationStep, j as ShutdownSignal, k as ServerDefinition } from './index-
|
|
2
|
-
export { l as EnvironmentValue, m as ExecOptions, N as NEEDS_APPLY, n as SshConfig, o as apt, p as archive, q as command, r as compose, s as cron, t as download, u as file, v as git, w as group, x as hostname, y as mount, z as net, A as op, B as package, C as quadlet, D as releaseUpgrade, F as rsync, G as script, H as service, I as ssh, J as sshd, K as swap, L as sysctl, P as system, Q as systemd, R as timer, T as ufw, U as user } from './index-
|
|
1
|
+
import { E as Environment, M as Module, a as ModuleMetaEntry, b as MetaEnvironmentValue, c as EnvironmentMetaEntry, S as SshdPortMetaEntry, d as SystemHostMetaEntry, e as SystemRebootMetaEntry, f as ModuleResult, g as ExecResult, h as ModuleStatus, i as SshConnection, O as OrchestrationStep, j as ShutdownSignal, k as ServerDefinition } from './index-jbXRUlJ6.js';
|
|
2
|
+
export { l as EnvironmentValue, m as ExecOptions, N as NEEDS_APPLY, n as SshConfig, o as apt, p as archive, q as command, r as compose, s as cron, t as download, u as file, v as git, w as group, x as hostname, y as mount, z as net, A as op, B as package, C as quadlet, D as releaseUpgrade, F as rsync, G as script, H as service, I as ssh, J as sshd, K as swap, L as sysctl, P as system, Q as systemd, R as timer, T as ufw, U as user } from './index-jbXRUlJ6.js';
|
|
3
3
|
|
|
4
4
|
/**
|
|
5
5
|
* Fail the run if a condition on the current env is not satisfied.
|
package/dist/index.js
CHANGED
package/dist/modules/index.d.ts
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
export { V as PackageSpec, W as UpgradeOptions, o as apt, p as archive, q as command, r as compose, s as cron, t as download, u as file, v as git, w as group, x as hostname, y as mount, z as net, A as op, B as package, C as quadlet, D as releaseUpgrade, F as rsync, G as script, H as service, I as ssh, J as sshd, K as swap, L as sysctl, P as system, Q as systemd, R as timer, T as ufw, U as user } from '../index-
|
|
1
|
+
export { V as PackageSpec, W as UpgradeOptions, o as apt, p as archive, q as command, r as compose, s as cron, t as download, u as file, v as git, w as group, x as hostname, y as mount, z as net, A as op, B as package, C as quadlet, D as releaseUpgrade, F as rsync, G as script, H as service, I as ssh, J as sshd, K as swap, L as sysctl, P as system, Q as systemd, R as timer, T as ufw, U as user } from '../index-jbXRUlJ6.js';
|
package/dist/modules/index.js
CHANGED
package/llm-guide.md
CHANGED
|
@@ -224,6 +224,15 @@ very slow artifact hosts or large downloads.
|
|
|
224
224
|
| `file.stat` | `(remotePath: string): Module` | No (always-applies) |
|
|
225
225
|
| `file.template` | `(remotePath: string, templatePath: string, options?: { mode?: string; owner?: string; strict?: boolean }): Module` | Yes |
|
|
226
226
|
|
|
227
|
+
**Note on `owner` and `group` values:** Every ownership option accepts either a POSIX name or a
|
|
228
|
+
numeric id — `owner: "www-data:www-data"` and `owner: "65532:65532"` are both valid, and the two
|
|
229
|
+
components may be mixed (`owner: "root:65532"`). Numeric ids are the only option for accounts that
|
|
230
|
+
have no passwd or group entry on the target host, such as `65532` in distroless images or `70` in
|
|
231
|
+
the postgres image. Drift detection compares the declared value against both the name and the id
|
|
232
|
+
reported by the host, so a numerically declared owner converges to `status: "ok"` like a named one.
|
|
233
|
+
`file.properties` keeps `owner` and `group` as separate options and therefore rejects a combined
|
|
234
|
+
`"user:group"` spec.
|
|
235
|
+
|
|
227
236
|
**Note on `file.copy` and the default mode:** When `options.mode` is omitted, `file.copy` sets the mode to the documented default of `0644`. The mode is applied during upload (`uploadFile` with `{ mode }`) and compared with the desired value in `check`, so subsequent runs detect mode drift (for example, a manual `chmod 0600`) as `needs-apply`. Pass `{ mode: "0600" }` explicitly when more restrictive permissions are required, such as for secrets.
|
|
228
237
|
|
|
229
238
|
**Note on `file.line`:** The `line` value must be a single line without CR/LF characters. Use `file.block` for multiline content.
|