paratix 0.15.0 → 0.16.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/{chunk-GB5QZWUM.js → chunk-56SN6NQH.js} +142 -34
- package/dist/cli.js +2 -2
- package/dist/{index-Da9ccU5K.d.ts → index-N3n0FmKh.d.ts} +35 -0
- 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 +18 -14
- package/package.json +1 -1
|
@@ -3446,19 +3446,8 @@ async function resolveComposeConfigImages(parameters) {
|
|
|
3446
3446
|
`${composeCommand(parameters.runtime, parameters.projectDirectory)} config --format json`,
|
|
3447
3447
|
EXEC_OPTS4
|
|
3448
3448
|
);
|
|
3449
|
-
if (result.code !== 0)
|
|
3450
|
-
|
|
3451
|
-
`[compose.pull] failed to resolve images for ${parameters.projectDirectory}`,
|
|
3452
|
-
result
|
|
3453
|
-
);
|
|
3454
|
-
}
|
|
3455
|
-
const images = parseComposeConfigImages(result.stdout);
|
|
3456
|
-
if (images === null) {
|
|
3457
|
-
return failed(
|
|
3458
|
-
`[compose.pull] failed to parse compose config images for ${parameters.projectDirectory}`
|
|
3459
|
-
);
|
|
3460
|
-
}
|
|
3461
|
-
return images;
|
|
3449
|
+
if (result.code !== 0) return null;
|
|
3450
|
+
return parseComposeConfigImages(result.stdout);
|
|
3462
3451
|
}
|
|
3463
3452
|
async function inspectComposeImageBeforePull(parameters) {
|
|
3464
3453
|
const result = await parameters.ssh.exec(
|
|
@@ -4194,7 +4183,15 @@ var compose = {
|
|
|
4194
4183
|
runtime,
|
|
4195
4184
|
ssh: connection
|
|
4196
4185
|
});
|
|
4197
|
-
if (
|
|
4186
|
+
if (images === null) {
|
|
4187
|
+
const fallbackResult = await connection.exec(
|
|
4188
|
+
`${composeCommand(runtime, projectDirectory)} pull 2>&1`,
|
|
4189
|
+
EXEC_OPTS4
|
|
4190
|
+
);
|
|
4191
|
+
if (fallbackResult.code !== 0)
|
|
4192
|
+
return failedCommand(`[compose.pull] failed for ${projectDirectory}`, fallbackResult);
|
|
4193
|
+
return { status: "changed" };
|
|
4194
|
+
}
|
|
4198
4195
|
const beforePull = await snapshotComposeImagesBeforePull({
|
|
4199
4196
|
images,
|
|
4200
4197
|
runtime,
|
|
@@ -10604,12 +10601,17 @@ function parseApkVersion(out, packageName) {
|
|
|
10604
10601
|
async function getInstalledVersion(ssh2, pm, packageName) {
|
|
10605
10602
|
const quoted = shellQuote(packageName);
|
|
10606
10603
|
if (pm === "apk") {
|
|
10607
|
-
const
|
|
10608
|
-
|
|
10604
|
+
const apkResult = await ssh2.exec(`apk version -v ${quoted}`, VERSION_EXEC_OPTS);
|
|
10605
|
+
if (apkResult.code !== 0) return null;
|
|
10606
|
+
return parseApkVersion(apkResult.stdout.trim(), packageName);
|
|
10609
10607
|
}
|
|
10610
10608
|
if (pm === "apt") {
|
|
10611
|
-
const
|
|
10612
|
-
|
|
10609
|
+
const aptResult = await ssh2.exec(
|
|
10610
|
+
`dpkg-query -W -f='\${Version}' ${quoted} 2>/dev/null`,
|
|
10611
|
+
VERSION_EXEC_OPTS
|
|
10612
|
+
);
|
|
10613
|
+
if (aptResult.code !== 0) return null;
|
|
10614
|
+
const aptVersion = aptResult.stdout.trim();
|
|
10613
10615
|
return aptVersion.length > 0 ? aptVersion : null;
|
|
10614
10616
|
}
|
|
10615
10617
|
const result = await ssh2.exec(
|
|
@@ -11025,10 +11027,11 @@ async function snapshotQuadletFile(ssh2, filePath) {
|
|
|
11025
11027
|
mode: normalizeQuadletMode(rawMode)
|
|
11026
11028
|
};
|
|
11027
11029
|
}
|
|
11028
|
-
async function restoreQuadletFileSnapshot(
|
|
11030
|
+
async function restoreQuadletFileSnapshot(parameters) {
|
|
11031
|
+
const { filePath, label, snapshot, ssh: ssh2 } = parameters;
|
|
11029
11032
|
if (snapshot.exists) {
|
|
11030
11033
|
if (await isSymlink(ssh2, filePath)) {
|
|
11031
|
-
throw new Error(`[
|
|
11034
|
+
throw new Error(`[${label}] refuses to restore through symlink at ${filePath}`);
|
|
11032
11035
|
}
|
|
11033
11036
|
await ssh2.writeFile(filePath, snapshot.content, { mode: snapshot.mode });
|
|
11034
11037
|
return;
|
|
@@ -11036,24 +11039,30 @@ async function restoreQuadletFileSnapshot(ssh2, filePath, snapshot) {
|
|
|
11036
11039
|
await ssh2.exec(`rm -f ${shellQuote(filePath)}`, { ignoreExitCode: true, silent: true });
|
|
11037
11040
|
}
|
|
11038
11041
|
async function applyQuadletFile(parameters) {
|
|
11042
|
+
const label = parameters.label ?? "quadlet.container";
|
|
11039
11043
|
const mkdirResult = await parameters.ssh.exec(CONTAINERS_SYSTEMD_DIRECTORY_COMMAND, {
|
|
11040
11044
|
ignoreExitCode: true,
|
|
11041
11045
|
silent: true
|
|
11042
11046
|
});
|
|
11043
11047
|
if (mkdirResult.code !== 0) {
|
|
11044
11048
|
return failedCommand(
|
|
11045
|
-
`[
|
|
11049
|
+
`[${label}: ${parameters.name}] failed to create quadlet directory`,
|
|
11046
11050
|
mkdirResult
|
|
11047
11051
|
);
|
|
11048
11052
|
}
|
|
11049
11053
|
if (await isSymlink(parameters.ssh, parameters.filePath)) {
|
|
11050
11054
|
return failed(
|
|
11051
|
-
`[
|
|
11055
|
+
`[${label}: ${parameters.name}] refuses to write through symlink at ${parameters.filePath}`
|
|
11052
11056
|
);
|
|
11053
11057
|
}
|
|
11054
11058
|
const snapshot = await snapshotQuadletFile(parameters.ssh, parameters.filePath);
|
|
11055
11059
|
const restoreSnapshot = async () => {
|
|
11056
|
-
await restoreQuadletFileSnapshot(
|
|
11060
|
+
await restoreQuadletFileSnapshot({
|
|
11061
|
+
filePath: parameters.filePath,
|
|
11062
|
+
label,
|
|
11063
|
+
snapshot,
|
|
11064
|
+
ssh: parameters.ssh
|
|
11065
|
+
});
|
|
11057
11066
|
};
|
|
11058
11067
|
try {
|
|
11059
11068
|
await parameters.ssh.writeFile(parameters.filePath, parameters.content, {
|
|
@@ -11062,7 +11071,7 @@ async function applyQuadletFile(parameters) {
|
|
|
11062
11071
|
} catch (error) {
|
|
11063
11072
|
const reason = error instanceof Error ? error.message : String(error);
|
|
11064
11073
|
return withRollbackFailure(
|
|
11065
|
-
failed(`[
|
|
11074
|
+
failed(`[${label}: ${parameters.name}] failed to write quadlet file: ${reason}`),
|
|
11066
11075
|
restoreSnapshot,
|
|
11067
11076
|
"quadlet apply failed"
|
|
11068
11077
|
);
|
|
@@ -11073,10 +11082,7 @@ async function applyQuadletFile(parameters) {
|
|
|
11073
11082
|
});
|
|
11074
11083
|
if (daemonReload.code === 0) return { status: "changed" };
|
|
11075
11084
|
return withRollbackFailure(
|
|
11076
|
-
failedCommand(
|
|
11077
|
-
`[quadlet.container: ${parameters.name}] systemctl daemon-reload failed`,
|
|
11078
|
-
daemonReload
|
|
11079
|
-
),
|
|
11085
|
+
failedCommand(`[${label}: ${parameters.name}] systemctl daemon-reload failed`, daemonReload),
|
|
11080
11086
|
restoreSnapshot,
|
|
11081
11087
|
"quadlet apply failed"
|
|
11082
11088
|
);
|
|
@@ -11112,7 +11118,7 @@ var QUADLET_PULL_CHANGED_OUTPUT_PATTERNS = [
|
|
|
11112
11118
|
];
|
|
11113
11119
|
function assertQuadletLineValue(key, value) {
|
|
11114
11120
|
if (QUADLET_CONTROL_CHARACTER_PATTERN.test(value)) {
|
|
11115
|
-
throw new Error(`quadlet
|
|
11121
|
+
throw new Error(`quadlet ${key} values must not contain control characters`);
|
|
11116
11122
|
}
|
|
11117
11123
|
}
|
|
11118
11124
|
function renderQuadletLine(key, value) {
|
|
@@ -11345,6 +11351,37 @@ function parseQuadletImageInspectOutput(output) {
|
|
|
11345
11351
|
};
|
|
11346
11352
|
}
|
|
11347
11353
|
|
|
11354
|
+
// src/modules/quadletNetworkHelpers.ts
|
|
11355
|
+
function getQuadletNetworkFilePath(name) {
|
|
11356
|
+
return `${CONTAINERS_SYSTEMD_DIRECTORY}/${name}.network`;
|
|
11357
|
+
}
|
|
11358
|
+
function buildQuadletNetworkUnitLines(options) {
|
|
11359
|
+
return compactQuadletLines([
|
|
11360
|
+
renderQuadletLine("NetworkName", options.name),
|
|
11361
|
+
maybeRenderQuadletLine("Driver", options.driver),
|
|
11362
|
+
maybeRenderQuadletLine("IPAMDriver", options.ipamDriver),
|
|
11363
|
+
maybeRenderQuadletBool("Internal", options.internal),
|
|
11364
|
+
maybeRenderQuadletBool("IPv6", options.ipv6),
|
|
11365
|
+
maybeRenderQuadletBool("DisableDNS", options.disableDns),
|
|
11366
|
+
maybeRenderQuadletLine("Subnet", options.subnet),
|
|
11367
|
+
maybeRenderQuadletLine("Gateway", options.gateway),
|
|
11368
|
+
maybeRenderQuadletLine("IPRange", options.ipRange),
|
|
11369
|
+
...renderQuadletRepeated("DNS", options.dns ?? []),
|
|
11370
|
+
...renderQuadletKeyValue("Options", options.options ?? {}),
|
|
11371
|
+
...renderQuadletKeyValue("Label", options.label ?? {}),
|
|
11372
|
+
...renderQuadletRepeated("PodmanArgs", options.podmanArgs ?? [])
|
|
11373
|
+
]);
|
|
11374
|
+
}
|
|
11375
|
+
function generateNetworkQuadlet(options) {
|
|
11376
|
+
const sections = [
|
|
11377
|
+
renderQuadletSection("Unit", [
|
|
11378
|
+
renderQuadletLine("Description", options.description ?? `Podman network: ${options.name}`)
|
|
11379
|
+
]),
|
|
11380
|
+
renderQuadletSection("Network", buildQuadletNetworkUnitLines(options))
|
|
11381
|
+
];
|
|
11382
|
+
return sections.join("\n").trimEnd();
|
|
11383
|
+
}
|
|
11384
|
+
|
|
11348
11385
|
// src/modules/quadletValidationHelpers.ts
|
|
11349
11386
|
var UNIT_NAME_PATTERN2 = new RegExp("^[\\w@.\\-]+$", "v");
|
|
11350
11387
|
var UNIT_NAME_ALL_DOTS_PATTERN = new RegExp("^\\.+$", "v");
|
|
@@ -11419,8 +11456,8 @@ function validateQuadletAuthFilePath(field, value) {
|
|
|
11419
11456
|
// src/modules/quadlet.ts
|
|
11420
11457
|
var QUADLET_RELOAD_HASH_LENGTH = 16;
|
|
11421
11458
|
var SYSTEMCTL2 = "systemctl";
|
|
11422
|
-
function buildQuadletReloadFlag(name, content) {
|
|
11423
|
-
const flagPrefix = `quadlet
|
|
11459
|
+
function buildQuadletReloadFlag(kind, name, content) {
|
|
11460
|
+
const flagPrefix = `quadlet-${kind}-${sha256String(name).slice(0, QUADLET_RELOAD_HASH_LENGTH)}-`;
|
|
11424
11461
|
return {
|
|
11425
11462
|
flagName: `${flagPrefix}${sha256String(content).slice(0, QUADLET_RELOAD_HASH_LENGTH)}`,
|
|
11426
11463
|
flagPrefix
|
|
@@ -11502,7 +11539,7 @@ async function applyQuadletImageUpdate(parameters) {
|
|
|
11502
11539
|
ssh: parameters.ssh
|
|
11503
11540
|
});
|
|
11504
11541
|
}
|
|
11505
|
-
async function
|
|
11542
|
+
async function buildQuadletDryRunDiff(ssh2, filePath, desired) {
|
|
11506
11543
|
try {
|
|
11507
11544
|
const exists = await ssh2.exists(filePath);
|
|
11508
11545
|
const current = exists ? await ssh2.readFile(filePath) : "";
|
|
@@ -11513,6 +11550,16 @@ async function buildQuadletContainerDryRunDiff(ssh2, filePath, desired) {
|
|
|
11513
11550
|
return void 0;
|
|
11514
11551
|
}
|
|
11515
11552
|
}
|
|
11553
|
+
async function podmanNetworkExists(ssh2, name) {
|
|
11554
|
+
const result = await ssh2.exec(`podman network exists -- ${shellQuote(name)}`, {
|
|
11555
|
+
ignoreExitCode: true,
|
|
11556
|
+
silent: true
|
|
11557
|
+
});
|
|
11558
|
+
return result.code === 0;
|
|
11559
|
+
}
|
|
11560
|
+
function quadletNetworkLiveHint(name) {
|
|
11561
|
+
return `network '${name}' already exists; the running network is not recreated automatically \u2014 remove it to apply changed settings`;
|
|
11562
|
+
}
|
|
11516
11563
|
var quadlet = {
|
|
11517
11564
|
/**
|
|
11518
11565
|
* Write a Podman Quadlet `.container` definition and reload systemd when it changes.
|
|
@@ -11528,11 +11575,11 @@ var quadlet = {
|
|
|
11528
11575
|
validateQuadletImageValue("image", options.image);
|
|
11529
11576
|
const filePath = getQuadletContainerFilePath(options.name);
|
|
11530
11577
|
const content = generateContainerQuadlet(options);
|
|
11531
|
-
const reloadFlag = buildQuadletReloadFlag(options.name, content);
|
|
11578
|
+
const reloadFlag = buildQuadletReloadFlag("container", options.name, content);
|
|
11532
11579
|
return {
|
|
11533
11580
|
async _applyDryRun(ssh2) {
|
|
11534
11581
|
if (!ssh2) return { status: "changed" };
|
|
11535
|
-
const diff = await
|
|
11582
|
+
const diff = await buildQuadletDryRunDiff(ssh2, filePath, content);
|
|
11536
11583
|
if (diff != null) return { diff, status: "changed" };
|
|
11537
11584
|
const flagPresent = await hasFlag(ssh2, reloadFlag.flagName);
|
|
11538
11585
|
if (flagPresent) return { status: "changed" };
|
|
@@ -11556,6 +11603,67 @@ var quadlet = {
|
|
|
11556
11603
|
name: `quadlet.container: ${options.name}`
|
|
11557
11604
|
};
|
|
11558
11605
|
},
|
|
11606
|
+
/**
|
|
11607
|
+
* Write a Podman Quadlet `.network` definition and reload systemd when it changes.
|
|
11608
|
+
*
|
|
11609
|
+
* Mirrors {@link quadlet.container}: the unit is written atomically to
|
|
11610
|
+
* `/etc/containers/systemd/<name>.network`, symlinks are refused, and
|
|
11611
|
+
* `systemctl daemon-reload` runs when the content changes. Containers
|
|
11612
|
+
* reference the network via their existing `networks` option
|
|
11613
|
+
* (e.g. `networks: ["app.network"]`).
|
|
11614
|
+
*
|
|
11615
|
+
* When the change lands and a network with the same name is already live on
|
|
11616
|
+
* the host, the result carries an advisory detail: Podman does not recreate
|
|
11617
|
+
* a running network, so changed `subnet`/`gateway`/… only take effect after
|
|
11618
|
+
* the network is removed.
|
|
11619
|
+
*
|
|
11620
|
+
* @param options - Configuration for the Quadlet network definition.
|
|
11621
|
+
* @returns A Module that ensures the Quadlet `.network` file is present and up to date.
|
|
11622
|
+
*/
|
|
11623
|
+
network(options) {
|
|
11624
|
+
validateQuadletName(options.name);
|
|
11625
|
+
const filePath = getQuadletNetworkFilePath(options.name);
|
|
11626
|
+
const content = generateNetworkQuadlet(options);
|
|
11627
|
+
const reloadFlag = buildQuadletReloadFlag("network", options.name, content);
|
|
11628
|
+
return {
|
|
11629
|
+
async _applyDryRun(ssh2) {
|
|
11630
|
+
if (!ssh2) return { status: "changed" };
|
|
11631
|
+
const diff = await buildQuadletDryRunDiff(ssh2, filePath, content);
|
|
11632
|
+
if (diff != null) {
|
|
11633
|
+
const liveHint = await podmanNetworkExists(ssh2, options.name) ? quadletNetworkLiveHint(options.name) : void 0;
|
|
11634
|
+
return liveHint == null ? { diff, status: "changed" } : { _dryRunDetail: liveHint, diff, status: "changed" };
|
|
11635
|
+
}
|
|
11636
|
+
const flagPresent = await hasFlag(ssh2, reloadFlag.flagName);
|
|
11637
|
+
return flagPresent ? { status: "changed" } : { _dryRunDetail: "(dry-run, daemon-reload pending)", status: "changed" };
|
|
11638
|
+
},
|
|
11639
|
+
_dryRunDiffProducer: true,
|
|
11640
|
+
async apply(ssh2) {
|
|
11641
|
+
if (!ssh2) return failed(`[quadlet.network: ${options.name}] SSH connection is required`);
|
|
11642
|
+
const contentChanges = await buildQuadletDryRunDiff(ssh2, filePath, content) != null;
|
|
11643
|
+
const result = await applyQuadletFile({
|
|
11644
|
+
content,
|
|
11645
|
+
filePath,
|
|
11646
|
+
label: "quadlet.network",
|
|
11647
|
+
name: options.name,
|
|
11648
|
+
ssh: ssh2
|
|
11649
|
+
});
|
|
11650
|
+
if (result.status !== "changed") return result;
|
|
11651
|
+
const flagFailure = await setVersionedFlag(ssh2, reloadFlag.flagName, reloadFlag.flagPrefix);
|
|
11652
|
+
if (flagFailure) return flagFailure;
|
|
11653
|
+
if (contentChanges && await podmanNetworkExists(ssh2, options.name)) {
|
|
11654
|
+
return { ...result, detail: quadletNetworkLiveHint(options.name) };
|
|
11655
|
+
}
|
|
11656
|
+
return result;
|
|
11657
|
+
},
|
|
11658
|
+
async check(ssh2) {
|
|
11659
|
+
if (!ssh2) return NEEDS_APPLY;
|
|
11660
|
+
const fileResult = await checkQuadletFile({ content, filePath, ssh: ssh2 });
|
|
11661
|
+
if (fileResult !== "ok") return fileResult;
|
|
11662
|
+
return await hasFlag(ssh2, reloadFlag.flagName) ? "ok" : NEEDS_APPLY;
|
|
11663
|
+
},
|
|
11664
|
+
name: `quadlet.network: ${options.name}`
|
|
11665
|
+
};
|
|
11666
|
+
},
|
|
11559
11667
|
/**
|
|
11560
11668
|
* Pull the latest image for a Quadlet-managed container and restart the service
|
|
11561
11669
|
* only when the image changed.
|
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.
|
|
6265
|
+
printCliHeader("0.16.0-81f051d");
|
|
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.
|
|
6303
|
+
program.name("paratix").description("Idempotent VPS setup tool in TypeScript").version("0.16.0-81f051d");
|
|
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.",
|
|
@@ -1466,6 +1466,23 @@ type QuadletImageUpdateOptions = {
|
|
|
1466
1466
|
serviceName?: string;
|
|
1467
1467
|
};
|
|
1468
1468
|
|
|
1469
|
+
type QuadletNetworkOptions = {
|
|
1470
|
+
description?: string;
|
|
1471
|
+
disableDns?: boolean;
|
|
1472
|
+
dns?: string[];
|
|
1473
|
+
driver?: string;
|
|
1474
|
+
gateway?: string;
|
|
1475
|
+
internal?: boolean;
|
|
1476
|
+
ipamDriver?: string;
|
|
1477
|
+
ipRange?: string;
|
|
1478
|
+
ipv6?: boolean;
|
|
1479
|
+
label?: Record<string, string>;
|
|
1480
|
+
name: string;
|
|
1481
|
+
options?: Record<string, string>;
|
|
1482
|
+
podmanArgs?: string[];
|
|
1483
|
+
subnet?: string;
|
|
1484
|
+
};
|
|
1485
|
+
|
|
1469
1486
|
/**
|
|
1470
1487
|
* Modules for managing Podman Quadlet definitions.
|
|
1471
1488
|
*
|
|
@@ -1484,6 +1501,24 @@ declare const quadlet: {
|
|
|
1484
1501
|
* @returns A Module that ensures the Quadlet file is present and up to date.
|
|
1485
1502
|
*/
|
|
1486
1503
|
container(options: QuadletContainerOptions): Module;
|
|
1504
|
+
/**
|
|
1505
|
+
* Write a Podman Quadlet `.network` definition and reload systemd when it changes.
|
|
1506
|
+
*
|
|
1507
|
+
* Mirrors {@link quadlet.container}: the unit is written atomically to
|
|
1508
|
+
* `/etc/containers/systemd/<name>.network`, symlinks are refused, and
|
|
1509
|
+
* `systemctl daemon-reload` runs when the content changes. Containers
|
|
1510
|
+
* reference the network via their existing `networks` option
|
|
1511
|
+
* (e.g. `networks: ["app.network"]`).
|
|
1512
|
+
*
|
|
1513
|
+
* When the change lands and a network with the same name is already live on
|
|
1514
|
+
* the host, the result carries an advisory detail: Podman does not recreate
|
|
1515
|
+
* a running network, so changed `subnet`/`gateway`/… only take effect after
|
|
1516
|
+
* the network is removed.
|
|
1517
|
+
*
|
|
1518
|
+
* @param options - Configuration for the Quadlet network definition.
|
|
1519
|
+
* @returns A Module that ensures the Quadlet `.network` file is present and up to date.
|
|
1520
|
+
*/
|
|
1521
|
+
network(options: QuadletNetworkOptions): Module;
|
|
1487
1522
|
/**
|
|
1488
1523
|
* Pull the latest image for a Quadlet-managed container and restart the service
|
|
1489
1524
|
* only when the image changed.
|
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-N3n0FmKh.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-N3n0FmKh.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-N3n0FmKh.js';
|
package/dist/modules/index.js
CHANGED
package/llm-guide.md
CHANGED
|
@@ -186,6 +186,8 @@ the idiomatic way to ensure a previously installed cron job is gone.
|
|
|
186
186
|
| `compose.restart` | `(options: { projectDirectory: string; runtime?: "docker" \| "podman" }): Module` | No |
|
|
187
187
|
| `compose.systemd` | `(options: { detached?: boolean; name?: string; projectDirectory: string; runtime?: "docker" \| "podman" }): Module` | Yes |
|
|
188
188
|
|
|
189
|
+
> **`compose.pull` change detection:** `compose.pull` compares image digests before and after the pull to report `ok` (nothing new) vs `changed`. This needs `compose config --format json`. With `runtime: "podman"`, when the compose provider is the Python `podman-compose` (which does not support `--format json`), digest-based detection is unavailable: the pull still runs, but always reports `changed`. The Docker and Compose v2 paths are unaffected.
|
|
190
|
+
|
|
189
191
|
### `download`
|
|
190
192
|
|
|
191
193
|
| Method | Signature | Idempotent |
|
|
@@ -360,6 +362,7 @@ Whitespace und Shell-Metazeichen werden abgelehnt. `package.absent` ist rein nam
|
|
|
360
362
|
| Method | Signature | Idempotent |
|
|
361
363
|
| --------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ | ---------- |
|
|
362
364
|
| `quadlet.container` | `(options: { addCapability?: string[]; addDevice?: string[]; annotation?: Record<string, string>; autoUpdate?: "local" \| "registry"; containerName?: string; description?: string; dns?: string[]; dnsOption?: string[]; dnsSearch?: string[]; dropCapability?: string[]; entrypoint?: string[]; environment?: Record<string, string>; environmentFiles?: string[]; exec?: string[]; exposeHostPort?: string[]; groupAdd?: string[]; healthCmd?: string; healthInterval?: string; healthOnFailure?: "kill" \| "none" \| "restart" \| "stop"; healthRetries?: number; healthStartPeriod?: string; healthTimeout?: string; hostName?: string; image: string; ip?: string; ip6?: string; label?: Record<string, string>; logDriver?: string; mask?: string[]; mount?: string[]; name: string; networks?: string[]; noNewPrivileges?: boolean; notify?: boolean; podmanArgs?: string[]; publishPorts?: string[]; pull?: "always" \| "missing" \| "never" \| "newer"; readOnly?: boolean; restart?: "always" \| "no" \| "on-abnormal" \| "on-abort" \| "on-failure" \| "on-success" \| "on-watchdog"; runInit?: boolean; secret?: string[]; seccompProfile?: string; securityLabelDisable?: boolean; securityLabelType?: string; stopTimeout?: number; sysctl?: Record<string, string>; timeoutStartSec?: number; timeoutStopSec?: number; timezone?: string; tmpfs?: string[]; ulimit?: string[]; unmask?: string[]; user?: string; userNs?: string; volumes?: string[]; wantedBy?: string; workingDir?: string }): Module` | Yes |
|
|
365
|
+
| `quadlet.network` | `(options: { description?: string; disableDns?: boolean; dns?: string[]; driver?: string; gateway?: string; internal?: boolean; ipRange?: string; ipamDriver?: string; ipv6?: boolean; label?: Record<string, string>; name: string; options?: Record<string, string>; podmanArgs?: string[]; subnet?: string }): Module` — writes `/etc/containers/systemd/<name>.network`; reference it from a container via `networks: ["<name>.network"]`. When the change lands and a network with the same name is already live, `changed` carries an advisory detail that the running network is not recreated until removed | Yes |
|
|
363
366
|
| `quadlet.updateImage` | `(options: { authFile?: string; image: string; name: string; serviceName?: string }): Module` — returns `changed` with the new registry digest (or local image ID fallback) in parentheses when a newer image was pulled | Partial |
|
|
364
367
|
|
|
365
368
|
### `releaseUpgrade`
|
|
@@ -803,20 +806,21 @@ Rules:
|
|
|
803
806
|
|
|
804
807
|
Diff-producing built-in modules:
|
|
805
808
|
|
|
806
|
-
| Module | Diff content
|
|
807
|
-
| ------------------------------------------- |
|
|
808
|
-
| `file.copy` | Unified diff between the remote file and the local source.
|
|
809
|
-
| `file.template` | Unified diff between the remote file and the rendered template.
|
|
810
|
-
| `sysctl.set` | `key = old → new` for the desired configuration.
|
|
811
|
-
| `hostname.set` | `hostname = old → new`.
|
|
812
|
-
| `swap.file` | Diff of the `/etc/fstab` entry (or removal of the line for `state: "absent"`).
|
|
813
|
-
| `swap.swappiness` | `vm.swappiness = old → new` (via `sysctl.set`).
|
|
814
|
-
| `swap.vfsCachePressure` | `vm.vfs_cache_pressure = old → new` (via `sysctl.set`).
|
|
815
|
-
| `cron.job` / `cron.absent` | Unified diff of the target user's crontab.
|
|
816
|
-
| `timer.scheduled` (present) | Concatenated diffs of the `.service` and `.timer` unit files.
|
|
817
|
-
| `timer.scheduled` (absent) / `timer.absent` | List of unit files to remove.
|
|
818
|
-
| `net.hosts` | Unified diff of `/etc/hosts`.
|
|
819
|
-
| `quadlet.container` | Unified diff of the `.container` unit file. If the file already matches but the `daemon-reload` flag is missing, `(dry-run, daemon-reload pending)` also appears as a detail suffix.
|
|
809
|
+
| Module | Diff content |
|
|
810
|
+
| ------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ |
|
|
811
|
+
| `file.copy` | Unified diff between the remote file and the local source. |
|
|
812
|
+
| `file.template` | Unified diff between the remote file and the rendered template. |
|
|
813
|
+
| `sysctl.set` | `key = old → new` for the desired configuration. |
|
|
814
|
+
| `hostname.set` | `hostname = old → new`. |
|
|
815
|
+
| `swap.file` | Diff of the `/etc/fstab` entry (or removal of the line for `state: "absent"`). |
|
|
816
|
+
| `swap.swappiness` | `vm.swappiness = old → new` (via `sysctl.set`). |
|
|
817
|
+
| `swap.vfsCachePressure` | `vm.vfs_cache_pressure = old → new` (via `sysctl.set`). |
|
|
818
|
+
| `cron.job` / `cron.absent` | Unified diff of the target user's crontab. |
|
|
819
|
+
| `timer.scheduled` (present) | Concatenated diffs of the `.service` and `.timer` unit files. |
|
|
820
|
+
| `timer.scheduled` (absent) / `timer.absent` | List of unit files to remove. |
|
|
821
|
+
| `net.hosts` | Unified diff of `/etc/hosts`. |
|
|
822
|
+
| `quadlet.container` | Unified diff of the `.container` unit file. If the file already matches but the `daemon-reload` flag is missing, `(dry-run, daemon-reload pending)` also appears as a detail suffix. |
|
|
823
|
+
| `quadlet.network` | Unified diff of the `.network` unit file. If the file already matches but the `daemon-reload` flag is missing, `(dry-run, daemon-reload pending)` also appears as a detail suffix. When a network with the same name is already live, the advisory hint is appended too. |
|
|
820
824
|
|
|
821
825
|
### Implementing a Diff for a Custom Module
|
|
822
826
|
|