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
149 lines (139 loc) • 44.9 kB
JavaScript
;Object.defineProperty(exports,Symbol.toStringTag,{value:"Module"});const l=require("fs/promises"),f=require("path"),Fe=require("child_process"),Ce=require("stream"),ht=require("os"),S=require("crypto"),O=require("fs"),Be=require("util"),dt=require("stream/promises");class ut extends Ce.Writable{#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 Jt 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 U=async(i,{logger:t}={},e={})=>{const a=Fe.spawn(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 ut;a.stdout.pipe(s);const r=new ut;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 Jt(i,a.exitCode,n,o);return{stdout:n,stderr:o}},qt=async(i,t,e=!1)=>{const a=f.basename(t);if(a==="."||a===".."||a==="")throw new Error(`Invalid path: ${t}`);return f.join(await ft(i,f.dirname(t),e),a)},ft=async(i,t,e=!1)=>{const a=new Set,s=f.normalize(t).split(f.sep);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=f.join(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 l.readlink(o,"utf8"),d=f.normalize(c).split(f.sep);f.isAbsolute(c)?(s.splice(0,r+1,...d),r=-1):(s.splice(r,1,...d),r--)}catch{}}return f.join(i,...s)};class j{constructor(t,e){this.options=e,this.#t=t}#t=null;#e=null;static async from(t,e){const s=(await U(["buildah","from",t],e)).stdout.toString("utf8").trim()||null;if(!s)throw new Error(`Failed to create a container from ${t}.`);return new j(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 U(["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=f.relative(this.mountPath,t).split(f.sep);if(e[0]==="..")throw new Error("Path is not in container");return e.unshift(""),e.join(f.sep)}async tempFolder(){const t=await this.resolve("tmp"),e=await l.mkdtemp(f.join(t,"buildahcker-"));return{pathInHost:e,pathInContainer:this.toPathInContainer(e),remove:async()=>{await l.rm(e,{recursive:!0,force:!0})}}}async resolve(t,e){return await this.mount(),await ft(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 U(["buildah","rm",t],this.options))}async run(t,e=[]){return await U(["buildah","run",...e,"--",this.name,...t],this.options)}async commit({timestamp:t=Date.now()}={}){return(await U(["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 pt=async(i,t,e)=>{const a=await j.from(i,e);try{return await t(a)}finally{await a.remove()}},z=async(i,t,e)=>typeof i=="string"?await pt(i,t,e):await t(i),Vt=async({source:i,command:t,buildahRunOptions:e,...a})=>await z(i,async s=>await s.run(t,e),a),Ut=/^[\w-]+$/;class Wt{cachePath;constructor(t){this.cachePath=f.resolve(t)}#t(t,e){if(!Ut.test(t)||!Ut.test(e))throw new Error(`Unsafe image id ${JSON.stringify(t)} or operation key ${JSON.stringify(e)}.`);return f.join(this.cachePath,t,e)}async getEntry(t,e){const a=this.#t(t,e);try{return(await l.readFile(a,"utf8")).trim()||void 0}catch{}}async setEntry(t,e,a){const s=this.#t(t,e);await l.mkdir(f.dirname(s),{recursive:!0}),await l.writeFile(s,a,"utf8")}}let et;const Ae=()=>{if(!et){const i=f.join(ht.homedir(),".buildahcker","cache","containers");et=new Wt(i)}return et},Ue=async(i,t,e)=>{const a=await U(["buildah","inspect",...t?["--type",t]:[],"--",i],e);return JSON.parse(a.stdout.toString("utf8"))},lt=async(i,t)=>(await U(["buildah","inspect","--format","{{.FromImageID}}","--type","image","--",i],t)).stdout.toString("utf8").trim();class B{constructor(t,e){this.options=e,this.#t=t}#t;static async from(t,e){const a=t==="scratch"?t:await lt(t,e);if(!a)throw new Error(`Could not get information about image ${t}`);return new B(a,e)}clone(){return new B(this.#t,this.options)}get imageId(){return this.#t}async tag(t,e={}){await U(["buildah","tag",this.#t,t],e)}async#e(t,e){return await pt(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 lt(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 wt=i=>{const t=f.normalize(i).split(f.sep);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(f.sep)},Zt=([i,t])=>[wt(i),t],Te=(i,t)=>i<t?-1:i>t?1:0,xe=([i],[t])=>Te(i,t),mt=i=>new Map(Object.entries(i).map(Zt).sort(xe)),yt=async i=>{const t=S.createHash("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()},He=i=>(i&l.constants.S_IFMT)===l.constants.S_IFLNK;class bt{async getHash(){const t=S.createHash("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 l.lstat(t)).isDirectory()||await l.rm(t)}catch(a){if(a.code!=="ENOENT")throw a}await this.writeContentTo(t);const e=await this.getAttributes();await l.lchown(t,e.uid,e.gid),He(e.mode)||await l.chmod(t,e.mode)}}class Qt extends bt{#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 Xt extends bt{_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 Q extends Xt{#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 gt extends Q{constructor(t){super(t),this._modeAllow=~l.constants.S_IFMT,this._modeMandatory=l.constants.S_IFREG}async _getContentHash(){const t=S.createHash("sha256");return t.update(await this.getContent()),t.digest()}async writeContentTo(t){await l.writeFile(t,await this.getContent())}}class Et extends Q{constructor(t){super(t),this._modeAllow=0,this._modeMandatory=l.constants.S_IFLNK|511}async _getContentHash(){const t=S.createHash("sha256");return t.update(await this.getContent()),t.digest()}async writeContentTo(t){await l.symlink(await this.getContent(),t)}}class vt extends Q{constructor(t){super({mode:493,...t}),this._modeAllow=~l.constants.S_IFMT,this._modeMandatory=l.constants.S_IFDIR}async _getContent(){return mt(await this._getDirectoryContent())}async _getContentHash(){return await yt(await this.getContent())}async writeContentTo(t){try{await l.mkdir(t)}catch(e){if(e.code!=="EEXIST")throw e}for(const[e,a]of await this.getContent())await a.writeTo(f.join(t,e))}}class Yt extends Qt{constructor(t,e){super(),this.sourceFilePath=t,this.options=e,this.sourceFilePath=f.resolve(t)}async _getFile(){const t=this.sourceFilePath,e=await l.lstat(t),a={uid:e.uid,gid:e.gid,mode:e.mode,...this.options?.overrideAttributes};if(e.isDirectory())return new te(t,a,this.options);if(e.isFile())return new M(t,a);if(e.isSymbolicLink())return new ee(t,a);throw new Error(`Unsupported file type ${e.mode.toString(8)}`)}}class te extends vt{constructor(t,e,a){super(e),this.sourceFilePath=t,this.options=a,this.sourceFilePath=f.resolve(t)}async _getDirectoryContent(){const t=this.sourceFilePath,e={},a=await l.readdir(t);for(const s of a)e[s]=new Yt(f.join(t,s),this.options);return e}}class M extends gt{constructor(t,e){super(e),this.sourceFilePath=t,this.sourceFilePath=f.resolve(t)}async _getContent(){return await l.readFile(this.sourceFilePath)}}class ee extends Et{constructor(t,e){super(e),this.sourceFilePath=t,this.sourceFilePath=f.resolve(t)}async _getContent(){return await l.readlink(this.sourceFilePath)}}class ae extends vt{content;constructor({content:t={},...e}={}){super(e),this.content=t}async _getDirectoryContent(){return this.content}}class Z extends gt{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 Le extends Et{content;constructor({content:t,...e}){super(e),this.content=t}async _getContent(){return this.content}}const kt=i=>{const t=mt(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},$t=i=>{i=i.map(wt).sort();const t=async e=>{const a=await e.mount();for(const s of i){const r=await qt(a,s);await l.rm(r,{force:!0,recursive:!0})}};return t.getCacheKey=async()=>{const e=S.createHash("sha256");return e.update(JSON.stringify(i)),`RM-FILES-${e.digest("base64url")}`},t},N=(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.createHash("sha256");return a.update(JSON.stringify(i)),a.update(JSON.stringify(t?.buildahArgs??[])),a.update(JSON.stringify(t?.extraHashData??[])),`RUN-${a.digest("base64url")}`},e},ie=(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 l.mkdir(t,{recursive:!0})}),N(["apk","add",...i],e)};let at;const Pe=()=>(at||(at=f.join(ht.homedir(),".buildahcker","cache","apk")),at),Tt=()=>({directories:[],files:[]});class se{#t=Tt();#e=".";packagesMap=new Map;addLine(t){if(t.length===0){this.#t=Tt(),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=f.posix.join(this.#e,a);this.#t.files.push(s);break}}}}const re=async i=>{const t=await ft(i,"lib/apk/db/installed"),e=await l.open(t);try{const a=new se;for await(const s of e.readLines())a.addLine(s);return a}finally{await e.close()}},ne=async i=>{try{(await l.readdir(i)).length===0&&await l.rmdir(i)}catch(t){if(t.code!=="ENOENT")throw t}},T=async i=>(i=f.resolve(i),await l.mkdir(f.dirname(i),{recursive:!0}),await l.writeFile(i,""),i),oe=async(i,t)=>typeof i=="number"?i:await new Promise((e,a)=>O.open(i,t,(s,r)=>s?a(s):e(r))),ce=async(i,t)=>typeof t=="number"?void 0:new Promise(e=>O.close(i,e)),Oe=Be.promisify(O.read),De=async(i,t,e)=>{const{buffer:a,bytesRead:s}=await Oe(i,{buffer:Buffer.alloc(e),position:t});if(s!==e)throw new Error(`Could not read ${s} bytes from file`);return a},ue=(i,t)=>{i=i.sort();const e=async a=>{const s=await a.mount(),r=await re(s),n=[],o=[];for(const c of i){const d=r.packagesMap.get(c);if(!d){t?.write(`Package not installed: ${c}
`);continue}n.push(...d.files),o.push(...d.directories)}for(const c of n){const d=await a.resolveParent(c);t?.write(`rm ${c}
`),await l.rm(d,{force:!0})}o.sort().reverse();for(const c of o){const d=await a.resolveParent(c);await ne(d)}};return e.getCacheKey=async()=>{const a=S.createHash("sha256");return a.update(JSON.stringify(i)),`APK-REMOVE-${a.digest("base64url")}`},e},Ke=(i=[],t)=>[ue(["apk-tools",...i],t),$t(["var/lib/apk","lib/apk","etc/apk","usr/share/apk","var/cache/apk"])],D=async({baseImage:i="alpine",apkPackages:t,commitOptions:e,logger:a,apkCache:s,containerCache:r})=>{const n=await B.from(i,{containerCache:r,commitOptions:e,logger:a});return await n.executeStep(ie(t,{apkCache:s})),n.imageId},X=async({existingSource:i,command:t,buildahRunOptions:e,...a})=>await Vt({source:i??await D(a),command:t,buildahRunOptions:e,logger:a.logger}),le=async({outputCoreFile:i,outputBootFile:t,modules:e,prefix:a,config:s,target:r,grubSource:n,containerCache:o,apkCache:c,logger:d})=>{i=await T(i),n||(n=await D({apkPackages:["grub","grub-bios","grub-efi"],containerCache:o,apkCache:c,logger:d})),await z(n,async b=>{const w=await b.tempFolder();try{s&&await l.writeFile(f.join(w.pathInHost,"config.cfg"),s),await b.run(["grub-mkimage","-O",r??"x86_64-efi","-p",a??"/boot/grub","-o","core.img",...s?["-c","config.cfg"]:[],"--",...e??[]],["--workingdir",w.pathInContainer,"-v",`${i}:${w.pathInContainer}/core.img:rw`]),t&&(t=await T(t),await l.cp(await b.resolve(`usr/lib/grub/${r}/boot.img`),t))}finally{await w.remove()}},{logger:d})},_t=({outputCoreFile:i,outputBootFile:t,modules:e,prefix:a,config:s,target:r,...n})=>{const o=async c=>{const d=await c.resolve(i),b=t?await c.resolve(t):void 0;await le({outputCoreFile:d,outputBootFile:b,modules:e,prefix:a,config:s,target:r,...n})};return o.getCacheKey=async()=>{const c=S.createHash("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 it={};/*! crc32.js (C) 2014-present SheetJS -- http://sheetjs.com */var xt;function Ne(){return xt||(xt=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,I=new Array(256),h=0;h!=256;++h)u=h,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,I[h]=u;return typeof Int32Array<"u"?new Int32Array(I):I}var a=e();function s(u){var I=0,h=0,p=0,y=typeof Int32Array<"u"?new Int32Array(4096):new Array(4096);for(p=0;p!=256;++p)y[p]=u[p];for(p=0;p!=256;++p)for(h=u[p],I=256+p;I<4096;I+=256)h=y[I]=h>>>8^u[h&255];var $=[];for(p=1;p!=16;++p)$[p-1]=typeof Int32Array<"u"?y.subarray(p*256,p*256+256):y.slice(p*256,p*256+256);return $}var r=s(a),n=r[0],o=r[1],c=r[2],d=r[3],b=r[4],w=r[5],g=r[6],k=r[7],_=r[8],E=r[9],m=r[10],v=r[11],C=r[12],A=r[13],F=r[14];function R(u,I){for(var h=I^-1,p=0,y=u.length;p<y;)h=h>>>8^a[(h^u.charCodeAt(p++))&255];return~h}function J(u,I){for(var h=I^-1,p=u.length-15,y=0;y<p;)h=F[u[y++]^h&255]^A[u[y++]^h>>8&255]^C[u[y++]^h>>16&255]^v[u[y++]^h>>>24]^m[u[y++]]^E[u[y++]]^_[u[y++]]^k[u[y++]]^g[u[y++]]^w[u[y++]]^b[u[y++]]^d[u[y++]]^c[u[y++]]^o[u[y++]]^n[u[y++]]^a[u[y++]];for(p+=15;y<p;)h=h>>>8^a[(h^u[y++])&255];return~h}function q(u,I){for(var h=I^-1,p=0,y=u.length,$=0,x=0;p<y;)$=u.charCodeAt(p++),$<128?h=h>>>8^a[(h^$)&255]:$<2048?(h=h>>>8^a[(h^(192|$>>6&31))&255],h=h>>>8^a[(h^(128|$&63))&255]):$>=55296&&$<57344?($=($&1023)+64,x=u.charCodeAt(p++)&1023,h=h>>>8^a[(h^(240|$>>8&7))&255],h=h>>>8^a[(h^(128|$>>2&63))&255],h=h>>>8^a[(h^(128|x>>6&15|($&3)<<4))&255],h=h>>>8^a[(h^(128|x&63))&255]):(h=h>>>8^a[(h^(224|$>>12&15))&255],h=h>>>8^a[(h^(128|$>>6&63))&255],h=h>>>8^a[(h^(128|$&63))&255]);return~h}t.table=a,t.bstr=R,t.buf=J,t.str=q})}(it)),it}var st=Ne();const Y=async i=>{const t=i.outputFile,e=await oe(t,"r+");try{for(const a of i.partitions){const s=O.createWriteStream("",{fd:e,start:a.output.offset,autoClose:!1});if("inputFile"in a){const r=a.input?.offset??0,n=a.input?.size??(await l.stat(a.inputFile)).size-r;if(n>a.output.size)throw new Error(`Partition too small for content: ${n} > ${a.output.size}`);const o=O.createReadStream(a.inputFile,{start:r,end:r+n,autoClose:!1});try{await dt.pipeline(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 P=(i=>(i.EfiSystem="C12A7328-F81F-11D2-BA4B-00A0C93EC93B",i.BiosBoot="21686148-6449-6E6F-744E-656564454649",i.LinuxData="0FC63DAF-8483-4772-8E79-3D69D8477DE4",i))(P||{});const rt=(i,t,e)=>{const a=Buffer.from(i.replaceAll("-",""),"hex");if(a.length!=16)throw new Error(`Invalid GUID: ${i}`);a.copy(t,e)},he=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 A=o,F=Math.ceil(C.size/512/2048)*2048;o+=F,t.push({offset:A*512,size:F*512});const R=Buffer.alloc(128);rt(C.type,R,0),rt(C.guid??S.randomUUID(),R,16),R.writeBigInt64LE(BigInt(A),32),R.writeBigInt64LE(BigInt(A+F-1),40),R.write(C.name,56,72,"utf-16le"),R.copy(n,c*128),c++}o+=r;const d=await T(i.outputFile);await l.truncate(d,o*512);const b=[],w=Buffer.alloc(512);w.write("000200",447,"hex"),w.writeUInt8(238,450),w.write("FFFFFF",447,"hex"),w.writeUint32LE(1,454),w.writeUint32LE(Math.min(4294967295,o-1),458),w.writeUint8(85,510),w.writeUint8(170,511);const g=1,k=o-1,_=2,E=o-r,m=Buffer.alloc(512);m.write("EFI PART",0,"ascii"),m.write("00000100",8,"hex"),m.writeUInt32LE(92,12),m.writeBigUInt64LE(BigInt(g),24),m.writeBigUInt64LE(BigInt(k),32),m.writeBigUInt64LE(BigInt(r),40),m.writeBigUInt64LE(BigInt(E-1),48),rt(i.guid??S.randomUUID(),m,56),m.writeBigUInt64LE(BigInt(_),72),m.writeUInt32LE(128,80),m.writeUInt32LE(128,84),m.writeInt32LE(st.buf(n),88),m.writeInt32LE(st.buf(m.subarray(0,92)),16);const v=Buffer.from(m);return v.writeBigUInt64LE(BigInt(k),24),v.writeBigUInt64LE(BigInt(g),32),v.writeBigUInt64LE(BigInt(E),72),v.writeUint32LE(0,16),v.writeInt32LE(st.buf(v.subarray(0,92)),16),b.push({inputBuffer:w,output:{offset:0,size:w.length}},{inputBuffer:m,output:{offset:g*512,size:m.length}},{inputBuffer:n,output:{offset:_*512,size:n.length}},{inputBuffer:n,output:{offset:E*512,size:n.length}},{inputBuffer:v,output:{offset:k*512,size:v.length}}),await Y({outputFile:d,partitions:b}),t},L=512,Ht=9,Lt=3,ze=90,Me=92,Pt=440,Ge=510,Ot=102,de=12,W=L-de,nt=W-de,je=2048,Dt=(i,t,e)=>i.writeBigUint64LE(e,t),Kt=(i,t,e)=>i.writeUint16LE(e,t+8),Nt=(i,t,e)=>i.writeUint16LE(e,t+10),fe=async i=>{const t=i.imageFile,e=await oe(t,"r+");try{const a=await De(e,0,L),s=await l.readFile(i.bootFile);let r=await l.readFile(i.coreFile);if(s.length!==L)throw new Error(`The boot file should have a size of ${L} bytes.`);const n=r.length%L;n!==0&&(r=Buffer.concat([r,Buffer.alloc(L-n)])),a.copy(s,Lt,Lt,ze),a.copy(s,Pt,Pt,Ge);const o=BigInt(i.partition.offset>>Ht);s.writeBigUint64LE(o,Me),s.writeUInt8(144,Ot),s.writeUInt8(144,Ot+1),Dt(r,W,o+1n),Kt(r,W,(r.length>>Ht)-1),Nt(r,W,je+(L>>4)),Dt(r,nt,0n),Kt(r,nt,0),Nt(r,nt,0),await Y({outputFile:e,partitions:[{inputBuffer:s,output:{offset:0,size:L}},{inputBuffer:r,output:i.partition}]})}finally{await ce(e,t)}},pe=async({outputFile:i,variables:t,grubSource:e,containerCache:a,apkCache:s,logger:r})=>{i=await T(i),e||(e=await D({apkPackages:["grub","grub-bios","grub-efi"],containerCache:a,apkCache:s,logger:r})),await z(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 l.cp(f.join(o.pathInHost,"grubenv"),i)}finally{await o.remove()}},{logger:r})},we=({outputFile:i,variables:t,...e})=>{const a=async s=>{const r=await s.resolve(i);await pe({outputFile:r,variables:t,...e})};return a.getCacheKey=async()=>{const s=S.createHash("sha256");return s.update(JSON.stringify({outputFile:i,variables:t})),`GRUB-MKENV-${s.digest("base64url")}`},a},zt=async i=>(await l.stat(i)).size,Mt=async(i,t)=>{const e=O.createReadStream(i),a=O.createWriteStream(t,{flags:"a"});await dt.pipeline(e,a)},St=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 D({apkPackages:["cryptsetup"],containerCache:r,apkCache:n,logger:o})),await z(s,async c=>{const b=(await c.run(["veritysetup","format","/image","/image.hash","--fec-device=/image.fec",`--salt=${e}`,`--uuid=${a}`],["-v",`${i}:/image:rw`])).stdout.toString("utf8"),[,w]=/Root hash:\s*([0-9a-f]{64})/.exec(b),g=await zt(i),k=await c.resolve("/image.hash");await Mt(k,i);const _=await zt(i),E=await c.resolve("/image.fec");await Mt(E,i),await l.rm(k),await l.rm(E);let m={};try{m=JSON.parse(await l.readFile(t,"utf8"))}catch{}const v={...m,rootHash:w,hashOffset:g,fecOffset:_};await l.writeFile(t,JSON.stringify(v))},{logger:o})},me=async({inputFolder:i,squashfsToolsSource:t,outputFile:e,timestamp:a=0,veritySetup:s,containerCache:r,apkCache:n,logger:o})=>{e=await T(e);const c=f.relative(i,e),d=[];c.startsWith(`..${f.sep}`)||d.push("-e",c),await X({apkPackages:["squashfs-tools"],existingSource:t,command:["mksquashfs","/in","/out","-noappend","-no-xattrs","-mkfs-time",`${a}`,"-all-time",`${a}`,...d],buildahRunOptions:["-v",`${i}:/in:ro`,"-v",`${e}:/out:rw`],containerCache:r,apkCache:n,logger:o}),s&&await St({file:e,...s})},It=({inputFolder:i,outputFile:t,...e})=>{const a=async s=>{const r=await s.resolve(i),n=await s.resolve(t);await me({inputFolder:r,outputFile:n,...e})};return a.getCacheKey=async()=>{const s=S.createHash("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},ye=async({inputFolder:i,mtoolsSource:t,outputFileSize:e,outputFile:a,containerCache:s,apkCache:r,logger:n})=>{t||(t=await D({apkPackages:["mtools"],containerCache:s,apkCache:r,logger:n})),a=await T(a),await l.truncate(a,e),await z(t,async o=>{await o.run(["mformat","-i","/out","-F","::"],["-v",`${a}:/out:rw`]);const c=await l.readdir(i);c.length>0&&await o.run(["mcopy","-i","/out","-s","-b","-p",...c.map(d=>`./${d}`),"::/"],["-v",`${i}:/in:ro`,"-v",`${a}:/out:rw`,"--workingdir","/in"])},{logger:n})},be=({inputFolder:i,outputFile:t,...e})=>{const a=async s=>{const r=await s.resolve(i),n=await s.resolve(t);await ye({inputFolder:r,outputFile:n,...e})};return a.getCacheKey=async()=>{const s=S.createHash("sha256");return s.update(JSON.stringify({inputFolder:i,outputFile:t,outputFileSize:e.outputFileSize})),`MKVFATFS-${s.digest("base64url")}`},a},ge=["ed25519"],Je=async({outputFolder:i,opensshSource:t,types:e=ge,prefix:a="ssh_host_",suffix:s="_key",containerCache:r,apkCache:n,logger:o})=>{const c={};await l.mkdir(i,{recursive:!0});let d=!1;const b=await Promise.all(e.map(async w=>{const g=`${a}${w}${s}`,k=`${g}.pub`;let _=!1;const E=f.join(i,k),m=f.join(i,g);c[g]=new M(m,{mode:384}),c[k]=new M(E,{mode:384});try{const[v,C]=await Promise.all([l.stat(E),l.stat(m)]);if(!v.isFile()||!C.isFile())throw new Error(`${g}.pub or ${g} exists in ${i} and is not a regular file.`);_=!0}catch(v){if(v.code!="ENOENT")throw v;d=!0}return{alreadyPresent:_,keyFile:g,type:w}}));return d&&await z(t??await D({apkPackages:["openssh"],containerCache:r,apkCache:n,logger:o}),async w=>{const g=await w.tempFolder();try{for(const{alreadyPresent:k,type:_,keyFile:E}of b)k||await w.run(["ssh-keygen","-t",_,"-f",E,"-N",""],["-v",`${i}:${g.pathInContainer}`,"--workingdir",g.pathInContainer])}finally{await g.remove()}},{logger:o}),c},Rt=async(i,t)=>{const e=new Map;try{return await i(async a=>{if(typeof a!="string"){let s=e.get(a.imageId);s||(s=await j.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()}},qe=async(i,t)=>{t=await T(t),await Rt(async e=>{await l.cp(await e(i),t)})},Ve=33*1024*1024,Ee=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:d,rootPartitionGrubCfg:b="/boot/grub.cfg",squashfsToolsSource:w,apkCache:g,containerCache:k,logger:_})=>{const E=await B.from(s,{containerCache:k,logger:_});return await E.executeStep([kt({[f.join(r,"grub.cfg")]:new Z({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}${d} root=${o}${c}"
configfile ${b}
}
menuentry B --id=b {
set root=(${i},gpt${d})
set buildahcker_params="buildahcker_current=b buildahcker_grubenv_device=${o}${t} buildahcker_grubenv=${e} buildahcker_other_root=${o}${c} root=${o}${d}"
configfile ${b}
}
`})}),It({inputFolder:r,outputFile:"/buildahcker.img",squashfsToolsSource:w,apkCache:g,containerCache:k,logger:_})]),{imageId:E.imageId,file:"buildahcker.img"}},We=async({grubSourceImage:i,containerCache:t,logger:e})=>{const a=await B.from(i,{containerCache:t,logger:e});return await a.executeStep([N(["grub-editenv","/buildahcker.img","create"]),N(["grub-editenv","/buildahcker.img","set","buildahcker_stable=a","buildahcker_new=n"])]),{imageId:a.imageId,file:"buildahcker.img"}},ve=async({efiPartitionSize:i,grubDiskDevice:t="hd0",grubEnvPath:e="/grubenv",grubPartitionIndex:a,grubSourceImage:s,mtoolsSource:r,useEfi:n,containerCache:o,apkCache:c,logger:d})=>{const b=await B.from("scratch",{containerCache:o,logger:d});return await b.executeStep([we({grubSource:s,outputFile:f.join("efi",e),variables:["buildahcker_stable=a","buildahcker_new=n"],containerCache:o,apkCache:c,logger:d})]),n&&await b.executeStep(_t({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:d})),await b.executeStep(be({inputFolder:"efi",outputFile:"/buildahcker.img",outputFileSize:i,mtoolsSource:r,apkCache:c,containerCache:o,logger:d})),{imageId:b.imageId,file:"buildahcker.img"}},ke=async({grubDiskDevice:i="hd0",grubPartitionIndex:t,grubSourceImage:e,containerCache:a,apkCache:s,logger:r})=>{const n=await B.from("scratch",{containerCache:a,logger:r});return await n.executeStep([_t({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"}}},Ze=async({biosBootPartitionSize:i,bootType:t="both",efiPartitionSize:e,grubDiskDevice:a,grubEnvPath:s,grubExtraConfig:r,grubSourceImage:n,grubSourcePath:o,grubTimeout:c,linuxDiskDevice:d,mtoolsSource:b,rootPartition:w,rootPartitionGrubCfg:g,rootPartitionSize:k,squashfsToolsSource:_,apkCache:E,containerCache:m,logger:v})=>{const C=t==="bios"||t==="both",A=t==="efi"||t==="both";n||(n=await D({apkPackages:[...A?["grub-efi"]:[],...C?["grub-bios"]:[]],apkCache:E,containerCache:m,logger:v}));const F={};let R=0;const J=C?++R:-1,q=++R,u=++R,I=++R,h=++R,p=C?await ke({grubDiskDevice:a,grubPartitionIndex:u,grubSourceImage:n,apkCache:E,containerCache:m,logger:v}):void 0;p&&(F[J]={type:P.BiosBoot,name:"biosboot",size:i,file:p.core}),e=Math.max(Ve,e??0),F[q]={name:A?"efi":"grubenv",type:A?P.EfiSystem:P.LinuxData,file:await ve({efiPartitionSize:e,grubDiskDevice:a,grubEnvPath:s,grubPartitionIndex:u,grubSourceImage:n,mtoolsSource:b,useEfi:A,apkCache:E,containerCache:m,logger:v})},F[u]={name:"grub",type:P.LinuxData,file:await Ee({grubDiskDevice:a,grubEnvPartitionIndex:q,grubExtraConfig:r,grubSourceImage:n,grubSourcePath:o,grubTimeout:c,linuxDiskDevice:d,rootPartitionAIndex:I,rootPartitionBIndex:h,rootPartitionGrubCfg:g,squashfsToolsSource:_,apkCache:E,containerCache:m,logger:v})},F[I]={name:"systemA",type:P.LinuxData,size:k,file:w},F[h]={name:"systemB",type:P.LinuxData,size:k};const y=await B.from("scratch",{containerCache:m,logger:v}),$=async x=>await Rt(async V=>{const tt=await x.resolve("buildahcker.img"),Ct=[];for(let H=1;H<=R;H++){const K=F[H];Ct.push({name:K.name,size:K.size??(await l.stat(await V(K.file))).size,type:K.type})}const Bt=await he({partitions:Ct,outputFile:tt}),At=[];for(let H=1;H<=R;H++){const K=F[H];K.file&&At.push({inputFile:await V(K.file),output:Bt[H-1]})}await Y({outputFile:tt,partitions:At}),p&&await fe({imageFile:tt,bootFile:await V(p.boot),coreFile:await V(p.core),partition:Bt[J-1]})});return $.getCacheKey=async()=>{const x=S.createHash("sha256");return x.update(JSON.stringify(F)),`ABPARTITIONSDISK-${x.digest("base64url")}`},await y.executeStep($),{imageId:y.imageId,file:"buildahcker.img"}},Qe=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:d})=>{const b=await B.from(e,{containerCache:c,logger:d});return await b.executeStep([kt({[t]:new Z({content:`linux ${s} $buildahcker_params${i?` ${i}`:""}${a?`
initrd ${a}`:""}
`}),[n]:new Z({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})}),It({inputFolder:".",outputFile:"/buildahcker.img",squashfsToolsSource:r,apkCache:o,containerCache:c,logger:d})]),{imageId:b.imageId,file:"buildahcker.img"}};class Ft{#t;#e;#a;constructor(t,e){this.#t=t,this.#e=e,this.#a=new ae({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 Ft(new M(t,{uid:0,gid:0,mode:384}),new M(e,{uid:0,gid:0,mode:384}))}}const $e=async({inputFile:i,outputFile:t,key:e,sbsignSource:a,containerCache:s,apkCache:r,logger:n})=>{t||(t=`${i}.signed`),t=await T(t),await X({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})},_e=({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),d=await r.resolve(t);n&&(await l.copyFile(d,c),await l.truncate(d)),await $e({inputFile:c,outputFile:d,key:e,...a}),n&&await l.rm(c)};return s.getCacheKey=async()=>{const r=S.createHash("sha256");return r.update(JSON.stringify({inputFile:i,outputFile:t})),r.update(await e.getHash()),`SBSIGN-${r.digest("base64url")}`},s},G=1024,Xe=G+12,ot=48,Gt=4,Ye=[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,3504198960,68661723,4135334616,3844915500,390545967,1230274059,3141532936,2825850620,1510247935,2395924756,2091215383,1878366691,2644384480,3553878443,565732008,854102364,3229815391,340358836,3861050807,4117890627,119113024,1493875044,2875275879,3090270611,1247431312,2660249211,1828433272,2141937292,2378227087,3811616794,291187481,34330861,4032846830,615137029,3603020806,3314634738,939183345,1776939221,2609017814,2295496738,2058945313,2926798794,1545135305,1330124605,3173225534,4084100981,17165430,307568514,3762199681,888469610,3332340585,3587147933,665062302,2042050490,2346497209,2559330125,1793573966,3190661285,1279665062,1595330642,2910671697],ta=(i,t)=>{const e=t.length;for(let a=0;a<e;a++)i=Ye[(i^t[a])&255]^i>>>8;return i},jt=i=>ta(1346745674,i.subarray(8))>>>0,ea=i=>{const t=i.toString("hex");return`${t.substring(0,8)}-${t.substring(8,12)}-${t.substring(12,16)}-${t.substring(16,20)}-${t.substring(20,32)}`},Se=async(i,t)=>{const e=await l.open(i,"r+");try{const a=Buffer.alloc(1);if((await e.read(a,0,a.length,Xe)).bytesRead!==a.length)throw new Error("Could not read blkszbits byte");let r=1<<a.readUInt8(0);r>G&&(r-=G);const n=Buffer.alloc(r);if((await e.read(n,0,n.length,G)).bytesRead!==n.length)throw new Error("Could not read full superblock");if(n.readUInt32LE(0)!==3774210530)throw new Error("Missing EROFS signature.");const c=n.readUInt32LE(Gt),d=jt(n);if(c!==d)throw new Error(`Original CRC does not match: 0x${c.toString(16)} !== 0x${d.toString(16)}`);return t.copy(n,ot,0,16),n.writeUInt32LE(jt(n),Gt),await e.write(n,0,n.length,G),ea(n.subarray(ot,ot+16))}finally{await e.close()}},Ie=async({inputFolder:i,erofsUtilsSource:t,timestamp:e=0,uuid:a="hash",veritySetup:s,outputFile:r,metadataFile:n=`${r}.json`,containerCache:o,apkCache:c,logger:d})=>{r=await T(r);const b=f.relative(i,r),w=[];if(b.startsWith(`..${f.sep}`)||w.push(`--exclude-path=${b}`),a!=="random"&&a!=="hash"&&w.push("-U",a),await X({apkPackages:["erofs-utils"],existingSource:t,command:["mkfs.erofs",...w,"-T",`${e}`,"/out","--","/in"],buildahRunOptions:["-v",`${i}:/in:ro`,"-v",`${r}:/out:rw`],containerCache:o,apkCache:c,logger:d}),a==="hash"){const g=O.createReadStream(r),k=S.createHash("sha256");await dt.pipeline(g,k);const _=k.digest();d?.write(`Hash: ${_.toString("hex")}
`);const E=await Se(r,_);await l.writeFile(n,JSON.stringify({uuid:E}))}s&&await St({file:r,metadataFile:n,...s})},aa=({inputFolder:i,outputFile:t,metadataFile:e,...a})=>{const s=async r=>{const n=await r.resolve(i),o=await r.resolve(t),c=e?await r.resolve(e):void 0;await Ie({inputFolder:n,outputFile:o,metadataFile:c,...a})};return s.getCacheKey=async()=>{const r=S.createHash("sha256");return r.update(JSON.stringify({inputFolder:i,outputFile:t,metadataFile:e,uuid:a.uuid??void 0,timestamp:a.timestamp??void 0,veritySetup:a.veritySetup?{salt:a.veritySetup.salt??void 0,uuid:a.veritySetup.uuid??void 0}:void 0})),`MKEROFS-${r.digest("base64url")}`},s},ia=i=>{const t=async e=>{const a=await e.resolve("/etc/veritytab"),s=[];for(const r of i.volumes){const n=JSON.parse(await l.readFile(await e.resolve(r.metadataFile),"utf8")),o=r.device??(n.uuid?`/dev/disk/by-uuid/${n.uuid}`:null);if(!o)throw new Error(`No device specified for ${r.name}`);const c=[`hash-offset=${n.hashOffset}`,`fec-device=${o}`,`fec-offset=${n.fecOffset}`,...r.options??[]];s.push(`${r.name} ${o} ${o} ${n.rootHash} ${c.join(",")}`)}await l.writeFile(a,s.join(`
`)+`
`,{mode:420,flag:i.replace?"w":"a"})};return t.getCacheKey=async()=>{const e=S.createHash("sha256");return e.update(JSON.stringify(i)),`VERITYTAB-${e.digest("base64url")}`},t},sa=({outputFile:i,addModules:t,installFiles:e,extraOptions:a=[]})=>{const s=["dracut",i,"--no-hostonly"];return t?.forEach(r=>{s.push("--add",r)}),e?.forEach(r=>{s.push("--install",r)}),s.push(...a),N(s,{extraHashData:["--kver","AUTOKERNELVERSION"],async beforeRun(r,n){await l.mkdir(await r.resolve("var/tmp"),{recursive:!0});const o=await l.readdir(await r.resolve("lib/modules"));if(o.length!=1)throw new Error(`Expected exactly one kernel version in lib/modules, found: ${o.join(", ")}`);n.push("--kver",o[0])}})},ra=({outputFile:i,splash:t,initrd:e,cmdline:a,sign:s})=>{const r=["ukify","build",`--output=${i}`];t&&r.push(`--splash=${t}`),e&&r.push(`--initrd=${e}`),a&&r.push(`--cmdline=${a}`);const n=[N(r,{extraHashData:["--linux=/lib/modules/AUTOKERNELVERSION/vmlinuz"],async beforeRun(o,c){const d=await l.readdir(await o.resolve("lib/modules"));if(d.length!=1)throw new Error(`Expected exactly one kernel version in lib/modules, found: ${d.join(", ")}`);c.push(`--linux=/lib/modules/${d[0]}/vmlinuz`)}})];return s&&n.push(_e({inputFile:i,outputFile:i,...s})),n},na=(i,{pacmanCache:t}={})=>{const e={};return t&&(e.buildahArgsNoHash=["--volume",`${t}:/var/cache/pacman/pkg:rw`],e.extraHashData=["--volume",":/var/cache/pacman/pkg:rw"],e.beforeRun=async()=>{await l.mkdir(t,{recursive:!0})}),N(["pacman","-Syu","--noconfirm","--",...i],e)};let ct;const oa=()=>(ct||(ct=f.join(ht.homedir(),".buildahcker","cache","pacman")),ct),Re=(i,t)=>{i=i.sort();const e=async a=>{const r=(await a.run(["pacman","-lq","-Q","--",...i])).stdout.toString("utf8").trim().split(`
`),n=[];for(const o of r){if(o.endsWith("/")){n.unshift(o);continue}const c=await a.resolveParent(o);t?.write(`rm ${o}
`),await l.rm(c,{force:!0})}for(const o of n){const c=await a.resolveParent(o);await ne(c)}};return e.getCacheKey=async()=>{const a=S.createHash("sha256");return a.update(JSON.stringify(i)),`PACMAN-REMOVE-${a.digest("base64url")}`},e},ca=(i=[],t)=>[Re(["pacman",...i],t),$t(["var/lib/pacman","var/cache/pacman","usr/share/pacman","usr/share/libalpm","etc/pacman.d"])];exports.ApkInstalledDatabaseParser=se;exports.BaseDirectory=vt;exports.BaseFile=bt;exports.BaseFileWithCachedContent=Q;exports.BaseFileWithLoadedAttributes=Xt;exports.BaseRegularFile=gt;exports.BaseSymbolicLink=Et;exports.CommandFailed=Jt;exports.Container=j;exports.DiskDirectory=te;exports.DiskFile=M;exports.DiskLocation=Yt;exports.DiskSymLink=ee;exports.FSContainerCache=Wt;exports.ImageBuilder=B;exports.MemDirectory=ae;exports.MemFile=Z;exports.MemSymLink=Le;exports.PartitionType=P;exports.ProxyFile=Qt;exports.SignKey=Ft;exports.WritableBuffer=ut;exports.abpartitionsBiosPartition=ke;exports.abpartitionsDisk=Ze;exports.abpartitionsGrubEnvPartition=We;exports.abpartitionsGrubPartition=Ee;exports.abpartitionsGrubenvAndEfiPartition=ve;exports.abpartitionsRootPartition=Qe;exports.addFiles=kt;exports.apkAdd=ie;exports.apkManuallyRemove=ue;exports.apkRemoveApk=Ke;exports.buildahInspect=Ue;exports.copyFileInImage=qe;exports.defaultApkCache=Pe;exports.defaultContainerCache=Ae;exports.defaultPacmanCache=oa;exports.defaultSSHKeyGenTypes=ge;exports.dracut=sa;exports.erofsSetUUID=Se;exports.exec=U;exports.getFullImageID=lt;exports.grubBiosSetup=fe;exports.grubMkenv=pe;exports.grubMkenvStep=we;exports.grubMkimage=le;exports.grubMkimageStep=_t;exports.hashDirectoryContent=yt;exports.mkerofs=Ie;exports.mkerofsStep=aa;exports.mksquashfs=me;exports.mksquashfsStep=It;exports.mkvfatfs=ye;exports.mkvfatfsStep=be;exports.normalizeDirectoryEntries=mt;exports.normalizeEntry=Zt;exports.normalizeRelativePath=wt;exports.pacmanInstall=na;exports.pacmanManuallyRemove=Re;exports.pacmanRemovePacman=ca;exports.parted=he;exports.prepareApkPackages=D;exports.prepareApkPackagesAndRun=X;exports.readApkInstalledDatabase=re;exports.rmFiles=$t;exports.run=N;exports.runInImageOrContainer=Vt;exports.sbsign=$e;exports.sbsignStep=_e;exports.sshKeygen=Je;exports.temporaryContainer=pt;exports.ukify=ra;exports.useFilesInImages=Rt;exports.veritySetup=St;exports.veritytab=ia;exports.withImageOrContainer=z;exports.writePartitions=Y;