mirror of
https://github.com/Start9Labs/start-os.git
synced 2026-03-30 12:11:56 +00:00
install wizard project (#1893)
* install wizard project * reboot endpoint * Update frontend/projects/install-wizard/src/app/pages/home/home.page.ts Co-authored-by: Lucy C <12953208+elvece@users.noreply.github.com> * Update frontend/projects/install-wizard/src/app/pages/home/home.page.ts Co-authored-by: Lucy C <12953208+elvece@users.noreply.github.com> * Update frontend/projects/install-wizard/src/app/pages/home/home.page.ts Co-authored-by: Lucy C <12953208+elvece@users.noreply.github.com> * update build * fix build * backend portion * increase image size * loaded * dont auto resize * fix install wizard * use localhost if still in setup mode * fix compat Co-authored-by: Lucy C <12953208+elvece@users.noreply.github.com> Co-authored-by: Aiden McClelland <me@drbonez.dev>
This commit is contained in:
committed by
Aiden McClelland
parent
bc23129759
commit
a2f65de1ce
1283
backend/Cargo.lock
generated
1283
backend/Cargo.lock
generated
File diff suppressed because it is too large
Load Diff
@@ -68,7 +68,9 @@ digest-old = { package = "digest", version = "0.9.0" }
|
||||
divrem = "1.0.0"
|
||||
ed25519 = { version = "1.5.2", features = ["pkcs8", "pem", "alloc"] }
|
||||
ed25519-dalek = { version = "1.0.1", features = ["serde"] }
|
||||
emver = { version = "0.1.6", features = ["serde"] }
|
||||
emver = { version = "0.1.7", git = "https://github.com/Start9Labs/emver-rs.git", features = [
|
||||
"serde",
|
||||
] }
|
||||
fd-lock-rs = "0.1.4"
|
||||
futures = "0.3.21"
|
||||
git-version = "0.3.5"
|
||||
@@ -89,6 +91,7 @@ jsonpath_lib = "0.3.0"
|
||||
lazy_static = "1.4.0"
|
||||
libc = "0.2.126"
|
||||
log = "0.4.17"
|
||||
mbrman = "0.5.0"
|
||||
models = { version = "*", path = "../libs/models" }
|
||||
nix = "0.25.0"
|
||||
nom = "7.1.1"
|
||||
|
||||
@@ -64,7 +64,7 @@ sudo chown -R $USER target
|
||||
sudo chown -R $USER ~/.cargo
|
||||
sudo chown -R $USER ../libs/target
|
||||
|
||||
if [-n fail]; then
|
||||
if [ -n "$fail" ]; then
|
||||
exit 1
|
||||
fi
|
||||
|
||||
|
||||
@@ -3,7 +3,7 @@ use std::sync::Arc;
|
||||
use std::time::Duration;
|
||||
|
||||
use embassy::context::rpc::RpcContextConfig;
|
||||
use embassy::context::{DiagnosticContext, SetupContext};
|
||||
use embassy::context::{DiagnosticContext, InstallContext, SetupContext};
|
||||
use embassy::disk::fsck::RepairStrategy;
|
||||
use embassy::disk::main::DEFAULT_PASSWORD;
|
||||
use embassy::disk::REPAIR_DISK_PATH;
|
||||
@@ -28,7 +28,45 @@ fn status_fn(_: i32) -> StatusCode {
|
||||
|
||||
#[instrument]
|
||||
async fn setup_or_init(cfg_path: Option<PathBuf>) -> Result<(), Error> {
|
||||
if tokio::fs::metadata("/media/embassy/config/disk.guid")
|
||||
if tokio::fs::metadata("/cdrom").await.is_ok() {
|
||||
#[cfg(feature = "avahi")]
|
||||
let _mdns = MdnsController::init();
|
||||
tokio::fs::write(
|
||||
"/etc/nginx/sites-available/default",
|
||||
include_str!("../nginx/install-wizard.conf"),
|
||||
)
|
||||
.await
|
||||
.with_ctx(|_| {
|
||||
(
|
||||
embassy::ErrorKind::Filesystem,
|
||||
"/etc/nginx/sites-available/default",
|
||||
)
|
||||
})?;
|
||||
Command::new("systemctl")
|
||||
.arg("reload")
|
||||
.arg("nginx")
|
||||
.invoke(embassy::ErrorKind::Nginx)
|
||||
.await?;
|
||||
let ctx = InstallContext::init(cfg_path).await?;
|
||||
tokio::time::sleep(Duration::from_secs(1)).await; // let the record state that I hate this
|
||||
CHIME.play().await?;
|
||||
rpc_server!({
|
||||
command: embassy::install_api,
|
||||
context: ctx.clone(),
|
||||
status: status_fn,
|
||||
middleware: [
|
||||
cors,
|
||||
]
|
||||
})
|
||||
.with_graceful_shutdown({
|
||||
let mut shutdown = ctx.shutdown.subscribe();
|
||||
async move {
|
||||
shutdown.recv().await.expect("context dropped");
|
||||
}
|
||||
})
|
||||
.await
|
||||
.with_kind(embassy::ErrorKind::Network)?;
|
||||
} else if tokio::fs::metadata("/media/embassy/config/disk.guid")
|
||||
.await
|
||||
.is_err()
|
||||
{
|
||||
|
||||
74
backend/src/context/install.rs
Normal file
74
backend/src/context/install.rs
Normal file
@@ -0,0 +1,74 @@
|
||||
use std::net::{IpAddr, SocketAddr};
|
||||
use std::ops::Deref;
|
||||
use std::path::Path;
|
||||
use std::sync::Arc;
|
||||
|
||||
use rpc_toolkit::Context;
|
||||
use serde::Deserialize;
|
||||
use tokio::sync::broadcast::Sender;
|
||||
use tracing::instrument;
|
||||
use url::Host;
|
||||
|
||||
use crate::util::config::load_config_from_paths;
|
||||
use crate::Error;
|
||||
|
||||
#[derive(Debug, Default, Deserialize)]
|
||||
#[serde(rename_all = "kebab-case")]
|
||||
pub struct InstallContextConfig {
|
||||
pub bind_rpc: Option<SocketAddr>,
|
||||
}
|
||||
impl InstallContextConfig {
|
||||
#[instrument(skip(path))]
|
||||
pub async fn load<P: AsRef<Path> + Send + 'static>(path: Option<P>) -> Result<Self, Error> {
|
||||
tokio::task::spawn_blocking(move || {
|
||||
load_config_from_paths(
|
||||
path.as_ref()
|
||||
.into_iter()
|
||||
.map(|p| p.as_ref())
|
||||
.chain(std::iter::once(Path::new(
|
||||
"/media/embassy/config/config.yaml",
|
||||
)))
|
||||
.chain(std::iter::once(Path::new(crate::util::config::CONFIG_PATH))),
|
||||
)
|
||||
})
|
||||
.await
|
||||
.unwrap()
|
||||
}
|
||||
}
|
||||
|
||||
pub struct InstallContextSeed {
|
||||
pub bind_rpc: SocketAddr,
|
||||
pub shutdown: Sender<()>,
|
||||
}
|
||||
|
||||
#[derive(Clone)]
|
||||
pub struct InstallContext(Arc<InstallContextSeed>);
|
||||
impl InstallContext {
|
||||
#[instrument(skip(path))]
|
||||
pub async fn init<P: AsRef<Path> + Send + 'static>(path: Option<P>) -> Result<Self, Error> {
|
||||
let cfg = InstallContextConfig::load(path.as_ref().map(|p| p.as_ref().to_owned())).await?;
|
||||
let (shutdown, _) = tokio::sync::broadcast::channel(1);
|
||||
Ok(Self(Arc::new(InstallContextSeed {
|
||||
bind_rpc: cfg.bind_rpc.unwrap_or(([127, 0, 0, 1], 5959).into()),
|
||||
shutdown,
|
||||
})))
|
||||
}
|
||||
}
|
||||
|
||||
impl Context for InstallContext {
|
||||
fn host(&self) -> Host<&str> {
|
||||
match self.0.bind_rpc.ip() {
|
||||
IpAddr::V4(a) => Host::Ipv4(a),
|
||||
IpAddr::V6(a) => Host::Ipv6(a),
|
||||
}
|
||||
}
|
||||
fn port(&self) -> u16 {
|
||||
self.0.bind_rpc.port()
|
||||
}
|
||||
}
|
||||
impl Deref for InstallContext {
|
||||
type Target = InstallContextSeed;
|
||||
fn deref(&self) -> &Self::Target {
|
||||
&*self.0
|
||||
}
|
||||
}
|
||||
@@ -1,11 +1,13 @@
|
||||
pub mod cli;
|
||||
pub mod diagnostic;
|
||||
pub mod install;
|
||||
pub mod rpc;
|
||||
pub mod sdk;
|
||||
pub mod setup;
|
||||
|
||||
pub use cli::CliContext;
|
||||
pub use diagnostic::DiagnosticContext;
|
||||
pub use install::InstallContext;
|
||||
pub use rpc::RpcContext;
|
||||
pub use sdk::SdkContext;
|
||||
pub use setup::SetupContext;
|
||||
@@ -35,3 +37,8 @@ impl From<SetupContext> for () {
|
||||
()
|
||||
}
|
||||
}
|
||||
impl From<InstallContext> for () {
|
||||
fn from(_: InstallContext) -> Self {
|
||||
()
|
||||
}
|
||||
}
|
||||
|
||||
@@ -2,7 +2,7 @@ use std::path::{Path, PathBuf};
|
||||
|
||||
use clap::ArgMatches;
|
||||
use rpc_toolkit::command;
|
||||
use serde::Deserialize;
|
||||
use serde::{Deserialize, Serialize};
|
||||
|
||||
use crate::context::RpcContext;
|
||||
use crate::disk::util::DiskInfo;
|
||||
@@ -18,7 +18,7 @@ pub mod util;
|
||||
pub const BOOT_RW_PATH: &str = "/media/boot-rw";
|
||||
pub const REPAIR_DISK_PATH: &str = "/media/embassy/config/repair-disk";
|
||||
|
||||
#[derive(Debug, Default, Deserialize)]
|
||||
#[derive(Debug, Default, Deserialize, Serialize)]
|
||||
#[serde(rename_all = "kebab-case")]
|
||||
pub struct OsPartitionInfo {
|
||||
pub boot: PathBuf,
|
||||
|
||||
@@ -12,7 +12,6 @@ use nom::sequence::{pair, preceded, terminated};
|
||||
use nom::IResult;
|
||||
use regex::Regex;
|
||||
use serde::{Deserialize, Serialize};
|
||||
use tokio::fs::File;
|
||||
use tokio::process::Command;
|
||||
use tracing::instrument;
|
||||
|
||||
@@ -20,7 +19,6 @@ use super::mount::filesystem::block_dev::BlockDev;
|
||||
use super::mount::filesystem::ReadOnly;
|
||||
use super::mount::guard::TmpMountGuard;
|
||||
use crate::disk::OsPartitionInfo;
|
||||
use crate::util::io::from_yaml_async_reader;
|
||||
use crate::util::serde::IoFormat;
|
||||
use crate::util::{Invoke, Version};
|
||||
use crate::{Error, ResultExt as _};
|
||||
@@ -44,6 +42,7 @@ pub struct PartitionInfo {
|
||||
pub capacity: u64,
|
||||
pub used: Option<u64>,
|
||||
pub embassy_os: Option<EmbassyOsRecoveryInfo>,
|
||||
pub guid: Option<String>,
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug, Default, Deserialize, Serialize)]
|
||||
@@ -219,15 +218,6 @@ pub async fn recovery_info(
|
||||
)?,
|
||||
));
|
||||
}
|
||||
let version_path = mountpoint.as_ref().join("root/appmgr/version");
|
||||
if tokio::fs::metadata(&version_path).await.is_ok() {
|
||||
return Ok(Some(EmbassyOsRecoveryInfo {
|
||||
version: from_yaml_async_reader(File::open(&version_path).await?).await?,
|
||||
full: true,
|
||||
password_hash: None,
|
||||
wrapped_key: None,
|
||||
}));
|
||||
}
|
||||
|
||||
Ok(None)
|
||||
}
|
||||
@@ -323,7 +313,11 @@ pub async fn list(os: &OsPartitionInfo) -> Result<Vec<DiskInfo>, Error> {
|
||||
disk_info.guid = g.clone();
|
||||
} else {
|
||||
for part in index.parts {
|
||||
disk_info.partitions.push(part_info(part).await);
|
||||
let mut part_info = part_info(part).await;
|
||||
if let Some(g) = disk_guids.get(&part_info.logicalname) {
|
||||
part_info.guid = g.clone();
|
||||
}
|
||||
disk_info.partitions.push(part_info);
|
||||
}
|
||||
}
|
||||
res.push(disk_info);
|
||||
@@ -398,6 +392,7 @@ async fn part_info(part: PathBuf) -> PartitionInfo {
|
||||
capacity,
|
||||
used,
|
||||
embassy_os,
|
||||
guid: None,
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -242,7 +242,12 @@ impl From<std::net::AddrParseError> for Error {
|
||||
}
|
||||
impl From<openssl::error::ErrorStack> for Error {
|
||||
fn from(e: openssl::error::ErrorStack) -> Self {
|
||||
Error::new(eyre!("OpenSSL ERROR:\n{}", e), ErrorKind::OpenSsl)
|
||||
Error::new(eyre!("{}", e), ErrorKind::OpenSsl)
|
||||
}
|
||||
}
|
||||
impl From<mbrman::Error> for Error {
|
||||
fn from(e: mbrman::Error) -> Self {
|
||||
Error::new(e, ErrorKind::DiskManagement)
|
||||
}
|
||||
}
|
||||
impl From<Error> for RpcError {
|
||||
|
||||
2
backend/src/fstab.template
Normal file
2
backend/src/fstab.template
Normal file
@@ -0,0 +1,2 @@
|
||||
{boot} /boot vfat defaults 0 2
|
||||
{root} / ext4 defaults 0 1
|
||||
@@ -35,6 +35,7 @@ pub mod middleware;
|
||||
pub mod migration;
|
||||
pub mod net;
|
||||
pub mod notifications;
|
||||
pub mod os_install;
|
||||
pub mod procedure;
|
||||
pub mod properties;
|
||||
pub mod s9pk;
|
||||
@@ -133,3 +134,8 @@ pub fn diagnostic_api() -> Result<(), RpcError> {
|
||||
pub fn setup_api() -> Result<(), RpcError> {
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[command(subcommands(version::git_info, echo, os_install::install))]
|
||||
pub fn install_api() -> Result<(), RpcError> {
|
||||
Ok(())
|
||||
}
|
||||
|
||||
29
backend/src/nginx/install-wizard.conf
Normal file
29
backend/src/nginx/install-wizard.conf
Normal file
@@ -0,0 +1,29 @@
|
||||
server {
|
||||
listen 80 default_server;
|
||||
listen [::]:80 default_server;
|
||||
|
||||
root /var/www/html/install;
|
||||
|
||||
index index.html index.htm index.nginx-debian.html;
|
||||
|
||||
server_name _;
|
||||
|
||||
proxy_buffering off;
|
||||
proxy_request_buffering off;
|
||||
proxy_socket_keepalive on;
|
||||
proxy_http_version 1.1;
|
||||
proxy_read_timeout 1800;
|
||||
|
||||
gzip on;
|
||||
gzip_vary on;
|
||||
gzip_min_length 1024;
|
||||
gzip_types text/plain text/css text/xml text/javascript application/x-javascript application/xml;
|
||||
|
||||
location /rpc/ {
|
||||
proxy_pass http://127.0.0.1:5959/;
|
||||
}
|
||||
|
||||
location / {
|
||||
try_files $uri $uri/ =404;
|
||||
}
|
||||
}
|
||||
317
backend/src/os_install.rs
Normal file
317
backend/src/os_install.rs
Normal file
@@ -0,0 +1,317 @@
|
||||
use std::path::{Path, PathBuf};
|
||||
|
||||
use color_eyre::eyre::eyre;
|
||||
use mbrman::{MBRPartitionEntry, CHS, MBR};
|
||||
use rpc_toolkit::command;
|
||||
use serde::{Deserialize, Serialize};
|
||||
use tokio::process::Command;
|
||||
|
||||
use crate::disk::mount::filesystem::bind::Bind;
|
||||
use crate::disk::mount::filesystem::block_dev::BlockDev;
|
||||
use crate::disk::mount::filesystem::ReadWrite;
|
||||
use crate::disk::mount::guard::{MountGuard, TmpMountGuard};
|
||||
use crate::disk::OsPartitionInfo;
|
||||
use crate::util::serde::IoFormat;
|
||||
use crate::util::{display_none, Invoke};
|
||||
use crate::{Error, ResultExt};
|
||||
|
||||
#[derive(Debug, Deserialize, Serialize)]
|
||||
#[serde(rename_all = "kebab-case")]
|
||||
pub struct PostInstallConfig {
|
||||
os_partitions: OsPartitionInfo,
|
||||
ethernet_interface: String,
|
||||
wifi_interface: Option<String>,
|
||||
}
|
||||
|
||||
#[command(subcommands(status, execute, reboot))]
|
||||
pub fn install() -> Result<(), Error> {
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[derive(Debug, Deserialize, Serialize)]
|
||||
#[serde(rename_all = "kebab-case")]
|
||||
pub struct InstallTarget {
|
||||
logicalname: PathBuf,
|
||||
embassy_data: bool,
|
||||
}
|
||||
|
||||
#[command(display(display_none))]
|
||||
pub async fn status() -> Result<Vec<InstallTarget>, Error> {
|
||||
let disks = crate::disk::util::list(&Default::default()).await?;
|
||||
Ok(disks
|
||||
.into_iter()
|
||||
.map(|d| InstallTarget {
|
||||
logicalname: d.logicalname,
|
||||
embassy_data: d.guid.is_some() || d.partitions.into_iter().any(|p| p.guid.is_some()),
|
||||
})
|
||||
.collect())
|
||||
}
|
||||
|
||||
pub async fn find_wifi_iface() -> Result<Option<String>, Error> {
|
||||
let mut ifaces = tokio::fs::read_dir("/sys/class/net").await?;
|
||||
while let Some(iface) = ifaces.next_entry().await? {
|
||||
if tokio::fs::metadata(iface.path().join("wireless"))
|
||||
.await
|
||||
.is_ok()
|
||||
{
|
||||
if let Some(iface) = iface.file_name().into_string().ok() {
|
||||
return Ok(Some(iface));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Ok(None)
|
||||
}
|
||||
|
||||
pub async fn find_eth_iface() -> Result<String, Error> {
|
||||
let mut ifaces = tokio::fs::read_dir("/sys/class/net").await?;
|
||||
while let Some(iface) = ifaces.next_entry().await? {
|
||||
if tokio::fs::metadata(iface.path().join("wireless"))
|
||||
.await
|
||||
.is_err()
|
||||
&& tokio::fs::metadata(iface.path().join("device"))
|
||||
.await
|
||||
.is_ok()
|
||||
{
|
||||
if let Some(iface) = iface.file_name().into_string().ok() {
|
||||
return Ok(iface);
|
||||
}
|
||||
}
|
||||
}
|
||||
Err(Error::new(
|
||||
eyre!("Could not detect ethernet interface"),
|
||||
crate::ErrorKind::Network,
|
||||
))
|
||||
}
|
||||
|
||||
// pub struct FDisk {
|
||||
// child: Child,
|
||||
// stdin: ChildStdin,
|
||||
// }
|
||||
// impl FDisk {
|
||||
// pub async fn command(&mut self, cmd: &[u8]) -> Result<(), Error> {
|
||||
|
||||
// }
|
||||
// }
|
||||
|
||||
pub fn partition_for(disk: impl AsRef<Path>, idx: usize) -> PathBuf {
|
||||
let disk_path = disk.as_ref();
|
||||
let (root, leaf) = if let (Some(root), Some(leaf)) = (
|
||||
disk_path.parent(),
|
||||
disk_path.file_name().and_then(|s| s.to_str()),
|
||||
) {
|
||||
(root, leaf)
|
||||
} else {
|
||||
return Default::default();
|
||||
};
|
||||
if leaf.ends_with(|c: char| c.is_ascii_digit()) {
|
||||
root.join(format!("{}p{}", leaf, idx))
|
||||
} else {
|
||||
root.join(format!("{}{}", leaf, idx))
|
||||
}
|
||||
}
|
||||
|
||||
#[command(display(display_none))]
|
||||
pub async fn execute(
|
||||
#[arg] logicalname: PathBuf,
|
||||
#[arg(short = 'o')] mut overwrite: bool,
|
||||
) -> Result<(), Error> {
|
||||
let disk = crate::disk::util::list(&Default::default())
|
||||
.await?
|
||||
.into_iter()
|
||||
.find(|d| &d.logicalname == &logicalname)
|
||||
.ok_or_else(|| {
|
||||
Error::new(
|
||||
eyre!("Unknown disk {}", logicalname.display()),
|
||||
crate::ErrorKind::DiskManagement,
|
||||
)
|
||||
})?;
|
||||
let eth_iface = find_eth_iface().await?;
|
||||
let wifi_iface = find_wifi_iface().await?;
|
||||
|
||||
overwrite |= disk.guid.is_none() && disk.partitions.iter().all(|p| p.guid.is_none());
|
||||
let sectors = (disk.capacity / 512) as u32;
|
||||
tokio::task::spawn_blocking(move || {
|
||||
let mut file = std::fs::File::options()
|
||||
.read(true)
|
||||
.write(true)
|
||||
.open(&logicalname)?;
|
||||
let (mut mbr, guid_part) = if overwrite {
|
||||
(MBR::new_from(&mut file, 512, rand::random())?, None)
|
||||
} else {
|
||||
let mut mbr = MBR::read_from(&mut file, 512)?;
|
||||
let mut guid_part = None;
|
||||
for (idx, part_info) in disk
|
||||
.partitions
|
||||
.iter()
|
||||
.enumerate()
|
||||
.map(|(idx, x)| (idx + 1, x))
|
||||
{
|
||||
if let Some(entry) = mbr.get_mut(idx) {
|
||||
if entry.starting_lba >= 33556480 {
|
||||
if idx < 3 {
|
||||
guid_part = Some(std::mem::replace(entry, MBRPartitionEntry::empty()))
|
||||
}
|
||||
break;
|
||||
}
|
||||
if part_info.guid.is_some() {
|
||||
return Err(Error::new(
|
||||
eyre!("Not enough space before embassy data"),
|
||||
crate::ErrorKind::InvalidRequest,
|
||||
));
|
||||
}
|
||||
*entry = MBRPartitionEntry::empty();
|
||||
}
|
||||
}
|
||||
(mbr, guid_part)
|
||||
};
|
||||
|
||||
mbr[1] = MBRPartitionEntry {
|
||||
boot: 0x80,
|
||||
first_chs: CHS::empty(),
|
||||
sys: 0x0b,
|
||||
last_chs: CHS::empty(),
|
||||
starting_lba: 2048,
|
||||
sectors: 2099200 - 2048,
|
||||
};
|
||||
mbr[2] = MBRPartitionEntry {
|
||||
boot: 0,
|
||||
first_chs: CHS::empty(),
|
||||
sys: 0x83,
|
||||
last_chs: CHS::empty(),
|
||||
starting_lba: 2099200,
|
||||
sectors: 33556480 - 2099200,
|
||||
};
|
||||
|
||||
if overwrite {
|
||||
mbr[3] = MBRPartitionEntry {
|
||||
boot: 0,
|
||||
first_chs: CHS::empty(),
|
||||
sys: 0x8e,
|
||||
last_chs: CHS::empty(),
|
||||
starting_lba: 33556480,
|
||||
sectors: sectors - 33556480,
|
||||
}
|
||||
} else if let Some(guid_part) = guid_part {
|
||||
mbr[3] = guid_part;
|
||||
}
|
||||
mbr.write_into(&mut file)?;
|
||||
|
||||
Ok(())
|
||||
})
|
||||
.await
|
||||
.unwrap()?;
|
||||
|
||||
let boot_part = partition_for(&disk.logicalname, 1);
|
||||
let root_part = partition_for(&disk.logicalname, 2);
|
||||
|
||||
Command::new("mkfs.vfat")
|
||||
.arg(&boot_part)
|
||||
.invoke(crate::ErrorKind::DiskManagement)
|
||||
.await?;
|
||||
Command::new("fatlabel")
|
||||
.arg(&boot_part)
|
||||
.arg("boot")
|
||||
.invoke(crate::ErrorKind::DiskManagement)
|
||||
.await?;
|
||||
|
||||
Command::new("mkfs.ext4")
|
||||
.arg(&root_part)
|
||||
.invoke(crate::ErrorKind::DiskManagement)
|
||||
.await?;
|
||||
Command::new("e2label")
|
||||
.arg(&root_part)
|
||||
.arg("rootfs")
|
||||
.invoke(crate::ErrorKind::DiskManagement)
|
||||
.await?;
|
||||
|
||||
let rootfs = TmpMountGuard::mount(&BlockDev::new(&root_part), ReadWrite).await?;
|
||||
tokio::fs::create_dir(rootfs.as_ref().join("config")).await?;
|
||||
tokio::fs::create_dir(rootfs.as_ref().join("next")).await?;
|
||||
let current = rootfs.as_ref().join("current");
|
||||
tokio::fs::create_dir(¤t).await?;
|
||||
|
||||
tokio::fs::create_dir(current.join("boot")).await?;
|
||||
let boot =
|
||||
MountGuard::mount(&BlockDev::new(&boot_part), current.join("boot"), ReadWrite).await?;
|
||||
|
||||
Command::new("unsquashfs")
|
||||
.arg("-n")
|
||||
.arg("-f")
|
||||
.arg("-d")
|
||||
.arg(¤t)
|
||||
.arg("/cdrom/casper/filesystem.squashfs")
|
||||
.invoke(crate::ErrorKind::Filesystem)
|
||||
.await?;
|
||||
|
||||
tokio::fs::write(
|
||||
rootfs.as_ref().join("config/config.yaml"),
|
||||
IoFormat::Yaml.to_vec(&PostInstallConfig {
|
||||
os_partitions: OsPartitionInfo {
|
||||
boot: boot_part.clone(),
|
||||
root: root_part.clone(),
|
||||
},
|
||||
ethernet_interface: eth_iface,
|
||||
wifi_interface: wifi_iface,
|
||||
})?,
|
||||
)
|
||||
.await?;
|
||||
|
||||
tokio::fs::write(
|
||||
current.join("etc/fstab"),
|
||||
format!(
|
||||
include_str!("fstab.template"),
|
||||
boot = boot_part.display(),
|
||||
root = root_part.display()
|
||||
),
|
||||
)
|
||||
.await?;
|
||||
|
||||
Command::new("chroot")
|
||||
.arg(¤t)
|
||||
.arg("systemd-machine-id-setup")
|
||||
.invoke(crate::ErrorKind::Unknown) // TODO systemd
|
||||
.await?;
|
||||
|
||||
Command::new("chroot")
|
||||
.arg(¤t)
|
||||
.arg("ssh-keygen")
|
||||
.arg("-A")
|
||||
.invoke(crate::ErrorKind::Unknown) // TODO ssh
|
||||
.await?;
|
||||
|
||||
let dev = MountGuard::mount(&Bind::new("/dev"), current.join("dev"), ReadWrite).await?;
|
||||
let sys = MountGuard::mount(&Bind::new("/sys"), current.join("sys"), ReadWrite).await?;
|
||||
let proc = MountGuard::mount(&Bind::new("/proc"), current.join("proc"), ReadWrite).await?;
|
||||
|
||||
Command::new("chroot")
|
||||
.arg(¤t)
|
||||
.arg("update-grub")
|
||||
.invoke(crate::ErrorKind::Unknown) // TODO grub
|
||||
.await?;
|
||||
Command::new("chroot")
|
||||
.arg(¤t)
|
||||
.arg("grub-install")
|
||||
.arg(&disk.logicalname)
|
||||
.invoke(crate::ErrorKind::Unknown) // TODO grub
|
||||
.await?;
|
||||
|
||||
dev.unmount().await?;
|
||||
sys.unmount().await?;
|
||||
proc.unmount().await?;
|
||||
boot.unmount().await?;
|
||||
rootfs.unmount().await?;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[command(display(display_none))]
|
||||
pub async fn reboot() -> Result<(), Error> {
|
||||
Command::new("sync")
|
||||
.invoke(crate::ErrorKind::Filesystem)
|
||||
.await?;
|
||||
Command::new("reboot")
|
||||
.invoke(crate::ErrorKind::Filesystem)
|
||||
.await?;
|
||||
Ok(())
|
||||
}
|
||||
Reference in New Issue
Block a user