forge
ExploreLog in

Tahinli/forge

public
0 watching
★ 0
8.1 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 notify_hub;
33mod pages;
34mod post_update;
35mod pulls_sync;
36mod raw;
37mod render;
38mod replication;
39mod self_update;
40mod signatures;
41mod ssh_backend;
42mod state;
43mod templates;
44#[cfg(test)]
45mod test_support;
46mod theme;
47mod throttle;
48mod views;
49mod webhooks;
50
51use std::path::PathBuf;
52use std::process::ExitCode;
53
54use forge_core::Config;
55use tracing_subscriber::EnvFilter;
56
57use topcoat::router::route;
58
59type BoxError = Box<dyn std::error::Error + Send + Sync>;
60
61const 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>]";
62const DEFAULT_CONFIG: &str = "config/forge.toml";
63
64#[route(GET "/healthz")]
65async fn healthz() -> Result<String, topcoat::Error> {
66 Ok(format!("ok {}", env!("FORGE_BUILD_SHA")))
67}
68
69async fn serve(config_path: &std::path::Path) -> Result<(), BoxError> {
70 let config = Config::load(config_path)?;
71 boot::serve(config).await?;
72 Ok(())
73}
74
75/// Splits the raw args into the config path, the `--admin` flag and the positional words.
76fn parse_args(
77 args: impl Iterator<Item = String>,
78) -> Result<(Option<PathBuf>, bool, Vec<String>), String> {
79 let mut config = None;
80 let mut admin = false;
81 let mut positional = Vec::new();
82 let mut args = args;
83 while let Some(arg) = args.next() {
84 match arg.as_str() {
85 "--config" => {
86 let path = args.next().ok_or("--config needs a path")?;
87 config = Some(PathBuf::from(path));
88 }
89 "--admin" => admin = true,
90 _ => positional.push(arg),
91 }
92 }
93 Ok((config, admin, positional))
94}
95
96fn main() -> ExitCode {
97 // The sandbox helpers must start before any runtime or subscriber exists.
98 if forge_runner::cli::is_sandbox_invocation() {
99 std::process::exit(forge_runner::cli::hidden_main());
100 }
101 // `runner` runs before the forge config is parsed, because its `--config` means the runner
102 // config, not the forge config.
103 let raw: Vec<std::ffi::OsString> = std::env::args_os().skip(1).collect();
104 if raw.first().is_some_and(|arg| arg == "runner") {
105 return forge_runner::cli::main_with_args("forge runner", raw.into_iter().skip(1));
106 }
107 let runtime = match tokio::runtime::Builder::new_multi_thread()
108 .enable_all()
109 .build()
110 {
111 Ok(runtime) => runtime,
112 Err(e) => {
113 eprintln!("forge: cannot start the runtime: {e}");
114 return ExitCode::FAILURE;
115 }
116 };
117 runtime.block_on(real_main())
118}
119
120async fn real_main() -> ExitCode {
121 // Logs go to stderr: the hook stages write protocol output to stdout.
122 let filter = EnvFilter::try_from_default_env().unwrap_or_else(|_| EnvFilter::new("info"));
123 tracing_subscriber::fmt()
124 .with_env_filter(filter)
125 .with_writer(std::io::stderr)
126 .init();
127
128 let (flag_config, admin, positional) = match parse_args(std::env::args().skip(1)) {
129 Ok(parsed) => parsed,
130 Err(e) => {
131 eprintln!("forge: {e}\n{USAGE}");
132 return ExitCode::from(2);
133 }
134 };
135 let config_path = flag_config
136 .or_else(|| std::env::var_os("FORGE_CONFIG").map(PathBuf::from))
137 .unwrap_or_else(|| PathBuf::from(DEFAULT_CONFIG));
138 let parts: Vec<&str> = positional.iter().map(String::as_str).collect();
139
140 let outcome: Result<(), BoxError> = match parts.as_slice() {
141 [] | ["serve"] => serve(&config_path).await,
142 ["--version"] => {
143 println!(
144 "forge {} {}",
145 env!("CARGO_PKG_VERSION"),
146 env!("FORGE_BUILD_SHA")
147 );
148 return ExitCode::SUCCESS;
149 }
150 ["hook", stage] => hook_cmd::run(stage).await.map_err(Into::into),
151 ["admin", "create-user", username] => admin_cli::create_user(&config_path, username, admin)
152 .await
153 .map_err(Into::into),
154 ["admin", "reset-password", username] => admin_cli::reset_password(&config_path, username)
155 .await
156 .map_err(Into::into),
157 ["admin", "set-username", current, new_display] => {
158 admin_cli::set_username(&config_path, current, new_display)
159 .await
160 .map_err(Into::into)
161 }
162 ["admin", "backup", rest @ ..] => match admin_cli::parse_backup_args(rest) {
163 Ok(opts) => admin_cli::backup(&config_path, opts)
164 .await
165 .map_err(Into::into),
166 Err(e) => {
167 eprintln!("forge: {e}\n{USAGE}");
168 return ExitCode::from(2);
169 }
170 },
171 ["admin", "restore", rest @ ..] => match admin_cli::parse_restore_args(rest) {
172 Ok((from, data_dir)) => admin_cli::restore(&from, &data_dir)
173 .await
174 .map_err(Into::into),
175 Err(e) => {
176 eprintln!("forge: {e}\n{USAGE}");
177 return ExitCode::from(2);
178 }
179 },
180 ["admin", "fsck"] => admin_cli::fsck(&config_path, false)
181 .await
182 .map_err(Into::into),
183 ["admin", "fsck", "--full"] => admin_cli::fsck(&config_path, true)
184 .await
185 .map_err(Into::into),
186 ["admin", "lfs-gc"] => admin_cli::lfs_gc(&config_path).await.map_err(Into::into),
187 ["admin", "backup-verify", path] => admin_cli::backup_verify(std::path::Path::new(path))
188 .await
189 .map_err(Into::into),
190 ["admin", "trash-list"] => admin_cli::trash_list(&config_path).map_err(Into::into),
191 ["admin", "delete-user", username] => admin_cli::delete_user(&config_path, username, false)
192 .await
193 .map_err(Into::into),
194 ["admin", "delete-user", username, "--yes"] => {
195 admin_cli::delete_user(&config_path, username, true)
196 .await
197 .map_err(Into::into)
198 }
199 ["admin", "audit-purge", "--older-than-days", days] => match days.parse::<u64>() {
200 Ok(days) => admin_cli::audit_purge(&config_path, days)
201 .await
202 .map_err(Into::into),
203 Err(_) => {
204 eprintln!("forge: --older-than-days needs a whole number of days\n{USAGE}");
205 return ExitCode::from(2);
206 }
207 },
208 ["admin", "trash-purge", "--older-than-days", days] => match days.parse::<u64>() {
209 Ok(days) => admin_cli::trash_purge(&config_path, days).map_err(Into::into),
210 Err(_) => {
211 eprintln!("forge: --older-than-days needs a whole number of days\n{USAGE}");
212 return ExitCode::from(2);
213 }
214 },
215 _ => {
216 eprintln!("{USAGE}");
217 return ExitCode::from(2);
218 }
219 };
220
221 match outcome {
222 // A self-update swap asks for a restart. systemd Restart=on-failure starts the new binary.
223 Ok(()) if self_update::restart_pending() => ExitCode::from(self_update::RESTART_EXIT_CODE),
224 Ok(()) => ExitCode::SUCCESS,
225 Err(e) => {
226 eprintln!("forge: {e}");
227 ExitCode::FAILURE
228 }
229 }
230}