//! onset-server: an Onset host you run yourself. One binary, one SQLite file //! and a folder of objects. It speaks the protocol in onset-cli's `wire` //! module, the same one the hosted service speaks. //! //! ```text //! onset-server serve --data DIR [--listen 127.0.0.1:8899] [--threads 16] //! onset-server user add HANDLE [--name NAME] [--email EMAIL] prints a token //! onset-server project add OWNER/NAME --owner HANDLE [--public] //! onset-server member add OWNER/NAME HANDLE [--role member|reader|owner] //! ``` mod api; mod state; use std::path::PathBuf; use anyhow::{Result, bail}; fn arg(args: &[String], name: &str) -> Option { args.iter().position(|a| a == name).and_then(|i| args.get(i + 1)).cloned() } fn main() -> Result<()> { let args: Vec = std::env::args().skip(1).collect(); // A folder from before Onset had its name keeps being used. let default = if !std::path::Path::new("onset-data").exists() && std::path::Path::new("vcs-data").exists() { "vcs-data" } else { "onset-data" }; let data = PathBuf::from(arg(&args, "--data").or_else(|| onset_cli::paths::var("SERVER_DATA")).unwrap_or_else(|| default.into())); let words: Vec<&str> = args.iter().take_while(|a| !a.starts_with("--")).map(String::as_str).collect(); match words.as_slice() { ["serve"] => { let listen = arg(&args, "--listen").unwrap_or_else(|| "127.0.0.1:8899".into()); let threads: usize = arg(&args, "--threads").and_then(|t| t.parse().ok()).unwrap_or(16); api::serve(&data, &listen, threads) } ["user", "add", handle] => { let st = state::State::open(&data)?; let name = arg(&args, "--name").unwrap_or_else(|| handle.to_string()); let email = arg(&args, "--email").unwrap_or_else(|| format!("{handle}@localhost")); let token = st.add_user(handle, &name, &email)?; println!("{token}"); Ok(()) } ["project", "add", key] => { let st = state::State::open(&data)?; let owner = arg(&args, "--owner").ok_or_else(|| anyhow::anyhow!("--owner HANDLE is required"))?; st.add_project(key, &owner, args.iter().any(|a| a == "--public"))?; println!("{key}"); Ok(()) } ["member", "add", key, handle] => { let st = state::State::open(&data)?; let role = arg(&args, "--role").unwrap_or_else(|| "member".into()); if !["owner", "member", "reader"].contains(&role.as_str()) { bail!("--role is owner, member or reader"); } st.add_member(key, handle, &role)?; Ok(()) } _ => { eprintln!("usage: onset-server serve --data DIR [--listen ADDR] | user add HANDLE | project add OWNER/NAME --owner HANDLE [--public] | member add OWNER/NAME HANDLE [--role ROLE]"); std::process::exit(2); } } }