Compare commits

..

No commits in common. "2e9045f4c3e089807f8597db78e88a8fe44c24ba" and "e73225738458948af9eeb1efd22797293c8c322b" have entirely different histories.

15 changed files with 154 additions and 195 deletions

View file

@ -1,19 +1,19 @@
import { import { mkdir, opendir } from "node:fs/promises";
chmod,
chown,
copyFile,
mkdir,
opendir,
symlink,
writeFile,
} from "node:fs/promises";
import path from "node:path"; import path from "node:path";
import { cwd } from "node:process"; import { cwd } from "node:process";
import { Fstab } from "./fstab.js"; import { Fstab } from "./fstab.js";
import { execFile, exists } from "./helpers/better-api.js"; import { execFile } from "./helpers/better-api.js";
import { PasswdEntry, readPasswd } from "./helpers/passwd.js"; import { PasswdEntry, sudoReadPasswd } from "./helpers/passwd.js";
import {
sudoChmod,
sudoChown,
sudoCopy,
sudoMkdirP,
sudoSymlink,
sudoWriteExecutable,
sudoWriteFile,
} from "./helpers/sudo.js";
import { logDebug } from "./helpers/logger.js"; import { logDebug } from "./helpers/logger.js";
import assert from "node:assert";
export class Alpine { export class Alpine {
dir: string; dir: string;
@ -21,10 +21,9 @@ export class Alpine {
this.dir = dir; this.dir = dir;
} }
fstab: Fstab = new Fstab(this); fstab: Fstab = new Fstab(this);
packages: string[] = [];
async mkdirP(dir: string): Promise<void> { async mkdirP(dir: string): Promise<void> {
await mkdir(this.path(dir), { recursive: true }); await sudoMkdirP(path.join(this.dir, dir));
} }
async writeFile( async writeFile(
filePath: string, filePath: string,
@ -33,22 +32,22 @@ export class Alpine {
): Promise<void> { ): Promise<void> {
const p = path.join(this.dir, filePath); const p = path.join(this.dir, filePath);
await this.mkdirP(path.dirname(filePath)); await this.mkdirP(path.dirname(filePath));
await writeFile(p, content); await sudoWriteFile(p, content);
if (permissions) { if (permissions) {
await chown(p, permissions.uid, permissions.gid); await sudoChown(p, `${permissions.uid}:${permissions.gid}`);
await chmod(p, 0o600); await sudoChmod(p, "600");
} }
} }
async writeExecutable(filePath: string, content: string): Promise<void> { async writeExecutable(filePath: string, content: string): Promise<void> {
await this.writeFile(filePath, content); await this.writeFile(filePath, content);
await chmod(this.path(filePath), 0o700); await sudoChmod(path.join(this.dir, filePath), "700");
}
async assertExists(path: string) {
assert(await exists(this.path(path)));
} }
path(p: string): string { path(p: string): string {
return path.join(this.dir, p); return path.join(this.dir, p);
} }
sudoWriteExecutable(filePath: string, content: string): Promise<void> {
return sudoWriteExecutable(this.path(filePath), content);
}
private getRelativeSymlink( private getRelativeSymlink(
target: string, target: string,
filePath: string filePath: string
@ -64,29 +63,16 @@ export class Alpine {
} }
async symlink(_target: string, _filePath: string): Promise<void> { async symlink(_target: string, _filePath: string): Promise<void> {
const { target, filePath } = this.getRelativeSymlink(_target, _filePath); const { target, filePath } = this.getRelativeSymlink(_target, _filePath);
await symlink(target, filePath); await sudoSymlink(target, filePath);
} }
readPasswd(): Promise<PasswdEntry[]> { readPasswd(): Promise<PasswdEntry[]> {
return readPasswd(this.path("/etc/passwd")); return sudoReadPasswd(this.path("/etc/passwd"));
} }
async userAdd( async userAdd(user: string): Promise<PasswdEntry> {
user: string, await execFile("sudo", [
{ "useradd",
system,
homeDir,
createHome,
}: { system: boolean; homeDir?: string; createHome: boolean } = {
system: false,
homeDir: "",
createHome: true,
}
): Promise<PasswdEntry> {
await execFile("useradd", [
"--user-group", "--user-group",
...(system ? ["--system"] : []),
...(homeDir ? ["--home-dir", homeDir] : []),
...(createHome ? ["--create-home"] : []),
"--root", "--root",
this.dir, this.dir,
user, user,
@ -99,14 +85,11 @@ export class Alpine {
return entry; return entry;
} }
async actuallyInstallPackages(): Promise<void> { async addPackages(packages: string[]): Promise<void> {
await this.installPackages(this.packages);
}
async installPackages(packages: string[]): Promise<void> {
logDebug( logDebug(
"installPackages", "addPackages",
await execFile("apk", [ await execFile("sudo", [
"apk",
"add", "add",
"--clean-protected", "--clean-protected",
"--root", "--root",
@ -115,9 +98,6 @@ export class Alpine {
]) ])
); );
} }
async addPackages(packages: string[]): Promise<void> {
this.packages = this.packages.concat(packages);
}
static async makeWorld({ static async makeWorld({
dir, dir,
@ -127,27 +107,27 @@ export class Alpine {
packages?: string[]; packages?: string[];
}): Promise<Alpine> { }): Promise<Alpine> {
const apkDir = path.join(dir, "/etc/apk"); const apkDir = path.join(dir, "/etc/apk");
await mkdir(apkDir, { recursive: true }); await sudoMkdirP(apkDir);
// hack // hack
{ {
const cacheDir = path.join(cwd(), "cache"); const cacheDir = path.join(cwd(), "cache");
await mkdir("cache", { recursive: true }); await mkdir("cache", { recursive: true });
await symlink(cacheDir, path.join(apkDir, "cache")); await sudoSymlink(cacheDir, path.join(apkDir, "cache"));
} }
{ {
const apkKeysDir = path.join(apkDir, "keys"); const apkKeysDir = path.join(apkDir, "keys");
const keysSrcDir = "alpine/keys"; const keysSrcDir = "alpine/keys";
await mkdir(apkKeysDir, { recursive: true }); await sudoMkdirP(apkKeysDir);
for await (const { name } of await opendir(keysSrcDir)) for await (const { name } of await opendir(keysSrcDir))
await copyFile( await sudoCopy(
path.join(keysSrcDir, name), path.join(keysSrcDir, name),
path.join(apkKeysDir, name) path.join(apkKeysDir, name)
); );
} }
await writeFile( await sudoWriteFile(
path.join(apkDir, "repositories"), path.join(apkDir, "repositories"),
[ [
"https://dl-cdn.alpinelinux.org/alpine/v3.17/main", "https://dl-cdn.alpinelinux.org/alpine/v3.17/main",
@ -156,7 +136,8 @@ export class Alpine {
); );
logDebug( logDebug(
"makeWorld", "makeWorld",
await execFile("apk", [ await execFile("sudo", [
"apk",
"add", "add",
"--initdb", "--initdb",
"--clean-protected", "--clean-protected",

View file

@ -1,4 +1,5 @@
import { Alpine } from "./alpine.js"; import { Alpine } from "./alpine.js";
import { sudoChmod, sudoMkdirP, sudoWriteFile } from "./helpers/sudo.js";
export class Fstab { export class Fstab {
private alpine: Alpine; private alpine: Alpine;
@ -16,19 +17,20 @@ export class Fstab {
.map(([key, val]) => `,${key}=${val}`) .map(([key, val]) => `,${key}=${val}`)
.join(""); .join("");
await this.addMount(`tmpfs ${path} tmpfs defaults,noexec,nosuid${add} 0 0`); await this.addMount(`tmpfs ${path} tmpfs defaults,noexec,nosuid${add} 0 0`);
await this.alpine.mkdirP(path); await sudoMkdirP(this.alpine.path(path));
} }
// Writes fstab to disk. // Writes fstab to disk.
// Intended for internal use only, use addMount or addTmpfs instead // Intended for internal use only, use addMount or addTmpfs instead
async write() { async write() {
await this.alpine.writeFile( const path = this.alpine.path("/etc/fstab");
"/etc/fstab", await sudoWriteFile(
path,
this.mounts.join("\n") + this.mounts.join("\n") +
// Busybox mount no entiende la última línea sino // Busybox mount no entiende la última línea sino
"\n", "\n"
{ uid: 0, gid: 0 }
); );
await sudoChmod(path, "600");
} }
} }
interface TmpfsOptions { interface TmpfsOptions {

View file

@ -3,7 +3,7 @@ import {
execFile as execFileCallback, execFile as execFileCallback,
spawn as spawnCallback, spawn as spawnCallback,
} from "node:child_process"; } from "node:child_process";
import { access, stat } from "node:fs/promises"; import { access } from "node:fs/promises";
export const execFile = promisify(execFileCallback); export const execFile = promisify(execFileCallback);
export const spawn = promisify(spawnCallback); export const spawn = promisify(spawnCallback);
@ -16,12 +16,3 @@ export async function canAccess(path: string): Promise<boolean> {
return false; return false;
} }
} }
export async function exists(path: string): Promise<boolean> {
try {
await stat(path);
return true;
} catch {
return false;
}
}

View file

@ -1,4 +1,4 @@
import { readFile } from "node:fs/promises"; import { sudoReadFile } from "./sudo.js";
export interface PasswdEntry { export interface PasswdEntry {
name: string; name: string;
@ -31,7 +31,7 @@ export function parsePasswd(content: string): PasswdEntry[] {
return entry; return entry;
}); });
} }
export async function readPasswd(path: string): Promise<PasswdEntry[]> { export async function sudoReadPasswd(path: string): Promise<PasswdEntry[]> {
const content = await readFile(path, "utf-8"); const content = await sudoReadFile(path);
return parsePasswd(content); return parsePasswd(content);
} }

View file

@ -6,6 +6,7 @@ import { Alpine } from "./alpine.js";
import { generateForgejoSecretsFile } from "./services/forgejo/secrets.js"; import { generateForgejoSecretsFile } from "./services/forgejo/secrets.js";
import { generateGrafanaSecretsFile } from "./services/grafana/secrets.js"; import { generateGrafanaSecretsFile } from "./services/grafana/secrets.js";
import { execFile } from "./helpers/better-api.js"; import { execFile } from "./helpers/better-api.js";
import { sudoChown, sudoChownToRunningUser } from "./helpers/sudo.js";
import { setupKernel } from "./kernel.js"; import { setupKernel } from "./kernel.js";
import { runQemu } from "./qemu.js"; import { runQemu } from "./qemu.js";
import { Runit } from "./runit/index.js"; import { Runit } from "./runit/index.js";
@ -22,13 +23,6 @@ if (process.argv[2] === "generate-secrets") {
exit(0); exit(0);
} }
async function timed<T>(fn: () => Promise<T>): Promise<T> {
console.time(fn.toString());
const r = await fn();
console.timeEnd(fn.toString());
return r;
}
{ {
console.time("Building"); console.time("Building");
@ -38,8 +32,9 @@ async function timed<T>(fn: () => Promise<T>): Promise<T> {
await mkdir(kernelDir, { recursive: true }); await mkdir(kernelDir, { recursive: true });
const rootfsDir = await mkdtemp(path.join(tmpdir(), "define-alpine-")); const rootfsDir = await mkdtemp(path.join(tmpdir(), "define-alpine-"));
await sudoChown(rootfsDir, "root:root");
console.debug(rootfsDir); console.debug(rootfsDir);
const alpine = await timed(() => Alpine.makeWorld({ dir: rootfsDir })); const alpine = await Alpine.makeWorld({ dir: rootfsDir });
await alpine.addPackages(["helix", "htop", "iproute2-ss", "socat"]); await alpine.addPackages(["helix", "htop", "iproute2-ss", "socat"]);
await alpine.writeFile( await alpine.writeFile(
@ -49,19 +44,18 @@ socat tcp-listen:80,reuseaddr,fork tcp:localhost:3050 &
`, `,
{ uid: 0, gid: 0 } { uid: 0, gid: 0 }
); );
await timed(() => installFluentBit(alpine)); await installFluentBit(alpine);
const runit = await timed(() => Runit.setup(alpine)); const runit = await Runit.setup(alpine);
await timed(() => setupDhcpcd(alpine, runit)); await setupDhcpcd(alpine, runit);
await timed(() => setupNtpsec(alpine, runit)); await setupNtpsec(alpine, runit);
await timed(() => setupForgejo(alpine, runit)); await setupForgejo(alpine, runit);
await timed(() => setupLoki(alpine, runit)); await setupLoki(alpine, runit);
await timed(() => setupGrafana(alpine, runit)); await setupGrafana(alpine, runit);
const kernel = await timed(() => setupKernel(alpine, kernelDir)); const kernel = await setupKernel(alpine, kernelDir);
await timed(() => alpine.actuallyInstallPackages());
const squashfs = path.join(artifactsDir, "image.squashfs"); const squashfs = path.join(artifactsDir, "image.squashfs");
await timed(() => await execFile("sudo", [
execFile("mksquashfs", [ "mksquashfs",
alpine.dir, alpine.dir,
squashfs, squashfs,
"-root-mode", "-root-mode",
@ -69,13 +63,51 @@ socat tcp-listen:80,reuseaddr,fork tcp:localhost:3050 &
"-comp", "-comp",
"zstd", "zstd",
"-Xcompression-level", "-Xcompression-level",
"1", "3",
"-noappend", "-noappend",
"-quiet", "-quiet",
]) ]);
); await sudoChownToRunningUser(squashfs);
console.timeEnd("Building"); console.timeEnd("Building");
// runQemu(squashfs, kernel, { graphic: true }); runQemu(squashfs, kernel, { graphic: true });
// await makeService({
// parentDir: rootfsDir,
// name: "grafana",
// packages: ["grafana"],
// setup: async (dir) => {},
// initScript: async (dir) => {},
// });
// try {
// await spawn("sudo", ["chroot", rootfsDir], { stdio: "inherit" });
// } catch {}
} }
// interface Service {}
// async function makeService({
// parentDir,
// name,
// packages,
// setup,
// initScript: _initScript,
// }: {
// parentDir: string;
// name: string;
// packages?: string[];
// setup: (dir: string) => Promise<void>;
// initScript: (dir: string) => Promise<string>;
// }) {
// const rootsDir = path.join(parentDir, "/nulo/roots/");
// await mkdir(rootsDir, { recursive: true });
// const alpine = await Alpine.makeWorld({
// dir: path.join(rootsDir, name),
// packages,
// });
// await setup(alpine.dir);
// // const initScript = await _initScript(rootfsDir);
// }

View file

@ -1,7 +1,9 @@
import { copyFile, readFile, rm, writeFile } from "node:fs/promises"; import { constants } from "node:fs";
import { copyFile } from "node:fs/promises";
import path from "node:path"; import path from "node:path";
import { Alpine } from "./alpine.js"; import { Alpine } from "./alpine.js";
import { execFile } from "./helpers/better-api.js"; import { canAccess } from "./helpers/better-api.js";
import { sudoChownToRunningUser, sudoCopy, sudoRm } from "./helpers/sudo.js";
export type Kind = "lts" | "virt"; export type Kind = "lts" | "virt";
export type Kernel = { export type Kernel = {
@ -61,36 +63,10 @@ default=lts
await alpine.writeFile( await alpine.writeFile(
"/etc/mkinitfs/mkinitfs.conf", "/etc/mkinitfs/mkinitfs.conf",
`features="${features.join(" ")}" `features="${features.join(" ")}"`
disable_trigger=yes`
); );
await alpine.installPackages([`linux-${kind}`, "zstd"]); await alpine.addPackages([`linux-${kind}`]);
// patch mkinitfs to use faster zstd
const mkinitfs = alpine.path("/sbin/mkinitfs");
const newMkinitfs = (await readFile(mkinitfs, "utf-8")).replace(
'comp="zstd -19"',
'comp="zstd -3"'
);
await writeFile(mkinitfs, newMkinitfs);
const kernelRelease = (
await readFile(
alpine.path(`/usr/share/kernel/${kind}/kernel.release`),
"utf-8"
)
).trim();
// run mkinitfs manually as trigger was diabled
await execFile("chroot", [
alpine.dir,
"mkinitfs",
"-C",
"zstd",
"-o",
`/boot/initramfs-${kind}`,
kernelRelease,
]);
const initramfs = path.join(alpine.dir, `/boot/initramfs-${kind}`); const initramfs = path.join(alpine.dir, `/boot/initramfs-${kind}`);
const vmlinuz = path.join(alpine.dir, `/boot/vmlinuz-${kind}`); const vmlinuz = path.join(alpine.dir, `/boot/vmlinuz-${kind}`);
@ -101,9 +77,11 @@ disable_trigger=yes`
vmlinuz: path.join(output, "vmlinuz"), vmlinuz: path.join(output, "vmlinuz"),
}; };
await copyFile(initramfs, kernel.initramfs); await sudoCopy(initramfs, kernel.initramfs);
await copyFile(vmlinuz, kernel.vmlinuz); await sudoCopy(vmlinuz, kernel.vmlinuz);
await rm(initramfs); await sudoChownToRunningUser(kernel.initramfs);
await rm(vmlinuz); await sudoChownToRunningUser(kernel.vmlinuz);
await sudoRm(initramfs);
await sudoRm(vmlinuz);
return kernel; return kernel;
} }

10
npmrun
View file

@ -1,10 +0,0 @@
#!/bin/sh
script="$1"
if test -z "$script"; then
echo "No script set"
exit 1
fi
run="$(jq -r ".scripts[\"${script}\"]" < package.json)"
echo "> $run"
PATH="./node_modules/.bin:$PATH" sh -c "$run"

View file

@ -6,10 +6,8 @@
"main": "index.js", "main": "index.js",
"scripts": { "scripts": {
"build": "esbuild --log-level=warning --target=node18 --platform=node --sourcemap --outdir=build-javascript --format=esm --bundle index.ts", "build": "esbuild --log-level=warning --target=node18 --platform=node --sourcemap --outdir=build-javascript --format=esm --bundle index.ts",
"build:qemu": "esbuild --log-level=warning --target=node18 --platform=node --sourcemap --outdir=build-javascript --format=esm --bundle qemu.ts", "run": "pnpm build && node --enable-source-maps build-javascript/index.js",
"build-image": "./npmrun build && doas node --enable-source-maps build-javascript/index.js", "//test": "pnpm build && node --enable-source-maps build-javascript/**/*.test.js",
"run": "./npmrun build-image && ./npmrun build:qemu && node --enable-source-maps build-javascript/qemu.js",
"//test": "./npmrun build && node --enable-source-maps build-javascript/**/*.test.js",
"tsc:check": "tsc --noEmit" "tsc:check": "tsc --noEmit"
}, },
"keywords": [], "keywords": [],
@ -19,5 +17,8 @@
"@types/node": "^18.11.18", "@types/node": "^18.11.18",
"esbuild": "^0.17.0", "esbuild": "^0.17.0",
"typescript": "^4.9.4" "typescript": "^4.9.4"
},
"dependencies": {
"nanoid": "^4.0.1"
} }
} }

16
qemu.ts
View file

@ -2,19 +2,16 @@ import { mkdtemp, rm } from "node:fs/promises";
import { tmpdir } from "node:os"; import { tmpdir } from "node:os";
import path from "node:path"; import path from "node:path";
import { execFile } from "./helpers/better-api.js"; import { execFile } from "./helpers/better-api.js";
import { sudoChownToRunningUser } from "./helpers/sudo.js"; import { Kernel } from "./kernel.js";
export async function runQemu( export async function runQemu(
squashfs: string, squashfs: string,
kernel: { initramfs: string; vmlinuz: string }, kernel: Kernel,
{ graphic, noShutdown }: { graphic?: boolean; noShutdown?: boolean } = { { graphic, noShutdown }: { graphic?: boolean; noShutdown?: boolean } = {
graphic: true, graphic: true,
noShutdown: false, noShutdown: false,
} }
) { ) {
await sudoChownToRunningUser(kernel.initramfs);
await sudoChownToRunningUser(kernel.vmlinuz);
await sudoChownToRunningUser(squashfs);
const tmp = await mkdtemp(path.join(tmpdir(), "define-alpine-qemu-")); const tmp = await mkdtemp(path.join(tmpdir(), "define-alpine-qemu-"));
try { try {
const disk = path.join(tmp, "tmp.ext4"); const disk = path.join(tmp, "tmp.ext4");
@ -62,12 +59,3 @@ export async function runQemu(
await rm(tmp, { recursive: true, force: true }); await rm(tmp, { recursive: true, force: true });
} }
} }
await runQemu(
"artifacts/image.squashfs",
{
initramfs: "artifacts/kernel/initramfs",
vmlinuz: "artifacts/kernel/vmlinuz",
},
{ graphic: true }
);

View file

@ -1,4 +1,4 @@
import { readFile, rmdir } from "node:fs/promises"; import { readFile } from "node:fs/promises";
import path from "node:path"; import path from "node:path";
import { Alpine } from "../alpine.js"; import { Alpine } from "../alpine.js";
@ -60,12 +60,12 @@ export class Runit {
); );
// https://wiki.gentoo.org/wiki/Runit#Reboot_and_shutdown // https://wiki.gentoo.org/wiki/Runit#Reboot_and_shutdown
await alpine.writeExecutable( await alpine.sudoWriteExecutable(
"/usr/local/sbin/rpoweroff", "/usr/local/sbin/rpoweroff",
`#!/bin/sh `#!/bin/sh
runit-init 0` runit-init 0`
); );
await alpine.writeExecutable( await alpine.sudoWriteExecutable(
"/usr/local/sbin/rreboot", "/usr/local/sbin/rreboot",
`#!/bin/sh `#!/bin/sh
runit-init 6` runit-init 6`
@ -100,10 +100,10 @@ exec chpst -P getty 38400 ttyS0 linux`
logScript?: string logScript?: string
): Promise<void> { ): Promise<void> {
const runScriptPath = path.join("/etc/sv/", name, "/run"); const runScriptPath = path.join("/etc/sv/", name, "/run");
await this.alpine.writeExecutable(runScriptPath, script); await this.alpine.sudoWriteExecutable(runScriptPath, script);
if (logScript) { if (logScript) {
const logScriptPath = path.join("/etc/sv/", name, "/log/run"); const logScriptPath = path.join("/etc/sv/", name, "/log/run");
await this.alpine.writeExecutable(logScriptPath, logScript); await this.alpine.sudoWriteExecutable(logScriptPath, logScript);
await this.alpine.symlink( await this.alpine.symlink(
`/run/runit/supervise.${name}.log`, `/run/runit/supervise.${name}.log`,
path.join("/etc/sv/", name, "/log/supervise") path.join("/etc/sv/", name, "/log/supervise")

View file

@ -1,13 +1,14 @@
import { buildForgejo } from "./build.js"; import { buildForgejo } from "./build.js";
import { Alpine } from "../../alpine.js"; import { Alpine } from "../../alpine.js";
import { Runit } from "../../runit/index.js"; import { Runit } from "../../runit/index.js";
import { join } from "node:path";
import { loadForgejoSecretsFile } from "./secrets.js"; import { loadForgejoSecretsFile } from "./secrets.js";
import { sudoCopy } from "../../helpers/sudo.js";
import { FluentBitParser, runitLokiLogger } from "../../software/fluentbit.js"; import { FluentBitParser, runitLokiLogger } from "../../software/fluentbit.js";
import { copyFile } from "node:fs/promises";
export async function setupForgejo(alpine: Alpine, runit: Runit) { export async function setupForgejo(alpine: Alpine, runit: Runit) {
const bin = await buildForgejo(); const bin = await buildForgejo();
await copyFile(bin, alpine.path("/usr/local/bin/forgejo")); await sudoCopy(bin, join(alpine.dir, "/usr/local/bin/forgejo"));
await alpine.addPackages(["tzdata", "git"]); await alpine.addPackages(["tzdata", "git"]);
const entry = await alpine.userAdd("_forgejo"); const entry = await alpine.userAdd("_forgejo");

View file

@ -6,7 +6,6 @@ import { FluentBitParser, runitLokiLogger } from "../../software/fluentbit.js";
import { loadGrafanaSecretsFile } from "./secrets.js"; import { loadGrafanaSecretsFile } from "./secrets.js";
const provisioningDir = "/etc/grafana/provisioning/"; const provisioningDir = "/etc/grafana/provisioning/";
const grafanaHome = "/var/lib/grafana";
// TODO: grafana-image-renderer? // TODO: grafana-image-renderer?
// /etc/conf.d/grafana // /etc/conf.d/grafana
@ -24,14 +23,12 @@ export async function setupGrafana(
): Promise<void> { ): Promise<void> {
await alpine.addPackages(["grafana"]); await alpine.addPackages(["grafana"]);
const user = await alpine.userAdd("grafana", { const passwd = await alpine.readPasswd();
system: true, const user = passwd.find((e) => e.name === "grafana");
homeDir: grafanaHome, assert(!!user, "no existe el usuario grafana");
createHome: false,
});
// TODO: data // TODO: data
await alpine.fstab.addTmpfs(grafanaHome, { await alpine.fstab.addTmpfs("/var/lib/grafana", {
uid: user.uid, uid: user.uid,
gid: user.gid, gid: user.gid,
mode: "700", mode: "700",
@ -52,7 +49,7 @@ datasources:
user user
); );
await alpine.writeExecutable( await alpine.sudoWriteExecutable(
"/usr/local/sbin/nulo-grafana-cli", "/usr/local/sbin/nulo-grafana-cli",
`#!/bin/sh `#!/bin/sh
cd / cd /
@ -62,7 +59,7 @@ exec chpst -u grafana:grafana grafana-cli --homepath /usr/share/grafana --config
await runit.addService( await runit.addService(
"grafana", "grafana",
`#!/bin/sh `#!/bin/sh
export GRAFANA_HOME='${grafanaHome}' export GRAFANA_HOME=/var/lib/grafana
cd "$GRAFANA_HOME" cd "$GRAFANA_HOME"
@ -83,7 +80,7 @@ async function genConfig(): Promise<string> {
#################################### Paths #################################### #################################### Paths ####################################
[paths] [paths]
data = ${grafanaHome} data = /var/lib/grafana
# Temporary files in \`data\` directory older than given duration will be removed # Temporary files in \`data\` directory older than given duration will be removed
;temp_data_lifetime = 24h ;temp_data_lifetime = 24h
@ -91,7 +88,7 @@ data = ${grafanaHome}
# Directory where grafana can store logs # Directory where grafana can store logs
;logs = /var/log/grafana ;logs = /var/log/grafana
plugins = ${grafanaHome}/plugins plugins = /var/lib/grafana/plugins
# folder that contains provisioning config files that grafana will apply on startup and while running. # folder that contains provisioning config files that grafana will apply on startup and while running.
provisioning = ${provisioningDir} provisioning = ${provisioningDir}

View file

@ -1,11 +1,12 @@
import { copyFile } from "node:fs/promises"; import { join } from "node:path";
import { Alpine } from "../../alpine.js"; import { Alpine } from "../../alpine.js";
import { sudoCopy } from "../../helpers/sudo.js";
import { Runit } from "../../runit/index.js"; import { Runit } from "../../runit/index.js";
import { buildLoki } from "./build.js"; import { buildLoki } from "./build.js";
export async function setupLoki(alpine: Alpine, runit: Runit): Promise<void> { export async function setupLoki(alpine: Alpine, runit: Runit): Promise<void> {
const bin = await buildLoki(); const bin = await buildLoki();
await copyFile(bin, alpine.path("/usr/local/bin/loki")); await sudoCopy(bin, join(alpine.dir, "/usr/local/bin/loki"));
const user = await alpine.userAdd("loki"); const user = await alpine.userAdd("loki");

View file

@ -1,4 +1,5 @@
import { Alpine } from "../alpine.js"; import { Alpine } from "../alpine.js";
import { sudoWriteFile } from "../helpers/sudo.js";
import { Runit } from "../runit/index.js"; import { Runit } from "../runit/index.js";
import { FluentBitParser, runitLokiLogger } from "../software/fluentbit.js"; import { FluentBitParser, runitLokiLogger } from "../software/fluentbit.js";
@ -9,8 +10,8 @@ export async function setupNtpsec(alpine: Alpine, runit: Runit) {
// file:///usr/share/doc/ntpsec/quick.html // file:///usr/share/doc/ntpsec/quick.html
// file:///usr/share/doc/ntpsec/NTS-QuickStart.html // file:///usr/share/doc/ntpsec/NTS-QuickStart.html
// XXX: revisar driftfile, creo que tiene que poder escribir pero está readonly // XXX: revisar driftfile, creo que tiene que poder escribir pero está readonly
await alpine.writeFile( await sudoWriteFile(
"/etc/ntp.conf", alpine.path("/etc/ntp.conf"),
` `
driftfile /var/lib/ntp/ntp.drift driftfile /var/lib/ntp/ntp.drift

View file

@ -1,7 +1,7 @@
import { constants, copyFile } from "node:fs/promises";
import { join } from "node:path"; import { join } from "node:path";
import { Alpine } from "../alpine.js"; import { Alpine } from "../alpine.js";
import { buildRepro, reproRun } from "../helpers/repro-run.js"; import { buildRepro, reproRun } from "../helpers/repro-run.js";
import { sudoCopy } from "../helpers/sudo.js";
const parsersPath = "/etc/fluent-bit/parsers.conf"; const parsersPath = "/etc/fluent-bit/parsers.conf";
@ -9,11 +9,7 @@ export async function installFluentBit(alpine: Alpine): Promise<void> {
const bin = await buildFluentBit(); const bin = await buildFluentBit();
await saveParsers(alpine); await saveParsers(alpine);
await alpine.addPackages(["musl-fts", "yaml"]); await alpine.addPackages(["musl-fts", "yaml"]);
await copyFile( await sudoCopy(bin, join(alpine.dir, "/usr/local/bin/fluent-bit"));
bin,
alpine.path("/usr/local/bin/fluent-bit"),
constants.COPYFILE_FICLONE
);
} }
// ## Script generators // ## Script generators