72 lines
2 KiB
JavaScript
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);
|
|
})
|
|
);
|
|
}
|