CH-J Toolsspráva serverů přes SSH
Menu
Vydané zdroje

CH-J Tools

Procházejte adresáře a soubory konkrétního vydání aplikace.

Stáhnout zdroje ZIP
CH-J / Server Manager 406 souborů
core-20261024T135205Z-7c487d719de2 Přidáno: · Sestaveno:
disksVmIntegration.test.js Přidáno: Datum není evidováno 22,8 KB · 290 řádků
Kód Stáhnout soubor
1"use strict";
3// chj.disks against ONE isolated Linux test VM over real SSH (key login, sudo with password), through the real
4// DiskService/PrivilegedExecutor/remoteDisks.sh. Read-only checks always run. Destructive checks run only with
5// CHJ_DISKS_WRITE=1 and only on loop devices backed by /var/tmp/chj-disks-test/chjtest-<run>.img created by this test
6// (verified before every change); the system disk is never a target. Proxmox hosts get the read-only part only.
7// Environment as test/linuxVmIntegration.test.js: CHJ_VM_HOST, CHJ_VM_FINGERPRINT, CHJ_VM_KEY, CHJ_VM_USER (ai),
8// CHJ_VM_SUDO_PASSWORD or CHJ_VM_SUDO_PASSWORD_FILE + CHJ_VM_SUDO_PASSWORD_LINE (read at runtime, never printed).
9const test = require("node:test");
10const assert = require("node:assert/strict");
11const crypto = require("node:crypto");
12const fs = require("node:fs");
13const path = require("node:path");
14const { SessionManager } = require("../src/main/sessions/sessionManager");
15const { PrivilegedExecutor } = require("../src/main/sessions/privilegedExec");
16const { DiskService } = require("../src/main/disks/diskService");
18const env = process.env;
19const enabled = Boolean(env.CHJ_VM_HOST && env.CHJ_VM_FINGERPRINT && env.CHJ_VM_KEY && (env.CHJ_VM_SUDO_PASSWORD || env.CHJ_VM_SUDO_PASSWORD_FILE));
20const skip = enabled ? false : "Set CHJ_VM_* to run against an isolated Linux test VM.";
21const sudoPassword = !enabled ? "" : env.CHJ_VM_SUDO_PASSWORD
22 || fs.readFileSync(env.CHJ_VM_SUDO_PASSWORD_FILE, "utf8").split("\n")[Number(env.CHJ_VM_SUDO_PASSWORD_LINE || 1) - 1].split("=").slice(1).join("=").trim();
23const FIXTURE = fs.readFileSync(path.join(__dirname, "fixtures", "disks", "loopFixture.sh"), "utf8");
24const MiB = 1048576;
25const RUN = crypto.randomBytes(4).toString("hex");
26const TEST_IMAGE = /^\/var\/tmp\/chj-disks-test\/chjtest-[a-z0-9]+\.img$/;
28test("chj.disks on an isolated Linux test VM", { skip, timeout: 1800000 }, async (t) => {
29 const hostKeys = new Map();
30 const profileService = {
31 get: () => ({ id: "vm", label: "vm", host: env.CHJ_VM_HOST, port: 22, username: env.CHJ_VM_USER || "ai", authMethod: "privateKey", privateKeyPath: env.CHJ_VM_KEY }),
32 getHostKey: (h, p) => hostKeys.get(`${h}:${p}`) || null, trustHostKey: (h, p, fp) => hostKeys.set(`${h}:${p}`, fp), getStoredPassword: () => "", markUsed() {}
33 };
34 const manager = new SessionManager({ profileService });
35 const connect = async () => {
36 try { await manager.connect({ sessionId: "vm", profileId: "vm" }); }
37 catch (error) {
38 if (error.code !== "HOST_KEY_UNKNOWN") throw error;
39 assert.equal(`SHA256:${Buffer.from(error.fingerprint, "hex").toString("base64").replace(/=+$/, "")}`, env.CHJ_VM_FINGERPRINT, "documented host key");
40 manager.trustPending({ sessionId: "vm", profileId: "vm", fingerprint: error.fingerprint });
41 await manager.connect({ sessionId: "vm", profileId: "vm" });
42 }
43 };
44 await connect();
45 const executor = new PrivilegedExecutor({ sessionManager: manager });
46 const disks = new DiskService({ sessionManager: manager, privilegedExecutor: executor });
47 const auth = { sudoPassword };
48 const fixture = async (...args) => {
49 const run = await executor.run("vm", { script: FIXTURE, args, privileged: true, sudoPassword, purpose: "test.disks.fixture", timeoutMs: 300000, errorPrefix: "FIXTURE_" });
50 return Object.fromEntries(run.stdout.split("\n").filter((line) => line.includes("=")).map((line) => [line.slice(0, line.indexOf("=")), line.slice(line.indexOf("=") + 1)]));
51 };
52 const fixtureText = async (...args) => (await executor.run("vm", { script: FIXTURE, args, privileged: true, sudoPassword, purpose: "test.disks.fixture", timeoutMs: 300000, errorPrefix: "FIXTURE_" })).stdout.trim();
53 let wrote = false;
54 t.after(async () => {
55 if (wrote) { try { t.diagnostic(`cleanup: ${JSON.stringify(await fixture("cleanup", RUN))}`); } catch (error) { t.diagnostic(`cleanup failed: ${error.code} ${error.message}`); } }
56 await manager.disconnectAll("test");
57 });
59 // ---------- read-only ----------
60 let rootInventory;
61 await t.test("read-only inventory without root", async () => {
62 const inventory = await disks.inspect("vm");
63 assert.equal(inventory.supported, true); assert.equal(inventory.platform, "linux"); assert.equal(inventory.privileged, false);
64 assert.ok(inventory.disks.length >= 1, "at least one disk");
65 const system = inventory.disks.find((disk) => disk.systemRoles.includes("mount:/"));
66 assert.ok(system, "the disk holding / is identified without root");
67 t.diagnostic(`${inventory.os} | kernel ${inventory.kernel} | systemd ${inventory.systemd} | lsblk ${inventory.sources.lsblk} | disks ${inventory.disks.map((disk) => `${disk.path}:${disk.mediaType}/${disk.transport}/${disk.table?.type}`).join(", ")}`);
68 });
69 await t.test("privileged inventory, tools and suggestions", async () => {
70 rootInventory = await disks.inspect("vm", { privileged: true, ...auth });
71 assert.equal(rootInventory.privileged, true);
72 const system = rootInventory.disks.find((disk) => disk.systemRoles.includes("mount:/"));
73 assert.ok(system.table?.hash, "partition table read with sfdisk");
74 const root = rootInventory.nodes.find((node) => node.mountpoints.includes("/"));
75 assert.ok(root.fstype && root.uuid, "root file system type and UUID");
76 assert.ok(root.usage?.size > 0 && root.usage.used > 0, "root usage from df");
77 for (const part of system.partitions) if (part.kernel) assert.ok(part.uuid, `PARTUUID of ${part.path}`);
78 const tools = ["sfdisk", "partx", "growpart", "resize2fs", "xfs_growfs", "btrfs", "pvresize", "cryptsetup", "smartctl", "parted", "systemd-run", "setsid"].map((name) => `${name}:${rootInventory.tools[name] ? "yes" : "no"}`).join(" ");
79 t.diagnostic(`tools ${tools}`);
80 t.diagnostic(`root ${root.path} ${root.fstype} on ${system.path}; suggestions: ${rootInventory.suggestions.map((entry) => `${entry.id}=${entry.available ? "available" : entry.blocked.code}`).join(", ") || "none"}`);
81 for (const suggestion of rootInventory.suggestions.filter((entry) => entry.target.startsWith(system.path))) assert.ok(suggestion.available || suggestion.blocked, "system disk suggestions are explicit");
82 });
83 await t.test("system disk is protected against destructive plans", async () => {
84 const system = rootInventory.disks.find((disk) => disk.systemRoles.includes("mount:/"));
85 const rootPart = system.partitions.find((part) => part.mountpoints.includes("/"));
86 if (rootPart) {
87 await assert.rejects(() => disks.plan("vm", { operations: [{ type: "delete-partition", disk: system.name, number: rootPart.number }], ...auth }), { code: "DISKS_DEVICE_IN_USE" });
88 await assert.rejects(() => disks.plan("vm", { operations: [{ type: "unmount", device: rootPart.path }], ...auth }), { code: "DISKS_SYSTEM_MOUNT" });
89 if (rootPart.fstype === "xfs") await assert.rejects(() => disks.plan("vm", { operations: [{ type: "resize-partition", disk: system.name, number: rootPart.number, size: Math.floor(rootPart.size / 2) }], ...auth }), { code: "DISKS_XFS_SHRINK_UNSUPPORTED" });
90 else await assert.rejects(() => disks.plan("vm", { operations: [{ type: "resize-partition", disk: system.name, number: rootPart.number, size: Math.floor(rootPart.size / 2) }], ...auth }), { code: /^DISKS_(SHRINK_REQUIRES_UNMOUNT|PROTECTED_DEVICE)$/ });
91 }
92 await assert.rejects(() => disks.plan("vm", { operations: [{ type: "create-table", disk: system.name, label: "gpt" }], ...auth }), { code: /^DISKS_(DEVICE_IN_USE|PROTECTED_DEVICE)$/ });
93 const after = await disks.inspect("vm", { privileged: true, ...auth });
94 assert.equal(after.disks.find((disk) => disk.name === system.name).table.hash, system.table.hash, "the system disk is unchanged");
95 });
96 await t.test("wrong sudo password and SMART on a virtual disk", async () => {
97 executor.forgetPassword("vm");
98 await assert.rejects(() => disks.inspect("vm", { privileged: true, sudoPassword: "definitely-wrong-password" }), { code: "SUDO_PASSWORD_INCORRECT" });
99 const system = rootInventory.disks.find((disk) => disk.systemRoles.includes("mount:/"));
100 const smart = await disks.smart("vm", { device: system.path, ...auth });
101 t.diagnostic(`SMART ${system.path}: available=${smart.available} reason=${smart.reason} tool=${smart.tool}`);
102 assert.equal(typeof smart.available, "boolean");
103 });
105 if (env.CHJ_DISKS_WRITE !== "1") { t.diagnostic("CHJ_DISKS_WRITE is not 1: destructive loop-device tests skipped"); return; }
106 if (rootInventory.pve) { t.diagnostic("Proxmox VE host: destructive tests are never run on Proxmox hosts (read-only only)"); return; }
107 if (!rootInventory.tools.losetup && !rootInventory.tools.sfdisk) { t.diagnostic("losetup/sfdisk missing: destructive tests BLOCKED"); return; }
109 // ---------- destructive, dedicated loop images only ----------
110 const has = (tool) => Boolean(rootInventory.tools[tool]);
111 const fresh = () => disks.inspect("vm", { privileged: true, ...auth });
112 const assertTestDisk = (inventory, name) => {
113 const disk = inventory.disks.find((entry) => entry.name === name);
114 assert.ok(disk, `${name} present`); assert.equal(disk.kind, "loop"); assert.match(disk.loopBacking || "", TEST_IMAGE, "target is a CH-J test image");
115 assert.equal(disk.system, false); return disk;
116 };
117 const runJob = async (plan, { disconnect = false } = {}) => {
118 for (const view of plan.before) { if (!/^\/dev\/loop\d+$/.test(view.path)) throw new Error(`refusing to apply a plan touching ${view.path}`); assertTestDisk(await fresh(), view.name); }
119 const started = await disks.apply("vm", { planId: plan.planId, confirm: true, ...auth, rememberSudo: true });
120 if (disconnect) {
121 await manager.disconnect("vm", "test: simulated SSH outage");
122 await new Promise((resolve) => setTimeout(resolve, 1500));
123 await connect();
124 }
125 let status;
126 for (let i = 0; i < 400; i += 1) {
127 status = await disks.jobStatus("vm", { jobId: started.jobId, offset: 0, ...auth });
128 if (status.outcome !== "running") break;
129 await new Promise((resolve) => setTimeout(resolve, 750));
130 }
131 if (status.outcome !== "succeeded") t.diagnostic(`job ${started.jobId} ${status.outcome}: ${JSON.stringify(status.error)}\n${status.log.slice(-3000)}`);
132 return { ...status, started };
133 };
134 const plan = (operations) => disks.plan("vm", { operations, ...auth });
135 const mountpoint = (name) => `/mnt/chj-disks-test-${RUN}${name}`;
137 wrote = true;
138 const image = await fixture("create", RUN, "1024");
139 const loop = image.dev.slice(5);
140 t.diagnostic(`test image ${image.img} on ${image.dev}`);
141 let sums;
143 await t.test("GPT + ext4 partition + mount + data", async () => {
144 let inventory = await fresh(); assertTestDisk(inventory, loop);
145 const job = await runJob(await plan([{ type: "create-table", disk: loop, label: "gpt" }, { type: "create-partition", disk: loop, start: 2048, size: 512 * MiB / 512, fs: "ext4", label: "chjtest" }, { type: "mount", device: `${image.dev}p1`, mountpoint: mountpoint("a") }]));
146 assert.equal(job.outcome, "succeeded"); assert.match(job.log, /NOT a backup of the data|has no partition table/);
147 inventory = await fresh();
148 const part = assertTestDisk(inventory, loop).partitions[0];
149 assert.equal(part.fstype, "ext4"); assert.equal(part.label, "chjtest"); assert.deepEqual(part.mountpoints, [mountpoint("a")]);
150 sums = await fixtureText("write", mountpoint("a"));
151 });
153 await t.test("virtual disk enlarged: detected, auto-expanded online (ext4 mounted), data intact", async () => {
154 await fixture("grow", image.dev, "2048");
155 const inventory = await fresh();
156 const disk = assertTestDisk(inventory, loop);
157 // Newer sfdisk reports the old last LBA (CH-J adds gpt-relocate); sfdisk 2.34 already reports the new one and
158 // moves the backup header itself when writing. Either way the whole new space must be usable.
159 const relocate = disk.table.gptBackupMisplaced;
160 if (!relocate) { assert.equal(disk.table.lastUsableNow, disk.table.lastUsable); t.diagnostic("sfdisk reports the enlarged GPT area already (relocation on write)"); }
161 const suggestion = inventory.suggestions.find((entry) => entry.id === `partition:${loop}:1`);
162 assert.ok(suggestion?.available, "auto-expand offered"); assert.ok(suggestion.freeBytes > 1400 * MiB);
163 const expand = await disks.autoExpand("vm", { suggestionId: suggestion.id, ...auth });
164 assert.deepEqual(expand.steps.map((step) => step.name).filter((name) => !/^verify|^backup/.test(name)), [...(relocate ? ["gpt-relocate"] : []), "resize-part", "grow-fs"]);
165 const job = await runJob(expand);
166 assert.equal(job.outcome, "succeeded");
167 const after = assertTestDisk(await fresh(), loop);
168 const part = after.partitions[0];
169 assert.ok(part.bytes > 1900 * MiB, "partition grew"); assert.ok(part.usage.size > 1800 * MiB, "mounted ext4 grew online");
170 assert.equal(after.table.gptBackupMisplaced, false);
171 assert.equal(await fixtureText("sums", mountpoint("a")), sums, "files unchanged");
172 });
174 await t.test("offline ext4 shrink (unmount, e2fsck, minimum + reserve, fs before partition) with verified data", async () => {
175 await assert.rejects(() => plan([{ type: "resize-partition", disk: loop, number: 1, size: 768 * MiB / 512 }]), { code: "DISKS_SHRINK_REQUIRES_UNMOUNT" });
176 await assert.rejects(() => plan([{ type: "unmount", device: `${image.dev}p1` }, { type: "resize-partition", disk: loop, number: 1, size: 64 * MiB / 512 }]), { code: "DISKS_SIZE_TOO_SMALL" });
177 const job = await runJob(await plan([{ type: "unmount", device: `${image.dev}p1` }, { type: "resize-partition", disk: loop, number: 1, size: 768 * MiB / 512 }, { type: "mount", device: `${image.dev}p1`, mountpoint: mountpoint("a") }]));
178 assert.equal(job.outcome, "succeeded");
179 assert.match(job.log, /consistency check before shrinking/); assert.match(job.log, /verified: e2fsck -n clean/);
180 const part = assertTestDisk(await fresh(), loop).partitions[0];
181 assert.equal(part.bytes, 768 * MiB); assert.equal(await fixtureText("sums", mountpoint("a")), sums, "files unchanged after shrink");
182 });
184 await t.test("stale plan: a layout change between review and apply is refused before any change", async () => {
185 const reviewed = await plan([{ type: "create-partition", disk: loop, start: 2048 + 768 * MiB / 512, size: 64 * MiB / 512, fs: "ext4" }]);
186 await fixture("tamper", image.dev);
187 await assert.rejects(() => disks.apply("vm", { planId: reviewed.planId, confirm: true, ...auth }), { code: "DISKS_PLAN_STALE" });
188 const disk = assertTestDisk(await fresh(), loop);
189 assert.equal(disk.partitions.length, 2, "only the out-of-band partition exists; the plan changed nothing");
190 const extra = disk.partitions.find((part) => part.number === 2);
191 assert.equal((await runJob(await plan([{ type: "delete-partition", disk: loop, number: extra.number }]))).outcome, "succeeded");
192 });
194 if (has("mkfs.xfs") && has("xfs_growfs")) {
195 await t.test("XFS: create, online grow, shrink refused", async () => {
196 const start = 2048 + 768 * MiB / 512;
197 assert.equal((await runJob(await plan([{ type: "create-partition", disk: loop, start, size: 320 * MiB / 512, fs: "xfs" }, { type: "mount", device: `${image.dev}p2`, mountpoint: mountpoint("x") }]))).outcome, "succeeded");
198 const grow = await plan([{ type: "resize-partition", disk: loop, number: 2, size: 512 * MiB / 512 }]);
199 assert.deepEqual(grow.steps.map((step) => step.name).filter((name) => !/^verify|^backup/.test(name)), ["resize-part", "grow-fs"]);
200 assert.equal((await runJob(grow)).outcome, "succeeded");
201 try {
202 const part = assertTestDisk(await fresh(), loop).partitions.find((entry) => entry.number === 2);
203 assert.equal(part.bytes, 512 * MiB);
204 assert.equal(part.fsSize, 512 * MiB, "XFS data section grew online to the partition size (xfs_info)");
205 await assert.rejects(() => plan([{ type: "resize-partition", disk: loop, number: 2, size: 320 * MiB / 512 }]), { code: "DISKS_XFS_SHRINK_UNSUPPORTED" });
206 } finally {
207 assert.equal((await runJob(await plan([{ type: "unmount", device: `${image.dev}p2` }, { type: "delete-partition", disk: loop, number: 2 }]))).outcome, "succeeded");
208 }
209 });
210 } else t.diagnostic("XFS tools missing: XFS scenario UNSUPPORTED on this VM");
212 if (has("pvresize") && has("lvextend")) {
213 await t.test("LVM on a partition: partition -> PV -> LV -> ext4 auto expansion", async () => {
214 const start = 2048 + 768 * MiB / 512;
215 assert.equal((await runJob(await plan([{ type: "create-partition", disk: loop, start, size: 300 * MiB / 512, fs: "none" }]))).outcome, "succeeded");
216 const vg = `chjtest${RUN}`;
217 await fixture("lvm", `${image.dev}p2`, vg);
218 const inventory = await fresh();
219 const suggestion = inventory.suggestions.find((entry) => entry.id === `partition:${loop}:2`);
220 assert.ok(suggestion?.available, `auto-expand offered (${suggestion?.blocked?.code || ""})`);
221 const expand = await disks.autoExpand("vm", { suggestionId: suggestion.id, ...auth });
222 assert.deepEqual(expand.steps.map((step) => step.name).filter((name) => !/^verify|^backup/.test(name)), ["resize-part", "pv-resize", "lv-extend", "grow-fs"]);
223 assert.equal((await runJob(expand)).outcome, "succeeded");
224 const after = await fresh();
225 const lv = after.lvm.lvs.find((entry) => entry.vg === vg);
226 assert.ok(lv.size > 1000 * MiB, `LV grew (${lv.size})`);
227 const node = after.nodes.find((entry) => entry.displayPath === `/dev/${vg}/data`);
228 assert.ok(node.fsSize > 1000 * MiB, "ext4 inside the LV grew");
229 });
230 } else t.diagnostic("LVM tools missing: LVM scenario UNSUPPORTED on this VM");
232 await t.test("SSH outage during a job: the job continues, status is recovered, nothing runs twice", async () => {
233 const reviewed = await plan([{ type: "unmount", device: `${image.dev}p1` }, { type: "check", device: `${image.dev}p1` }, { type: "mount", device: `${image.dev}p1`, mountpoint: mountpoint("a") }]);
234 const job = await runJob(reviewed, { disconnect: true });
235 assert.equal(job.outcome, "succeeded", "finished on the server despite the dropped SSH connection");
236 const { jobs } = await disks.jobs("vm", auth);
237 assert.ok(jobs.some((entry) => entry.id === job.started.jobId && entry.outcome === "succeeded"), "job listed after reconnect");
238 await assert.rejects(() => disks.apply("vm", { planId: reviewed.planId, confirm: true, ...auth }), { code: "DISKS_PLAN_EXPIRED" });
239 await assert.rejects(() => executor.run("vm", { script: disks.script, args: ["job-start", job.started.jobId], privileged: true, sudoPassword, frames: [{ tag: "PLAN", data: "verify-size /dev/null 0\n" }, { tag: "HELPER", data: "#" }, { tag: "TITLE", data: "x" }], errorPrefix: "DISKS_" }), { code: "DISKS_JOB_EXISTS" });
240 assert.equal(await fixtureText("sums", mountpoint("a")), sums);
241 });
243 await t.test("a running disk job blocks every other disk job on the server (lock contention)", async () => {
244 const reviewed = await plan([{ type: "set-label", device: `${image.dev}p1`, label: "chjlock" }]);
245 await fixture("hold-lock", RUN);
246 try { await assert.rejects(() => disks.apply("vm", { planId: reviewed.planId, confirm: true, ...auth }), { code: "DISKS_BUSY" }); }
247 finally { await fixture("release-lock", RUN); }
248 const retry = await plan([{ type: "set-label", device: `${image.dev}p1`, label: "chjlock" }]);
249 assert.equal((await runJob(retry)).outcome, "succeeded", "works again once the lock is free");
250 });
252 await t.test("conflicting jobs never overlap on the server", async () => {
253 const first = await plan([{ type: "unmount", device: `${image.dev}p1` }, { type: "check", device: `${image.dev}p1` }]);
254 const second = await plan([{ type: "set-label", device: `${image.dev}p1`, label: "chjb" }]);
255 const a = await disks.apply("vm", { planId: first.planId, confirm: true, ...auth });
256 let b = null; let bError = null;
257 try { b = await disks.apply("vm", { planId: second.planId, confirm: true, ...auth }); } catch (error) { bError = error; }
258 const wait = async (jobId) => { for (let i = 0; i < 200; i += 1) { const status = await disks.jobStatus("vm", { jobId, ...auth }); if (status.outcome !== "running") return status; await new Promise((resolve) => setTimeout(resolve, 500)); } return null; };
259 const sa = await wait(a.jobId); const sb = b ? await wait(b.jobId) : null;
260 t.diagnostic(`first ${sa.outcome}; second ${bError ? bError.code : `${sb.outcome} ${sb.error?.code || ""}`}`);
261 if (bError) assert.ok(["DISKS_BUSY", "DISKS_PLAN_STALE"].includes(bError.code));
262 else if (sb.outcome === "failed") assert.match(sb.error.code, /DISKS_(BUSY|DEVICE_IN_USE|LAYOUT_CHANGED)/);
263 else assert.ok(Date.parse(sb.started || sb.created) >= Date.parse(sa.finished), "second job started only after the first finished");
264 });
266 if (has("cryptsetup")) {
267 await t.test("LUKS with ext4: keyring mapping is refused, dm-crypt mapping grows disk -> partition -> LUKS -> ext4", async () => {
268 const second = await fixture("create", `${RUN}l`, "512");
269 const name = second.dev.slice(5);
270 assert.equal((await runJob(await plan([{ type: "create-table", disk: name, label: "gpt" }, { type: "create-partition", disk: name, start: 2048, size: 128 * MiB / 512, fs: "none" }]))).outcome, "succeeded");
271 const luks = await fixture("luks", `${second.dev}p1`, `chjtest${RUN}k`, "dm-crypt");
272 t.diagnostic(`LUKS key location ${luks.keylocation}`);
273 const inventory = await fresh();
274 const suggestion = inventory.suggestions.find((entry) => entry.id === `partition:${name}:1`);
275 if (/keyring/.test(luks.keylocation)) { assert.equal(suggestion.blocked.code, "DISKS_LUKS_PASSPHRASE_REQUIRED"); return; }
276 assert.ok(suggestion?.available, `auto-expand offered (${suggestion?.blocked?.code || ""})`);
277 const expand = await disks.autoExpand("vm", { suggestionId: suggestion.id, ...auth });
278 assert.deepEqual(expand.steps.map((step) => step.name).filter((name) => !/^verify|^backup/.test(name)), ["resize-part", "luks-resize", "grow-fs"]);
279 assert.equal((await runJob(expand)).outcome, "succeeded");
280 const map = (await fresh()).nodes.find((node) => node.path === luks.map);
281 assert.ok(map.size > 400 * MiB && map.fsSize > 400 * MiB, "mapping and ext4 grew");
282 const keyring = await fixture("create", `${RUN}m`, "256");
283 assert.equal((await runJob(await plan([{ type: "create-table", disk: keyring.dev.slice(5), label: "gpt" }, { type: "create-partition", disk: keyring.dev.slice(5), start: 2048, size: 64 * MiB / 512, fs: "none" }]))).outcome, "succeeded");
284 const second2 = await fixture("luks", `${keyring.dev}p1`, `chjtest${RUN}n`, "keyring");
285 const blocked = (await fresh()).suggestions.find((entry) => entry.id === `partition:${keyring.dev.slice(5)}:1`);
286 if (/keyring/.test(second2.keylocation)) assert.equal(blocked.blocked.code, "DISKS_LUKS_PASSPHRASE_REQUIRED");
287 else t.diagnostic(`cryptsetup used ${second2.keylocation} even without --disable-keyring`);
288 });
289 } else t.diagnostic("cryptsetup missing: LUKS scenario UNSUPPORTED on this VM");
290});
SHA-256e7be9a72072595f412df939080d381ce0194ef1e2b92f9ec6b78bbaca6005955

CH-J Proprietary Software License 1.14 · Zdroje jsou zveřejněny pod CH-J Proprietary Software License 1.14. Jejich dostupnost nemění licenční podmínky ani neposkytuje další oprávnění.

SHA-256 archivu476ed8aef0f1e96a5acdf223127b6692acdf4492eeaf5d0816b6e2a634fbbda6
Soubory