85 lines
2.4 KiB
JavaScript
85 lines
2.4 KiB
JavaScript
import { execFile as _execFile, spawn } from "node:child_process";
|
|
import { mkdir, readFile, stat, symlink, writeFile } from "node:fs/promises";
|
|
import { homedir } from "node:os";
|
|
import { join } from "node:path";
|
|
import { promisify } from "node:util";
|
|
const execFile = promisify(_execFile);
|
|
|
|
/**
|
|
* @typedef {object} Alpine
|
|
* @prop {(packages: string[]) => Promise<void>} install
|
|
* @prop {(runlevel: string, service: string) => Promise<void>} rcUpdate
|
|
*/
|
|
|
|
/**
|
|
* Initialize an Alpine 3.18 rootfs at `root`
|
|
* @param {string} root
|
|
* @returns {Promise<Alpine>}
|
|
*/
|
|
export async function init(root) {
|
|
await runInContainer(
|
|
root,
|
|
`
|
|
mkdir -p /rootfs/etc/apk
|
|
cp -r /etc/apk/keys /rootfs/etc/apk/
|
|
echo https://dl-cdn.alpinelinux.org/alpine/v3.18/main >> /rootfs/etc/apk/repositories
|
|
echo https://dl-cdn.alpinelinux.org/alpine/v3.18/community >> /rootfs/etc/apk/repositories
|
|
ln -s /var/cache/apk /rootfs/etc/apk/cache
|
|
apk add --initdb --root /rootfs alpine-base
|
|
`
|
|
);
|
|
return {
|
|
async install(packages) {
|
|
const worldPath = join(root, "/etc/apk/world");
|
|
const oldWorld = (await readFile(worldPath, "utf-8")).split("\n");
|
|
const newWorld = oldWorld.concat(packages);
|
|
await writeFile(worldPath, newWorld.join("\n"));
|
|
|
|
await runInContainer(root, `apk fix --root /rootfs`);
|
|
},
|
|
async rcUpdate(runlevel, service) {
|
|
const servicePath = `/etc/init.d/${service}`;
|
|
try {
|
|
await stat(join(root, servicePath));
|
|
} catch (error) {
|
|
throw new Error(`Can't stat service ${service}: ${error}`);
|
|
}
|
|
await symlink(
|
|
servicePath,
|
|
join(root, `/etc/runlevels/`, runlevel, service)
|
|
);
|
|
},
|
|
};
|
|
}
|
|
|
|
const IS_DEV = process.env.NODE_ENV === "development";
|
|
/**
|
|
* Run script inside an Alpine 3.18 container with `root` mounted in /rootfs
|
|
* @param {string} root
|
|
* @param {string} script
|
|
*/
|
|
async function runInContainer(root, script) {
|
|
const cacheDir = join(homedir(), ".cache/apkit");
|
|
await mkdir(cacheDir, { recursive: true });
|
|
const proc = spawn(
|
|
"podman",
|
|
[
|
|
"run",
|
|
"-i",
|
|
"--rm",
|
|
"-v",
|
|
`${root}:/rootfs:Z`,
|
|
"-v",
|
|
`${cacheDir}:/var/cache/apk:Z`,
|
|
"docker.io/alpine:3.18",
|
|
"sh",
|
|
"-ec",
|
|
script,
|
|
],
|
|
{ stdio: IS_DEV ? "inherit" : "pipe" }
|
|
);
|
|
await new Promise((resolve, reject) =>
|
|
proc.on("exit", (code) => (code === 0 ? resolve(void 0) : reject(code)))
|
|
);
|
|
}
|