use std::path::Path; use std::sync::Arc; use embassy::context::rpc::RpcContextConfig; use embassy::context::{DiagnosticContext, SetupContext}; use embassy::disk::main::DEFAULT_PASSWORD; use embassy::hostname::get_product_key; use embassy::middleware::cors::cors; use embassy::middleware::diagnostic::diagnostic; use embassy::middleware::encrypt::encrypt; use embassy::net::mdns::MdnsController; use embassy::sound::MARIO_COIN; use embassy::util::Invoke; use embassy::{Error, ResultExt}; use http::StatusCode; use rpc_toolkit::rpc_server; use tokio::process::Command; fn status_fn(_: i32) -> StatusCode { StatusCode::OK } async fn init(cfg_path: Option<&str>) -> Result<(), Error> { let cfg = RpcContextConfig::load(cfg_path).await?; embassy::disk::util::mount("LABEL=EMBASSY", "/embassy-os").await?; if tokio::fs::metadata("/embassy-os/disk.guid").await.is_ok() { embassy::disk::main::load( tokio::fs::read_to_string("/embassy-os/disk.guid") .await? .trim(), cfg.zfs_pool_name(), cfg.datadir(), DEFAULT_PASSWORD, ) .await?; log::info!("Loaded Disk"); } else { #[cfg(feature = "avahi")] let mdns = MdnsController::init(); tokio::fs::write( "/etc/nginx/sites-available/default", include_str!("../nginx/setup-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 = SetupContext::init(cfg_path).await?; let encrypt = encrypt(Arc::new(get_product_key().await?)); MARIO_COIN.play().await?; rpc_server!({ command: embassy::setup_api, context: ctx.clone(), status: status_fn, middleware: [ cors, encrypt, ] }) .with_graceful_shutdown({ let mut shutdown = ctx.shutdown.subscribe(); async move { shutdown.recv().await.expect("context dropped"); } }) .await .with_kind(embassy::ErrorKind::Network)?; } let secret_store = cfg.secret_store().await?; let log_dir = cfg.datadir().join("main").join("logs"); if tokio::fs::metadata(&log_dir).await.is_err() { tokio::fs::create_dir_all(&log_dir).await?; } embassy::disk::util::bind(&log_dir, "/var/log/journal", false).await?; Command::new("systemctl") .arg("restart") .arg("systemd-journald") .invoke(embassy::ErrorKind::Journald) .await?; log::info!("Mounted Logs"); let tmp_docker = cfg.datadir().join("tmp").join("docker"); if tokio::fs::metadata(&tmp_docker).await.is_ok() { tokio::fs::remove_dir_all(&tmp_docker).await?; } Command::new("cp") .arg("-r") .arg("/var/lib/docker") .arg(&tmp_docker) .invoke(embassy::ErrorKind::Filesystem) .await?; Command::new("systemctl") .arg("stop") .arg("docker") .invoke(embassy::ErrorKind::Docker) .await?; embassy::disk::util::bind(&tmp_docker, "/var/lib/docker", false).await?; Command::new("systemctl") .arg("reset-failed") .arg("docker") .invoke(embassy::ErrorKind::Docker) .await?; Command::new("systemctl") .arg("start") .arg("docker") .invoke(embassy::ErrorKind::Docker) .await?; log::info!("Mounted Docker Data"); embassy::install::load_images().await?; log::info!("Loaded Docker Images"); embassy::ssh::sync_keys_from_db(&secret_store, "/root/.ssh/authorized_keys").await?; log::info!("Synced SSH Keys"); // todo!("sync wifi"); embassy::hostname::sync_hostname().await?; log::info!("Synced Hostname"); if tokio::fs::metadata("/var/www/html/main/public") .await .is_err() { tokio::fs::create_dir_all("/var/www/html/main/public").await? } if tokio::fs::symlink_metadata("/var/www/html/main/public/package-data") .await .is_err() { tokio::fs::symlink( cfg.datadir().join("package-data").join("public"), "/var/www/html/main/public/package-data", ) .await?; } log::info!("Enabled nginx public dir"); embassy::net::wifi::synchronize_wpa_supplicant_conf(&cfg.datadir().join("main")).await?; Ok(()) } async fn run_script_if_exists>(path: P) { let script = path.as_ref(); if script.exists() { match Command::new("/bin/bash").arg(script).spawn() { Ok(mut c) => { if let Err(e) = c.wait().await { log::error!("Error Running {}: {}", script.display(), e) } } Err(e) => log::error!("Error Running {}: {}", script.display(), e), } } } async fn inner_main(cfg_path: Option<&str>) -> Result<(), Error> { embassy::sound::BEP.play().await?; run_script_if_exists("/embassy-os/preinit.sh").await; let res = if let Err(e) = init(cfg_path).await { (|| async { log::error!("{}", e.source); log::debug!("{}", e.source); embassy::sound::BEETHOVEN.play().await?; #[cfg(feature = "avahi")] let mdns = MdnsController::init(); tokio::fs::write( "/etc/nginx/sites-available/default", include_str!("../nginx/diagnostic-ui.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 = DiagnosticContext::init(cfg_path, e).await?; rpc_server!({ command: embassy::diagnostic_api, context: ctx.clone(), status: status_fn, middleware: [ cors, diagnostic, ] }) .with_graceful_shutdown({ let mut shutdown = ctx.shutdown.subscribe(); async move { shutdown.recv().await.expect("context dropped"); } }) .await .with_kind(embassy::ErrorKind::Network)?; Ok::<_, Error>(()) })() .await } else { Ok(()) }; run_script_if_exists("/embassy-os/postinit.sh").await; res } fn main() { let matches = clap::App::new("embassyd") .arg( clap::Arg::with_name("config") .short("c") .long("config") .takes_value(true), ) .arg( clap::Arg::with_name("verbosity") .short("v") .multiple(true) .takes_value(false), ) .get_matches(); simple_logging::log_to_stderr(match matches.occurrences_of("verbosity") { 0 => log::LevelFilter::Off, 1 => log::LevelFilter::Error, 2 => log::LevelFilter::Warn, 3 => log::LevelFilter::Info, 4 => log::LevelFilter::Debug, _ => log::LevelFilter::Trace, }); let cfg_path = matches.value_of("config"); let res = { let rt = tokio::runtime::Builder::new_multi_thread() .enable_all() .build() .expect("failed to initialize runtime"); rt.block_on(inner_main(cfg_path)) }; match res { Ok(_) => (), Err(e) => { eprintln!("{}", e.source); log::debug!("{:?}", e.source); drop(e.source); std::process::exit(e.kind as i32) } } }