define-alpine-the-sequel/alpine.ts

147 lines
3.9 KiB
TypeScript
Raw Normal View History

2023-01-16 15:14:20 +00:00
import {
chmod,
copyFile,
mkdir,
opendir,
symlink,
writeFile,
} from "node:fs/promises";
import path from "node:path";
import { cwd } from "node:process";
2023-02-08 14:46:33 +00:00
import { Fstab } from "./fstab.js";
2023-02-02 22:23:39 +00:00
import { execFile } from "./helpers/better-api.js";
2023-02-08 14:46:33 +00:00
import { PasswdEntry, sudoReadPasswd } from "./helpers/passwd.js";
import { sudoWriteExecutable } from "./helpers/sudo.js";
2023-02-10 00:38:00 +00:00
import { logDebug } from "./helpers/logger.js";
2023-01-16 15:14:20 +00:00
export class Alpine {
dir: string;
private constructor({ dir }: { dir: string }) {
this.dir = dir;
}
2023-02-08 14:46:33 +00:00
fstab: Fstab = new Fstab(this);
2023-01-16 15:14:20 +00:00
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);
}
2023-02-08 14:46:33 +00:00
path(p: string): string {
return path.join(this.dir, p);
}
sudoWriteExecutable(filePath: string, content: string): Promise<void> {
return sudoWriteExecutable(this.path(filePath), content);
2023-01-16 15:14:20 +00:00
}
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]);
}
2023-02-08 14:46:33 +00:00
async userAdd(user: string): Promise<PasswdEntry> {
await execFile("sudo", [
"useradd",
"--user-group",
"--root",
this.dir,
user,
]);
const passwd = await sudoReadPasswd(this.path("/etc/passwd"));
const entry = passwd.find((e) => e.name === user);
if (!entry) {
throw new Error("fatal(userAdd): no encontré el usuario " + user);
}
return entry;
}
2023-01-16 15:14:20 +00:00
async addPackages(packages: string[]): Promise<void> {
2023-02-10 00:38:00 +00:00
logDebug(
"addPackages",
await execFile("sudo", [
"apk",
"add",
"--clean-protected",
"--root",
this.dir,
...packages,
])
);
2023-01-16 15:14:20 +00:00
}
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")
);
2023-02-10 00:43:54 +00:00
logDebug(
"makeWorld",
await execFile("sudo", [
"apk",
"add",
"--initdb",
"--clean-protected",
"--root",
dir,
...["alpine-baselayout", "busybox", "libc-utils", "alpine-keys"],
...(packages || []),
])
);
2023-01-16 15:14:20 +00:00
2023-02-08 14:46:33 +00:00
const alpine = new Alpine({ dir });
await alpine.fstab.write();
return alpine;
2023-01-16 15:14:20 +00:00
}
}