forked from
microcosm.blue/microcosm-rs
Constellation, Spacedust, Slingshot, UFOs: atproto crates and services for microcosm
1use clap::{ArgAction, Parser};
2use metrics_exporter_prometheus::{BuildError as PromBuildError, PrometheusBuilder};
3use std::path::PathBuf;
4use tokio_util::sync::CancellationToken;
5use who_am_i::{Tokens, serve};
6
7/// Aggregate links in the at-mosphere
8#[derive(Parser, Debug, Clone)]
9#[command(version, about, long_about = None)]
10struct Args {
11 /// secret key from which the cookie-signing key is derived
12 ///
13 /// must have at least 512 bits (64 bytes) of randomness
14 ///
15 /// eg: `cat /dev/urandom | head -c 64 | base64`
16 #[arg(long, env)]
17 app_secret: String,
18 /// path to at-oauth private key (PEM pk8 format)
19 ///
20 /// generate with:
21 ///
22 /// openssl ecparam -genkey -noout -name prime256v1 \
23 /// | openssl pkcs8 -topk8 -nocrypt -out <PATH-TO-PRIV-KEY>.pem
24 #[arg(long, env)]
25 oauth_private_key: Option<PathBuf>,
26 /// path to jwt private key (PEM pk8 format)
27 ///
28 /// generate with:
29 ///
30 /// openssl ecparam -genkey -noout -name prime256v1 \
31 /// | openssl pkcs8 -topk8 -nocrypt -out <PATH-TO-PRIV-KEY>.pem
32 #[arg(long)]
33 jwt_private_key: PathBuf,
34 /// this server's client-reachable base url, for oauth redirect + jwt check
35 ///
36 /// required unless running in localhost mode with --dev
37 #[arg(long, env)]
38 base_url: Option<String>,
39 /// host:port to bind to on startup
40 #[arg(long, env, default_value = "127.0.0.1:9997")]
41 bind: String,
42 /// Enable dev mode
43 ///
44 /// enables automatic template reloading, uses localhost oauth config, etc
45 #[arg(long, action)]
46 dev: bool,
47 /// Hosts who are allowed to one-click auth
48 ///
49 /// Pass this argument multiple times to allow multiple hosts
50 #[arg(long = "allow_host", short = 'a', action = ArgAction::Append)]
51 allowed_hosts: Vec<String>,
52}
53
54#[tokio::main(flavor = "current_thread")]
55async fn main() {
56 let shutdown = CancellationToken::new();
57
58 let ctrlc_shutdown = shutdown.clone();
59 ctrlc::set_handler(move || ctrlc_shutdown.cancel()).expect("failed to set ctrl-c handler");
60
61 let args = Args::parse();
62
63 // let bind = args.bind.to_socket_addrs().expect("--bind must be ToSocketAddrs");
64
65 let base = args.base_url.unwrap_or_else(|| {
66 if args.dev {
67 format!("http://{}", args.bind)
68 } else {
69 panic!("not in --dev mode so --base-url is required")
70 }
71 });
72
73 if !args.dev && args.oauth_private_key.is_none() {
74 panic!("--at-oauth-key is required except in --dev");
75 } else if args.dev && args.oauth_private_key.is_some() {
76 eprintln!("warn: --at-oauth-key is ignored in dev (localhost config)");
77 }
78
79 if args.allowed_hosts.is_empty() {
80 panic!("at least one --allowed-host host must be set");
81 }
82
83 println!("starting with allowed_hosts hosts:");
84 for host in &args.allowed_hosts {
85 println!(" - {host}");
86 }
87
88 let tokens = Tokens::from_files(args.jwt_private_key).unwrap();
89
90 if let Err(e) = install_metrics_server() {
91 eprintln!("failed to install metrics server: {e:?}");
92 };
93
94 serve(
95 shutdown,
96 args.app_secret,
97 args.oauth_private_key,
98 tokens,
99 base,
100 args.bind,
101 args.allowed_hosts,
102 args.dev,
103 )
104 .await;
105}
106
107fn install_metrics_server() -> Result<(), PromBuildError> {
108 println!("installing metrics server...");
109 let host = [0, 0, 0, 0];
110 let port = 8765;
111 PrometheusBuilder::new()
112 .set_enable_unit_suffix(false)
113 .with_http_listener((host, port))
114 .install()?;
115 println!(
116 "metrics server installed! listening on http://{}.{}.{}.{}:{port}",
117 host[0], host[1], host[2], host[3]
118 );
119 Ok(())
120}