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
1,909 lines (1,900 loc) • 66.9 kB
JavaScript
import { mkdir as U, readlink as Qt, open as Xt, writeFile as A, readFile as x, readdir as j, rmdir as _e, constants as M, lstat as rt, rm as H, lchown as Yt, chmod as yt, symlink as ke, mkdtemp as Se, stat as Q, truncate as $t, cp as _t, copyFile as Re } from "fs/promises";
import { homedir as kt } from "os";
import { join as v, normalize as gt, sep as N, isAbsolute as Ie, basename as Be, dirname as Y, posix as Ce, resolve as G, relative as St } from "path";
import { createHash as k, randomUUID as bt } from "crypto";
import { read as Fe, open as xe, close as Te, createWriteStream as te, createReadStream as Rt } from "fs";
import { promisify as Ae } from "util";
import { spawn as Ue } from "child_process";
import { Writable as Oe } from "stream";
import { pipeline as It } from "stream/promises";
const V = (e, t) => {
const a = async (i) => {
let s = [...e];
s = await t?.beforeRun?.(i, s) ?? s, await i.run(s, [
...t?.buildahArgs ?? [],
...t?.buildahArgsNoHash ?? []
]);
};
return a.getCacheKey = async () => {
const i = k("sha256");
return i.update(JSON.stringify(e)), i.update(JSON.stringify(t?.buildahArgs ?? [])), i.update(JSON.stringify(t?.extraHashData ?? [])), `RUN-${i.digest("base64url")}`;
}, a;
}, Le = (e, { apkCache: t } = {}) => {
const a = {};
return t && (a.buildahArgsNoHash = [
"--volume",
`${t}:/etc/apk/cache:rw`
], a.extraHashData = ["--volume", ":/etc/apk/cache:rw"], a.beforeRun = async () => {
await U(t, { recursive: !0 });
}), V(["apk", "add", ...e], a);
};
let ut;
const za = () => (ut || (ut = v(kt(), ".buildahcker", "cache", "apk")), ut), ee = async (e, t, a = !1) => {
const i = Be(t);
if (i === "." || i === ".." || i === "")
throw new Error(`Invalid path: ${t}`);
return v(
await Bt(e, Y(t), a),
i
);
}, Bt = async (e, t, a = !1) => {
const i = /* @__PURE__ */ new Set(), s = gt(t).split(N);
for (let n = 0; n < s.length; n++) {
const r = s[n];
if (!r || r === "." || n < 1 && r === ".." && !a) {
s.splice(n, 1), n--;
continue;
} else if (r === "..") {
if (n < 1)
throw new Error(`Invalid path: ${t}`);
s.splice(n - 1, 2), n -= 2;
continue;
}
const o = v(e, ...s.slice(0, n + 1));
if (i.has(o))
throw new Error(`Recursive link in path: ${o}`);
i.add(o);
try {
const c = await Qt(o, "utf8"), u = gt(c).split(N);
Ie(c) ? (s.splice(0, n + 1, ...u), n = -1) : (s.splice(n, 1, ...u), n--);
} catch {
}
}
return v(e, ...s);
}, Lt = () => ({
directories: [],
files: []
});
class Pe {
#t = Lt();
#e = ".";
packagesMap = /* @__PURE__ */ new Map();
addLine(t) {
if (t.length === 0) {
this.#t = Lt(), this.#e = ".";
return;
}
if (t[1] != ":")
throw new Error("Unexpected line syntax!");
const a = t[0], i = t.substring(2);
switch (a) {
case "P": {
this.#t.packageName = i, this.packagesMap.set(i, this.#t);
break;
}
case "F": {
this.#e = i, this.#t.directories.push(i);
break;
}
case "R": {
const s = Ce.join(this.#e, i);
this.#t.files.push(s);
break;
}
}
}
}
const Ke = async (e) => {
const t = await Bt(
e,
"lib/apk/db/installed"
), a = await Xt(t);
try {
const i = new Pe();
for await (const s of a.readLines())
i.addLine(s);
return i;
} finally {
await a.close();
}
}, ae = async (e) => {
try {
(await j(e)).length === 0 && await _e(e);
} catch (t) {
if (t.code !== "ENOENT")
throw t;
}
}, He = async (e, t) => {
try {
return await x(e);
} catch (a) {
if (a.code === "ENOENT") {
const i = await t();
return await U(Y(e), { recursive: !0 }), await A(e, i), i;
}
throw a;
}
}, Ne = async (e, t, a) => (await He(
e,
async () => Buffer.from(await t(), a)
)).toString(a), I = async (e) => (e = G(e), await U(Y(e), { recursive: !0 }), await A(e, ""), e), ie = async (e, t) => typeof e == "number" ? e : await new Promise(
(a, i) => xe(
e,
t,
(s, n) => s ? i(s) : a(n)
)
), se = async (e, t) => typeof t == "number" ? void 0 : new Promise((a) => Te(e, a)), De = Ae(Fe), ze = async (e, t, a) => {
const { buffer: i, bytesRead: s } = await De(e, {
buffer: Buffer.alloc(a),
position: t
});
if (s !== a)
throw new Error(`Could not read ${s} bytes from file`);
return i;
}, ne = (e) => {
const t = gt(e).split(N);
if (t[t.length - 1] === "" && t.pop(), (t[0] === "." || t[0] === "") && t.shift(), t.length === 0 || t[0] === "..")
throw new Error(`Unsafe path: ${e}`);
return t.join(N);
}, Me = ([e, t]) => [ne(e), t], Ge = (e, t) => e < t ? -1 : e > t ? 1 : 0, Je = ([e], [t]) => Ge(e, t), Ct = (e) => new Map(Object.entries(e).map(Me).sort(Je)), re = async (e) => {
const t = k("sha256");
for (const [a, i] of e) {
const s = await i.getHash();
t.update(`${a.length},${s.length},`), t.update(a), t.update(s);
}
return t.digest();
}, Ve = (e) => (e & M.S_IFMT) === M.S_IFLNK;
class oe {
async getHash() {
const t = k("sha256"), a = await this.getAttributes();
t.update(
`${a.mode.toString(8)},${a.uid},${a.gid}`
);
const i = await this.getContentHash();
return t.update(i), t.digest();
}
async writeTo(t) {
try {
(await rt(t)).isDirectory() || await H(t);
} catch (i) {
if (i.code !== "ENOENT")
throw i;
}
await this.writeContentTo(t);
const a = await this.getAttributes();
await Yt(t, a.uid, a.gid), Ve(a.mode) || await yt(t, a.mode);
}
}
class je extends oe {
#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 We extends oe {
_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 Ft extends We {
#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 ce extends Ft {
constructor(t) {
super(t), this._modeAllow = ~M.S_IFMT, this._modeMandatory = M.S_IFREG;
}
async _getContentHash() {
const t = k("sha256");
return t.update(await this.getContent()), t.digest();
}
async writeContentTo(t) {
await A(t, await this.getContent());
}
}
class ue extends Ft {
constructor(t) {
super(t), this._modeAllow = 0, this._modeMandatory = M.S_IFLNK | 511;
}
async _getContentHash() {
const t = k("sha256");
return t.update(await this.getContent()), t.digest();
}
async writeContentTo(t) {
await ke(await this.getContent(), t);
}
}
class le extends Ft {
constructor(t) {
super({ mode: 493, ...t }), this._modeAllow = ~M.S_IFMT, this._modeMandatory = M.S_IFDIR;
}
async _getContent() {
return Ct(await this._getDirectoryContent());
}
async _getContentHash() {
return await re(await this.getContent());
}
async writeContentTo(t) {
try {
await U(t);
} catch (a) {
if (a.code !== "EEXIST")
throw a;
}
for (const [a, i] of await this.getContent())
await i.writeTo(v(t, a));
}
}
class st extends je {
constructor(t, a) {
super(), this.sourceFilePath = t, this.options = a, this.sourceFilePath = G(t);
}
async _getFile() {
const t = this.sourceFilePath, a = await rt(t), i = {
uid: a.uid,
gid: a.gid,
mode: a.mode,
...this.options?.overrideAttributes
};
if (a.isDirectory())
return new he(t, i, this.options);
if (a.isFile())
return new X(t, i);
if (a.isSymbolicLink())
return new qe(t, i);
throw new Error(`Unsupported file type ${a.mode.toString(8)}`);
}
}
class he extends le {
constructor(t, a, i) {
super(a), this.sourceFilePath = t, this.options = i, this.sourceFilePath = G(t);
}
async _getDirectoryContent() {
const t = this.sourceFilePath, a = {}, i = await j(t);
for (const s of i)
a[s] = new st(v(t, s), this.options);
return a;
}
}
class X extends ce {
constructor(t, a) {
super(a), this.sourceFilePath = t, this.sourceFilePath = G(t);
}
async _getContent() {
return await x(this.sourceFilePath);
}
}
class qe extends ue {
constructor(t, a) {
super(a), this.sourceFilePath = t, this.sourceFilePath = G(t);
}
async _getContent() {
return await Qt(this.sourceFilePath);
}
}
const fe = async (e, t) => {
for (const [a, i] of e) {
const s = await t.resolve(a);
await U(Y(s), { recursive: !0 }), await i.writeTo(s);
}
}, Ma = (e) => {
const t = new he(e, { uid: 0, gid: 0, mode: 493 }), a = async (i) => {
await fe(await t.getContent(), i);
};
return a.getCacheKey = async () => `ADD-ROOT-DIRECTORY-${(await t.getContentHash()).toString("base64url")}`, a;
}, de = (e) => {
const t = Ct(e), a = async (i) => {
await fe(t, i);
};
return a.getCacheKey = async () => `ADD-FILES-${(await re(t)).toString("base64url")}`, a;
}, we = (e) => {
e = e.map(ne).sort();
const t = async (a) => {
const i = await a.mount();
for (const s of e) {
const n = await ee(
i,
s
);
await H(n, { force: !0, recursive: !0 });
}
};
return t.getCacheKey = async () => {
const a = k("sha256");
return a.update(JSON.stringify(e)), `RM-FILES-${a.digest("base64url")}`;
}, t;
}, Ze = (e, t) => {
e = e.sort();
const a = async (i) => {
const s = await i.mount(), n = await Ke(s), r = [], o = [];
for (const c of e) {
const u = n.packagesMap.get(c);
if (!u) {
t?.write(`Package not installed: ${c}
`);
continue;
}
r.push(...u.files), o.push(...u.directories);
}
for (const c of r) {
const u = await i.resolveParent(c);
t?.write(`rm ${c}
`), await H(u, { force: !0 });
}
o.sort().reverse();
for (const c of o) {
const u = await i.resolveParent(c);
await ae(u);
}
};
return a.getCacheKey = async () => {
const i = k("sha256");
return i.update(JSON.stringify(e)), `APK-REMOVE-${i.digest("base64url")}`;
}, a;
}, Ga = (e = [], t) => [
Ze(["apk-tools", ...e], t),
we([
"var/lib/apk",
"lib/apk",
"etc/apk",
"usr/share/apk",
"var/cache/apk"
])
];
class Pt extends Oe {
#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, a, i) {
this.#t.push(
Buffer.isBuffer(t) ? t : Buffer.from(t, a)
), i();
}
}
class Qe extends Error {
constructor(t, a, i, s) {
super(
`Command failed: ${t.join(" ")}
${i?.toString("utf8") ?? ""}
${s?.toString("utf8") ?? ""}`
), this.command = t, this.exitCode = a, this.stdout = i, this.stderr = s;
}
}
const P = async (e, { logger: t } = {}, a = {}) => {
const i = Ue(e[0], e.slice(1), {
...a,
stdio: "pipe"
});
t?.write(`[${i.pid}]$ ${e.join(" ")}
`), t && (i.stdout.pipe(t, { end: !1 }), i.stderr.pipe(t, { end: !1 }));
const s = new Pt();
i.stdout.pipe(s);
const n = new Pt();
i.stderr.pipe(n), await new Promise((c) => i.on("exit", c)), t?.write(`[${i.pid}] Exit code: ${i.exitCode}
`);
const r = await s.promise, o = await n.promise;
if (i.exitCode !== 0)
throw new Qe(e, i.exitCode, r, o);
return { stdout: r, stderr: o };
};
class ot {
constructor(t, a) {
this.options = a, this.#t = t;
}
#t = null;
#e = null;
static async from(t, a) {
const s = (await P(["buildah", "from", t], a)).stdout.toString("utf8").trim() || null;
if (!s)
throw new Error(`Failed to create a container from ${t}.`);
return new ot(s, a);
}
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 a = this.name;
if (t = (await P(["buildah", "mount", a], this.options)).stdout.toString("utf8").trim() || null, !t)
throw new Error(`Could not mount container ${a}`);
this.#e = t;
}
return t;
}
toPathInContainer(t) {
const a = St(this.mountPath, t).split(N);
if (a[0] === "..")
throw new Error("Path is not in container");
return a.unshift(""), a.join(N);
}
async tempFolder() {
const t = await this.resolve("tmp"), a = await Se(v(t, "buildahcker-"));
return {
pathInHost: a,
pathInContainer: this.toPathInContainer(a),
remove: async () => {
await H(a, { recursive: !0, force: !0 });
}
};
}
async resolve(t, a) {
return await this.mount(), await Bt(this.mountPath, t, a);
}
async resolveParent(t, a) {
return await this.mount(), await ee(
this.mountPath,
t,
a
);
}
async remove() {
const t = this.#t;
t && (this.#t = null, this.#e = null, await P(["buildah", "rm", t], this.options));
}
async run(t, a = []) {
return await P(
["buildah", "run", ...a, "--", this.name, ...t],
this.options
);
}
async commit({ timestamp: t } = {}) {
return (await P(
[
"buildah",
"commit",
...t != null ? ["--timestamp", `${Math.round(t / 1e3)}`] : [],
this.name
],
this.options
)).stdout.toString("utf8").trim();
}
async executeStep(t) {
if (Array.isArray(t))
for (const a of t)
await this.executeStep(a);
else
await t(this);
}
}
const pe = async (e, t, a) => {
const i = await ot.from(e, a);
try {
return await t(i);
} finally {
await i.remove();
}
}, W = async (e, t, a) => typeof e == "string" ? await pe(e, t, a) : await t(e), Xe = async ({
source: e,
command: t,
buildahRunOptions: a,
...i
}) => await W(
e,
async (s) => await s.run(t, a),
i
), Ja = async (e, t, a) => {
const i = await P(
["buildah", "inspect", ...t ? ["--type", t] : [], "--", e],
a
);
return JSON.parse(i.stdout.toString("utf8"));
}, Kt = async (e, t) => (await P(
[
"buildah",
"inspect",
"--format",
"{{.FromImageID}}",
"--type",
"image",
"--",
e
],
t
)).stdout.toString("utf8").trim();
class T {
constructor(t, a) {
this.options = a, this.#t = t;
}
#t;
static async from(t, a) {
const i = t === "scratch" ? t : await Kt(t, a);
if (!i)
throw new Error(`Could not get information about image ${t}`);
return new T(i, a);
}
clone() {
return new T(this.#t, this.options);
}
get imageId() {
return this.#t;
}
async tag(t, a = {}) {
await P(["buildah", "tag", this.#t, t], a);
}
async #e(t, a) {
return await pe(
a ?? this.#t,
async (i) => {
await i.executeStep(t);
const s = await i.commit(this.options?.commitOptions);
return a && (this.#t = s), s;
},
this.options
);
}
async executeStep(t) {
const a = this.options?.containerCache;
if (!a) {
await this.#e(t);
return;
}
if (Array.isArray(t)) {
for (const r of t)
await this.executeStep(r);
return;
}
const i = this.#t;
let s;
const n = await t.getCacheKey?.();
if (n && (s = await a.getEntry(i, n), s))
try {
s = await Kt(s, this.options);
} catch {
s = void 0;
}
s || (s = await this.#e(t, i), n && await a.setEntry(i, n, s)), this.#t = s;
}
}
const J = async ({
baseImage: e = "alpine",
apkPackages: t,
commitOptions: a,
logger: i,
apkCache: s,
containerCache: n
}) => {
const r = await T.from(e, {
containerCache: n,
commitOptions: a,
logger: i
});
return await r.executeStep(Le(t, { apkCache: s })), r.imageId;
}, q = async ({
existingSource: e,
command: t,
buildahRunOptions: a,
...i
}) => await Xe({
source: e ?? await J(i),
command: t,
buildahRunOptions: a,
logger: i.logger
});
var lt = {};
var Ht;
function Ye() {
return Ht || (Ht = 1, (function(e) {
(function(t) {
t(typeof DO_NOT_EXPORT_CRC > "u" ? e : {});
})(function(t) {
t.version = "1.2.2";
function a() {
for (var l = 0, S = new Array(256), h = 0; h != 256; ++h)
l = h, l = l & 1 ? -306674912 ^ l >>> 1 : l >>> 1, l = l & 1 ? -306674912 ^ l >>> 1 : l >>> 1, l = l & 1 ? -306674912 ^ l >>> 1 : l >>> 1, l = l & 1 ? -306674912 ^ l >>> 1 : l >>> 1, l = l & 1 ? -306674912 ^ l >>> 1 : l >>> 1, l = l & 1 ? -306674912 ^ l >>> 1 : l >>> 1, l = l & 1 ? -306674912 ^ l >>> 1 : l >>> 1, l = l & 1 ? -306674912 ^ l >>> 1 : l >>> 1, S[h] = l;
return typeof Int32Array < "u" ? new Int32Array(S) : S;
}
var i = a();
function s(l) {
var S = 0, h = 0, g = 0, E = typeof Int32Array < "u" ? new Int32Array(4096) : new Array(4096);
for (g = 0; g != 256; ++g) E[g] = l[g];
for (g = 0; g != 256; ++g)
for (h = l[g], S = 256 + g; S < 4096; S += 256) h = E[S] = h >>> 8 ^ l[h & 255];
var _ = [];
for (g = 1; g != 16; ++g) _[g - 1] = typeof Int32Array < "u" ? E.subarray(g * 256, g * 256 + 256) : E.slice(g * 256, g * 256 + 256);
return _;
}
var n = s(i), r = n[0], o = n[1], c = n[2], u = n[3], d = n[4], f = n[5], p = n[6], m = n[7], b = n[8], y = n[9], w = n[10], $ = n[11], C = n[12], F = n[13], B = n[14];
function R(l, S) {
for (var h = S ^ -1, g = 0, E = l.length; g < E; ) h = h >>> 8 ^ i[(h ^ l.charCodeAt(g++)) & 255];
return ~h;
}
function tt(l, S) {
for (var h = S ^ -1, g = l.length - 15, E = 0; E < g; ) h = B[l[E++] ^ h & 255] ^ F[l[E++] ^ h >> 8 & 255] ^ C[l[E++] ^ h >> 16 & 255] ^ $[l[E++] ^ h >>> 24] ^ w[l[E++]] ^ y[l[E++]] ^ b[l[E++]] ^ m[l[E++]] ^ p[l[E++]] ^ f[l[E++]] ^ d[l[E++]] ^ u[l[E++]] ^ c[l[E++]] ^ o[l[E++]] ^ r[l[E++]] ^ i[l[E++]];
for (g += 15; E < g; ) h = h >>> 8 ^ i[(h ^ l[E++]) & 255];
return ~h;
}
function et(l, S) {
for (var h = S ^ -1, g = 0, E = l.length, _ = 0, O = 0; g < E; )
_ = l.charCodeAt(g++), _ < 128 ? h = h >>> 8 ^ i[(h ^ _) & 255] : _ < 2048 ? (h = h >>> 8 ^ i[(h ^ (192 | _ >> 6 & 31)) & 255], h = h >>> 8 ^ i[(h ^ (128 | _ & 63)) & 255]) : _ >= 55296 && _ < 57344 ? (_ = (_ & 1023) + 64, O = l.charCodeAt(g++) & 1023, h = h >>> 8 ^ i[(h ^ (240 | _ >> 8 & 7)) & 255], h = h >>> 8 ^ i[(h ^ (128 | _ >> 2 & 63)) & 255], h = h >>> 8 ^ i[(h ^ (128 | O >> 6 & 15 | (_ & 3) << 4)) & 255], h = h >>> 8 ^ i[(h ^ (128 | O & 63)) & 255]) : (h = h >>> 8 ^ i[(h ^ (224 | _ >> 12 & 15)) & 255], h = h >>> 8 ^ i[(h ^ (128 | _ >> 6 & 63)) & 255], h = h >>> 8 ^ i[(h ^ (128 | _ & 63)) & 255]);
return ~h;
}
t.table = i, t.bstr = R, t.buf = tt, t.str = et;
});
})(lt)), lt;
}
var ht = Ye();
const xt = async (e) => {
const t = e.outputFile, a = await ie(t, "r+");
try {
for (const i of e.partitions) {
const s = te("", {
fd: a,
start: i.output.offset,
autoClose: !1
});
if ("inputFile" in i) {
const n = i.input?.offset ?? 0, r = i.input?.size ?? (await Q(i.inputFile)).size - n;
if (r > i.output.size)
throw new Error(
`Partition too small for content: ${r} > ${i.output.size}`
);
const o = Rt(i.inputFile, {
start: n,
end: n + r,
autoClose: !1
});
try {
await It(o, s);
} finally {
await o.close();
}
} else {
if (i.inputBuffer.length > i.output.size)
throw new Error(
`Partition too small for content: ${i.inputBuffer.length} > ${i.output.size}`
);
await new Promise(
(n, r) => s.write(
i.inputBuffer,
(o) => o ? r(o) : n()
)
);
}
}
} finally {
await se(a, t);
}
};
var z = /* @__PURE__ */ ((e) => (e.EfiSystem = "C12A7328-F81F-11D2-BA4B-00A0C93EC93B", e.BiosBoot = "21686148-6449-6E6F-744E-656564454649", e.LinuxData = "0FC63DAF-8483-4772-8E79-3D69D8477DE4", e))(z || {});
const ft = (e, t, a) => {
const i = Buffer.from(e.replaceAll("-", ""), "hex");
if (i.length != 16)
throw new Error(`Invalid GUID: ${e}`);
i.copy(t, a);
}, ta = async (e) => {
const t = [], n = Math.ceil(
34
), r = Buffer.alloc(16384);
let o = n, c = 0;
for (const C of e.partitions) {
const F = o, B = Math.ceil(C.size / 512 / 2048) * 2048;
o += B, t.push({
offset: F * 512,
size: B * 512
});
const R = Buffer.alloc(128);
ft(C.type, R, 0), ft(C.guid ?? bt(), R, 16), R.writeBigInt64LE(BigInt(F), 32), R.writeBigInt64LE(BigInt(F + B - 1), 40), R.write(C.name, 56, 72, "utf-16le"), R.copy(r, c * 128), c++;
}
o += n;
const u = await I(e.outputFile);
await $t(u, o * 512);
const d = [], f = Buffer.alloc(512);
f.write("000200", 447, "hex"), f.writeUInt8(238, 450), f.write("FFFFFF", 447, "hex"), f.writeUint32LE(1, 454), f.writeUint32LE(Math.min(4294967295, o - 1), 458), f.writeUint8(85, 510), f.writeUint8(170, 511);
const p = 1, m = o - 1, b = 2, y = o - n, w = Buffer.alloc(512);
w.write("EFI PART", 0, "ascii"), w.write("00000100", 8, "hex"), w.writeUInt32LE(92, 12), w.writeBigUInt64LE(BigInt(p), 24), w.writeBigUInt64LE(BigInt(m), 32), w.writeBigUInt64LE(BigInt(n), 40), w.writeBigUInt64LE(
BigInt(y - 1),
48
), ft(e.guid ?? bt(), w, 56), w.writeBigUInt64LE(BigInt(b), 72), w.writeUInt32LE(128, 80), w.writeUInt32LE(128, 84), w.writeInt32LE(ht.buf(r), 88), w.writeInt32LE(
ht.buf(w.subarray(0, 92)),
16
);
const $ = Buffer.from(w);
return $.writeBigUInt64LE(BigInt(m), 24), $.writeBigUInt64LE(BigInt(p), 32), $.writeBigUInt64LE(
BigInt(y),
72
), $.writeUint32LE(0, 16), $.writeInt32LE(
ht.buf($.subarray(0, 92)),
16
), d.push(
{
inputBuffer: f,
output: { offset: 0, size: f.length }
},
{
inputBuffer: w,
output: {
offset: p * 512,
size: w.length
}
},
{
inputBuffer: r,
output: {
offset: b * 512,
size: r.length
}
},
{
inputBuffer: r,
output: {
offset: y * 512,
size: r.length
}
},
{
inputBuffer: $,
output: {
offset: m * 512,
size: $.length
}
}
), await xt({
outputFile: u,
partitions: d
}), t;
}, K = 512, Et = 9, Nt = 3, ea = 90, aa = 92, Dt = 440, ia = 510, zt = 102, me = 12, nt = K - me, dt = nt - me, sa = 2048, Mt = (e, t, a) => e.writeBigUint64LE(a, t), Gt = (e, t, a) => e.writeUint16LE(a, t + 8), Jt = (e, t, a) => e.writeUint16LE(a, t + 10), ye = (e, t) => {
const a = e.length % K;
a !== 0 && (e = Buffer.concat([
e,
Buffer.alloc(K - a)
]));
const i = BigInt(t >> Et);
return Mt(e, nt, i + 1n), Gt(
e,
nt,
(e.length >> Et) - 1
), Jt(
e,
nt,
sa + (K >> 4)
), Mt(e, dt, 0n), Gt(e, dt, 0), Jt(e, dt, 0), e;
}, ge = (e, t, a) => {
if (e.length !== K)
throw new Error(
`The boot file should have a size of ${K} bytes.`
);
a?.copy(
e,
Nt,
Nt,
ea
), a?.copy(
e,
Dt,
Dt,
ia
);
const i = BigInt(t >> Et);
return e.writeBigUint64LE(i, aa), e.writeUInt8(144, zt), e.writeUInt8(144, zt + 1), e;
}, na = async (e) => {
const t = e.imageFile, a = await ie(t, "r+");
try {
const i = await ze(a, 0, K), s = await x(e.bootFile), n = await x(e.coreFile);
await xt({
outputFile: a,
partitions: [
{
inputBuffer: ge(
s,
e.partition.offset,
i
),
output: { offset: 0, size: K }
},
{
inputBuffer: ye(n, e.partition.offset),
output: e.partition
}
]
});
} finally {
await se(a, t);
}
}, ra = async (e) => {
const t = await x(e.bootFile ?? e.outputBootFile), a = e.existingBootSectorFile ? await x(e.existingBootSectorFile) : void 0;
await A(
await I(e.outputBootFile),
ge(t, e.diskOffset, a)
);
}, oa = async (e) => {
const t = await x(e.coreFile ?? e.outputCoreFile);
await A(
await I(e.outputCoreFile),
ye(t, e.diskOffset)
);
}, ca = async ({
outputCoreFile: e,
outputBootFile: t,
biosSetupDiskOffset: a,
modules: i,
prefix: s,
config: n,
pubkey: r,
memdisk: o,
disableCli: c,
disableShimLock: u,
target: d,
grubSource: f,
containerCache: p,
apkCache: m,
logger: b
}) => {
e = await I(e), f || (f = await J({
apkPackages: ["grub", "grub-bios", "grub-efi"],
containerCache: p,
apkCache: m,
logger: b
})), await W(
f,
async (y) => {
const w = await y.tempFolder();
try {
n && await A(v(w.pathInHost, "config.cfg"), n), await y.run(
[
"grub-mkimage",
"-O",
d ?? "x86_64-efi",
"-o",
"core.img",
...n ? ["-c", "config.cfg"] : [],
...o ? ["-m", "memdisk.img"] : [],
...r ? ["-k", "pubkey.key"] : [],
...c ? ["--disable-cli"] : [],
...u ? ["--disable-shim-lock"] : [],
...s ? ["-p", s] : [],
"--",
...i ?? []
],
[
"--workingdir",
w.pathInContainer,
"-v",
`${e}:${w.pathInContainer}/core.img:rw`,
...o ? [
"-v",
`${o}:${w.pathInContainer}/memdisk.img:ro`
] : [],
...r ? ["-v", `${r}:${w.pathInContainer}/pubkey.key:ro`] : []
]
), t && (t = await I(t), await _t(
await y.resolve(`usr/lib/grub/${d}/boot.img`),
t
)), a && (await oa({
diskOffset: a,
outputCoreFile: e
}), t && await ra({
diskOffset: a,
outputBootFile: t
}));
} finally {
await w.remove();
}
},
{ logger: b }
);
}, be = ({
outputCoreFile: e,
outputBootFile: t,
biosSetupDiskOffset: a,
modules: i,
prefix: s,
config: n,
memdisk: r,
pubkey: o,
disableCli: c,
disableShimLock: u,
target: d,
...f
}) => {
const p = async (m) => {
const b = await m.resolve(e), y = t ? await m.resolve(t) : void 0, w = r ? await m.resolve(r) : void 0, $ = o ? await m.resolve(o) : void 0;
await ca({
outputCoreFile: b,
outputBootFile: y,
biosSetupDiskOffset: a,
modules: i,
prefix: s,
config: n,
memdisk: w,
pubkey: $,
disableCli: c,
disableShimLock: u,
target: d,
...f
});
};
return p.getCacheKey = async () => {
const m = k("sha256");
return m.update(
JSON.stringify({
outputCoreFile: e,
outputBootFile: t,
biosSetupDiskOffset: a,
modules: i,
prefix: s,
config: n,
memdisk: r,
pubkey: o,
disableCli: c,
disableShimLock: u,
target: d
})
), `GRUB-MKIMAGE-${m.digest("base64url")}`;
}, p;
}, ua = async ({
outputFile: e,
variables: t,
grubSource: a,
containerCache: i,
apkCache: s,
logger: n
}) => {
e = await I(e), a || (a = await J({
apkPackages: ["grub", "grub-bios", "grub-efi"],
containerCache: i,
apkCache: s,
logger: n
})), await W(
a,
async (r) => {
const o = await r.tempFolder(), c = ["--workingdir", o.pathInContainer];
try {
await r.run(
["grub-editenv", "grubenv", "create"],
c
), t && t.length > 0 && await r.run(
["grub-editenv", "grubenv", "set", ...t],
c
), await _t(v(o.pathInHost, "grubenv"), e);
} finally {
await o.remove();
}
},
{ logger: n }
);
}, la = ({
outputFile: e,
variables: t,
...a
}) => {
const i = async (s) => {
const n = await s.resolve(e);
await ua({
outputFile: n,
variables: t,
...a
});
};
return i.getCacheKey = async () => {
const s = k("sha256");
return s.update(
JSON.stringify({
outputFile: e,
variables: t
})
), `GRUB-MKENV-${s.digest("base64url")}`;
}, i;
}, Vt = async (e) => (await Q(e)).size, jt = async (e, t) => {
const a = Rt(e), i = te(t, { flags: "a" });
await It(a, i);
}, Ee = async ({
file: e,
metadataFile: t = `${e}.json`,
salt: a = "",
uuid: i = "00000000-0000-0000-0000-000000000000",
cryptsetupSource: s,
containerCache: n,
apkCache: r,
logger: o
}) => {
s || (s = await J({
apkPackages: ["cryptsetup"],
containerCache: n,
apkCache: r,
logger: o
})), await W(
s,
async (c) => {
const d = (await c.run(
[
"veritysetup",
"format",
"/image",
"/image.hash",
"--fec-device=/image.fec",
`--salt=${a}`,
`--uuid=${i}`
],
["-v", `${e}:/image:rw`]
)).stdout.toString("utf8"), [, f] = /Root hash:\s*([0-9a-f]{64})/.exec(d), p = await Vt(e), m = await c.resolve("/image.hash");
await jt(m, e);
const b = await Vt(e), y = await c.resolve("/image.fec");
await jt(y, e), await H(m), await H(y);
let w = {};
try {
w = JSON.parse(await x(t, "utf8"));
} catch {
}
const $ = {
...w,
rootHash: f,
hashOffset: p,
fecOffset: b
};
await A(t, JSON.stringify($));
},
{ logger: o }
);
}, ha = async ({
inputFolder: e,
squashfsToolsSource: t,
outputFile: a,
timestamp: i = 0,
veritySetup: s,
containerCache: n,
apkCache: r,
logger: o
}) => {
a = await I(a);
const c = St(e, a), u = [];
c.startsWith(`..${N}`) || u.push("-e", c), await q({
apkPackages: ["squashfs-tools"],
existingSource: t,
command: [
"mksquashfs",
"/in",
"/out",
"-noappend",
"-no-xattrs",
"-mkfs-time",
`${i}`,
"-all-time",
`${i}`,
...u
],
buildahRunOptions: [
"-v",
`${e}:/in:ro`,
"-v",
`${a}:/out:rw`
],
containerCache: n,
apkCache: r,
logger: o
}), s && await Ee({
file: a,
...s
});
}, ve = ({
inputFolder: e,
outputFile: t,
...a
}) => {
const i = async (s) => {
const n = await s.resolve(e), r = await s.resolve(t);
await ha({ inputFolder: n, outputFile: r, ...a });
};
return i.getCacheKey = async () => {
const s = k("sha256");
return s.update(
JSON.stringify({
inputFolder: e,
outputFile: t,
timestamp: a.timestamp ?? void 0,
veritySetup: a?.veritySetup ? {
salt: a.veritySetup.salt ?? void 0,
uuid: a.veritySetup.uuid ?? void 0
} : void 0
})
), `MKSQUASHFS-${s.digest("base64url")}`;
}, i;
}, fa = async ({
inputFolder: e,
mtoolsSource: t,
outputFileSize: a,
outputFile: i,
containerCache: s,
apkCache: n,
logger: r
}) => {
t || (t = await J({
apkPackages: ["mtools"],
containerCache: s,
apkCache: n,
logger: r
})), i = await I(i), await $t(i, a), await W(
t,
async (o) => {
await o.run(
["mformat", "-i", "/out", "-F", "::"],
["-v", `${i}:/out:rw`]
);
const c = await j(e);
c.length > 0 && await o.run(
[
"mcopy",
"-i",
"/out",
"-s",
"-b",
"-p",
...c.map((u) => `./${u}`),
"::/"
],
[
"-v",
`${e}:/in:ro`,
"-v",
`${i}:/out:rw`,
"--workingdir",
"/in"
]
);
},
{ logger: r }
);
}, da = ({
inputFolder: e,
outputFile: t,
...a
}) => {
const i = async (s) => {
const n = await s.resolve(e), r = await s.resolve(t);
await fa({ inputFolder: n, outputFile: r, ...a });
};
return i.getCacheKey = async () => {
const s = k("sha256");
return s.update(
JSON.stringify({
inputFolder: e,
outputFile: t,
outputFileSize: a.outputFileSize
})
), `MKVFATFS-${s.digest("base64url")}`;
}, i;
};
class wa extends le {
content;
constructor({
content: t = {},
...a
} = {}) {
super(a), this.content = t;
}
async _getDirectoryContent() {
return this.content;
}
}
class vt extends ce {
content;
constructor({
content: t = "",
...a
} = {}) {
super(a), this.content = t;
}
async _getContent() {
const t = this.content;
return Buffer.isBuffer(t) ? t : Buffer.from(t, "utf8");
}
}
class Va extends ue {
content;
constructor({
content: t,
...a
}) {
super(a), this.content = t;
}
async _getContent() {
return this.content;
}
}
const ja = (e) => {
const t = Ct(e), a = async (i) => {
for (const [
s,
{ recursive: n, mode: r, dmode: o = r, uid: c, gid: u }
] of t) {
const d = [await i.resolve(s)];
for (; d.length > 0; ) {
const f = d.shift(), p = await rt(f);
if (p.isDirectory() && (o != null && await yt(f, o), n)) {
const b = await j(f);
d.push(...b.map((y) => v(f, y)));
}
const m = p.isDirectory() ? o : r;
m != null && !p.isSymbolicLink() && await yt(f, m), (c != null || u != null) && await Yt(f, c ?? p.uid, u ?? p.gid);
}
}
};
return a.getCacheKey = async () => {
const i = k("sha256");
return i.update(JSON.stringify(e)), `SETATTRIBUTES-${i.digest("base64url")}`;
}, a;
}, pa = ["ed25519"], Wa = async ({
outputFolder: e,
opensshSource: t,
types: a = pa,
prefix: i = "ssh_host_",
suffix: s = "_key",
containerCache: n,
apkCache: r,
logger: o
}) => {
const c = {};
e = G(e), await U(e, { recursive: !0 });
let u = !1;
const d = await Promise.all(
a.map(async (f) => {
const p = `${i}${f}${s}`, m = `${p}.pub`;
let b = !1;
const y = v(e, m), w = v(e, p);
c[p] = new X(w, { mode: 384 }), c[m] = new X(y, { mode: 384 });
try {
const [$, C] = await Promise.all([
Q(y),
Q(w)
]);
if (!$.isFile() || !C.isFile())
throw new Error(
`${p}.pub or ${p} exists in ${e} and is not a regular file.`
);
b = !0;
} catch ($) {
if ($.code != "ENOENT")
throw $;
u = !0;
}
return { alreadyPresent: b, keyFile: p, type: f };
})
);
return u && await W(
t ?? await J({
apkPackages: ["openssh"],
containerCache: n,
apkCache: r,
logger: o
}),
async (f) => {
const p = await f.tempFolder();
try {
for (const { alreadyPresent: m, type: b, keyFile: y } of d)
m || await f.run(
["ssh-keygen", "-t", b, "-f", y, "-N", ""],
[
"-v",
`${e}:${p.pathInContainer}`,
"--workingdir",
p.pathInContainer
]
);
} finally {
await p.remove();
}
},
{ logger: o }
), c;
}, $e = async (e, t) => {
const a = /* @__PURE__ */ new Map();
try {
return await e(async (i) => {
if (typeof i != "string") {
let s = a.get(i.imageId);
s || (s = await ot.from(i.imageId, t), a.set(i.imageId, s)), i = await s.resolve(i.file);
}
return i;
});
} finally {
for (const i of a.values())
await i.remove();
}
}, qa = async (e, t) => {
t = await I(t), await $e(async (a) => {
await _t(await a(e), t);
});
}, ma = 33 * 1024 * 1024, ya = async ({
grubDiskDevice: e = "hd0",
grubEnvPartitionIndex: t,
grubEnvPath: a = "/grubenv",
grubExtraConfig: i = "",
grubSourceImage: s,
grubSourcePath: n = "/usr/lib/grub",
grubTimeout: r = 3,
linuxDiskDevice: o = "/dev/sda",
rootPartitionAIndex: c,
rootPartitionBIndex: u,
rootPartitionGrubCfg: d = "/boot/grub.cfg",
squashfsToolsSource: f,
apkCache: p,
containerCache: m,
logger: b
}) => {
const y = await T.from(s, {
containerCache: m,
logger: b
});
return await y.executeStep([
de({
[v(n, "grub.cfg")]: new vt({
content: `
insmod all_video
set envfile=(${e},gpt${t})/${a}
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=${r}
${i}
menuentry A --id=a {
set root=(${e},gpt${c})
set buildahcker_params="buildahcker_current=a buildahcker_grubenv_device=${o}${t} buildahcker_grubenv=${a} buildahcker_other_root=${o}${u} root=${o}${c}"
configfile ${d}
}
menuentry B --id=b {
set root=(${e},gpt${u})
set buildahcker_params="buildahcker_current=b buildahcker_grubenv_device=${o}${t} buildahcker_grubenv=${a} buildahcker_other_root=${o}${c} root=${o}${u}"
configfile ${d}
}
`
})
}),
ve({
inputFolder: n,
outputFile: "/buildahcker.img",
squashfsToolsSource: f,
apkCache: p,
containerCache: m,
logger: b
})
]), { imageId: y.imageId, file: "buildahcker.img" };
}, Za = async ({
grubSourceImage: e,
containerCache: t,
logger: a
}) => {
const i = await T.from(e, {
containerCache: t,
logger: a
});
return await i.executeStep([
V(["grub-editenv", "/buildahcker.img", "create"]),
V([
"grub-editenv",
"/buildahcker.img",
"set",
"buildahcker_stable=a",
"buildahcker_new=n"
])
]), { imageId: i.imageId, file: "buildahcker.img" };
}, ga = async ({
efiPartitionSize: e,
grubDiskDevice: t = "hd0",
grubEnvPath: a = "/grubenv",
grubPartitionIndex: i,
grubSourceImage: s,
mtoolsSource: n,
useEfi: r,
containerCache: o,
apkCache: c,
logger: u
}) => {
const d = await T.from(
"scratch",
// TODO: allow passing this as a parameter?
{
containerCache: o,
logger: u
}
);
return await d.executeStep([
la({
grubSource: s,
outputFile: v("efi", a),
variables: ["buildahcker_stable=a", "buildahcker_new=n"],
containerCache: o,
apkCache: c,
logger: u
})
]), r && await d.executeStep(
be({
outputCoreFile: "efi/EFI/boot/bootx64.efi",
grubSource: s,
target: "x86_64-efi",
modules: ["part_gpt", "squash4"],
prefix: `(${t},gpt${i})/`,
containerCache: o,
apkCache: c,
logger: u
})
), await d.executeStep(
da({
inputFolder: "efi",
outputFile: "/buildahcker.img",
outputFileSize: e,
mtoolsSource: n,
apkCache: c,
containerCache: o,
logger: u
})
), { imageId: d.imageId, file: "buildahcker.img" };
}, ba = async ({
grubDiskDevice: e = "hd0",
grubPartitionIndex: t,
grubSourceImage: a,
containerCache: i,
apkCache: s,
logger: n
}) => {
const r = await T.from("scratch", {
containerCache: i,
logger: n
});
return await r.executeStep([
be({
outputCoreFile: "buildahcker_grub_core.img",
outputBootFile: "buildahcker_grub_boot.img",
grubSource: a,
target: "i386-pc",
modules: ["biosdisk", "part_gpt", "squash4"],
prefix: `(${e},gpt${t})/`,
containerCache: i,
apkCache: s,
logger: n
})
]), {
core: { imageId: r.imageId, file: "buildahcker_grub_core.img" },
boot: { imageId: r.imageId, file: "buildahcker_grub_boot.img" }
};
}, Qa = async ({
biosBootPartitionSize: e,
bootType: t = "both",
efiPartitionSize: a,
grubDiskDevice: i,
grubEnvPath: s,
grubExtraConfig: n,
grubSourceImage: r,
grubSourcePath: o,
grubTimeout: c,
linuxDiskDevice: u,
mtoolsSource: d,
rootPartition: f,
rootPartitionGrubCfg: p,
rootPartitionSize: m,
squashfsToolsSource: b,
apkCache: y,
containerCache: w,
logger: $
}) => {
const C = t === "bios" || t === "both", F = t === "efi" || t === "both";
r || (r = await J({
apkPackages: [
...F ? ["grub-efi"] : [],
...C ? ["grub-bios"] : []
],
apkCache: y,
containerCache: w,
logger: $
}));
const B = {};
let R = 0;
const tt = C ? ++R : -1, et = ++R, l = ++R, S = ++R, h = ++R, g = C ? await ba({
grubDiskDevice: i,
grubPartitionIndex: l,
grubSourceImage: r,
apkCache: y,
containerCache: w,
logger: $
}) : void 0;
g && (B[tt] = {
type: z.BiosBoot,
name: "biosboot",
size: e,
file: g.core
}), a = Math.max(ma, a ?? 0), B[et] = {
name: F ? "efi" : "grubenv",
type: F ? z.EfiSystem : z.LinuxData,
file: await ga({
efiPartitionSize: a,
grubDiskDevice: i,
grubEnvPath: s,
grubPartitionIndex: l,
grubSourceImage: r,
mtoolsSource: d,
useEfi: F,
apkCache: y,
containerCache: w,
logger: $
})
}, B[l] = {
name: "grub",
type: z.LinuxData,
file: await ya({
grubDiskDevice: i,
grubEnvPartitionIndex: et,
grubExtraConfig: n,
grubSourceImage: r,
grubSourcePath: o,
grubTimeout: c,
linuxDiskDevice: u,
rootPartitionAIndex: S,
rootPartitionBIndex: h,
rootPartitionGrubCfg: p,
squashfsToolsSource: b,
apkCache: y,
containerCache: w,
logger: $
})
}, B[S] = {
name: "systemA",
type: z.LinuxData,
size: m,
file: f
}, B[h] = {
name: "systemB",
type: z.LinuxData,
size: m
};
const E = await T.from("scratch", {
containerCache: w,
logger: $
}), _ = async (O) => await $e(async (at) => {
const ct = await O.resolve("buildahcker.img"), At = [];
for (let L = 1; L <= R; L++) {
const D = B[L];
At.push({
name: D.name,
size: D.size ?? (await Q(await at(D.file))).size,
type: D.type
});
}
const Ut = await ta({
partitions: At,
outputFile: ct
}), Ot = [];
for (let L = 1; L <= R; L++) {
const D = B[L];
D.file && Ot.push({
inputFile: await at(D.file),
output: Ut[L - 1]
});
}
await xt({
outputFile: ct,
partitions: Ot
}), g && await na({
imageFile: ct,
bootFile: await at(g.boot),
coreFile: await at(g.core),
partition: Ut[tt - 1]
});
});
return _.getCacheKey = async () => {
const O = k("sha256");
return O.update(JSON.stringify(B)), `ABPARTITIONSDISK-${O.digest("base64url")}`;
}, await E.executeStep(_), {
imageId: E.imageId,
file: "buildahcker.img"
};
}, Xa = async ({
kernelCmdline: e,
rootPartitionGrubCfg: t = "/boot/grub.cfg",
sourceRootImage: a,
sourceRootInitrdPath: i = "/boot/initramfs-lts",
sourceRootKernelPath: s = "/boot/vmlinuz-lts",
squashfsToolsSource: n,
updateToolPath: r = "/sbin/buildahckerABTool",
apkCache: o,
containerCache: c,
logger: u
}) => {
const d = await T.from(a, {
containerCache: c,
logger: u
});
return await d.executeStep([
de({
[t]: new vt({
content: `linux ${s} $buildahcker_params${e ? ` ${e}` : ""}${i ? `
initrd ${i}` : ""}
`
}),
[r]: new vt({
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
})
}),
ve({
inputFolder: ".",
outputFile: "/buildahcker.img",
squashfsToolsSource: n,
apkCache: o,
containerCache: c,
logger: u
})
]), { imageId: d.imageId, file: "buildahcker.img" };
};
class Tt {
#t;
#e;
#a;
constructor(t, a) {
this.#t = t, this.#e = a, this.#a = new wa({
content: {
cert: t,
key: a
}
});
}
async certificatePath() {
return this.#t.sourceFilePath;
}
async privateKeyPath() {
return this.#e.sourceFilePath;
}
async getHash() {
return await this.#a.getContentHash();
}
static from(t, a) {
return new Tt(
new X(t, { uid: 0, gid: 0, mode: 384 }),
new X(a, { uid: 0, gid: 0, mode: 384 })
);
}
}
const Ea = async ({
inputFile: e,
outputFile: t,
key: a,
sbsignSource: i,
containerCache: s,
apkCache: n,
logger: r
}) => {
t || (t = `${e}.signed`), t = await I(t), await q({
apkPackages: ["sbsigntool"],
existingSource: i,
command: [
"sbsign",
"--key",
"/in.key",
"--cert",
"/in.crt",
"--output",
"/out.efi",
"/in.efi"
],
buildahRunOptions: [
"-v",
`${e}:/in.efi:ro`,
"-v",
`${await a.certificatePath()}:/in.crt:ro`,
"-v",
`${await a.privateKeyPath()}:/in.key:ro`,
"-v",
`${t}:/out.efi:rw`
],
containerCache: s,
apkCache: n,
logger: r
});
}, va = ({
inputFile: e,
outputFile: t,
key: a,
...i
}) => {
t || (t = `${e}.signed`);
const s = async (n) => {
const r = e === t, o = r ? `${e}.unsigned` : e, c = await n.resolve(o), u = await n.resolve(t);
r && (await Re(u, c), await $t(u)), await Ea({ inputFile: c, outputFile: u, key: a, ...i }), r && await H(c);
};
return s.getCacheKey = async () => {
const n = k("sha256");
return n.update(
JSON.stringify({
inputFile: e,
outputFile: t
})
), n.update(await a.getHash()), `SBSIGN-${n.digest("base64url")}`;
}, s;
}, Z = 1024, $a = Z + 12, wt = 48, Wt = 4, _a = [
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,
3878