use std::path::PathBuf;
use std::sync::Arc;
use anyhow::{Context, Result};
use serde::{Deserialize, Serialize};
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct Running {
pub url: String,
pub pid: u32,
pub version: String,
}
pub fn running_file() -> PathBuf {
crate::config::config_dir().join("engine.json")
}
pub fn running() -> Option<Running> {
let text = std::fs::read_to_string(running_file()).ok()?;
let r: Running = serde_json::from_str(&text).ok()?;
alive(r.pid).then_some(r)
}
fn alive(pid: u32) -> bool {
#[cfg(unix)]
{
std::process::Command::new("kill")
.args(["-0", &pid.to_string()])
.stderr(std::process::Stdio::null())
.status()
.is_ok_and(|s| s.success())
}
#[cfg(not(unix))]
{
let _ = pid;
true
}
}
pub fn serve(from: Option<&str>, host: &str, port: u16) -> Result<()> {
let root = crate::config::Config::load().index_dir(from);
let data = crate::view::Data::load_or_empty(&root, &crate::config::local_verifier());
let addr = format!("{host}:{port}");
let listener = std::net::TcpListener::bind(&addr).with_context(|| format!("bind {addr}"))?;
let addr = listener.local_addr()?.to_string();
if let Some(cfg) = crate::server::ServerConfig::from_env()? {
let (url, sign_in) = (cfg.public_url.clone(), cfg.sign_in.clone());
let server = crate::server::Server::open(&root, cfg).context("open the server table")?;
migrate(&server, &root)?;
if let Some(skip) = server.settings.log_skip() {
let off = crate::telemetry::parse_skip(&skip);
for kind in ["access", "tool"] {
crate::telemetry::set_logs(kind, !off.iter().any(|k| k == kind));
}
}
crate::ui::ok(&format!("comindly server for {url}, listening on {addr}"));
let ways: Vec<&str> = sign_in
.iter()
.map(|m| match m {
crate::server::SignIn::Email => "emailed links",
crate::server::SignIn::Code => "codes admins hand out",
crate::server::SignIn::Oidc => "the OpenID Connect provider",
})
.collect();
crate::ui::note(&format!("members sign in with {}", ways.join(", ")));
return crate::web::serve(data, listener, crate::web::Access::Server(Arc::new(server)));
}
let token = crate::config::ui_token()?;
let bound = listener.local_addr()?.port();
let running = Running {
url: format!("http://{host}:{bound}"),
pid: std::process::id(),
version: env!("CARGO_PKG_VERSION").into(),
};
std::fs::write(running_file(), serde_json::to_string(&running)?)?;
crate::ui::ok(&format!(
"comindly engine on {} (Ctrl-C to stop)",
running.url
));
let served = crate::web::serve(data, listener, crate::web::Access::Local(token));
let _ = std::fs::remove_file(running_file());
served
}
fn migrate(server: &crate::server::Server, root: &str) -> Result<()> {
let Some(text) = server.settings.legacy_config() else {
return Ok(());
};
if server.settings.repos().is_empty() {
let old: crate::config::Config = toml::from_str(&text).unwrap_or_default();
server.settings.set_repos("upgrade", &old.repos)?;
}
crate::rt::block_on(async {
let stored = crate::team::load(root).await?;
let mut settings = stored.settings;
if crate::config::adopt_legacy(&text, &mut settings) {
crate::team::save_as(root, &settings, stored.version, "upgrade").await?;
}
anyhow::Ok(())
})?;
server.settings.drop_legacy_config()?;
crate::ui::note("moved the server's kept config.toml into its settings and the index's");
Ok(())
}