define-alpine-the-sequel/alpine.ts

134 lines
3.6 KiB
TypeScript
Raw Normal View History

2023-01-16 15:14:20 +00:00
import {
chmod,
copyFile,
mkdir,
mkdtemp,
opendir,
rm,
symlink,
writeFile,
} from "node:fs/promises";
import { tmpdir } from "node:os";
import path from "node:path";
import { cwd } from "node:process";
2023-02-02 22:23:39 +00:00
import { execFile } from "./helpers/better-api.js";
2023-01-16 15:14:20 +00:00
export class Alpine {
dir: string;
private constructor({ dir }: { dir: string }) {
this.dir = dir;
}
async mkdir(dir: string, opts?: { recursive: boolean }): Promise<void> {
await mkdir(path.join(this.dir, dir), opts);
}
async writeFile(filePath: string, content: string): Promise<void> {
await this.mkdir(path.dirname(filePath), { recursive: true });
await writeFile(path.join(this.dir, filePath), content);
}
async writeExecutable(filePath: string, content: string): Promise<void> {
await this.writeFile(filePath, content);
await chmod(path.join(this.dir, filePath), 700);
}
async sudoWriteExecutable(filePath: string, content: string): Promise<void> {
const dir = await mkdtemp(
path.join(tmpdir(), "define-alpine-sudoWriteExecutable-")
);
try {
const tmpFile = path.join(dir, path.basename(filePath));
const finalPath = path.join(this.dir, filePath);
await writeFile(tmpFile, content);
await execFile("sudo", [
"mkdir",
"--parents",
path.join(this.dir, path.dirname(filePath)),
]);
await execFile("sudo", ["mv", tmpFile, finalPath]);
await execFile("sudo", ["chmod", "700", finalPath]);
} finally {
await rm(dir, { recursive: true, force: true });
}
}
private getRelativeSymlink(
target: string,
filePath: string
): { target: string; filePath: string } {
const realFilePath = path.join(this.dir, filePath);
return {
target: path.relative(
path.dirname(realFilePath),
path.join(this.dir, target)
),
filePath: realFilePath,
};
}
async symlink(_target: string, _filePath: string): Promise<void> {
const { target, filePath } = this.getRelativeSymlink(_target, _filePath);
await symlink(target, filePath);
}
async sudoSymlink(_target: string, _filePath: string): Promise<void> {
const { target, filePath } = this.getRelativeSymlink(_target, _filePath);
await execFile("sudo", ["ln", "-s", target, filePath]);
}
async addPackages(packages: string[]): Promise<void> {
await execFile("sudo", [
"apk",
"add",
"--clean-protected",
"--root",
this.dir,
...packages,
]);
}
static async makeWorld({
dir,
packages,
}: {
dir: string;
packages?: string[];
}): Promise<Alpine> {
const apkDir = path.join(dir, "/etc/apk");
await mkdir(apkDir, { recursive: true });
// hack
{
const cacheDir = path.join(cwd(), "cache");
await mkdir("cache", { recursive: true });
await symlink(cacheDir, path.join(apkDir, "cache"));
}
{
const apkKeysDir = path.join(apkDir, "keys");
const keysSrcDir = "alpine/keys";
await mkdir(apkKeysDir);
for await (const { name } of await opendir(keysSrcDir))
await copyFile(
path.join(keysSrcDir, name),
path.join(apkKeysDir, name)
);
}
await writeFile(
path.join(apkDir, "repositories"),
[
"https://dl-cdn.alpinelinux.org/alpine/v3.17/main",
"https://dl-cdn.alpinelinux.org/alpine/v3.17/community",
].join("\n")
);
await execFile("sudo", [
"apk",
"add",
"--initdb",
"--clean-protected",
"--root",
dir,
...["alpine-baselayout", "busybox", "libc-utils", "alpine-keys"],
...(packages || []),
]);
return new Alpine({ dir });
}
}