From 02bce4ed6104a9c63815b9bba899234dd61b7551 Mon Sep 17 00:00:00 2001 From: Aiden McClelland Date: Tue, 6 Jan 2026 17:54:10 -0700 Subject: [PATCH] start consolidating --- Makefile | 9 +- core/src/backup/restore.rs | 10 +- core/src/bins/start_init.rs | 44 ++------ core/src/bins/startd.rs | 13 +-- core/src/context/cli.rs | 21 +--- core/src/context/config.rs | 3 - core/src/context/diagnostic.rs | 6 +- core/src/context/install.rs | 44 -------- core/src/context/mod.rs | 2 - core/src/context/rpc.rs | 10 +- core/src/context/setup.rs | 20 ++-- core/src/disk/main.rs | 11 +- core/src/disk/mod.rs | 2 + core/src/disk/util.rs | 12 +-- core/src/lib.rs | 7 +- core/src/main/startbox.rs | 5 - core/src/net/static_server.rs | 16 +-- core/src/os_install/gpt.rs | 14 ++- core/src/os_install/mbr.rs | 12 ++- core/src/os_install/mod.rs | 180 +++++++++++++++------------------ core/src/setup.rs | 115 ++++++++++++++------- core/src/shutdown.rs | 4 +- 22 files changed, 232 insertions(+), 328 deletions(-) delete mode 100644 core/src/context/install.rs diff --git a/Makefile b/Makefile index dc4e1e27f..d7772e371 100644 --- a/Makefile +++ b/Makefile @@ -12,8 +12,8 @@ RUST_ARCH := $(shell if [ "$(ARCH)" = "riscv64" ]; then echo riscv64gc; else ech REGISTRY_BASENAME := $(shell PROJECT=start-registry PLATFORM=$(ARCH) ./build/env/basename.sh) TUNNEL_BASENAME := $(shell PROJECT=start-tunnel PLATFORM=$(ARCH) ./build/env/basename.sh) IMAGE_TYPE=$(shell if [ "$(PLATFORM)" = raspberrypi ]; then echo img; else echo iso; fi) -WEB_UIS := web/dist/raw/ui/index.html web/dist/raw/setup-wizard/index.html web/dist/raw/install-wizard/index.html -COMPRESSED_WEB_UIS := web/dist/static/ui/index.html web/dist/static/setup-wizard/index.html web/dist/static/install-wizard/index.html +WEB_UIS := web/dist/raw/ui/index.html web/dist/raw/setup-wizard/index.html +COMPRESSED_WEB_UIS := web/dist/static/ui/index.html web/dist/static/setup-wizard/index.html FIRMWARE_ROMS := build/lib/firmware/$(PLATFORM) $(shell jq --raw-output '.[] | select(.platform[] | contains("$(PLATFORM)")) | "./build/lib/firmware/$(PLATFORM)/" + .id + ".rom.gz"' build/lib/firmware.json) BUILD_SRC := $(call ls-files, build/lib) build/lib/depends build/lib/conflicts $(FIRMWARE_ROMS) IMAGE_RECIPE_SRC := $(call ls-files, build/image-recipe/) @@ -22,7 +22,6 @@ CORE_SRC := $(call ls-files, core) $(shell git ls-files --recurse-submodules pat WEB_SHARED_SRC := $(call ls-files, web/projects/shared) $(call ls-files, web/projects/marketplace) $(shell ls -p web/ | grep -v / | sed 's/^/web\//g') web/node_modules/.package-lock.json web/config.json patch-db/client/dist/index.js sdk/baseDist/package.json web/patchdb-ui-seed.json sdk/dist/package.json WEB_UI_SRC := $(call ls-files, web/projects/ui) WEB_SETUP_WIZARD_SRC := $(call ls-files, web/projects/setup-wizard) -WEB_INSTALL_WIZARD_SRC := $(call ls-files, web/projects/install-wizard) WEB_START_TUNNEL_SRC := $(call ls-files, web/projects/start-tunnel) PATCH_DB_CLIENT_SRC := $(shell git ls-files --recurse-submodules patch-db/client) GZIP_BIN := $(shell which pigz || which gzip) @@ -333,10 +332,6 @@ web/dist/raw/setup-wizard/index.html: $(WEB_SETUP_WIZARD_SRC) $(WEB_SHARED_SRC) npm --prefix web run build:setup touch web/dist/raw/setup-wizard/index.html -web/dist/raw/install-wizard/index.html: $(WEB_INSTALL_WIZARD_SRC) $(WEB_SHARED_SRC) web/.angular/.updated - npm --prefix web run build:install - touch web/dist/raw/install-wizard/index.html - web/dist/raw/start-tunnel/index.html: $(WEB_START_TUNNEL_SRC) $(WEB_SHARED_SRC) web/.angular/.updated npm --prefix web run build:tunnel touch web/dist/raw/start-tunnel/index.html diff --git a/core/src/backup/restore.rs b/core/src/backup/restore.rs index daddd2248..17060db05 100644 --- a/core/src/backup/restore.rs +++ b/core/src/backup/restore.rs @@ -75,9 +75,9 @@ pub async fn restore_packages_rpc( } #[instrument(skip_all)] -pub async fn recover_full_embassy( +pub async fn recover_full_server( ctx: &SetupContext, - disk_guid: Arc, + disk_guid: InternedString, start_os_password: String, recovery_source: TmpMountGuard, server_id: &str, @@ -116,11 +116,13 @@ pub async fn recover_full_embassy( .await?; drop(db); - let init_result = init(&ctx.webserver, &ctx.config, init_phases).await?; + let config = ctx.config.peek(|c| c.clone()); + + let init_result = init(&ctx.webserver, &config, init_phases).await?; let rpc_ctx = RpcContext::init( &ctx.webserver, - &ctx.config, + &config, disk_guid.clone(), Some(init_result), rpc_ctx_phases, diff --git a/core/src/bins/start_init.rs b/core/src/bins/start_init.rs index fedc9f623..fe2d61e79 100644 --- a/core/src/bins/start_init.rs +++ b/core/src/bins/start_init.rs @@ -1,11 +1,9 @@ -use std::sync::Arc; - use tokio::process::Command; use tracing::instrument; use crate::context::config::ServerConfig; use crate::context::rpc::InitRpcContextPhases; -use crate::context::{DiagnosticContext, InitContext, InstallContext, RpcContext, SetupContext}; +use crate::context::{DiagnosticContext, InitContext, RpcContext, SetupContext}; use crate::disk::REPAIR_DISK_PATH; use crate::disk::fsck::RepairStrategy; use crate::disk::main::DEFAULT_PASSWORD; @@ -79,40 +77,11 @@ async fn setup_or_init( .invoke(crate::ErrorKind::OpenSsl) .await?; - if tokio::fs::metadata("/run/live/medium").await.is_ok() { - Command::new("sed") - .arg("-i") - .arg("s/PasswordAuthentication no/PasswordAuthentication yes/g") - .arg("/etc/ssh/sshd_config") - .invoke(crate::ErrorKind::Filesystem) - .await?; - Command::new("systemctl") - .arg("reload") - .arg("ssh") - .invoke(crate::ErrorKind::OpenSsh) - .await?; - - let ctx = InstallContext::init().await?; - - server.serve_ui_for(ctx.clone()); - - ctx.shutdown - .subscribe() - .recv() - .await - .expect("context dropped"); - - return Ok(Err(Shutdown { - disk_guid: None, - restart: true, - })); - } - if tokio::fs::metadata("/media/startos/config/disk.guid") .await .is_err() { - let ctx = SetupContext::init(server, config)?; + let ctx = SetupContext::init(server, config.clone())?; server.serve_ui_for(ctx.clone()); @@ -156,9 +125,9 @@ async fn setup_or_init( disk_phase.start(); let guid_string = tokio::fs::read_to_string("/media/startos/config/disk.guid") // unique identifier for volume group - keeps track of the disk that goes with your embassy .await?; - let disk_guid = Arc::new(String::from(guid_string.trim())); + let disk_guid = InternedString::intern(guid_string.trim()); let requires_reboot = crate::disk::main::import( - &**disk_guid, + &*disk_guid, DATA_DIR, if tokio::fs::metadata(REPAIR_DISK_PATH).await.is_ok() { RepairStrategy::Aggressive @@ -236,11 +205,10 @@ pub async fn main( .await .is_ok() { - Some(Arc::new( + Some(InternedString::intern( tokio::fs::read_to_string("/media/startos/config/disk.guid") // unique identifier for volume group - keeps track of the disk that goes with your embassy .await? - .trim() - .to_owned(), + .trim(), )) } else { None diff --git a/core/src/bins/startd.rs b/core/src/bins/startd.rs index 86c391508..a06162a0d 100644 --- a/core/src/bins/startd.rs +++ b/core/src/bins/startd.rs @@ -1,6 +1,5 @@ use std::cmp::max; use std::ffi::OsString; -use std::sync::Arc; use std::time::Duration; use clap::Parser; @@ -15,11 +14,11 @@ use crate::context::{DiagnosticContext, InitContext, RpcContext}; use crate::net::gateway::{BindTcp, SelfContainedNetworkInterfaceListener, UpgradableListener}; use crate::net::static_server::refresher; use crate::net::web_server::{Acceptor, WebServer}; +use crate::prelude::*; use crate::shutdown::Shutdown; use crate::system::launch_metrics_task; use crate::util::io::append_file; use crate::util::logger::LOGGER; -use crate::{Error, ErrorKind, ResultExt}; #[instrument(skip_all)] async fn inner_main( @@ -53,11 +52,10 @@ async fn inner_main( let ctx = RpcContext::init( &server.acceptor_setter(), config, - Arc::new( + InternedString::intern( tokio::fs::read_to_string("/media/startos/config/disk.guid") // unique identifier for volume group - keeps track of the disk that goes with your embassy .await? - .trim() - .to_owned(), + .trim(), ), None, rpc_ctx_phases, @@ -167,11 +165,10 @@ pub fn main(args: impl IntoIterator) { .await .is_ok() { - Some(Arc::new( + Some(InternedString::intern( tokio::fs::read_to_string("/media/startos/config/disk.guid") // unique identifier for volume group - keeps track of the disk that goes with your embassy .await? - .trim() - .to_owned(), + .trim(), )) } else { None diff --git a/core/src/context/cli.rs b/core/src/context/cli.rs index 5aeda762e..4346f304c 100644 --- a/core/src/context/cli.rs +++ b/core/src/context/cli.rs @@ -23,7 +23,7 @@ use tracing::instrument; use super::setup::CURRENT_SECRET; use crate::context::config::{ClientConfig, local_config_path}; -use crate::context::{DiagnosticContext, InitContext, InstallContext, RpcContext, SetupContext}; +use crate::context::{DiagnosticContext, InitContext, RpcContext, SetupContext}; use crate::developer::{OS_DEVELOPER_KEY_PATH, default_developer_key_path}; use crate::middleware::auth::local::LocalAuthContext; use crate::prelude::*; @@ -394,22 +394,3 @@ impl CallRemote for CliContext { .await } } -impl CallRemote for CliContext { - async fn call_remote( - &self, - method: &str, - _: OrdMap<&'static str, Value>, - params: Value, - _: Empty, - ) -> Result { - crate::middleware::auth::signature::call_remote( - self, - self.rpc_url.clone(), - HeaderMap::new(), - self.rpc_url.host_str(), - method, - params, - ) - .await - } -} diff --git a/core/src/context/config.rs b/core/src/context/config.rs index 6f777670b..a92fcaa05 100644 --- a/core/src/context/config.rs +++ b/core/src/context/config.rs @@ -111,8 +111,6 @@ impl ClientConfig { pub struct ServerConfig { #[arg(short, long)] pub config: Option, - #[arg(long)] - pub ethernet_interface: Option, #[arg(skip)] pub os_partitions: Option, #[arg(long)] @@ -131,7 +129,6 @@ impl ContextConfig for ServerConfig { self.config.take() } fn merge_with(&mut self, other: Self) { - self.ethernet_interface = self.ethernet_interface.take().or(other.ethernet_interface); self.os_partitions = self.os_partitions.take().or(other.os_partitions); self.socks_listen = self.socks_listen.take().or(other.socks_listen); self.revision_cache_size = self diff --git a/core/src/context/diagnostic.rs b/core/src/context/diagnostic.rs index e1bff2466..5c49ebc86 100644 --- a/core/src/context/diagnostic.rs +++ b/core/src/context/diagnostic.rs @@ -6,15 +6,15 @@ use rpc_toolkit::yajrc::RpcError; use tokio::sync::broadcast::Sender; use tracing::instrument; -use crate::Error; use crate::context::config::ServerConfig; +use crate::prelude::*; use crate::rpc_continuations::RpcContinuations; use crate::shutdown::Shutdown; pub struct DiagnosticContextSeed { pub shutdown: Sender, pub error: Arc, - pub disk_guid: Option>, + pub disk_guid: Option, pub rpc_continuations: RpcContinuations, } @@ -24,7 +24,7 @@ impl DiagnosticContext { #[instrument(skip_all)] pub fn init( _config: &ServerConfig, - disk_guid: Option>, + disk_guid: Option, error: Error, ) -> Result { tracing::error!("Error: {}: Starting diagnostic UI", error); diff --git a/core/src/context/install.rs b/core/src/context/install.rs deleted file mode 100644 index 04717cf93..000000000 --- a/core/src/context/install.rs +++ /dev/null @@ -1,44 +0,0 @@ -use std::ops::Deref; -use std::sync::Arc; - -use rpc_toolkit::Context; -use tokio::sync::broadcast::Sender; -use tracing::instrument; - -use crate::Error; -use crate::net::utils::find_eth_iface; -use crate::rpc_continuations::RpcContinuations; - -pub struct InstallContextSeed { - pub ethernet_interface: String, - pub shutdown: Sender<()>, - pub rpc_continuations: RpcContinuations, -} - -#[derive(Clone)] -pub struct InstallContext(Arc); -impl InstallContext { - #[instrument(skip_all)] - pub async fn init() -> Result { - let (shutdown, _) = tokio::sync::broadcast::channel(1); - Ok(Self(Arc::new(InstallContextSeed { - ethernet_interface: find_eth_iface().await?, - shutdown, - rpc_continuations: RpcContinuations::new(), - }))) - } -} - -impl AsRef for InstallContext { - fn as_ref(&self) -> &RpcContinuations { - &self.rpc_continuations - } -} - -impl Context for InstallContext {} -impl Deref for InstallContext { - type Target = InstallContextSeed; - fn deref(&self) -> &Self::Target { - &*self.0 - } -} diff --git a/core/src/context/mod.rs b/core/src/context/mod.rs index efe261b0c..3c6cff2ef 100644 --- a/core/src/context/mod.rs +++ b/core/src/context/mod.rs @@ -2,13 +2,11 @@ pub mod cli; pub mod config; pub mod diagnostic; pub mod init; -pub mod install; pub mod rpc; pub mod setup; pub use cli::CliContext; pub use diagnostic::DiagnosticContext; pub use init::InitContext; -pub use install::InstallContext; pub use rpc::RpcContext; pub use setup::SetupContext; diff --git a/core/src/context/rpc.rs b/core/src/context/rpc.rs index 6988e5b75..c934459c3 100644 --- a/core/src/context/rpc.rs +++ b/core/src/context/rpc.rs @@ -60,7 +60,7 @@ pub struct RpcContextSeed { pub os_partitions: OsPartitionInfo, pub wifi_interface: Option, pub ethernet_interface: String, - pub disk_guid: Arc, + pub disk_guid: InternedString, pub ephemeral_sessions: SyncMutex, pub db: TypedPatchDb, pub sync_db: watch::Sender, @@ -134,7 +134,7 @@ impl RpcContext { pub async fn init( webserver: &WebServerAcceptorSetter, config: &ServerConfig, - disk_guid: Arc, + disk_guid: InternedString, init_result: Option, InitRpcContextPhases { mut load_db, @@ -340,11 +340,7 @@ impl RpcContext { ) })?, wifi_interface: wifi_interface.clone(), - ethernet_interface: if let Some(eth) = config.ethernet_interface.clone() { - eth - } else { - find_eth_iface().await? - }, + ethernet_interface: find_eth_iface().await?, disk_guid, ephemeral_sessions: SyncMutex::new(Sessions::new()), sync_db: watch::Sender::new(db.sequence().await), diff --git a/core/src/context/setup.rs b/core/src/context/setup.rs index ecef31f83..16303f9f2 100644 --- a/core/src/context/setup.rs +++ b/core/src/context/setup.rs @@ -18,7 +18,7 @@ use crate::MAIN_DATA; use crate::account::AccountInfo; use crate::context::RpcContext; use crate::context::config::ServerConfig; -use crate::disk::OsPartitionInfo; +use crate::disk::mount::guard::TmpMountGuard; use crate::hostname::Hostname; use crate::net::gateway::UpgradableListener; use crate::net::web_server::{WebServer, WebServerAcceptorSetter}; @@ -28,6 +28,7 @@ use crate::rpc_continuations::{Guid, RpcContinuation, RpcContinuations}; use crate::setup::SetupProgress; use crate::shutdown::Shutdown; use crate::util::future::NonDetachingJoinHandle; +use crate::util::sync::SyncMutex; lazy_static::lazy_static! { pub static ref CURRENT_SECRET: Jwk = Jwk::generate_ec_key(josekit::jwk::alg::ec::EcCurve::P256).unwrap_or_else(|e| { @@ -66,15 +67,15 @@ impl TryFrom<&AccountInfo> for SetupResult { pub struct SetupContextSeed { pub webserver: WebServerAcceptorSetter, - pub config: ServerConfig, - pub os_partitions: OsPartitionInfo, + pub config: SyncMutex, pub disable_encryption: bool, pub progress: FullProgressTracker, pub task: OnceCell>, pub result: OnceCell>, - pub disk_guid: OnceCell>, + pub disk_guid: OnceCell, pub shutdown: Sender>, pub rpc_continuations: RpcContinuations, + pub install_rootfs: SyncMutex>, } #[derive(Clone)] @@ -83,27 +84,22 @@ impl SetupContext { #[instrument(skip_all)] pub fn init( webserver: &WebServer, - config: &ServerConfig, + config: ServerConfig, ) -> Result { let (shutdown, _) = tokio::sync::broadcast::channel(1); let mut progress = FullProgressTracker::new(); progress.enable_logging(true); Ok(Self(Arc::new(SetupContextSeed { webserver: webserver.acceptor_setter(), - config: config.clone(), - os_partitions: config.os_partitions.clone().ok_or_else(|| { - Error::new( - eyre!("missing required configuration: `os-partitions`"), - ErrorKind::NotFound, - ) - })?, disable_encryption: config.disable_encryption.unwrap_or(false), + config: SyncMutex::new(config), progress, task: OnceCell::new(), result: OnceCell::new(), disk_guid: OnceCell::new(), shutdown, rpc_continuations: RpcContinuations::new(), + install_rootfs: SyncMutex::new(None), }))) } #[instrument(skip_all)] diff --git a/core/src/disk/main.rs b/core/src/disk/main.rs index af4c6e354..af043dd69 100644 --- a/core/src/disk/main.rs +++ b/core/src/disk/main.rs @@ -2,6 +2,7 @@ use std::collections::BTreeMap; use std::path::{Path, PathBuf}; use color_eyre::eyre::eyre; +use imbl_value::InternedString; use tokio::process::Command; use tracing::instrument; @@ -20,10 +21,10 @@ pub const MAIN_FS_SIZE: FsSize = FsSize::Gigabytes(8); #[instrument(skip_all)] pub async fn create( disks: &I, - pvscan: &BTreeMap>, + pvscan: &BTreeMap>, datadir: impl AsRef, password: Option<&str>, -) -> Result +) -> Result where for<'a> &'a I: IntoIterator, P: AsRef, @@ -37,9 +38,9 @@ where #[instrument(skip_all)] pub async fn create_pool( disks: &I, - pvscan: &BTreeMap>, + pvscan: &BTreeMap>, encrypted: bool, -) -> Result +) -> Result where for<'a> &'a I: IntoIterator, P: AsRef, @@ -79,7 +80,7 @@ where cmd.arg(disk.as_ref()); } cmd.invoke(crate::ErrorKind::DiskManagement).await?; - Ok(guid) + Ok(guid.into()) } #[derive(Debug, Clone, Copy)] diff --git a/core/src/disk/mod.rs b/core/src/disk/mod.rs index 7857dbdca..63d1757b6 100644 --- a/core/src/disk/mod.rs +++ b/core/src/disk/mod.rs @@ -25,6 +25,8 @@ pub struct OsPartitionInfo { pub bios: Option, pub boot: PathBuf, pub root: PathBuf, + #[serde(skip)] // internal use only + pub data: Option, } impl OsPartitionInfo { pub fn contains(&self, logicalname: impl AsRef) -> bool { diff --git a/core/src/disk/util.rs b/core/src/disk/util.rs index 596b31f2b..2ec9dd9f3 100644 --- a/core/src/disk/util.rs +++ b/core/src/disk/util.rs @@ -20,9 +20,9 @@ use super::mount::guard::TmpMountGuard; use crate::disk::OsPartitionInfo; use crate::disk::mount::guard::GenericMountGuard; use crate::hostname::Hostname; +use crate::prelude::*; use crate::util::Invoke; use crate::util::serde::IoFormat; -use crate::{Error, ResultExt as _}; #[derive(Clone, Copy, Debug, Deserialize, Serialize)] #[serde(rename_all = "camelCase")] @@ -40,7 +40,7 @@ pub struct DiskInfo { pub model: Option, pub partitions: Vec, pub capacity: u64, - pub guid: Option, + pub guid: Option, } #[derive(Clone, Debug, Deserialize, Serialize)] @@ -51,7 +51,7 @@ pub struct PartitionInfo { pub capacity: u64, pub used: Option, pub start_os: BTreeMap, - pub guid: Option, + pub guid: Option, } #[derive(Clone, Debug, Default, Deserialize, Serialize)] @@ -215,7 +215,7 @@ pub async fn get_percentage>(path: P) -> Result { } #[instrument(skip_all)] -pub async fn pvscan() -> Result>, Error> { +pub async fn pvscan() -> Result>, Error> { let pvscan_out = Command::new("pvscan") .invoke(crate::ErrorKind::DiskManagement) .await?; @@ -448,7 +448,7 @@ async fn part_info(part: PathBuf) -> PartitionInfo { } } -fn parse_pvscan_output(pvscan_output: &str) -> BTreeMap> { +fn parse_pvscan_output(pvscan_output: &str) -> BTreeMap> { fn parse_line(line: &str) -> IResult<&str, (&str, Option<&str>)> { let pv_parse = preceded( tag(" PV "), @@ -471,7 +471,7 @@ fn parse_pvscan_output(pvscan_output: &str) -> BTreeMap> for entry in entries { match parse_line(entry) { Ok((_, (pv, vg))) => { - ret.insert(PathBuf::from(pv), vg.map(|s| s.to_owned())); + ret.insert(PathBuf::from(pv), vg.map(InternedString::intern)); } Err(_) => { tracing::warn!("Failed to parse pvscan output line: {}", entry); diff --git a/core/src/lib.rs b/core/src/lib.rs index a371fb49f..347d46002 100644 --- a/core/src/lib.rs +++ b/core/src/lib.rs @@ -242,12 +242,7 @@ pub fn main_api() -> ParentHandler { .with_about("Commands to display logs, restart the server, etc"), ) .subcommand("init", init::init_api::()) - .subcommand("setup", setup::setup::()) - .subcommand( - "install", - os_install::install::() - .with_about("Commands to list disk info, install StartOS, and reboot"), - ); + .subcommand("setup", setup::setup::()); if &*PLATFORM != "raspberrypi" { api = api.subcommand("kiosk", kiosk::()); } diff --git a/core/src/main/startbox.rs b/core/src/main/startbox.rs index d322aaeb7..050f02c2f 100644 --- a/core/src/main/startbox.rs +++ b/core/src/main/startbox.rs @@ -11,11 +11,6 @@ fn main() { "$CARGO_MANIFEST_DIR/../web/dist/static/setup-wizard" )) .ok(); - startos::net::static_server::INSTALL_WIZARD_CELL - .set(include_dir::include_dir!( - "$CARGO_MANIFEST_DIR/../web/dist/static/install-wizard" - )) - .ok(); #[cfg(not(feature = "beta"))] startos::db::model::public::DB_UI_SEED_CELL .set(include_str!(concat!( diff --git a/core/src/net/static_server.rs b/core/src/net/static_server.rs index 639fd3a2e..08af326b6 100644 --- a/core/src/net/static_server.rs +++ b/core/src/net/static_server.rs @@ -30,7 +30,7 @@ use tokio::io::{AsyncRead, AsyncReadExt, AsyncSeekExt, BufReader}; use tokio_util::io::ReaderStream; use url::Url; -use crate::context::{DiagnosticContext, InitContext, InstallContext, RpcContext, SetupContext}; +use crate::context::{DiagnosticContext, InitContext, RpcContext, SetupContext}; use crate::hostname::Hostname; use crate::middleware::auth::Auth; use crate::middleware::auth::session::ValidSessionToken; @@ -178,20 +178,6 @@ impl UiContext for SetupContext { } } -pub static INSTALL_WIZARD_CELL: OnceLock> = OnceLock::new(); - -impl UiContext for InstallContext { - fn ui_dir() -> &'static Dir<'static> { - INSTALL_WIZARD_CELL.get().unwrap_or(&EMPTY_DIR) - } - fn api() -> ParentHandler { - main_api() - } - fn middleware(server: Server) -> HttpServer { - server.middleware(Cors::new()) - } -} - pub fn rpc_router>( ctx: C, server: HttpServer, diff --git a/core/src/os_install/gpt.rs b/core/src/os_install/gpt.rs index 491c62431..9625209e3 100644 --- a/core/src/os_install/gpt.rs +++ b/core/src/os_install/gpt.rs @@ -9,7 +9,7 @@ use crate::os_install::partition_for; use crate::prelude::*; pub async fn partition(disk: &DiskInfo, overwrite: bool) -> Result { - let efi = { + let (efi, data_part) = { let disk = disk.clone(); tokio::task::spawn_blocking(move || { let use_efi = Path::new("/sys/firmware/efi").exists(); @@ -93,6 +93,7 @@ pub async fn partition(disk: &DiskInfo, overwrite: bool) -> Result Result Result Result Result { - { + let data_part = { let sectors = (disk.capacity / 512) as u32; let disk = disk.clone(); tokio::task::spawn_blocking(move || { @@ -59,6 +59,7 @@ pub async fn partition(disk: &DiskInfo, overwrite: bool) -> Result Result() -> ParentHandler { - ParentHandler::new() - .subcommand("disk", disk::().with_about("Command to list disk info")) - .subcommand( - "execute", - from_fn_async(execute::) - .no_display() - .with_about("Install StartOS over existing version") - .with_call_remote::(), - ) - .subcommand( - "reboot", - from_fn_async(reboot) - .no_display() - .with_about("Restart the server") - .with_call_remote::(), - ) -} - -pub fn disk() -> ParentHandler { - ParentHandler::new().subcommand( - "list", - from_fn_async(list) - .no_display() - .with_about("List disk info") - .with_call_remote::(), - ) -} - -pub async fn list(_: InstallContext) -> Result, Error> { - let skip = match async { - Ok::<_, Error>( - Path::new( - &String::from_utf8( - Command::new("grub-probe-default") - .arg("-t") - .arg("disk") - .arg("/run/live/medium") - .invoke(crate::ErrorKind::Grub) - .await?, - )? - .trim(), - ) - .to_owned(), - ) - } - .await - { - Ok(a) => Some(a), - Err(e) => { - tracing::error!("Could not determine live usb device: {}", e); - tracing::debug!("{:?}", e); - None - } - }; - Ok(crate::disk::util::list(&Default::default()) - .await? - .into_iter() - .filter(|i| Some(&*i.logicalname) != skip.as_deref()) - .collect()) -} - -pub fn partition_for(disk: impl AsRef, idx: usize) -> PathBuf { +pub fn partition_for(disk: impl AsRef, idx: u32) -> PathBuf { let disk_path = disk.as_ref(); let (root, leaf) = if let (Some(root), Some(leaf)) = ( disk_path.parent(), @@ -122,34 +59,52 @@ async fn partition(disk: &mut DiskInfo, overwrite: bool) -> Result, } -pub async fn execute( - _: C, - ExecuteParams { - logicalname, - mut overwrite, - }: ExecuteParams, -) -> Result<(), Error> { - let mut disk = crate::disk::util::list(&Default::default()) - .await? - .into_iter() - .find(|d| &d.logicalname == &logicalname) +#[derive(Deserialize, Serialize, Parser, TS)] +#[serde(rename_all = "camelCase")] +#[command(rename_all = "kebab-case")] +struct DataDrive { + #[arg(long = "data-drive")] + logicalname: PathBuf, + #[arg(long)] + wipe: bool, +} + +pub async fn install_os( + ctx: SetupContext, + InstallOsParams { + os_drive, + data_drive, + }: InstallOsParams, +) -> Result { + let mut disks = crate::disk::util::list(&Default::default()).await?; + let disk = disks + .iter_mut() + .find(|d| &d.logicalname == &os_drive) .ok_or_else(|| { Error::new( - eyre!("Unknown disk {}", logicalname.display()), + eyre!("Unknown disk {}", os_drive.display()), crate::ErrorKind::DiskManagement, ) })?; - let eth_iface = find_eth_iface().await?; - overwrite |= disk.guid.is_none() && disk.partitions.iter().all(|p| p.guid.is_none()); + let overwrite = if let Some(data_drive) = &data_drive { + data_drive.wipe + || ((disk.guid.is_none() || disk.logicalname != data_drive.logicalname) + && disk + .partitions + .iter() + .all(|p| p.guid.is_none() || p.logicalname != data_drive.logicalname)) + } else { + true + }; - let part_info = partition(&mut disk, overwrite).await?; + let part_info = partition(disk, overwrite).await?; if let Some(efi) = &part_info.efi { Command::new("mkfs.vfat") @@ -271,11 +226,12 @@ pub async fn execute( rootfs.path().join("config/config.yaml"), IoFormat::Yaml.to_vec(&ServerConfig { os_partitions: Some(part_info.clone()), - ethernet_interface: Some(eth_iface), ..Default::default() })?, ) .await?; + ctx.config + .mutate(|c| c.os_partitions = Some(part_info.clone())); let lower = TmpMountGuard::mount(&BlockDev::new(&image_path), MountType::ReadOnly).await?; let work = config_path.join("work"); @@ -396,15 +352,47 @@ pub async fn execute( tokio::fs::remove_dir_all(&work).await?; lower.unmount().await?; - rootfs.unmount().await?; + let mut setup_info = SetupInfo::default(); - Ok(()) -} + if let Some(data_drive) = data_drive { + let mut logicalname = &*data_drive.logicalname; + if logicalname == &os_drive { + logicalname = part_info.data.as_deref().ok_or_else(|| { + Error::new( + eyre!("not enough room on OS drive for data"), + ErrorKind::InvalidRequest, + ) + })?; + } + if let Some(guid) = disks.iter().find_map(|d| { + d.guid + .as_ref() + .filter(|_| &d.logicalname == logicalname) + .cloned() + .or_else(|| { + d.partitions.iter().find_map(|p| { + p.guid + .as_ref() + .filter(|_| &p.logicalname == logicalname) + .cloned() + }) + }) + }) { + setup_info.guid = Some(guid); + setup_info.attach = true; + } else { + let guid = crate::setup::setup_data_drive(&ctx, logicalname).await?; + setup_info.guid = Some(guid); + } + } -pub async fn reboot(ctx: InstallContext) -> Result<(), Error> { - Command::new("sync") - .invoke(crate::ErrorKind::Filesystem) - .await?; - ctx.shutdown.send(()).unwrap(); - Ok(()) + write_file_atomic( + rootfs.path().join("config/setup.json"), + IoFormat::JsonPretty.to_vec(&setup_info)?, + ) + .await?; + + ctx.install_rootfs.replace(Some(rootfs)); + + Ok(setup_info) } diff --git a/core/src/setup.rs b/core/src/setup.rs index 1c1103961..e19620e35 100644 --- a/core/src/setup.rs +++ b/core/src/setup.rs @@ -1,6 +1,5 @@ use std::collections::BTreeMap; use std::path::{Path, PathBuf}; -use std::sync::Arc; use std::time::Duration; use color_eyre::eyre::eyre; @@ -18,7 +17,7 @@ use ts_rs::TS; use crate::account::AccountInfo; use crate::auth::write_shadow; -use crate::backup::restore::recover_full_embassy; +use crate::backup::restore::recover_full_server; use crate::backup::target::BackupTargetFS; use crate::context::rpc::InitRpcContextPhases; use crate::context::setup::SetupResult; @@ -40,7 +39,8 @@ use crate::shutdown::Shutdown; use crate::system::sync_kiosk; use crate::util::Invoke; use crate::util::crypto::EncryptedWire; -use crate::util::io::{Counter, create_file, dir_copy, dir_size}; +use crate::util::io::{Counter, create_file, dir_copy, dir_size, read_file_to_string}; +use crate::util::serde::IoFormat; use crate::{DATA_DIR, Error, ErrorKind, MAIN_DATA, PACKAGE_DATA, PLATFORM, ResultExt}; pub fn setup() -> ParentHandler { @@ -53,6 +53,10 @@ pub fn setup() -> ParentHandler { ) .subcommand("disk", disk::()) .subcommand("attach", from_fn_async(attach).no_cli()) + .subcommand( + "install-os", + from_fn_async(crate::os_install::install_os).no_cli(), + ) .subcommand("execute", from_fn_async(execute).no_cli()) .subcommand("cifs", cifs::()) .subcommand("complete", from_fn_async(complete).no_cli()) @@ -81,7 +85,12 @@ pub fn disk() -> ParentHandler { } pub async fn list_disks(ctx: SetupContext) -> Result, Error> { - crate::disk::util::list(&ctx.os_partitions).await + crate::disk::util::list( + &ctx.config + .peek(|c| c.os_partitions.clone()) + .unwrap_or_default(), + ) + .await } #[instrument(skip_all)] @@ -91,7 +100,7 @@ async fn setup_init( kiosk: Option, init_phases: InitPhases, ) -> Result<(AccountInfo, InitResult), Error> { - let init_result = init(&ctx.webserver, &ctx.config, init_phases).await?; + let init_result = init(&ctx.webserver, &ctx.config.peek(|c| c.clone()), init_phases).await?; let account = init_result .net_ctrl @@ -127,10 +136,10 @@ async fn setup_init( #[ts(export)] pub struct AttachParams { #[serde(rename = "startOsPassword")] - password: Option, - guid: Arc, + pub password: Option, + pub guid: InternedString, #[ts(optional)] - kiosk: Option, + pub kiosk: Option, } #[instrument(skip_all)] @@ -193,7 +202,7 @@ pub async fn attach( let (account, net_ctrl) = setup_init(&setup_ctx, password, kiosk, init_phases).await?; - let rpc_ctx = RpcContext::init(&setup_ctx.webserver, &setup_ctx.config, disk_guid, Some(net_ctrl), rpc_ctx_phases).await?; + let rpc_ctx = RpcContext::init(&setup_ctx.webserver, &setup_ctx.config.peek(|c| c.clone()), disk_guid, Some(net_ctrl), rpc_ctx_phases).await?; Ok(((&account).try_into()?, rpc_ctx)) })?; @@ -206,8 +215,17 @@ pub async fn attach( #[ts(export)] #[serde(tag = "status")] pub enum SetupStatusRes { - Complete(SetupResult), + NeedsInstall, + Incomplete(SetupInfo), Running(SetupProgress), + Complete(SetupResult), +} + +#[derive(Default, Debug, Deserialize, Serialize, TS)] +#[serde(rename_all = "camelCase")] +pub struct SetupInfo { + pub guid: Option, + pub attach: bool, } #[derive(Debug, Deserialize, Serialize, TS)] @@ -218,17 +236,30 @@ pub struct SetupProgress { pub guid: Guid, } -pub async fn status(ctx: SetupContext) -> Result, Error> { +pub async fn status(ctx: SetupContext) -> Result { if let Some(res) = ctx.result.get() { match res { - Ok((res, _)) => Ok(Some(SetupStatusRes::Complete(res.clone()))), + Ok((res, _)) => Ok(SetupStatusRes::Complete(res.clone())), Err(e) => Err(e.clone_output()), } } else { if ctx.task.initialized() { - Ok(Some(SetupStatusRes::Running(ctx.progress().await))) + Ok(SetupStatusRes::Running(ctx.progress().await)) } else { - Ok(None) + let path = if tokio::fs::metadata("/run/live/medium").await.is_ok() { + let Some(path) = ctx + .install_rootfs + .peek(|fs| fs.as_ref().map(|fs| fs.path().join("config/setup.json"))) + else { + return Ok(SetupStatusRes::NeedsInstall); + }; + path + } else { + Path::new("/media/startos/config/setup.json").to_path_buf() + }; + IoFormat::Json + .from_slice(read_file_to_string(path).await?.as_bytes()) + .map(SetupStatusRes::Incomplete) } } } @@ -304,6 +335,28 @@ pub enum RecoverySource { }, } +pub async fn setup_data_drive( + ctx: &SetupContext, + logicalname: &Path, +) -> Result { + let encryption_password = if ctx.disable_encryption { + None + } else { + Some(DEFAULT_PASSWORD) + }; + let guid = crate::disk::main::create( + &[logicalname], + &pvscan().await?, + DATA_DIR, + encryption_password, + ) + .await?; + let _ = crate::disk::main::import(&*guid, DATA_DIR, RepairStrategy::Preen, encryption_password) + .await?; + let _ = ctx.disk_guid.set(guid.clone()); + Ok(guid) +} + #[derive(Deserialize, Serialize, TS)] #[serde(rename_all = "camelCase")] #[ts(export)] @@ -432,23 +485,7 @@ pub async fn execute_inner( let rpc_ctx_phases = InitRpcContextPhases::new(&progress); disk_phase.start(); - let encryption_password = if ctx.disable_encryption { - None - } else { - Some(DEFAULT_PASSWORD) - }; - let guid = Arc::new( - crate::disk::main::create( - &[start_os_logicalname], - &pvscan().await?, - DATA_DIR, - encryption_password, - ) - .await?, - ); - let _ = crate::disk::main::import(&*guid, DATA_DIR, RepairStrategy::Preen, encryption_password) - .await?; - let _ = ctx.disk_guid.set(guid.clone()); + let guid = setup_data_drive(&ctx, &start_os_logicalname).await?; disk_phase.complete(); let progress = SetupExecuteProgress { @@ -490,7 +527,7 @@ pub struct SetupExecuteProgress { async fn fresh_setup( ctx: &SetupContext, - guid: Arc, + guid: InternedString, start_os_password: &str, kiosk: Option, SetupExecuteProgress { @@ -506,11 +543,13 @@ async fn fresh_setup( db.put(&ROOT, &Database::init(&account, kiosk)?).await?; drop(db); - let init_result = init(&ctx.webserver, &ctx.config, init_phases).await?; + let config = ctx.config.peek(|c| c.clone()); + + let init_result = init(&ctx.webserver, &config, init_phases).await?; let rpc_ctx = RpcContext::init( &ctx.webserver, - &ctx.config, + &config, guid, Some(init_result), rpc_ctx_phases, @@ -525,7 +564,7 @@ async fn fresh_setup( #[instrument(skip_all)] async fn recover( ctx: &SetupContext, - guid: Arc, + guid: InternedString, start_os_password: String, recovery_source: BackupTargetFS, server_id: String, @@ -534,7 +573,7 @@ async fn recover( progress: SetupExecuteProgress, ) -> Result<(SetupResult, RpcContext), Error> { let recovery_source = TmpMountGuard::mount(&recovery_source, ReadWrite).await?; - recover_full_embassy( + recover_full_server( ctx, guid.clone(), start_os_password, @@ -550,7 +589,7 @@ async fn recover( #[instrument(skip_all)] async fn migrate( ctx: &SetupContext, - guid: Arc, + guid: InternedString, old_guid: &str, start_os_password: String, kiosk: Option, @@ -636,7 +675,7 @@ async fn migrate( let rpc_ctx = RpcContext::init( &ctx.webserver, - &ctx.config, + &ctx.config.peek(|c| c.clone()), guid, Some(net_ctrl), rpc_ctx_phases, diff --git a/core/src/shutdown.rs b/core/src/shutdown.rs index 5df2317a3..9d5640001 100644 --- a/core/src/shutdown.rs +++ b/core/src/shutdown.rs @@ -1,5 +1,3 @@ -use std::sync::Arc; - use crate::PLATFORM; use crate::context::RpcContext; use crate::disk::main::export; @@ -10,7 +8,7 @@ use crate::util::Invoke; #[derive(Debug, Clone)] pub struct Shutdown { - pub disk_guid: Option>, + pub disk_guid: Option, pub restart: bool, } impl Shutdown {