| 1 | |
| 2 | |
| 3 | |
| 4 | |
| 5 | |
| 6 | |
| 7 | #![recursion_limit = "256"] |
| 8 | |
| 9 | mod access; |
| 10 | mod admin_cli; |
| 11 | mod admin_client; |
| 12 | mod advisories; |
| 13 | mod api; |
| 14 | mod audit; |
| 15 | mod auth; |
| 16 | mod backup; |
| 17 | mod blobs; |
| 18 | mod boot; |
| 19 | mod ci; |
| 20 | mod client_ip; |
| 21 | mod compression; |
| 22 | mod git_auth; |
| 23 | mod git_http; |
| 24 | mod hook_cmd; |
| 25 | mod i18n; |
| 26 | mod internal; |
| 27 | mod lfs; |
| 28 | mod lfs_pointer; |
| 29 | mod mail; |
| 30 | mod mirrors; |
| 31 | mod notify; |
| 32 | mod notify_hub; |
| 33 | mod pages; |
| 34 | mod post_update; |
| 35 | mod pulls_sync; |
| 36 | mod raw; |
| 37 | mod render; |
| 38 | mod replication; |
| 39 | mod self_update; |
| 40 | mod signatures; |
| 41 | mod ssh_backend; |
| 42 | mod state; |
| 43 | mod templates; |
| 44 | #[cfg(test)] |
| 45 | mod test_support; |
| 46 | mod theme; |
| 47 | mod throttle; |
| 48 | mod views; |
| 49 | mod webhooks; |
| 50 | |
| 51 | use std::path::PathBuf; |
| 52 | use std::process::ExitCode; |
| 53 | |
| 54 | use forge_core::Config; |
| 55 | use tracing_subscriber::EnvFilter; |
| 56 | |
| 57 | use topcoat::router::route; |
| 58 | |
| 59 | type BoxError = Box<dyn std::error::Error + Send + Sync>; |
| 60 | |
| 61 | const 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>]"; |
| 62 | const DEFAULT_CONFIG: &str = "config/forge.toml"; |
| 63 | |
| 64 | #[route(GET "/healthz")] |
| 65 | async fn healthz() -> Result<String, topcoat::Error> { |
| 66 | Ok(format!("ok {}", env!("FORGE_BUILD_SHA"))) |
| 67 | } |
| 68 | |
| 69 | async 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 | |
| 76 | fn 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 | |
| 96 | fn main() -> ExitCode { |
| 97 | |
| 98 | if forge_runner::cli::is_sandbox_invocation() { |
| 99 | std::process::exit(forge_runner::cli::hidden_main()); |
| 100 | } |
| 101 | |
| 102 | |
| 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 | |
| 120 | async fn real_main() -> ExitCode { |
| 121 | |
| 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 | |
| 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 | } |