fireactions/js/tar2squashfs.js

72 lines
2 KiB
JavaScript

// TODO: sandboxear este proceso (¿seccomp? ¿minijail?)
// TODO: sandboxear mkquashfs
// TODO: fijarse como hace firecracker-containerd
// TODO: fijarse como hace [ignite](https://github.com/weaveworks/ignite)
import gunzip from "gunzip-maybe";
import { spawn } from "node:child_process";
import { Readable } from "node:stream";
import { extract, pack } from "tar-stream";
/**
* Takes many tar streams and converts them to a squashfs image.
*
* ## Why?
*
* We need a way to download OCI images (composed of layers that are tarballs) to something
* that can be accessed inside a VM. I wanted to not need to copy the image each time a VM
* is made. So, having a local HTTP cache and having the agent inside the VM download it into
* a temporary filesystem would copy it. This is bad for the life of an SSD, slow for an HDD,
* and too much to save into RAM for each VM.
*
* Instead, we download the images before booting the VM and package the layers up into a
* squashfs image that can be mounted inside the VM. This way, we reuse downloaded images
* efficiently.
*
* @param streams {Promise<Readable>[]}
* @param output {string}
*/
export async function tar2squashfs(streams, output) {
const child = spawn(
"mksquashfs",
[
"-",
output,
"-tar",
...["-comp", "zstd"],
...["-Xcompression-level", "3"],
],
{
stdio: ["pipe", "inherit", "inherit"],
}
);
const p = pack();
p.pipe(child.stdin);
for (const streamP of streams) {
const stream = await streamP;
const ex = extract();
ex.on("entry", (header, stream, next) => {
stream.pipe(p.entry(header, next));
});
stream.pipe(gunzip()).pipe(ex);
await new Promise((resolve) =>
ex.on("finish", () => {
resolve(void 0);
})
);
}
console.debug("finalizing");
p.finalize();
await new Promise((resolve, reject) =>
child.on("close", (code) => {
code === 0 ? resolve(void 0) : reject(code);
})
);
}