use super::{download, run, run_chroot, tempfile, Image}; use crate::setup::Setup; use anyhow::Context as _; use std::{ fs::{self, File}, io::Write as _, path::Path }; pub fn init_os(setup: &Setup, img: &Image) -> anyhow::Result<()> { eprintln!("Preparing operating system ..."); // write raspberry pi config fs::write(img.boot.0.join("cmdline.txt"), &setup.os.rpi.cmdline)?; let mut config = File::create(img.boot.0.join("config.txt"))?; writeln!( config, r#"# Do not modify this file. Create and/or modify usercfg.txt instead. [pi3] kernel=vmlinuz-rpi initramfs initramfs-rpi [pi3+] kernel=vmlinuz-rpi initramfs initramfs-rpi [pi4] enable_gic=1 kernel=vmlinuz-rpi4 initramfs initramfs-rpi4 [all]"# )?; if setup.os.alpine.arch.is64bit() { writeln!(config, "arm_64bit=1")?; } writeln!(config, "include usercfg.txt")?; drop(config); fs::write(img.boot.0.join("usercfg.txt"), &setup.os.rpi.usercfg)?; // write initial apk config let etc = img.root.0.join("etc"); let etc_apk = etc.join("apk"); let etc_apk_keys = etc_apk.join("keys"); fs::create_dir_all(&etc_apk_keys).context("Failed to create /etc/apk/keys directory")?; let mut repositories = File::create(etc_apk.join("repositories"))?; for repo in ["main", "community"] { writeln!(repositories, "{}/{}/{repo}", setup.os.alpine.mirror, setup.os.alpine.branch)?; } for repo in &setup.os.alpine.extra_repos { writeln!(repositories, "{repo}")?; } drop(repositories); // download apk keys let keys = tempfile()?; download( keys.path(), &format!( "https://gitlab.alpinelinux.org/alpine/aports/-/archive/{branch}/aports-{branch}.tar.bz2?path=main/alpine-keys", branch = setup.os.alpine.branch.git_branch() ) )?; run(&[ "tar", "xfj", keys.path().to_str().unwrap(), "-C", etc_apk_keys.to_str().unwrap(), "--strip-components=3", "--wildcards", "*/alpine-devel@lists.alpinelinux.org-*.rsa.pub" ])?; drop(keys); // copy additional keys for key in &setup.os.alpine.extra_keys { let key = Path::new(key); let name = key.file_name().unwrap(); fs::copy(Path::new("setup").join(key), etc_apk_keys.join(name))?; } // bootstrap alpine run(&[ "docker", "run", "--rm", "-v", &format!("{root}:{root}", root = &*img.root), "alpine", "apk", "add", "--root", &img.root, "--update-cache", "--initdb", "--arch", setup.os.alpine.arch.to_str(), "agetty", "alpine-base", "alpine-sdk", "ca-certificates", "elogind", "eudev", "haveged", "linux-rpi", "linux-rpi4", "openssh", "parted", "raspberrypi-bootloader", "sudo", "tzdata", "udev-init-scripts", "util-linux-login", "zram-init" ])?; run(&[ "sed", "-E", "-e", "s,getty(.*)$,agetty --noclear\\1 linux,", "-i", etc.join("inittab").to_str().unwrap() ])?; for service in ["udev", "udev-postmount", "udev-settle", "udev-trigger"] { run_chroot(img, "root", ["rc-update", "add", service, "sysinit"])?; } for service in ["modules", "syslog", "swclock"] { run_chroot(img, "root", ["rc-update", "add", service, "boot"])?; } for service in ["elogind", "haveged", "ntpd", "sshd"] { run_chroot(img, "root", ["rc-update", "add", service, "default"])?; } // configure fstab fs::write( etc.join("fstab"), r#"# /dev/mmcblk0p1 /boot vfat defaults 0 2 /dev/mmcblk0p2 / ext4 defaults,noatime 0 1 "# )?; // something is broken with abuild's functions.sh, so let's set CBUILD ourselves let profile_d = etc.join("profile.d"); fs::create_dir_all(&profile_d)?; fs::write( profile_d.join("cbuild.sh"), &format!("export CBUILD={}", setup.os.alpine.arch.cbuild()) )?; // set up the locale run_chroot(img, "root", ["setup-hostname", &setup.os.host.hostname])?; run_chroot(img, "root", ["setup-timezone", "-z", &setup.os.host.timezone])?; if let Some(variant) = setup.os.host.keymap_variant.as_deref() { run_chroot(img, "root", ["setup-keymap", &setup.os.host.keymap, variant])?; } else { run_chroot(img, "root", ["setup-keymap", &setup.os.host.keymap])?; } // set up a user that we can use to install packages run_chroot(img, "root", ["adduser", "-D", "mkalpiimg"])?; run_chroot(img, "root", ["adduser", "mkalpiimg", "abuild"])?; run_chroot(img, "root", "echo mkalpiimg:mkalpiimg | chpasswd")?; fs::write(etc.join("sudoers.d").join("mkalpiimg"), "%abuild ALL=(ALL) NOPASSWD: ALL")?; fs::create_dir_all(img.root.0.join("var").join("cache").join("distfiles"))?; run_chroot(img, "root", "chgrp abuild /var/cache/distfiles; chmod 775 /var/cache/distfiles")?; run_chroot(img, "mkalpiimg", ["abuild-keygen", "-a", "-n", "-b", "4096"])?; run_chroot(img, "root", "cp /home/mkalpiimg/.abuild/*.rsa.pub /etc/apk/keys/")?; // avoid a bug with cargo in qemu: https://github.com/rust-lang/cargo/issues/10583 let cargo = img.root.0.join("home").join("mkalpiimg").join(".cargo"); fs::create_dir_all(&cargo)?; fs::write(cargo.join("config.toml"), "[net]\ngit-fetch-with-cli = true\n")?; // set up the other users as requested fs::write(etc.join("sudoers.d").join("wheel"), "%wheel ALL=(ALL:ALL) ALL")?; for user in &setup.os.user { run_chroot(img, "root", ["adduser", "-s", "/bin/ash", "-D", &user.name])?; if user.sudo { run_chroot(img, "root", ["adduser", &user.name, "wheel"])?; } for grp in &user.groups { run_chroot(img, "root", ["adduser", &user.name, grp])?; } let ssh = img.root.0.join("home").join(&user.name).join(".ssh"); fs::create_dir_all(&ssh)?; let mut keys = File::create(ssh.join("authorized_keys"))?; for key in &user.authorized_keys { writeln!(keys, "{key}")?; } drop(keys); run_chroot(img, "root", ["chown", "-R", &user.name, &format!("/home/{}/.ssh", user.name)])?; run_chroot(img, &user.name, "chmod 700 ~/.ssh; chmod 600 ~/.ssh/*")?; } Ok(()) }