1use std::path::PathBuf;
5use std::sync::Arc;
6
7use anyhow::{Context, Result};
8use serde::{Deserialize, Serialize};
9
10#[derive(Debug, Clone, Serialize, Deserialize)]
12pub struct Running {
13 pub url: String,
14 pub pid: u32,
15 pub version: String,
16}
17
18pub fn running_file() -> PathBuf {
20 crate::config::config_dir().join("engine.json")
21}
22
23pub fn running() -> Option<Running> {
25 let text = std::fs::read_to_string(running_file()).ok()?;
26 let r: Running = serde_json::from_str(&text).ok()?;
27 alive(r.pid).then_some(r)
28}
29
30fn alive(pid: u32) -> bool {
31 #[cfg(unix)]
32 {
33 std::process::Command::new("kill")
34 .args(["-0", &pid.to_string()])
35 .stderr(std::process::Stdio::null())
36 .status()
37 .is_ok_and(|s| s.success())
38 }
39 #[cfg(not(unix))]
40 {
41 let _ = pid;
42 true
43 }
44}
45
46pub fn serve(from: Option<&str>, host: &str, port: u16) -> Result<()> {
48 let root = crate::config::Config::load().index_dir(from);
49 let data = crate::view::Data::load_or_empty(&root, &crate::config::local_verifier());
50 let addr = format!("{host}:{port}");
51 let listener = std::net::TcpListener::bind(&addr).with_context(|| format!("bind {addr}"))?;
52 let addr = listener.local_addr()?.to_string();
53 if let Some(cfg) = crate::server::ServerConfig::from_env()? {
54 let (url, sign_in) = (cfg.public_url.clone(), cfg.sign_in.clone());
55 let server = crate::server::Server::open(&root, cfg).context("open the server table")?;
56 migrate(&server, &root)?;
57 if let Some(skip) = server.settings.log_skip() {
59 let off = crate::telemetry::parse_skip(&skip);
60 for kind in ["access", "tool"] {
61 crate::telemetry::set_logs(kind, !off.iter().any(|k| k == kind));
62 }
63 }
64 crate::ui::ok(&format!("comindly server for {url}, listening on {addr}"));
65 let ways: Vec<&str> = sign_in
66 .iter()
67 .map(|m| match m {
68 crate::server::SignIn::Email => "emailed links",
69 crate::server::SignIn::Code => "codes admins hand out",
70 crate::server::SignIn::Oidc => "the OpenID Connect provider",
71 })
72 .collect();
73 crate::ui::note(&format!("members sign in with {}", ways.join(", ")));
74 return crate::web::serve(data, listener, crate::web::Access::Server(Arc::new(server)));
75 }
76 let token = crate::config::ui_token()?;
77 let bound = listener.local_addr()?.port();
78 let running = Running {
79 url: format!("http://{host}:{bound}"),
80 pid: std::process::id(),
81 version: env!("CARGO_PKG_VERSION").into(),
82 };
83 std::fs::write(running_file(), serde_json::to_string(&running)?)?;
84 crate::ui::ok(&format!(
85 "comindly engine on {} (Ctrl-C to stop)",
86 running.url
87 ));
88 let served = crate::web::serve(data, listener, crate::web::Access::Local(token));
89 let _ = std::fs::remove_file(running_file());
90 served
91}
92
93fn migrate(server: &crate::server::Server, root: &str) -> Result<()> {
95 let Some(text) = server.settings.legacy_config() else {
96 return Ok(());
97 };
98 if server.settings.repos().is_empty() {
99 let old: crate::config::Config = toml::from_str(&text).unwrap_or_default();
100 server.settings.set_repos("upgrade", &old.repos)?;
101 }
102 crate::rt::block_on(async {
103 let stored = crate::team::load(root).await?;
104 let mut settings = stored.settings;
105 if crate::config::adopt_legacy(&text, &mut settings) {
106 crate::team::save_as(root, &settings, stored.version, "upgrade").await?;
107 }
108 anyhow::Ok(())
109 })?;
110 server.settings.drop_legacy_config()?;
111 crate::ui::note("moved the server's kept config.toml into its settings and the index's");
112 Ok(())
113}