forge
ExploreLog in

Tahinli/forge

public
0 watching
★ 0
7.7 KiB
1//! forge: binary entry point. Dispatch: serve (default) | hook <name> | runner <register|run|check> ... | admin create-user <username>, admin set-username <current> <NewCase>, admin reset-password <username>.
2//!
3//! `runner` is the forge CI runner, the same command line as the standalone `forge-runner` binary.
4//! The hidden `__sandbox` helper is dispatched here too, because the runner starts it as this
5//! executable.
6
7#![recursion_limit = "256"]
8
9mod access;
10mod admin_cli;
11mod admin_client;
12mod advisories;
13mod api;
14mod audit;
15mod auth;
16mod backup;
17mod blobs;
18mod boot;
19mod ci;
20mod client_ip;
21mod compression;
22mod git_auth;
23mod git_http;
24mod hook_cmd;
25mod i18n;
26mod internal;
27mod lfs;
28mod lfs_pointer;
29mod mail;
30mod mirrors;
31mod notify;
32mod pages;
33mod post_update;
34mod pulls_sync;
35mod raw;
36mod render;
37mod replication;
38mod signatures;
39mod ssh_backend;
40mod state;
41mod templates;
42#[cfg(test)]
43mod test_support;
44mod theme;
45mod throttle;
46mod views;
47mod webhooks;
48
49use std::path::PathBuf;
50use std::process::ExitCode;
51
52use forge_core::Config;
53use tracing_subscriber::EnvFilter;
54
55use topcoat::router::route;
56
57type BoxError = Box<dyn std::error::Error + Send + Sync>;
58
59const USAGE: &str = "usage: forge [--config <path>] [serve | hook <pre-receive|post-receive> | runner <register|run|check> ... | admin create-user <username> [--admin] | admin set-username <current> <NewCase> | admin reset-password <username> | admin trash-list | admin trash-purge --older-than-days <N> | admin audit-purge --older-than-days <N> | admin delete-user <username> [--yes] | admin backup --out <DIR> [--include-key] [--repos-only | --db-only] | admin backup-verify <PATH> | admin fsck [--full] | admin lfs-gc | admin restore --from <SET> --data-dir <NEW_DIR>]";
60const DEFAULT_CONFIG: &str = "config/forge.toml";
61
62#[route(GET "/healthz")]
63async fn healthz() -> Result<String, topcoat::Error> {
64 Ok(format!("ok {}", env!("FORGE_BUILD_SHA")))
65}
66
67async fn serve(config_path: &std::path::Path) -> Result<(), BoxError> {
68 let config = Config::load(config_path)?;
69 boot::serve(config).await?;
70 Ok(())
71}
72
73/// Splits the raw args into the config path, the `--admin` flag and the positional words.
74fn parse_args(
75 args: impl Iterator<Item = String>,
76) -> Result<(Option<PathBuf>, bool, Vec<String>), String> {
77 let mut config = None;
78 let mut admin = false;
79 let mut positional = Vec::new();
80 let mut args = args;
81 while let Some(arg) = args.next() {
82 match arg.as_str() {
83 "--config" => {
84 let path = args.next().ok_or("--config needs a path")?;
85 config = Some(PathBuf::from(path));
86 }
87 "--admin" => admin = true,
88 _ => positional.push(arg),
89 }
90 }
91 Ok((config, admin, positional))
92}
93
94fn main() -> ExitCode {
95 // The sandbox helpers must start before any runtime or subscriber exists.
96 if forge_runner::cli::is_sandbox_invocation() {
97 std::process::exit(forge_runner::cli::hidden_main());
98 }
99 // `runner` runs before the forge config is parsed, because its `--config` means the runner
100 // config, not the forge config.
101 let raw: Vec<std::ffi::OsString> = std::env::args_os().skip(1).collect();
102 if raw.first().is_some_and(|arg| arg == "runner") {
103 return forge_runner::cli::main_with_args("forge runner", raw.into_iter().skip(1));
104 }
105 let runtime = match tokio::runtime::Builder::new_multi_thread()
106 .enable_all()
107 .build()
108 {
109 Ok(runtime) => runtime,
110 Err(e) => {
111 eprintln!("forge: cannot start the runtime: {e}");
112 return ExitCode::FAILURE;
113 }
114 };
115 runtime.block_on(real_main())
116}
117
118async fn real_main() -> ExitCode {
119 // Logs go to stderr: the hook stages write protocol output to stdout.
120 let filter = EnvFilter::try_from_default_env().unwrap_or_else(|_| EnvFilter::new("info"));
121 tracing_subscriber::fmt()
122 .with_env_filter(filter)
123 .with_writer(std::io::stderr)
124 .init();
125
126 let (flag_config, admin, positional) = match parse_args(std::env::args().skip(1)) {
127 Ok(parsed) => parsed,
128 Err(e) => {
129 eprintln!("forge: {e}\n{USAGE}");
130 return ExitCode::from(2);
131 }
132 };
133 let config_path = flag_config
134 .or_else(|| std::env::var_os("FORGE_CONFIG").map(PathBuf::from))
135 .unwrap_or_else(|| PathBuf::from(DEFAULT_CONFIG));
136 let parts: Vec<&str> = positional.iter().map(String::as_str).collect();
137
138 let outcome: Result<(), BoxError> = match parts.as_slice() {
139 [] | ["serve"] => serve(&config_path).await,
140 ["hook", stage] => hook_cmd::run(stage).await.map_err(Into::into),
141 ["admin", "create-user", username] => admin_cli::create_user(&config_path, username, admin)
142 .await
143 .map_err(Into::into),
144 ["admin", "reset-password", username] => admin_cli::reset_password(&config_path, username)
145 .await
146 .map_err(Into::into),
147 ["admin", "set-username", current, new_display] => {
148 admin_cli::set_username(&config_path, current, new_display)
149 .await
150 .map_err(Into::into)
151 }
152 ["admin", "backup", rest @ ..] => match admin_cli::parse_backup_args(rest) {
153 Ok(opts) => admin_cli::backup(&config_path, opts)
154 .await
155 .map_err(Into::into),
156 Err(e) => {
157 eprintln!("forge: {e}\n{USAGE}");
158 return ExitCode::from(2);
159 }
160 },
161 ["admin", "restore", rest @ ..] => match admin_cli::parse_restore_args(rest) {
162 Ok((from, data_dir)) => admin_cli::restore(&from, &data_dir)
163 .await
164 .map_err(Into::into),
165 Err(e) => {
166 eprintln!("forge: {e}\n{USAGE}");
167 return ExitCode::from(2);
168 }
169 },
170 ["admin", "fsck"] => admin_cli::fsck(&config_path, false)
171 .await
172 .map_err(Into::into),
173 ["admin", "fsck", "--full"] => admin_cli::fsck(&config_path, true)
174 .await
175 .map_err(Into::into),
176 ["admin", "lfs-gc"] => admin_cli::lfs_gc(&config_path).await.map_err(Into::into),
177 ["admin", "backup-verify", path] => admin_cli::backup_verify(std::path::Path::new(path))
178 .await
179 .map_err(Into::into),
180 ["admin", "trash-list"] => admin_cli::trash_list(&config_path).map_err(Into::into),
181 ["admin", "delete-user", username] => admin_cli::delete_user(&config_path, username, false)
182 .await
183 .map_err(Into::into),
184 ["admin", "delete-user", username, "--yes"] => {
185 admin_cli::delete_user(&config_path, username, true)
186 .await
187 .map_err(Into::into)
188 }
189 ["admin", "audit-purge", "--older-than-days", days] => match days.parse::<u64>() {
190 Ok(days) => admin_cli::audit_purge(&config_path, days)
191 .await
192 .map_err(Into::into),
193 Err(_) => {
194 eprintln!("forge: --older-than-days needs a whole number of days\n{USAGE}");
195 return ExitCode::from(2);
196 }
197 },
198 ["admin", "trash-purge", "--older-than-days", days] => match days.parse::<u64>() {
199 Ok(days) => admin_cli::trash_purge(&config_path, days).map_err(Into::into),
200 Err(_) => {
201 eprintln!("forge: --older-than-days needs a whole number of days\n{USAGE}");
202 return ExitCode::from(2);
203 }
204 },
205 _ => {
206 eprintln!("{USAGE}");
207 return ExitCode::from(2);
208 }
209 };
210
211 match outcome {
212 Ok(()) => ExitCode::SUCCESS,
213 Err(e) => {
214 eprintln!("forge: {e}");
215 ExitCode::FAILURE
216 }
217 }
218}