"use strict"; // Builds synthetic remoteDisks.sh "inspect" output for chj.disks unit tests (no real server data). const SECTION = (name) => `\x1eCHJ_SECTION=${name}\n`; const ALL_TOOLS = ["lsblk", "blkid", "findmnt", "sfdisk", "partx", "blockdev", "udevadm", "wipefs", "e2fsck", "resize2fs", "dumpe2fs", "e2label", "mkfs.ext4", "xfs_growfs", "xfs_repair", "xfs_admin", "xfs_info", "mkfs.xfs", "btrfs", "mkfs.btrfs", "fsck.vfat", "fatlabel", "mkfs.vfat", "ntfsresize", "ntfslabel", "mkfs.ntfs", "mkswap", "swaplabel", "pvs", "vgs", "lvs", "pvresize", "lvextend", "lvreduce", "cryptsetup", "systemd-run", "setsid", "base64", "sha256sum", "mount", "umount"]; function inspectOutput(spec = {}) { const uid = spec.uid ?? 0; const tools = (spec.tools || ALL_TOOLS).filter((tool) => !(spec.withoutTools || []).includes(tool)); const sys = []; const blkid = []; const mountinfo = []; const df = []; const pt = []; const fsinfo = []; let minor = 0; const devno = (major) => `${major}:${minor++}`; const nodes = []; for (const disk of spec.disks || []) { const lbs = disk.lbs || 512; const holdersOfDisk = disk.holders || []; nodes.push({ name: disk.name, devno: devno(8), sectors512: disk.sectors * lbs / 512, part: "-", start: "-", parent: "-", rota: disk.rota ?? 0, lbs, model: disk.model ?? "QEMU HARDDISK", vendor: disk.vendor ?? "QEMU", holders: holdersOfDisk, slaves: [], sysPath: `/sys/devices/pci0000:00/virtio2/block/${disk.name}`, fs: disk.fs, mount: disk.mount, used: disk.used, backing: disk.backing }); const parts = disk.partitions || []; if (disk.table) { const json = { partitiontable: { label: disk.table, id: "TEST-ID", device: `/dev/${disk.name}`, unit: "sectors", firstlba: disk.table === "gpt" ? 2048 : undefined, lastlba: disk.table === "gpt" ? disk.lastlba ?? disk.sectors - 34 : undefined, sectorsize: lbs, partitions: parts.map((part) => ({ node: `/dev/${disk.name}${/\d$/.test(disk.name) ? "p" : ""}${part.n}`, start: part.start, size: part.size, type: part.type || (disk.table === "gpt" ? "0FC63DAF-8483-4772-8E79-3D69D8477DE4" : "83"), uuid: `PART-${part.n}`, name: part.name, attrs: part.attrs, bootable: part.bootable })) } }; pt.push(`@@disk ${disk.name} ${disk.hash || "a".repeat(64)}`, JSON.stringify(json, null, 2), "@@end"); } for (const part of parts) { const name = `${disk.name}${/\d$/.test(disk.name) ? "p" : ""}${part.n}`; nodes.push({ name, devno: devno(8), sectors512: (part.kernelSize ?? part.size) * lbs / 512, part: part.n, start: part.start * lbs / 512, parent: disk.name, rota: 0, lbs, model: "", vendor: "", holders: part.holders || [], slaves: [], sysPath: `/sys/devices/virtio2/block/${disk.name}/${name}`, fs: part.fs, mount: part.mount, used: part.used, label: part.label, swap: part.swap, fsSize: part.fsSize }); } } for (const dm of spec.dm || []) nodes.push({ name: dm.name, devno: devno(253), sectors512: dm.bytes / 512, part: "-", start: "-", parent: "-", rota: 0, lbs: 512, model: "", vendor: "", holders: dm.holders || [], slaves: dm.slaves || [], dmName: dm.dmName, dmUuid: dm.dmUuid, sysPath: `/sys/devices/virtual/block/${dm.name}`, fs: dm.fs, mount: dm.mount, used: dm.used, fsSize: dm.fsSize }); for (const node of nodes) { sys.push([node.name, node.devno, node.sectors512, 0, 0, node.part, node.start, node.parent, node.rota, node.lbs, node.lbs, node.model, node.vendor, "", "", node.holders.join(",") + (node.holders.length ? "," : ""), node.slaves.join(",") + (node.slaves.length ? "," : ""), node.dmName || "", node.dmUuid || "", node.backing || "", "", node.sysPath, 0].join("|")); const path = node.dmName ? `/dev/mapper/${node.dmName}` : `/dev/${node.name}`; if (node.fs) blkid.push([`DEVNAME=${path}`, `TYPE=${node.fs}`, `UUID=uuid-${node.name}`, node.label ? `LABEL=${node.label}` : ""].filter(Boolean).join("\n")); if (node.mount) { mountinfo.push(`${20 + nodes.indexOf(node)} 1 ${node.devno} / ${node.mount} rw,relatime - ${node.fs} ${path} rw`); const total = Math.floor((node.fsSize ?? node.sectors512 * 512) / 1024); const used = Math.floor((node.used ?? 0) / 1024); df.push(`${path} ${total} ${used} ${total - used} ${Math.round(used / Math.max(1, total) * 100)}% ${node.mount}`); } if (node.fs && /^ext/.test(node.fs) && (node.fsSize || node.used !== undefined)) { const blocks = Math.floor((node.fsSize ?? node.sectors512 * 512) / 4096); fsinfo.push(`@@fs ${path} ${node.fs}`, `Block count: ${blocks}`, `Block size: 4096`, `Free blocks: ${blocks - Math.ceil((node.used ?? 0) / 4096)}`, "Filesystem state: clean"); } if (node.fs === "xfs" && node.fsSize) fsinfo.push(`@@fs ${path} xfs`, `data = bsize=4096 blocks=${Math.floor(node.fsSize / 4096)}, imaxpct=25`); } const swaps = ["Filename Type Size Used Priority", ...nodes.filter((node) => node.swap).map((node) => `/dev/${node.name} partition ${node.sectors512 / 2} 0 -2`)]; let out = "\x1eCHJ_READY\n"; out += SECTION("meta") + `uid=${uid}\nos=Test Linux\nosId=test\nkernel=6.8.0\nsystemd=1\npve=${spec.pve ? 1 : 0}\nmountinfo=1\ntime=1\n`; out += SECTION("tools") + tools.map((tool) => `${tool}=/usr/sbin/${tool}`).join("\n") + "\n"; out += SECTION("versions") + "util-linux=sfdisk from util-linux 2.39.3\n"; out += SECTION("sysblock") + sys.join("\n") + "\n"; out += SECTION("lsblk") + JSON.stringify({ blockdevices: (spec.disks || []).map((disk) => ({ name: disk.name, kname: disk.name, pttype: disk.table || null })) }) + "\n"; out += SECTION("blkid") + blkid.join("\n\n") + "\n"; out += SECTION("mountinfo") + mountinfo.join("\n") + "\n"; out += SECTION("df") + ["Filesystem 1024-blocks Used Available Capacity Mounted on", ...df].join("\n") + "\n"; out += SECTION("swaps") + swaps.join("\n") + "\n"; out += SECTION("mdstat") + "\n"; if (uid === 0) { out += SECTION("pt") + pt.join("\n") + "\n"; if (spec.lvm) { const report = (key, rows) => JSON.stringify({ report: [{ [key]: rows }] }); out += SECTION("lvm") + `@@pvs\n${report("pv", spec.lvm.pvs || [])}\n@@vgs\n${report("vg", spec.lvm.vgs || [])}\n@@lvs\n${report("lv", spec.lvm.lvs || [])}\n`; } if (spec.crypt) out += SECTION("crypt") + Object.entries(spec.crypt).map(([name, status]) => `@@map ${name}\ntype: ${status.type || "LUKS2"}\ncipher: aes-xts-plain64\nkey location: ${status.keyLocation}\ndevice: ${status.device}\n`).join(""); out += SECTION("fsinfo") + fsinfo.join("\n") + "\n"; } return out; } const GiB = 1024 ** 3; const MiB = 1024 ** 2; const sectors = (bytes) => bytes / 512; module.exports = { ALL_TOOLS, GiB, MiB, inspectOutput, sectors };