From e5f1c32ed73c81bc34f88536443cd1ed7e6d8405 Mon Sep 17 00:00:00 2001 From: Yuhang Wei Date: Fri, 15 Nov 2024 10:48:51 +0000 Subject: [PATCH 1/3] feat(kbimg): complete development of kbimg cli tool support generating vm-img, pxe-img, upgrade-img, admin-container and dm-verity vm-img support generating customize image by writing toml config. possible configurable terms: copy_files, users, grub, systemd_service, chroot_script, disk_partition, persist_mkdir and dm_verity Signed-off-by: Yuhang Wei --- Cargo.lock | 88 +- Cargo.toml | 3 +- KubeOS-Rust/kbimg/Cargo.toml | 5 +- KubeOS-Rust/kbimg/kbimg.toml | 81 +- KubeOS-Rust/kbimg/src/admin_container.rs | 84 +- KubeOS-Rust/kbimg/src/commands.rs | 158 +- KubeOS-Rust/kbimg/src/custom.rs | 207 +++ KubeOS-Rust/kbimg/src/docker_img.rs | 39 +- KubeOS-Rust/kbimg/src/main.rs | 151 +- KubeOS-Rust/kbimg/src/repo.rs | 448 +++-- KubeOS-Rust/kbimg/src/scripts_gen.rs | 1996 ++++++---------------- KubeOS-Rust/kbimg/src/utils.rs | 51 +- KubeOS-Rust/kbimg/src/values.rs | 1768 ++++++++++++++++++- 13 files changed, 3106 insertions(+), 1973 deletions(-) create mode 100644 KubeOS-Rust/kbimg/src/custom.rs diff --git a/Cargo.lock b/Cargo.lock index a4cecc3d..09fd6847 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -304,31 +304,6 @@ dependencies = [ "libc", ] -[[package]] -name = "crossbeam-deque" -version = "0.8.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "613f8cc01fe9cf1a3eb3d7f488fd2fa8388403e97039e2f73692932e291a770d" -dependencies = [ - "crossbeam-epoch", - "crossbeam-utils", -] - -[[package]] -name = "crossbeam-epoch" -version = "0.9.18" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5b82ac4a3c2ca9c3460964f020e1402edd5753411d7737aa39c3714ad1b5420e" -dependencies = [ - "crossbeam-utils", -] - -[[package]] -name = "crossbeam-utils" -version = "0.8.20" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "22ec99545bb0ed0ea7bb9b8e1e9122ea386ff8a48c0922e43f36d45ab09e0e80" - [[package]] name = "crypto-common" version = "0.1.6" @@ -539,6 +514,16 @@ version = "2.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "6c2141d6d6c8512188a7891b4b01590a45f6dac67afb4f255c4124dbb86d4eaa" +[[package]] +name = "fs2" +version = "0.4.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9564fc758e15025b46aa6643b1b77d047d1a56a1aea6e01002ac0c7026876213" +dependencies = [ + "libc", + "winapi", +] + [[package]] name = "futures" version = "0.3.31" @@ -1156,10 +1141,11 @@ dependencies = [ "anyhow", "clap", "env_logger", + "fs2", "log", "regex", "serde", - "sysinfo", + "strfmt", "toml 0.7.6", ] @@ -1458,15 +1444,6 @@ version = "0.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "61807f77802ff30975e01f4f071c8ba10c022052f98b3294119f3e615d13e5be" -[[package]] -name = "ntapi" -version = "0.4.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e8a3895c6391c39d7fe7ebc444a87eb2991b2a0bc718fdabd071eec617fc68e4" -dependencies = [ - "winapi", -] - [[package]] name = "num-traits" version = "0.2.17" @@ -1874,26 +1851,6 @@ dependencies = [ "rand_core 0.5.1", ] -[[package]] -name = "rayon" -version = "1.10.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b418a60154510ca1a002a752ca9714984e21e4241e804d32555251faf8b78ffa" -dependencies = [ - "either", - "rayon-core", -] - -[[package]] -name = "rayon-core" -version = "1.12.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1465873a3dfdaa8ae7cb14b4383657caab0b3e8a0aa9ae8e04b044854c8dfce2" -dependencies = [ - "crossbeam-deque", - "crossbeam-utils", -] - [[package]] name = "redox_syscall" version = "0.2.16" @@ -2310,6 +2267,12 @@ version = "0.9.8" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "6980e8d7511241f8acf4aebddbb1ff938df5eebe98691418c4468d0b72a96a67" +[[package]] +name = "strfmt" +version = "0.2.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7a8348af2d9fc3258c8733b8d9d8db2e56f54b2363a4b5b81585c7875ed65e65" + [[package]] name = "strsim" version = "0.10.0" @@ -2353,21 +2316,6 @@ dependencies = [ "futures-core", ] -[[package]] -name = "sysinfo" -version = "0.29.11" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cd727fc423c2060f6c92d9534cef765c65a6ed3f428a03d7def74a8c4348e666" -dependencies = [ - "cfg-if", - "core-foundation-sys", - "libc", - "ntapi", - "once_cell", - "rayon", - "winapi", -] - [[package]] name = "system-configuration" version = "0.6.1" diff --git a/Cargo.toml b/Cargo.toml index 37a83d2b..79221187 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -19,6 +19,7 @@ async-trait = { version = "0.1" } clap = { version = "~4.3", default-features = false } cli = { version = "1.0.6", path = "./KubeOS-Rust/cli" } env_logger = { version = "~0.10" } +fs2 = { version = "0.4.3" } futures = { version = "0.3" } jsonrpc = { version = "~0.13.0", features = ["simple_uds"] } jsonrpc-core = { version = "18.0" } @@ -36,7 +37,7 @@ schemars = { version = "=0.8.21" } serde = { version = "1.0.215", features = ["derive"] } serde_json = { version = "1.0.133" } sha2 = { version = "=0.10.8" } -sysinfo = { version = "=0.29.11" } +strfmt = { version = "0.2.4" } thiserror = { version = "2.0" } tokio = { version = "~1.38.0", default-features = false } tokio-retry = { version = "0.3" } diff --git a/KubeOS-Rust/kbimg/Cargo.toml b/KubeOS-Rust/kbimg/Cargo.toml index 8bb93e30..f29eb910 100644 --- a/KubeOS-Rust/kbimg/Cargo.toml +++ b/KubeOS-Rust/kbimg/Cargo.toml @@ -1,5 +1,5 @@ [package] -description = "KubeOS kbimg" +description = "CLI tool for generating various types of image for KubeOS" edition.workspace = true license.workspace = true name = "kbimg" @@ -16,8 +16,9 @@ clap = { workspace = true, features = [ "usage", ] } env_logger = { workspace = true } +fs2 = { workspace = true } log = { workspace = true } regex = { workspace = true } serde = { workspace = true } -sysinfo = { workspace = true } +strfmt = { workspace = true } toml = { workspace = true } diff --git a/KubeOS-Rust/kbimg/kbimg.toml b/KubeOS-Rust/kbimg/kbimg.toml index aaa9b48f..d3cb507f 100644 --- a/KubeOS-Rust/kbimg/kbimg.toml +++ b/KubeOS-Rust/kbimg/kbimg.toml @@ -1,59 +1,59 @@ [from_repo] -agent_path = "/root/KubeOS/bin/os-agent" -image_type = "vm-repo" +agent_path = "./bin/rust/release/os-agent" legacy_bios = false repo_path = "/etc/yum.repos.d/openEuler.repo" -root_passwd = "$1$xyz$RdLyKTL32WEvK3lg8CXID0" -version = "v1" +root_passwd = "$1$xyz$RdLyKTL32WEvK3lg8CXID0" # default passwd: openEuler12#$, use "openssl passwd -6 -salt $(head -c18 /dev/urandom | openssl base64)" to generate your passwd rpmlist = [ "NetworkManager", + "cloud-init", "conntrack-tools", + "containerd", "containernetworking-plugins", - "coreutils", + "cri-tools", "dhcp", - "docker", - "dosfstools", - "dracut", "ebtables", "ethtool", - "gawk", - "hwinfo", + "iptables", "kernel", "kubernetes-kubeadm", "kubernetes-kubelet", - "lvm2", - "net-tools", "openssh-server", "passwd", - "parted", "rsyslog", "socat", - "sudo", + "tar", "vi", + # Below packages are required for pxe-image. Uncomment them if you want to generate pxe-image. + # "coreutils", + # "dosfstools", + # "dracut", + # "gawk", + # "hwinfo", + # "net-tools", + # "parted", ] - -# [from_dockerimg] -# docker_img = "" -# image_type = "vm-docker" +upgrade_img = "kubeos-upgrade:v1" +version = "v1" # [admin_container] -# dockerfile = "" -# docker_img = "" +# img_name = "kubeos-admin-container:v1" +# hostshell = "./bin/hostshell" # [pxe_config] -# rootfs_name = "kubeos.tar" -# disk = "/dev/sda" -# server_ip = "192.168.1.50" +# dhcp = false +# disk = "/dev/vda" # local_ip = "192.168.1.100" -# route_ip = "192.168.1.1" -# netmask = "255.255.255.0" # net_name = "eth0" +# netmask = "255.255.255.0" +# rootfs_name = "kubeos.tar" +# route_ip = "192.168.1.1" +# server_ip = "192.168.1.50" # [[users]] -# groups = ["admin"] +# groups = ["admin", "wheel"] # name = "foo" # passwd = "foo" -# sudo = "ALL=(ALL) ALL" +# primary_group = "foo" # [[users]] # groups = ["example"] @@ -61,27 +61,32 @@ rpmlist = [ # passwd = "bar" # [[copy_files]] -# dst = "/root/ztest2" -# src = "/root/KubeOS/ztest2" +# create_dir = "/root/test" +# dst = "/root/test/foo.txt" +# src = "/root/KubeOS/foo.txt" # [[copy_files]] -# dst = "/root/ztest1" -# src = "/root/KubeOS/ztest1/*" +# dst = "/etc/bar.txt" +# src = "../bar.txt" # [grub] # passwd = "foo" # [systemd_service] -# name = ["kubelet", "containerd"] +# name = ["containerd", "kubelet"] # [chroot_script] -# path = "../../ztest/myscript.sh" +# path = "./my_chroot.sh" +# rm = true # [disk_partition] -# first = 100 -# second = 2500 -# third = 2300 -# img_size = 30 +# img_size = 30 # GB +# root = 3000 # MiB # [persist_mkdir] -# name = ["lv_data"] +# name = ["bar", "foo"] + +# [dm_verity] +# efi_key = "foo" +# grub_key = "bar" +# keys_dir = "./keys" diff --git a/KubeOS-Rust/kbimg/src/admin_container.rs b/KubeOS-Rust/kbimg/src/admin_container.rs index 03196739..b674e79b 100644 --- a/KubeOS-Rust/kbimg/src/admin_container.rs +++ b/KubeOS-Rust/kbimg/src/admin_container.rs @@ -1,5 +1,5 @@ /* - * Copyright (c) Huawei Technologies Co., Ltd. 2023. All rights reserved. + * Copyright (c) Huawei Technologies Co., Ltd. 2024. All rights reserved. * KubeOS is licensed under the Mulan PSL v2. * You can use this software according to the terms and conditions of the Mulan PSL v2. * You may obtain a copy of Mulan PSL v2 at: @@ -15,7 +15,7 @@ use std::{ path::PathBuf, }; -use anyhow::bail; +use anyhow::{bail, Result}; use crate::{ commands::AdminContainerInfo, @@ -26,42 +26,25 @@ use crate::{ }; impl CreateImage for AdminContainerInfo { - fn prepare(&self, _: &mut Config) -> anyhow::Result<()> { - let dockerfile = &self.dockerfile; - let image_name = &self.docker_img; - verify_admin_input(&dockerfile, &image_name)?; - check_dockerfile_valid(&dockerfile)?; + fn prepare(&self, _: &mut Config) -> Result<()> { + let hostshell = &self.hostshell; + let image_name = &self.img_name; + verify_admin_input(hostshell, &image_name)?; + utils::is_file_valid("admin-container hosthshell", hostshell)?; Ok(()) } - fn generate_scripts(&self, _: &Config) -> anyhow::Result { - // admin-container - match create_dir_all(ADMIN_CONTAINER_DIR) { - Ok(_) => { - // Dockerfile - let dockerfile_path = format!("{}/{}", ADMIN_CONTAINER_DIR, ADMIN_DOCKERFILE); - let mut dockerfile = File::create(&dockerfile_path)?; - gen_admin_dockerfile(&mut dockerfile)?; - set_permissions(&dockerfile_path, CONFIG_PERMISSION)?; - // set-ssh-pub-key.service - let set_ssh_pub_key_service_path = format!("{}/{}", ADMIN_CONTAINER_DIR, ADMIN_SET_SSH_PUB_KEY_SERVICE); - let mut set_ssh_pub_key_service = File::create(&set_ssh_pub_key_service_path)?; - gen_set_ssh_pub_key_service(&mut set_ssh_pub_key_service)?; - set_permissions(&set_ssh_pub_key_service_path, CONFIG_PERMISSION)?; - // set-ssh-pub-key.sh - let set_ssh_pub_key_path = format!("{}/{}", ADMIN_CONTAINER_DIR, ADMIN_SET_SSH_PUB_KEY_SH); - let mut set_ssh_pub_key = File::create(&set_ssh_pub_key_path)?; - gen_set_ssh_pub_key(&mut set_ssh_pub_key)?; - set_permissions(&set_ssh_pub_key_path, EXEC_PERMISSION)?; - }, - Err(e) => { - bail!(e); - }, - } - // kbimg.sh + fn generate_scripts(&self, _: &Config) -> Result { + create_dir_all(ADMIN_CONTAINER_DIR)?; + utils::set_permissions(ADMIN_CONTAINER_DIR, DIR_PERMISSION)?; + self.write_dockerfile()?; + self.write_set_ssh_pub_key_service()?; + self.write_set_ssh_pub_key_sh()?; + let kbimg_path = format!("{}/{}", SCRIPTS_DIR, KBIMG_SH); let mut kbimg = File::create(&format!("{}/{}", SCRIPTS_DIR, KBIMG_SH))?; - gen_admin_vars(&mut kbimg, &self.docker_img, &self.dockerfile)?; + gen_admin_vars(&mut kbimg, &self.img_name, &self.hostshell)?; + gen_test_lock(&mut kbimg)?; gen_create_admin_img(&mut kbimg)?; set_permissions(&kbimg_path, EXEC_PERMISSION)?; @@ -69,16 +52,37 @@ impl CreateImage for AdminContainerInfo { } } -fn verify_admin_input(dockerfile: &PathBuf, image_name: &str) -> anyhow::Result<()> { - if !utils::is_valid_param(dockerfile.to_str().unwrap()) { - bail!("params {} is invalid, please check input", dockerfile.to_str().unwrap()); +impl AdminContainerInfo { + fn write_dockerfile(&self) -> Result<()> { + let dockerfile_path = format!("{}/{}", ADMIN_CONTAINER_DIR, ADMIN_DOCKERFILE); + let mut dockerfile = File::create(&dockerfile_path)?; + base_gen(&mut dockerfile, ADMIN_DOCKERFILE_CONTENT, false)?; + set_permissions(&dockerfile_path, CONFIG_PERMISSION)?; + Ok(()) + } + + fn write_set_ssh_pub_key_service(&self) -> Result<()> { + let set_ssh_pub_key_service_path = format!("{}/{}", ADMIN_CONTAINER_DIR, ADMIN_SET_SSH_PUB_KEY_SERVICE); + let mut set_ssh_pub_key_service = File::create(&set_ssh_pub_key_service_path)?; + base_gen(&mut set_ssh_pub_key_service, SET_SSH_PUB_KEY_SERVICE, false)?; + set_permissions(&set_ssh_pub_key_service_path, CONFIG_PERMISSION)?; + Ok(()) + } + fn write_set_ssh_pub_key_sh(&self) -> Result<()> { + let set_ssh_pub_key_path = format!("{}/{}", ADMIN_CONTAINER_DIR, ADMIN_SET_SSH_PUB_KEY_SH); + let mut set_ssh_pub_key = File::create(&set_ssh_pub_key_path)?; + base_gen(&mut set_ssh_pub_key, SET_SSH_PUB_KEY_SH, true)?; + set_permissions(&set_ssh_pub_key_path, EXEC_PERMISSION)?; + Ok(()) + } +} + +fn verify_admin_input(hostshell: &PathBuf, image_name: &str) -> Result<()> { + if !utils::is_valid_param(hostshell.to_str().unwrap()) { + bail!("params {} is invalid, please check input", hostshell.to_str().unwrap()); } if !utils::is_valid_param(image_name) { bail!("params {} is invalid, please check input", image_name); } Ok(()) } - -fn check_dockerfile_valid(dockerfile: &PathBuf) -> anyhow::Result<()> { - utils::is_file_valid("admin-container Dockerfile", dockerfile) -} diff --git a/KubeOS-Rust/kbimg/src/commands.rs b/KubeOS-Rust/kbimg/src/commands.rs index f334f048..c5bc3890 100644 --- a/KubeOS-Rust/kbimg/src/commands.rs +++ b/KubeOS-Rust/kbimg/src/commands.rs @@ -1,5 +1,5 @@ /* - * Copyright (c) Huawei Technologies Co., Ltd. 2023. All rights reserved. + * Copyright (c) Huawei Technologies Co., Ltd. 2024. All rights reserved. * KubeOS is licensed under the Mulan PSL v2. * You can use this software according to the terms and conditions of the Mulan PSL v2. * You may obtain a copy of Mulan PSL v2 at: @@ -12,99 +12,89 @@ use std::path::PathBuf; -use clap::{Args, Parser, Subcommand}; +use clap::{Parser, Subcommand, ValueEnum}; use serde::Deserialize; #[derive(Parser)] #[clap(name = "kbimg")] #[clap(author, version, about)] -#[clap(long_about = "A tool for creating KubeOS images.")] +#[clap(about = "CLI tool for generating various types of image for KubeOS")] pub struct Cli { - /// Path to the detailed configuration toml file - #[clap(short, long, value_parser)] - pub config: Option, - /// Enable debug mode, keep the scripts after execution + /// Enable debug mode, generate the scripts without execution #[clap(short, long, action)] pub debug: bool, #[clap(subcommand)] - pub commands: Option, + pub commands: Commands, } -#[derive(Subcommand, Debug, Deserialize)] +#[derive(Subcommand, Debug)] pub enum Commands { - /// Create a new container image for upgrading KubeOS - #[clap(name = "upgrade")] - UpgradeImage(RepoInfo), - /// Create a new KubeOS vm image from repo - #[clap(name = "vm-repo")] - VMRepo(RepoInfo), - /// Create a new KubeOS vm image from docker image - #[clap(name = "vm-docker")] - VMDocker(DockerInfo), - /// Create a new KubeOS pxe image from repo - #[clap(name = "pxe-repo")] - PxeRepo(RepoInfo), - /// Create a new KubeOS pxe image from docker image - #[clap(name = "pxe-docker")] - PxeDocker(DockerInfo), - /// Create a KubeOS admin-container image + /// Create a new KubeOS image + Create { + #[arg(value_enum)] + image_type: CreateType, + /// Path to the configuration file + #[arg(short, long, value_parser)] + file: PathBuf, + }, +} + +#[derive(ValueEnum, Clone, Debug)] +pub enum CreateType { + #[clap(name = "vm-img")] + VM, + #[clap(name = "pxe-img")] + PXE, + #[clap(name = "upgrade-img")] + Upgrade, #[clap(name = "admin-container")] - AdminContainer(AdminContainerInfo), + AdminContainer, } -#[derive(Args, Debug, Deserialize, Clone)] +#[derive(Debug, Deserialize, Clone)] pub struct RepoInfo { /// Required: KubeOS version - #[clap(short, long, value_parser)] pub version: String, /// Required: Repo path for installing packages - #[clap(short = 'p', long, value_parser)] pub repo_path: PathBuf, - /// Required: Path to the agent binary - #[clap(short = 'b', long, value_parser)] + /// Required: Path to the os-agent binary pub agent_path: PathBuf, /// Required: Encrypted password for root user - #[clap(short = 'e', long, value_parser)] pub root_passwd: String, - /// Required for upgrade - #[clap(short = 'd', long, value_parser)] - pub docker_img: Option, + /// Required for creating upgrade docker image + pub upgrade_img: Option, /// Required: RPM packages - #[clap(short = 'r', long, value_parser)] pub rpmlist: Vec, /// Optional: boot mode, default is uefi, enable this flag for legacy bios - #[clap(short, long, value_parser)] pub legacy_bios: bool, - #[clap(skip)] - pub image_type: String, - #[clap(skip)] + pub image_type: Option, pub arch: Option, } -#[derive(Args, Debug, Deserialize, Clone)] -pub struct DockerInfo { +#[derive(Debug, Deserialize, Clone)] +pub struct DockerImgInfo { /// Required: Name of the container image - #[clap(short, long, value_parser)] - pub docker_img: String, - #[clap(skip)] - pub image_type: String, + pub upgrade_img: String, + /// Optional: boot mode, default is uefi, enable this flag for legacy bios + pub legacy_bios: bool, + pub image_type: Option, + pub arch: Option, } -#[derive(Args, Debug, Deserialize, Clone)] +#[derive(Debug, Deserialize, Clone)] pub struct AdminContainerInfo { /// Required: Name of the container image - #[clap(short, long, value_parser)] - pub docker_img: String, - /// Required: Path to the Dockerfile - #[clap(short, long, value_parser)] - pub dockerfile: PathBuf, + pub img_name: String, + /// Required: Path to the hostshell binary + pub hostshell: PathBuf, } #[derive(Debug, Deserialize, Default, Clone)] pub struct Config { pub from_repo: Option, - pub from_dockerimg: Option, + pub from_dockerimg: Option, pub admin_container: Option, + pub pxe_config: Option, pub users: Option>, pub copy_files: Option>, pub grub: Option, @@ -112,26 +102,27 @@ pub struct Config { pub chroot_script: Option, pub disk_partition: Option, pub persist_mkdir: Option, - pub pxe_config: Option, + pub dm_verity: Option, } #[derive(Deserialize, Debug, Clone)] pub struct User { pub name: String, pub passwd: String, + pub primary_group: Option, pub groups: Option>, - pub sudo: Option, } #[derive(Deserialize, Debug, Clone)] pub struct CopyFile { pub src: String, pub dst: String, + pub create_dir: Option, } #[derive(Deserialize, Debug, Clone)] pub struct Grub { - pub passwd: Option, + pub passwd: String, } #[derive(Deserialize, Debug, Clone)] @@ -141,15 +132,14 @@ pub struct SystemdService { #[derive(Deserialize, Debug, Clone)] pub struct ChrootScript { - pub path: String, + pub path: PathBuf, + pub rm: Option, } #[derive(Deserialize, Debug, Clone)] pub struct DiskPartition { - pub first: u32, - pub second: u32, - pub third: u32, - pub img_size: u32, + pub root: u32, + pub img_size: Option, } #[derive(Deserialize, Debug, Clone)] @@ -157,14 +147,52 @@ pub struct PersistMkdir { pub name: Vec, } -// pxe config #[derive(Debug, Deserialize, Clone)] pub struct PxeConfig { pub rootfs_name: String, pub disk: String, pub server_ip: String, - pub local_ip: String, + pub local_ip: Option, pub route_ip: String, - pub netmask: String, - pub net_name: String, + pub netmask: Option, + pub net_name: Option, + pub dhcp: Option, +} + +#[derive(Debug, Deserialize, Clone)] +pub struct DmVerity { + pub efi_key: String, + pub grub_key: String, + pub keys_dir: Option, +} + +#[derive(Debug, Deserialize, Clone, Default, PartialEq)] +pub enum ImageType { + #[default] + #[serde(rename = "vm-repo")] + VMRepo, + #[serde(rename = "vm-docker")] + VMDocker, + #[serde(rename = "pxe-repo")] + PxeRepo, + #[serde(rename = "pxe-docker")] + PxeDocker, + #[serde(rename = "admin-container")] + AdminContainer, + #[serde(rename = "upgrade")] + UpgradeImage, +} + +impl From<&str> for ImageType { + fn from(input: &str) -> Self { + match input { + "vm-repo" => ImageType::VMRepo, + "vm-docker" => ImageType::VMDocker, + "pxe-repo" => ImageType::PxeRepo, + "pxe-docker" => ImageType::PxeDocker, + "admin-container" => ImageType::AdminContainer, + "upgrade" => ImageType::UpgradeImage, + _ => ImageType::VMRepo, + } + } } diff --git a/KubeOS-Rust/kbimg/src/custom.rs b/KubeOS-Rust/kbimg/src/custom.rs new file mode 100644 index 00000000..55071bfa --- /dev/null +++ b/KubeOS-Rust/kbimg/src/custom.rs @@ -0,0 +1,207 @@ +/* + * Copyright (c) Huawei Technologies Co., Ltd. 2024. All rights reserved. + * KubeOS is licensed under the Mulan PSL v2. + * You can use this software according to the terms and conditions of the Mulan PSL v2. + * You may obtain a copy of Mulan PSL v2 at: + * http://license.coscl.org.cn/MulanPSL2 + * THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY KIND, EITHER EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO NON-INFRINGEMENT, MERCHANTABILITY OR FIT FOR A PARTICULAR + * PURPOSE. + * See the Mulan PSL v2 for more details. + */ + +use std::{ + fs::{self, File}, + io::Write, +}; + +use anyhow::{anyhow, Result}; + +use crate::{ + commands::{ChrootScript, CopyFile, DmVerity, Grub, User}, + scripts_gen::base_gen, + utils, + values::*, +}; + +impl CopyFile { + pub(crate) fn gen_copy_files(&self, file: &mut dyn Write) -> Result<()> { + if let Some(dir) = &self.create_dir { + writeln!(file, " mkdir -p \"${{RPM_ROOT}}\"{}", dir)?; + } + if !self.src.is_empty() && !self.dst.is_empty() { + writeln!(file, " cp -r {} \"${{RPM_ROOT}}{}\"", self.src, self.dst)?; + } + Ok(()) + } +} + +impl Grub { + pub(crate) fn gen_grub_config(&self, file: &mut dyn Write, legacy_bios: bool, arch: &str) -> Result<()> { + writeln!( + file, + r#"function grub_config() {{ + local GRUB_PATH"# + )?; + if legacy_bios && arch == "x86_64" { + writeln!(file, " GRUB_PATH=\"${{RPM_ROOT}}\"/boot/grub2")?; + } else { + writeln!(file, " GRUB_PATH=\"${{RPM_ROOT}}\"/boot/efi/EFI/openEuler")?; + } + writeln!( + file, + r#" local GRUB_PASSWORD + GRUB_PASSWORD=$(echo -e "{}\n{}" | grub2-mkpasswd-pbkdf2 | grep PBKDF2 | awk '{{print $7}}') + echo "GRUB2_PASSWORD=${{GRUB_PASSWORD}}" >"${{GRUB_PATH}}"/user.cfg + chmod 600 "${{GRUB_PATH}}"/user.cfg +}} +"#, + self.passwd, self.passwd + )?; + Ok(()) + } +} + +impl ChrootScript { + pub(crate) fn gen_chroot_script(&self, file: &mut dyn Write) -> Result<()> { + let script_path = &self.path; + utils::is_file_valid("chroot script", &script_path)?; + let absolute_path = script_path.canonicalize()?; + let script_name = absolute_path.file_name().ok_or_else(|| anyhow!("script name not found"))?; + writeln!( + file, + r#"function chroot_script() {{ + cp "{}" "${{RPM_ROOT}}" + chroot "${{RPM_ROOT}}" bash /{} + {} +}} +"#, + absolute_path.as_path().to_str().unwrap(), + script_name.to_str().unwrap(), + if self.rm.unwrap_or(false) { format!("rm -f /{}", script_name.to_str().unwrap()) } else { "".to_string() } + )?; + Ok(()) + } +} + +impl User { + pub(crate) fn gen_add_users(&self, file: &mut dyn Write) -> Result<()> { + let name = &self.name; + let passwd = &self.passwd; + let mut group_script = String::new(); + let mut script = format!("useradd -m"); + group_script.push_str(&format!("getent group {} || groupadd {}", name, name)); + if let Some(primary_group) = &self.primary_group { + script.push_str(&format!(" -g {}", primary_group)); + } else { + script.push_str(&format!(" -g {}", name)); + } + if let Some(groups) = &self.groups { + script.push_str(&format!(" -G {}", groups.join(","))); + } + script.push_str(&format!(" -s /bin/bash \"{}\"\n", name)); + script.push_str(&format!("echo \"{}:{}\" | chpasswd", name, passwd)); + writeln!(file, "{}\n{}", group_script, script)?; + Ok(()) + } +} + +impl DmVerity { + pub(crate) fn write_dm_verity_repo(&self) -> Result<()> { + fs::create_dir_all(DMV_DIR)?; + utils::set_permissions(DMV_DIR, DIR_PERMISSION)?; + let dmv_chroot = format!("{}/{}", DMV_DIR, DMV_CHROOT); + let dmv_main = format!("{}/{}", DMV_DIR, DMV_MAIN); + let dmv_upgrade_rollback = format!("{}/{}", DMV_DIR, DMV_UPGRADE_ROLLBACK); + let mut dmv_main_file = File::create(&dmv_main)?; + let mut dmv_chroot_file = File::create(&dmv_chroot)?; + let mut dmv_upgrade_rollback_file = File::create(&dmv_upgrade_rollback)?; + base_gen(&mut dmv_main_file, DMV_MAIN_SH, true)?; + base_gen(&mut dmv_chroot_file, DMV_CHROOT_NEW_GRUB_SH, true)?; + base_gen(&mut dmv_upgrade_rollback_file, DMV_UPGRADE_ROLLBACK_SH, true)?; + utils::set_permissions(&dmv_main, EXEC_PERMISSION)?; + utils::set_permissions(&dmv_chroot, EXEC_PERMISSION)?; + utils::set_permissions(&dmv_upgrade_rollback, EXEC_PERMISSION)?; + + fs::create_dir_all(DMV_DRACUT_DIR)?; + utils::set_permissions(DMV_DRACUT_DIR, DIR_PERMISSION)?; + let dmv_dracut_module = format!("{}/{}", DMV_DRACUT_DIR, DMV_DRACUT_MODULE); + let dmv_dracut_mount = format!("{}/{}", DMV_DRACUT_DIR, DMV_DRACUT_MOUNT); + let mut dmv_dracut_module_file = File::create(&dmv_dracut_module)?; + let mut dmv_dracut_mount_file = File::create(&dmv_dracut_mount)?; + base_gen(&mut dmv_dracut_module_file, DMV_MODULE_SETUP_SH, true)?; + base_gen(&mut dmv_dracut_mount_file, DMV_MOUNT_SH, true)?; + utils::set_permissions(&dmv_dracut_module, EXEC_PERMISSION)?; + utils::set_permissions(&dmv_dracut_mount, EXEC_PERMISSION)?; + Ok(()) + } + + pub(crate) fn write_dm_verity_upgrade(&self, file: &mut dyn Write) -> Result<()> { + self.write_dm_verity_repo()?; + self.write_dmv_dockerignore()?; + self.write_dmv_upgrade_dockerfile()?; + + writeln!(file, r#"docker build -t "${{DOCKER_IMG}}" -f "${{SCRIPTS_DIR}}"/Dockerfile "${{SCRIPTS_DIR}}""#)?; + Ok(()) + } + + pub(crate) fn write_dmv_upgrade_dockerfile(&self) -> Result<()> { + let dockerfile_path = format!("{}/{}", SCRIPTS_DIR, DOCKERFILE); + let mut dockerfile = File::create(&dockerfile_path)?; + base_gen(&mut dockerfile, DMV_DOCKERFILE, false)?; + utils::set_permissions(&dockerfile_path, CONFIG_PERMISSION)?; + Ok(()) + } + + pub(crate) fn write_dmv_dockerignore(&self) -> Result<()> { + let dockerig_path = format!("{}/{}", SCRIPTS_DIR, ".dockerignore"); + let mut dockerignore = File::create(&dockerig_path)?; + base_gen(&mut dockerignore, "system.*", false)?; + utils::set_permissions(&dockerig_path, CONFIG_PERMISSION)?; + Ok(()) + } +} + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn test_gen_copy_files_with_multiple_dirs() { + let copy_file = CopyFile { + src: String::from("/home/aaa/test.txt"), + dst: String::from("/home/bbb/test/test1.txt"), + create_dir: Some(String::from("/home/bbb/test")), + }; + + let mut buffer = std::io::Cursor::new(Vec::new()); + copy_file.gen_copy_files(&mut buffer).unwrap(); + + let output = String::from_utf8(buffer.into_inner()).unwrap(); + println!("{}", output); + assert_eq!( + output, + " mkdir -p \"${RPM_ROOT}\"/home/bbb/test\n cp -r /home/aaa/test.txt \"${RPM_ROOT}/home/bbb/test/test1.txt\"\n" + ); + } + + #[test] + fn test_gen_users() { + let user = User { + name: String::from("test"), + passwd: String::from("test"), + primary_group: Some(String::from("test")), + groups: Some(vec![String::from("test1"), String::from("test2")]), + }; + + let mut buffer = std::io::Cursor::new(Vec::new()); + user.gen_add_users(&mut buffer).unwrap(); + + let output = String::from_utf8(buffer.into_inner()).unwrap(); + println!("{}", output); + assert_eq!( + output, + "getent group test || groupadd test\nuseradd -m -g test -G test1,test2 -s /bin/bash \"test\"\necho \"test:test\" | chpasswd\n" + ); + } +} diff --git a/KubeOS-Rust/kbimg/src/docker_img.rs b/KubeOS-Rust/kbimg/src/docker_img.rs index 9c5a8c20..bcfad6d0 100644 --- a/KubeOS-Rust/kbimg/src/docker_img.rs +++ b/KubeOS-Rust/kbimg/src/docker_img.rs @@ -1,5 +1,5 @@ /* - * Copyright (c) Huawei Technologies Co., Ltd. 2023. All rights reserved. + * Copyright (c) Huawei Technologies Co., Ltd. 2024. All rights reserved. * KubeOS is licensed under the Mulan PSL v2. * You can use this software according to the terms and conditions of the Mulan PSL v2. * You may obtain a copy of Mulan PSL v2 at: @@ -15,36 +15,43 @@ use std::{fs::File, path::PathBuf, process::Command, str}; use anyhow::bail; use crate::{ - commands::DockerInfo, + commands::{DockerImgInfo, ImageType}, scripts_gen::*, utils::{self, set_permissions}, values::*, Config, CreateImage, }; -impl CreateImage for DockerInfo { - fn prepare(&self, _: &mut Config) -> anyhow::Result<()> { - let image_name = &self.docker_img; +impl CreateImage for DockerImgInfo { + fn prepare(&self, config: &mut Config) -> anyhow::Result<()> { + if self.legacy_bios && self.arch.as_deref() == Some("aarch64") { + bail!("aarch64 does not support legacy bios"); + } + if config.dm_verity.is_some() { + bail!("dm-verity is not supported for from_dockerimg"); + } + let image_name = &self.upgrade_img; verify_docker_input(&image_name)?; check_docker_image(&image_name)?; Ok(()) } fn generate_scripts(&self, config: &Config) -> anyhow::Result { - // kbimg.sh let kbimg_path = format!("{}/{}", SCRIPTS_DIR, KBIMG_SH); let mut kbimg = File::create(&kbimg_path)?; - gen_global_vars(&mut kbimg)?; - gen_docker_vars(&mut kbimg, &self.docker_img)?; + base_gen(&mut kbimg, GLOBAL_VARS, true)?; + gen_docker_vars(&mut kbimg, &self.upgrade_img)?; gen_global_func(&mut kbimg)?; + gen_mount_proc_dev_sys(&mut kbimg)?; + gen_unmount_dir(&mut kbimg)?; gen_create_os_tar_from_docker(&mut kbimg)?; - if self.image_type == "vm-docker" { - // kbimg.sh - gen_init_part(&mut kbimg)?; - gen_create_img(&mut kbimg, false, &config)?; + if self.image_type == Some(ImageType::VMDocker) { + write_bootloader(self.arch.as_deref().unwrap(), self.legacy_bios)?; + gen_init_partition(&mut kbimg)?; + gen_set_partuuid(&mut kbimg, self.legacy_bios, config.dm_verity.is_some())?; + gen_create_img(&mut kbimg, self.legacy_bios, &config)?; gen_create_vm_docker_img(&mut kbimg)?; } else { - // kbimg.sh gen_create_pxe_docker_img(&mut kbimg)?; } set_permissions(&kbimg_path, EXEC_PERMISSION)?; @@ -61,8 +68,10 @@ fn verify_docker_input(image_name: &str) -> anyhow::Result<()> { } fn check_docker_image(image_name: &str) -> anyhow::Result<()> { - let output = - Command::new("docker").args(&["images", "-q", image_name]).output().expect("Failed to execute command"); + let output = Command::new("docker") + .args(&["images", "-q", image_name]) + .output() + .expect("Failed to execute command: docker images -q {img_name}"); if output.status.success() { let stdout = str::from_utf8(&output.stdout).expect("Invalid UTF-8 output"); diff --git a/KubeOS-Rust/kbimg/src/main.rs b/KubeOS-Rust/kbimg/src/main.rs index beb389d5..3fc68cb1 100644 --- a/KubeOS-Rust/kbimg/src/main.rs +++ b/KubeOS-Rust/kbimg/src/main.rs @@ -1,5 +1,5 @@ /* - * Copyright (c) Huawei Technologies Co., Ltd. 2023. All rights reserved. + * Copyright (c) Huawei Technologies Co., Ltd. 2024. All rights reserved. * KubeOS is licensed under the Mulan PSL v2. * You can use this software according to the terms and conditions of the Mulan PSL v2. * You may obtain a copy of Mulan PSL v2 at: @@ -12,111 +12,126 @@ use std::{fs, path::PathBuf, process::exit}; -use anyhow::{bail, Result}; +use anyhow::Result; use clap::Parser; use env_logger::{Builder, Env, Target}; -use log::{debug, error, info}; +use log::{debug, error}; mod admin_container; mod commands; +mod custom; mod docker_img; mod repo; mod scripts_gen; mod utils; mod values; -use utils::{execute_scripts, get_arch}; -use values::SCRIPTS_DIR; +use utils::{check_config_toml, execute_scripts, get_arch}; +use values::{DIR_PERMISSION, SCRIPTS_DIR}; -use crate::commands::{Cli, Commands, Config}; +use crate::commands::{Cli, Config}; trait CreateImage { /// validate cmd args, check disk size and other prepare work fn prepare(&self, config: &mut Config) -> Result<()>; - /// generate scripts for creating image. If debug is enabled, keep the scripts, otherwise execute them + /// generate scripts for creating image. If debug is enabled, just generate the scripts without execution. fn generate_scripts(&self, config: &Config) -> Result; } -fn process(info: Box, mut config: Config) -> Result<()> { - match fs::create_dir_all(SCRIPTS_DIR) { - Ok(_) => { - info.prepare(&mut config)?; - let path = info.generate_scripts(&config)?; - execute_scripts(path)?; - Ok(()) - }, - Err(e) => bail!(e), +fn process(info: Box, mut config: Config, debug: bool) -> Result<()> { + let dir = PathBuf::from(SCRIPTS_DIR); + if dir.exists() { + debug!("Removing existing scripts directory"); + fs::remove_dir_all(&dir)?; + } + fs::create_dir_all(&dir)?; + utils::set_permissions(&dir, DIR_PERMISSION)?; + info.prepare(&mut config)?; + let path = info.generate_scripts(&config)?; + if !debug { + execute_scripts(path)?; + } else { + debug!("Executed following command to generate KubeOS image: bash {:?}", path); } + Ok(()) } fn main() { let cli = Cli::parse(); let default_log_level: &str = if cli.debug { "debug" } else { "info" }; Builder::from_env(Env::default().default_filter_or(default_log_level)).target(Target::Stdout).init(); - match cli.config { - Some(config) => { - info!("Loading config file"); - debug!("Config file path: {:?}", config); - let content = fs::read_to_string(config).unwrap(); - let data: Config = toml::from_str(&content).unwrap(); - debug!("Config: {:?}", data); - let info = if let Some(mut info) = data.from_repo.clone() { - info.arch = Some(get_arch()); - Some(Box::new(info) as Box) - } else if let Some(info) = data.from_dockerimg.clone() { - Some(Box::new(info) as Box) - } else if let Some(info) = data.admin_container.clone() { - Some(Box::new(info) as Box) + + let arch = get_arch().expect("Failed to get architecture"); + debug!("Architecture: {:?}", arch); + let (create_type, config) = match cli.commands { + commands::Commands::Create { image_type, file } => (image_type, file), + }; + debug!("Config file path: {:?}", config); + let content = fs::read_to_string(config).expect("Failed to read config file"); + let data: Config = toml::from_str(&content).expect("Failed to parse toml file"); + debug!("Config: {:?}", data); + + let info; + match create_type { + commands::CreateType::VM => { + check_config_toml(&data).unwrap(); + if let Some(mut i) = data.from_repo.clone() { + i.arch = Some(arch); + i.image_type = Some(commands::ImageType::VMRepo); + info = Some(Box::new(i) as Box) + } else if let Some(mut i) = data.from_dockerimg.clone() { + i.arch = Some(arch); + i.image_type = Some(commands::ImageType::VMDocker); + info = Some(Box::new(i) as Box) } else { - None - }; - if let Some(i) = info { - match process(i, data) { - Ok(_) => { - info!("Image created successfully"); - }, - Err(e) => { - error!("Failed to create image: {:?}", e); - }, - } + error!("Missing required fields in config file for creating vm image"); + exit(1); } - exit(0); }, - None => {}, - } - let info = match cli.commands { - Some(Commands::UpgradeImage(mut info)) => { - info.image_type = "upgrade".to_string(); - Some(Box::new(info) as Box) - }, - Some(Commands::VMRepo(mut info)) => { - info.image_type = "vm-repo".to_string(); - debug!("VMRepo: {:?}", info); - Some(Box::new(info) as Box) - }, - Some(Commands::VMDocker(mut info)) => { - info.image_type = "vm-docker".to_string(); - Some(Box::new(info) as Box) + commands::CreateType::PXE => { + check_config_toml(&data).unwrap(); + if let Some(mut i) = data.from_repo.clone() { + i.arch = Some(arch); + i.image_type = Some(commands::ImageType::PxeRepo); + info = Some(Box::new(i) as Box) + } else if let Some(mut i) = data.from_dockerimg.clone() { + i.arch = Some(arch); + i.image_type = Some(commands::ImageType::PxeDocker); + info = Some(Box::new(i) as Box) + } else { + error!("Missing required fields in config file for creating pxe image"); + exit(1); + } }, - Some(Commands::PxeRepo(mut info)) => { - info.image_type = "pxe-repo".to_string(); - Some(Box::new(info) as Box) + commands::CreateType::Upgrade => { + if let Some(mut i) = data.from_repo.clone() { + i.arch = Some(arch); + i.image_type = Some(commands::ImageType::UpgradeImage); + info = Some(Box::new(i) as Box) + } else { + error!("Missing from_repo in config file for creating upgrade image"); + exit(1); + } }, - Some(Commands::PxeDocker(mut info)) => { - info.image_type = "pxe-docker".to_string(); - Some(Box::new(info) as Box) + commands::CreateType::AdminContainer => { + if let Some(i) = data.admin_container.clone() { + info = Some(Box::new(i) as Box) + } else { + error!("Missing admin_container in config file for creating admin container image"); + exit(1); + } }, - Some(Commands::AdminContainer(info)) => Some(Box::new(info) as Box), - None => None, - }; + } + if let Some(i) = info { - match process(i, Config::default()) { + match process(i, data, cli.debug) { Ok(_) => { - info!("Image created successfully"); + println!("Image created successfully"); }, Err(e) => { error!("Failed to create image: {:?}", e); }, } } + exit(0); } diff --git a/KubeOS-Rust/kbimg/src/repo.rs b/KubeOS-Rust/kbimg/src/repo.rs index 43aabd3b..1a49645c 100644 --- a/KubeOS-Rust/kbimg/src/repo.rs +++ b/KubeOS-Rust/kbimg/src/repo.rs @@ -1,5 +1,5 @@ /* - * Copyright (c) Huawei Technologies Co., Ltd. 2023. All rights reserved. + * Copyright (c) Huawei Technologies Co., Ltd. 2024. All rights reserved. * KubeOS is licensed under the Mulan PSL v2. * You can use this software according to the terms and conditions of the Mulan PSL v2. * You may obtain a copy of Mulan PSL v2 at: @@ -12,192 +12,356 @@ use std::{ env, - fs::{create_dir_all, File}, + fs::{self, File}, path::PathBuf, }; -use anyhow::bail; -use sysinfo::{System, SystemExt, DiskExt}; +use anyhow::{anyhow, bail, Result}; +use fs2::{available_space, free_space, total_space}; +use log::{debug, warn}; use crate::{ - commands::RepoInfo, + commands::{DiskPartition, ImageType, PxeConfig, RepoInfo}, scripts_gen::*, - utils::{self, check_pxe_conf_valid, set_permissions}, + utils::{self, set_permissions}, values::*, Config, CreateImage, }; impl CreateImage for RepoInfo { - fn prepare(&self, config: &mut Config) -> anyhow::Result<()> { - verify_repo_input(&self)?; - check_disk_space(&self.image_type)?; + fn prepare(&self, config: &mut Config) -> Result<()> { + self.check_legacy_bios()?; + self.check_dm_verity(&config)?; + self.verify_repo_input()?; + check_disk_space(self.image_type.as_ref().unwrap(), &config.disk_partition)?; check_repo_file_valid(&self.repo_path)?; check_agent_file_valid(&self.agent_path)?; - if self.image_type == "pxe-repo" { - if let Some(pxe_config) = &config.pxe_config { - check_pxe_conf_valid(&pxe_config)?; - } else { - bail!("pxe config not found!") - } + if self.image_type == Some(ImageType::PxeRepo) { + self.check_pxe(config)?; + } + if self.image_type == Some(ImageType::UpgradeImage) { + self.check_upgrade_image(config)?; } Ok(()) } - fn generate_scripts(&self, config: &Config) -> anyhow::Result { - // rpmlist - let rpmlist_path = format!("{}/{}", SCRIPTS_DIR, RPMLIST); - let mut rpmlist = File::create(&rpmlist_path)?; - gen_rpm_list(&mut rpmlist, &self.rpmlist)?; - set_permissions(&rpmlist_path, CONFIG_PERMISSION)?; - // 00bootup - match create_dir_all(BOOTUP_DIR) { - Ok(_) => { - if let Some(pxe_config) = &config.pxe_config { - let global_cfg_path = format!("{}/{}", BOOTUP_DIR, BOOTUP_GLOBAL_CFG); - let mut global_cfg = File::create(&global_cfg_path)?; - gen_global_cfg(&mut global_cfg, &pxe_config)?; - set_permissions(&global_cfg_path, CONFIG_PERMISSION)?; - } - let module_setup_path = format!("{}/{}", BOOTUP_DIR, BOOTUP_MODULE_SETUP_SH); - let mut module_setup = File::create(&module_setup_path)?; - gen_module_setup(&mut module_setup)?; - set_permissions(&module_setup_path, EXEC_PERMISSION)?; - let mount_path = format!("{}/{}", BOOTUP_DIR, BOOTUP_MOUNT_SH); - let mut mount = File::create(&mount_path)?; - gen_mount(&mut mount)?; - set_permissions(&mount_path, EXEC_PERMISSION)?; - }, - Err(e) => { - bail!(e); - }, - } - // misc-files - match create_dir_all(MISC_FILES_DIR) { - Ok(_) => { - let boot_efi_mount_path = format!("{}/{}", MISC_FILES_DIR, MISC_BOOT_EFI_MOUNT); - let mut boot_efi_mount = File::create(&boot_efi_mount_path)?; - gen_boot_efi_mount(&mut boot_efi_mount)?; - set_permissions(&boot_efi_mount_path, CONFIG_PERMISSION)?; - let boot_grub2_mount_path = format!("{}/{}", MISC_FILES_DIR, MISC_BOOT_GRUB2_MOUNT); - let mut boot_grub2_mount = File::create(&boot_grub2_mount_path)?; - gen_boot_grub2_mount(&mut boot_grub2_mount)?; - set_permissions(&boot_grub2_mount_path, CONFIG_PERMISSION)?; - let etc_mount_path = format!("{}/{}", MISC_FILES_DIR, MISC_ETC_MOUNT); - let mut etc_mount = File::create(&etc_mount_path)?; - gen_etc_mount(&mut etc_mount)?; - set_permissions(&etc_mount_path, CONFIG_PERMISSION)?; - let os_agent_service_path = format!("{}/{}", MISC_FILES_DIR, MISC_OS_AGENT_SERVICE); - let mut os_agent_service = File::create(&os_agent_service_path)?; - gen_os_agent_service(&mut os_agent_service)?; - set_permissions(&os_agent_service_path, CONFIG_PERMISSION)?; - let os_release_path = format!("{}/{}", MISC_FILES_DIR, MISC_OS_RELEASE); - let mut os_release = File::create(&os_release_path)?; - gen_os_release(&mut os_release)?; - set_permissions(&os_release_path, CONFIG_PERMISSION)?; - let persist_mount_path = format!("{}/{}", MISC_FILES_DIR, MISC_PERSIST_MOUNT); - let mut persist_mount = File::create(&persist_mount_path)?; - gen_persist_mount(&mut persist_mount)?; - set_permissions(&persist_mount_path, CONFIG_PERMISSION)?; - let var_mount_path = format!("{}/{}", MISC_FILES_DIR, MISC_VAR_MOUNT); - let mut var_mount = File::create(&var_mount_path)?; - gen_var_mount(&mut var_mount)?; - set_permissions(&var_mount_path, CONFIG_PERMISSION)?; - }, - Err(e) => { - bail!(e); - }, - } - // grub.cfg - let grub_cfg_path = format!("{}/{}", SCRIPTS_DIR, GRUB_CFG); - let mut grub_cfg = File::create(&grub_cfg_path)?; - gen_grub_cfg(&mut grub_cfg)?; - set_permissions(&grub_cfg_path, CONFIG_PERMISSION)?; - // set_in_chroot.sh - let set_in_chroot_path = format!("{}/{}", SCRIPTS_DIR, SET_IN_CHROOT_SH); - let mut set_in_chroot = File::create(&set_in_chroot_path)?; - gen_set_in_chroot(&mut set_in_chroot, self.legacy_bios, &config)?; - set_permissions(&set_in_chroot_path, EXEC_PERMISSION)?; - // kbimg.sh + fn generate_scripts(&self, config: &Config) -> Result { + self.write_rpmlist(config)?; + self.write_misc_files()?; + self.write_grub_cfg(config.dm_verity.is_some())?; + self.write_set_in_chroot(config)?; + let kbimg_path = self.create_kbimg_script(config)?; + set_permissions(&kbimg_path, EXEC_PERMISSION)?; + Ok(kbimg_path) + } +} + +impl RepoInfo { + fn create_kbimg_script(&self, config: &Config) -> Result { let kbimg_path = format!("{}/{}", SCRIPTS_DIR, KBIMG_SH); let mut kbimg = File::create(&kbimg_path)?; - gen_global_vars(&mut kbimg)?; - gen_repo_vars(&mut kbimg, &self)?; + base_gen(&mut kbimg, GLOBAL_VARS, true)?; + gen_repo_vars(&mut kbimg, &self, &config.dm_verity, &config.grub)?; gen_global_func(&mut kbimg)?; gen_mount_proc_dev_sys(&mut kbimg)?; gen_unmount_dir(&mut kbimg)?; - gen_create_os_tar_from_repo(&mut kbimg, &self, &config)?; - if self.image_type == "vm-repo" { - // bootloader.sh - let bootloader_path = format!("{}/{}", SCRIPTS_DIR, BOOTLOADER_SH); - let mut bootloader = File::create(&bootloader_path)?; - gen_bootloader(&mut bootloader, self.arch.as_ref().unwrap(), self.legacy_bios)?; - set_permissions(&bootloader_path, EXEC_PERMISSION)?; - // kbimg.sh - gen_init_part(&mut kbimg)?; - gen_create_img(&mut kbimg, self.legacy_bios, &config)?; - gen_create_vm_repo_img(&mut kbimg)?; - } else if self.image_type == "pxe-repo" { - // kbimg.sh - gen_create_pxe_repo_img(&mut kbimg)?; + gen_create_os_tar_from_repo(&mut kbimg, &self, config)?; + self.generate_image_specific_scripts(&mut kbimg, config)?; + Ok(PathBuf::from(kbimg_path)) + } + + fn generate_image_specific_scripts(&self, kbimg: &mut File, config: &Config) -> Result<()> { + match self.image_type.as_ref().unwrap() { + ImageType::UpgradeImage => self.generate_upgrade_image_scripts(kbimg, config)?, + ImageType::VMRepo => self.generate_vm_repo_scripts(kbimg, config)?, + ImageType::PxeRepo => self.generate_pxe_repo_scripts(kbimg, config)?, + _ => bail!("Invalid image type: {:?}", self.image_type), + } + Ok(()) + } + + fn generate_upgrade_image_scripts(&self, kbimg: &mut File, config: &Config) -> Result<()> { + if config.dm_verity.is_none() { + self.write_upgrade_dockerfile()?; + gen_create_docker_img(kbimg)?; } else { - // Dockerfile - let dockerfile_path = format!("{}/{}", SCRIPTS_DIR, DOCKERFILE); - let mut dockerfile = File::create(&dockerfile_path)?; - gen_dockerfile(&mut dockerfile)?; - set_permissions(&dockerfile_path, CONFIG_PERMISSION)?; - // kbimg.sh - gen_create_docker_img(&mut kbimg)?; + self.generate_vm_repo_scripts(kbimg, config)?; + config.dm_verity.as_ref().unwrap().write_dm_verity_upgrade(kbimg)?; } - set_permissions(&kbimg_path, EXEC_PERMISSION)?; + Ok(()) + } - Ok(PathBuf::from(&format!("{}/{}", SCRIPTS_DIR, KBIMG_SH))) + fn generate_vm_repo_scripts(&self, kbimg: &mut File, config: &Config) -> Result<()> { + write_bootloader(self.arch.as_deref().unwrap(), self.legacy_bios)?; + gen_init_partition(kbimg)?; + gen_set_partuuid(kbimg, self.legacy_bios, config.dm_verity.is_some())?; + gen_create_img(kbimg, self.legacy_bios, config)?; + gen_create_vm_repo_img(kbimg)?; + if let Some(dmv) = config.dm_verity.as_ref() { + dmv.write_dm_verity_repo()?; + } + Ok(()) } -} -fn verify_repo_input(info: &RepoInfo) -> anyhow::Result<()> { - if !utils::is_valid_param(info.repo_path.to_str().unwrap()) { - bail!("params {} is invalid, please check input", info.repo_path.to_str().unwrap()); + fn generate_pxe_repo_scripts(&self, kbimg: &mut File, config: &Config) -> Result<()> { + gen_create_pxe_repo_img(kbimg)?; + self.write_bootup(config)?; + Ok(()) + } + + fn write_set_in_chroot(&self, config: &Config) -> Result<()> { + let set_in_chroot_path = format!("{}/{}", SCRIPTS_DIR, SET_IN_CHROOT_SH); + let mut set_in_chroot = File::create(&set_in_chroot_path)?; + gen_set_in_chroot( + &mut set_in_chroot, + self.legacy_bios, + self.arch.as_deref().unwrap_or(""), + self.image_type.as_ref().unwrap(), + config, + )?; + set_permissions(&set_in_chroot_path, EXEC_PERMISSION)?; + Ok(()) + } + + fn write_bootup(&self, config: &Config) -> Result<()> { + fs::create_dir_all(BOOTUP_DIR)?; + utils::set_permissions(BOOTUP_DIR, DIR_PERMISSION)?; + let mount_path = format!("{}/{}", BOOTUP_DIR, BOOTUP_MOUNT_SH); + let mut mount = File::create(&mount_path)?; + if let Some(pxe_config) = &config.pxe_config { + gen_global_cfg(&mut mount, &pxe_config)?; + } + gen_mount(&mut mount, config)?; + set_permissions(&mount_path, EXEC_PERMISSION)?; + + let module_setup_path = format!("{}/{}", BOOTUP_DIR, BOOTUP_MODULE_SETUP_SH); + let mut module_setup = File::create(&module_setup_path)?; + base_gen(&mut module_setup, MODULE_SETUP, true)?; + set_permissions(&module_setup_path, EXEC_PERMISSION)?; + Ok(()) + } + + fn write_rpmlist(&self, config: &Config) -> Result<()> { + let rpmlist_path = format!("{}/{}", SCRIPTS_DIR, RPMLIST); + let mut rpmlist = File::create(&rpmlist_path)?; + gen_rpm_list( + &mut rpmlist, + &self.rpmlist, + self.arch.as_deref().unwrap(), + self.legacy_bios, + config.dm_verity.is_some(), + )?; + set_permissions(&rpmlist_path, CONFIG_PERMISSION)?; + Ok(()) + } + + fn write_misc_files(&self) -> Result<()> { + fs::create_dir_all(MISC_FILES_DIR)?; + utils::set_permissions(MISC_FILES_DIR, DIR_PERMISSION)?; + + if self.legacy_bios { + let boot_grub2_mount_path = format!("{}/{}", MISC_FILES_DIR, MISC_BOOT_GRUB2_MOUNT); + let mut boot_grub2_mount = File::create(&boot_grub2_mount_path)?; + base_gen(&mut boot_grub2_mount, BOOT_GRUB2_MOUNT, false)?; + set_permissions(&boot_grub2_mount_path, CONFIG_PERMISSION)?; + } else { + let boot_efi_mount_path = format!("{}/{}", MISC_FILES_DIR, MISC_BOOT_EFI_MOUNT); + let mut boot_efi_mount = File::create(&boot_efi_mount_path)?; + base_gen(&mut boot_efi_mount, BOOT_EFI_MOUNT, false)?; + set_permissions(&boot_efi_mount_path, CONFIG_PERMISSION)?; + } + + let etc_mount_path = format!("{}/{}", MISC_FILES_DIR, MISC_ETC_MOUNT); + let mut etc_mount = File::create(&etc_mount_path)?; + base_gen(&mut etc_mount, ETC_MOUNT, false)?; + set_permissions(&etc_mount_path, CONFIG_PERMISSION)?; + + let opt_cni_mount_path = format!("{}/{}", MISC_FILES_DIR, MISC_OPT_CNI_MOUNT); + let mut opt_cni_mount = File::create(&opt_cni_mount_path)?; + base_gen(&mut opt_cni_mount, OPT_CNI_MOUNT, false)?; + set_permissions(&opt_cni_mount_path, CONFIG_PERMISSION)?; + + let os_agent_service_path = format!("{}/{}", MISC_FILES_DIR, MISC_OS_AGENT_SERVICE); + let mut os_agent_service = File::create(&os_agent_service_path)?; + base_gen(&mut os_agent_service, OS_AGENT_SERVICE, false)?; + set_permissions(&os_agent_service_path, CONFIG_PERMISSION)?; + + let os_release_path = format!("{}/{}", MISC_FILES_DIR, MISC_OS_RELEASE); + let mut os_release = File::create(&os_release_path)?; + gen_os_release(&mut os_release)?; + set_permissions(&os_release_path, CONFIG_PERMISSION)?; + + let persist_mount_path = format!("{}/{}", MISC_FILES_DIR, MISC_PERSIST_MOUNT); + let mut persist_mount = File::create(&persist_mount_path)?; + base_gen(&mut persist_mount, PERSIST_MOUNT, false)?; + set_permissions(&persist_mount_path, CONFIG_PERMISSION)?; + + let var_mount_path = format!("{}/{}", MISC_FILES_DIR, MISC_VAR_MOUNT); + let mut var_mount = File::create(&var_mount_path)?; + base_gen(&mut var_mount, VAR_MOUNT, false)?; + set_permissions(&var_mount_path, CONFIG_PERMISSION)?; + Ok(()) + } + + fn write_grub_cfg(&self, dmv: bool) -> Result<()> { + let grub_cfg_path = format!("{}/{}", SCRIPTS_DIR, GRUB_CFG); + let mut grub_cfg = File::create(&grub_cfg_path)?; + if dmv { + base_gen(&mut grub_cfg, DMV_MAIN_GRUB_CFG, false)?; + } else { + base_gen(&mut grub_cfg, GRUB_CFG_CONTENTS, false)?; + } + set_permissions(&grub_cfg_path, CONFIG_PERMISSION)?; + Ok(()) } - if !utils::is_valid_param(&info.version) { - bail!("params {} is invalid, please check input", info.version); + + fn write_upgrade_dockerfile(&self) -> Result<()> { + let dockerfile_path = format!("{}/{}", SCRIPTS_DIR, DOCKERFILE); + let mut dockerfile = File::create(&dockerfile_path)?; + base_gen(&mut dockerfile, OS_TAR_DOCKERFILE, false)?; + set_permissions(&dockerfile_path, CONFIG_PERMISSION)?; + Ok(()) + } + + fn check_legacy_bios(&self) -> Result<()> { + if self.legacy_bios && self.arch.as_deref() == Some("aarch64") { + bail!("aarch64 does not support legacy bios"); + } + Ok(()) } - if !utils::is_valid_param(info.agent_path.to_str().unwrap()) { - bail!("params {} is invalid, please check input", info.agent_path.to_str().unwrap()); + + fn check_dm_verity(&self, config: &Config) -> Result<()> { + if config.dm_verity.is_some() { + if self.legacy_bios { + bail!("dm_verity does not support legacy bios"); + } + if config.grub.is_none() { + bail!("grub is required for dm_verity"); + } + if self.image_type != Some(ImageType::VMRepo) && self.image_type != Some(ImageType::UpgradeImage) { + bail!("dm_verity only supports VMRepo and UpgradeImage mode"); + } + } + Ok(()) } - if let Some(docker_img) = &info.docker_img { - if !utils::is_valid_param(docker_img) { - bail!("params {} is invalid, please check input", docker_img); + + fn verify_repo_input(&self) -> Result<()> { + if !utils::is_valid_param(self.repo_path.to_str().unwrap()) { + bail!("params {} is invalid, please check input", self.repo_path.to_str().unwrap()); + } + if !utils::is_valid_param(&self.version) { + bail!("params {} is invalid, please check input", self.version); + } + if !utils::is_valid_param(self.agent_path.to_str().unwrap()) { + bail!("params {} is invalid, please check input", self.agent_path.to_str().unwrap()); } + if let Some(docker_img) = &self.upgrade_img { + if !utils::is_valid_param(docker_img) { + bail!("params {} is invalid, please check input", docker_img); + } + } + Ok(()) + } + + fn check_upgrade_image(&self, config: &Config) -> Result<()> { + if self.upgrade_img.is_none() { + bail!("upgrade_img field is required for generating upgrade-img"); + } + if config.pxe_config.is_some() && config.dm_verity.is_some() { + bail!("dm_verity does NOT support PXE upgrade-img"); + } + Ok(()) + } + + fn check_pxe(&self, config: &Config) -> Result<()> { + if self.legacy_bios { + warn!("KubeOS PXE image does NOT support legacy bios for x86_64 or aarch64"); + } + let pxe_config = + config.pxe_config.as_ref().ok_or_else(|| anyhow!("pxe_config is required for building pxe image"))?; + check_pxe_conf_valid(pxe_config)?; + Ok(()) + } +} + +// Check pxe config +fn check_pxe_conf_valid(config: &PxeConfig) -> anyhow::Result<()> { + if config.dhcp.unwrap_or(false) { + if config.local_ip.is_some() || config.net_name.is_some() { + bail!("dhcp and local_ip/net_name cannot be set at the same time"); + } + } else { + let local_ip = config.local_ip.as_ref().ok_or_else(|| anyhow!("local_ip not found!"))?; + if !utils::is_addr_valid(local_ip) { + bail!("address {} is invalid, please check input", local_ip) + } + let netmask = config.netmask.as_ref().ok_or_else(|| anyhow!("netmask not found!"))?; + if !utils::is_addr_valid(netmask) { + bail!("address {} is invalid, please check input", netmask) + } + } + if !utils::is_addr_valid(&config.server_ip) { + bail!("address {} is invalid, please check input", &config.server_ip) + } + if !utils::is_addr_valid(&config.route_ip) { + bail!("address {} is invalid, please check input", &config.route_ip) } Ok(()) } -fn check_disk_space(image_type: &str) -> anyhow::Result<()> { +fn check_disk_space(image_type: &ImageType, disk: &Option) -> Result<()> { let max_size: u64 = match image_type { - "upgrade" => 6, - "vm-repo" => 25, - "pxe-repo" => 5, - _ => bail!("Invalid image type: {}", image_type), + ImageType::UpgradeImage => 6, + ImageType::VMRepo => disk.as_ref().and_then(|d| d.img_size).unwrap_or(20) as u64 + 5, + ImageType::PxeRepo => 5, + _ => bail!("Invalid image type: {:?}", image_type), }; let current_dir = env::current_dir().expect("Failed to get current directory"); - let root_dir = current_dir.ancestors().last().expect("Failed to get current directory").to_path_buf(); - let mut sys = System::new_all(); - sys.refresh_all(); - for d in sys.disks() { - if d.mount_point() == root_dir { - if d.available_space() < max_size * 1024 * 1024 { - bail!("The available disk space is not enough, at least {}GiB.", max_size); - } - } + debug!("Current Directory: {}", current_dir.display()); + let total_space = total_space(¤t_dir).expect("Failed to get total space"); + let available_space = available_space(¤t_dir).expect("Failed to get available space"); + let free_space = free_space(¤t_dir).expect("Failed to get free space"); + debug!("Total space: {} bytes", total_space); + debug!("Available space: {} bytes", available_space); + debug!("Free space: {} bytes", free_space); + + if available_space < max_size * 1024 * 1024 * 1024 { + bail!( + "Not enough space to create image, available space: {} GiB, required space: {} GiB", + available_space / 1024 / 1024 / 1024, + max_size + ); } + Ok(()) } -fn check_repo_file_valid(repo_path: &PathBuf) -> anyhow::Result<()> { +fn check_repo_file_valid(repo_path: &PathBuf) -> Result<()> { utils::is_file_valid("REPO file", repo_path) } -fn check_agent_file_valid(agent_path: &PathBuf) -> anyhow::Result<()> { +fn check_agent_file_valid(agent_path: &PathBuf) -> Result<()> { utils::is_file_valid("os-agent binary", agent_path) } + +#[cfg(test)] +mod tests { + use super::*; + + fn init() { + let _ = env_logger::builder() + .target(env_logger::Target::Stdout) + .filter_level(log::LevelFilter::Trace) + .is_test(true) + .try_init(); + } + + #[test] + fn test_check_disk_space_vm_repo() { + init(); + let image_type = "vm-repo".into(); + let result = check_disk_space(&image_type, &None); + assert!(result.is_ok()); + } +} diff --git a/KubeOS-Rust/kbimg/src/scripts_gen.rs b/KubeOS-Rust/kbimg/src/scripts_gen.rs index 7d574888..4f9abd24 100644 --- a/KubeOS-Rust/kbimg/src/scripts_gen.rs +++ b/KubeOS-Rust/kbimg/src/scripts_gen.rs @@ -1,5 +1,5 @@ /* - * Copyright (c) Huawei Technologies Co., Ltd. 2023. All rights reserved. + * Copyright (c) Huawei Technologies Co., Ltd. 2024. All rights reserved. * KubeOS is licensed under the Mulan PSL v2. * You can use this software according to the terms and conditions of the Mulan PSL v2. * You may obtain a copy of Mulan PSL v2 at: @@ -10,763 +10,509 @@ * See the Mulan PSL v2 for more details. */ -use std::{fs::File, io::Write, path::PathBuf}; +use std::{collections::HashMap, io::Write, path::PathBuf}; -use anyhow::{bail, Ok, Result}; +use anyhow::{anyhow, bail, Result}; +use strfmt::strfmt; -use crate::{commands::*, values::SCRIPTS_DIR}; +use crate::{commands::*, utils, values::*}; + +pub fn base_gen(file: &mut dyn Write, content: &str, sh: bool) -> Result<()> { + if sh { + writeln!(file, "#!/bin/bash")?; + } + gen_copyright(file)?; + writeln!(file, "{}", content)?; + Ok(()) +} /* copyright */ -pub(crate) fn gen_copyright(file: &mut File) -> Result<()> { - writeln!( - file, - r#"## Copyright (c) Huawei Technologies Co., Ltd. 2022. All rights reserved. -# KubeOS is licensed under the Mulan PSL v2. -# You can use this software according to the terms and conditions of the Mulan PSL v2. -# You may obtain a copy of Mulan PSL v2 at: -# http://license.coscl.org.cn/MulanPSL2 -# THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY KIND, EITHER EXPRESS OR -# IMPLIED, INCLUDING BUT NOT LIMITED TO NON-INFRINGEMENT, MERCHANTABILITY OR FIT FOR A PARTICULAR -# PURPOSE. -## See the Mulan PSL v2 for more details. -"# - )?; +pub(crate) fn gen_copyright(file: &mut dyn Write) -> Result<()> { + writeln!(file, "{COPYRIGHT}")?; Ok(()) } /* region: kbimg.sh */ -pub(crate) fn gen_global_vars(file: &mut File) -> Result<()> { - writeln!(file, "#!/bin/bash")?; - gen_copyright(file)?; +pub(crate) fn gen_test_lock(file: &mut dyn Write) -> Result<()> { + writeln!(file, "{TEST_LOCK}")?; + Ok(()) +} - writeln!( - file, - r#"set -e +pub(crate) fn gen_cleanup(file: &mut dyn Write) -> Result<()> { + writeln!(file, "{CLEANUP}")?; + Ok(()) +} -SCRIPTS_DIR=$(cd "$(dirname "$0")" && pwd) -LOCK="${{SCRIPTS_DIR}}"/test.lock -RPM_ROOT="${{SCRIPTS_DIR}}"/rootfs -TMP_MOUNT_PATH="${{SCRIPTS_DIR}}"/mnt -"# - )?; +pub(crate) fn gen_global_func(file: &mut dyn Write) -> Result<()> { + writeln!(file, "{DELETE_DIR}")?; + writeln!(file, "{DELETE_FILE}")?; + writeln!(file, "{LOG}")?; + gen_test_lock(file)?; + gen_cleanup(file)?; Ok(()) } -pub(crate) fn gen_global_func(file: &mut File) -> Result<()> { - writeln!( - file, - r#"function delete_dir() {{ - local ret=0 - local dir="$1" - unmount_dir "${{dir}}" - ret=$? - if [ "${{ret}}" -eq 0 ]; then - rm -rf "${{dir}}" - return 0 - else - log_error_print "${{dir}} is failed to unmount , can not delete ${{dir}}." - return 1 - fi -}} - -function delete_file() {{ - local file="$1" - if [ ! -e "${{file}}" ]; then - return 0 - fi - - if [ ! -f "${{file}}" ]; then - log_error_print "${{file}} is not a file." - return 1 - fi - - rm -f "${{file}}" - return 0 -}} - -function clean_space() {{ - delete_dir "${{RPM_ROOT}}" - delete_dir "${{TMP_MOUNT_PATH}}" - delete_file "${{SCRIPTS_DIR}}"/os.tar - rm -rf "${{LOCK}}" - delete_file "${{ADMIN_CONTAINER_DIR}}"/hostshell -}} - -function clean_img() {{ - delete_file "${{SCRIPTS_DIR}}"/system.img - delete_file "${{SCRIPTS_DIR}}"/update.img - delete_file "${{SCRIPTS_DIR}}"/initramfs.img - delete_file "${{SCRIPTS_DIR}}"/kubeos.tar -}} - -function file_lock() {{ - local lock_file=$1 - exec {{lock_fd}}>"${{lock_file}}" - flock -xn "${{lock_fd}}" -}} - -function test_lock() {{ - file_lock "${{LOCK}}" - local status=$? - if [ $status -ne 0 ]; then - log_error_print "There is already an generate process running." - exit 203 - fi -}} - -function log_error_print() {{ - local logmsg - logmsg="[ ERROR ] - ""$(date "+%b %d %Y %H:%M:%S")"" $1" - echo "$logmsg" -}} - -function log_info_print() {{ - local logmsg - logmsg="[ INFO ] - ""$(date "+%b %d %Y %H:%M:%S")"" $1" - echo "$logmsg" -}} -"# - )?; +pub(crate) fn gen_mount_proc_dev_sys(file: &mut dyn Write) -> Result<()> { + writeln!(file, "{MOUNT_PROC_DEV_SYS}")?; Ok(()) } -pub(crate) fn gen_mount_proc_dev_sys(file: &mut File) -> Result<()> { - writeln!( - file, - r#"function mount_proc_dev_sys() {{ - local tmp_root=$1 - mount -t proc none "${{tmp_root}}"/proc - mount --bind /dev "${{tmp_root}}"/dev - mount --bind /dev/pts "${{tmp_root}}"/dev/pts - mount -t sysfs none "${{tmp_root}}"/sys -}} -"# - )?; +pub(crate) fn gen_unmount_dir(file: &mut dyn Write) -> Result<()> { + writeln!(file, "{UNMOUNT_DIR}")?; Ok(()) } -pub(crate) fn gen_unmount_dir(file: &mut File) -> Result<()> { - writeln!( - file, - r#"function unmount_dir() {{ - local dir=$1 - - if [ -L "${{dir}}" ] || [ -f "${{dir}}" ]; then - log_error_print "${{dir}} is not a directory, please check it." - return 1 - fi - - if [ ! -d "${{dir}}" ]; then - return 0 - fi - - local real_dir - real_dir=$(readlink -e "${{dir}}") - local mnts - mnts=$(awk '{{print $2}}' < /proc/mounts | grep "^${{real_dir}}" | sort -r) - for m in ${{mnts}}; do - log_info_print "Unmount ${{m}}" - umount -f "${{m}}" || true - done - - return 0 -}} -"# - )?; +pub(crate) fn gen_init_partition(file: &mut dyn Write) -> Result<()> { + writeln!(file, "{INIT_PARTITION}")?; Ok(()) } -pub(crate) fn gen_init_part(file: &mut File) -> Result<()> { - writeln!( - file, - r#"function init_part() {{ - local offset - offset=$(fdisk -l "${{SCRIPTS_DIR}}"/system.img | grep "$1" | awk '{{print $2}}') - local sizelimit - sizelimit=$(fdisk -l "${{SCRIPTS_DIR}}"/system.img | grep "$1" | awk '{{print $3}}') - sizelimit=$(echo "($sizelimit - $offset)*512" | bc) - offset=$(echo "${{offset}}*512" | bc) - local loop - loop=$(losetup -f) - losetup -o "${{offset}}" --sizelimit "${{sizelimit}}" "${{loop}}" "${{SCRIPTS_DIR}}"/system.img - if [ "$2" == "BOOT" ];then - mkfs.vfat -n "$2" "${{loop}}" - mount -t vfat "${{loop}}" "$3" - else - mkfs.ext4 -L "$2" "${{loop}}" - mount -t ext4 "${{loop}}" "$3" - rm -rf "$3/lost+found" - fi -}} -"# - )?; +pub(crate) fn write_bootloader(arch: &str, legacy_bios: bool) -> Result<()> { + let bootloader_path = format!("{}/{}", SCRIPTS_DIR, BOOTLOADER_SH); + let mut bootloader = std::fs::File::create(&bootloader_path)?; + gen_bootloader(&mut bootloader, arch, legacy_bios)?; + utils::set_permissions(&bootloader_path, EXEC_PERMISSION)?; Ok(()) } // repo -pub(crate) fn gen_repo_vars(file: &mut File, info: &RepoInfo) -> Result<()> { +pub(crate) fn gen_repo_vars( + file: &mut dyn Write, + info: &RepoInfo, + dm_verity: &Option, + grub: &Option, +) -> Result<()> { writeln!( file, r#"REPO_PATH="{}" VERSION="{}" AGENT_PATH="{}" +# shellcheck disable=SC2016 ROOT_PASSWD='{}' -BOOT_MODE="{}" "#, info.repo_path.to_str().unwrap(), &info.version, info.agent_path.to_str().unwrap(), &info.root_passwd, - if info.legacy_bios { "legacy" } else { "efi" } )?; - if let Some(docker_img) = &info.docker_img { - writeln!(file, "DOCKER_IMG=\"{}\"\n", docker_img)?; + if info.image_type == Some(ImageType::UpgradeImage) { + writeln!(file, "DOCKER_IMG=\"{}\"\n", info.upgrade_img.as_ref().unwrap())?; + } + if let Some(dm_verity) = dm_verity { + writeln!( + file, + r#"RSApw='{}' +GPGpw='{}' +GRUBpw='{}' +source "${{SCRIPTS_DIR}}"/dm-verity/dm_verity.sh &>/dev/null +"#, + dm_verity.efi_key, + dm_verity.grub_key, + grub.as_ref().unwrap().passwd, + )?; } Ok(()) } -pub(crate) fn gen_prepare_yum(file: &mut File) -> Result<()> { - writeln!( - file, - r#"function prepare_yum() {{ - # init rpmdb - rpm --root "${{RPM_ROOT}}" --initdb - mkdir -p "${{RPM_ROOT}}"{{/etc/yum.repos.d,/persist,/proc,/dev/pts,/sys}} - mount_proc_dev_sys "${{RPM_ROOT}}" - # init yum repo - local iso_repo="${{RPM_ROOT}}"/etc/yum.repos.d/iso.repo - cat "${{REPO_PATH}}" > "$iso_repo" -}} -"# - )?; +pub(crate) fn gen_prepare_yum(file: &mut dyn Write) -> Result<()> { + writeln!(file, "{PREPARE_YUM}")?; Ok(()) } -pub(crate) fn gen_install_packages(file: &mut File, arch: &str, legacy_bios: bool) -> Result<()> { - writeln!( - file, - r#"function install_packages() {{ - prepare_yum "${{REPO_PATH}}" - - echo "install package.." - - local filesize - filesize=$(stat -c "%s" "${{SCRIPTS_DIR}}"/rpmlist) - local maxsize=$((1024*1024)) - if [ "${{filesize}}" -gt "${{maxsize}}" ]; then - echo "please check if rpmlist is too big or something wrong" - exit 7 - fi - - local rpms_name - rpms_name=$(tr "\n" " " < "${{SCRIPTS_DIR}}"/rpmlist) - old_ifs="$IFS" - IFS=' '"# - )?; - - if arch == "x86_64" { - if legacy_bios { - writeln!(file, "\trpms_name+=\" grub2\"")?; - } else { - writeln!(file, "\trpms_name+=\" grub2-efi grub2-tools grub2-efi-x64-modules grub2-pc-modules\"")?; - } - writeln!( - file, - r#" read -ra rpms <<< "${{rpms_name}}" - IFS="$old_ifs" - yum -y --installroot="${{RPM_ROOT}}" install --nogpgcheck --setopt install_weak_deps=False "${{rpms[@]}}""# - )?; - } else if arch == "aarch64" { - writeln!( - file, - r#" read -ra rpms <<< "${{rpms_name}}" - IFS="$old_ifs" - yum -y --installroot="${{RPM_ROOT}}" install --nogpgcheck --setopt install_weak_deps=False "${{rpms[@]}}" grub2-efi grub2-tools grub2-efi-aa64-modules"# - )?; - } - writeln!( - file, - r#" yum -y --installroot="${{RPM_ROOT}}" clean all -}} -"# - )?; +pub(crate) fn gen_install_packages(file: &mut dyn Write) -> Result<()> { + writeln!(file, "{INSTALL_PACKAGES}")?; Ok(()) } -pub(crate) fn gen_copy_files(file: &mut File, copy_files: &Vec) -> Result<()> { +pub(crate) fn gen_copy_files(file: &mut dyn Write, copy_files: &Vec) -> Result<()> { writeln!(file, "function copy_files() {{")?; for copy_file in copy_files { - let dst = format!("{}/rootfs{}", SCRIPTS_DIR, ©_file.dst); - let dst = PathBuf::from(dst); - if !dst.exists() { - writeln!(file, "\tmkdir -p \"${{RPM_ROOT}}{}\"", ©_file.dst)?; - } - let src = PathBuf::from(©_file.src); - if src.is_dir() { - writeln!(file, "\tcp -r {} \"${{RPM_ROOT}}{}\"", ©_file.src, ©_file.dst)?; - } else { - writeln!(file, "\tcp {} \"${{RPM_ROOT}}{}\"", ©_file.src, ©_file.dst)?; - } + copy_file.gen_copy_files(file)?; } writeln!(file, "}}\n")?; Ok(()) } -pub(crate) fn gen_grub_config(file: &mut File, legacy_bios: bool, grub: &Grub) -> Result<()> { - writeln!( - file, - r#"function grub_config() {{ - local GRUB_PATH"# - )?; - if legacy_bios { - writeln!(file, "\tGRUB_PATH=\"${{RPM_ROOT}}\"/boot/grub2")?; - } else { - writeln!(file, "\tGRUB_PATH=\"${{RPM_ROOT}}\"/boot/efi/EFI/openEuler")?; - } - if let Some(grub_passwd) = &grub.passwd { - writeln!( - file, - r#" local GRUB_PASSWD - GRUB_PASSWD=$(echo -e "{}\n{}" | grub2-mkpasswd-pbkdf2 | grep PBKDF2 | awk '{{print $7}}') - echo "GRUB2_PASSWD=${{GRUB_PASSWD}}" > "${{GRUB_PATH}}"/user.cfg - chmod 600 "${{GRUB_PATH}}"/user.cfg -}} -"#, - grub_passwd, grub_passwd - )?; - } +pub(crate) fn gen_grub_config(file: &mut dyn Write, legacy_bios: bool, arch: &str, grub: &Grub) -> Result<()> { + grub.gen_grub_config(file, legacy_bios, arch)?; Ok(()) } -pub(crate) fn gen_chroot_script(file: &mut File, chroot_script: &ChrootScript) -> Result<()> { - let script_path = PathBuf::from(&chroot_script.path); - match script_path.canonicalize() { - core::result::Result::Ok(absolute_path) => { - if let Some(script_name) = absolute_path.file_name() { - writeln!( - file, - r#"function chroot_script() {{ - cp "{}" "${{RPM_ROOT}}" - chroot "${{RPM_ROOT}}" bash /{} -}} -"#, - absolute_path.as_path().to_str().unwrap(), - script_name.to_str().unwrap() - )?; - } - Ok(()) - }, - Err(e) => bail!(e), - } +pub(crate) fn gen_chroot_script(file: &mut dyn Write, chroot_script: &ChrootScript) -> Result<()> { + chroot_script.gen_chroot_script(file)?; + Ok(()) } -pub(crate) fn gen_install_misc(file: &mut File, legacy_bios: bool, config: &Config) -> Result<()> { +pub(crate) fn gen_install_misc( + file: &mut dyn Write, + legacy_bios: bool, + arch: &str, + image_type: &ImageType, + config: &Config, +) -> Result<()> { if let Some(copy_files) = &config.copy_files { gen_copy_files(file, ©_files)?; } if let Some(grub) = &config.grub { - gen_grub_config(file, legacy_bios, &grub)?; + if config.dm_verity.is_none() { + gen_grub_config(file, legacy_bios, arch, &grub)?; + } } if let Some(chroot_script) = &config.chroot_script { gen_chroot_script(file, &chroot_script)?; } - writeln!( - file, - r#"function install_misc() {{ - cp "${{SCRIPTS_DIR}}"/misc-files/*mount "${{SCRIPTS_DIR}}"/misc-files/os-agent.service "${{RPM_ROOT}}"/usr/lib/systemd/system/ - cp "${{SCRIPTS_DIR}}"/misc-files/os-release "${{RPM_ROOT}}"/usr/lib/ - cp "${{AGENT_PATH}}" "${{RPM_ROOT}}"/usr/bin - rm "${{RPM_ROOT}}"/etc/os-release - - cat < "${{RPM_ROOT}}"/usr/lib/os-release -NAME=${{NAME}} -ID=${{NAME}} -EOF - echo "PRETTY_NAME=\"${{NAME}} ${{VERSION}}\"" >> "${{RPM_ROOT}}"/usr/lib/os-release - echo "VERSION_ID=${{VERSION}}" >> "${{RPM_ROOT}}"/usr/lib/os-release - mv "${{RPM_ROOT}}"/boot/vmlinuz* "${{RPM_ROOT}}"/boot/vmlinuz - mv "${{RPM_ROOT}}"/boot/initramfs* "${{RPM_ROOT}}"/boot/initramfs.img"# - )?; - - if legacy_bios { - writeln!( - file, - r#" cp "${{SCRIPTS_DIR}}"/grub.cfg "${{RPM_ROOT}}"/boot/grub2 + let mut vars = HashMap::new(); + if legacy_bios && arch == "x86_64" { + vars.insert( + "COPY_GRUB_CFG".to_string(), + r#"cp "${SCRIPTS_DIR}"/grub.cfg "${RPM_ROOT}"/boot/grub2 sed -i "s/insmod part_gpt/insmod part_msdos/g; \ -s/set root='hd0,gpt2'/set root='hd0,msdos2'/g; \ -s/set root='hd0,gpt3'/set root='hd0,msdos3'/g" \ -"${{RPM_ROOT}}"/boot/grub2/grub.cfg"# - )?; + s/set root='hd0,gpt2'/set root='hd0,msdos2'/g; \ + s/set root='hd0,gpt3'/set root='hd0,msdos3'/g" \ + "${RPM_ROOT}"/boot/grub2/grub.cfg"# + .to_string(), + ); } else { - writeln!(file, "\tcp \"${{SCRIPTS_DIR}}\"/grub.cfg \"${{RPM_ROOT}}\"/boot/efi/EFI/openEuler")?; + vars.insert( + "COPY_GRUB_CFG".to_string(), + r#"cp "${SCRIPTS_DIR}"/grub.cfg "${RPM_ROOT}"/boot/efi/EFI/openEuler"#.to_string(), + ); } - writeln!( - file, - r#" cp -r "${{SCRIPTS_DIR}}"/00bootup "${{RPM_ROOT}}"/usr/lib/dracut/modules.d/ - cp "${{SCRIPTS_DIR}}"/set_in_chroot.sh "${{RPM_ROOT}}" - - # (optional) custom config"# - )?; + let mut pxe_bootup_files = String::new(); + if image_type == &ImageType::PxeRepo { + pxe_bootup_files.push_str(r#"cp -r "${SCRIPTS_DIR}"/00bootup "${RPM_ROOT}"/usr/lib/dracut/modules.d/"#); + } + vars.insert("PXE_BOOTUP_FILES".to_string(), pxe_bootup_files); + + let mut dm_verity_files = String::new(); + if config.dm_verity.is_some() { + dm_verity_files.push_str( + format!( + "cp -r \"${{SCRIPTS_DIR}}\"/05dmverity \"${{RPM_ROOT}}\"/usr/lib/dracut/modules.d/\n cp \"${{SCRIPTS_DIR}}\"/dm-verity/{} \"${{RPM_ROOT}}\"/usr/bin", + DMV_UPGRADE_ROLLBACK + ) + .as_str(), + ); + } + vars.insert("DM_VERITY_FILES".to_string(), dm_verity_files); + let mut custom_script = String::new(); if let Some(_) = &config.copy_files { - writeln!(file, "\tcopy_files")?; + custom_script.push_str(" copy_files\n"); } if let Some(_) = &config.grub { - writeln!(file, "\tgrub_config")?; + if config.dm_verity.is_none() { + custom_script.push_str(" grub_config\n"); + } } if let Some(_) = &config.chroot_script { - writeln!(file, "\tchroot_script")?; + custom_script.push_str(" chroot_script\n"); } + vars.insert("CUSTOM_SCRIPT".to_string(), custom_script); - writeln!( - file, - r#" - ROOT_PASSWD="${{ROOT_PASSWD}}" BOOT_MODE="${{BOOT_MODE}}" chroot "${{RPM_ROOT}}" bash /set_in_chroot.sh - rm "${{RPM_ROOT}}/set_in_chroot.sh" -}} -"# - )?; + let dynamic_script = strfmt(INSTALL_MISC, &vars)?; + writeln!(file, "{dynamic_script}")?; Ok(()) } -pub(crate) fn gen_create_os_tar_from_repo(file: &mut File, info: &RepoInfo, config: &Config) -> Result<()> { +pub(crate) fn gen_create_os_tar_from_repo(file: &mut dyn Write, info: &RepoInfo, config: &Config) -> Result<()> { + let arch = info.arch.clone().ok_or_else(|| anyhow!("arch is None"))?; gen_prepare_yum(file)?; - gen_install_packages(file, info.arch.as_ref().unwrap(), info.legacy_bios)?; - gen_install_misc(file, info.legacy_bios, config)?; + gen_install_packages(file)?; + gen_install_misc(file, info.legacy_bios, &arch, &info.image_type.clone().unwrap(), config)?; - writeln!( - file, - r#"function create_os_tar_from_repo() {{ - install_packages - install_misc - unmount_dir "${{RPM_ROOT}}" - tar -C "${{RPM_ROOT}}" -cf "${{SCRIPTS_DIR}}"/os.tar . -}} -"# - )?; + writeln!(file, "{CREATE_OS_TAR_FROM_REPO}")?; Ok(()) } -pub(crate) fn gen_create_img(file: &mut File, legacy_bios: bool, config: &Config) -> Result<()> { - let (first, second, third, img_size) = if let Some(disk_partition) = &config.disk_partition { - let first = disk_partition.first; - let second = disk_partition.second; - let third = disk_partition.third; - let img_size = disk_partition.img_size; - if first + second + third + 2100 > img_size * 1024 { - bail!("Image size({}G) is not enough for partitions, please check input", img_size) +pub(crate) fn gen_set_partuuid(file: &mut dyn Write, legacy_bios: bool, dm_verity: bool) -> Result<()> { + if dm_verity { + return Ok(()); + } + let mut vars = HashMap::new(); + let (grub_path, root_partuuid): (String, String); + if legacy_bios { + grub_path = "/boot/grub2/grub.cfg".to_string(); + root_partuuid = SET_PARTUUID_LEGACY.to_string(); + } else { + grub_path = "/boot/efi/EFI/openEuler/grub.cfg".to_string(); + root_partuuid = SET_PARTUUID_EFI.to_string(); + } + vars.insert("GRUB_PATH".to_string(), grub_path); + vars.insert("ROOT_PARTUUID".to_string(), root_partuuid); + let dynamic_script = strfmt(SET_PARTUUID, &vars)?; + writeln!(file, "{dynamic_script}")?; + Ok(()) +} + +// gen_create_img create image file from os.tar. +// For legacy bios, use msdos partition table, and for uefi, use gpt partition table +// In the case of dm_verity, there are 7 partitions which are boot1, root1, hash1, boot2, root2, hash2 and persist. +// The partion size relationship between root and hash is 20:1. +// In the case of no dm_verity, there are 4 partitions which are boot, root1, root2 and persist. +pub(crate) fn gen_create_img(file: &mut dyn Write, legacy_bios: bool, config: &Config) -> Result<()> { + let (img_size, init_boot, partition_info) = + gen_partition(legacy_bios, config.dm_verity.is_some(), &config.disk_partition)?; + let mut vars = HashMap::new(); + + let mut mkdir_persist: String = String::new(); + if let Some(persist_mkdir) = &config.persist_mkdir { + for name in &persist_mkdir.name { + mkdir_persist.push_str(&format!(" mkdir -p \"${{TMP_MOUNT_PATH}}\"/{}\n", name)); } - (first, first + second, first + second + third, img_size) + } + let init_rootb = format!( + "init_part \"${{SCRIPTS_DIR}}\"/system.img{} ROOT-B \"${{TMP_MOUNT_PATH}}\"", + if config.dm_verity.is_some() { "5" } else { "3" } + ); + let init_persist = format!( + "init_part \"${{SCRIPTS_DIR}}\"/system.img{} PERSIST \"${{TMP_MOUNT_PATH}}\"", + if config.dm_verity.is_some() { "7" } else { "4" } + ); + let mut dmv_main = String::new(); + let mut set_partuuid = String::from(r#"set_partuuid "${TMP_MOUNT_PATH}""#); + if config.dm_verity.is_some() { + let keys_dir = if let Some(p) = &config.dm_verity.as_ref().unwrap().keys_dir { + if !p.exists() { + bail!("dm_verity keys_dir does not exist: {}", p.to_str().unwrap()); + } + let canonical_path = p.as_path().canonicalize()?; + let canonical_str = + canonical_path.to_str().ok_or_else(|| anyhow!("Failed to convert canonicalized path to string"))?; + canonical_str.to_string() + } else { + String::new() + }; + dmv_main = format!( + r#"rm -f "${{SCRIPTS_DIR}}"/update.img + {}dmvmain "${{RSApw}}" "${{GPGpw}}" "${{GRUBpw}}""#, + if keys_dir.is_empty() { + "".to_string() + } else { + format!("KEYDIR={} CERTDB={}/certdb ", keys_dir, keys_dir) + } + ); + set_partuuid = String::new(); + } + + vars.insert("IMG_SIZE".to_string(), img_size.to_string()); + vars.insert("PARTITIONS".to_string(), partition_info); + vars.insert("INIT_BOOT".to_string(), init_boot); + vars.insert("SET_PARTUUID".to_string(), set_partuuid); + vars.insert("INIT_ROOTB".to_string(), init_rootb); + vars.insert("INIT_PERSIST".to_string(), init_persist); + vars.insert("MKDIR_PERSIST".to_string(), mkdir_persist); + vars.insert("DMV_MAIN".to_string(), dmv_main); + let dynamic_script = strfmt(CREATE_IMAGE, &vars)?; + + writeln!(file, "{dynamic_script}")?; + Ok(()) +} + +pub(crate) fn gen_partition( + legacy_bios: bool, + dm_verity: bool, + disk_partition: &Option, +) -> Result<(u32, String, String)> { + let img_size = disk_partition.as_ref().and_then(|dp| dp.img_size).unwrap_or(IMAGE_SIZE); + + let init_boot = setup_init_boot(dm_verity, legacy_bios); + let partition_info = if dm_verity { + create_dm_verity_partitions(disk_partition, img_size)? } else { - (60, 2160, 4260, 20) + create_standard_partitions(legacy_bios, disk_partition, img_size)? }; + Ok((img_size, init_boot, partition_info)) +} - writeln!( - file, - r#"function create_img() {{ - rm -f "${{SCRIPTS_DIR}}"/system.img "${{SCRIPTS_DIR}}/update.img" - qemu-img create "${{SCRIPTS_DIR}}/system.img" {}G"#, - img_size - )?; - - if legacy_bios { - writeln!( - file, - r#" local BOOT_PATH=${{TMP_MOUNT_PATH}}/boot/grub2 - parted "${{SCRIPTS_DIR}}/system.img" -s mklabel msdos - parted "${{SCRIPTS_DIR}}/system.img" -s mkpart primary ext4 1MiB {}MiB"#, - first - )?; +fn setup_init_boot(dm_verity: bool, legacy_bios: bool) -> String { + if dm_verity || !legacy_bios { + r#"init_part "${SCRIPTS_DIR}"/system.img1 BOOT "${BOOT_PATH}""#.to_string() } else { - writeln!( - file, - r#" local BOOT_PATH=${{TMP_MOUNT_PATH}}/boot/efi + r#"init_part "${SCRIPTS_DIR}"/system.img1 GRUB2 "${BOOT_PATH}""#.to_string() + } +} + +fn create_dm_verity_partitions(disk_partition: &Option, img_size: u32) -> Result { + let sizes = calculate_dm_verity_partition_sizes(disk_partition, img_size)?; + let mut partition_info = format!( + r#"local BOOT_PATH=${{TMP_MOUNT_PATH}}/boot/efi parted "${{SCRIPTS_DIR}}/system.img" -s mklabel gpt parted "${{SCRIPTS_DIR}}/system.img" -s mkpart primary fat32 1MiB {}MiB"#, - first - )?; + sizes[0] + ); + for i in 0..(sizes.len() - 1) { + partition_info.push_str(&format!( + r#" + parted "${{SCRIPTS_DIR}}/system.img" -s mkpart primary {} {}MiB {}MiB"#, + if i == 2 { "fat32" } else { "ext4" }, + sizes[i], + sizes[i + 1] + )); } - - writeln!( - file, - r#" parted "${{SCRIPTS_DIR}}/system.img" -s mkpart primary ext4 {}MiB {}MiB - parted "${{SCRIPTS_DIR}}/system.img" -s mkpart primary ext4 {}MiB {}MiB + partition_info.push_str(&format!( + r#" parted "${{SCRIPTS_DIR}}/system.img" -s mkpart primary ext4 {}MiB 100%"#, - first, second, second, third, third - )?; - - writeln!( - file, - r#" local device - device=$(losetup -f) - losetup "${{device}}" "${{SCRIPTS_DIR}}"/system.img + sizes[sizes.len() - 1] + )); + Ok(partition_info) +} + +fn create_standard_partitions( + legacy_bios: bool, + disk_partition: &Option, + img_size: u32, +) -> Result { + let sizes = calculate_standard_partition_sizes(disk_partition, img_size)?; + let label = if legacy_bios { "msdos" } else { "gpt" }; + let boot_path = if legacy_bios { "grub2" } else { "efi" }; + + let mut partition_info = format!( + r#"local BOOT_PATH=${{TMP_MOUNT_PATH}}/boot/{} + parted "${{SCRIPTS_DIR}}/system.img" -s mklabel {} + parted "${{SCRIPTS_DIR}}/system.img" -s mkpart primary {} 1MiB {}MiB"#, + boot_path, + label, + if legacy_bios { "ext4" } else { "fat32" }, + sizes[0] + ); + for i in 0..(sizes.len() - 1) { + partition_info.push_str(&format!( + r#" + parted "${{SCRIPTS_DIR}}/system.img" -s mkpart primary ext4 {}MiB {}MiB"#, + sizes[i], + sizes[i + 1] + )); + } + partition_info.push_str(&format!( + r#" + parted "${{SCRIPTS_DIR}}/system.img" -s mkpart primary ext4 {}MiB 100%"#, + sizes[sizes.len() - 1] + )); + Ok(partition_info) +} - mkdir -p "${{TMP_MOUNT_PATH}}" +fn calculate_dm_verity_partition_sizes(disk_partition: &Option, img_size: u32) -> Result> { + let base_sizes = if let Some(p) = disk_partition { + vec![BOOT_SIZE, p.root, p.root / 20, BOOT_SIZE, p.root, p.root / 20] + } else { + vec![BOOT_SIZE, ROOT_SIZE, HASH_SIZE, BOOT_SIZE, ROOT_SIZE, HASH_SIZE] + }; - init_part "${{SCRIPTS_DIR}}"/system.img2 ROOT-A "${{TMP_MOUNT_PATH}}" - - mkdir -p "${{BOOT_PATH}}" - chmod 755 "${{BOOT_PATH}}""# - )?; + let cumulative_sizes = compute_cumulative_sizes(&base_sizes, img_size)?; + Ok(cumulative_sizes) +} - if legacy_bios { - writeln!( - file, - r#" init_part "${{SCRIPTS_DIR}}"/system.img1 GRUB2 "${{BOOT_PATH}}" - tar -x -C "${{TMP_MOUNT_PATH}}" -f "${{SCRIPTS_DIR}}"/os.tar - sed -i "s/insmod part_gpt/insmod part_msdos/g; \ -s/set root='hd0,gpt2'/set root='hd0,msdos2'/g; \ -s/set root='hd0,gpt3'/set root='hd0,msdos3'/g" \ -"${{TMP_MOUNT_PATH}}"/boot/grub2/grub.cfg"# - )?; +fn calculate_standard_partition_sizes(disk_partition: &Option, img_size: u32) -> Result> { + let base_sizes = if let Some(p) = disk_partition { + vec![BOOT_SIZE, p.root, p.root] } else { - writeln!( - file, - r#" init_part "${{SCRIPTS_DIR}}"/system.img1 BOOT "${{BOOT_PATH}}" - tar -x -C "${{TMP_MOUNT_PATH}}" -f "${{SCRIPTS_DIR}}"/os.tar"# - )?; - } - - writeln!( - file, - r#" sync - cp "${{SCRIPTS_DIR}}"/bootloader.sh "${{TMP_MOUNT_PATH}}" - mount_proc_dev_sys "${{TMP_MOUNT_PATH}}" - DEVICE="${{device}}" BOOT_MODE="${{BOOT_MODE}}" chroot "${{TMP_MOUNT_PATH}}" bash bootloader.sh - rm -rf "${{TMP_MOUNT_PATH}}"/bootloader.sh - sync - - dd if=/dev/disk/by-label/ROOT-A of="${{SCRIPTS_DIR}}"/update.img bs=8M - sync - unmount_dir "${{TMP_MOUNT_PATH}}" - init_part "${{SCRIPTS_DIR}}"/system.img3 ROOT-B "${{TMP_MOUNT_PATH}}" - umount "${{TMP_MOUNT_PATH}}" - - init_part "${{SCRIPTS_DIR}}"/system.img4 PERSIST "${{TMP_MOUNT_PATH}}" - mkdir "${{TMP_MOUNT_PATH}}"/{{var,etc,etcwork}}"# - )?; + vec![BOOT_SIZE, ROOT_SIZE, ROOT_SIZE] + }; - if let Some(persist_mkdir) = &config.persist_mkdir { - for name in &persist_mkdir.name { - writeln!(file, "\tmkdir \"${{TMP_MOUNT_PATH}}\"/{}", name)?; - } - } + let cumulative_sizes = compute_cumulative_sizes(&base_sizes, img_size)?; + Ok(cumulative_sizes) +} - writeln!( - file, - r#" mkdir -p "${{TMP_MOUNT_PATH}}"/etc/KubeOS/certs - umount "${{TMP_MOUNT_PATH}}" +fn compute_cumulative_sizes(base_sizes: &[u32], img_size: u32) -> Result> { + let cumulative_sizes: Vec = base_sizes + .iter() + .scan(0, |acc, &p_size| { + *acc += p_size; + Some(*acc) + }) + .collect(); - losetup -D - parted "${{SCRIPTS_DIR}}"/system.img -- set 1 boot on - qemu-img convert "${{SCRIPTS_DIR}}"/system.img -O qcow2 "${{SCRIPTS_DIR}}"/system.qcow2 -}} -"# - )?; - Ok(()) + if cumulative_sizes.last().unwrap_or(&0) + PERSIST_SIZE > img_size * 1024 { + bail!("Image size({}G) is not enough for partitions, please check input", img_size); + } + Ok(cumulative_sizes) } -pub(crate) fn gen_create_vm_repo_img(file: &mut File) -> Result<()> { - writeln!( - file, - r#"function create_vm_repo_img() {{ - create_os_tar_from_repo - create_img -}} - -test_lock -trap clean_space EXIT -trap clean_img ERR - -create_vm_repo_img"# - )?; +pub(crate) fn gen_create_vm_repo_img(file: &mut dyn Write) -> Result<()> { + writeln!(file, "{CREATE_VM_REPO_IMAGE}")?; Ok(()) } -pub(crate) fn gen_create_pxe_repo_img(file: &mut File) -> Result<()> { - writeln!( - file, - r#"function create_pxe_repo_img() {{ - rm -rf "${{SCRIPTS_DIR}}"/initramfs.img "${{SCRIPTS_DIR}}"/kubeos.tar - create_os_tar_from_repo - tar -xvf "${{SCRIPTS_DIR}}"/os.tar "${{SCRIPTS_DIR}}"/initramfs.img - mv "${{SCRIPTS_DIR}}"/os.tar "${{SCRIPTS_DIR}}"/kubeos.tar -}} - -test_lock -trap clean_space EXIT -trap clean_img ERR - -create_pxe_repo_img"# - )?; +pub(crate) fn gen_create_pxe_repo_img(file: &mut dyn Write) -> Result<()> { + writeln!(file, "{CREATE_PXE_REPO_IMAGE}")?; Ok(()) } -pub(crate) fn gen_create_docker_img(file: &mut File) -> Result<()> { - writeln!( - file, - r#"function create_docker_img() {{ - create_os_tar_from_repo - docker build -t "${{DOCKER_IMG}}" -f "${{SCRIPTS_DIR}}"/Dockerfile . -}} - -test_lock -trap clean_space EXIT -trap clean_img ERR - -create_docker_img"# - )?; +pub(crate) fn gen_create_docker_img(file: &mut dyn Write) -> Result<()> { + writeln!(file, "{CREATE_DOCKER_IMAGE}")?; Ok(()) } // docker -pub(crate) fn gen_docker_vars(file: &mut File, image_name: &str) -> Result<()> { +pub(crate) fn gen_docker_vars(file: &mut dyn Write, image_name: &str) -> Result<()> { writeln!( file, r#" -IMAGE_NAME="{}" -BOOT_MODE=efi +DOCKER_IMG="{}" "#, image_name )?; Ok(()) } -pub(crate) fn gen_create_os_tar_from_docker(file: &mut File) -> Result<()> { - writeln!( - file, - r#"function create_os_tar_from_docker() {{ - container_id=$(docker create "${{DOCKER_IMG}}") - echo "$container_id" - docker cp "$container_id":/os.tar "${{SCRIPTS_DIR}}" - docker rm "$container_id" -}} -"# - )?; +pub(crate) fn gen_create_os_tar_from_docker(file: &mut dyn Write) -> Result<()> { + writeln!(file, "{CREATE_OS_TAR_FROM_DOCKER}")?; Ok(()) } -pub(crate) fn gen_create_vm_docker_img(file: &mut File) -> Result<()> { - writeln!( - file, - r#"function create_vm_docker_img() {{ - create_os_tar_from_docker - create_img -}} - -test_lock -trap clean_space EXIT -trap clean_img ERR - -create_vm_docker_img"# - )?; +pub(crate) fn gen_create_vm_docker_img(file: &mut dyn Write) -> Result<()> { + writeln!(file, "{CREATE_VM_DOCKER_IMAGE}")?; Ok(()) } -pub(crate) fn gen_create_pxe_docker_img(file: &mut File) -> Result<()> { - writeln!( - file, - r#"function create_pxe_docker_img() {{ - rm -rf "${{SCRIPTS_DIR}}"/initramfs.img "${{SCRIPTS_DIR}}"/kubeos.tar - create_os_tar_from_docker - tar -xvf "${{SCRIPTS_DIR}}"/os.tar "${{SCRIPTS_DIR}}"/initramfs.img - mv "${{SCRIPTS_DIR}}"/os.tar "${{SCRIPTS_DIR}}"/kubeos.tar -}} - -test_lock -trap clean_space EXIT -trap clean_img ERR - -create_pxe_docker_img"# - )?; +pub(crate) fn gen_create_pxe_docker_img(file: &mut dyn Write) -> Result<()> { + writeln!(file, "{CREATE_PXE_DOCKER_IMAGE}")?; Ok(()) } // admin -pub(crate) fn gen_admin_vars(file: &mut File, docker_img: &str, dockerfile: &PathBuf) -> Result<()> { +pub(crate) fn gen_admin_vars(file: &mut dyn Write, docker_img: &str, hostshell: &PathBuf) -> Result<()> { writeln!(file, "#!/bin/bash")?; gen_copyright(file)?; writeln!( file, - r#"set -e + r#"set -eux -DOCKER_IMG={} -DOCKERFILE={} +SCRIPTS_DIR=$(dirname "$0") +LOCK="${{SCRIPTS_DIR}}"/test.lock ADMIN_CONTAINER_DIR="${{SCRIPTS_DIR}}"/admin-container +DOCKER_IMG={} +DOCKERFILE="${{ADMIN_CONTAINER_DIR}}"/Dockerfile +HOSTSHELL={} "#, - dockerfile.to_str().unwrap(), - docker_img + docker_img, + hostshell.to_str().unwrap() )?; Ok(()) } -pub(crate) fn gen_create_admin_img(file: &mut File) -> Result<()> { - writeln!( - file, - r#"function create_admin_img() {{ - local kubeos_root_dir=$(dirname $(dirname $(dirname "${{SCRIPTS_DIR}}"))) - cp "${{kubeos_root_dir}}"/bin/hostshell "${{ADMIN_CONTAINER_DIR}}" - docker build -t "${{DOCKER_IMG}}" -f "${{DOCKERFILE}}" "${{ADMIN_CONTAINER_DIR}}" - rm -rf "${{ADMIN_CONTAINER_DIR}}"/hostshell -}} - -test_lock -trap clean_space EXIT -trap clean_img ERR - -create_admin_img"# - )?; +pub(crate) fn gen_create_admin_img(file: &mut dyn Write) -> Result<()> { + writeln!(file, "{CREATE_ADMIN_IMAGE}")?; Ok(()) } /* endregion */ /* region: set_in_chroot.sh */ -pub(crate) fn gen_add_users(file: &mut File, users: &Vec) -> Result<()> { +pub(crate) fn gen_add_users(file: &mut dyn Write, users: &Vec) -> Result<()> { writeln!(file, "# add users")?; + writeln!(file, r#"sed -i 's/^CREATE_MAIL_SPOOL=yes/CREATE_MAIL_SPOOL=no/' /etc/default/useradd"#)?; for user in users { - let name = &user.name; - let passwd = &user.passwd; - let groups = match user.groups.clone() { - Some(groups) => groups, - None => vec![name.clone()], - }; - for group in &groups { - writeln!( - file, - r#"if ! getent group "{}" > /dev/null 2>&1; then - groupadd "{}" -fi"#, - group, group - )?; - } - write!(file, "useradd -m -g {}", &groups[0])?; - if groups.len() > 1 { - let additional_groups = &groups[1..].join(","); - write!(file, " -G {}", additional_groups)?; - } - writeln!(file, " -s /bin/bash \"{}\"", &name)?; - writeln!(file, "echo \"{}:{}\" | chpasswd", name, passwd)?; - if let Some(sudo) = &user.sudo { - writeln!( - file, - r#"if visudo -c; then - echo -e "{} {}" | tee -a /etc/sudoers -else - echo "Sudoers file syntax check failed. Please fix the sudoers file manually." - exit 5 -fi"#, - name, sudo - )?; - } + user.gen_add_users(file)?; } Ok(()) } -pub(crate) fn gen_systemd_services(file: &mut File, systemd_services: &SystemdService) -> Result<()> { +pub(crate) fn gen_systemd_services(file: &mut dyn Write, systemd_services: &SystemdService) -> Result<()> { writeln!(file, "# systemd")?; for service_name in &systemd_services.name { writeln!(file, "systemctl enable {}", service_name)?; @@ -774,27 +520,50 @@ pub(crate) fn gen_systemd_services(file: &mut File, systemd_services: &SystemdSe Ok(()) } -pub(crate) fn gen_set_in_chroot(file: &mut File, legacy_bios: bool, config: &Config) -> Result<()> { +pub(crate) fn gen_set_in_chroot( + file: &mut dyn Write, + legacy_bios: bool, + arch: &str, + image_type: &ImageType, + config: &Config, +) -> Result<()> { writeln!(file, "#!/bin/bash")?; gen_copyright(file)?; - writeln!( - file, - r#"ln -s /usr/lib/systemd/system/os-agent.service /usr/lib/systemd/system/multi-user.target.wants/os-agent.service -ln -s /usr/lib/systemd/system/kubelet.service /usr/lib/systemd/system/multi-user.target.wants/kubelet.service"# - )?; - if legacy_bios { - writeln!( - file, + let mut vars = HashMap::new(); + if legacy_bios && arch == "x86_64" { + vars.insert( + "BOOT_MOUNT_ENABLE".to_string(), "ln -s /usr/lib/systemd/system/boot-grub2.mount /lib/systemd/system/local-fs.target.wants/boot-grub2.mount" - )?; + .to_string(), + ); } else { - writeln!( - file, + vars.insert( + "BOOT_MOUNT_ENABLE".to_string(), "ln -s /usr/lib/systemd/system/boot-efi.mount /lib/systemd/system/local-fs.target.wants/boot-efi.mount" - )?; + .to_string(), + ); } - writeln!(file, r#"ln -s /usr/lib/systemd/system/etc.mount /lib/systemd/system/local-fs.target.wants/etc.mount"#)?; + let mut pxe_dracut = String::new(); + if image_type == &ImageType::PxeRepo { + pxe_dracut = r#"dracut -f -v --add bootup /initramfs.img --kver "$(ls /lib/modules)" # added in pxe case +rm -rf /usr/lib/dracut/modules.d/00bootup"# + .to_string(); + } + vars.insert("PXE_DRACUT".into(), pxe_dracut); + + let mut dm_verity_dracut = String::new(); + if config.dm_verity.is_some() { + dm_verity_dracut = r#"if [ -d "/usr/lib/dracut/modules.d/05dmverity" ]; then + dracut -f -v --add dmverity /boot/initramfs-verity.img --kver "$(ls /lib/modules)" + rm -rf /usr/lib/dracut/modules.d/05dmverity +fi +grub2-editenv /boot/efi/EFI/openEuler/grubenv create"# + .to_string(); + } + vars.insert("DM_VERITY_DRACUT".into(), dm_verity_dracut); + let dynamic_script = strfmt(SET_IN_CHROOT, &vars)?; + writeln!(file, "{dynamic_script}")?; if let Some(users) = &config.users { gen_add_users(file, users)?; @@ -802,895 +571,145 @@ ln -s /usr/lib/systemd/system/kubelet.service /usr/lib/systemd/system/multi-user if let Some(systemd_services) = &config.systemd_service { gen_systemd_services(file, systemd_services)?; } - - writeln!( - file, - r#" -str=$(sed -n '/^root:/p' /etc/shadow | awk -F "root:" '{{print $2}}') -umask 0666 -mv /etc/shadow /etc/shadow_bak -sed -i '/^root:/d' /etc/shadow_bak -echo "root:""${{ROOT_PASSWD}}""${{str:1}}" > /etc/shadow -cat /etc/shadow_bak >> /etc/shadow -rm -rf /etc/shadow_bak - -dracut -f -v --add bootup /initramfs.img --kver "$(ls /lib/modules)" -rm -rf /usr/lib/dracut/modules.d/00bootup"# - )?; - Ok(()) } /* endregion */ /* region: bootloader.sh */ -pub(crate) fn gen_bootloader(file: &mut File, arch: &str, legacy_bios: bool) -> Result<()> { +pub(crate) fn gen_bootloader(file: &mut dyn Write, arch: &str, legacy_bios: bool) -> Result<()> { writeln!(file, "#!/bin/bash")?; gen_copyright(file)?; writeln!( file, - r#"set -eu -set -o pipefail -set -x - -function install_grub2 () {{"# + r#"set -eux +set -o pipefail"# )?; - - if arch == "aarch64" || (arch == "x86_64" && !legacy_bios) { - writeln!( - file, - r#" cp -r /usr/lib/grub/x86_64-efi boot/efi/EFI/openEuler - eval "grub2-mkimage -d /usr/lib/grub/x86_64-efi -O x86_64-efi --output=/boot/efi/EFI/openEuler/grubx64.efi '--prefix=(,gpt1)/EFI/openEuler' fat part_gpt part_msdos linux" - - mkdir -p /boot/efi/EFI/BOOT/ - cp -f /boot/efi/EFI/openEuler/grubx64.efi /boot/efi/EFI/BOOT/BOOTX64.EFI -}} -"# - )?; - } else { - writeln!( - file, - r#" GRUBNAME=$(which grub2-install) - echo "Installing GRUB2..." - FORCE_OPT=${{FORCE_OPT:-"--force"}} - TARGET_OPT=${{TARGET_OPT:-"--target=i386-pc"}} - - $GRUBNAME --modules="biosdisk part_msdos" "$FORCE_OPT" "$TARGET_OPT" "$DEVICE" -}} -"# - )?; + match arch { + "x86_64" => { + if legacy_bios { + writeln!(file, "{BOOT_LOADER_LEGACY}")?; + } else { + writeln!(file, "{BOOT_LOADER_X86_UEFI}")?; + } + }, + "aarch64" => { + writeln!(file, "{BOOT_LOADER_AARCH64}")?; + }, + _ => bail!("Unsupported architecture: {}", arch), } - writeln!( - file, - r#"install_grub2 -"# - )?; Ok(()) } /* endregion */ /* region: rpmlist */ -pub(crate) fn gen_rpm_list(file: &mut File, rpmlist: &Vec) -> Result<()> { +pub(crate) fn gen_rpm_list( + file: &mut dyn Write, + rpmlist: &Vec, + arch: &str, + legacy_bios: bool, + dm_verity: bool, +) -> Result<()> { for rpm in rpmlist { writeln!(file, "{}", rpm)?; } - Ok(()) -} -/* endregion */ - -/* region: grub.cfg */ -pub(crate) fn gen_grub_cfg(file: &mut File) -> Result<()> { - gen_copyright(file)?; - - writeln!( - file, - r#"set pager=1 - -if [ -f ${{config_directory}}/grubenv ]; then - load_env -f ${{config_directory}}/grubenv -elif [ -s $prefix/grubenv ]; then - load_env -fi -if [ "${{next_entry}}" ] ; then - set default="${{next_entry}}" - set next_entry= - save_env next_entry - set boot_once=true -else - set default="${{saved_entry}}" -fi - -if [ x"${{feature_menuentry_id}}" = xy ]; then - menuentry_id_option="--id" -else - menuentry_id_option="" -fi - -export menuentry_id_option - -if [ "${{prev_saved_entry}}" ]; then - set saved_entry="${{prev_saved_entry}}" - save_env saved_entry - set prev_saved_entry= - save_env prev_saved_entry - set boot_once=true -fi - -function savedefault {{ - if [ -z "${{boot_once}}" ]; then - saved_entry="${{chosen}}" - save_env saved_entry - fi -}} - -function load_video {{ - if [ x$feature_all_video_module = xy ]; then - insmod all_video - else - insmod efi_gop - insmod efi_uga - insmod ieee1275_fb - insmod vbe - insmod vga - insmod video_bochs - insmod video_cirrus - fi -}} - -terminal_output console -if [ x$feature_timeout_style = xy ] ; then - set timeout_style=menu - set timeout=5 -# Fallback normal timeout code in case the timeout_style feature is -# unavailable. -else - set timeout=5 -fi -set superusers="root" -### END /etc/grub.d/00_header ### - -### BEGIN /etc/grub.d/01_users ### -if [ -f ${{prefix}}/user.cfg ]; then - source ${{prefix}}/user.cfg - if [ -n "${{GRUB2_PASSWORD}}" ]; then - set superusers="root" - export superusers - password_pbkdf2 root ${{GRUB2_PASSWORD}} - fi -fi -### END /etc/grub.d/01_users ### - -### BEGIN /etc/grub.d/10_linux ### -menuentry 'A' --class KubeOS --class gnu-linux --class gnu --class os --unrestricted $menuentry_id_option 'KubeOS-A' {{ - load_video - set gfxpayload=keep - insmod gzio - insmod part_gpt - insmod ext2 - set root='hd0,gpt2' - linux /boot/vmlinuz root=/dev/vda2 ro rootfstype=ext4 nomodeset quiet oops=panic softlockup_panic=1 nmi_watchdog=1 rd.shell=0 selinux=0 crashkernel=256M panic=3 - initrd /boot/initramfs.img -}} - -menuentry 'B' --class KubeOS --class gnu-linux --class gnu --class os --unrestricted $menuentry_id_option 'KubeOS-B' {{ - load_video - set gfxpayload=keep - insmod gzio - insmod part_gpt - insmod ext2 - set root='hd0,gpt3' - linux /boot/vmlinuz root=/dev/vda3 ro rootfstype=ext4 nomodeset quiet oops=panic softlockup_panic=1 nmi_watchdog=1 rd.shell=0 selinux=0 crashkernel=256M panic=3 - initrd /boot/initramfs.img -}} - -### END /etc/grub.d/10_linux ### - -### BEGIN /etc/grub.d/10_reset_boot_success ### -# Hiding the menu is ok if last boot was ok or if this is a first boot attempt to boot the entry -if [ "${{boot_success}}" = "1" -o "${{boot_indeterminate}}" = "1" ]; then - set menu_hide_ok=1 -else - set menu_hide_ok=0 -fi -# Reset boot_indeterminate after a successful boot -if [ "${{boot_success}}" = "1" ] ; then - set boot_indeterminate=0 -# Avoid boot_indeterminate causing the menu to be hidden more then once -elif [ "${{boot_indeterminate}}" = "1" ]; then - set boot_indeterminate=2 -fi -# Reset boot_success for current boot -set boot_success=0 -save_env boot_success boot_indeterminate -### END /etc/grub.d/10_reset_boot_success ### - -### BEGIN /etc/grub.d/12_menu_auto_hide ### -if [ x$feature_timeout_style = xy ] ; then - if [ "${{menu_show_once}}" ]; then - unset menu_show_once - save_env menu_show_once - set timeout_style=menu - set timeout=60 - elif [ "${{menu_auto_hide}}" -a "${{menu_hide_ok}}" = "1" ]; then - set orig_timeout_style=${{timeout_style}} - set orig_timeout=${{timeout}} - if [ "${{fastboot}}" = "1" ]; then - # timeout_style=menu + timeout=0 avoids the countdown code keypress check - set timeout_style=menu - set timeout=0 - else - set timeout_style=hidden - set timeout=1 - fi - fi -fi -### END /etc/grub.d/12_menu_auto_hide ### - -### BEGIN /etc/grub.d/20_linux_xen ### -### END /etc/grub.d/20_linux_xen ### - -### BEGIN /etc/grub.d/20_ppc_terminfo ### -### END /etc/grub.d/20_ppc_terminfo ### - -### BEGIN /etc/grub.d/30_uefi-firmware ### -### END /etc/grub.d/30_uefi-firmware ### - -### BEGIN /etc/grub.d/40_custom ### -# This file provides an easy way to add custom menu entries. Simply type the -# menu entries you want to add after this comment. Be careful not to change -# the 'exec tail' line above. -### END /etc/grub.d/40_custom ### - -### BEGIN /etc/grub.d/41_custom ### -if [ -f ${{config_directory}}/custom.cfg ]; then - source ${{config_directory}}/custom.cfg -elif [ -z "${{config_directory}}" -a -f $prefix/custom.cfg ]; then - source $prefix/custom.cfg; -fi -### END /etc/grub.d/41_custom ### -"# - )?; + match arch { + "x86_64" => { + if legacy_bios { + writeln!(file, "grub2")?; + } else { + writeln!(file, "grub2-efi\ngrub2-tools\ngrub2-efi-x64-modules\ngrub2-pc-modules")?; + if dm_verity { + writeln!(file, "efibootmgr\nveritysetup\nshim\nmokutil\ngrub2-efi-x64")?; + } + } + }, + "aarch64" => { + writeln!(file, "grub2-efi\ngrub2-tools\ngrub2-efi-aa64-modules")?; + if dm_verity { + writeln!(file, "efibootmgr\nveritysetup\nshim\nmokutil\ngrub2-efi-aa64")?; + } + }, + _ => bail!("Unsupported architecture: {}", arch), + } Ok(()) } /* endregion */ /* region: 00bootup */ // 00bootup/global.cfg -pub(crate) fn gen_global_cfg(file: &mut File, pxe_config: &PxeConfig) -> Result<()> { +pub(crate) fn gen_global_cfg(file: &mut dyn Write, config: &PxeConfig) -> Result<()> { + writeln!(file, "#!/bin/bash")?; + gen_copyright(file)?; writeln!( file, - r#"# rootfs file name -rootfs_name={} - + r#"rootfs_name={} # select the target disk to install kubeOS disk={} - # pxe server ip address where stores the rootfs on the http server server_ip={} -# target machine ip -local_ip={} -# target machine route -route_ip={} -# target machine netmask -netmask={} -# target machine netDevice name -net_name={} -"#, - pxe_config.rootfs_name, - pxe_config.disk, - pxe_config.server_ip, - pxe_config.local_ip, - pxe_config.route_ip, - pxe_config.netmask, - pxe_config.net_name - )?; - Ok(()) -} - -// 00bootup/module-setup.sh -pub(crate) fn gen_module_setup(file: &mut File) -> Result<()> { - writeln!(file, "#!/bin/bash")?; - gen_copyright(file)?; - - writeln!( - file, - r#"check() {{ - return 0 -}} - -depends() {{ - echo systemd -}} - -install() {{ - inst_multiple -o grub2-mkimage mkfs.ext4 mkfs.vfat lsblk tar cpio gunzip lspci parted dhclient ifconfig curl hwinfo head tee arch df awk route - inst_hook mount 00 "$moddir/mount.sh" - inst_simple "$moddir/mount.sh" "/mount.sh" - inst_simple "$moddir/Global.cfg" "/Global.cfg" -}} - -installkernel() {{ - hostonly='' - instmods='drivers/ata drivers/nvme drivers/scsi drivers/net fs/fat fs/nls' -}} -"# +route_ip={}"#, + config.rootfs_name, config.disk, config.server_ip, config.route_ip, )?; + if config.dhcp.unwrap_or(false) { + writeln!(file, "dhcs=/dhclient-script",)?; + } else { + writeln!( + file, + "local_ip={}\nnet_name={}\nnetmask={}\n", + config.local_ip.as_ref().unwrap(), + config.net_name.as_ref().unwrap(), + config.netmask.as_ref().unwrap(), + )?; + } Ok(()) } // 00bootup/mount.sh -pub(crate) fn gen_mount(file: &mut File) -> Result<()> { - writeln!(file, "#!/bin/bash")?; - gen_copyright(file)?; - - writeln!( - file, - r#"arch=$(arch) -min_size=8 -log=/install.log - -source ./Global.cfg - -function CheckSpace() {{ - local disk_ava - disk_ava="$(parted -l | grep "${{disk}}" | awk '{{print $3}}')" - if echo "${{disk_ava}}" | grep "[GT]B$"; then - if echo "${{disk_ava}}" | grep GB$; then - disk_ava="$(echo "${{disk_ava}}" | awk -F G '{{print $1}}' | awk -F . '{{print $1}}')" - if [ "${{disk_ava}}" -lt ${{min_size}} ]; then - echo "The available disk space is not enough, at least ${{min_size}}GB." | tee -a ${{log}} - return 1 - fi - fi - else - echo "The available disk space is not enough, at least ${{min_size}}G." | tee -a ${{log}} - return 1 - fi - - return 0 -}} - -function mount_proc_dev_sys() {{ - local tmp_root=$1 - mount -t proc none "${{tmp_root}}/proc" - mount --bind /dev "${{tmp_root}}/dev" - mount --bind /dev/pts "${{tmp_root}}/dev/pts" - mount -t sysfs none "${{tmp_root}}/sys" -}} - -function GetDisk() {{ - mapfile -t disks < <(hwinfo --disk --short 2>&1 | grep -vi "^disk" | awk '{{print $1}}') - if [ ${{#disks[*]}} -gt 0 ]; then - if [ -n "${{disk}}" ] && echo "${{disks[@]}}" | grep -wq "${{disk}}" ; then - echo "${{disk}} exists, start partition" | tee -a ${{log}} - else - echo "disk not exist, please choose correct disk" | tee -a ${{log}} - fi - else - echo "no disk found" | tee -a ${{log}} - return 1 - fi - CheckSpace - local status=$? - if [ $status -ne 0 ]; then - echo "no enough space on ${{disk}}" | tee -a ${{log}} - return 1 - fi - - return 0 -}} - -function PartitionAndFormatting() {{ - echo "Partitioning and formatting disk $disk..." - # partition and format - parted "${{disk}}" -s mklabel gpt >> ${{log}} 2>&1 - local status=$? - if [ $status -ne 0 ]; then - echo "partition failed" | tee -a ${{log}} - return 1 - fi - - parted "${{disk}}" -s mkpart primary fat16 1M 100M >> ${{log}} 2>&1 - local status=$? - if [ $status -ne 0 ]; then - echo "partition failed" | tee -a ${{log}} - return 1 - fi - - parted "${{disk}}" -s mkpart primary ext4 100M 2600M >> ${{log}} 2>&1 - local status=$? - if [ $status -ne 0 ]; then - echo "partition failed" | tee -a ${{log}} - return 1 - fi - - parted "${{disk}}" -s mkpart primary ext4 2600M 5100M >> ${{log}} 2>&1 - local status=$? - if [ $status -ne 0 ]; then - echo "partition failed" | tee -a ${{log}} - return 1 - fi - - parted "${{disk}}" -s mkpart primary ext4 5100M 100% >> ${{log}} 2>&1 - local status=$? - if [ $status -ne 0 ]; then - echo "partition failed" | tee -a ${{log}} - return 1 - fi - - parted "${{disk}}" -s set 1 boot on >> ${{log}} 2>&1 - local status=$? - if [ $status -ne 0 ]; then - echo "partition failed" | tee -a ${{log}} - return 1 - fi - - mkfs.vfat -n "BOOT" "${{disk}}"1 >> ${{log}} 2>&1 - local status=$? - if [ $status -ne 0 ]; then - echo "format failed" | tee -a ${{log}} - return 1 - fi - - mkfs.ext4 -L "ROOT-A" "${{disk}}"2 >> ${{log}} 2>&1 - local status=$? - if [ $status -ne 0 ]; then - echo "format failed" | tee -a ${{log}} - return 1 - fi - - mkfs.ext4 -L "ROOT-B" "${{disk}}"3 >> ${{log}} 2>&1 - local status=$? - if [ $status -ne 0 ]; then - echo "format failed" | tee -a ${{log}} - return 1 - fi - - mkfs.ext4 -L "PERSIST" "${{disk}}"4 >> ${{log}} 2>&1 - local status=$? - if [ $status -ne 0 ]; then - echo "format failed" | tee -a ${{log}} - return 1 - fi - - return 0 -}} - -function InitNetwork() {{ - echo "Initializing network..." - mapfile -t netNames < <(ifconfig -a | awk '{{print $1}}' | grep : | grep '^e' | awk -F: '{{print $1}}') - if [ ${{#netNames[*]}} -gt 0 ]; then - if [ -n "${{net_name}}" ] && echo "${{netNames[@]}}" | grep -wq "${{net_name}}" ; then - echo "${{net_name}} exists, start set ip" | tee -a ${{log}} - else - echo "net_name not exist, choose default net" | tee -a ${{log}} - net_name=${{netNames[0]}} - fi - else - echo "no net Device found" | tee -a ${{log}} - return 1 - fi - - ifconfig "${{net_name}}" up - local status=$? - if [ $status -ne 0 ]; then - echo "load net card failed" | tee -a ${{log}} - return 1 - fi - sleep 3 - - ifconfig "${{net_name}}" "${{local_ip}}" netmask "${{netmask}}" >> ${{log}} 2>&1 - local status=$? - if [ $status -ne 0 ]; then - echo "ip set failed" | tee -a ${{log}} - return 1 - fi - sleep 3 - - route add default gw "${{route_ip}}" >> ${{log}} 2>&1 - local status=$? - if [ $status -ne 0 ]; then - echo "add route failed" | tee -a ${{log}} - return 1 - fi - sleep 3 - return 0 -}} - -function MountRoot() {{ - echo "Mounting rootfs..." - # mount rootfs - mount "${{disk}}"2 /sysroot >> ${{log}} 2>&1 - local status=$? - if [ $status -ne 0 ]; then - echo "mount rootfs failed" | tee -a ${{log}} - return 1 - fi - - return 0 -}} - -function MountPersist() {{ - echo "Mounting persist" - mount "${{disk}}"4 /sysroot/persist >> ${{log}} 2>&1 - local status=$? - if [ $status -ne 0 ]; then - echo "mount persist failed" | tee -a ${{log}} - return 1 - fi - mkdir /sysroot/persist/{{var,etc,etcwork}} - mkdir -p /sysroot/persist/etc/KubeOS/certs - return 0 -}} - -function MountBoot() {{ - echo "Mounting boot" - mkdir -p /sysroot/boot/efi - mount "${{disk}}"1 /sysroot/boot/efi >> ${{log}} 2>&1 - local status=$? - if [ $status -ne 0 ]; then - echo "mount boot failed" | tee -a ${{log}} - return 1 - fi - return 0 -}} - -function GetRootfs() {{ - echo "Downloading rootfs..." - - curl -o /"${{rootfs_name}}" http://"${{server_ip}}"/"${{rootfs_name}}" - if [ ! -e "/${{rootfs_name}}" ]; then - echo "download rootfs failed" | tee -a ${{log}} - return 1 - fi - - tar -xf /"${{rootfs_name}}" -C /sysroot - local status=$? - if [ $status -ne 0 ]; then - echo "decompose rootfs failed" | tee -a ${{log}} - return 1 - fi - - rm -rf "${{rootfs_name:?}}" - mount -o remount,ro "${{disk}}"2 /sysroot >> ${{log}} 2>&1 - return 0 -}} - -function Inst_Grub2_x86() {{ - # copy the files that boot need - cp -r /sysroot/usr/lib/grub/x86_64-efi /sysroot/boot/efi/EFI/openEuler - eval "grub2-mkimage -d /sysroot/usr/lib/grub/x86_64-efi -O x86_64-efi --output=/sysroot/boot/efi/EFI/openEuler/grubx64.efi '--prefix=(,gpt1)/EFI/openEuler' fat part_gpt part_msdos linux" >> ${{log}} 2>&1 - local status=$? - if [ $status -ne 0 ]; then - echo "grub2-mkimage on x86 failed" | tee -a ${{log}} - return 1 - fi - - mkdir -p /sysroot/boot/efi/EFI/BOOT/ - cp -f /sysroot/boot/efi/EFI/openEuler/grubx64.efi /sysroot/boot/efi/EFI/BOOT/BOOTX64.EFI - - return 0 -}} - -function Inst_Grub2_aarch64() {{ - cp -r /sysroot/usr/lib/grub/arm64-efi /sysroot/boot/efi/EFI/openEuler/ - eval "grub2-mkimage -d /sysroot/usr/lib/grub/arm64-efi -O arm64-efi --output=/sysroot/boot/efi/EFI/openEuler/grubaa64.efi '--prefix=(,gpt1)/EFI/openEuler' fat part_gpt part_msdos linux" >> ${{log}} 2>&1 - local status=$? - if [ $status -ne 0 ]; then - echo "grub2-mkimage on aarch64 failed" | tee -a ${{log}} - return 1 - fi - - mkdir -p /sysroot/boot/efi/EFI/BOOT/ - cp -f /sysroot/boot/efi/EFI/openEuler/grubaa64.efi /sysroot/boot/efi/EFI/BOOT/BOOTAA64.EFI - - return 0 -}} - -function SetBoot() {{ - # mount boot - echo "Setting boot" - - if [ "$arch" == "x86_64" ]; then - Inst_Grub2_x86 - local status=$? - if [ $status -ne 0 ]; then - echo "install grub on x86 failed" | tee -a ${{log}} - return 1 - fi - fi - - if [ "$arch" == "aarch64" ]; then - Inst_Grub2_aarch64 - local status=$? - if [ $status -ne 0 ]; then - echo "install grub on aarch64 failed" | tee -a ${{log}} - return 1 - fi - fi - sed -i 's#/dev/sda#'"${{disk}}"'#g' /sysroot/boot/efi/EFI/openEuler/grub.cfg - - return 0 -}} - -function Bootup_Main() {{ - # get disk - echo "Checking disk info..." | tee -a ${{log}} - GetDisk - local status=$? - if [ $status -ne 0 ]; then - echo "Checking disk info failed" | tee -a ${{log}} - return 1 - fi - - # partition and format disk - echo "Partion and formatting..." | tee -a ${{log}} - PartitionAndFormatting - local status=$? - if [ $status -ne 0 ]; then - echo "Partition and formatting disk failed" | tee -a ${{log}} - return 1 - fi - - # init network - echo "Initializing network..." | tee -a ${{log}} - InitNetwork - local status=$? - if [ $status -ne 0 ]; then - echo "Initializing network failed" | tee -a ${{log}} - return 1 - fi - - # mount partitions - - # mount boot - echo "Mounting root..." | tee -a ${{log}} - MountRoot - local status=$? - if [ $status -ne 0 ]; then - echo "Mounting root failed" | tee -a ${{log}} - return 1 - fi - - echo "Mounting boot..." | tee -a ${{log}} - MountBoot - local status=$? - if [ $status -ne 0 ]; then - echo "Mounting boot failed" | tee -a ${{log}} - return 1 - fi - - # download rootfs - echo "Downloading rootfs..." | tee -a ${{log}} - GetRootfs - local status=$? - if [ $status -ne 0 ]; then - echo "Downloading rootfs failed" | tee -a ${{log}} - return 1 - fi - mount_proc_dev_sys /sysroot - # set boot - echo "Setting boot..." | tee -a ${{log}} - SetBoot - local status=$? - if [ $status -ne 0 ]; then - echo "Setting boot failed" | tee -a ${{log}} - return 1 - fi - # mount persist - echo "Mounting persist..." | tee -a ${{log}} - MountPersist - local status=$? - if [ $status -ne 0 ]; then - echo "Mounting persist failed" | tee -a ${{log}} - return 1 - fi - return 0 -}} - -Bootup_Main -ret=$? -if [ ${{ret}} -eq 0 ]; then - echo "kubeOS install success! switch to root" | tee -a ${{log}} - cp ${{log}} /sysroot/persist -else - echo "kubeOS install failed, see install.log" | tee -a ${{log}} -fi - -"# - )?; - Ok(()) -} -/* endregion */ - -/* region: dockerfile */ -pub(crate) fn gen_dockerfile(file: &mut File) -> Result<()> { - writeln!( - file, - r#"FROM scratch -COPY os.tar / -CMD ["/bin/sh"] -"# - )?; - Ok(()) -} -/* endregion */ - -/* region: admin-container */ -// admin-container/dockerfile -pub(crate) fn gen_admin_dockerfile(file: &mut File) -> Result<()> { - gen_copyright(file)?; - - writeln!( - file, - r#"FROM openeuler-22.03-lts -MAINTAINER - -RUN yum -y install openssh-clients util-linux - -ADD ./sysmaster-0.2.3-1.oe2203.aarch64.rpm /home -RUN rpm -ivh /home/sysmaster-0.2.3-1.oe2203.aarch64.rpm - -COPY ./hostshell /usr/bin/ -COPY ./set-ssh-pub-key.sh /usr/local/bin -COPY ./set-ssh-pub-key.service /usr/lib/sysmaster - -EXPOSE 22 -# set sshd.service and set-ssh-pub-key.service pulled up by default -RUN sed -i 's/sysinit.target/sysinit.target;sshd.service;set-ssh-pub-key.service/g' /usr/lib/sysmaster/basic.target - -CMD ["/usr/lib/sysmaster/init"] -"# - )?; - Ok(()) -} - -// admin-container/set-ssh-pub-key.service -pub(crate) fn gen_set_ssh_pub_key_service(file: &mut File) -> Result<()> { - gen_copyright(file)?; - - writeln!( - file, - r#"[Unit] -Description="set ssh authorized keys according to the secret which is set by user" - -[Service] -ExecStart="/usr/local/bin/set-ssh-pub-key.sh" -"# - )?; - Ok(()) -} - -// admin-container/set-ssh-pub-key.sh -pub(crate) fn gen_set_ssh_pub_key(file: &mut File) -> Result<()> { - gen_copyright(file)?; +pub(crate) fn gen_mount(file: &mut dyn Write, config: &Config) -> Result<()> { + let mut mkdir_args = String::from("mkdir /sysroot/persist/{var,etc,etcwork,opt,optwork"); + if let Some(persist_mkdir) = &config.persist_mkdir { + for name in &persist_mkdir.name { + mkdir_args.push_str(&format!(",{}", name)); + } + } + mkdir_args.push('}'); - writeln!( - file, - r#"ssh_pub=$(cat /etc/secret-volume/ssh-pub-key) -ssh_dir="/root/.ssh" -authorized_file="$ssh_dir/authorized_keys" + let (first, second, third) = if let Some(disk_partition) = &config.disk_partition { + let first = BOOT_SIZE; + let second = disk_partition.root; + let third = disk_partition.root; + (first, first + second, first + second + third) + } else { + (BOOT_SIZE, BOOT_SIZE + ROOT_SIZE, BOOT_SIZE + 2 * ROOT_SIZE) + }; -if [ ! -d "$ssh_dir" ]; then - mkdir "$ssh_dir" - chmod 700 "$ssh_dir" -fi + let mut vars = HashMap::new(); + vars.insert("MKDIR_COMMAND".to_string(), mkdir_args); + vars.insert("PARTITION1_SIZE".to_string(), first.to_string()); + vars.insert("PARTITION2_SIZE".to_string(), second.to_string()); + vars.insert("PARTITION3_SIZE".to_string(), third.to_string()); -if [ ! -f "$authorized_file" ]; then - touch "$authorized_file" - chmod 600 "$authorized_file" -fi + if config.pxe_config.as_ref().unwrap().dhcp.unwrap_or(false) { + vars.insert("SET_IP".to_string(), DHCP_SET_IP.to_string()); + vars.insert("MANUAL_GET_IF_NAME".to_string(), "".to_string()); + } else { + vars.insert("SET_IP".to_string(), MANUAL_SET_IP.to_string()); + vars.insert("MANUAL_GET_IF_NAME".to_string(), MANUAL_GET_IF_NAME.to_string()); + } + let dynamic_script = strfmt(INIT_NETWORK_PARTITION, &vars)?; -echo "$ssh_pub" >> "$authorized_file" -"# - )?; + writeln!(file, "{BOOTUP_MOUNT_1}")?; + writeln!(file, "{dynamic_script}")?; + writeln!(file, "{BOOTUP_MOUNT_2}")?; Ok(()) } /* endregion */ /* region: misc-files */ -// misc-files/boot-efi.mount -pub(crate) fn gen_boot_efi_mount(file: &mut File) -> Result<()> { - gen_copyright(file)?; - - writeln!( - file, - r#"[Unit] -Description=grub2 Dir -DefaultDependencies=no -Conflicts=umount.target -Before=local-fs.target umount.target - -[Mount] -What=/dev/disk/by-label/BOOT -Where=/boot/efi -Type=vfat -Options=defaults - -[Install] -WantedBy=local-fs.target -"# - )?; - Ok(()) -} - -// misc-files/boot-grub2.mount -pub(crate) fn gen_boot_grub2_mount(file: &mut File) -> Result<()> { - gen_copyright(file)?; - - writeln!( - file, - r#"[Unit] -Description=grub2 Dir -DefaultDependencies=no -Conflicts=umount.target -Before=local-fs.target umount.target - -[Mount] -What=/dev/disk/by-label/GRUB2 -Where=/boot/grub2 -Type=ext4 -Options=defaults - -[Install] -WantedBy=local-fs.target -"# - )?; - Ok(()) -} - -// misc-files/etc.mount -pub(crate) fn gen_etc_mount(file: &mut File) -> Result<()> { - gen_copyright(file)?; - - writeln!( - file, - r#"[Unit] -Description=etc Dir -DefaultDependencies=no -Conflicts=umount.target -Before=local-fs.target umount.target -Wants=persist.mount -After=persist.mount - -[Mount] -What=overlay -Where=/etc -Type=overlay -Options=upperdir=/persist/etc,lowerdir=/etc,workdir=/persist/etcwork - -[Install] -WantedBy=local-fs.target -"# - )?; - Ok(()) -} - -// misc-files/os-agent.service -pub(crate) fn gen_os_agent_service(file: &mut File) -> Result<()> { - gen_copyright(file)?; - - writeln!( - file, - r#"[Unit] -Description=Agent For KubeOS - -[Service] -Environment=GOTRACEBACK=crash -ExecStart=/usr/bin/os-agent -KillMode=process -Restart=on-failure - -[Install] -WantedBy=multi-user.target -"# - )?; - Ok(()) -} - // misc-files/os-release -pub(crate) fn gen_os_release(file: &mut File) -> Result<()> { +pub(crate) fn gen_os_release(file: &mut dyn Write) -> Result<()> { writeln!( file, r#"NAME=KubeOS @@ -1699,56 +718,25 @@ ID=KubeOS )?; Ok(()) } +/* endregion */ -// misc-files/persist.mount -pub(crate) fn gen_persist_mount(file: &mut File) -> Result<()> { - gen_copyright(file)?; - - writeln!( - file, - r#"[Unit] -Description=PERSIST Dir (/persist) -DefaultDependencies=no -Conflicts=umount.target -Before=local-fs.target umount.target - -[Mount] -What=/dev/disk/by-label/PERSIST -Where=/persist -Type=ext4 -Options=defaults - -[Install] -WantedBy=local-fs.target -"# - )?; - Ok(()) -} - -// misc-files/var.mount -pub(crate) fn gen_var_mount(file: &mut File) -> Result<()> { - gen_copyright(file)?; - - writeln!( - file, - r#"[Unit] -Description=var Dir -DefaultDependencies=no -Conflicts=umount.target -Before=local-fs.target umount.target -Wants=persist.mount -After=persist.mount - -[Mount] -What=/persist/var -Where=/var -Type=node -Options=bind - -[Install] -WantedBy=local-fs.target -"# - )?; - Ok(()) +#[cfg(test)] +mod tests { + use std::collections::HashMap; + + use strfmt::strfmt; + + #[test] + fn test_strfmt() { + let mystring = r#" + function {{ + Hello {name}, {age} years old. + }} + "#; + let mut vars = HashMap::new(); + vars.insert("name".to_string(), "John".to_string()); + vars.insert("age".to_string(), "30".to_string()); + let result = strfmt(mystring, &vars).unwrap(); + println!("{}", result); + } } -/* endregion */ diff --git a/KubeOS-Rust/kbimg/src/utils.rs b/KubeOS-Rust/kbimg/src/utils.rs index c7ea5505..48960990 100644 --- a/KubeOS-Rust/kbimg/src/utils.rs +++ b/KubeOS-Rust/kbimg/src/utils.rs @@ -1,5 +1,5 @@ /* - * Copyright (c) Huawei Technologies Co., Ltd. 2023. All rights reserved. + * Copyright (c) Huawei Technologies Co., Ltd. 2024. All rights reserved. * KubeOS is licensed under the Mulan PSL v2. * You can use this software according to the terms and conditions of the Mulan PSL v2. * You may obtain a copy of Mulan PSL v2 at: @@ -10,13 +10,18 @@ * See the Mulan PSL v2 for more details. */ -use std::{fs, os::unix::fs::PermissionsExt, path::PathBuf, process::Command}; +use std::{ + fs, + os::unix::fs::PermissionsExt, + path::{Path, PathBuf}, + process::Command, +}; -use anyhow::bail; +use anyhow::{bail, Result}; -use crate::commands::PxeConfig; +use crate::commands::Config; -pub(crate) fn execute_scripts(script: PathBuf) -> anyhow::Result<()> { +pub(crate) fn execute_scripts(script: PathBuf) -> Result<()> { if !script.exists() { bail!("Script does not exist: {:?}", script); } @@ -27,8 +32,8 @@ pub(crate) fn execute_scripts(script: PathBuf) -> anyhow::Result<()> { Ok(()) } -pub(crate) fn set_permissions(path: &str, permission_value: u32) -> anyhow::Result<()> { - let metadata = fs::metadata(path)?; +pub(crate) fn set_permissions>(path: P, permission_value: u32) -> Result<()> { + let metadata = fs::metadata(&path)?; let mut permissions = metadata.permissions(); permissions.set_mode(permission_value); fs::set_permissions(path, permissions)?; @@ -42,7 +47,7 @@ pub(crate) fn is_valid_param + std::fmt::Debug>(param: S) -> bool } /// Check if the path exists and is indeed a file -pub(crate) fn is_file_valid(msg: &str, path: &PathBuf) -> anyhow::Result<()> { +pub(crate) fn is_file_valid(msg: &str, path: &PathBuf) -> Result<()> { if !path.exists() { bail!("{} does not exist: {:?}", msg, path); } @@ -71,27 +76,21 @@ pub(crate) fn is_addr_valid(addr: &str) -> bool { true } -/// Check pxe config -pub(crate) fn check_pxe_conf_valid(pxe_config: &PxeConfig) -> anyhow::Result<()> { - if !is_addr_valid(&pxe_config.server_ip) { - bail!("address {} is invalid, please check input", &pxe_config.server_ip) - } - if !is_addr_valid(&pxe_config.local_ip) { - bail!("address {} is invalid, please check input", &pxe_config.local_ip) - } - if !is_addr_valid(&pxe_config.route_ip) { - bail!("address {} is invalid, please check input", &pxe_config.route_ip) - } - if !is_addr_valid(&pxe_config.netmask) { - bail!("address {} is invalid, please check input", &pxe_config.netmask) +/// Get architecture +pub(crate) fn get_arch() -> Result { + let output = std::process::Command::new("arch").output().expect("Failed to execute `arch` command"); + let arch = String::from_utf8_lossy(&output.stdout).trim().to_string(); + if arch != "x86_64" && arch != "aarch64" { + bail!("Unsupported architecture: {}", arch); } - Ok(()) + Ok(arch) } -/// Get architecture -pub(crate) fn get_arch() -> String { - let output = std::process::Command::new("arch").output().expect("Failed to execute `arch` command"); - String::from_utf8_lossy(&output.stdout).trim().to_string() +pub(crate) fn check_config_toml(config: &Config) -> Result<()> { + if config.from_repo.is_some() && config.from_dockerimg.is_some() { + bail!("Both from_repo and from_dockerimg are provided in config file. Please provide only one of them") + } + Ok(()) } #[cfg(test)] diff --git a/KubeOS-Rust/kbimg/src/values.rs b/KubeOS-Rust/kbimg/src/values.rs index 3d0cd95e..714ca4cd 100644 --- a/KubeOS-Rust/kbimg/src/values.rs +++ b/KubeOS-Rust/kbimg/src/values.rs @@ -1,5 +1,5 @@ /* - * Copyright (c) Huawei Technologies Co., Ltd. 2023. All rights reserved. + * Copyright (c) Huawei Technologies Co., Ltd. 2024. All rights reserved. * KubeOS is licensed under the Mulan PSL v2. * You can use this software according to the terms and conditions of the Mulan PSL v2. * You may obtain a copy of Mulan PSL v2 at: @@ -19,7 +19,6 @@ pub(crate) const RPMLIST: &str = "rpmlist"; pub(crate) const DOCKERFILE: &str = "Dockerfile"; pub(crate) const BOOTUP_DIR: &str = "./scripts-auto/00bootup"; -pub(crate) const BOOTUP_GLOBAL_CFG: &str = "Global.cfg"; pub(crate) const BOOTUP_MODULE_SETUP_SH: &str = "module-setup.sh"; pub(crate) const BOOTUP_MOUNT_SH: &str = "mount.sh"; @@ -32,11 +31,1776 @@ pub(crate) const MISC_FILES_DIR: &str = "./scripts-auto/misc-files"; pub(crate) const MISC_BOOT_EFI_MOUNT: &str = "boot-efi.mount"; pub(crate) const MISC_BOOT_GRUB2_MOUNT: &str = "boot-grub2.mount"; pub(crate) const MISC_ETC_MOUNT: &str = "etc.mount"; +pub(crate) const MISC_OPT_CNI_MOUNT: &str = "opt-cni.mount"; pub(crate) const MISC_OS_AGENT_SERVICE: &str = "os-agent.service"; pub(crate) const MISC_OS_RELEASE: &str = "os-release"; pub(crate) const MISC_PERSIST_MOUNT: &str = "persist.mount"; pub(crate) const MISC_VAR_MOUNT: &str = "var.mount"; +pub(crate) const DMV_DIR: &str = "./scripts-auto/dm-verity"; +pub(crate) const DMV_CHROOT: &str = "chroot_new_grub.sh"; +pub(crate) const DMV_MAIN: &str = "dm_verity.sh"; +pub(crate) const DMV_DRACUT_DIR: &str = "./scripts-auto/05dmverity"; +pub(crate) const DMV_DRACUT_MOUNT: &str = "dmv-mount.sh"; +pub(crate) const DMV_DRACUT_MODULE: &str = "module-setup.sh"; +pub(crate) const DMV_UPGRADE_ROLLBACK: &str = "kubeos-dmv"; + // permissions pub(crate) const CONFIG_PERMISSION: u32 = 0o640; pub(crate) const EXEC_PERMISSION: u32 = 0o550; +pub(crate) const DIR_PERMISSION: u32 = 0o750; + +// KubeOS image(GB) and partition(MiB) size +pub(crate) const BOOT_SIZE: u32 = 60; +pub(crate) const ROOT_SIZE: u32 = 2560; +pub(crate) const HASH_SIZE: u32 = 128; +pub(crate) const PERSIST_SIZE: u32 = 2100; +pub(crate) const IMAGE_SIZE: u32 = 20; + +pub const COPYRIGHT: &str = r#"# Copyright (c) Huawei Technologies Co., Ltd. 2024. All rights reserved. +# KubeOS is licensed under the Mulan PSL v2. +# You can use this software according to the terms and conditions of the Mulan PSL v2. +# You may obtain a copy of Mulan PSL v2 at: +# http://license.coscl.org.cn/MulanPSL2 +# THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY KIND, EITHER EXPRESS OR +# IMPLIED, INCLUDING BUT NOT LIMITED TO NON-INFRINGEMENT, MERCHANTABILITY OR FIT FOR A PARTICULAR +# PURPOSE. +# See the Mulan PSL v2 for more details. +"#; + +pub const GLOBAL_VARS: &str = r#"set -eux + +NAME=KubeOS +ID=kubeos +SCRIPTS_DIR=$(cd "$(dirname "$0")" && pwd) +LOCK="${SCRIPTS_DIR}"/test.lock +RPM_ROOT="${SCRIPTS_DIR}"/rootfs +TMP_MOUNT_PATH="${SCRIPTS_DIR}"/mnt"#; + +pub const TEST_LOCK: &str = r#"function file_lock() { + local lock_file=$1 + exec {lock_fd}>"${lock_file}" + flock -xn "${lock_fd}" +} + +function test_lock() { + file_lock "${LOCK}" + local status=$? + if [ $status -ne 0 ]; then + log_error_print "There is already an generate process running." + exit 203 + fi +} +"#; + +pub const CLEANUP: &str = r#"function clean_space() { + delete_dir "${RPM_ROOT}" + delete_dir "${TMP_MOUNT_PATH}" + delete_file "${SCRIPTS_DIR}"/os.tar + rm -rf "${LOCK}" + delete_dir "${SCRIPTS_DIR}/dm-verity/tmp" +} + +function clean_img() { + losetup -D + delete_file "${SCRIPTS_DIR}"/system.img + delete_file "${SCRIPTS_DIR}"/update.img + delete_file "${SCRIPTS_DIR}"/initramfs.img + delete_file "${SCRIPTS_DIR}"/kubeos.tar + delete_file "${SCRIPTS_DIR}"/update-root.img + delete_file "${SCRIPTS_DIR}"/update-boot.img + delete_file "${SCRIPTS_DIR}"/update-hash.img + delete_file "${SCRIPTS_DIR}"/update-roothash +} +"#; + +pub const DELETE_DIR: &str = r#"function delete_dir() { + local ret=0 + local dir="$1" + unmount_dir "${dir}" + ret=$? + if [ "${ret}" -eq 0 ]; then + rm -rf "${dir}" + return 0 + else + log_error_print "${dir} is failed to unmount , can not delete ${dir}." + return 1 + fi +} +"#; + +pub const DELETE_FILE: &str = r#"function delete_file() { + local file="$1" + if [ ! -e "${file}" ]; then + return 0 + fi + + if [ ! -f "${file}" ]; then + log_error_print "${file} is not a file." + return 1 + fi + + rm -f "${file}" + return 0 +} +"#; + +pub const LOG: &str = r#"function log_error_print() { + local logmsg + logmsg="[ ERROR ] - ""$(date "+%b %d %Y %H:%M:%S")"" $1" + echo "$logmsg" +} + +function log_info_print() { + local logmsg + logmsg="[ INFO ] - ""$(date "+%b %d %Y %H:%M:%S")"" $1" + echo "$logmsg" +} +"#; + +pub const MOUNT_PROC_DEV_SYS: &str = r#"function mount_proc_dev_sys() { + local tmp_root=$1 + mount -t proc none "${tmp_root}"/proc + mount --bind /dev "${tmp_root}"/dev + mount --bind /dev/pts "${tmp_root}"/dev/pts + mount -t sysfs none "${tmp_root}"/sys +} +"#; + +pub const UNMOUNT_DIR: &str = r#"function unmount_dir() { + local dir=$1 + + if [ -L "${dir}" ] || [ -f "${dir}" ]; then + log_error_print "${dir} is not a directory, please check it." + return 1 + fi + + if [ ! -d "${dir}" ]; then + return 0 + fi + + local real_dir + real_dir=$(readlink -e "${dir}") + local mnts + mnts=$(awk '{print $2}' "$iso_repo" +} +"#; + +pub const INSTALL_PACKAGES: &str = r#"function install_packages() { + prepare_yum "${REPO_PATH}" + + echo "install package.." + + local filesize + filesize=$(stat -c "%s" "${SCRIPTS_DIR}"/rpmlist) + local maxsize=$((1024 * 1024)) + if [ "${filesize}" -gt "${maxsize}" ]; then + echo "please check if rpmlist is too big or something wrong" + exit 7 + fi + + local rpms_name + rpms_name=$(tr "\n" " " <"${SCRIPTS_DIR}"/rpmlist) + read -ra rpms <<<"${rpms_name}" + yum -y --installroot="${RPM_ROOT}" install --nogpgcheck --setopt install_weak_deps=False "${rpms[@]}" + yum -y --installroot="${RPM_ROOT}" clean all +} +"#; + +pub const INSTALL_MISC: &str = r#"function install_misc() {{ + cp "${{SCRIPTS_DIR}}"/misc-files/*mount "${{SCRIPTS_DIR}}"/misc-files/os-agent.service "${{RPM_ROOT}}"/usr/lib/systemd/system/ + cp "${{SCRIPTS_DIR}}"/misc-files/os-release "${{RPM_ROOT}}"/usr/lib/ + cp "${{AGENT_PATH}}" "${{RPM_ROOT}}"/usr/bin + rm "${{RPM_ROOT}}"/etc/os-release + + cat <"${{RPM_ROOT}}"/usr/lib/os-release +NAME="${{NAME}}" +ID=${{ID}} +PRETTY_NAME="${{NAME}} ${{VERSION}}" +VERSION_ID=${{VERSION}} +EOF + mv "${{RPM_ROOT}}"/boot/vmlinuz* "${{RPM_ROOT}}"/boot/vmlinuz + mv "${{RPM_ROOT}}"/boot/initramfs* "${{RPM_ROOT}}"/boot/initramfs.img + {COPY_GRUB_CFG} + {PXE_BOOTUP_FILES} + {DM_VERITY_FILES} + # custom config +{CUSTOM_SCRIPT} + + cp "${{SCRIPTS_DIR}}"/set_in_chroot.sh "${{RPM_ROOT}}" + ROOT_PASSWD="${{ROOT_PASSWD}}" chroot "${{RPM_ROOT}}" bash /set_in_chroot.sh + rm "${{RPM_ROOT}}/set_in_chroot.sh" +}} +"#; + +pub const SET_IN_CHROOT: &str = r#"set -eux +ln -s /usr/lib/systemd/system/os-agent.service /usr/lib/systemd/system/multi-user.target.wants/os-agent.service +ln -s /usr/lib/systemd/system/kubelet.service /usr/lib/systemd/system/multi-user.target.wants/kubelet.service +ln -s /usr/lib/systemd/system/etc.mount /lib/systemd/system/local-fs.target.wants/etc.mount +ln -s /usr/lib/systemd/system/opt-cni.mount /lib/systemd/system/local-fs.target.wants/opt-cni.mount +mkdir -p /opt/cni +{BOOT_MOUNT_ENABLE} + +str=$(sed -n '/^root:/p' /etc/shadow | awk -F "root:" '{{print $2}}') +umask 0666 +mv /etc/shadow /etc/shadow_bak +sed -i '/^root:/d' /etc/shadow_bak +echo "root:""${{ROOT_PASSWD}}""${{str:1}}" >/etc/shadow +cat /etc/shadow_bak >>/etc/shadow +rm -rf /etc/shadow_bak +{PXE_DRACUT} +{DM_VERITY_DRACUT}"#; + +pub const SET_PARTUUID: &str = r#"function set_partuuid() {{ + root_path=$1 + grub_path="$root_path{GRUB_PATH}" + {ROOT_PARTUUID} + + sed -i "s/vmlinuz root=\/dev\/vda2/vmlinuz root=PARTUUID=$ROOTA_PARTUUID/g" "$grub_path" + sed -i "s/vmlinuz root=\/dev\/vda3/vmlinuz root=PARTUUID=$ROOTB_PARTUUID/g" "$grub_path" +}} +"#; + +pub const SET_PARTUUID_LEGACY: &str = r#"DISK_ID=$(sfdisk --disk-id "${SCRIPTS_DIR}"/system.img | tr '[:upper:]' '[:lower:]' | cut -c3-) + ROOTA_PARTUUID="${DISK_ID}-02" + ROOTB_PARTUUID="${DISK_ID}-03""#; + +pub const SET_PARTUUID_EFI: &str = r#"ROOTA_PARTUUID=$(sfdisk --part-uuid "${SCRIPTS_DIR}"/system.img 2 | tr '[:upper:]' '[:lower:]') + ROOTB_PARTUUID=$(sfdisk --part-uuid "${SCRIPTS_DIR}"/system.img 3 | tr '[:upper:]' '[:lower:]')"#; + +pub const CREATE_IMAGE: &str = r#"function create_img() {{ + rm -f "${{SCRIPTS_DIR}}"/system.img + qemu-img create "${{SCRIPTS_DIR}}/system.img" {IMG_SIZE}G + {PARTITIONS} + local device + device=$(losetup -f) + losetup "${{device}}" "${{SCRIPTS_DIR}}"/system.img + mkdir -p "${{TMP_MOUNT_PATH}}" + + init_part "${{SCRIPTS_DIR}}"/system.img2 ROOT-A "${{TMP_MOUNT_PATH}}" + mkdir -p "${{BOOT_PATH}}" + chmod 755 "${{BOOT_PATH}}" + {INIT_BOOT} + tar -x -C "${{TMP_MOUNT_PATH}}" -f "${{SCRIPTS_DIR}}"/os.tar + {SET_PARTUUID} + sync + cp "${{SCRIPTS_DIR}}"/bootloader.sh "${{TMP_MOUNT_PATH}}" + mount_proc_dev_sys "${{TMP_MOUNT_PATH}}" + DEVICE="${{device}}" chroot "${{TMP_MOUNT_PATH}}" bash bootloader.sh + rm -rf "${{TMP_MOUNT_PATH}}"/bootloader.sh + sync + unmount_dir "${{TMP_MOUNT_PATH}}" + + {INIT_ROOTB} + umount "${{TMP_MOUNT_PATH}}" + + {INIT_PERSIST} +{MKDIR_PERSIST} + mkdir "${{TMP_MOUNT_PATH}}"/{{var,etc,etcwork,opt,optwork}} + mkdir -p "${{TMP_MOUNT_PATH}}"/etc/KubeOS/certs + umount "${{TMP_MOUNT_PATH}}" + + losetup -D + parted "${{SCRIPTS_DIR}}"/system.img -- set 1 boot on + {DMV_MAIN} + qemu-img convert "${{SCRIPTS_DIR}}"/system.img -O qcow2 "${{SCRIPTS_DIR}}"/system.qcow2 +}} +"#; + +pub const CREATE_OS_TAR_FROM_REPO: &str = r#"function create_os_tar_from_repo() { + install_packages + install_misc + unmount_dir "${RPM_ROOT}" + tar -C "${RPM_ROOT}" -cf "${SCRIPTS_DIR}"/os.tar . + cp "${SCRIPTS_DIR}"/os.tar "${SCRIPTS_DIR}"/kubeos.tar +} +"#; + +pub const CREATE_OS_TAR_FROM_DOCKER: &str = r#"function create_os_tar_from_docker() { + container_id=$(docker create "${DOCKER_IMG}") + echo "$container_id" + docker cp "$container_id":/os.tar "${SCRIPTS_DIR}" + docker rm "$container_id" +} +"#; + +pub const CREATE_VM_REPO_IMAGE: &str = r#"function create_vm_repo_img() { + create_os_tar_from_repo + create_img +} + +test_lock +trap clean_space EXIT +trap clean_img ERR + +create_vm_repo_img"#; + +pub const CREATE_PXE_REPO_IMAGE: &str = r#"function create_pxe_repo_img() { + rm -rf "${SCRIPTS_DIR}"/initramfs.img "${SCRIPTS_DIR}"/kubeos.tar + create_os_tar_from_repo + tar -xvf "${SCRIPTS_DIR}"/os.tar -C "${SCRIPTS_DIR}" ./initramfs.img + mv "${SCRIPTS_DIR}"/os.tar "${SCRIPTS_DIR}"/kubeos.tar +} + +test_lock +trap clean_space EXIT +trap clean_img ERR + +create_pxe_repo_img"#; + +pub const CREATE_DOCKER_IMAGE: &str = r#"function create_docker_img() { + create_os_tar_from_repo + docker build -t "${DOCKER_IMG}" -f "${SCRIPTS_DIR}"/Dockerfile "${SCRIPTS_DIR}" +} + +test_lock +trap clean_space EXIT +trap clean_img ERR + +create_docker_img"#; + +pub const CREATE_VM_DOCKER_IMAGE: &str = r#"function create_vm_docker_img() { + create_os_tar_from_docker + create_img +} + +test_lock +trap clean_space EXIT +trap clean_img ERR + +create_vm_docker_img"#; + +pub const CREATE_PXE_DOCKER_IMAGE: &str = r#"function create_pxe_docker_img() { + rm -rf "${SCRIPTS_DIR}"/initramfs.img "${SCRIPTS_DIR}"/kubeos.tar + create_os_tar_from_docker + tar -xvf "${SCRIPTS_DIR}"/os.tar -C "${SCRIPTS_DIR}" ./initramfs.img + mv "${SCRIPTS_DIR}"/os.tar "${SCRIPTS_DIR}"/kubeos.tar +} + +test_lock +trap clean_space EXIT +trap clean_img ERR + +create_pxe_docker_img"#; + +pub const CREATE_ADMIN_IMAGE: &str = r#"function create_admin_img() { + cp "${HOSTSHELL}" "${ADMIN_CONTAINER_DIR}"/hostshell + docker build -t "${DOCKER_IMG}" -f "${DOCKERFILE}" "${ADMIN_CONTAINER_DIR}" +} + +test_lock +trap 'rm -f "${ADMIN_CONTAINER_DIR}"/hostshell;rm -f "${LOCK}"' EXIT +trap 'rm -f "${ADMIN_CONTAINER_DIR}"/hostshell;rm -f "${LOCK}"' ERR + +create_admin_img"#; + +pub const BOOT_LOADER_LEGACY: &str = r#" +GRUBNAME=$(which grub2-install) +echo "Installing GRUB2..." +GRUB_OPTS=${GRUB_OPTS:-"--force"} +GRUB_OPTS="$GRUB_OPTS --target=i386-pc" +# shellcheck disable=SC2086 +$GRUBNAME --modules="biosdisk part_msdos" $GRUB_OPTS "$DEVICE""#; + +pub const BOOT_LOADER_X86_UEFI: &str = r#" +cp -r /usr/lib/grub/x86_64-efi boot/efi/EFI/openEuler +eval "grub2-mkimage -d /usr/lib/grub/x86_64-efi -O x86_64-efi --output=/boot/efi/EFI/openEuler/grubx64.efi '--prefix=(,gpt1)/EFI/openEuler' fat part_gpt part_msdos linux" +mkdir -p /boot/efi/EFI/BOOT/ +cp -f /boot/efi/EFI/openEuler/grubx64.efi /boot/efi/EFI/BOOT/BOOTX64.EFI"#; + +pub const BOOT_LOADER_AARCH64: &str = r#" +cp -r /usr/lib/grub/arm64-efi /boot/efi/EFI/openEuler/ +eval "grub2-mkimage -d /usr/lib/grub/arm64-efi -O arm64-efi --output=/boot/efi/EFI/openEuler/grubaa64.efi '--prefix=(,gpt1)/EFI/openEuler' fat part_gpt part_msdos linux" +mkdir -p /boot/efi/EFI/BOOT/ +cp -f /boot/efi/EFI/openEuler/grubaa64.efi /boot/efi/EFI/BOOT/BOOTAA64.EFI"#; + +pub const MODULE_SETUP: &str = r#"check() { + return 0 +} + +depends() { + echo systemd +} + +install() { + inst_multiple -o grub2-mkimage mkfs.ext4 mkfs.vfat lsblk tar cpio gunzip lspci parted dhclient ifconfig curl hwinfo head tee arch df awk route + inst_hook mount 00 "$moddir/mount.sh" + inst_simple "$moddir/mount.sh" "/mount.sh" +} + +installkernel() { + hostonly='' instmods =drivers/ata =drivers/nvme =drivers/scsi =drivers/net =fs/fat =fs/nls +}"#; + +pub const BOOTUP_MOUNT_1: &str = r#"arch=$(arch) +min_size=8 +log=/install.log + +function CheckSpace() { + local disk_ava + disk_ava="$(parted -l | grep "${disk}" | awk '{print $3}')" + if echo "${disk_ava}" | grep "[GT]B$"; then + if echo "${disk_ava}" | grep "GB$"; then + disk_ava="$(echo "${disk_ava}" | awk -F G '{print $1}' | awk -F . '{print $1}')" + if [ "${disk_ava}" -lt ${min_size} ]; then + echo "The available disk space is not enough, at least ${min_size}GB." | tee -a ${log} + return 1 + fi + fi + else + echo "The available disk space is not enough, at least ${min_size}G." | tee -a ${log} + return 1 + fi + + return 0 +} + +function mount_proc_dev_sys() { + local tmp_root=$1 + mount -t proc none "${tmp_root}/proc" + mount --bind /dev "${tmp_root}/dev" + mount --bind /dev/pts "${tmp_root}/dev/pts" + mount -t sysfs none "${tmp_root}/sys" +} + +function GetDisk() { + mapfile -t disks < <(hwinfo --disk --short 2>&1 | grep -vi "^disk" | awk '{print $1}') + if [ ${#disks[*]} -gt 0 ]; then + if [ -n "${disk}" ] && echo "${disks[@]}" | grep -wq "${disk}" ; then + echo "${disk} exists, start partition" | tee -a ${log} + else + echo "disk not exist, please choose correct disk" | tee -a ${log} + fi + else + echo "no disk found" | tee -a ${log} + return 1 + fi + if ! CheckSpace; then + echo "no enough space on ${disk}" | tee -a ${log} + return 1 + fi + + return 0 +} +"#; + +pub const BOOTUP_MOUNT_2: &str = r#"function MountRoot() { + echo "Mounting rootfs..." + # mount rootfs + mount "${disk}2" /sysroot >> "${log}" 2>&1 + if ! mount "${disk}2" /sysroot >> "${log}" 2>&1; then + echo "mount rootfs failed" | tee -a "${log}" + return 1 + fi + + return 0 +} + +function MountBoot() { + echo "Mounting boot" + mkdir -p /sysroot/boot/efi + mount "${disk}1" /sysroot/boot/efi >> "${log}" 2>&1 + if ! mount "${disk}1" /sysroot/boot/efi >> "${log}" 2>&1; then + echo "mount boot failed" | tee -a "${log}" + return 1 + fi + return 0 +} + +function GetRootfs() { + echo "Downloading rootfs..." + + curl -o /${rootfs_name} http://${server_ip}/${rootfs_name} + if [ ! -e "/${rootfs_name}" ]; then + echo "download rootfs failed" | tee -a ${log} + return 1 + fi + + if ! tar -xf /${rootfs_name} -C /sysroot; then + echo "decompose rootfs failed" | tee -a ${log} + return 1 + fi + + rm -rf "/${rootfs_name:?}" + mount -o remount,ro ${disk}2 /sysroot >> ${log} 2>&1 + return 0 +} + +function Inst_Grub2_x86() { + # copy the files that boot need + cp -r /sysroot/usr/lib/grub/x86_64-efi /sysroot/boot/efi/EFI/openEuler + if ! eval "grub2-mkimage -d /sysroot/usr/lib/grub/x86_64-efi -O x86_64-efi --output=/sysroot/boot/efi/EFI/openEuler/grubx64.efi '--prefix=(,gpt1)/EFI/openEuler' fat part_gpt part_msdos linux" >> ${log} 2>&1; then + echo "grub2-mkimage on x86 failed" | tee -a ${log} + return 1 + fi + + mkdir -p /sysroot/boot/efi/EFI/BOOT/ + cp -f /sysroot/boot/efi/EFI/openEuler/grubx64.efi /sysroot/boot/efi/EFI/BOOT/BOOTX64.EFI + + return 0 +} + +function Inst_Grub2_aarch64() { + cp -r /sysroot/usr/lib/grub/arm64-efi /sysroot/boot/efi/EFI/openEuler/ + eval "grub2-mkimage -d /sysroot/usr/lib/grub/arm64-efi -O arm64-efi --output=/sysroot/boot/efi/EFI/openEuler/grubaa64.efi '--prefix=(,gpt1)/EFI/openEuler' fat part_gpt part_msdos linux" >> ${log} 2>&1 + if ! eval "grub2-mkimage -d /sysroot/usr/lib/grub/arm64-efi -O arm64-efi --output=/sysroot/boot/efi/EFI/openEuler/grubaa64.efi '--prefix=(,gpt1)/EFI/openEuler' fat part_gpt part_msdos linux" >> ${log} 2>&1; then + echo "grub2-mkimage on aarch64 failed" | tee -a ${log} + return 1 + fi + mkdir -p /sysroot/boot/efi/EFI/BOOT/ + cp -f /sysroot/boot/efi/EFI/openEuler/grubaa64.efi /sysroot/boot/efi/EFI/BOOT/BOOTAA64.EFI + + return 0 +} + +function set_partuuid() { + grub_path="/sysroot/boot/efi/EFI/openEuler/grub.cfg" + sed -i "s/vmlinuz root=\/dev\/vda2/vmlinuz root=PARTUUID=$ROOTA_PARTUUID/g" "$grub_path" + sed -i "s/vmlinuz root=\/dev\/vda3/vmlinuz root=PARTUUID=$ROOTB_PARTUUID/g" "$grub_path" + return 0 +} + +function SetBoot() { + # mount boot + echo "Setting boot" + + if ! set_partuuid; then + echo "set partuuid failed" | tee -a "${log}" + return 1 + fi + + if [ "$arch" == "x86_64" ]; then + if ! Inst_Grub2_x86; then + echo "install grub on x86 failed" | tee -a "${log}" + return 1 + fi + fi + + if [ "$arch" == "aarch64" ]; then + if ! Inst_Grub2_aarch64; then + echo "install grub on aarch64 failed" | tee -a "${log}" + return 1 + fi + fi + + return 0 +} + +function Bootup_Main() { + # get disk + echo "Checking disk info..." | tee -a "${log}" + if ! GetDisk; then + echo "Checking disk info failed" | tee -a "${log}" + return 1 + fi + + # partition and format disk + echo "Partion and formatting..." | tee -a "${log}" + if ! PartitionAndFormatting; then + echo "Partition and formatting disk failed" | tee -a "${log}" + return 1 + fi + + # init network + echo "Initializing network..." | tee -a "${log}" + if ! InitNetwork; then + echo "Initializing network failed" | tee -a "${log}" + return 1 + fi + + # mount partitions + echo "Mounting root..." | tee -a "${log}" + if ! MountRoot; then + echo "Mounting root failed" | tee -a "${log}" + return 1 + fi + + # mount boot + echo "Mounting boot..." | tee -a "${log}" + if ! MountBoot; then + echo "Mounting boot failed" | tee -a "${log}" + return 1 + fi + + # download rootfs + echo "Downloading rootfs..." | tee -a "${log}" + if ! GetRootfs; then + echo "Downloading rootfs failed" | tee -a "${log}" + return 1 + fi + mount_proc_dev_sys /sysroot + # set boot + echo "Setting boot..." | tee -a "${log}" + if ! SetBoot; then + echo "Setting boot failed" | tee -a "${log}" + return 1 + fi + # mount persist + echo "Mounting persist..." | tee -a "${log}" + if ! MountPersist; then + echo "Mounting persist failed" | tee -a "${log}" + return 1 + fi + return 0 +} + +Bootup_Main +ret=$? +if [ ${ret} -eq 0 ]; then + echo "KubeOS is installed successfully! Switch to root." | tee -a ${log} + cp ${log} /sysroot/persist +else + echo "Failed to install KubeOS, please check install.log." | tee -a ${log} +fi"#; + +pub const INIT_NETWORK_PARTITION: &str = r#"function PartitionAndFormatting() {{ + echo "Partitioning and formatting disk $disk..." + # partition and format + parted "${{disk}}" -s mklabel gpt >> "${{log}}" 2>&1 + if ! parted "${{disk}}" -s mklabel gpt >> "${{log}}" 2>&1; then + echo "partition failed" | tee -a "${{log}}" + return 1 + fi + + if ! parted "${{disk}}" -s mkpart primary fat16 1MiB {PARTITION1_SIZE}MiB >> "${{log}}" 2>&1; then + echo "partition failed" | tee -a "${{log}}" + return 1 + fi + + if ! parted "${{disk}}" -s mkpart primary ext4 {PARTITION1_SIZE}MiB {PARTITION2_SIZE}MiB >> "${{log}}" 2>&1; then + echo "partition failed" | tee -a "${{log}}" + return 1 + fi + + if ! parted "${{disk}}" -s mkpart primary ext4 {PARTITION2_SIZE}MiB {PARTITION3_SIZE}MiB >> "${{log}}" 2>&1; then + echo "partition failed" | tee -a "${{log}}" + return 1 + fi + + if ! parted "${{disk}}" -s mkpart primary ext4 {PARTITION3_SIZE}MiB 100% >> "${{log}}" 2>&1; then + echo "partition failed" | tee -a "${{log}}" + return 1 + fi + + if ! parted "${{disk}}" -s set 1 boot on >> "${{log}}" 2>&1; then + echo "partition failed" | tee -a "${{log}}" + return 1 + fi + + if ! mkfs.vfat -n "BOOT" "${{disk}}1" >> "${{log}}" 2>&1; then + echo "format failed" | tee -a "${{log}}" + return 1 + fi + + if ! mkfs.ext4 -L "ROOT-A" "${{disk}}2" >> "${{log}}" 2>&1; then + echo "format failed" | tee -a "${{log}}" + return 1 + fi + + if ! mkfs.ext4 -L "ROOT-B" "${{disk}}3" >> "${{log}}" 2>&1; then + echo "format failed" | tee -a "${{log}}" + return 1 + fi + + if ! mkfs.ext4 -L "PERSIST" "${{disk}}4" >> "${{log}}" 2>&1; then + echo "format failed" | tee -a "${{log}}" + return 1 + fi + + ROOTA_PARTUUID=$(blkid "${{disk}}2" | awk -F 'PARTUUID="' '{{print $2}}' | awk -F '"' '{{print $1}}') + ROOTB_PARTUUID=$(blkid "${{disk}}3" | awk -F 'PARTUUID="' '{{print $2}}' | awk -F '"' '{{print $1}}') + + return 0 +}} + +function InitNetwork() {{ + echo "Initializing network..." + mapfile -t netNames < <(ifconfig -a | awk '{{print $1}}' | grep : | grep '^e' | awk -F: '{{print $1}}') + {MANUAL_GET_IF_NAME} + + for netif in "${{netNames[@]}}";do + echo "Setup ${{netif}} link up" + if ! ifconfig "${{netif}}" up; then + echo "load ${{netif}} net card failed" | tee -a ${{log}} + continue + fi + done + sleep 3 + + {SET_IP} + sleep 3 + + if ! route add default gw "${{route_ip}}" >> "${{log}}" 2>&1; then + echo "add route failed" | tee -a "${{log}}" + return 1 + fi + sleep 3 + return 0 +}} + +function MountPersist() {{ + echo "Mounting persist" + mount "${{disk}}4" /sysroot/persist >> "${{log}}" 2>&1 + if ! mount "${{disk}}4" /sysroot/persist >> "${{log}}" 2>&1; then + echo "mount persist failed" | tee -a "${{log}}" + return 1 + fi + {MKDIR_COMMAND} + mkdir -p /sysroot/persist/etc/KubeOS/certs + return 0 +}} +"#; + +pub const MANUAL_GET_IF_NAME: &str = r#" + if [ ${#netNames[*]} -gt 0 ]; then + if [ -n "${net_name}" ] && echo "${netNames[@]}" | grep -wq "${net_name}" ; then + echo "${net_name} exists, start set ip" | tee -a "${log}" + else + echo "net_name not exist, choose default net" | tee -a "${log}" + net_name=${netNames[0]} + fi + else + echo "no net Device found" | tee -a "${log}" + return 1 + fi"#; + +pub const DHCP_SET_IP: &str = r#"mkdir -p /var/lib/dhclient + cat > "${dhcs}" <> "${log}" 2>&1; then + echo "dhcp setup ip address failed" | tee -a "${log}" + return 1 + fi + fi +"#; + +pub const MANUAL_SET_IP: &str = r#" + if ! ifconfig "${net_name}" "${local_ip}" netmask "${netmask}" >> "${log}" 2>&1; then + echo "ip set failed" | tee -a "${log}" + return 1 + fi +"#; + +pub const OS_TAR_DOCKERFILE: &str = r#"FROM scratch +COPY os.tar / +CMD ["/bin/sh"]"#; + +pub const DMV_DOCKERFILE: &str = r#"FROM scratch +COPY ./update-boot.img ./update-hash.img ./update-root.img / +CMD ["/bin/sh"]"#; + +pub const ADMIN_DOCKERFILE_CONTENT: &str = r#"FROM openeuler/openeuler:24.03-lts +RUN dnf upgrade -y && dnf -y install openssh-clients util-linux sysmaster +COPY ./set-ssh-pub-key.sh ./hostshell /usr/local/bin +COPY ./set-ssh-pub-key.service /usr/lib/sysmaster/system +EXPOSE 22 +RUN ln -s /usr/lib/sysmaster/system/set-ssh-pub-key.service /etc/sysmaster/system/multi-user.target.wants/set-ssh-pub-key.service +CMD ["/usr/lib/sysmaster/init"]"#; + +pub const SET_SSH_PUB_KEY_SERVICE: &str = r#"[Unit] +Description=set ssh authorized keys according to the secret which is set by user + +[Service] +ExecStart=/usr/local/bin/set-ssh-pub-key.sh"#; + +pub const SET_SSH_PUB_KEY_SH: &str = r#"ssh_pub=$(cat /etc/secret-volume/ssh-pub-key) +ssh_dir="/root/.ssh" +authorized_file="$ssh_dir/authorized_keys" + +if [ ! -d "$ssh_dir" ]; then + mkdir "$ssh_dir" + chmod 700 "$ssh_dir" +fi + +if [ ! -f "$authorized_file" ]; then + touch "$authorized_file" + chmod 600 "$authorized_file" +fi + +echo "$ssh_pub" >> "$authorized_file""#; + +pub const DMV_MOUNT_SH: &str = r#"set -x +export PATH=/bin:/sbin:/usr/bin:/usr/sbin + +echo "create dm-verity device for rootfs..." + +roothash= + +function parse_kernel_args() { + CMDLINE=$(cat /proc/cmdline) + for param in $CMDLINE; do + case "${param}" in + root=*) + root_device="${param}" + echo "${root_device}" + ;; + dmvroothash=*) + roothash=$(echo "${param}" | cut -d'=' -f2 | tr -d '\n') + echo "${roothash}" + ;; + esac + done +} + +function try_another() { + bootres=$(efibootmgr) + bootcurrent=$(echo "$bootres" | grep "BootCurrent" | cut -d ':' -f2 | tr -d ' ') + currentposition=$(echo "$bootres" | grep "Boot$bootcurrent" | cut -d '(' -f2 | cut -b 1) + + if [ "$currentposition" = "1" ]; then + another=4 + elif [ "$currentposition" = "4" ]; then + another=1 + else + echo "reboot" + fi + + exist=$(echo "$bootres" | grep "$another,GPT") + if [ "$exist" = "" ]; then + arch=$(arch) + if [ "$arch" = "x86_64" ]; then + efibootmgr -c -d "/dev/${DEVprefix}" -p "$another" -l "\EFI\openEuler\shimx64.efi" + elif [ "$arch" = "aarch64" ]; then + efibootmgr -c -d "/dev/${DEVprefix}" -p "$another" -l "\EFI\openEuler\shimaa64.efi" + else + echo "$arch not support" + reboot + fi + fi + anotherbootNum=$(efibootmgr | grep "$another,GPT" | cut -b 5-8) + + efibootmgr -o "$anotherbootNum,$bootcurrent" + reboot +} + +parse_kernel_args + +root1=$(echo "${root_device}" | cut -d'=' -f2) +root2=$(echo "${root_device}" | cut -d'=' -f3) +final="" +DEVprefix="vda" +hwinfo --disk + +if [ "${root2}" = "" ]; then + final=${root1: -1} + DEVprefix=$(echo "$root1" | cut -d'/' -f3 | sed 's/[^a-z]//g') +elif [ "${root1}" = "LABEL" ] && [ "${root2}" != "" ]; then + root3=$(readlink "/dev/disk/by-label/$root2") + final=${root3: -1} + ## shellcheck disable=SC2001 + DEVprefix=$(echo "$root3" | sed 's/[^a-z]//g') +elif [ "${root1}" = "UUID" ] && [ "${root2}" != "" ]; then + root3=$(readlink "/dev/disk/by-uuid/$root2") + final=${root3: -1} + DEVprefix=$(echo "$root3" | sed 's/[^a-z]//g') +elif [ "${root1}" = "PARTUUID" ] && [ "${root2}" != "" ]; then + root3=$(readlink "/dev/disk/by-partuuid/$root2") + final=${root3: -1} + DEVprefix=$(echo "$root3" | sed 's/[^a-z]//g') +else + echo "${root_device}: root-device identifier error, parse failed" +fi + +hashpart=$(echo "${final}+1" | bc) +veritysetup create kubeos-root "/dev/${DEVprefix}${final}" "/dev/${DEVprefix}${hashpart}" "${roothash}" +dmvstatus=$(veritysetup status kubeos-root | grep "status:" | cut -d : -f2 | tr -d " ") +if [ "$dmvstatus" = "verified" ]; then + echo "dm-verity verify success! switch to kubeos-root...." + mount /dev/mapper/kubeos-root /sysroot +else + try_another +fi + +if [ $? -ne 0 ]; then + echo "mount rootfs failed" + reboot +fi"#; + +pub const DMV_MODULE_SETUP_SH: &str = r#"check() { + return 0 +} + +install() { + inst_multiple -o grub2-mkimage fdisk cpio veritysetup cut awk efibootmgr modprobe arch bc hwinfo partprobe + inst_hook pre-mount 05 "$moddir/dmv-mount.sh" +} + +installkernel() { + hostonly='' \ + instmods \ + =drivers/scsi \ + =drivers/virtio \ + =drivers/block +}"#; + +pub const DMV_CHROOT_NEW_GRUB_SH: &str = r#"set -x +WORKDIR="/tmp4grub" + +function create_new_grubxx_efi() { + + ARCH=$(arch) + if [ "$ARCH" == "x86_64" ]; then + MODULES="all_video boot btrfs cat configfile cryptodisk echo efifwsetup efinet ext2 f2fs fat font gcry_rijndael gcry_rsa gcry_serpent gcry_sha256 gcry_sha512 gcry_twofish gcry_whirlpool gfxmenu gfxterm gzio halt hfsplus http iso9660 jpeg loadenv loopback linux lvm lsefi lsefimmap luks luks2 mdraid09 mdraid1x minicmd net normal part_apple part_msdos part_gpt password_pbkdf2 pgp png reboot regexp search search_fs_uuid search_fs_file search_label serial sleep syslinuxcfg test tftp video xfs zstd tpm backtrace chain usb usbserial_common usbserial_pl2303 usbserial_ftdi usbserial_usbdebug keylayouts at_keyboard" + grub2-mkimage -d /usr/lib/grub/x86_64-efi -O x86_64-efi -p /EFI/openEuler --pubkey "${WORKDIR}/gpg.key" --output "${WORKDIR}/grubx64.efi" -c "${WORKDIR}"/grub.init.cfg --sbat "${WORKDIR}/sbat.csv" $MODULES 2>&1 | tee "${WORKDIR}/mkimage.log" + fi + + if [ "$ARCH" == "aarch64" ]; then + MODULES="all_video boot btrfs cat configfile cryptodisk echo efifwsetup efinet ext2 f2fs fat font gcry_rijndael gcry_rsa gcry_serpent gcry_sha256 gcry_sha512 gcry_twofish gcry_whirlpool gfxmenu gfxterm gzio halt hfsplus http iso9660 jpeg loadenv loopback linux lvm lsefi lsefimmap luks luks2 mdraid09 mdraid1x minicmd net normal part_apple part_msdos part_gpt password_pbkdf2 pgp png reboot regexp search search_fs_uuid search_fs_file search_label serial sleep syslinuxcfg test tftp video xfs zstd tpm" + grub2-mkimage -d /usr/lib/grub/arm64-efi -O arm64-efi -p /EFI/openEuler --pubkey "${WORKDIR}/gpg.key" --output "${WORKDIR}/grubaa64.efi" -c "${WORKDIR}/grub.init.cfg" --sbat "${WORKDIR}/sbat.csv" $MODULES 2>&1 | tee "${WORKDIR}/mkimage.log" + fi + + if [ $? -ne 0 ]; then + echo "create grubxx.efi failed" + return 7 + fi +} + +create_new_grubxx_efi"#; + +pub const DMV_MAIN_SH: &str = r#"set -e + +CURDIR=$(cd "$(dirname $0)";pwd) +PWDDD=$(cd "$CURDIR/"; pwd) +KEYDIR="$PWDDD/dm-verity/keys" +CERTDB="$KEYDIR/certdb" +BIOSkeyname="rsa4BIOS" +WORKDIR="$PWDDD/dm-verity/tmp" +GPGkeyid="gpgKey4kubeos" +GPG_KEY="" + +function keys_exist() { + keyExist=True + if [ ! -d "${KEYDIR}" ]; then + keyExist=False + mkdir -p "${KEYDIR}" + else + for file in "${BIOSkeyname}.der" "gpg.key" "gpg.log" + do + if [ ! "$(find "${KEYDIR}" -name "${file}")" ]; then + keyExist=False + break + fi + done + if [ ! -d "${CERTDB}" ]; then + keyExist=False + fi + fi + echo ${keyExist} +} + +function gpg_key_gen() { + + GPG_PASSWORD=$1 + + id=$(gpg --list-keys | grep "${GPGkeyid}" | awk '{ print $3 }') + if [ "$id" == ${GPGkeyid} ];then + fgpt=$(gpg --with-colons --fingerprint gpgKey4kubeos | grep -m 1 "^fpr" | sed -n 's/^fpr:::::::::\([[:alnum:]]\+\):/\1/p') + gpg --batch --yes --delete-secret-keys "$fgpt" + gpg --batch --yes --delete-keys "$fgpt" + fi + + cat > "${KEYDIR}/gpg.batch.file" << EOF +Key-Type: RSA +Key-Length: 4096 +Subkey-Type: RSA +Subkey-Length: 4096 +Name-Real: ${GPGkeyid} +Expire-Date: 0 +Passphrase: ${GPG_PASSWORD} +EOF + + gpg --batch --gen-key "${KEYDIR}/gpg.batch.file" + gpg --list-keys --keyid-format LONG ${GPGkeyid} | grep pub > "${KEYDIR}/gpg.log" + GPG_KEY=$(gpg --list-keys --keyid-format LONG ${GPGkeyid} | grep pub | awk -F 'rsa4096/' '{print $2}' | cut -b 1-16) + if [ "$GPG_KEY" = "" ]; then + echo "GPG-key-gen ID failed" + return 7 + fi + gpg --export "$GPG_KEY" > "${KEYDIR}/gpg.key" + rm -f "${KEYDIR}/gpg.batch.file" + if [ $? -ne 0 ]; then + echo "GPG-key-gen failed" + return 7 + fi +} + +function BIOS_key_gen() { + + PIN_PASSWORD=$1 + keyname=$BIOSkeyname + + if [ -d "${CERTDB}" ]; then + rm -rf "${CERTDB}" + fi + mkdir -p "${CERTDB}" + cat > "${KEYDIR}/pinfile" << EOF +$PIN_PASSWORD +EOF + + openssl genrsa -out "${KEYDIR}/${keyname}.key" 4096 + openssl req -new -key "${KEYDIR}/${keyname}.key" -out "${KEYDIR}/${keyname}.csr" -subj '/C=AA/ST=BB/O=CC/OU=DD/CN=BIOS-cert-for-kubeos-secure-boot' + openssl x509 -req -days 365 -in "${KEYDIR}/${keyname}.csr" -signkey "${KEYDIR}/${keyname}.key" -out "${KEYDIR}/${keyname}.crt" + openssl x509 -in "${KEYDIR}/${keyname}.crt" -out "${KEYDIR}/${keyname}.der" -outform der + + certutil -N -d "${CERTDB}" -f "${KEYDIR}/pinfile" + certutil -A -n ${keyname} -d "${CERTDB}" -t CT,CT,CT -i "${KEYDIR}/${keyname}.crt" -f "${KEYDIR}/pinfile" + openssl pkcs12 -export -out "${KEYDIR}/${keyname}.p12" -inkey "${KEYDIR}/${keyname}.key" -in "${KEYDIR}/${keyname}.crt" -password pass:"${PIN_PASSWORD}" + pk12util -d "${CERTDB}" -i "${KEYDIR}/${keyname}.p12" -w "${KEYDIR}/pinfile" -k "${KEYDIR}/pinfile" + + rm -f "${KEYDIR}/pinfile" + rm -f "${KEYDIR}/${keyname}.p12" + rm -f "${KEYDIR}/${keyname}.crt" + rm -f "${KEYDIR}/${keyname}.csr" + rm -f "${KEYDIR}/${keyname}.key" + + if [ $? -ne 0 ]; then + echo "BIOS-key-gen failed" + return 7 + fi +} + +function create_new_grubxx_efi() { + + GRUB_PASSWORD=$2 + GPG_PASSWORD=$1 + GRUB_version=2.06 + + GRUB_PASSWORD_HASH=$(echo -e "$GRUB_PASSWORD\n$GRUB_PASSWORD" | grub2-mkpasswd-pbkdf2 | grep -o "grub.*") + + loopN=$(losetup -f) + losetup -P "$loopN" "${PWDDD}/system.img" + bootUUID=$(blkid | grep "${loopN}p1" | awk -F ' UUID=' '{print $2}' | cut -b 2-10) + losetup -d "${loopN}" + + cat > "${WORKDIR}/grub.init.cfg" << EOF +#set debug=linux,linuxefi,crypt +#export debug +set check_signatures=enforce +export check_signatures +set superusers=root +export superusers +set prefix='/EFI/openEuler' +export prefix +password_pbkdf2 root $GRUB_PASSWORD_HASH +set root='hd0,gpt1' +search --no-floppy --fs-uuid --set=root $bootUUID +echo "now in grub-init......1....." +configfile /EFI/openEuler/grub.cfg +echo /EFI/openEuler/grub.cfg did not boot the system, rebooting the system in 10 seconds.. +sleep 10 +reboot +EOF + + cat > "${WORKDIR}/sbat.csv" << EOF +sbat,1,SBAT Version,sbat,1,https://github.com/rhboot/shim/blob/main/SBAT.md +grub,4,Free Software Foundation,grub,$GRUB_version,https//www.gnu.org/software/grub/ +grub.openeuler,1,The openEuler Project,grub2,$GRUB_version-0,https://gitee.com/src-openeuler/grub2 +EOF + + gpg --pinentry-mode=loopback --passphrase "${GPG_PASSWORD}" --default-key "$GPG_KEY" --detach-sign "${WORKDIR}/grub.init.cfg" + + if [ $? -ne 0 ]; then + echo "prepare new grub files failed" + return 7 + fi +} + +function sign_efi_imgs() { + + PIN_PASSWORD=$1 + GRUB_PASSWORD2=$3 + GPG_PASSWORD=$2 + cat > "${WORKDIR}/pinfile" << EOF +$PIN_PASSWORD +EOF + + tmpRoot="${WORKDIR}/tmproot" + tmpBoot="${WORKDIR}/tmpboot" + mkdir -p "${tmpRoot}" + mkdir -p "${tmpBoot}" + + loopX=$(losetup -f) + losetup -P "${loopX}" "$PWDDD/system.img" + mount "${loopX}p1" "$tmpBoot" + mount "${loopX}p2" "$tmpRoot" + + ARCH=$(arch) + suffix= + if [ "$ARCH" == "x86_64" ]; then + suffix="x64.efi" + elif [ "$ARCH" == "aarch64" ]; then + suffix="aa64.efi" + else + echo "ARCH $ARCH not support currently" + return 7 + fi + + mkdir -p "$tmpRoot/tmp4grub" + cp "${WORKDIR}/grub.init.cfg" "$tmpRoot/tmp4grub/grub.init.cfg" + cp "${WORKDIR}/grub.init.cfg.sig" "$tmpRoot/tmp4grub/grub.init.cfg.sig" + cp "${WORKDIR}/sbat.csv" "$tmpRoot/tmp4grub/sbat.csv" + cp "${KEYDIR}/gpg.key" "$tmpRoot/tmp4grub/gpg.key" + cp "${PWDDD}/dm-verity/chroot_new_grub.sh" "$tmpRoot/chroot_new_grub.sh" + chroot "$tmpRoot" bash /chroot_new_grub.sh + cp "$tmpRoot/tmp4grub/grub$suffix" "${WORKDIR}" + rm -rf "$tmpRoot/tmp4grub" + rm -f "$tmpRoot/chroot_new_grub.sh" + + bootuuid=$(blkid | grep "${loopX}p1" | awk -F ' UUID=' '{print $2}' | cut -b 2-10) + sed -i "s/What=\/dev\/disk\/by-label\/BOOT/What=\/dev\/disk\/by-uuid\/$bootuuid/g" "$tmpRoot/usr/lib/systemd/system/boot-efi.mount" + + if [ $? -ne 0 ]; then + echo "create grubxx.efi failed" + return 7 + fi + + IMGs="shim fb mm" + for img in $IMGs + do + /bin/cp "$tmpBoot/EFI/openEuler/$img$suffix" "${WORKDIR}" + done + + IMGs="$IMGs grub" + for img in $IMGs + do + pesign -n "${CERTDB}" -c ${BIOSkeyname} --pinfile "${WORKDIR}/pinfile" -s -i "$WORKDIR/$img$suffix" -o "${WORKDIR}/$img$suffix.signed" + /bin/cp "${WORKDIR}/$img$suffix.signed" "$tmpBoot/EFI/openEuler/$img$suffix" + done + + if [ $? -ne 0 ]; then + echo "pesign efi failed" + return 7 + fi + + /bin/cp "$PWDDD/grub.cfg" "${WORKDIR}" + if [ "$ARCH" == "x86_64" ]; then + /bin/cp "$tmpRoot/boot/vmlinuz" "${WORKDIR}" + /bin/cp "$tmpRoot/boot/initramfs-verity.img" "${WORKDIR}" + elif [ "$ARCH" == "aarch64" ]; then + /bin/cp "$tmpRoot/boot/vmlinuz" "${WORKDIR}/vmlinuz.gz" + /bin/cp "$tmpRoot/boot/initramfs-verity.img" "${WORKDIR}/initramfs-verity.img" + gzip -d "${WORKDIR}/vmlinuz.gz" + else + echo "ARCH $ARCH not support currently" + fi + + if [ $? -ne 0 ]; then + echo "copy/gzip -d failed" + return 7 + fi + + pesign -n "${CERTDB}" -c ${BIOSkeyname} --pinfile "${WORKDIR}/pinfile" -s -i "$WORKDIR/vmlinuz" -o "${WORKDIR}/vmlinuz.signed" + gpg --pinentry-mode=loopback --passphrase "${GPG_PASSWORD}" --default-key "$GPG_KEY" --detach-sign "${WORKDIR}/vmlinuz.signed" + gpg --pinentry-mode=loopback --passphrase "${GPG_PASSWORD}" --default-key "$GPG_KEY" --detach-sign "${WORKDIR}/initramfs-verity.img" + + if [ $? -ne 0 ]; then + echo "gpg sign failed" + return 7 + fi + + if [ "$ARCH" == "x86_64" ]; then + /bin/cp "${WORKDIR}/vmlinuz.signed" "$tmpRoot/boot/vmlinuz" + /bin/cp "${WORKDIR}/vmlinuz.signed.sig" "$tmpRoot/boot/vmlinuz.sig" + /bin/cp "${WORKDIR}/initramfs-verity.img" "$tmpRoot/boot/initramfs-verity.img" + /bin/cp "${WORKDIR}/initramfs-verity.img.sig" "$tmpRoot/boot/initramfs-verity.img.sig" + elif [ "$ARCH" == "aarch64" ]; then + gzip "${WORKDIR}/vmlinuz.signed" + /bin/cp "${WORKDIR}/vmlinuz.signed.gz" "$tmpRoot/boot/vmlinuz" + /bin/cp "${WORKDIR}/vmlinuz.signed.sig" "$tmpRoot/boot/vmlinuz.sig" + /bin/cp "${WORKDIR}/initramfs-verity.img" "$tmpRoot/boot/initramfs-verity.img" + /bin/cp "${WORKDIR}/initramfs-verity.img.sig" "$tmpRoot/boot/initramfs-verity.img.sig" + else + echo "ARCH $ARCH not support currently" + fi + + if [ $? -ne 0 ]; then + echo "copy/gzip back(vmlinuz/initramfs) failed" + return 7 + fi + + ssh-keygen -t rsa -f "$tmpRoot/etc/ssh/ssh_host_rsa_key" -N '' + ssh-keygen -t ecdsa -f "$tmpRoot/etc/ssh/ssh_host_ecdsa_key" -N '' + ssh-keygen -t ed25519 -f "$tmpRoot/etc/ssh/ssh_host_ed25519_key" -N '' + sync + umount "$tmpRoot" + + veritysetup format "${loopX}p2" "${loopX}p3" --root-hash-file="${WORKDIR}/roothash" + veritysetup verify "${loopX}p2" "${loopX}p3" --root-hash-file="${WORKDIR}/roothash" --debug + + dmvroothash=$(cat "${WORKDIR}/roothash") + + vzlines=$(grep -n boot/vmlinuz "${WORKDIR}/grub.cfg" | cut -d : -f1) + for ln in $vzlines + do + ori=$(sed -n "${ln}p" "${WORKDIR}/grub.cfg") + sed -i "${ln}c \ ${ori} dmvroothash=${dmvroothash}" "${WORKDIR}/grub.cfg" + done + + GRUB_PASSWORD_HASH2=$(echo -e "$GRUB_PASSWORD2\n$GRUB_PASSWORD2" | grub2-mkpasswd-pbkdf2 | grep -o "grub.*") + grub2pwhline=$(awk '/password_pbkdf2/{print NR}' "${WORKDIR}/grub.cfg") + oripwh=$(sed -n "${grub2pwhline}p" "${WORKDIR}/grub.cfg") + sed -i "${grub2pwhline}c ${oripwh} ${GRUB_PASSWORD_HASH2}" "${WORKDIR}/grub.cfg" + + gpg --pinentry-mode=loopback --passphrase "${GPG_PASSWORD}" --default-key "$GPG_KEY" --detach-sign "${WORKDIR}/grub.cfg" + + if [ $? -ne 0 ]; then + echo "modify grub.cfg failed" + return 7 + fi + + /bin/cp "${WORKDIR}/grub.cfg" "$tmpBoot/EFI/openEuler/grub.cfg" + /bin/cp "${WORKDIR}/grub.cfg.sig" "$tmpBoot/EFI/openEuler/grub.cfg.sig" + /bin/cp "$tmpBoot/EFI/openEuler/fb$suffix" "$tmpBoot/EFI/BOOT/fb$suffix" + /bin/cp "$tmpBoot/EFI/openEuler/mm$suffix" "$tmpBoot/EFI/BOOT/mm$suffix" + if [ "$ARCH" == "x86_64" ]; then + /bin/cp "$tmpBoot/EFI/openEuler/shim$suffix" "$tmpBoot/EFI/BOOT/BOOTX64.EFI" + elif [ "$ARCH" == "aarch64" ]; then + /bin/cp "$tmpBoot/EFI/openEuler/shim$suffix" "$tmpBoot/EFI/BOOT/BOOTAA64.EFI" + else + echo "ARCH $ARCH not support currently" + return 7 + fi + /bin/cp "${KEYDIR}/${BIOSkeyname}.der" "$tmpBoot/EFI/${BIOSkeyname}.der" + if [ $? -ne 0 ]; then + echo "copy back efi failed" + return 7 + fi + + sync + umount "$tmpBoot" + dd if="${loopX}p2" of="$PWDDD"/update-root.img bs=8M + dd if="${loopX}p1" of="$PWDDD"/update-boot.img bs=8M + dd if="${loopX}p3" of="$PWDDD"/update-hash.img bs=8M + losetup -D + rm -rf "${WORKDIR}" +} + +function dmvmain() { + set -e + umask 0177 + + if [ -d "${WORKDIR}" ]; then + rm -rf "${WORKDIR}" + fi + mkdir -m 750 -p "${WORKDIR}" + + BIOSpwd=$1 + GPGpwd=$2 + GRUBpwd=$3 + if [ "$(keys_exist)" == "False" ]; then + rm -rf "${KEYDIR}" + mkdir -p "${KEYDIR}" + gpg_key_gen "$GPGpwd" + BIOS_key_gen "$BIOSpwd" + fi + + GPG_KEY=$(gpg --list-keys --keyid-format LONG ${GPGkeyid} | grep pub | awk -F 'rsa4096/' '{print $2}' | cut -b 1-16) + rm -f "${KEYDIR}/gpg.key" + gpg --export "$GPG_KEY" > "${KEYDIR}/gpg.key" + create_new_grubxx_efi "$GPGpwd" "$GRUBpwd" + sign_efi_imgs "$BIOSpwd" "$GPGpwd" "$GRUBpwd" +}"#; + +pub const DMV_UPGRADE_ROLLBACK_SH: &str = r#"set -eux +if [ "$1" != "upgrade" ] && [ "$1" != "rollback" ]; then + echo "Invalid argument: $1" + exit 1 +fi + +currentROOT=$(veritysetup status kubeos-root | grep "data device" | cut -d / -f3 | tr -d " ") +currentROOTdev=$(echo "$currentROOT" | sed 's/[^a-z]//g') +currentROOTpart=${currentROOT: -1} + +if [ "$currentROOTpart" = "2" ]; then + nextBOOTpart="4" + nextROOTpart="5" + nextHASHpart="6" + grubBOOT="B" +elif [ "$currentROOTpart" = "5" ]; then + nextBOOTpart="1" + nextROOTpart="2" + nextHASHpart="3" + grubBOOT="A" +else + echo "Current ROOT [$currentROOTpart] error" + return 7 +fi + +if [ "$1" = "upgrade" ]; then + dd if=/persist/update-boot.img of=/dev/"$currentROOTdev$nextBOOTpart" bs=8M + dd if=/persist/update-root.img of=/dev/"$currentROOTdev$nextROOTpart" bs=8M + dd if=/persist/update-hash.img of=/dev/"$currentROOTdev$nextHASHpart" bs=8M + exit +fi + +mkdir -p /tmp/tmp4BOOT +mount /dev/"$currentROOTdev$nextBOOTpart" /tmp/tmp4BOOT +grub2-editenv /tmp/tmp4BOOT/EFI/openEuler/grubenv set saved_entry="$grubBOOT" +umount /tmp/tmp4BOOT + +bootres=$(efibootmgr) +exist=$(echo "$bootres" | grep "$nextBOOTpart,GPT" || true) +if [ -z "$exist" ]; then + arch=$(arch) + if [ "$arch" = "x86_64" ]; then + efibootmgr -c -d "/dev/$currentROOTdev" -p "$nextBOOTpart" -l "\EFI\openEuler\shimx64.efi" -L openEuler + elif [ "$arch" = "aarch64" ]; then + efibootmgr -c -d "/dev/$currentROOTdev" -p "$nextBOOTpart" -l "\EFI\openEuler\shimaa64.efi" -L openEuler + else + echo "$arch not support" + return 7 + fi +fi +nextbootNum=$(efibootmgr | grep "$nextBOOTpart,GPT" | cut -b 5-8) +bootcurrent=$(efibootmgr | grep "BootCurrent" | cut -d ':' -f2 | tr -d ' ') +efibootmgr -o "$nextbootNum,$bootcurrent""#; + +pub const BOOT_EFI_MOUNT: &str = r#"[Unit] +Description=grub2 Dir +DefaultDependencies=no +Conflicts=umount.target +Before=local-fs.target umount.target + +[Mount] +What=/dev/disk/by-label/BOOT +Where=/boot/efi +Type=vfat +Options=defaults + +[Install] +WantedBy=local-fs.target"#; + +pub const BOOT_GRUB2_MOUNT: &str = r#"[Unit] +Description=grub2 Dir +DefaultDependencies=no +Conflicts=umount.target +Before=local-fs.target umount.target + +[Mount] +What=/dev/disk/by-label/GRUB2 +Where=/boot/grub2 +Type=ext4 +Options=defaults + +[Install] +WantedBy=local-fs.target"#; + +pub const ETC_MOUNT: &str = r#"[Unit] +Description=etc Dir +DefaultDependencies=no +Conflicts=umount.target +Before=local-fs.target umount.target +Wants=persist.mount +After=persist.mount + +[Mount] +What=overlay +Where=/etc +Type=overlay +Options=upperdir=/persist/etc,lowerdir=/etc,workdir=/persist/etcwork + +[Install] +WantedBy=local-fs.target"#; + +pub const OPT_CNI_MOUNT: &str = r#"[Unit] +Description=opt cni Dir +DefaultDependencies=no +Conflicts=umount.target +Before=local-fs.target umount.target +Wants=persist.mount +After=persist.mount + +[Mount] +What=overlay +Where=/opt/cni +Type=overlay +Options=upperdir=/persist/opt,lowerdir=/opt/cni,workdir=/persist/optwork + +[Install] +WantedBy=local-fs.target"#; + +pub const OS_AGENT_SERVICE: &str = r#"[Unit] +Description=Agent For KubeOS + +[Service] +Environment=GOTRACEBACK=crash +ExecStart=/usr/bin/os-agent +KillMode=process +Restart=on-failure + +[Install] +WantedBy=multi-user.target"#; + +pub const PERSIST_MOUNT: &str = r#"[Unit] +Description=PERSIST Dir (/persist) +DefaultDependencies=no +Conflicts=umount.target +Before=local-fs.target umount.target + +[Mount] +What=/dev/disk/by-label/PERSIST +Where=/persist +Type=ext4 +Options=defaults + +[Install] +WantedBy=local-fs.target"#; + +pub const VAR_MOUNT: &str = r#"[Unit] +Description=var Dir +DefaultDependencies=no +Conflicts=umount.target +Before=local-fs.target umount.target +Wants=persist.mount +After=persist.mount + +[Mount] +What=/persist/var +Where=/var +Type=node +Options=bind + +[Install] +WantedBy=local-fs.target"#; + +pub const DMV_MAIN_GRUB_CFG: &str = r#"set pager=1 + +set superusers="root" +export superusers +password_pbkdf2 root + +WHITELIST="boot_success saved_entry boot_indeterminate prev_saved_entry next_entry feature_menuentry_id boot_once feature_all_video_module menu_show_once feature_timeout_style menu_auto_hide menu_hide_ok fastboot config_directory" + +if [ -f ${prefix}/grubenv ]; then + load_env -f ${prefix}/grubenv --skip-sig $WHITELIST +fi +if [ "${next_entry}" ] ; then + set default="${next_entry}" + set next_entry= + save_env next_entry + set boot_once=true +else + set default="${saved_entry}" +fi + +if [ x"${feature_menuentry_id}" = xy ]; then + menuentry_id_option="--id" +else + menuentry_id_option="" +fi + +export menuentry_id_option + +if [ "${prev_saved_entry}" ]; then + set saved_entry="${prev_saved_entry}" + save_env saved_entry + set prev_saved_entry= + save_env prev_saved_entry + set boot_once=true +fi + +terminal_output console +if [ x$feature_timeout_style = xy ] ; then + set timeout_style=menu + set timeout=5 +else + set timeout=5 +fi + +menuentry 'A' --class KubeOS --class gnu-linux --class gnu --class os --unrestricted $menuentry_id_option 'KubeOS-A' { + set gfxpayload=keep + set root='hd0,gpt2' + linux /boot/vmlinuz root=/dev/vda2 ro rootfstype=ext4 nomodeset quiet oops=panic softlockup_panic=1 nmi_watchdog=1 rd.shell=0 selinux=0 crashkernel=256M panic=3 console=ttyS0 apparmor=0 + initrd /boot/initramfs-verity.img +} + +menuentry 'B' --class KubeOS --class gnu-linux --class gnu --class os --unrestricted $menuentry_id_option 'KubeOS-B' { + set gfxpayload=keep + set root='hd0,gpt5' + linux /boot/vmlinuz root=/dev/vda5 ro rootfstype=ext4 nomodeset quiet oops=panic softlockup_panic=1 nmi_watchdog=1 rd.shell=0 selinux=0 crashkernel=256M panic=3 console=ttyS0 apparmor=0 + initrd /boot/initramfs-verity.img +} + +### END /etc/grub.d/10_linux ### + +### BEGIN /etc/grub.d/10_reset_boot_success ### +# Hiding the menu is ok if last boot was ok or if this is a first boot attempt to boot the entry +if [ "${boot_success}" = "1" -o "${boot_indeterminate}" = "1" ]; then + set menu_hide_ok=1 +else + set menu_hide_ok=0 +fi +# Reset boot_indeterminate after a successful boot +if [ "${boot_success}" = "1" ] ; then + set boot_indeterminate=0 +# Avoid boot_indeterminate causing the menu to be hidden more then once +elif [ "${boot_indeterminate}" = "1" ]; then + set boot_indeterminate=2 +fi +# Reset boot_success for current boot +set boot_success=0 +save_env boot_success boot_indeterminate +### END /etc/grub.d/10_reset_boot_success ### + +### BEGIN /etc/grub.d/12_menu_auto_hide ### +if [ x$feature_timeout_style = xy ] ; then + if [ "${menu_show_once}" ]; then + unset menu_show_once + save_env menu_show_once + set timeout_style=menu + set timeout=60 + elif [ "${menu_auto_hide}" -a "${menu_hide_ok}" = "1" ]; then + set orig_timeout_style=${timeout_style} + set orig_timeout=${timeout} + if [ "${fastboot}" = "1" ]; then + # timeout_style=menu + timeout=0 avoids the countdown code keypress check + set timeout_style=menu + set timeout=0 + else + set timeout_style=hidden + set timeout=1 + fi + fi +fi +### END /etc/grub.d/12_menu_auto_hide ### + +### BEGIN /etc/grub.d/20_linux_xen ### +### END /etc/grub.d/20_linux_xen ### + +### BEGIN /etc/grub.d/20_ppc_terminfo ### +### END /etc/grub.d/20_ppc_terminfo ### + +### BEGIN /etc/grub.d/30_uefi-firmware ### +### END /etc/grub.d/30_uefi-firmware ### + +### BEGIN /etc/grub.d/40_custom ### +# This file provides an easy way to add custom menu entries. Simply type the +# menu entries you want to add after this comment. Be careful not to change +# the 'exec tail' line above. +### END /etc/grub.d/40_custom ### + +### BEGIN /etc/grub.d/41_custom ### +if [ -f ${config_directory}/custom.cfg ]; then + source ${config_directory}/custom.cfg +elif [ -z "${config_directory}" -a -f ${prefix}/custom.cfg ]; then + source ${prefix}/custom.cfg; +fi +### END /etc/grub.d/41_custom ###"#; + +pub const GRUB_CFG_CONTENTS: &str = r#"set pager=1 + +if [ -f ${config_directory}/grubenv ]; then + load_env -f ${config_directory}/grubenv +elif [ -s $prefix/grubenv ]; then + load_env +fi +if [ "${next_entry}" ] ; then + set default="${next_entry}" + set next_entry= + save_env next_entry + set boot_once=true +else + set default="${saved_entry}" +fi + +if [ x"${feature_menuentry_id}" = xy ]; then + menuentry_id_option="--id" +else + menuentry_id_option="" +fi + +export menuentry_id_option + +if [ "${prev_saved_entry}" ]; then + set saved_entry="${prev_saved_entry}" + save_env saved_entry + set prev_saved_entry= + save_env prev_saved_entry + set boot_once=true +fi + +function savedefault { + if [ -z "${boot_once}" ]; then + saved_entry="${chosen}" + save_env saved_entry + fi +} + +function load_video { + if [ x$feature_all_video_module = xy ]; then + insmod all_video + else + insmod efi_gop + insmod efi_uga + insmod ieee1275_fb + insmod vbe + insmod vga + insmod video_bochs + insmod video_cirrus + fi +} + +terminal_output console +if [ x$feature_timeout_style = xy ] ; then + set timeout_style=menu + set timeout=5 +# Fallback normal timeout code in case the timeout_style feature is +# unavailable. +else + set timeout=5 +fi +set superusers="root" +### END /etc/grub.d/00_header ### + +### BEGIN /etc/grub.d/01_users ### +if [ -f ${prefix}/user.cfg ]; then + source ${prefix}/user.cfg + if [ -n "${GRUB2_PASSWORD}" ]; then + set superusers="root" + export superusers + password_pbkdf2 root ${GRUB2_PASSWORD} + fi +fi +### END /etc/grub.d/01_users ### + +### BEGIN /etc/grub.d/10_linux ### +menuentry 'A' --class KubeOS --class gnu-linux --class gnu --class os --unrestricted $menuentry_id_option 'KubeOS-A' { + load_video + set gfxpayload=keep + insmod gzio + insmod part_gpt + insmod ext2 + search --no-floppy --label ROOT-A --set=root + linux /boot/vmlinuz root=/dev/vda2 ro rootfstype=ext4 nomodeset quiet oops=panic softlockup_panic=1 nmi_watchdog=1 rd.shell=0 selinux=0 crashkernel=256M panic=3 + initrd /boot/initramfs.img +} + +menuentry 'B' --class KubeOS --class gnu-linux --class gnu --class os --unrestricted $menuentry_id_option 'KubeOS-B' { + load_video + set gfxpayload=keep + insmod gzio + insmod part_gpt + insmod ext2 + search --no-floppy --label ROOT-B --set=root + linux /boot/vmlinuz root=/dev/vda3 ro rootfstype=ext4 nomodeset quiet oops=panic softlockup_panic=1 nmi_watchdog=1 rd.shell=0 selinux=0 crashkernel=256M panic=3 + initrd /boot/initramfs.img +} + +### END /etc/grub.d/10_linux ### + +### BEGIN /etc/grub.d/10_reset_boot_success ### +# Hiding the menu is ok if last boot was ok or if this is a first boot attempt to boot the entry +if [ "${boot_success}" = "1" -o "${boot_indeterminate}" = "1" ]; then + set menu_hide_ok=1 +else + set menu_hide_ok=0 +fi +# Reset boot_indeterminate after a successful boot +if [ "${boot_success}" = "1" ] ; then + set boot_indeterminate=0 +# Avoid boot_indeterminate causing the menu to be hidden more then once +elif [ "${boot_indeterminate}" = "1" ]; then + set boot_indeterminate=2 +fi +# Reset boot_success for current boot +set boot_success=0 +save_env boot_success boot_indeterminate +### END /etc/grub.d/10_reset_boot_success ### + +### BEGIN /etc/grub.d/12_menu_auto_hide ### +if [ x$feature_timeout_style = xy ] ; then + if [ "${menu_show_once}" ]; then + unset menu_show_once + save_env menu_show_once + set timeout_style=menu + set timeout=60 + elif [ "${menu_auto_hide}" -a "${menu_hide_ok}" = "1" ]; then + set orig_timeout_style=${timeout_style} + set orig_timeout=${timeout} + if [ "${fastboot}" = "1" ]; then + # timeout_style=menu + timeout=0 avoids the countdown code keypress check + set timeout_style=menu + set timeout=0 + else + set timeout_style=hidden + set timeout=1 + fi + fi +fi +### END /etc/grub.d/12_menu_auto_hide ### + +### BEGIN /etc/grub.d/20_linux_xen ### +### END /etc/grub.d/20_linux_xen ### + +### BEGIN /etc/grub.d/20_ppc_terminfo ### +### END /etc/grub.d/20_ppc_terminfo ### + +### BEGIN /etc/grub.d/30_uefi-firmware ### +### END /etc/grub.d/30_uefi-firmware ### + +### BEGIN /etc/grub.d/40_custom ### +# This file provides an easy way to add custom menu entries. Simply type the +# menu entries you want to add after this comment. Be careful not to change +# the 'exec tail' line above. +### END /etc/grub.d/40_custom ### + +### BEGIN /etc/grub.d/41_custom ### +if [ -f ${config_directory}/custom.cfg ]; then + source ${config_directory}/custom.cfg +elif [ -z "${config_directory}" -a -f $prefix/custom.cfg ]; then + source $prefix/custom.cfg; +fi +### END /etc/grub.d/41_custom ###"#; -- Gitee From 89a6995147cf2df5145820d56a936d9db84f0c76 Mon Sep 17 00:00:00 2001 From: Yuhang Wei Date: Fri, 15 Nov 2024 12:43:27 +0000 Subject: [PATCH 2/3] feat(os-agent): support upgrade and rollback in dm-verity mode In dm-verity mode, the upgrade container image contains three raw images: update-boot.img, update-root.img and update-hash.img. Thus, the upgrade and rollback are different to normal case. We will add a bash script to /usr/bin/kubeos-dmv to wrap upgrade and rollback operation. The script will be injected into image by kbimg tool. Using http/https to download .img upgrade.img is no longer supported. Instead, a url with upgrade.tar need to be passed for downloading upgrade root tar. Signed-off-by: Yuhang Wei --- KubeOS-Rust/agent/src/rpc/agent_impl.rs | 26 ++++- KubeOS-Rust/manager/src/api/types.rs | 6 +- KubeOS-Rust/manager/src/sys_mgmt/config.rs | 22 ++++- .../manager/src/sys_mgmt/containerd_image.rs | 50 ++++++++-- .../manager/src/sys_mgmt/disk_image.rs | 89 +++++++++++------ .../manager/src/sys_mgmt/docker_image.rs | 77 +++++++++++---- KubeOS-Rust/manager/src/sys_mgmt/values.rs | 4 + KubeOS-Rust/manager/src/utils/common.rs | 23 ++++- .../manager/src/utils/image_manager.rs | 24 ++++- KubeOS-Rust/manager/src/utils/partition.rs | 96 +++++++++++++------ 10 files changed, 308 insertions(+), 109 deletions(-) diff --git a/KubeOS-Rust/agent/src/rpc/agent_impl.rs b/KubeOS-Rust/agent/src/rpc/agent_impl.rs index ab826413..68c62733 100644 --- a/KubeOS-Rust/agent/src/rpc/agent_impl.rs +++ b/KubeOS-Rust/agent/src/rpc/agent_impl.rs @@ -17,7 +17,7 @@ use log::{debug, info}; use manager::{ api::{AgentStatus, ConfigureRequest, ImageType, Response, UpgradeRequest}, sys_mgmt::{CtrImageHandler, DiskImageHandler, DockerImageHandler, CONFIG_TEMPLATE, DEFAULT_GRUBENV_PATH}, - utils::{get_partition_info, switch_boot_menuentry, RealCommandExecutor}, + utils::{get_partition_info, is_dmv_mode, switch_boot_menuentry, CommandExecutor, RealCommandExecutor}, }; use nix::{sys::reboot::RebootMode, unistd::sync}; @@ -64,10 +64,12 @@ impl AgentImpl { debug!("Received an 'prepare upgrade' request: {:?}", req); info!("Start preparing for upgrading to version: {}", req.version); + let dmv_mode = is_dmv_mode(&RealCommandExecutor {}); + info!("dm-verity mode: {}", dmv_mode); let handler: Box> = match req.image_type.as_str() { - "containerd" => Box::new(ImageType::Containerd(CtrImageHandler::default())), - "docker" => Box::new(ImageType::Docker(DockerImageHandler::default())), - "disk" => Box::new(ImageType::Disk(DiskImageHandler::default())), + "containerd" => Box::new(ImageType::Containerd(CtrImageHandler { dmv: dmv_mode, ..Default::default() })), + "docker" => Box::new(ImageType::Docker(DockerImageHandler { dmv: dmv_mode, ..Default::default() })), + "disk" => Box::new(ImageType::Disk(DiskImageHandler { dmv: dmv_mode, ..Default::default() })), _ => bail!("Invalid image type \"{}\"", req.image_type), }; @@ -85,6 +87,14 @@ impl AgentImpl { } info!("Start to upgrade"); let command_executor = RealCommandExecutor {}; + let dmv_mode = is_dmv_mode(&command_executor); + info!("dm-verity mode: {}", dmv_mode); + if dmv_mode { + command_executor.run_command("/usr/bin/kubeos-dmv", &["rollback"])?; + info!("Switch to next boot partition and reboot"); + self.reboot()?; + return Ok(Response { status: AgentStatus::Upgraded }); + } let (_, next_partition_info) = get_partition_info(&command_executor)?; // based on boot mode use different command to switch boot partition @@ -123,6 +133,14 @@ impl AgentImpl { } info!("Start to rollback"); let command_executor = RealCommandExecutor {}; + let dmv_mode = is_dmv_mode(&command_executor); + info!("dm-verity mode: {}", dmv_mode); + if dmv_mode { + command_executor.run_command("/usr/bin/kubeos-dmv", &["rollback"])?; + info!("Switch to next boot partition and reboot"); + self.reboot()?; + return Ok(Response { status: AgentStatus::Upgraded }); + } let (_, next_partition_info) = get_partition_info(&command_executor)?; switch_boot_menuentry( &command_executor, diff --git a/KubeOS-Rust/manager/src/api/types.rs b/KubeOS-Rust/manager/src/api/types.rs index 98aeaa33..70fda12f 100644 --- a/KubeOS-Rust/manager/src/api/types.rs +++ b/KubeOS-Rust/manager/src/api/types.rs @@ -116,7 +116,7 @@ mod tests { let mut mock_executor1 = MockCommandExec::new(); mock_executor1.expect_run_command().returning(|_, _| Ok(())); mock_executor1.expect_run_command_with_output().returning(|_, _| Ok(String::new())); - let c_handler = CtrImageHandler::new(PreparePath::default(), mock_executor1); + let c_handler = CtrImageHandler::new(PreparePath::default(), mock_executor1, false); let image_type = ImageType::Containerd(c_handler); let result = image_type.download_image(&req); assert!(result.is_err()); @@ -124,7 +124,7 @@ mod tests { let mut mock_executor2 = MockCommandExec::new(); mock_executor2.expect_run_command().returning(|_, _| Ok(())); mock_executor2.expect_run_command_with_output().returning(|_, _| Ok(String::new())); - let docker_handler = DockerImageHandler::new(PreparePath::default(), "test".into(), mock_executor2); + let docker_handler = DockerImageHandler::new(PreparePath::default(), "test".into(), mock_executor2, false); let image_type = ImageType::Docker(docker_handler); let result = image_type.download_image(&req); assert!(result.is_err()); @@ -132,7 +132,7 @@ mod tests { let mut mock_executor3 = MockCommandExec::new(); mock_executor3.expect_run_command().returning(|_, _| Ok(())); mock_executor3.expect_run_command_with_output().returning(|_, _| Ok(String::new())); - let disk_handler = DiskImageHandler::new(PreparePath::default(), mock_executor3, "test".into()); + let disk_handler = DiskImageHandler::new(PreparePath::default(), mock_executor3, "test".into(), false); let image_type = ImageType::Disk(disk_handler); let result = image_type.download_image(&req); assert!(result.is_err()); diff --git a/KubeOS-Rust/manager/src/sys_mgmt/config.rs b/KubeOS-Rust/manager/src/sys_mgmt/config.rs index ac79ef6c..c2471683 100644 --- a/KubeOS-Rust/manager/src/sys_mgmt/config.rs +++ b/KubeOS-Rust/manager/src/sys_mgmt/config.rs @@ -247,6 +247,11 @@ fn handle_add_key(expect_configs: &HashMap, is_only_key_valid: impl Configuration for GrubCmdline { fn set_config(&self, config: &mut Sysconfig) -> Result<()> { + let c = RealCommandExecutor {}; + if is_dmv_mode(&c) { + warn!("dm-verity mode is enabled, skip setting grub.cmdline configuration"); + return Ok(()); + } if self.is_cur_partition { info!("Start setting grub.cmdline.current configuration"); } else { @@ -258,8 +263,7 @@ impl Configuration for GrubCmdline { let config_partition = if cfg!(test) { self.is_cur_partition } else { - self.get_config_partition(RealCommandExecutor {}) - .with_context(|| "Failed to get config partition".to_string())? + self.get_config_partition(c).with_context(|| "Failed to get config partition".to_string())? }; debug!("Config_partition: {} (false means partition A, true means partition B)", config_partition); let configs = get_and_set_grubcfg(&mut config.contents, &self.grub_path, config_partition) @@ -371,7 +375,12 @@ mod tests { let mut executor = MockCommandExec::new(); // the output shows that current root menuentry is A - let command_output1 = "sda\nsda1 /boot/efi vfat 98566144\nsda2 / ext4 13000245248\nsda3 ext4 13000245248\nsda4 /persist ext4 453458788352\nsr0 iso9660 964689261\n"; + let command_output1 = r#"vda 23622320128 +vda1 /boot/efi vfat 61865984 BOOT +vda2 / ext4 3145728000 ROOT-A +vda3 ext4 2621440000 ROOT-B +vda4 /persist ext4 17791188992 PERSIST +"#; executor.expect_run_command_with_output().times(1).returning(|_, _| Ok(command_output1.to_string())); let result = grub_cmdline.get_config_partition(executor).unwrap(); @@ -381,7 +390,12 @@ mod tests { let mut executor = MockCommandExec::new(); // the output shows that current root menuentry is A - let command_output1 = "sda\nsda1 /boot/efi vfat 98566144\nsda2 / ext4 13000245248\nsda3 ext4 13000245248\nsda4 /persist ext4 453458788352\nsr0 iso9660 964689261\n"; + let command_output1 = r#"vda 23622320128 +vda1 /boot/efi vfat 61865984 BOOT +vda2 / ext4 3145728000 ROOT-A +vda3 ext4 2621440000 ROOT-B +vda4 /persist ext4 17791188992 PERSIST +"#; executor.expect_run_command_with_output().times(1).returning(|_, _| Ok(command_output1.to_string())); grub_cmdline.is_cur_partition = false; let result = grub_cmdline.get_config_partition(executor).unwrap(); diff --git a/KubeOS-Rust/manager/src/sys_mgmt/containerd_image.rs b/KubeOS-Rust/manager/src/sys_mgmt/containerd_image.rs index 80caf291..c8fd9e8c 100644 --- a/KubeOS-Rust/manager/src/sys_mgmt/containerd_image.rs +++ b/KubeOS-Rust/manager/src/sys_mgmt/containerd_image.rs @@ -17,13 +17,14 @@ use log::{debug, info}; use crate::{ api::{ImageHandler, UpgradeRequest}, - sys_mgmt::{IMAGE_PERMISSION, NEED_BYTES}, + sys_mgmt::{DMV_BOOT_IMG, DMV_HASH_IMG, DMV_ROOT_IMG, IMAGE_PERMISSION, NEED_BYTES}, utils::*, }; pub struct CtrImageHandler { pub paths: PreparePath, pub executor: T, + pub dmv: bool, } const DEFAULT_NAMESPACE: &str = "k8s.io"; @@ -34,22 +35,31 @@ impl ImageHandler for CtrImageHandler { self.get_image(req)?; self.get_rootfs_archive(req, IMAGE_PERMISSION)?; + if self.dmv { + return Ok(UpgradeImageManager::new( + self.paths.clone(), + PartitionInfo::default(), + self.executor.clone(), + self.dmv, + )); + } let (_, next_partition_info) = get_partition_info(&self.executor)?; - let img_manager = UpgradeImageManager::new(self.paths.clone(), next_partition_info, self.executor.clone()); + let img_manager = + UpgradeImageManager::new(self.paths.clone(), next_partition_info, self.executor.clone(), false); img_manager.create_os_image(IMAGE_PERMISSION) } } impl Default for CtrImageHandler { fn default() -> Self { - Self { paths: PreparePath::default(), executor: RealCommandExecutor {} } + Self { paths: PreparePath::default(), executor: RealCommandExecutor {}, dmv: false } } } impl CtrImageHandler { #[cfg(test)] - pub fn new(paths: PreparePath, executor: T) -> Self { - Self { paths, executor } + pub fn new(paths: PreparePath, executor: T, dmv: bool) -> Self { + Self { paths, executor, dmv } } fn get_image(&self, req: &UpgradeRequest) -> Result<()> { @@ -76,8 +86,27 @@ impl CtrImageHandler { self.check_and_unmount(mount_path).with_context(|| "Failed to clean containerd environment".to_string())?; self.executor .run_command("ctr", &["-n", DEFAULT_NAMESPACE, "images", "mount", "--rw", image_name, mount_path])?; - // copy os.tar from mount_path to its partent dir - self.copy_file(self.paths.mount_path.join(&self.paths.rootfs_file), &self.paths.tar_path, permission)?; + if self.dmv { + // copy update-boot.img/update-root.img/update-hash.img from mount_path to /persist + self.copy_file( + self.paths.mount_path.join(DMV_BOOT_IMG), + &self.paths.persist_path.join(DMV_BOOT_IMG), + permission, + )?; + self.copy_file( + self.paths.mount_path.join(DMV_ROOT_IMG), + &self.paths.persist_path.join(DMV_ROOT_IMG), + permission, + )?; + self.copy_file( + self.paths.mount_path.join(DMV_HASH_IMG), + &self.paths.persist_path.join(DMV_HASH_IMG), + permission, + )?; + } else { + // copy os.tar from mount_path to its partent dir + self.copy_file(self.paths.mount_path.join(&self.paths.rootfs_file), &self.paths.tar_path, permission)?; + } self.check_and_unmount(mount_path).with_context(|| "Failed to clean containerd environment".to_string())?; Ok(()) } @@ -179,7 +208,7 @@ mod tests { }) .times(1) .returning(|_, _| Ok(command_output2.to_string())); - let ctr = CtrImageHandler::new(PreparePath::default(), mock_executor); + let ctr = CtrImageHandler::new(PreparePath::default(), mock_executor, false); let result = ctr.get_image(&req); assert!(result.is_ok()); } @@ -240,7 +269,7 @@ mod tests { .times(1) .returning(|_, _| Ok("".to_string())); - let ctr = CtrImageHandler::new(paths, mock_executor); + let ctr = CtrImageHandler::new(paths, mock_executor, false); let result = ctr.get_rootfs_archive(&req, IMAGE_PERMISSION); assert!(result.is_ok()); } @@ -294,7 +323,8 @@ mod tests { .times(1) .returning(|_, _| Ok(())); - let result = CtrImageHandler::new(PreparePath::default(), mock_executor).check_and_unmount("test_mount_path"); + let result = + CtrImageHandler::new(PreparePath::default(), mock_executor, false).check_and_unmount("test_mount_path"); assert!(result.is_ok()); } diff --git a/KubeOS-Rust/manager/src/sys_mgmt/disk_image.rs b/KubeOS-Rust/manager/src/sys_mgmt/disk_image.rs index 838b9ec0..67771c2a 100644 --- a/KubeOS-Rust/manager/src/sys_mgmt/disk_image.rs +++ b/KubeOS-Rust/manager/src/sys_mgmt/disk_image.rs @@ -1,6 +1,6 @@ use std::{ fs, - os::unix::fs::PermissionsExt, + os::unix::fs::{DirBuilderExt, PermissionsExt}, path::{Path, PathBuf}, }; @@ -21,57 +21,66 @@ pub struct DiskImageHandler { pub paths: PreparePath, pub executor: T, pub certs_path: String, + pub dmv: bool, } impl ImageHandler for DiskImageHandler { fn download_image(&self, req: &UpgradeRequest) -> Result> { + if self.dmv { + bail!("DM-Verity doesn't support disk image upgrade"); + } + clean_env(&self.paths.update_path, &self.paths.mount_path, &self.paths.image_path)?; + fs::DirBuilder::new().recursive(true).mode(IMAGE_PERMISSION).create(&self.paths.mount_path)?; self.download(req)?; - self.checksum_match(self.paths.image_path.to_str().unwrap_or_default(), &req.check_sum)?; + self.checksum_match(self.paths.tar_path.to_str().unwrap_or_default(), &req.check_sum)?; let (_, next_partition_info) = get_partition_info(&self.executor)?; - let img_manager = UpgradeImageManager::new(self.paths.clone(), next_partition_info, self.executor.clone()); - Ok(img_manager) + let img_manager = + UpgradeImageManager::new(self.paths.clone(), next_partition_info, self.executor.clone(), false); + img_manager.create_os_image(IMAGE_PERMISSION) } } impl Default for DiskImageHandler { fn default() -> Self { - Self { paths: PreparePath::default(), executor: RealCommandExecutor {}, certs_path: CERTS_PATH.to_string() } + Self { + paths: PreparePath::default(), + executor: RealCommandExecutor {}, + certs_path: CERTS_PATH.to_string(), + dmv: false, + } } } impl DiskImageHandler { #[cfg(test)] - pub fn new(paths: PreparePath, executor: T, certs_path: String) -> Self { - Self { paths, executor, certs_path } + pub fn new(paths: PreparePath, executor: T, certs_path: String, dmv: bool) -> Self { + Self { paths, executor, certs_path, dmv } } fn download(&self, req: &UpgradeRequest) -> Result<()> { let mut resp = self.send_download_request(req)?; if resp.status() != reqwest::StatusCode::OK { - bail!("Failed to download image from {}, status: {}", req.image_url, resp.status()); + bail!("Failed to download upgrade tar from {}, status: {}", req.image_url, resp.status()); } debug!("Received response body size: {:?}", resp.content_length().unwrap_or_default()); let need_bytes = resp.content_length().unwrap_or_default() + BUFFER; check_disk_size( i64::try_from(need_bytes).with_context(|| "Failed to transform content length from u64 to i64")?, - self.paths.image_path.parent().unwrap_or_else(|| Path::new(PERSIST_DIR)), + self.paths.tar_path.parent().unwrap_or_else(|| Path::new(PERSIST_DIR)), )?; - let mut out = fs::File::create(&self.paths.image_path)?; - trace!("Start to save upgrade image to path {}", &self.paths.image_path.display()); + let dst = &self.paths.tar_path; + let mut out = fs::File::create(dst)?; + trace!("Start to save upgrade tar to path {}", dst.display()); out.set_permissions(fs::Permissions::from_mode(IMAGE_PERMISSION))?; let bytes = resp.copy_to(&mut out)?; - info!( - "Download image successfully, upgrade image path: {}, write bytes: {}", - &self.paths.image_path.display(), - bytes - ); + info!("Download upgrade tar successfully, upgrade tar path: {}, write bytes: {}", dst.display(), bytes); Ok(()) } fn checksum_match(&self, file_path: &str, check_sum: &str) -> Result<()> { - info!("Start checking image checksum"); + info!("Start checking file checksum"); let check_sum = check_sum.to_ascii_lowercase(); let file = fs::read(file_path)?; let mut hasher = Sha256::new(); @@ -93,7 +102,7 @@ impl DiskImageHandler { if !req.image_url.starts_with("https://") { // http request if !req.flag_safe { - bail!("The upgrade image url is not safe"); + bail!("The upgrade tar url is not safe"); } info!("Discover http request to: {}", &req.image_url); client = Client::new(); @@ -180,6 +189,23 @@ mod tests { } } + #[test] + fn test_dmv_mode() { + init(); + let handler = DiskImageHandler::new(PreparePath::default(), RealCommandExecutor {}, String::new(), true); + let req = UpgradeRequest { + version: "v2".into(), + check_sum: "1327e27d600538354d93bd68cce86566dd089e240c126dc3019cafabdc65aa02".into(), + image_type: "disk".into(), + container_image: "".into(), + image_url: "https://localhost:8082/aaa.txt".to_string(), + flag_safe: true, + mtls: true, + certs: CertsInfo { ca_cert: "".to_string(), client_cert: "".to_string(), client_key: "".to_string() }, + }; + assert!(handler.download_image(&req).is_err()); + } + #[test] fn test_get_certs_path() { init(); @@ -193,8 +219,12 @@ mod tests { init(); // generate tmp file let tmp_file = NamedTempFile::new().unwrap(); - let handler = - DiskImageHandler::::new(PreparePath::default(), RealCommandExecutor {}, String::new()); + let handler = DiskImageHandler::::new( + PreparePath::default(), + RealCommandExecutor {}, + String::new(), + false, + ); let res = handler.cert_exist(tmp_file.path().to_str().unwrap()); assert!(res.is_ok()); @@ -365,17 +395,14 @@ mod tests { } #[test] - fn test_download_image() { + fn test_download() { init(); let tmp_file = NamedTempFile::new().unwrap(); let mock_executor = MockCommandExec::new(); - let mut handler = DiskImageHandler::new(PreparePath::default(), mock_executor, String::new()); - handler.executor.expect_clone().times(1).returning(|| MockCommandExec::new()); - let command_output1 = "sda\nsda1 /boot/efi vfat 94M\nsda2 / ext4 12.1G\nsda3 ext4 12.1G\nsda4 /persist ext4 422.3G\nsr0 iso9660 0.9G\n"; - handler.executor.expect_run_command_with_output().times(1).returning(|_, _| Ok(command_output1.to_string())); - handler.paths.image_path = tmp_file.path().to_path_buf(); - assert_eq!(true, handler.paths.image_path.exists()); + let mut handler = DiskImageHandler::new(PreparePath::default(), mock_executor, String::new(), false); + handler.paths.update_path = tmp_file.path().parent().unwrap().to_path_buf(); + handler.paths.tar_path = tmp_file.path().to_path_buf(); let url = mockito::server_url(); let upgrade_request = UpgradeRequest { @@ -392,15 +419,15 @@ mod tests { .with_status(200) .with_body("This is a test txt file for KubeOS test.\n") .create(); - handler.download_image(&upgrade_request).unwrap(); - assert_eq!(true, handler.paths.image_path.exists()); + handler.download(&upgrade_request).unwrap(); + assert_eq!(true, handler.paths.tar_path.exists()); assert_eq!( - fs::read(handler.paths.image_path.to_str().unwrap()).unwrap(), + fs::read(handler.paths.tar_path.to_str().unwrap()).unwrap(), "This is a test txt file for KubeOS test.\n".as_bytes() ); let _m = mockito::mock("GET", "/test.txt").with_status(404).with_body("Not found").create(); - let res = handler.download_image(&upgrade_request); + let res = handler.download(&upgrade_request); assert!(res.is_err()) } } diff --git a/KubeOS-Rust/manager/src/sys_mgmt/docker_image.rs b/KubeOS-Rust/manager/src/sys_mgmt/docker_image.rs index 4d97552c..fa32f977 100644 --- a/KubeOS-Rust/manager/src/sys_mgmt/docker_image.rs +++ b/KubeOS-Rust/manager/src/sys_mgmt/docker_image.rs @@ -3,7 +3,7 @@ use log::{debug, info, trace}; use crate::{ api::{ImageHandler, UpgradeRequest}, - sys_mgmt::{IMAGE_PERMISSION, NEED_BYTES}, + sys_mgmt::{DMV_BOOT_IMG, DMV_HASH_IMG, DMV_ROOT_IMG, IMAGE_PERMISSION, NEED_BYTES}, utils::*, }; @@ -11,6 +11,7 @@ pub struct DockerImageHandler { pub paths: PreparePath, pub container_name: String, pub executor: T, + pub dmv: bool, } impl ImageHandler for DockerImageHandler { @@ -19,22 +20,36 @@ impl ImageHandler for DockerImageHandler { self.get_image(req)?; self.get_rootfs_archive(req)?; + if self.dmv { + return Ok(UpgradeImageManager::new( + self.paths.clone(), + PartitionInfo::default(), + self.executor.clone(), + self.dmv, + )); + } let (_, next_partition_info) = get_partition_info(&self.executor)?; - let img_manager = UpgradeImageManager::new(self.paths.clone(), next_partition_info, self.executor.clone()); + let img_manager = + UpgradeImageManager::new(self.paths.clone(), next_partition_info, self.executor.clone(), false); img_manager.create_os_image(IMAGE_PERMISSION) } } impl Default for DockerImageHandler { fn default() -> Self { - Self { paths: PreparePath::default(), container_name: "kubeos-temp".into(), executor: RealCommandExecutor {} } + Self { + paths: PreparePath::default(), + container_name: "kubeos-temp".into(), + executor: RealCommandExecutor {}, + dmv: false, + } } } impl DockerImageHandler { #[cfg(test)] - pub fn new(paths: PreparePath, container_name: String, executor: T) -> Self { - Self { paths, container_name, executor } + pub fn new(paths: PreparePath, container_name: String, executor: T, dmv: bool) -> Self { + Self { paths, container_name, executor, dmv } } fn get_image(&self, req: &UpgradeRequest) -> Result<()> { @@ -56,15 +71,11 @@ impl DockerImageHandler { debug!("Create container {}", self.container_name); let container_id = self.executor.run_command_with_output("docker", &["create", "--name", &self.container_name, image_name])?; - debug!("Copy rootfs from container {} to {}", container_id, self.paths.update_path.display()); - self.executor.run_command( - "docker", - &[ - "cp", - format!("{}:/{}", container_id, self.paths.rootfs_file).as_str(), - self.paths.update_path.to_str().unwrap(), - ], - )?; + if self.dmv { + self.dmv_copy_upgrade_files(container_id.as_str())?; + } else { + self.normal_copy_upgrade_files(container_id.as_str())?; + } self.check_and_rm_container().with_context(|| "Failed to remove kubeos-temp container".to_string())?; Ok(()) } @@ -79,6 +90,36 @@ impl DockerImageHandler { } Ok(()) } + + fn normal_copy_upgrade_files(&self, container_id: &str) -> Result<()> { + debug!("Copy rootfs from container {} to {}", container_id, self.paths.update_path.display()); + self.executor.run_command( + "docker", + &[ + "cp", + format!("{}:/{}", container_id, self.paths.rootfs_file).as_str(), + self.paths.update_path.to_str().unwrap(), + ], + )?; + Ok(()) + } + + fn dmv_copy_upgrade_files(&self, container_id: &str) -> Result<()> { + debug!("Copy dm-verity upgrade files from container {} to {}", container_id, self.paths.persist_path.display()); + self.executor.run_command( + "docker", + &["cp", format!("{}:/{}", container_id, DMV_BOOT_IMG).as_str(), self.paths.update_path.to_str().unwrap()], + )?; + self.executor.run_command( + "docker", + &["cp", format!("{}:/{}", container_id, DMV_ROOT_IMG).as_str(), self.paths.persist_path.to_str().unwrap()], + )?; + self.executor.run_command( + "docker", + &["cp", format!("{}:/{}", container_id, DMV_HASH_IMG).as_str(), self.paths.persist_path.to_str().unwrap()], + )?; + Ok(()) + } } #[cfg(test)] @@ -126,8 +167,8 @@ mod tests { .times(1) .returning(|_, _| Ok(())); - let result = - DockerImageHandler::new(PreparePath::default(), "test".into(), mock_executor).check_and_rm_container(); + let result = DockerImageHandler::new(PreparePath::default(), "test".into(), mock_executor, false) + .check_and_rm_container(); assert!(result.is_ok()); assert_eq!(DockerImageHandler::default().container_name, "kubeos-temp"); @@ -176,7 +217,7 @@ mod tests { .times(1) .returning(|_, _| Ok(command_output2.to_string())); - let docker = DockerImageHandler::new(PreparePath::default(), "kubeos-temp".into(), mock_executor); + let docker = DockerImageHandler::new(PreparePath::default(), "kubeos-temp".into(), mock_executor, false); let result = docker.get_image(&req); assert!(result.is_ok()); } @@ -229,7 +270,7 @@ mod tests { .times(1) .returning(|_, _| Ok(())); - let docker = DockerImageHandler::new(PreparePath::default(), "kubeos-temp".into(), mock_executor); + let docker = DockerImageHandler::new(PreparePath::default(), "kubeos-temp".into(), mock_executor, false); let result = docker.get_rootfs_archive(&req); assert!(result.is_ok()); } diff --git a/KubeOS-Rust/manager/src/sys_mgmt/values.rs b/KubeOS-Rust/manager/src/sys_mgmt/values.rs index b107efc3..c6e3479a 100644 --- a/KubeOS-Rust/manager/src/sys_mgmt/values.rs +++ b/KubeOS-Rust/manager/src/sys_mgmt/values.rs @@ -27,6 +27,10 @@ pub const MOUNT_DIR: &str = "kubeos-update"; pub const OS_IMAGE_NAME: &str = "update.img"; pub const CERTS_PATH: &str = "/etc/KubeOS/certs/"; +pub const DMV_BOOT_IMG: &str = "update-boot.img"; +pub const DMV_ROOT_IMG: &str = "update-root.img"; +pub const DMV_HASH_IMG: &str = "update-hash.img"; + pub const DEFAULT_KERNEL_CONFIG_PERM: u32 = 0o644; pub const DEFAULT_GRUB_CFG_PERM: u32 = 0o751; pub const IMAGE_PERMISSION: u32 = 0o600; diff --git a/KubeOS-Rust/manager/src/utils/common.rs b/KubeOS-Rust/manager/src/utils/common.rs index 9baf99e3..ce3ca8df 100644 --- a/KubeOS-Rust/manager/src/utils/common.rs +++ b/KubeOS-Rust/manager/src/utils/common.rs @@ -20,8 +20,10 @@ use anyhow::{anyhow, bail, Context, Result}; use log::{debug, info, trace}; use nix::{mount, mount::MntFlags}; -use super::executor::CommandExecutor; -use crate::sys_mgmt::{MOUNT_DIR, OS_IMAGE_NAME, PERSIST_DIR, ROOTFS_ARCHIVE, UPDATE_DIR}; +use crate::{ + sys_mgmt::{MOUNT_DIR, OS_IMAGE_NAME, PERSIST_DIR, ROOTFS_ARCHIVE, UPDATE_DIR}, + utils::CommandExecutor, +}; /// * persist_path: /persist /// @@ -160,7 +162,15 @@ pub fn switch_boot_menuentry( } pub fn get_boot_mode() -> String { - if is_file_exist("/sys/firmware/efi") { "uefi".into() } else { "bios".into() } + if is_file_exist("/sys/firmware/efi") { + "uefi".into() + } else { + "bios".into() + } +} + +pub fn is_dmv_mode(c: &T) -> bool { + c.run_command("veritysetup", &["status", "kubeos-root"]).is_ok() } #[cfg(test)] @@ -307,4 +317,11 @@ mod tests { assert_eq!(is_command_available("ls", &executor), true); assert_eq!(is_command_available("aaaabb", &executor), false); } + + #[test] + fn test_is_dmv_mode() { + init(); + let executor = RealCommandExecutor {}; + assert_eq!(is_dmv_mode(&executor), false); + } } diff --git a/KubeOS-Rust/manager/src/utils/image_manager.rs b/KubeOS-Rust/manager/src/utils/image_manager.rs index 3d849194..0e68fa03 100644 --- a/KubeOS-Rust/manager/src/utils/image_manager.rs +++ b/KubeOS-Rust/manager/src/utils/image_manager.rs @@ -30,11 +30,12 @@ pub struct UpgradeImageManager { pub paths: PreparePath, pub next_partition: PartitionInfo, pub executor: T, + pub dmv: bool, } impl UpgradeImageManager { - pub fn new(paths: PreparePath, next_partition: PartitionInfo, executor: T) -> Self { - Self { paths, next_partition, executor } + pub fn new(paths: PreparePath, next_partition: PartitionInfo, executor: T, dmv: bool) -> Self { + Self { paths, next_partition, executor, dmv } } fn image_path_str(&self) -> Result<&str> { @@ -53,11 +54,12 @@ impl UpgradeImageManager { let image_str = self.image_path_str()?; // convert bytes to the count of 2MB block - let count = self.next_partition.size / ( 2 << 20 ); + let count = self.next_partition.size / (2 << 20); debug!("Create image {}, count {}", image_str, count); - self.executor.run_command("dd", &["if=/dev/zero", &format!("of={}", image_str), "bs=2M", &format!("count={}", count)])?; + self.executor + .run_command("dd", &["if=/dev/zero", &format!("of={}", image_str), "bs=2M", &format!("count={}", count)])?; fs::set_permissions(&self.paths.image_path, Permissions::from_mode(permission))?; Ok(()) } @@ -99,6 +101,12 @@ impl UpgradeImageManager { } pub fn install(&self) -> Result<()> { + if self.dmv { + info!("Dm-verity mode, installing boot, root and hash images"); + self.executor.run_command("/usr/bin/kubeos-dmv", &["upgrade"])?; + info!("Next boot, root and hash partitions are overwritten and unable to rollback to the previous version anymore if the eviction of node fails"); + return Ok(()); + } let image_str = self.image_path_str()?; let device = self.next_partition.device.as_str(); self.executor @@ -197,8 +205,14 @@ mod tests { tar_path: "/tmp/update/image.tar".into(), rootfs_file: "image.tar".into(), }, - PartitionInfo { device: "/dev/sda3".into(), fs_type: "ext4".into(), menuentry: "B".into(), size:13000245248}, + PartitionInfo { + device: "/dev/sda3".into(), + fs_type: "ext4".into(), + menuentry: "B".into(), + size: 13000245248, + }, mock, + false, ); let img_manager = img_manager.create_os_image(0o755).unwrap(); diff --git a/KubeOS-Rust/manager/src/utils/partition.rs b/KubeOS-Rust/manager/src/utils/partition.rs index 11ae01dd..4941ee9d 100644 --- a/KubeOS-Rust/manager/src/utils/partition.rs +++ b/KubeOS-Rust/manager/src/utils/partition.rs @@ -10,8 +10,8 @@ * See the Mulan PSL v2 for more details. */ -use anyhow::{bail, Result}; -use log::{debug, trace}; +use anyhow::{bail, Context, Result}; +use log::trace; use super::executor::CommandExecutor; @@ -25,37 +25,41 @@ pub struct PartitionInfo { /// get_partition_info returns the current partition info and the next partition info. pub fn get_partition_info(executor: &T) -> Result<(PartitionInfo, PartitionInfo), anyhow::Error> { - let lsblk = executor.run_command_with_output("lsblk", &["-blno", "NAME,MOUNTPOINT,FSTYPE,SIZE"])?; - // After split whitespace, the root directory line should have 3 elements, which are "sda2 / ext4". + let lsblk = executor.run_command_with_output("lsblk", &["-blno", "NAME,MOUNTPOINT,FSTYPE,SIZE,LABEL"])?; let mut cur_partition = PartitionInfo::default(); let mut next_partition = PartitionInfo::default(); - let splitted_len = 4; + let mut found_boot = 0; trace!("get_partition_info lsblk command output:\n{}", lsblk); for line in lsblk.lines() { let res: Vec<&str> = line.split_whitespace().collect(); - if res.len() == splitted_len && res[1] == "/" { - debug!("root directory line: device={}, fs_type={}", res[0], res[2]); - cur_partition.device = format!("/dev/{}", res[0]).to_string(); - cur_partition.fs_type = res[2].to_string(); - next_partition.fs_type = res[2].to_string(); - // convert &str to i64. if the conversion fails, use 0. - cur_partition.size = res[3].parse().unwrap_or(0); - // root partition is the same size. - next_partition.size = cur_partition.size; - if res[0].contains('2') { - // root directory is mounted on sda2, so sda3 is the next partition - cur_partition.menuentry = String::from("A"); - next_partition.menuentry = String::from("B"); - next_partition.device = format!("/dev/{}", res[0].replace('2', "3")).to_string(); - } else if res[0].contains('3') { - // root directory is mounted on sda3, so sda2 is the next partition - cur_partition.menuentry = String::from("B"); - next_partition.menuentry = String::from("A"); - next_partition.device = format!("/dev/{}", res[0].replace('3', "2")).to_string(); + if res.len() == 5 && res[4] == "BOOT" { + trace!("Found boot partition:\n{:?}", res); + found_boot = 2; + continue; + } + if found_boot > 0 { + trace!("Handling two root partitions:\n{:?}", res); + if res[1] == "/" { + // current partition + cur_partition.device = format!("/dev/{}", res[0]).to_string(); + cur_partition.fs_type = res[2].to_string(); + cur_partition.size = res[3] + .parse() + .with_context(|| format!("Failed to parse current partition size to i64: \"{}\"", res[3]))?; + cur_partition.menuentry = if res[0].contains("2") { String::from("A") } else { String::from("B") }; + } else { + // next partition + next_partition.device = format!("/dev/{}", res[0]).to_string(); + next_partition.fs_type = res[1].to_string(); + next_partition.size = res[2] + .parse() + .with_context(|| format!("Failed to parse next partition size to i64: \"{}\"", res[2]))?; + next_partition.menuentry = if res[0].contains("2") { String::from("A") } else { String::from("B") }; } + found_boot -= 1; } } - if cur_partition.menuentry.is_empty() { + if cur_partition.menuentry.is_empty() || next_partition.menuentry.is_empty() { bail!("Failed to get partition info, lsblk output: {}", lsblk); } Ok((cur_partition, next_partition)) @@ -90,22 +94,52 @@ mod tests { #[test] fn test_get_partition_info() { init(); - let command_output1 = "sda\nsda1 /boot/efi vfat 98566144\nsda2 / ext4 13000245248\nsda3 ext4 13000245248\nsda4 /persist ext4 453458788352\nsr0 iso9660 964689261\n"; + let command_output1 = r#"vda 23622320128 +vda1 /boot/efi vfat 61865984 BOOT +vda2 / ext4 3145728000 ROOT-A +vda3 ext4 2621440000 ROOT-B +vda4 /persist ext4 17791188992 PERSIST +"#; let mut mock = MockCommandExec::new(); mock.expect_run_command_with_output().times(1).returning(|_, _| Ok(command_output1.to_string())); let res = get_partition_info(&mock).unwrap(); let expect_res = ( - PartitionInfo { device: "/dev/sda2".to_string(), menuentry: "A".to_string(), fs_type: "ext4".to_string(), size: 13000245248}, - PartitionInfo { device: "/dev/sda3".to_string(), menuentry: "B".to_string(), fs_type: "ext4".to_string(), size: 13000245248}, + PartitionInfo { + device: "/dev/vda2".to_string(), + menuentry: "A".to_string(), + fs_type: "ext4".to_string(), + size: 3145728000, + }, + PartitionInfo { + device: "/dev/vda3".to_string(), + menuentry: "B".to_string(), + fs_type: "ext4".to_string(), + size: 2621440000, + }, ); assert_eq!(res, expect_res); - let command_output2 = "sda\nsda1 /boot/efi vfat 98566144\nsda2 ext4 13000245248\nsda3 / ext4 13000245248\nsda4 /persist ext4 453458788352\nsr0 iso9660 964689261\n"; + let command_output2 = r#"vda 23622320128 +vda1 /boot/efi vfat 61865984 BOOT +vda2 ext4 3145728000 ROOT-A +vda3 / ext4 2621440000 ROOT-B +vda4 /persist ext4 17791188992 PERSIST +"#; mock.expect_run_command_with_output().times(1).returning(|_, _| Ok(command_output2.to_string())); let res = get_partition_info(&mock).unwrap(); let expect_res = ( - PartitionInfo { device: "/dev/sda3".to_string(), menuentry: "B".to_string(), fs_type: "ext4".to_string(), size: 13000245248}, - PartitionInfo { device: "/dev/sda2".to_string(), menuentry: "A".to_string(), fs_type: "ext4".to_string(), size: 13000245248}, + PartitionInfo { + device: "/dev/vda3".to_string(), + menuentry: "B".to_string(), + fs_type: "ext4".to_string(), + size: 2621440000, + }, + PartitionInfo { + device: "/dev/vda2".to_string(), + menuentry: "A".to_string(), + fs_type: "ext4".to_string(), + size: 3145728000, + }, ); assert_eq!(res, expect_res); -- Gitee From 8b3f48c0097e960c574fd822fbdbde517910fed0 Mon Sep 17 00:00:00 2001 From: Yuhang Wei Date: Sun, 17 Nov 2024 18:59:30 +0000 Subject: [PATCH 3/3] fix(kbimg,os-agent): rename kubeos-dmv rollback to kubeos-dmv switch Signed-off-by: Yuhang Wei --- KubeOS-Rust/agent/src/rpc/agent_impl.rs | 4 ++-- KubeOS-Rust/kbimg/src/values.rs | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/KubeOS-Rust/agent/src/rpc/agent_impl.rs b/KubeOS-Rust/agent/src/rpc/agent_impl.rs index 68c62733..f335dab0 100644 --- a/KubeOS-Rust/agent/src/rpc/agent_impl.rs +++ b/KubeOS-Rust/agent/src/rpc/agent_impl.rs @@ -90,7 +90,7 @@ impl AgentImpl { let dmv_mode = is_dmv_mode(&command_executor); info!("dm-verity mode: {}", dmv_mode); if dmv_mode { - command_executor.run_command("/usr/bin/kubeos-dmv", &["rollback"])?; + command_executor.run_command("/usr/bin/kubeos-dmv", &["switch"])?; info!("Switch to next boot partition and reboot"); self.reboot()?; return Ok(Response { status: AgentStatus::Upgraded }); @@ -136,7 +136,7 @@ impl AgentImpl { let dmv_mode = is_dmv_mode(&command_executor); info!("dm-verity mode: {}", dmv_mode); if dmv_mode { - command_executor.run_command("/usr/bin/kubeos-dmv", &["rollback"])?; + command_executor.run_command("/usr/bin/kubeos-dmv", &["switch"])?; info!("Switch to next boot partition and reboot"); self.reboot()?; return Ok(Response { status: AgentStatus::Upgraded }); diff --git a/KubeOS-Rust/kbimg/src/values.rs b/KubeOS-Rust/kbimg/src/values.rs index 714ca4cd..3986c13f 100644 --- a/KubeOS-Rust/kbimg/src/values.rs +++ b/KubeOS-Rust/kbimg/src/values.rs @@ -1355,7 +1355,7 @@ function dmvmain() { }"#; pub const DMV_UPGRADE_ROLLBACK_SH: &str = r#"set -eux -if [ "$1" != "upgrade" ] && [ "$1" != "rollback" ]; then +if [ "$1" != "upgrade" ] && [ "$1" != "switch" ]; then echo "Invalid argument: $1" exit 1 fi -- Gitee