bootear imgaenes dentro de VM
This commit is contained in:
parent
f480b54259
commit
d876b46145
10 changed files with 362 additions and 83 deletions
2
.gitignore
vendored
2
.gitignore
vendored
|
@ -8,3 +8,5 @@ vmlinux.bin
|
||||||
node_modules/
|
node_modules/
|
||||||
|
|
||||||
*.log
|
*.log
|
||||||
|
|
||||||
|
build.cjs
|
||||||
|
|
|
@ -1,5 +1,5 @@
|
||||||
import { execFile as _execFile } from "node:child_process";
|
import { execFile as _execFile } from "node:child_process";
|
||||||
import { access, mkdir } from "node:fs/promises";
|
import { access, mkdir, writeFile } from "node:fs/promises";
|
||||||
import { join } from "node:path";
|
import { join } from "node:path";
|
||||||
import { tar2squashfs } from "./tar2squashfs.js";
|
import { tar2squashfs } from "./tar2squashfs.js";
|
||||||
import { subtle } from "node:crypto";
|
import { subtle } from "node:crypto";
|
||||||
|
@ -10,15 +10,23 @@ const getToken = memoizeDownloader(_getToken);
|
||||||
let squashfsDownloads = new Map<string, Promise<string>>();
|
let squashfsDownloads = new Map<string, Promise<string>>();
|
||||||
|
|
||||||
const tmpDir = "cache/";
|
const tmpDir = "cache/";
|
||||||
{
|
// {
|
||||||
const image = "gitea.nulo.in/nulo/zulip-checkin-cyborg";
|
// const image = "gitea.nulo.in/nulo/zulip-checkin-cyborg";
|
||||||
const tag = "latest";
|
// const tag = "latest";
|
||||||
|
|
||||||
await mkdir(tmpDir, { recursive: true });
|
// await downloadImage(image, tag);
|
||||||
await downloadContainer(image, tag);
|
// }
|
||||||
|
|
||||||
|
export const CONFIG_PATH_IN_IMAGE = "/.fireactions-image-config.json";
|
||||||
|
|
||||||
|
export function parseImageRef(ref: string): { image: string; tag: string } {
|
||||||
|
const [image, tag] = ref.split(":");
|
||||||
|
if (!image || !tag) throw new Error("Invalid image ref " + ref);
|
||||||
|
return { image, tag };
|
||||||
}
|
}
|
||||||
|
|
||||||
async function downloadContainer(image: string, tag: string) {
|
export async function downloadImage(image: string, tag: string) {
|
||||||
|
await mkdir(tmpDir, { recursive: true });
|
||||||
const manifest = await getContainerManifest(image, tag);
|
const manifest = await getContainerManifest(image, tag);
|
||||||
|
|
||||||
// sanity check
|
// sanity check
|
||||||
|
@ -34,8 +42,14 @@ async function downloadContainer(image: string, tag: string) {
|
||||||
|
|
||||||
console.debug(manifest);
|
console.debug(manifest);
|
||||||
|
|
||||||
await saveSquashfs(image, manifest);
|
|
||||||
const config = await jsonBlob(image, manifest.config.digest);
|
const config = await jsonBlob(image, manifest.config.digest);
|
||||||
|
const squashfsFile = await saveSquashfs(
|
||||||
|
image,
|
||||||
|
manifest,
|
||||||
|
JSON.stringify(config, null, 2)
|
||||||
|
);
|
||||||
|
|
||||||
|
return { squashfsFile };
|
||||||
}
|
}
|
||||||
|
|
||||||
// https://stackoverflow.com/a/67600346
|
// https://stackoverflow.com/a/67600346
|
||||||
|
@ -51,10 +65,11 @@ async function hashData(data: string, algorithm = "SHA-512"): Promise<string> {
|
||||||
|
|
||||||
async function saveSquashfs(
|
async function saveSquashfs(
|
||||||
image: string,
|
image: string,
|
||||||
manifest: Manifest
|
manifest: Manifest,
|
||||||
|
configJson: string
|
||||||
): Promise<string> {
|
): Promise<string> {
|
||||||
const manifestDigest = await hashData(JSON.stringify(manifest));
|
const manifestDigest = await hashData(JSON.stringify(manifest));
|
||||||
const key = `${image.replaceAll("/", "%")}#${manifestDigest}`;
|
const key = `${image.replaceAll("/", "%")}#${manifestDigest}.squashfs`;
|
||||||
|
|
||||||
let p = squashfsDownloads.get(key);
|
let p = squashfsDownloads.get(key);
|
||||||
if (!p) {
|
if (!p) {
|
||||||
|
@ -68,7 +83,17 @@ async function saveSquashfs(
|
||||||
const res = await getBlob(image, layer.digest);
|
const res = await getBlob(image, layer.digest);
|
||||||
return res.body!;
|
return res.body!;
|
||||||
});
|
});
|
||||||
await tar2squashfs(layerStreams, output);
|
await tar2squashfs(layerStreams, output, [
|
||||||
|
{
|
||||||
|
content: configJson,
|
||||||
|
headers: {
|
||||||
|
name: CONFIG_PATH_IN_IMAGE,
|
||||||
|
uid: 0,
|
||||||
|
gid: 0,
|
||||||
|
mode: 0o400,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
]);
|
||||||
}
|
}
|
||||||
return output;
|
return output;
|
||||||
})();
|
})();
|
||||||
|
|
48
js/index.js
48
js/index.js
|
@ -1,11 +1,10 @@
|
||||||
import { delay } from "nanodelay";
|
import { delay } from "nanodelay";
|
||||||
import { nanoid } from "nanoid";
|
import { nanoid } from "nanoid";
|
||||||
import { ChildProcess, execFile, spawn } from "node:child_process";
|
import { spawn } from "node:child_process";
|
||||||
import { writeFile } from "node:fs/promises";
|
import { createServer, request as _request } from "node:http";
|
||||||
import { createServer, request as _request, IncomingMessage } from "node:http";
|
|
||||||
import { tmpdir } from "node:os";
|
import { tmpdir } from "node:os";
|
||||||
import { join } from "node:path";
|
import { join } from "node:path";
|
||||||
import { promisify } from "node:util";
|
import { downloadImage, parseImageRef } from "./container-baby.js";
|
||||||
|
|
||||||
const server = createServer(listener);
|
const server = createServer(listener);
|
||||||
|
|
||||||
|
@ -17,19 +16,43 @@ async function listener(req, res) {
|
||||||
const url = getUrl(req);
|
const url = getUrl(req);
|
||||||
if (url.pathname == "/run") {
|
if (url.pathname == "/run") {
|
||||||
if (req.method == "POST") {
|
if (req.method == "POST") {
|
||||||
await runVm();
|
const image = url.searchParams.get("image");
|
||||||
|
if (!image) return res.writeHead(400).end("missing image param");
|
||||||
|
await runVm(image);
|
||||||
} else res.writeHead(405).end("wrong method");
|
} else res.writeHead(405).end("wrong method");
|
||||||
} else res.writeHead(404).end("not found");
|
} else res.writeHead(404).end("not found");
|
||||||
}
|
}
|
||||||
|
|
||||||
async function runVm() {
|
/**
|
||||||
|
* @param {string} image - ref to an OCI image
|
||||||
|
*/
|
||||||
|
async function runVm(image) {
|
||||||
try {
|
try {
|
||||||
await FirecrackerInstance.run();
|
const ref = parseImageRef(image);
|
||||||
|
const { squashfsFile } = await downloadImage(ref.image, ref.tag);
|
||||||
|
await FirecrackerInstance.run({
|
||||||
|
drives: [
|
||||||
|
{
|
||||||
|
drive_id: "image",
|
||||||
|
is_read_only: true,
|
||||||
|
is_root_device: false,
|
||||||
|
path_on_host: squashfsFile,
|
||||||
|
},
|
||||||
|
],
|
||||||
|
});
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
console.error(err);
|
console.error(err);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @typedef {object} Drive
|
||||||
|
* @prop {string} drive_id
|
||||||
|
* @prop {string} path_on_host
|
||||||
|
* @prop {boolean} is_root_device
|
||||||
|
* @prop {boolean} is_read_only
|
||||||
|
*/
|
||||||
|
|
||||||
class FirecrackerInstance {
|
class FirecrackerInstance {
|
||||||
proc;
|
proc;
|
||||||
socketPath;
|
socketPath;
|
||||||
|
@ -52,8 +75,12 @@ class FirecrackerInstance {
|
||||||
}
|
}
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
static async run() {
|
/**
|
||||||
|
* @param [opts] {{ drives?: Drive[] }}
|
||||||
|
*/
|
||||||
|
static async run(opts) {
|
||||||
const self = new FirecrackerInstance();
|
const self = new FirecrackerInstance();
|
||||||
|
await delay(15);
|
||||||
|
|
||||||
await self.request("PUT", "/boot-source", {
|
await self.request("PUT", "/boot-source", {
|
||||||
kernel_image_path: "../vmlinux.bin",
|
kernel_image_path: "../vmlinux.bin",
|
||||||
|
@ -65,6 +92,11 @@ class FirecrackerInstance {
|
||||||
is_root_device: true,
|
is_root_device: true,
|
||||||
is_read_only: false,
|
is_read_only: false,
|
||||||
});
|
});
|
||||||
|
if (opts?.drives) {
|
||||||
|
for (const drive of opts.drives) {
|
||||||
|
await self.request("PUT", "/drives/" + drive.drive_id, drive);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// API requests are handled asynchronously, it is important the configuration is
|
// API requests are handled asynchronously, it is important the configuration is
|
||||||
// set, before `InstanceStart`.
|
// set, before `InstanceStart`.
|
||||||
|
|
|
@ -5,7 +5,7 @@
|
||||||
"description": "",
|
"description": "",
|
||||||
"main": "index.js",
|
"main": "index.js",
|
||||||
"scripts": {
|
"scripts": {
|
||||||
"test": "echo \"Error: no test specified\" && exit 1"
|
"start": "esbuild --bundle index.js --platform=node --sourcemap > build.cjs && node --enable-source-maps build.cjs"
|
||||||
},
|
},
|
||||||
"keywords": [],
|
"keywords": [],
|
||||||
"author": "",
|
"author": "",
|
||||||
|
|
|
@ -24,10 +24,11 @@ import { extract, pack } from "tar-stream";
|
||||||
* squashfs image that can be mounted inside the VM. This way, we reuse downloaded images
|
* squashfs image that can be mounted inside the VM. This way, we reuse downloaded images
|
||||||
* efficiently.
|
* efficiently.
|
||||||
*
|
*
|
||||||
* @param streams {Promise<ReadableStream>[]}
|
* @param {Promise<ReadableStream>[]} streams
|
||||||
* @param output {string}
|
* @param {string} output
|
||||||
|
* @param {{ content: string, headers: import("tar-stream").Headers }[]} extraFiles
|
||||||
*/
|
*/
|
||||||
export async function tar2squashfs(streams, output) {
|
export async function tar2squashfs(streams, output, extraFiles) {
|
||||||
const child = spawn(
|
const child = spawn(
|
||||||
"mksquashfs",
|
"mksquashfs",
|
||||||
[
|
[
|
||||||
|
@ -38,12 +39,14 @@ export async function tar2squashfs(streams, output) {
|
||||||
...["-Xcompression-level", "3"],
|
...["-Xcompression-level", "3"],
|
||||||
],
|
],
|
||||||
{
|
{
|
||||||
stdio: "pipe",
|
// stdio: "pipe",
|
||||||
|
stdio: ["pipe", "inherit", "inherit"],
|
||||||
}
|
}
|
||||||
);
|
);
|
||||||
|
|
||||||
const p = pack();
|
const p = pack();
|
||||||
p.pipe(child.stdin);
|
p.pipe(child.stdin);
|
||||||
|
p.on("error", console.error);
|
||||||
|
|
||||||
for (const streamP of streams) {
|
for (const streamP of streams) {
|
||||||
const stream = await streamP;
|
const stream = await streamP;
|
||||||
|
@ -61,6 +64,11 @@ export async function tar2squashfs(streams, output) {
|
||||||
})
|
})
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
for (const { headers, content } of extraFiles) {
|
||||||
|
p.entry(headers, content);
|
||||||
|
}
|
||||||
|
|
||||||
p.finalize();
|
p.finalize();
|
||||||
|
|
||||||
await new Promise((resolve, reject) =>
|
await new Promise((resolve, reject) =>
|
||||||
|
|
140
rootfs/build.js
140
rootfs/build.js
|
@ -1,13 +1,15 @@
|
||||||
|
// @ts-check
|
||||||
import { init } from "@nulo/apkit";
|
import { init } from "@nulo/apkit";
|
||||||
import { execFile as _execFile } from "node:child_process";
|
import { execFile as _execFile } from "node:child_process";
|
||||||
import {
|
import {
|
||||||
chmod,
|
chmod,
|
||||||
|
copyFile,
|
||||||
|
lstat,
|
||||||
mkdir,
|
mkdir,
|
||||||
mkdtemp,
|
mkdtemp,
|
||||||
readFile,
|
readFile,
|
||||||
readdir,
|
readdir,
|
||||||
rm,
|
rm,
|
||||||
symlink,
|
|
||||||
writeFile,
|
writeFile,
|
||||||
} from "node:fs/promises";
|
} from "node:fs/promises";
|
||||||
import { tmpdir } from "node:os";
|
import { tmpdir } from "node:os";
|
||||||
|
@ -17,73 +19,69 @@ const execFile = promisify(_execFile);
|
||||||
|
|
||||||
const root = await mkdtemp(join(tmpdir(), "fireactions-rootfs."));
|
const root = await mkdtemp(join(tmpdir(), "fireactions-rootfs."));
|
||||||
const alpine = await init(root);
|
const alpine = await init(root);
|
||||||
await alpine.install(["dropbear", "util-linux", "dropbear-dbclient", "dhcpcd"]);
|
await alpine.install(["util-linux", "dhcpcd", "eudev"]);
|
||||||
|
|
||||||
// gotta go fast
|
const initPath = await buildInit();
|
||||||
await append(r("/etc/rc.conf"), 'rc_parallel="YES"');
|
await copyFile(initPath, r("/sbin/fireactions-init"));
|
||||||
|
|
||||||
await mkdirp(r("/usr/local/sbin"));
|
await writeSbin(
|
||||||
console.debug(
|
r("/sbin/init"),
|
||||||
await execFile("go", [
|
`#!/bin/sh
|
||||||
"build",
|
/etc/init-files/00-pseudofs.sh
|
||||||
"-tags=netgo",
|
/etc/init-files/05-misc.sh
|
||||||
"-o",
|
|
||||||
r("/usr/local/sbin/fireactions-agent"),
|
#exec /sbin/fireactions-init
|
||||||
"./agent",
|
/sbin/fireactions-init || sh
|
||||||
])
|
|
||||||
);
|
|
||||||
// https://github.com/OpenRC/openrc/blob/master/service-script-guide.md
|
|
||||||
await writeFile(
|
|
||||||
r("/etc/init.d/fireactions-agent"),
|
|
||||||
`#!/sbin/openrc-run
|
|
||||||
pidfile="/run/\${RC_SVCNAME}.pid"
|
|
||||||
command_background=true
|
|
||||||
command=/usr/local/sbin/fireactions-agent
|
|
||||||
output_log=/dev/stdout
|
|
||||||
error_log=/dev/stdout
|
|
||||||
`
|
`
|
||||||
);
|
);
|
||||||
await chmod(r("/etc/init.d/fireactions-agent"), 0o700);
|
|
||||||
await alpine.rcUpdate("default", "fireactions-agent");
|
|
||||||
|
|
||||||
await alpine.rcUpdate("boot", "devfs");
|
await mkdir(r("/etc/init-files/"));
|
||||||
await alpine.rcUpdate("boot", "procfs");
|
await writeSbin(
|
||||||
await alpine.rcUpdate("boot", "sysfs");
|
r("/etc/init-files/00-pseudofs.sh"),
|
||||||
// alpine.rcUpdate('default', 'dhcpcd')
|
`#!/bin/sh
|
||||||
await alpine.rcUpdate("default", "dropbear");
|
mountpoint -q /proc || mount -o nosuid,noexec,nodev -t proc proc /proc
|
||||||
|
mountpoint -q /sys || mount -o nosuid,noexec,nodev -t sysfs sys /sys
|
||||||
await writeFile(r("/etc/securetty"), "ttyS0");
|
mountpoint -q /run || mount -o mode=0755,nosuid,nodev -t tmpfs run /run
|
||||||
await symlink("agetty", r("/etc/init.d/agetty.ttyS0"));
|
mountpoint -q /dev || mount -o mode=0755,nosuid -t devtmpfs dev /dev
|
||||||
await alpine.rcUpdate("default", "agetty.ttyS0");
|
mkdir -p -m0755 /run/runit /run/lvm /run/user /run/lock /run/log /dev/pts /dev/shm
|
||||||
|
mountpoint -q /dev/pts || mount -o mode=0620,gid=5,nosuid,noexec -n -t devpts devpts /dev/pts
|
||||||
await mkdirp(r("/etc/dropbear"));
|
mountpoint -q /dev/shm || mount -o mode=1777,nosuid,nodev -n -t tmpfs shm /dev/shm
|
||||||
for (const t of ["rsa", "dss", "ed25519", "ecdsa"]) {
|
mountpoint -q /sys/kernel/security || mount -n -t securityfs securityfs /sys/kernel/security
|
||||||
await execFile("dropbearkey", [
|
`
|
||||||
"-t",
|
|
||||||
t,
|
|
||||||
"-f",
|
|
||||||
r(`/etc/dropbear/dropbear_${t}_host_key`),
|
|
||||||
]);
|
|
||||||
}
|
|
||||||
|
|
||||||
await execFile("sudo", ["mkdir", "-p", r("/root/.ssh")]);
|
|
||||||
await writeFile(
|
|
||||||
"authorized_keys",
|
|
||||||
"ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIEhrcTPMrhrOdwpgFkRFjTt8rdxt3gg16LJvBCZENRWa user@personal"
|
|
||||||
);
|
);
|
||||||
await execFile("sudo", [
|
|
||||||
"mv",
|
|
||||||
"authorized_keys",
|
|
||||||
r("/root/.ssh/authorized_keys"),
|
|
||||||
]);
|
|
||||||
|
|
||||||
await alpine.rcUpdate("default", "networking");
|
await writeSbin(
|
||||||
await writeFile(
|
r("/etc/init-files/05-misc.sh"),
|
||||||
r("/etc/network/interfaces"),
|
`#!/bin/sh
|
||||||
`auto lo
|
install -m0664 -o root -g utmp /dev/null /run/utmp
|
||||||
iface lo inet loopback`
|
#halt -B # for wtmp
|
||||||
|
|
||||||
|
ip link set up dev lo
|
||||||
|
hostname -F /etc/hostname
|
||||||
|
`
|
||||||
);
|
);
|
||||||
|
|
||||||
|
// await mkdirp(r("/etc/dropbear"));
|
||||||
|
// for (const t of ["rsa", "dss", "ed25519", "ecdsa"]) {
|
||||||
|
// await execFile("dropbearkey", [
|
||||||
|
// "-t",
|
||||||
|
// t,
|
||||||
|
// "-f",
|
||||||
|
// r(`/etc/dropbear/dropbear_${t}_host_key`),
|
||||||
|
// ]);
|
||||||
|
// }
|
||||||
|
|
||||||
|
// await execFile("sudo", ["mkdir", "-p", r("/root/.ssh")]);
|
||||||
|
// await writeFile(
|
||||||
|
// "authorized_keys",
|
||||||
|
// "ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIEhrcTPMrhrOdwpgFkRFjTt8rdxt3gg16LJvBCZENRWa user@personal"
|
||||||
|
// );
|
||||||
|
// await execFile("sudo", [
|
||||||
|
// "mv",
|
||||||
|
// "authorized_keys",
|
||||||
|
// r("/root/.ssh/authorized_keys"),
|
||||||
|
// ]);
|
||||||
|
|
||||||
const ext4 = "rootfs.ext4";
|
const ext4 = "rootfs.ext4";
|
||||||
await rm(ext4);
|
await rm(ext4);
|
||||||
await execFile("fallocate", ["--length", "1G", ext4]);
|
await execFile("fallocate", ["--length", "1G", ext4]);
|
||||||
|
@ -129,3 +127,27 @@ async function mkdirp(path) {
|
||||||
function r(path) {
|
function r(path) {
|
||||||
return join(root, path);
|
return join(root, path);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param {import("fs").PathLike} path
|
||||||
|
* @param {string} content
|
||||||
|
*/
|
||||||
|
async function writeSbin(path, content) {
|
||||||
|
try {
|
||||||
|
await lstat(path);
|
||||||
|
await rm(path, { recursive: true });
|
||||||
|
} catch {}
|
||||||
|
await writeFile(path, content);
|
||||||
|
await chmod(path, 0o500);
|
||||||
|
}
|
||||||
|
|
||||||
|
async function buildInit() {
|
||||||
|
const srcPath = "./rootfs/init";
|
||||||
|
await execFile("podman", [
|
||||||
|
...["run", "--rm", "-it"],
|
||||||
|
...["-v", `${srcPath}:/home/rust/src:Z`],
|
||||||
|
"docker.io/messense/rust-musl-cross:x86_64-musl",
|
||||||
|
...["cargo", "build", "--release"],
|
||||||
|
]);
|
||||||
|
return join(srcPath, "target/x86_64-unknown-linux-musl/release/init");
|
||||||
|
}
|
||||||
|
|
1
rootfs/init/.gitignore
vendored
Normal file
1
rootfs/init/.gitignore
vendored
Normal file
|
@ -0,0 +1 @@
|
||||||
|
target/
|
126
rootfs/init/Cargo.lock
generated
Normal file
126
rootfs/init/Cargo.lock
generated
Normal file
|
@ -0,0 +1,126 @@
|
||||||
|
# This file is automatically @generated by Cargo.
|
||||||
|
# It is not intended for manual editing.
|
||||||
|
version = 3
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "bitflags"
|
||||||
|
version = "1.3.2"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "bef38d45163c2f1dde094a7dfd33ccf595c92905c8f8f4fdc18d06fb1037718a"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "cc"
|
||||||
|
version = "1.0.79"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "50d30906286121d95be3d479533b458f87493b30a4b5f79a607db8f5d11aa91f"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "cfg-if"
|
||||||
|
version = "1.0.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "init"
|
||||||
|
version = "0.1.0"
|
||||||
|
dependencies = [
|
||||||
|
"nix",
|
||||||
|
"serde",
|
||||||
|
"serde_json",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "itoa"
|
||||||
|
version = "1.0.6"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "453ad9f582a441959e5f0d088b02ce04cfe8d51a8eaf077f12ac6d3e94164ca6"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "libc"
|
||||||
|
version = "0.2.146"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "f92be4933c13fd498862a9e02a3055f8a8d9c039ce33db97306fd5a6caa7f29b"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "nix"
|
||||||
|
version = "0.19.1"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "b2ccba0cfe4fdf15982d1674c69b1fd80bad427d293849982668dfe454bd61f2"
|
||||||
|
dependencies = [
|
||||||
|
"bitflags",
|
||||||
|
"cc",
|
||||||
|
"cfg-if",
|
||||||
|
"libc",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "proc-macro2"
|
||||||
|
version = "1.0.60"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "dec2b086b7a862cf4de201096214fa870344cf922b2b30c167badb3af3195406"
|
||||||
|
dependencies = [
|
||||||
|
"unicode-ident",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "quote"
|
||||||
|
version = "1.0.28"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "1b9ab9c7eadfd8df19006f1cf1a4aed13540ed5cbc047010ece5826e10825488"
|
||||||
|
dependencies = [
|
||||||
|
"proc-macro2",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "ryu"
|
||||||
|
version = "1.0.13"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "f91339c0467de62360649f8d3e185ca8de4224ff281f66000de5eb2a77a79041"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "serde"
|
||||||
|
version = "1.0.164"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "9e8c8cf938e98f769bc164923b06dce91cea1751522f46f8466461af04c9027d"
|
||||||
|
dependencies = [
|
||||||
|
"serde_derive",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "serde_derive"
|
||||||
|
version = "1.0.164"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "d9735b638ccc51c28bf6914d90a2e9725b377144fc612c49a611fddd1b631d68"
|
||||||
|
dependencies = [
|
||||||
|
"proc-macro2",
|
||||||
|
"quote",
|
||||||
|
"syn",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "serde_json"
|
||||||
|
version = "1.0.97"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "bdf3bf93142acad5821c99197022e170842cdbc1c30482b98750c688c640842a"
|
||||||
|
dependencies = [
|
||||||
|
"itoa",
|
||||||
|
"ryu",
|
||||||
|
"serde",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "syn"
|
||||||
|
version = "2.0.18"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "32d41677bcbe24c20c52e7c70b0d8db04134c5d1066bf98662e2871ad200ea3e"
|
||||||
|
dependencies = [
|
||||||
|
"proc-macro2",
|
||||||
|
"quote",
|
||||||
|
"unicode-ident",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "unicode-ident"
|
||||||
|
version = "1.0.9"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "b15811caf2415fb889178633e7724bad2509101cde276048e013b9def5e51fa0"
|
11
rootfs/init/Cargo.toml
Normal file
11
rootfs/init/Cargo.toml
Normal file
|
@ -0,0 +1,11 @@
|
||||||
|
[package]
|
||||||
|
name = "init"
|
||||||
|
version = "0.1.0"
|
||||||
|
edition = "2021"
|
||||||
|
|
||||||
|
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
|
||||||
|
|
||||||
|
[dependencies]
|
||||||
|
serde = { version = "1.0.164", features = ["derive"] }
|
||||||
|
serde_json = "1.0.97"
|
||||||
|
nix = "0.19"
|
52
rootfs/init/src/main.rs
Normal file
52
rootfs/init/src/main.rs
Normal file
|
@ -0,0 +1,52 @@
|
||||||
|
use nix::mount::MsFlags;
|
||||||
|
use serde::{Deserialize, Serialize};
|
||||||
|
use std::fs;
|
||||||
|
use std::{env, io, os::unix, path::Path, process};
|
||||||
|
// inspirado en https://github.com/superfly/init-snapshot/blob/public/src/bin/init/main.rs#L230
|
||||||
|
|
||||||
|
#[derive(Serialize, Deserialize, Debug)]
|
||||||
|
#[serde(rename_all = "PascalCase")]
|
||||||
|
struct Config {
|
||||||
|
env: Vec<String>,
|
||||||
|
cmd: Vec<String>,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Serialize, Deserialize, Debug)]
|
||||||
|
struct ImageConfig {
|
||||||
|
architecture: String,
|
||||||
|
config: Config,
|
||||||
|
}
|
||||||
|
|
||||||
|
fn main() -> Result<(), io::Error> {
|
||||||
|
let src_path = "/image_root";
|
||||||
|
fs::create_dir_all(src_path)?;
|
||||||
|
|
||||||
|
nix::mount::mount::<_, _, _, [u8]>(
|
||||||
|
Some("/dev/vdb"),
|
||||||
|
src_path,
|
||||||
|
Some("squashfs"),
|
||||||
|
MsFlags::MS_RDONLY,
|
||||||
|
None,
|
||||||
|
)
|
||||||
|
.unwrap();
|
||||||
|
|
||||||
|
let config_file = Path::new(&src_path).join(".fireactions-image-config.json");
|
||||||
|
let config_text = fs::read(config_file)?;
|
||||||
|
let config: ImageConfig = serde_json::from_slice(&config_text).unwrap();
|
||||||
|
|
||||||
|
println!("{:#?}", config);
|
||||||
|
|
||||||
|
unix::fs::chroot(src_path)?;
|
||||||
|
env::set_current_dir("/")?;
|
||||||
|
|
||||||
|
let mut child = process::Command::new(&config.config.cmd[0])
|
||||||
|
.args(&config.config.cmd[1..])
|
||||||
|
.env_clear()
|
||||||
|
.envs(config.config.env.iter().map(|s| s.split_once('=').unwrap()))
|
||||||
|
.spawn()?;
|
||||||
|
let status = child.wait()?;
|
||||||
|
if !status.success() {
|
||||||
|
println!("{}", status);
|
||||||
|
}
|
||||||
|
Ok(())
|
||||||
|
}
|
Loading…
Reference in a new issue