Red Bear OS installer baseline from 0.1.0 pre-patched archive
This commit is contained in:
@@ -0,0 +1,83 @@
|
||||
use std::fmt::Display;
|
||||
|
||||
#[derive(Clone, Debug, Default, Deserialize, Serialize)]
|
||||
pub struct FileConfig {
|
||||
pub path: String,
|
||||
pub data: String,
|
||||
#[serde(default)]
|
||||
pub symlink: bool,
|
||||
#[serde(default)]
|
||||
pub directory: bool,
|
||||
pub mode: Option<u32>,
|
||||
pub uid: Option<u32>,
|
||||
pub gid: Option<u32>,
|
||||
#[serde(default)]
|
||||
pub recursive_chown: bool,
|
||||
#[serde(default)]
|
||||
pub postinstall: bool,
|
||||
}
|
||||
|
||||
impl FileConfig {
|
||||
pub fn new_file(path: String, data: String) -> FileConfig {
|
||||
FileConfig {
|
||||
path,
|
||||
data,
|
||||
..Default::default()
|
||||
}
|
||||
}
|
||||
|
||||
pub fn new_directory(path: String) -> FileConfig {
|
||||
FileConfig {
|
||||
path,
|
||||
data: String::new(),
|
||||
directory: true,
|
||||
..Default::default()
|
||||
}
|
||||
}
|
||||
|
||||
pub fn with_mod(&mut self, mode: u32, uid: u32, gid: u32) -> &mut FileConfig {
|
||||
self.mode = Some(mode);
|
||||
self.uid = Some(uid);
|
||||
self.gid = Some(gid);
|
||||
self
|
||||
}
|
||||
|
||||
pub fn with_recursive_mod(&mut self, mode: u32, uid: u32, gid: u32) -> &mut FileConfig {
|
||||
self.with_mod(mode, uid, gid);
|
||||
self.recursive_chown = true;
|
||||
self
|
||||
}
|
||||
}
|
||||
|
||||
impl Display for FileConfig {
|
||||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||
write!(f, "{}", self.path)?;
|
||||
if self.symlink {
|
||||
write!(f, " -> {}", self.data)?;
|
||||
} else if self.directory {
|
||||
write!(f, " type=dir")?;
|
||||
if self.recursive_chown {
|
||||
write!(f, " chown=yes")?;
|
||||
}
|
||||
} else {
|
||||
write!(
|
||||
f,
|
||||
" size={}B",
|
||||
arg_parser::to_human_readable_string(self.data.len() as u64)
|
||||
)?;
|
||||
if self.postinstall {
|
||||
write!(f, "!")?;
|
||||
}
|
||||
}
|
||||
if let Some(uid) = self.uid {
|
||||
write!(f, " uid={}", uid)?;
|
||||
}
|
||||
if let Some(uid) = self.uid {
|
||||
write!(f, " gid={}", uid)?;
|
||||
}
|
||||
if let Some(mode) = self.mode {
|
||||
write!(f, " mode={:3o}", mode)?;
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,88 @@
|
||||
use anyhow::{Context, Result};
|
||||
use libc::{gid_t, uid_t};
|
||||
|
||||
use std::ffi::{CString, OsStr};
|
||||
use std::fs::{self, File};
|
||||
use std::io::{Error, Write};
|
||||
use std::os::unix::ffi::OsStrExt;
|
||||
use std::os::unix::fs::{symlink, PermissionsExt};
|
||||
use std::path::Path;
|
||||
|
||||
fn chown<P: AsRef<Path>>(path: P, uid: uid_t, gid: gid_t, recursive: bool) -> Result<()> {
|
||||
let path = path.as_ref();
|
||||
|
||||
let c_path = CString::new(path.as_os_str().as_bytes()).unwrap();
|
||||
if unsafe { libc::chown(c_path.as_ptr(), uid, gid) } != 0 {
|
||||
return Err(Error::last_os_error().into());
|
||||
}
|
||||
|
||||
if recursive && path.is_dir() {
|
||||
for entry_res in fs::read_dir(path)? {
|
||||
let entry = entry_res?;
|
||||
chown(entry.path(), uid, gid, recursive)?;
|
||||
}
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
// TODO: Rewrite impls
|
||||
impl crate::FileConfig {
|
||||
pub(crate) fn create<P: AsRef<Path>>(&self, prefix: P) -> Result<()> {
|
||||
let path = self.path.trim_start_matches('/');
|
||||
let target_file = prefix.as_ref().join(path);
|
||||
|
||||
if self.directory {
|
||||
println!("Create directory {}", target_file.display());
|
||||
fs::create_dir_all(&target_file)
|
||||
.with_context(|| format!("failed to create directory {}", target_file.display()))?;
|
||||
self.apply_perms(&target_file)?;
|
||||
return Ok(());
|
||||
} else if let Some(parent) = target_file.parent() {
|
||||
println!("Create file parent {}", parent.display());
|
||||
fs::create_dir_all(parent)
|
||||
.with_context(|| format!("failed to create file parent {}", parent.display()))?;
|
||||
}
|
||||
|
||||
if self.symlink {
|
||||
println!("Create symlink {} to {}", target_file.display(), self.data);
|
||||
if target_file.is_symlink() {
|
||||
fs::remove_file(&target_file).with_context(|| {
|
||||
format!("failed to remove old symlink {}", target_file.display())
|
||||
})?;
|
||||
}
|
||||
symlink(&OsStr::new(&self.data), &target_file).with_context(|| {
|
||||
format!(
|
||||
"failed to create symlink {} to {}",
|
||||
target_file.display(),
|
||||
self.data
|
||||
)
|
||||
})?;
|
||||
Ok(())
|
||||
} else {
|
||||
println!("Create file {}", target_file.display());
|
||||
let mut file = File::create(&target_file)
|
||||
.with_context(|| format!("failed to create file {}", target_file.display()))?;
|
||||
file.write_all(self.data.as_bytes())?;
|
||||
|
||||
self.apply_perms(target_file)
|
||||
}
|
||||
}
|
||||
|
||||
fn apply_perms<P: AsRef<Path>>(&self, target: P) -> Result<()> {
|
||||
let path = target.as_ref();
|
||||
let mode = self
|
||||
.mode
|
||||
.unwrap_or_else(|| if self.directory { 0o0755 } else { 0o0644 });
|
||||
let uid = self.uid.unwrap_or(!0);
|
||||
let gid = self.gid.unwrap_or(!0);
|
||||
|
||||
// chmod
|
||||
fs::set_permissions(path, fs::Permissions::from_mode(mode))
|
||||
.with_context(|| format!("failed to set permissions on {}", path.display()))?;
|
||||
|
||||
// chown
|
||||
chown(path, uid, gid, self.recursive_chown)
|
||||
.with_context(|| format!("failed to chown {}", path.display()))
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,42 @@
|
||||
#[derive(Clone, Debug, Default, Deserialize, Serialize)]
|
||||
pub struct GeneralConfig {
|
||||
/// Specify a path where cookbook exists, all packages will be installed locally
|
||||
pub cookbook: Option<String>,
|
||||
/// Allow prompts for missing information such as user password
|
||||
pub prompt: Option<bool>,
|
||||
/// Total filesystem size in MB
|
||||
pub filesystem_size: Option<u32>,
|
||||
/// EFI partition size in MB, default to 2MB
|
||||
pub efi_partition_size: Option<u32>,
|
||||
/// Skip disk partitioning, assume whole disk is a partition
|
||||
pub skip_partitions: Option<bool>,
|
||||
/// Set a plain text password to encrypt the disk
|
||||
pub encrypt_disk: Option<String>,
|
||||
/// Use live disk for bootloader config, default is false
|
||||
pub live_disk: Option<bool>,
|
||||
/// If set, write bootloader disk into this path
|
||||
pub write_bootloader: Option<String>,
|
||||
/// Use AR to write files instead of FUSE-based mount
|
||||
/// (bypasses FUSE, but slower and requires namespaced context such as "podman unshare")
|
||||
pub no_mount: Option<bool>,
|
||||
}
|
||||
|
||||
impl GeneralConfig {
|
||||
/// Merge two config, "other" is more dominant
|
||||
pub(super) fn merge(&mut self, other: GeneralConfig) {
|
||||
if let Some(cookbook) = other.cookbook {
|
||||
self.cookbook = Some(cookbook);
|
||||
}
|
||||
self.filesystem_size = other.filesystem_size.or(self.filesystem_size);
|
||||
self.efi_partition_size = other.efi_partition_size.or(self.efi_partition_size);
|
||||
self.skip_partitions = other.skip_partitions.or(self.skip_partitions);
|
||||
if let Some(encrypt_disk) = other.encrypt_disk {
|
||||
self.encrypt_disk = Some(encrypt_disk);
|
||||
}
|
||||
self.live_disk = other.live_disk.or(self.live_disk);
|
||||
if let Some(write_bootloader) = other.write_bootloader {
|
||||
self.write_bootloader = Some(write_bootloader);
|
||||
}
|
||||
self.no_mount = other.no_mount.or(self.no_mount);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,134 @@
|
||||
use std::collections::BTreeMap;
|
||||
use std::fmt::Display;
|
||||
use std::fs;
|
||||
use std::mem;
|
||||
use std::path::{Path, PathBuf};
|
||||
|
||||
use anyhow::bail;
|
||||
use anyhow::Context;
|
||||
use anyhow::Result;
|
||||
|
||||
use crate::PackageConfig;
|
||||
|
||||
pub mod file;
|
||||
#[cfg(feature = "installer")]
|
||||
pub mod file_impl;
|
||||
pub mod general;
|
||||
pub mod package;
|
||||
pub mod user;
|
||||
|
||||
#[derive(Clone, Debug, Default, Deserialize, Serialize)]
|
||||
pub struct Config {
|
||||
#[serde(default)]
|
||||
pub include: Vec<PathBuf>,
|
||||
#[serde(default)]
|
||||
pub general: general::GeneralConfig,
|
||||
#[serde(default)]
|
||||
pub packages: BTreeMap<String, package::PackageConfig>,
|
||||
#[serde(default)]
|
||||
pub files: Vec<file::FileConfig>,
|
||||
#[serde(default)]
|
||||
pub users: BTreeMap<String, user::UserConfig>,
|
||||
#[serde(default)]
|
||||
pub groups: BTreeMap<String, user::GroupConfig>,
|
||||
}
|
||||
|
||||
impl Config {
|
||||
/// Load installer config from a TOML path
|
||||
pub fn from_file(path: &Path) -> Result<Self> {
|
||||
let mut config: Config = match fs::read_to_string(&path) {
|
||||
Ok(config_data) => match toml::from_str(&config_data) {
|
||||
Ok(config) => config,
|
||||
Err(err) => {
|
||||
bail!("failed to decode '{}': {}", path.display(), err);
|
||||
}
|
||||
},
|
||||
Err(err) => {
|
||||
bail!("failed to read '{}': {}", path.display(), err);
|
||||
}
|
||||
};
|
||||
|
||||
let config_dir = path.parent().unwrap();
|
||||
|
||||
let mut configs = mem::take(&mut config.include)
|
||||
.into_iter()
|
||||
.map(|path| {
|
||||
Config::from_file(&config_dir.join(&path))
|
||||
.with_context(|| format!("Importing from {}", path.display()))
|
||||
})
|
||||
.collect::<Result<Vec<Config>>>()?;
|
||||
configs.push(config); // Put ourself last to ensure that it overwrites anything else.
|
||||
|
||||
config = configs.remove(0);
|
||||
|
||||
for other_config in configs {
|
||||
config.merge(other_config);
|
||||
}
|
||||
|
||||
Ok(config)
|
||||
}
|
||||
|
||||
/// Load hardcoded install config to fetch bootloaders
|
||||
pub fn bootloader_config() -> Self {
|
||||
let mut bootloader_config = Config::default();
|
||||
// TODO: This is unused
|
||||
bootloader_config.files.push(file::FileConfig {
|
||||
path: "/etc/pkg.d/50_redox".to_string(),
|
||||
data: "https://static.redox-os.org/pkg".to_string(),
|
||||
..Default::default()
|
||||
});
|
||||
bootloader_config
|
||||
.packages
|
||||
.insert("bootloader".to_string(), PackageConfig::default());
|
||||
bootloader_config
|
||||
}
|
||||
|
||||
pub fn merge(&mut self, other: Config) {
|
||||
assert!(self.include.is_empty());
|
||||
assert!(other.include.is_empty());
|
||||
|
||||
let Config {
|
||||
include: _,
|
||||
general: other_general,
|
||||
packages: other_packages,
|
||||
files: other_files,
|
||||
users: other_users,
|
||||
groups: other_groups,
|
||||
} = other;
|
||||
|
||||
self.general.merge(other_general);
|
||||
|
||||
for (package, package_config) in other_packages {
|
||||
self.packages.insert(package, package_config);
|
||||
}
|
||||
|
||||
self.files.extend(other_files);
|
||||
|
||||
for (user, user_config) in other_users {
|
||||
self.users.insert(user, user_config);
|
||||
}
|
||||
|
||||
for (group, group_config) in other_groups {
|
||||
self.groups.insert(group, group_config);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Display for Config {
|
||||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||
writeln!(f, "files:")?;
|
||||
for file in &self.files {
|
||||
writeln!(f, "- {}", file)?;
|
||||
}
|
||||
writeln!(f, "users:")?;
|
||||
for (name, user) in &self.users {
|
||||
writeln!(f, "- {}:{}", name, user)?;
|
||||
}
|
||||
write!(f, "packages: ")?;
|
||||
for name in self.packages.keys() {
|
||||
write!(f, " {}", name)?;
|
||||
}
|
||||
writeln!(f, "")?;
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,19 @@
|
||||
#[derive(Clone, Debug, Deserialize, Serialize)]
|
||||
#[serde(untagged)]
|
||||
pub enum PackageConfig {
|
||||
Empty,
|
||||
Build(String),
|
||||
|
||||
// TODO: Sum type
|
||||
Spec {
|
||||
version: Option<String>,
|
||||
git: Option<String>,
|
||||
path: Option<String>,
|
||||
},
|
||||
}
|
||||
|
||||
impl Default for PackageConfig {
|
||||
fn default() -> Self {
|
||||
Self::Empty
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,43 @@
|
||||
use std::fmt::Display;
|
||||
|
||||
#[derive(Clone, Debug, Default, Deserialize, Serialize)]
|
||||
pub struct UserConfig {
|
||||
pub password: Option<String>,
|
||||
pub uid: Option<u32>,
|
||||
pub gid: Option<u32>,
|
||||
pub name: Option<String>,
|
||||
pub home: Option<String>,
|
||||
pub shell: Option<String>,
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug, Default, Deserialize, Serialize)]
|
||||
pub struct GroupConfig {
|
||||
pub gid: Option<u32>,
|
||||
// FIXME move this to the UserConfig struct as extra_groups
|
||||
pub members: Vec<String>,
|
||||
}
|
||||
|
||||
impl Display for UserConfig {
|
||||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||
if let Some(uid) = &self.uid {
|
||||
write!(f, " uid={}", uid)?;
|
||||
}
|
||||
if let Some(gid) = &self.gid {
|
||||
write!(f, " gid={}", gid)?;
|
||||
}
|
||||
if let Some(name) = &self.name {
|
||||
write!(f, " name={}", name)?;
|
||||
}
|
||||
if let Some(home) = &self.home {
|
||||
write!(f, " home={}", home)?;
|
||||
}
|
||||
if let Some(shell) = &self.shell {
|
||||
write!(f, " shell={}", shell)?;
|
||||
}
|
||||
if self.password.as_ref().is_some_and(|s| !s.is_empty()) {
|
||||
write!(f, " password=yes")?;
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user