UNPKG

buildahcker

Version:

Buildahcker is a node.js library to create and run commands in OCI (Open Container Initiative) container images (or docker images), based on Buildah and a hash-based cache. It also contains utilities to easily create a partitioned bootable disk image of a

2,003 lines (1,993 loc) 59.4 kB
import { readlink as Jt, mkdtemp as pe, rm as O, readFile as G, mkdir as D, writeFile as z, lstat as Vt, lchown as me, chmod as ye, constants as K, symlink as be, readdir as Q, open as Wt, rmdir as ge, cp as wt, stat as q, truncate as pt, copyFile as Ee } from "fs/promises"; import { normalize as ft, sep as H, join as R, isAbsolute as ve, basename as $e, dirname as mt, relative as yt, resolve as V, posix as _e } from "path"; import { spawn as Se } from "child_process"; import { Writable as ke } from "stream"; import { homedir as bt } from "os"; import { createHash as S, randomUUID as Ct } from "crypto"; import { read as Re, open as Ie, close as Ce, createWriteStream as jt, createReadStream as gt } from "fs"; import { promisify as Be } from "util"; import { pipeline as Et } from "stream/promises"; class Bt extends ke { #t = []; promise; constructor() { super(), this.promise = new Promise((t) => { this._final = t; }).then(() => { const t = Buffer.concat(this.#t); return this.#t = [], t; }); } _write(t, e, a) { this.#t.push( Buffer.isBuffer(t) ? t : Buffer.from(t, e) ), a(); } } class Fe extends Error { constructor(t, e, a, s) { super( `Command failed: ${t.join(" ")} ${a?.toString("utf8") ?? ""} ${s?.toString("utf8") ?? ""}` ), this.command = t, this.exitCode = e, this.stdout = a, this.stderr = s; } } const L = async (i, { logger: t } = {}, e = {}) => { const a = Se(i[0], i.slice(1), { ...e, stdio: "pipe" }); t?.write(`[${a.pid}]$ ${i.join(" ")} `), t && (a.stdout.pipe(t, { end: !1 }), a.stderr.pipe(t, { end: !1 })); const s = new Bt(); a.stdout.pipe(s); const r = new Bt(); a.stderr.pipe(r), await new Promise((c) => a.on("exit", c)), t?.write(`[${a.pid}] Exit code: ${a.exitCode} `); const n = await s.promise, o = await r.promise; if (a.exitCode !== 0) throw new Fe(i, a.exitCode, n, o); return { stdout: n, stderr: o }; }, qt = async (i, t, e = !1) => { const a = $e(t); if (a === "." || a === ".." || a === "") throw new Error(`Invalid path: ${t}`); return R( await vt(i, mt(t), e), a ); }, vt = async (i, t, e = !1) => { const a = /* @__PURE__ */ new Set(), s = ft(t).split(H); for (let r = 0; r < s.length; r++) { const n = s[r]; if (!n || n === "." || r < 1 && n === ".." && !e) { s.splice(r, 1), r--; continue; } else if (n === "..") { if (r < 1) throw new Error(`Invalid path: ${t}`); s.splice(r - 1, 2), r -= 2; continue; } const o = R(i, ...s.slice(0, r + 1)); if (a.has(o)) throw new Error(`Recursive link in path: ${o}`); a.add(o); try { const c = await Jt(o, "utf8"), h = ft(c).split(H); ve(c) ? (s.splice(0, r + 1, ...h), r = -1) : (s.splice(r, 1, ...h), r--); } catch { } } return R(i, ...s); }; class at { constructor(t, e) { this.options = e, this.#t = t; } #t = null; #e = null; static async from(t, e) { const s = (await L(["buildah", "from", t], e)).stdout.toString("utf8").trim() || null; if (!s) throw new Error(`Failed to create a container from ${t}.`); return new at(s, e); } get name() { const t = this.#t; if (!t) throw new Error("The container has been destroyed!"); return t; } get mountPath() { const t = this.#e; if (!t) throw new Error("The container is not mounted!"); return t; } async mount() { let t = this.#e; if (!t) { const e = this.name; if (t = (await L(["buildah", "mount", e], this.options)).stdout.toString("utf8").trim() || null, !t) throw new Error(`Could not mount container ${e}`); this.#e = t; } return t; } toPathInContainer(t) { const e = yt(this.mountPath, t).split(H); if (e[0] === "..") throw new Error("Path is not in container"); return e.unshift(""), e.join(H); } async tempFolder() { const t = await this.resolve("tmp"), e = await pe(R(t, "buildahcker-")); return { pathInHost: e, pathInContainer: this.toPathInContainer(e), remove: async () => { await O(e, { recursive: !0, force: !0 }); } }; } async resolve(t, e) { return await this.mount(), await vt(this.mountPath, t, e); } async resolveParent(t, e) { return await this.mount(), await qt( this.mountPath, t, e ); } async remove() { const t = this.#t; t && (this.#t = null, this.#e = null, await L(["buildah", "rm", t], this.options)); } async run(t, e = []) { return await L( ["buildah", "run", ...e, "--", this.name, ...t], this.options ); } async commit({ timestamp: t = Date.now() } = {}) { return (await L( [ "buildah", "commit", "--timestamp", `${Math.round(t / 1e3)}`, this.name ], this.options )).stdout.toString("utf8").trim(); } async executeStep(t) { if (Array.isArray(t)) for (const e of t) await this.executeStep(e); else await t(this); } } const Zt = async (i, t, e) => { const a = await at.from(i, e); try { return await t(a); } finally { await a.remove(); } }, W = async (i, t, e) => typeof i == "string" ? await Zt(i, t, e) : await t(i), Ue = async ({ source: i, command: t, buildahRunOptions: e, ...a }) => await W( i, async (s) => await s.run(t, e), a ), Ft = /^[\w-]+$/; class Ae { cachePath; constructor(t) { this.cachePath = V(t); } #t(t, e) { if (!Ft.test(t) || !Ft.test(e)) throw new Error( `Unsafe image id ${JSON.stringify(t)} or operation key ${JSON.stringify(e)}.` ); return R(this.cachePath, t, e); } async getEntry(t, e) { const a = this.#t(t, e); try { return (await G(a, "utf8")).trim() || void 0; } catch { } } async setEntry(t, e, a) { const s = this.#t(t, e); await D(mt(s), { recursive: !0 }), await z(s, a, "utf8"); } } let st; const Ua = () => { if (!st) { const i = R(bt(), ".buildahcker", "cache", "containers"); st = new Ae(i); } return st; }, Aa = async (i, t, e) => { const a = await L( ["buildah", "inspect", ...t ? ["--type", t] : [], "--", i], e ); return JSON.parse(a.stdout.toString("utf8")); }, Ut = async (i, t) => (await L( [ "buildah", "inspect", "--format", "{{.FromImageID}}", "--type", "image", "--", i ], t )).stdout.toString("utf8").trim(); class F { constructor(t, e) { this.options = e, this.#t = t; } #t; static async from(t, e) { const a = t === "scratch" ? t : await Ut(t, e); if (!a) throw new Error(`Could not get information about image ${t}`); return new F(a, e); } clone() { return new F(this.#t, this.options); } get imageId() { return this.#t; } async tag(t, e = {}) { await L(["buildah", "tag", this.#t, t], e); } async #e(t, e) { return await Zt( e ?? this.#t, async (a) => { await a.executeStep(t); const s = await a.commit(this.options?.commitOptions); return e && (this.#t = s), s; }, this.options ); } async executeStep(t) { const e = this.options?.containerCache; if (!e) { await this.#e(t); return; } if (Array.isArray(t)) { for (const n of t) await this.executeStep(n); return; } const a = this.#t; let s; const r = await t.getCacheKey?.(); if (r && (s = await e.getEntry(a, r), s)) try { s = await Ut(s, this.options); } catch { s = void 0; } s || (s = await this.#e(t, a), r && await e.setEntry(a, r, s)), this.#t = s; } } const Qt = (i) => { const t = ft(i).split(H); if (t[t.length - 1] === "" && t.pop(), (t[0] === "." || t[0] === "") && t.shift(), t.length === 0 || t[0] === "..") throw new Error(`Unsafe path: ${i}`); return t.join(H); }, Te = ([i, t]) => [Qt(i), t], xe = (i, t) => i < t ? -1 : i > t ? 1 : 0, Le = ([i], [t]) => xe(i, t), Xt = (i) => new Map(Object.entries(i).map(Te).sort(Le)), Yt = async (i) => { const t = S("sha256"); for (const [e, a] of i) { const s = await a.getHash(); t.update(`${e.length},${s.length},`), t.update(e), t.update(s); } return t.digest(); }, Oe = (i) => (i & K.S_IFMT) === K.S_IFLNK; class te { async getHash() { const t = S("sha256"), e = await this.getAttributes(); t.update( `${e.mode.toString(8)},${e.uid},${e.gid}` ); const a = await this.getContentHash(); return t.update(a), t.digest(); } async writeTo(t) { try { (await Vt(t)).isDirectory() || await O(t); } catch (a) { if (a.code !== "ENOENT") throw a; } await this.writeContentTo(t); const e = await this.getAttributes(); await me(t, e.uid, e.gid), Oe(e.mode) || await ye(t, e.mode); } } class He extends te { #t; async getUnderlyingFile() { return this.#t || (this.#t = await this._getFile()), this.#t; } async getAttributes() { return await (await this.getUnderlyingFile()).getAttributes(); } async getContentHash() { return await (await this.getUnderlyingFile()).getContentHash(); } async writeContentTo(t) { return await (await this.getUnderlyingFile()).writeContentTo(t); } } class Pe extends te { _modeAllow = 65535; _modeMandatory = 0; attributes; constructor(t) { super(), this.attributes = { uid: 0, gid: 0, mode: 420, ...t }; } async getAttributes() { return { ...this.attributes, mode: this.attributes.mode & this._modeAllow | this._modeMandatory }; } } class $t extends Pe { #t; #e; async getContent() { return this.#t || (this.#t = await this._getContent()), this.#t; } async getContentHash() { return this.#e || (this.#e = await this._getContentHash()), this.#e; } } class ee extends $t { constructor(t) { super(t), this._modeAllow = ~K.S_IFMT, this._modeMandatory = K.S_IFREG; } async _getContentHash() { const t = S("sha256"); return t.update(await this.getContent()), t.digest(); } async writeContentTo(t) { await z(t, await this.getContent()); } } class ae extends $t { constructor(t) { super(t), this._modeAllow = 0, this._modeMandatory = K.S_IFLNK | 511; } async _getContentHash() { const t = S("sha256"); return t.update(await this.getContent()), t.digest(); } async writeContentTo(t) { await be(await this.getContent(), t); } } class ie extends $t { constructor(t) { super({ mode: 493, ...t }), this._modeAllow = ~K.S_IFMT, this._modeMandatory = K.S_IFDIR; } async _getContent() { return Xt(await this._getDirectoryContent()); } async _getContentHash() { return await Yt(await this.getContent()); } async writeContentTo(t) { try { await D(t); } catch (e) { if (e.code !== "EEXIST") throw e; } for (const [e, a] of await this.getContent()) await a.writeTo(R(t, e)); } } class Ne extends He { constructor(t, e) { super(), this.sourceFilePath = t, this.options = e, this.sourceFilePath = V(t); } async _getFile() { const t = this.sourceFilePath, e = await Vt(t), a = { uid: e.uid, gid: e.gid, mode: e.mode, ...this.options?.overrideAttributes }; if (e.isDirectory()) return new Ke(t, a, this.options); if (e.isFile()) return new Z(t, a); if (e.isSymbolicLink()) return new De(t, a); throw new Error(`Unsupported file type ${e.mode.toString(8)}`); } } class Ke extends ie { constructor(t, e, a) { super(e), this.sourceFilePath = t, this.options = a, this.sourceFilePath = V(t); } async _getDirectoryContent() { const t = this.sourceFilePath, e = {}, a = await Q(t); for (const s of a) e[s] = new Ne(R(t, s), this.options); return e; } } class Z extends ee { constructor(t, e) { super(e), this.sourceFilePath = t, this.sourceFilePath = V(t); } async _getContent() { return await G(this.sourceFilePath); } } class De extends ae { constructor(t, e) { super(e), this.sourceFilePath = t, this.sourceFilePath = V(t); } async _getContent() { return await Jt(this.sourceFilePath); } } class ze extends ie { content; constructor({ content: t = {}, ...e } = {}) { super(e), this.content = t; } async _getDirectoryContent() { return this.content; } } class dt extends ee { content; constructor({ content: t = "", ...e } = {}) { super(e), this.content = t; } async _getContent() { const t = this.content; return Buffer.isBuffer(t) ? t : Buffer.from(t, "utf8"); } } class Ta extends ae { content; constructor({ content: t, ...e }) { super(e), this.content = t; } async _getContent() { return this.content; } } const se = (i) => { const t = Xt(i), e = async (a) => { for (const [s, r] of t) await r.writeTo(await a.resolve(s)); }; return e.getCacheKey = async () => `ADD-FILES-${(await Yt(t)).toString("base64url")}`, e; }, re = (i) => { i = i.map(Qt).sort(); const t = async (e) => { const a = await e.mount(); for (const s of i) { const r = await qt( a, s ); await O(r, { force: !0, recursive: !0 }); } }; return t.getCacheKey = async () => { const e = S("sha256"); return e.update(JSON.stringify(i)), `RM-FILES-${e.digest("base64url")}`; }, t; }, J = (i, t) => { const e = async (a) => { let s = [...i]; s = await t?.beforeRun?.(a, s) ?? s, await a.run(s, [ ...t?.buildahArgs ?? [], ...t?.buildahArgsNoHash ?? [] ]); }; return e.getCacheKey = async () => { const a = S("sha256"); return a.update(JSON.stringify(i)), a.update(JSON.stringify(t?.buildahArgs ?? [])), a.update(JSON.stringify(t?.extraHashData ?? [])), `RUN-${a.digest("base64url")}`; }, e; }, Me = (i, { apkCache: t } = {}) => { const e = {}; return t && (e.buildahArgsNoHash = [ "--volume", `${t}:/etc/apk/cache:rw` ], e.extraHashData = ["--volume", ":/etc/apk/cache:rw"], e.beforeRun = async () => { await D(t, { recursive: !0 }); }), J(["apk", "add", ...i], e); }; let rt; const xa = () => (rt || (rt = R(bt(), ".buildahcker", "cache", "apk")), rt), At = () => ({ directories: [], files: [] }); class Ge { #t = At(); #e = "."; packagesMap = /* @__PURE__ */ new Map(); addLine(t) { if (t.length === 0) { this.#t = At(), this.#e = "."; return; } if (t[1] != ":") throw new Error("Unexpected line syntax!"); const e = t[0], a = t.substring(2); switch (e) { case "P": { this.#t.packageName = a, this.packagesMap.set(a, this.#t); break; } case "F": { this.#e = a, this.#t.directories.push(a); break; } case "R": { const s = _e.join(this.#e, a); this.#t.files.push(s); break; } } } } const Je = async (i) => { const t = await vt( i, "lib/apk/db/installed" ), e = await Wt(t); try { const a = new Ge(); for await (const s of e.readLines()) a.addLine(s); return a; } finally { await e.close(); } }, ne = async (i) => { try { (await Q(i)).length === 0 && await ge(i); } catch (t) { if (t.code !== "ENOENT") throw t; } }, U = async (i) => (i = V(i), await D(mt(i), { recursive: !0 }), await z(i, ""), i), oe = async (i, t) => typeof i == "number" ? i : await new Promise( (e, a) => Ie( i, t, (s, r) => s ? a(s) : e(r) ) ), ce = async (i, t) => typeof t == "number" ? void 0 : new Promise((e) => Ce(i, e)), Ve = Be(Re), We = async (i, t, e) => { const { buffer: a, bytesRead: s } = await Ve(i, { buffer: Buffer.alloc(e), position: t }); if (s !== e) throw new Error(`Could not read ${s} bytes from file`); return a; }, je = (i, t) => { i = i.sort(); const e = async (a) => { const s = await a.mount(), r = await Je(s), n = [], o = []; for (const c of i) { const h = r.packagesMap.get(c); if (!h) { t?.write(`Package not installed: ${c} `); continue; } n.push(...h.files), o.push(...h.directories); } for (const c of n) { const h = await a.resolveParent(c); t?.write(`rm ${c} `), await O(h, { force: !0 }); } o.sort().reverse(); for (const c of o) { const h = await a.resolveParent(c); await ne(h); } }; return e.getCacheKey = async () => { const a = S("sha256"); return a.update(JSON.stringify(i)), `APK-REMOVE-${a.digest("base64url")}`; }, e; }, La = (i = [], t) => [ je(["apk-tools", ...i], t), re([ "var/lib/apk", "lib/apk", "etc/apk", "usr/share/apk", "var/cache/apk" ]) ], M = async ({ baseImage: i = "alpine", apkPackages: t, commitOptions: e, logger: a, apkCache: s, containerCache: r }) => { const n = await F.from(i, { containerCache: r, commitOptions: e, logger: a }); return await n.executeStep(Me(t, { apkCache: s })), n.imageId; }, _t = async ({ existingSource: i, command: t, buildahRunOptions: e, ...a }) => await Ue({ source: i ?? await M(a), command: t, buildahRunOptions: e, logger: a.logger }), qe = async ({ outputCoreFile: i, outputBootFile: t, modules: e, prefix: a, config: s, target: r, grubSource: n, containerCache: o, apkCache: c, logger: h }) => { i = await U(i), n || (n = await M({ apkPackages: ["grub", "grub-bios", "grub-efi"], containerCache: o, apkCache: c, logger: h })), await W( n, async (m) => { const d = await m.tempFolder(); try { s && await z(R(d.pathInHost, "config.cfg"), s), await m.run( [ "grub-mkimage", "-O", r ?? "x86_64-efi", "-p", a ?? "/boot/grub", "-o", "core.img", ...s ? ["-c", "config.cfg"] : [], "--", ...e ?? [] ], [ "--workingdir", d.pathInContainer, "-v", `${i}:${d.pathInContainer}/core.img:rw` ] ), t && (t = await U(t), await wt( await m.resolve(`usr/lib/grub/${r}/boot.img`), t )); } finally { await d.remove(); } }, { logger: h } ); }, ue = ({ outputCoreFile: i, outputBootFile: t, modules: e, prefix: a, config: s, target: r, ...n }) => { const o = async (c) => { const h = await c.resolve(i), m = t ? await c.resolve(t) : void 0; await qe({ outputCoreFile: h, outputBootFile: m, modules: e, prefix: a, config: s, target: r, ...n }); }; return o.getCacheKey = async () => { const c = S("sha256"); return c.update( JSON.stringify({ outputCoreFile: i, outputBootFile: t, modules: e, prefix: a, config: s, target: r }) ), `GRUB-MKIMAGE-${c.digest("base64url")}`; }, o; }; var nt = {}; /*! crc32.js (C) 2014-present SheetJS -- http://sheetjs.com */ var Tt; function Ze() { return Tt || (Tt = 1, function(i) { (function(t) { t(typeof DO_NOT_EXPORT_CRC > "u" ? i : {}); })(function(t) { t.version = "1.2.2"; function e() { for (var u = 0, _ = new Array(256), l = 0; l != 256; ++l) u = l, u = u & 1 ? -306674912 ^ u >>> 1 : u >>> 1, u = u & 1 ? -306674912 ^ u >>> 1 : u >>> 1, u = u & 1 ? -306674912 ^ u >>> 1 : u >>> 1, u = u & 1 ? -306674912 ^ u >>> 1 : u >>> 1, u = u & 1 ? -306674912 ^ u >>> 1 : u >>> 1, u = u & 1 ? -306674912 ^ u >>> 1 : u >>> 1, u = u & 1 ? -306674912 ^ u >>> 1 : u >>> 1, u = u & 1 ? -306674912 ^ u >>> 1 : u >>> 1, _[l] = u; return typeof Int32Array < "u" ? new Int32Array(_) : _; } var a = e(); function s(u) { var _ = 0, l = 0, f = 0, p = typeof Int32Array < "u" ? new Int32Array(4096) : new Array(4096); for (f = 0; f != 256; ++f) p[f] = u[f]; for (f = 0; f != 256; ++f) for (l = u[f], _ = 256 + f; _ < 4096; _ += 256) l = p[_] = l >>> 8 ^ u[l & 255]; var v = []; for (f = 1; f != 16; ++f) v[f - 1] = typeof Int32Array < "u" ? p.subarray(f * 256, f * 256 + 256) : p.slice(f * 256, f * 256 + 256); return v; } var r = s(a), n = r[0], o = r[1], c = r[2], h = r[3], m = r[4], d = r[5], y = r[6], E = r[7], $ = r[8], b = r[9], w = r[10], g = r[11], C = r[12], B = r[13], I = r[14]; function k(u, _) { for (var l = _ ^ -1, f = 0, p = u.length; f < p; ) l = l >>> 8 ^ a[(l ^ u.charCodeAt(f++)) & 255]; return ~l; } function X(u, _) { for (var l = _ ^ -1, f = u.length - 15, p = 0; p < f; ) l = I[u[p++] ^ l & 255] ^ B[u[p++] ^ l >> 8 & 255] ^ C[u[p++] ^ l >> 16 & 255] ^ g[u[p++] ^ l >>> 24] ^ w[u[p++]] ^ b[u[p++]] ^ $[u[p++]] ^ E[u[p++]] ^ y[u[p++]] ^ d[u[p++]] ^ m[u[p++]] ^ h[u[p++]] ^ c[u[p++]] ^ o[u[p++]] ^ n[u[p++]] ^ a[u[p++]]; for (f += 15; p < f; ) l = l >>> 8 ^ a[(l ^ u[p++]) & 255]; return ~l; } function Y(u, _) { for (var l = _ ^ -1, f = 0, p = u.length, v = 0, A = 0; f < p; ) v = u.charCodeAt(f++), v < 128 ? l = l >>> 8 ^ a[(l ^ v) & 255] : v < 2048 ? (l = l >>> 8 ^ a[(l ^ (192 | v >> 6 & 31)) & 255], l = l >>> 8 ^ a[(l ^ (128 | v & 63)) & 255]) : v >= 55296 && v < 57344 ? (v = (v & 1023) + 64, A = u.charCodeAt(f++) & 1023, l = l >>> 8 ^ a[(l ^ (240 | v >> 8 & 7)) & 255], l = l >>> 8 ^ a[(l ^ (128 | v >> 2 & 63)) & 255], l = l >>> 8 ^ a[(l ^ (128 | A >> 6 & 15 | (v & 3) << 4)) & 255], l = l >>> 8 ^ a[(l ^ (128 | A & 63)) & 255]) : (l = l >>> 8 ^ a[(l ^ (224 | v >> 12 & 15)) & 255], l = l >>> 8 ^ a[(l ^ (128 | v >> 6 & 63)) & 255], l = l >>> 8 ^ a[(l ^ (128 | v & 63)) & 255]); return ~l; } t.table = a, t.bstr = k, t.buf = X, t.str = Y; }); }(nt)), nt; } var ot = Ze(); const St = async (i) => { const t = i.outputFile, e = await oe(t, "r+"); try { for (const a of i.partitions) { const s = jt("", { fd: e, start: a.output.offset, autoClose: !1 }); if ("inputFile" in a) { const r = a.input?.offset ?? 0, n = a.input?.size ?? (await q(a.inputFile)).size - r; if (n > a.output.size) throw new Error( `Partition too small for content: ${n} > ${a.output.size}` ); const o = gt(a.inputFile, { start: r, end: r + n, autoClose: !1 }); try { await Et(o, s); } finally { await o.close(); } } else { if (a.inputBuffer.length > a.output.size) throw new Error( `Partition too small for content: ${a.inputBuffer.length} > ${a.output.size}` ); await new Promise( (r, n) => s.write( a.inputBuffer, (o) => o ? n(o) : r() ) ); } } } finally { await ce(e, t); } }; var N = /* @__PURE__ */ ((i) => (i.EfiSystem = "C12A7328-F81F-11D2-BA4B-00A0C93EC93B", i.BiosBoot = "21686148-6449-6E6F-744E-656564454649", i.LinuxData = "0FC63DAF-8483-4772-8E79-3D69D8477DE4", i))(N || {}); const ct = (i, t, e) => { const a = Buffer.from(i.replaceAll("-", ""), "hex"); if (a.length != 16) throw new Error(`Invalid GUID: ${i}`); a.copy(t, e); }, Qe = async (i) => { const t = [], r = Math.ceil( 34 ), n = Buffer.alloc(128 * 128); let o = r, c = 0; for (const C of i.partitions) { const B = o, I = Math.ceil(C.size / 512 / 2048) * 2048; o += I, t.push({ offset: B * 512, size: I * 512 }); const k = Buffer.alloc(128); ct(C.type, k, 0), ct(C.guid ?? Ct(), k, 16), k.writeBigInt64LE(BigInt(B), 32), k.writeBigInt64LE(BigInt(B + I - 1), 40), k.write(C.name, 56, 72, "utf-16le"), k.copy(n, c * 128), c++; } o += r; const h = await U(i.outputFile); await pt(h, o * 512); const m = [], d = Buffer.alloc(512); d.write("000200", 447, "hex"), d.writeUInt8(238, 450), d.write("FFFFFF", 447, "hex"), d.writeUint32LE(1, 454), d.writeUint32LE(Math.min(4294967295, o - 1), 458), d.writeUint8(85, 510), d.writeUint8(170, 511); const y = 1, E = o - 1, $ = 2, b = o - r, w = Buffer.alloc(512); w.write("EFI PART", 0, "ascii"), w.write("00000100", 8, "hex"), w.writeUInt32LE(92, 12), w.writeBigUInt64LE(BigInt(y), 24), w.writeBigUInt64LE(BigInt(E), 32), w.writeBigUInt64LE(BigInt(r), 40), w.writeBigUInt64LE( BigInt(b - 1), 48 ), ct(i.guid ?? Ct(), w, 56), w.writeBigUInt64LE(BigInt($), 72), w.writeUInt32LE(128, 80), w.writeUInt32LE(128, 84), w.writeInt32LE(ot.buf(n), 88), w.writeInt32LE( ot.buf(w.subarray(0, 92)), 16 ); const g = Buffer.from(w); return g.writeBigUInt64LE(BigInt(E), 24), g.writeBigUInt64LE(BigInt(y), 32), g.writeBigUInt64LE( BigInt(b), 72 ), g.writeUint32LE(0, 16), g.writeInt32LE( ot.buf(g.subarray(0, 92)), 16 ), m.push( { inputBuffer: d, output: { offset: 0, size: d.length } }, { inputBuffer: w, output: { offset: y * 512, size: w.length } }, { inputBuffer: n, output: { offset: $ * 512, size: n.length } }, { inputBuffer: n, output: { offset: b * 512, size: n.length } }, { inputBuffer: g, output: { offset: E * 512, size: g.length } } ), await St({ outputFile: h, partitions: m }), t; }, x = 512, xt = 9, Lt = 3, Xe = 90, Ye = 92, Ot = 440, ta = 510, Ht = 102, le = 12, et = x - le, ut = et - le, ea = 2048, Pt = (i, t, e) => i.writeBigUint64LE(e, t), Nt = (i, t, e) => i.writeUint16LE(e, t + 8), Kt = (i, t, e) => i.writeUint16LE(e, t + 10), aa = async (i) => { const t = i.imageFile, e = await oe(t, "r+"); try { const a = await We(e, 0, x), s = await G(i.bootFile); let r = await G(i.coreFile); if (s.length !== x) throw new Error( `The boot file should have a size of ${x} bytes.` ); const n = r.length % x; n !== 0 && (r = Buffer.concat([ r, Buffer.alloc(x - n) ])), a.copy( s, Lt, Lt, Xe ), a.copy( s, Ot, Ot, ta ); const o = BigInt( i.partition.offset >> xt ); s.writeBigUint64LE(o, Ye), s.writeUInt8(144, Ht), s.writeUInt8(144, Ht + 1), Pt(r, et, o + 1n), Nt( r, et, (r.length >> xt) - 1 ), Kt( r, et, ea + (x >> 4) ), Pt(r, ut, 0n), Nt(r, ut, 0), Kt(r, ut, 0), await St({ outputFile: e, partitions: [ { inputBuffer: s, output: { offset: 0, size: x } }, { inputBuffer: r, output: i.partition } ] }); } finally { await ce(e, t); } }, ia = async ({ outputFile: i, variables: t, grubSource: e, containerCache: a, apkCache: s, logger: r }) => { i = await U(i), e || (e = await M({ apkPackages: ["grub", "grub-bios", "grub-efi"], containerCache: a, apkCache: s, logger: r })), await W( e, async (n) => { const o = await n.tempFolder(), c = ["--workingdir", o.pathInContainer]; try { await n.run( ["grub-editenv", "grubenv", "create"], c ), t && t.length > 0 && await n.run( ["grub-editenv", "grubenv", "set", ...t], c ), await wt(R(o.pathInHost, "grubenv"), i); } finally { await o.remove(); } }, { logger: r } ); }, sa = ({ outputFile: i, variables: t, ...e }) => { const a = async (s) => { const r = await s.resolve(i); await ia({ outputFile: r, variables: t, ...e }); }; return a.getCacheKey = async () => { const s = S("sha256"); return s.update( JSON.stringify({ outputFile: i, variables: t }) ), `GRUB-MKENV-${s.digest("base64url")}`; }, a; }, Dt = async (i) => (await q(i)).size, zt = async (i, t) => { const e = gt(i), a = jt(t, { flags: "a" }); await Et(e, a); }, he = async ({ file: i, metadataFile: t = `${i}.json`, salt: e = "", uuid: a = "00000000-0000-0000-0000-000000000000", cryptsetupSource: s, containerCache: r, apkCache: n, logger: o }) => { s || (s = await M({ apkPackages: ["cryptsetup"], containerCache: r, apkCache: n, logger: o })), await W( s, async (c) => { const m = (await c.run( [ "veritysetup", "format", "/image", "/image.hash", "--fec-device=/image.fec", `--salt=${e}`, `--uuid=${a}` ], ["-v", `${i}:/image:rw`] )).stdout.toString("utf8"), [, d] = /Root hash:\s*([0-9a-f]{64})/.exec(m), y = await Dt(i), E = await c.resolve("/image.hash"); await zt(E, i); const $ = await Dt(i), b = await c.resolve("/image.fec"); await zt(b, i), await O(E), await O(b); let w = {}; try { w = JSON.parse(await G(t, "utf8")); } catch { } const g = { ...w, rootHash: d, hashOffset: y, fecOffset: $ }; await z(t, JSON.stringify(g)); }, { logger: o } ); }, ra = async ({ inputFolder: i, squashfsToolsSource: t, outputFile: e, timestamp: a = 0, veritySetup: s, containerCache: r, apkCache: n, logger: o }) => { e = await U(e); const c = yt(i, e), h = []; c.startsWith(`..${H}`) || h.push("-e", c), await _t({ apkPackages: ["squashfs-tools"], existingSource: t, command: [ "mksquashfs", "/in", "/out", "-noappend", "-no-xattrs", "-mkfs-time", `${a}`, "-all-time", `${a}`, ...h ], buildahRunOptions: [ "-v", `${i}:/in:ro`, "-v", `${e}:/out:rw` ], containerCache: r, apkCache: n, logger: o }), s && await he({ file: e, ...s }); }, fe = ({ inputFolder: i, outputFile: t, ...e }) => { const a = async (s) => { const r = await s.resolve(i), n = await s.resolve(t); await ra({ inputFolder: r, outputFile: n, ...e }); }; return a.getCacheKey = async () => { const s = S("sha256"); return s.update( JSON.stringify({ inputFolder: i, outputFile: t, timestamp: e.timestamp ?? void 0, veritySetup: e?.veritySetup ? { salt: e.veritySetup.salt ?? void 0, uuid: e.veritySetup.uuid ?? void 0 } : void 0 }) ), `MKSQUASHFS-${s.digest("base64url")}`; }, a; }, na = async ({ inputFolder: i, mtoolsSource: t, outputFileSize: e, outputFile: a, containerCache: s, apkCache: r, logger: n }) => { t || (t = await M({ apkPackages: ["mtools"], containerCache: s, apkCache: r, logger: n })), a = await U(a), await pt(a, e), await W( t, async (o) => { await o.run( ["mformat", "-i", "/out", "-F", "::"], ["-v", `${a}:/out:rw`] ); const c = await Q(i); c.length > 0 && await o.run( [ "mcopy", "-i", "/out", "-s", "-b", "-p", ...c.map((h) => `./${h}`), "::/" ], [ "-v", `${i}:/in:ro`, "-v", `${a}:/out:rw`, "--workingdir", "/in" ] ); }, { logger: n } ); }, oa = ({ inputFolder: i, outputFile: t, ...e }) => { const a = async (s) => { const r = await s.resolve(i), n = await s.resolve(t); await na({ inputFolder: r, outputFile: n, ...e }); }; return a.getCacheKey = async () => { const s = S("sha256"); return s.update( JSON.stringify({ inputFolder: i, outputFile: t, outputFileSize: e.outputFileSize }) ), `MKVFATFS-${s.digest("base64url")}`; }, a; }, ca = ["ed25519"], Oa = async ({ outputFolder: i, opensshSource: t, types: e = ca, prefix: a = "ssh_host_", suffix: s = "_key", containerCache: r, apkCache: n, logger: o }) => { const c = {}; await D(i, { recursive: !0 }); let h = !1; const m = await Promise.all( e.map(async (d) => { const y = `${a}${d}${s}`, E = `${y}.pub`; let $ = !1; const b = R(i, E), w = R(i, y); c[y] = new Z(w, { mode: 384 }), c[E] = new Z(b, { mode: 384 }); try { const [g, C] = await Promise.all([ q(b), q(w) ]); if (!g.isFile() || !C.isFile()) throw new Error( `${y}.pub or ${y} exists in ${i} and is not a regular file.` ); $ = !0; } catch (g) { if (g.code != "ENOENT") throw g; h = !0; } return { alreadyPresent: $, keyFile: y, type: d }; }) ); return h && await W( t ?? await M({ apkPackages: ["openssh"], containerCache: r, apkCache: n, logger: o }), async (d) => { const y = await d.tempFolder(); try { for (const { alreadyPresent: E, type: $, keyFile: b } of m) E || await d.run( ["ssh-keygen", "-t", $, "-f", b, "-N", ""], [ "-v", `${i}:${y.pathInContainer}`, "--workingdir", y.pathInContainer ] ); } finally { await y.remove(); } }, { logger: o } ), c; }, de = async (i, t) => { const e = /* @__PURE__ */ new Map(); try { return await i(async (a) => { if (typeof a != "string") { let s = e.get(a.imageId); s || (s = await at.from(a.imageId, t), e.set(a.imageId, s)), a = await s.resolve(a.file); } return a; }); } finally { for (const a of e.values()) await a.remove(); } }, Ha = async (i, t) => { t = await U(t), await de(async (e) => { await wt(await e(i), t); }); }, ua = 33 * 1024 * 1024, la = async ({ grubDiskDevice: i = "hd0", grubEnvPartitionIndex: t, grubEnvPath: e = "/grubenv", grubExtraConfig: a = "", grubSourceImage: s, grubSourcePath: r = "/usr/lib/grub", grubTimeout: n = 3, linuxDiskDevice: o = "/dev/sda", rootPartitionAIndex: c, rootPartitionBIndex: h, rootPartitionGrubCfg: m = "/boot/grub.cfg", squashfsToolsSource: d, apkCache: y, containerCache: E, logger: $ }) => { const b = await F.from(s, { containerCache: E, logger: $ }); return await b.executeStep([ se({ [R(r, "grub.cfg")]: new dt({ content: ` insmod all_video set envfile=(${i},gpt${t})/${e} load_env --file $envfile buildahcker_stable buildahcker_new if [ ( $buildahcker_new == b ) -o ( ( $buildahcker_new != a ) -a ( $buildahcker_stable == b ) ) ] ; then set default=b set fallback=a else set default=a set fallback=b fi if [ $buildahcker_new != n ] ; then set buildahcker_new=n save_env --file $envfile buildahcker_stable buildahcker_new fi export buildahcker_params set timeout=${n} ${a} menuentry A --id=a { set root=(${i},gpt${c}) set buildahcker_params="buildahcker_current=a buildahcker_grubenv_device=${o}${t} buildahcker_grubenv=${e} buildahcker_other_root=${o}${h} root=${o}${c}" configfile ${m} } menuentry B --id=b { set root=(${i},gpt${h}) set buildahcker_params="buildahcker_current=b buildahcker_grubenv_device=${o}${t} buildahcker_grubenv=${e} buildahcker_other_root=${o}${c} root=${o}${h}" configfile ${m} } ` }) }), fe({ inputFolder: r, outputFile: "/buildahcker.img", squashfsToolsSource: d, apkCache: y, containerCache: E, logger: $ }) ]), { imageId: b.imageId, file: "buildahcker.img" }; }, Pa = async ({ grubSourceImage: i, containerCache: t, logger: e }) => { const a = await F.from(i, { containerCache: t, logger: e }); return await a.executeStep([ J(["grub-editenv", "/buildahcker.img", "create"]), J([ "grub-editenv", "/buildahcker.img", "set", "buildahcker_stable=a", "buildahcker_new=n" ]) ]), { imageId: a.imageId, file: "buildahcker.img" }; }, ha = async ({ efiPartitionSize: i, grubDiskDevice: t = "hd0", grubEnvPath: e = "/grubenv", grubPartitionIndex: a, grubSourceImage: s, mtoolsSource: r, useEfi: n, containerCache: o, apkCache: c, logger: h }) => { const m = await F.from( "scratch", // TODO: allow passing this as a parameter? { containerCache: o, logger: h } ); return await m.executeStep([ sa({ grubSource: s, outputFile: R("efi", e), variables: ["buildahcker_stable=a", "buildahcker_new=n"], containerCache: o, apkCache: c, logger: h }) ]), n && await m.executeStep( ue({ outputCoreFile: "efi/EFI/boot/bootx64.efi", grubSource: s, target: "x86_64-efi", modules: ["part_gpt", "squash4"], prefix: `(${t},gpt${a})/`, containerCache: o, apkCache: c, logger: h }) ), await m.executeStep( oa({ inputFolder: "efi", outputFile: "/buildahcker.img", outputFileSize: i, mtoolsSource: r, apkCache: c, containerCache: o, logger: h }) ), { imageId: m.imageId, file: "buildahcker.img" }; }, fa = async ({ grubDiskDevice: i = "hd0", grubPartitionIndex: t, grubSourceImage: e, containerCache: a, apkCache: s, logger: r }) => { const n = await F.from("scratch", { containerCache: a, logger: r }); return await n.executeStep([ ue({ outputCoreFile: "buildahcker_grub_core.img", outputBootFile: "buildahcker_grub_boot.img", grubSource: e, target: "i386-pc", modules: ["biosdisk", "part_gpt", "squash4"], prefix: `(${i},gpt${t})/`, containerCache: a, apkCache: s, logger: r }) ]), { core: { imageId: n.imageId, file: "buildahcker_grub_core.img" }, boot: { imageId: n.imageId, file: "buildahcker_grub_boot.img" } }; }, Na = async ({ biosBootPartitionSize: i, bootType: t = "both", efiPartitionSize: e, grubDiskDevice: a, grubEnvPath: s, grubExtraConfig: r, grubSourceImage: n, grubSourcePath: o, grubTimeout: c, linuxDiskDevice: h, mtoolsSource: m, rootPartition: d, rootPartitionGrubCfg: y, rootPartitionSize: E, squashfsToolsSource: $, apkCache: b, containerCache: w, logger: g }) => { const C = t === "bios" || t === "both", B = t === "efi" || t === "both"; n || (n = await M({ apkPackages: [ ...B ? ["grub-efi"] : [], ...C ? ["grub-bios"] : [] ], apkCache: b, containerCache: w, logger: g })); const I = {}; let k = 0; const X = C ? ++k : -1, Y = ++k, u = ++k, _ = ++k, l = ++k, f = C ? await fa({ grubDiskDevice: a, grubPartitionIndex: u, grubSourceImage: n, apkCache: b, containerCache: w, logger: g }) : void 0; f && (I[X] = { type: N.BiosBoot, name: "biosboot", size: i, file: f.core }), e = Math.max(ua, e ?? 0), I[Y] = { name: B ? "efi" : "grubenv", type: B ? N.EfiSystem : N.LinuxData, file: await ha({ efiPartitionSize: e, grubDiskDevice: a, grubEnvPath: s, grubPartitionIndex: u, grubSourceImage: n, mtoolsSource: m, useEfi: B, apkCache: b, containerCache: w, logger: g }) }, I[u] = { name: "grub", type: N.LinuxData, file: await la({ grubDiskDevice: a, grubEnvPartitionIndex: Y, grubExtraConfig: r, grubSourceImage: n, grubSourcePath: o, grubTimeout: c, linuxDiskDevice: h, rootPartitionAIndex: _, rootPartitionBIndex: l, rootPartitionGrubCfg: y, squashfsToolsSource: $, apkCache: b, containerCache: w, logger: g }) }, I[_] = { name: "systemA", type: N.LinuxData, size: E, file: d }, I[l] = { name: "systemB", type: N.LinuxData, size: E }; const p = await F.from("scratch", { containerCache: w, logger: g }), v = async (A) => await de(async (tt) => { const it = await A.resolve("buildahcker.img"), kt = []; for (let T = 1; T <= k; T++) { const P = I[T]; kt.push({ name: P.name, size: P.size ?? (await q(await tt(P.file))).size, type: P.type }); } const Rt = await Qe({ partitions: kt, outputFile: it }), It = []; for (let T = 1; T <= k; T++) { const P = I[T]; P.file && It.push({ inputFile: await tt(P.file), output: Rt[T - 1] }); } await St({ outputFile: it, partitions: It }), f && await aa({ imageFile: it, bootFile: await tt(f.boot), coreFile: await tt(f.core), partition: Rt[X - 1] }); }); return v.getCacheKey = async () => { const A = S("sha256"); return A.update(JSON.stringify(I)), `ABPARTITIONSDISK-${A.digest("base64url")}`; }, await p.executeStep(v), { imageId: p.imageId, file: "buildahcker.img" }; }, Ka = async ({ kernelCmdline: i, rootPartitionGrubCfg: t = "/boot/grub.cfg", sourceRootImage: e, sourceRootInitrdPath: a = "/boot/initramfs-lts", sourceRootKernelPath: s = "/boot/vmlinuz-lts", squashfsToolsSource: r, updateToolPath: n = "/sbin/buildahckerABTool", apkCache: o, containerCache: c, logger: h }) => { const m = await F.from(e, { containerCache: c, logger: h }); return await m.executeStep([ se({ [t]: new dt({ content: `linux ${s} $buildahcker_params${i ? ` ${i}` : ""}${a ? ` initrd ${a}` : ""} ` }), [n]: new dt({ content: `#!/bin/sh set -e function readCmdline() { cat /proc/cmdline | sed -nE 's/^.*[[:space:]]'"$1"'=([^[:space:]]*)([[:space:]]|$).*$/\\1/p' } BUILDHACKER_CURRENT="$(readCmdline buildahcker_current)" BUILDHACKER_CURRENT_ROOT="$(readCmdline root)" BUILDHACKER_OTHER_ROOT="$(readCmdline buildahcker_other_root)" BUILDHACKER_GRUBENV="$(readCmdline buildahcker_grubenv)" BUILDHACKER_GRUBENV_DEVICE="$(readCmdline buildahcker_grubenv_device)" if [ -z "$BUILDHACKER_CURRENT" ] || [ -z "$BUILDHACKER_OTHER_ROOT" ] || [ -z "$BUILDHACKER_GRUBENV" ] || [ -z "$BUILDHACKER_GRUBENV_DEVICE" ] ; then echo Not running in expected buildahcker A/B partition environment. exit 1 fi case "$BUILDHACKER_CURRENT" in 'a') BUILDHACKER_OTHER=b ;; 'b') BUILDHACKER_OTHER=a ;; *) echo Invalid buildahcker_current value: $BUILDHACKER_CURRENT exit 1 ;; esac mkdir -p /run/buildahcker-ab-grubenv if ! mountpoint /run/buildahcker-ab-grubenv &> /dev/null ; then mount -o ro -t vfat $BUILDHACKER_GRUBENV_DEVICE /run/buildahcker-ab-grubenv fi function readGrubenv() { grub-editenv "/run/buildahcker-ab-grubenv$BUILDHACKER_GRUBENV" list | sed -nE 's/(^.*[[:space:]]|^)'"$1"'=([^[:space:]]*)([[:space:]]|$).*$/\\2/p' } function updateGrubenv() { mount -o remount,rw /run/buildahcker-ab-grubenv grub-editenv "/run/buildahcker-ab-grubenv$BUILDHACKER_GRUBENV" set "$@" mount -o remount,ro /run/buildahcker-ab-grubenv } BUILDAHCKER_STABLE=$(readGrubenv buildahcker_stable) BUILDAHCKER_NEW=$(readGrubenv buildahcker_new) case "$*" in 'show' | '') echo Current: "$BUILDHACKER_CURRENT" echo Current root: "$BUILDHACKER_CURRENT_ROOT" echo Other: "$BUILDHACKER_OTHER" echo Other root: "$BUILDHACKER_OTHER_ROOT" echo Stable: "$BUILDAHCKER_STABLE" if [ "$BUILDAHCKER_STABLE" != "$BUILDHACKER_CURRENT" ] ; then echo "Warning: current system is not marked as stable!" fi if [ "$BUILDAHCKER_NEW" != "n" ] ; then echo "Warning: next reboot will be on $BUILDAHCKER_NEW" fi ;; 'update') if [ "$BUILDAHCKER_STABLE" != "$BUILDHACKER_CURRENT" ]; then echo Current system is not marked as stable! echo Updates should only be done from a stable system. echo Use mark-stable or reboot the system before updating. exit 1 fi echo "Target partition: $BUILDHACKER_OTHER_ROOT" echo "Update in progress..." dd of="$BUILDHACKER_OTHER_ROOT" echo "Finished writing on $BUILDHACKER_OTHER_ROOT" updateGrubenv buildahcker_new=$BUILDHACKER_OTHER echo Next reboot will be on "$BUILDHACKER_OTHER"! echo To cancel, use cancel-update or run update with a different update. ;; 'cancel-update') if [ "$BUILDAHCKER_NEW" != "n" ]; then updateGrubenv buildahcker_new=n echo Update cancelled, next reboot will be on "$BUILDHACKER_STABLE" else echo There is no update in progress. fi ;; 'mark-stable') if [ "$BUILDAHCKER_STABLE" != "$BUILDHACKER_CURRENT" ]; then updateGrubenv buildahcker_stable=$BUILDHACKER_CURRENT echo "Current system successfully marked as stable." else echo "Current system was already marked as stable." fi ;; 'is-stable') if [ "$BUILDAHCKER_STABLE" == "$BUILDHACKER_CURRENT" ]; then exit 0 else exit 1 fi ;; *) echo Invalid command: "$*" exit 1 ;; esac `, mode: 320 }) }), fe({ inputFolder: ".", outputFile: "/buildahcker.img", squashfsToolsSource: r, apkCache: o, containerCache: c, logger: h }) ]), { imageId: m.imageId, file: "buildahcker.img" }; }; class we { #t; #e; #a; constructor(t, e) { this.#t = t, this.#e = e, this.#a = new ze({ content: { cert: t, key: e } }); } async certificatePath() { return this.#t.sourceFilePath; } async privateKeyPath() { return this.#e.sourceFilePath; } async getHash() { return await this.#a.getContentHash(); } static from(t, e) { return new we( new Z(t, { uid: 0, gid: 0, mode: 384 }), new Z(e, { uid: 0, gid: 0, mode: 384 }) ); } } const da = async ({ inputFile: i, outputFile: t, key: e, sbsignSource: a, containerCache: s, apkCache: r, logger: n }) => { t || (t = `${i}.signed`), t = await U(t), await _t({ apkPackages: ["sbsigntool"], existingSource: a, command: [ "sbsign", "--key", "/in.key", "--cert", "/in.crt", "--output", "/out.efi", "/in.efi" ], buildahRunOptions: [ "-v", `${i}:/in.efi:ro`, "-v", `${await e.certificatePath()}:/in.crt:ro`, "-v", `${await e.privateKeyPath()}:/in.key:ro`, "-v", `${t}:/out.efi:rw` ], containerCache: s, apkCache: r, logger: n }); }, wa = ({ inputFile: i, outputFile: t, key: e, ...a }) => { t || (t = `${i}.signed`); const s = async (r) => { const n = i === t, o = n ? `${i}.unsigned` : i, c = await r.resolve(o), h = await r.resolve(t); n && (await Ee(h, c), await pt(h)), await da({ inputFile: c, outputFile: h, key: e, ...a }), n && await O(c); }; return s.getCacheKey = async () => { const r = S("sha256"); return r.update( JSON.stringify({ inputFile: i, outputFile: t }) ), r.update(await e.getHash()), `SBSIGN-${r.digest("base64url")}`; }, s; }, j = 1024, pa = j + 12, lt = 48, Mt = 4, ma = [ 0, 4067132163, 3778769143, 324072436, 3348797215, 904991772, 648144872, 3570033899, 2329499855, 2024987596, 1809983544, 2575936315, 1296289744, 3207089363, 2893594407, 1578318884, 274646895, 3795141740, 4049975192, 51262619, 3619967088, 632279923, 922689671, 3298075524, 2592579488, 1760304291, 2075979607, 2312596564, 1562183871, 2943781820, 3156637768, 1313733451, 549293790, 3537243613, 3246849577, 871202090, 3878099393, 357341890, 102525238, 4101499445, 2858735121, 1477399826, 1264559846, 3107202533, 1845379342, 2677391885, 2361733625, 2125378298, 820201905, 3263744690, 3520608582, 598981189, 4151959214, 85089709, 373468761, 3827903834, 3124367742, 1213305469, 1526817161, 2842354314, 2107672161, 2412447074, 2627466902, 1861252501, 1098587580, 3004210879, 2688576843, 1378610760, 2262928035, 1955203488, 1742404180, 2511436119, 3416409459, 969524848, 714683780, 3639785095, 205050476, 4266873199, 3976438427, 526918040, 1361435347, 2739821008, 2954799652, 1114974503, 2529119692, 1691668175, 2005155131, 2247081528, 3690758684, 697762079, 986182379, 3366744552, 476452099, 3993867776, 4250756596, 255256311, 1640403810, 2477592673, 2164122517, 1922457750, 2791048317, 1412925310, 1197962378, 3037525897, 3944729517, 427051182, 170179418, 4165941337, 746937522, 3740196785, 3451792453, 1070968646, 1905808397, 2213795598, 2426610938, 1657317369, 3053634322, 1147748369, 1463399397, 2773627110, 4215344322, 153784257, 444234805, 3893493558, 1021025245, 3467647198, 3722505002, 797665321, 2197175160, 1889384571, 1674398607, 2443626636, 1164749927, 3070701412, 2757221520, 1446797203, 137323447, 4198817972, 3910406976, 461344835, 3484808360, 1037989803, 781091935, 3705997148, 2460548119, 1623424788, 1939049696, 2180517859, 1429367560, 2807687179, 3020495871, 1180866812, 410100952, 3927582683, 4182430767, 186734380, 3756733383, 763408580, 1053836080, 3434856499, 2722870694, 1344288421, 1131464017, 2971354706, 1708204729, 2545590714, 2229949006, 1988219213, 680717673, 3673779818, 3383336350, 1002577565, 4010310262, 493091189, 238226049, 4233660802, 2987750089, 1082061258, 1395524158, 2705686845, 1972364758, 2279892693, 2494862625, 1725896226, 952904198, 3399985413, 3656866545, 731699698, 4283874585, 222117402, 510512622, 3959836397, 3280807620, 837199303, 582374963,