@homeflare/alchemy 0.19.0 → 0.20.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/linux/index.js +140 -93
- package/dist/linux/index.js.map +11 -10
- package/dist/linux/region.d.ts +11 -0
- package/dist/linux/region.d.ts.map +1 -1
- package/dist/linux/remote-file-lifecycle.d.ts.map +1 -1
- package/dist/linux/remote-file-plan.d.ts +22 -0
- package/dist/linux/remote-file-plan.d.ts.map +1 -1
- package/dist/linux/remote-file.d.ts +4 -1
- package/dist/linux/remote-file.d.ts.map +1 -1
- package/dist/linux/unit-handlers.d.ts.map +1 -1
- package/dist/linux/unit-lifecycle.d.ts +1 -5
- package/dist/linux/unit-lifecycle.d.ts.map +1 -1
- package/dist/linux/unit-preflight.d.ts +50 -0
- package/dist/linux/unit-preflight.d.ts.map +1 -0
- package/dist/proxmox/index.d.ts +15 -3
- package/dist/proxmox/index.d.ts.map +1 -1
- package/dist/proxmox/index.js +4 -1
- package/dist/proxmox/index.js.map +1 -1
- package/docs/adopted-deploys.md +13 -0
- package/docs/linux-host.md +11 -1
- package/docs/provider-standard.md +138 -0
- package/docs/upstream-conformance.md +164 -0
- package/package.json +1 -1
- package/src/linux/region.ts +15 -0
- package/src/linux/remote-file-lifecycle.test.ts +0 -88
- package/src/linux/remote-file-lifecycle.ts +25 -7
- package/src/linux/remote-file-plan.ts +37 -1
- package/src/linux/remote-file-region.test.ts +181 -0
- package/src/linux/remote-file.ts +4 -1
- package/src/linux/unit-handlers.ts +12 -5
- package/src/linux/unit-lifecycle.ts +12 -30
- package/src/linux/unit-preflight.test.ts +128 -0
- package/src/linux/unit-preflight.ts +116 -0
- package/src/linux/unit-rename.test.ts +76 -0
- package/src/proxmox/ceph-adopt.test.ts +210 -0
- package/src/proxmox/constraints-forms.test.ts +14 -6
- package/src/proxmox/constraints-node-forms.test.ts +12 -3
- package/src/proxmox/index.test.ts +7 -4
- package/src/proxmox/index.ts +15 -3
package/dist/linux/index.js
CHANGED
|
@@ -624,6 +624,7 @@ var tokens = (spec) => {
|
|
|
624
624
|
end: `${comment} END ${spec.name}`
|
|
625
625
|
};
|
|
626
626
|
};
|
|
627
|
+
var sameRegion = (a, b) => a === undefined || b === undefined ? a === undefined && b === undefined : a.name === b.name && (a.comment ?? DEFAULT_COMMENT) === (b.comment ?? DEFAULT_COMMENT);
|
|
627
628
|
var regionProblems = (spec) => {
|
|
628
629
|
const found = [];
|
|
629
630
|
const comment = spec.comment ?? DEFAULT_COMMENT;
|
|
@@ -748,6 +749,16 @@ var identity = async (runner, props) => {
|
|
|
748
749
|
return { ...uid === undefined ? {} : { uid }, ...gid === undefined ? {} : { gid } };
|
|
749
750
|
};
|
|
750
751
|
var digestOf = (text) => sha256Hex(encoder.encode(text));
|
|
752
|
+
var relocation = (props, output) => {
|
|
753
|
+
if (props.path !== output.path)
|
|
754
|
+
return "path";
|
|
755
|
+
if (sameRegion(props.region, output.region))
|
|
756
|
+
return "none";
|
|
757
|
+
if (props.region === undefined || output.region === undefined) {
|
|
758
|
+
throw refuse3(props.path, `changes between owning the whole file and owning the managed region ` + `${JSON.stringify((props.region ?? output.region)?.name ?? "")} at the same path. Those ` + "are two different claims on one file: destroy this resource and declare a new one.");
|
|
759
|
+
}
|
|
760
|
+
return "region";
|
|
761
|
+
};
|
|
751
762
|
var ownedDigest = (file, region) => {
|
|
752
763
|
if (file === undefined)
|
|
753
764
|
return;
|
|
@@ -814,7 +825,7 @@ var planWrite = async (runner, props, want) => {
|
|
|
814
825
|
// src/linux/remote-file-lifecycle.ts
|
|
815
826
|
var diffFile = async (runner, news, output) => {
|
|
816
827
|
const want = await identity(runner, news);
|
|
817
|
-
if (news
|
|
828
|
+
if (relocation(news, output) === "path") {
|
|
818
829
|
await runner.checkWrite?.(news.path, { mode: news.mode ?? DEFAULT_MODE2, ...want });
|
|
819
830
|
return { action: "replace" };
|
|
820
831
|
}
|
|
@@ -830,8 +841,8 @@ var reconcileFile = async (runner, props, output, adopt = false) => {
|
|
|
830
841
|
if (want.uid !== undefined && want.uid !== runner.effectiveUid() && !canActAsRoot(runner)) {
|
|
831
842
|
throw refuse3(props.path, `owner ${String(props.owner)} is not the deploying user; only root may chown. Deploy through ` + "a root destination or a privileged HostRunner. This provider never calls sudo itself.");
|
|
832
843
|
}
|
|
833
|
-
const
|
|
834
|
-
const prior =
|
|
844
|
+
const move = output === undefined ? "none" : relocation(props, output);
|
|
845
|
+
const prior = move === "none" ? output : undefined;
|
|
835
846
|
const planned = await planWrite(runner, props, want);
|
|
836
847
|
const live = ownedDigest(planned.before, props.region);
|
|
837
848
|
const declared = digestOf(props.content);
|
|
@@ -849,9 +860,16 @@ var reconcileFile = async (runner, props, output, adopt = false) => {
|
|
|
849
860
|
}
|
|
850
861
|
throw refuse3(props.path, "the write returned but the file on disk does not match the declaration");
|
|
851
862
|
}
|
|
852
|
-
if (
|
|
853
|
-
|
|
854
|
-
|
|
863
|
+
if (move === "none" || output === undefined)
|
|
864
|
+
return after;
|
|
865
|
+
await deleteFile(runner, output);
|
|
866
|
+
if (move === "path")
|
|
867
|
+
return after;
|
|
868
|
+
const settled = await readFileAttributes(runner, props.path, props.region);
|
|
869
|
+
if (settled === undefined || settled.contentSha256 !== declared) {
|
|
870
|
+
throw refuse3(props.path, "removing the previous managed block did not leave ours intact");
|
|
871
|
+
}
|
|
872
|
+
return settled;
|
|
855
873
|
};
|
|
856
874
|
var deleteFile = async (runner, output) => {
|
|
857
875
|
const stat = await runner.stat(output.path);
|
|
@@ -914,6 +932,80 @@ import { Unowned as Unowned4 } from "alchemy/AdoptPolicy";
|
|
|
914
932
|
import { isResolved as isResolved3 } from "alchemy/Diff";
|
|
915
933
|
import * as Effect8 from "effect/Effect";
|
|
916
934
|
|
|
935
|
+
// src/linux/systemctl.ts
|
|
936
|
+
var SYSTEMCTL = "systemctl";
|
|
937
|
+
var SHOW_PROPERTIES = [
|
|
938
|
+
"Id",
|
|
939
|
+
"LoadState",
|
|
940
|
+
"ActiveState",
|
|
941
|
+
"SubState",
|
|
942
|
+
"UnitFileState",
|
|
943
|
+
"FragmentPath",
|
|
944
|
+
"NeedDaemonReload"
|
|
945
|
+
];
|
|
946
|
+
|
|
947
|
+
class SystemctlError extends Error {
|
|
948
|
+
constructor(command, exitCode, stderr) {
|
|
949
|
+
super(`systemctl ${command} -> ${String(exitCode)}: ${stderr.trim().slice(0, 300)}`);
|
|
950
|
+
this.name = "SystemctlError";
|
|
951
|
+
}
|
|
952
|
+
}
|
|
953
|
+
var parseShow = (stdout) => {
|
|
954
|
+
const fields = new Map;
|
|
955
|
+
for (const line of stdout.split(`
|
|
956
|
+
`)) {
|
|
957
|
+
const at = line.indexOf("=");
|
|
958
|
+
if (at <= 0)
|
|
959
|
+
continue;
|
|
960
|
+
const key = line.slice(0, at);
|
|
961
|
+
if (!fields.has(key))
|
|
962
|
+
fields.set(key, line.slice(at + 1));
|
|
963
|
+
}
|
|
964
|
+
return fields;
|
|
965
|
+
};
|
|
966
|
+
var statusOf = (stdout) => {
|
|
967
|
+
const fields = parseShow(stdout);
|
|
968
|
+
const loadState = fields.get("LoadState") ?? "not-found";
|
|
969
|
+
const unitFileState = fields.get("UnitFileState") ?? "";
|
|
970
|
+
const fragmentPath = fields.get("FragmentPath") ?? "";
|
|
971
|
+
const subState = fields.get("SubState") ?? "";
|
|
972
|
+
return {
|
|
973
|
+
activeState: fields.get("ActiveState") ?? "inactive",
|
|
974
|
+
known: loadState !== "not-found" || unitFileState !== "",
|
|
975
|
+
loadState,
|
|
976
|
+
needDaemonReload: fields.get("NeedDaemonReload") === "yes",
|
|
977
|
+
...subState === "" ? {} : { subState },
|
|
978
|
+
...unitFileState === "" ? {} : { unitFileState },
|
|
979
|
+
...fragmentPath === "" ? {} : { fragmentPath }
|
|
980
|
+
};
|
|
981
|
+
};
|
|
982
|
+
var showUnit = async (runner, name) => {
|
|
983
|
+
const result = await runner.exec([
|
|
984
|
+
SYSTEMCTL,
|
|
985
|
+
"show",
|
|
986
|
+
"--no-pager",
|
|
987
|
+
"-p",
|
|
988
|
+
SHOW_PROPERTIES.join(","),
|
|
989
|
+
"--",
|
|
990
|
+
name
|
|
991
|
+
]);
|
|
992
|
+
if (result.exitCode !== 0)
|
|
993
|
+
throw new SystemctlError(`show ${name}`, result.exitCode, result.stderr);
|
|
994
|
+
return statusOf(result.stdout);
|
|
995
|
+
};
|
|
996
|
+
var run = async (runner, args) => {
|
|
997
|
+
const result = await runner.exec([SYSTEMCTL, ...args]);
|
|
998
|
+
if (result.exitCode !== 0) {
|
|
999
|
+
throw new SystemctlError(args.join(" "), result.exitCode, result.stderr);
|
|
1000
|
+
}
|
|
1001
|
+
};
|
|
1002
|
+
var daemonReload = (runner) => run(runner, ["daemon-reload"]);
|
|
1003
|
+
var enableUnit = (runner, name) => run(runner, ["enable", "--", name]);
|
|
1004
|
+
var disableUnit = (runner, name) => run(runner, ["disable", "--", name]);
|
|
1005
|
+
var startUnit = (runner, name) => run(runner, ["start", "--", name]);
|
|
1006
|
+
var stopUnit = (runner, name) => run(runner, ["stop", "--", name]);
|
|
1007
|
+
var restartUnit = (runner, name) => run(runner, ["restart", "--", name]);
|
|
1008
|
+
|
|
917
1009
|
// src/linux/unit-form.ts
|
|
918
1010
|
var DEFAULT_UNIT_DIRECTORY = "/etc/systemd/system";
|
|
919
1011
|
var UNIT_SEARCH_DIRECTORIES = [
|
|
@@ -994,80 +1086,6 @@ var textProblems = (text) => {
|
|
|
994
1086
|
return found;
|
|
995
1087
|
};
|
|
996
1088
|
|
|
997
|
-
// src/linux/systemctl.ts
|
|
998
|
-
var SYSTEMCTL = "systemctl";
|
|
999
|
-
var SHOW_PROPERTIES = [
|
|
1000
|
-
"Id",
|
|
1001
|
-
"LoadState",
|
|
1002
|
-
"ActiveState",
|
|
1003
|
-
"SubState",
|
|
1004
|
-
"UnitFileState",
|
|
1005
|
-
"FragmentPath",
|
|
1006
|
-
"NeedDaemonReload"
|
|
1007
|
-
];
|
|
1008
|
-
|
|
1009
|
-
class SystemctlError extends Error {
|
|
1010
|
-
constructor(command, exitCode, stderr) {
|
|
1011
|
-
super(`systemctl ${command} -> ${String(exitCode)}: ${stderr.trim().slice(0, 300)}`);
|
|
1012
|
-
this.name = "SystemctlError";
|
|
1013
|
-
}
|
|
1014
|
-
}
|
|
1015
|
-
var parseShow = (stdout) => {
|
|
1016
|
-
const fields = new Map;
|
|
1017
|
-
for (const line of stdout.split(`
|
|
1018
|
-
`)) {
|
|
1019
|
-
const at = line.indexOf("=");
|
|
1020
|
-
if (at <= 0)
|
|
1021
|
-
continue;
|
|
1022
|
-
const key = line.slice(0, at);
|
|
1023
|
-
if (!fields.has(key))
|
|
1024
|
-
fields.set(key, line.slice(at + 1));
|
|
1025
|
-
}
|
|
1026
|
-
return fields;
|
|
1027
|
-
};
|
|
1028
|
-
var statusOf = (stdout) => {
|
|
1029
|
-
const fields = parseShow(stdout);
|
|
1030
|
-
const loadState = fields.get("LoadState") ?? "not-found";
|
|
1031
|
-
const unitFileState = fields.get("UnitFileState") ?? "";
|
|
1032
|
-
const fragmentPath = fields.get("FragmentPath") ?? "";
|
|
1033
|
-
const subState = fields.get("SubState") ?? "";
|
|
1034
|
-
return {
|
|
1035
|
-
activeState: fields.get("ActiveState") ?? "inactive",
|
|
1036
|
-
known: loadState !== "not-found" || unitFileState !== "",
|
|
1037
|
-
loadState,
|
|
1038
|
-
needDaemonReload: fields.get("NeedDaemonReload") === "yes",
|
|
1039
|
-
...subState === "" ? {} : { subState },
|
|
1040
|
-
...unitFileState === "" ? {} : { unitFileState },
|
|
1041
|
-
...fragmentPath === "" ? {} : { fragmentPath }
|
|
1042
|
-
};
|
|
1043
|
-
};
|
|
1044
|
-
var showUnit = async (runner, name) => {
|
|
1045
|
-
const result = await runner.exec([
|
|
1046
|
-
SYSTEMCTL,
|
|
1047
|
-
"show",
|
|
1048
|
-
"--no-pager",
|
|
1049
|
-
"-p",
|
|
1050
|
-
SHOW_PROPERTIES.join(","),
|
|
1051
|
-
"--",
|
|
1052
|
-
name
|
|
1053
|
-
]);
|
|
1054
|
-
if (result.exitCode !== 0)
|
|
1055
|
-
throw new SystemctlError(`show ${name}`, result.exitCode, result.stderr);
|
|
1056
|
-
return statusOf(result.stdout);
|
|
1057
|
-
};
|
|
1058
|
-
var run = async (runner, args) => {
|
|
1059
|
-
const result = await runner.exec([SYSTEMCTL, ...args]);
|
|
1060
|
-
if (result.exitCode !== 0) {
|
|
1061
|
-
throw new SystemctlError(args.join(" "), result.exitCode, result.stderr);
|
|
1062
|
-
}
|
|
1063
|
-
};
|
|
1064
|
-
var daemonReload = (runner) => run(runner, ["daemon-reload"]);
|
|
1065
|
-
var enableUnit = (runner, name) => run(runner, ["enable", "--", name]);
|
|
1066
|
-
var disableUnit = (runner, name) => run(runner, ["disable", "--", name]);
|
|
1067
|
-
var startUnit = (runner, name) => run(runner, ["start", "--", name]);
|
|
1068
|
-
var stopUnit = (runner, name) => run(runner, ["stop", "--", name]);
|
|
1069
|
-
var restartUnit = (runner, name) => run(runner, ["restart", "--", name]);
|
|
1070
|
-
|
|
1071
1089
|
// src/linux/unit-settle.ts
|
|
1072
1090
|
var refuse4 = (name, message) => new Error(`Systemd.Unit ${name}: ${message}`);
|
|
1073
1091
|
var attributesOf = (props, unitSha256, status) => ({
|
|
@@ -1123,21 +1141,13 @@ var settle = async (runner, props, step) => {
|
|
|
1123
1141
|
return settled;
|
|
1124
1142
|
};
|
|
1125
1143
|
|
|
1126
|
-
// src/linux/unit-
|
|
1127
|
-
var encoder2 = new TextEncoder;
|
|
1144
|
+
// src/linux/unit-preflight.ts
|
|
1128
1145
|
var decoder2 = new TextDecoder;
|
|
1129
1146
|
var assertValid = (props, expect) => {
|
|
1130
1147
|
const found = unitProblems(props, expect);
|
|
1131
1148
|
if (found.length > 0)
|
|
1132
1149
|
throw refuse4(props.name, found.join("; "));
|
|
1133
1150
|
};
|
|
1134
|
-
var readUnit = async (runner, props) => {
|
|
1135
|
-
const bytes = await runner.readFile(unitPathFor(props));
|
|
1136
|
-
const status = await showUnit(runner, props.name);
|
|
1137
|
-
if (bytes === undefined && !status.known)
|
|
1138
|
-
return;
|
|
1139
|
-
return attributesOf(props, bytes === undefined ? "" : digestOf2(decoder2.decode(bytes)), status);
|
|
1140
|
-
};
|
|
1141
1151
|
var assertMayWrite = (runner, name) => {
|
|
1142
1152
|
if (canActAsRoot(runner))
|
|
1143
1153
|
return;
|
|
@@ -1148,10 +1158,40 @@ var assertUsable = (props, status) => {
|
|
|
1148
1158
|
throw refuse4(props.name, "is masked. If that is stale, `systemctl unmask` it deliberately, then redeploy. Nothing " + "was written.");
|
|
1149
1159
|
}
|
|
1150
1160
|
};
|
|
1161
|
+
var assertUnclaimed = async (runner, props) => {
|
|
1162
|
+
const path = unitPathFor(props);
|
|
1163
|
+
const bytes = await runner.readFile(path);
|
|
1164
|
+
const live = bytes === undefined ? undefined : digestOf2(decoder2.decode(bytes));
|
|
1165
|
+
if (live !== undefined && live !== digestOf2(unitText(props))) {
|
|
1166
|
+
throw refuse4(props.name, `${path} is already on this host and is not this resource. Remove it, or declare it as a ` + "new resource and deploy with --adopt.");
|
|
1167
|
+
}
|
|
1168
|
+
};
|
|
1169
|
+
var assertRenameTarget = async (runner, old, name) => {
|
|
1170
|
+
assertMayWrite(runner, old.name);
|
|
1171
|
+
assertMayWrite(runner, name);
|
|
1172
|
+
assertUsable({ name }, await showUnit(runner, name));
|
|
1173
|
+
};
|
|
1174
|
+
var assertReplaceable = async (runner, next, old, expect) => {
|
|
1175
|
+
assertValid(next, expect);
|
|
1176
|
+
await assertRenameTarget(runner, old, next.name);
|
|
1177
|
+
await assertUnclaimed(runner, next);
|
|
1178
|
+
await runner.checkWrite?.(unitPathFor(next), UNIT_WRITE);
|
|
1179
|
+
};
|
|
1180
|
+
|
|
1181
|
+
// src/linux/unit-lifecycle.ts
|
|
1182
|
+
var encoder2 = new TextEncoder;
|
|
1183
|
+
var decoder3 = new TextDecoder;
|
|
1184
|
+
var readUnit = async (runner, props) => {
|
|
1185
|
+
const bytes = await runner.readFile(unitPathFor(props));
|
|
1186
|
+
const status = await showUnit(runner, props.name);
|
|
1187
|
+
if (bytes === undefined && !status.known)
|
|
1188
|
+
return;
|
|
1189
|
+
return attributesOf(props, bytes === undefined ? "" : digestOf2(decoder3.decode(bytes)), status);
|
|
1190
|
+
};
|
|
1151
1191
|
var diffUnit = async (runner, news, output, expect) => {
|
|
1152
1192
|
assertValid(news, expect);
|
|
1153
1193
|
if (news.name !== output.name || unitPathFor(news) !== output.unitPath) {
|
|
1154
|
-
|
|
1194
|
+
await assertReplaceable(runner, news, output, expect);
|
|
1155
1195
|
return { action: "replace", deleteFirst: true };
|
|
1156
1196
|
}
|
|
1157
1197
|
const desired2 = digestOf2(unitText(news));
|
|
@@ -1183,8 +1223,10 @@ var reconcileUnit = async (runner, props, output, adopt = false, expect) => {
|
|
|
1183
1223
|
const text = unitText(props);
|
|
1184
1224
|
const desired2 = digestOf2(text);
|
|
1185
1225
|
const before = await runner.readFile(path);
|
|
1186
|
-
const liveSha = before === undefined ? undefined : digestOf2(
|
|
1226
|
+
const liveSha = before === undefined ? undefined : digestOf2(decoder3.decode(before));
|
|
1187
1227
|
const moved = output !== undefined && (output.name !== props.name || output.unitPath !== path);
|
|
1228
|
+
if (moved && output !== undefined)
|
|
1229
|
+
await assertReplaceable(runner, props, output, expect);
|
|
1188
1230
|
const prior = moved ? undefined : output;
|
|
1189
1231
|
if (prior === undefined && !(adopt && output === undefined) && liveSha !== undefined && liveSha !== desired2) {
|
|
1190
1232
|
throw refuse4(props.name, `${path} is already on this host and is not this resource. Remove it, or declare it as a ` + "new resource and deploy with --adopt.");
|
|
@@ -1232,7 +1274,12 @@ var diffHandler = (runner, instanceId, news, output, expect) => {
|
|
|
1232
1274
|
if (isResolved3(news))
|
|
1233
1275
|
return lift(() => diffUnit(runner, news, output, expect));
|
|
1234
1276
|
const name = resolvedString(news, "name");
|
|
1235
|
-
|
|
1277
|
+
if (name === undefined || name === output.name)
|
|
1278
|
+
return Effect8.succeed(undefined);
|
|
1279
|
+
return lift(async () => {
|
|
1280
|
+
await assertRenameTarget(runner, output, name);
|
|
1281
|
+
return { action: "replace", deleteFirst: true };
|
|
1282
|
+
});
|
|
1236
1283
|
};
|
|
1237
1284
|
var reconcileHandler = (runner, args, expect) => Effect8.flatMap(adoptsAtApply(args), (adopt) => lift(() => reconcileUnit(runner, args.news, args.output, adopt, expect)));
|
|
1238
1285
|
var deleteHandler = (runner, output) => lift(() => deleteUnit(runner, output));
|
|
@@ -1550,5 +1597,5 @@ export {
|
|
|
1550
1597
|
sshRunner
|
|
1551
1598
|
};
|
|
1552
1599
|
|
|
1553
|
-
//# debugId=
|
|
1600
|
+
//# debugId=999A0F25FFF74BEF64756E2164756E21
|
|
1554
1601
|
//# sourceMappingURL=index.js.map
|