mirror of
https://github.com/Start9Labs/start-os.git
synced 2026-03-26 02:11:53 +00:00
* Feat: Multi-lock capabilities add to config * wip: RPC.rs fixes, new combinatoric * wip: changes * chore: More things that are bulk * fix: Saving * chore: Remove a dyn object * chore: Add tests + remove unused * Fix/feat bulk locking (#1427) * fix: health check * fix: start/stop service * fix: install/uninstall services * chore: Fix the notifications * fix: Version * fix: Version as serde * chore: Update to latest patch db * chore: Change the htLock to something that makes more sense * chore: Fix the rest of the ht * "chore: More ht_lock":
382 lines
14 KiB
Rust
382 lines
14 KiB
Rust
use std::sync::Arc;
|
|
use std::time::Duration;
|
|
|
|
use color_eyre::eyre::eyre;
|
|
use embassy::context::{DiagnosticContext, RpcContext};
|
|
use embassy::core::rpc_continuations::RequestGuid;
|
|
use embassy::db::subscribe;
|
|
use embassy::middleware::auth::auth;
|
|
use embassy::middleware::cors::cors;
|
|
use embassy::middleware::diagnostic::diagnostic;
|
|
#[cfg(feature = "avahi")]
|
|
use embassy::net::mdns::MdnsController;
|
|
use embassy::net::tor::tor_health_check;
|
|
use embassy::shutdown::Shutdown;
|
|
use embassy::system::launch_metrics_task;
|
|
use embassy::util::logger::EmbassyLogger;
|
|
use embassy::util::{daemon, Invoke};
|
|
use embassy::{static_server, Error, ErrorKind, ResultExt};
|
|
use futures::{FutureExt, TryFutureExt};
|
|
use reqwest::{Client, Proxy};
|
|
use rpc_toolkit::hyper::{Body, Response, Server, StatusCode};
|
|
use rpc_toolkit::rpc_server;
|
|
use tokio::process::Command;
|
|
use tokio::signal::unix::signal;
|
|
use tracing::instrument;
|
|
|
|
fn status_fn(_: i32) -> StatusCode {
|
|
StatusCode::OK
|
|
}
|
|
|
|
fn err_to_500(e: Error) -> Response<Body> {
|
|
tracing::error!("{}", e);
|
|
tracing::debug!("{:?}", e);
|
|
Response::builder()
|
|
.status(StatusCode::INTERNAL_SERVER_ERROR)
|
|
.body(Body::empty())
|
|
.unwrap()
|
|
}
|
|
|
|
#[instrument]
|
|
async fn inner_main(cfg_path: Option<&str>) -> Result<Option<Shutdown>, Error> {
|
|
let (rpc_ctx, shutdown) = {
|
|
embassy::hostname::sync_hostname().await?;
|
|
let rpc_ctx = RpcContext::init(
|
|
cfg_path,
|
|
Arc::new(
|
|
tokio::fs::read_to_string("/embassy-os/disk.guid") // unique identifier for volume group - keeps track of the disk that goes with your embassy
|
|
.await?
|
|
.trim()
|
|
.to_owned(),
|
|
),
|
|
)
|
|
.await?;
|
|
let mut shutdown_recv = rpc_ctx.shutdown.subscribe();
|
|
|
|
let sig_handler_ctx = rpc_ctx.clone();
|
|
let sig_handler = tokio::spawn(async move {
|
|
use tokio::signal::unix::SignalKind;
|
|
futures::future::select_all(
|
|
[
|
|
SignalKind::interrupt(),
|
|
SignalKind::quit(),
|
|
SignalKind::terminate(),
|
|
]
|
|
.iter()
|
|
.map(|s| {
|
|
async move {
|
|
signal(*s)
|
|
.expect(&format!("register {:?} handler", s))
|
|
.recv()
|
|
.await
|
|
}
|
|
.boxed()
|
|
}),
|
|
)
|
|
.await;
|
|
sig_handler_ctx
|
|
.shutdown
|
|
.send(None)
|
|
.map_err(|_| ())
|
|
.expect("send shutdown signal");
|
|
});
|
|
|
|
let mut db = rpc_ctx.db.handle();
|
|
let receipts = embassy::context::rpc::RpcSetNginxReceipts::new(&mut db).await?;
|
|
|
|
rpc_ctx.set_nginx_conf(&mut db, receipts).await?;
|
|
drop(db);
|
|
let auth = auth(rpc_ctx.clone());
|
|
let ctx = rpc_ctx.clone();
|
|
let server = rpc_server!({
|
|
command: embassy::main_api,
|
|
context: ctx,
|
|
status: status_fn,
|
|
middleware: [
|
|
cors,
|
|
auth,
|
|
]
|
|
})
|
|
.with_graceful_shutdown({
|
|
let mut shutdown = rpc_ctx.shutdown.subscribe();
|
|
async move {
|
|
shutdown.recv().await.expect("context dropped");
|
|
}
|
|
});
|
|
|
|
let metrics_ctx = rpc_ctx.clone();
|
|
let metrics_task = tokio::spawn(async move {
|
|
launch_metrics_task(&metrics_ctx.metrics_cache, || {
|
|
metrics_ctx.shutdown.subscribe()
|
|
})
|
|
.await
|
|
});
|
|
|
|
let rev_cache_ctx = rpc_ctx.clone();
|
|
let revision_cache_task = tokio::spawn(async move {
|
|
let mut sub = rev_cache_ctx.db.subscribe();
|
|
let mut shutdown = rev_cache_ctx.shutdown.subscribe();
|
|
loop {
|
|
let rev = match tokio::select! {
|
|
a = sub.recv() => a,
|
|
_ = shutdown.recv() => break,
|
|
} {
|
|
Ok(a) => a,
|
|
Err(_) => {
|
|
rev_cache_ctx.revision_cache.write().await.truncate(0);
|
|
continue;
|
|
}
|
|
}; // TODO: handle falling behind
|
|
let mut cache = rev_cache_ctx.revision_cache.write().await;
|
|
cache.push_back(rev);
|
|
if cache.len() > rev_cache_ctx.revision_cache_size {
|
|
cache.pop_front();
|
|
}
|
|
}
|
|
});
|
|
|
|
let ws_ctx = rpc_ctx.clone();
|
|
let ws_server = {
|
|
let builder = Server::bind(&ws_ctx.bind_ws);
|
|
|
|
let make_svc = ::rpc_toolkit::hyper::service::make_service_fn(move |_| {
|
|
let ctx = ws_ctx.clone();
|
|
async move {
|
|
Ok::<_, ::rpc_toolkit::hyper::Error>(::rpc_toolkit::hyper::service::service_fn(
|
|
move |req| {
|
|
let ctx = ctx.clone();
|
|
async move {
|
|
tracing::debug!("Request to {}", req.uri().path());
|
|
match req.uri().path() {
|
|
"/ws/db" => {
|
|
Ok(subscribe(ctx, req).await.unwrap_or_else(err_to_500))
|
|
}
|
|
path if path.starts_with("/rest/rpc/") => {
|
|
match RequestGuid::from(
|
|
path.strip_prefix("/rest/rpc/").unwrap(),
|
|
) {
|
|
None => {
|
|
tracing::debug!("No Guid Path");
|
|
Response::builder()
|
|
.status(StatusCode::BAD_REQUEST)
|
|
.body(Body::empty())
|
|
}
|
|
Some(guid) => {
|
|
match ctx
|
|
.rpc_stream_continuations
|
|
.lock()
|
|
.await
|
|
.remove(&guid)
|
|
{
|
|
None => Response::builder()
|
|
.status(StatusCode::NOT_FOUND)
|
|
.body(Body::empty()),
|
|
Some(cont) => match (cont.handler)(req).await {
|
|
Ok(r) => Ok(r),
|
|
Err(e) => Response::builder()
|
|
.status(
|
|
StatusCode::INTERNAL_SERVER_ERROR,
|
|
)
|
|
.body(Body::from(format!("{}", e))),
|
|
},
|
|
}
|
|
}
|
|
}
|
|
}
|
|
_ => Response::builder()
|
|
.status(StatusCode::NOT_FOUND)
|
|
.body(Body::empty()),
|
|
}
|
|
}
|
|
},
|
|
))
|
|
}
|
|
});
|
|
builder.serve(make_svc)
|
|
}
|
|
.with_graceful_shutdown({
|
|
let mut shutdown = rpc_ctx.shutdown.subscribe();
|
|
async move {
|
|
shutdown.recv().await.expect("context dropped");
|
|
}
|
|
});
|
|
|
|
let file_server_ctx = rpc_ctx.clone();
|
|
let file_server = {
|
|
static_server::init(file_server_ctx, {
|
|
let mut shutdown = rpc_ctx.shutdown.subscribe();
|
|
async move {
|
|
shutdown.recv().await.expect("context dropped");
|
|
}
|
|
})
|
|
};
|
|
|
|
let tor_health_ctx = rpc_ctx.clone();
|
|
let tor_client = Client::builder()
|
|
.proxy(
|
|
Proxy::http(format!(
|
|
"socks5h://{}:{}",
|
|
rpc_ctx.tor_socks.ip(),
|
|
rpc_ctx.tor_socks.port()
|
|
))
|
|
.with_kind(crate::ErrorKind::Network)?,
|
|
)
|
|
.build()
|
|
.with_kind(crate::ErrorKind::Network)?;
|
|
let tor_health_daemon = daemon(
|
|
move || {
|
|
let ctx = tor_health_ctx.clone();
|
|
let client = tor_client.clone();
|
|
async move { tor_health_check(&client, &ctx.net_controller.tor).await }
|
|
},
|
|
Duration::from_secs(300),
|
|
rpc_ctx.shutdown.subscribe(),
|
|
);
|
|
|
|
embassy::sound::CHIME.play().await?;
|
|
|
|
futures::try_join!(
|
|
server
|
|
.map_err(|e| Error::new(e, ErrorKind::Network))
|
|
.map_ok(|_| tracing::debug!("RPC Server Shutdown")),
|
|
metrics_task
|
|
.map_err(|e| Error::new(
|
|
eyre!("{}", e).wrap_err("Metrics daemon panicked!"),
|
|
ErrorKind::Unknown
|
|
))
|
|
.map_ok(|_| tracing::debug!("Metrics daemon Shutdown")),
|
|
revision_cache_task
|
|
.map_err(|e| Error::new(
|
|
eyre!("{}", e).wrap_err("Revision Cache daemon panicked!"),
|
|
ErrorKind::Unknown
|
|
))
|
|
.map_ok(|_| tracing::debug!("Revision Cache daemon Shutdown")),
|
|
ws_server
|
|
.map_err(|e| Error::new(e, ErrorKind::Network))
|
|
.map_ok(|_| tracing::debug!("WebSocket Server Shutdown")),
|
|
file_server
|
|
.map_err(|e| Error::new(e, ErrorKind::Network))
|
|
.map_ok(|_| tracing::debug!("Static File Server Shutdown")),
|
|
tor_health_daemon
|
|
.map_err(|e| Error::new(
|
|
e.wrap_err("Tor Health daemon panicked!"),
|
|
ErrorKind::Unknown
|
|
))
|
|
.map_ok(|_| tracing::debug!("Tor Health daemon Shutdown")),
|
|
)?;
|
|
|
|
let mut shutdown = shutdown_recv
|
|
.recv()
|
|
.await
|
|
.with_kind(crate::ErrorKind::Unknown)?;
|
|
|
|
sig_handler.abort();
|
|
|
|
if let Some(shutdown) = &mut shutdown {
|
|
drop(shutdown.db_handle.take());
|
|
}
|
|
|
|
(rpc_ctx, shutdown)
|
|
};
|
|
rpc_ctx.shutdown().await?;
|
|
|
|
Ok(shutdown)
|
|
}
|
|
|
|
fn main() {
|
|
let matches = clap::App::new("embassyd")
|
|
.arg(
|
|
clap::Arg::with_name("config")
|
|
.short("c")
|
|
.long("config")
|
|
.takes_value(true),
|
|
)
|
|
.get_matches();
|
|
|
|
EmbassyLogger::init();
|
|
|
|
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(async {
|
|
match inner_main(cfg_path).await {
|
|
Ok(a) => Ok(a),
|
|
Err(e) => {
|
|
(|| async {
|
|
tracing::error!("{}", e.source);
|
|
tracing::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,
|
|
if tokio::fs::metadata("/embassy-os/disk.guid").await.is_ok() {
|
|
Some(Arc::new(
|
|
tokio::fs::read_to_string("/embassy-os/disk.guid") // unique identifier for volume group - keeps track of the disk that goes with your embassy
|
|
.await?
|
|
.trim()
|
|
.to_owned(),
|
|
))
|
|
} else {
|
|
None
|
|
},
|
|
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>(None)
|
|
})()
|
|
.await
|
|
}
|
|
}
|
|
})
|
|
};
|
|
|
|
match res {
|
|
Ok(None) => (),
|
|
Ok(Some(s)) => s.execute(),
|
|
Err(e) => {
|
|
eprintln!("{}", e.source);
|
|
tracing::debug!("{:?}", e.source);
|
|
drop(e.source);
|
|
std::process::exit(e.kind as i32)
|
|
}
|
|
}
|
|
}
|