Sam Jeston
History
118 lines·
3.7 KB
·Rust
| 1 | use std::net::SocketAddr; |
| 2 | use std::path::PathBuf; |
| 3 | |
| 4 | use clap::Parser; |
| 5 | |
| 6 | /// Openhub: a minimal, self-hostable git server. |
| 7 | #[derive(Parser, Debug, Clone)] |
| 8 | #[command(name = "openhub", version, about)] |
| 9 | pub struct Config { |
| 10 | /// Directory holding the database, bare repositories and the SSH host key. |
| 11 | #[arg(long, env = "OPENHUB_DATA_DIR", default_value = "./data")] |
| 12 | pub data_dir: PathBuf, |
| 13 | |
| 14 | /// Address the web interface (and git over HTTP) listens on. |
| 15 | #[arg(long, env = "OPENHUB_HTTP_ADDR", default_value = "0.0.0.0:3000")] |
| 16 | pub http_addr: SocketAddr, |
| 17 | |
| 18 | /// Address the built-in SSH server listens on. |
| 19 | #[arg(long, env = "OPENHUB_SSH_ADDR", default_value = "0.0.0.0:2222")] |
| 20 | pub ssh_addr: SocketAddr, |
| 21 | |
| 22 | /// Public base URL of the web interface, used for links and HTTP clone URLs. |
| 23 | #[arg( |
| 24 | long, |
| 25 | env = "OPENHUB_PUBLIC_URL", |
| 26 | default_value = "http://localhost:3000" |
| 27 | )] |
| 28 | pub public_url: String, |
| 29 | |
| 30 | /// Host name shown in SSH clone URLs. Defaults to the host of the public URL. |
| 31 | #[arg(long, env = "OPENHUB_SSH_HOST")] |
| 32 | pub ssh_host: Option<String>, |
| 33 | |
| 34 | /// Port shown in SSH clone URLs. Defaults to the SSH listen port. |
| 35 | #[arg(long, env = "OPENHUB_SSH_PORT")] |
| 36 | pub ssh_port: Option<u16>, |
| 37 | |
| 38 | /// Disable self-service sign up. The first account can always be created. |
| 39 | #[arg(long, env = "OPENHUB_DISABLE_REGISTRATION")] |
| 40 | pub disable_registration: bool, |
| 41 | |
| 42 | /// Path to the git executable used to serve fetches and pushes. |
| 43 | #[arg(long, env = "OPENHUB_GIT_BIN", default_value = "git")] |
| 44 | pub git_bin: String, |
| 45 | } |
| 46 | |
| 47 | impl Config { |
| 48 | pub fn normalize(&mut self) { |
| 49 | while self.public_url.ends_with('/') { |
| 50 | self.public_url.pop(); |
| 51 | } |
| 52 | } |
| 53 | |
| 54 | pub fn db_path(&self) -> PathBuf { |
| 55 | self.data_dir.join("openhub.db") |
| 56 | } |
| 57 | |
| 58 | pub fn repos_dir(&self) -> PathBuf { |
| 59 | self.data_dir.join("repos") |
| 60 | } |
| 61 | |
| 62 | pub fn host_key_path(&self) -> PathBuf { |
| 63 | self.data_dir.join("ssh_host_ed25519_key") |
| 64 | } |
| 65 | |
| 66 | pub fn repo_path(&self, repo_id: i64) -> PathBuf { |
| 67 | self.repos_dir().join(format!("{repo_id}.git")) |
| 68 | } |
| 69 | |
| 70 | pub fn secure_cookies(&self) -> bool { |
| 71 | self.public_url.starts_with("https://") |
| 72 | } |
| 73 | |
| 74 | fn public_host(&self) -> String { |
| 75 | let rest = self |
| 76 | .public_url |
| 77 | .split_once("://") |
| 78 | .map(|(_, r)| r) |
| 79 | .unwrap_or(&self.public_url); |
| 80 | let authority = rest.split('/').next().unwrap_or(rest); |
| 81 | // Strip a port, taking care not to break bracketed IPv6 literals. |
| 82 | match authority.rsplit_once(':') { |
| 83 | Some((host, port)) if !host.is_empty() && port.chars().all(|c| c.is_ascii_digit()) => { |
| 84 | host.to_string() |
| 85 | } |
| 86 | _ => authority.to_string(), |
| 87 | } |
| 88 | } |
| 89 | |
| 90 | pub fn ssh_display_host(&self) -> String { |
| 91 | self.ssh_host.clone().unwrap_or_else(|| self.public_host()) |
| 92 | } |
| 93 | |
| 94 | pub fn ssh_display_port(&self) -> u16 { |
| 95 | self.ssh_port.unwrap_or(self.ssh_addr.port()) |
| 96 | } |
| 97 | |
| 98 | pub fn ssh_clone_url(&self, owner: &str, repo: &str) -> String { |
| 99 | let host = self.ssh_display_host(); |
| 100 | match self.ssh_display_port() { |
| 101 | 22 => format!("git@{host}:{owner}/{repo}.git"), |
| 102 | port => format!("ssh://git@{host}:{port}/{owner}/{repo}.git"), |
| 103 | } |
| 104 | } |
| 105 | |
| 106 | pub fn http_clone_url(&self, owner: &str, repo: &str) -> String { |
| 107 | format!("{}/{owner}/{repo}.git", self.public_url) |
| 108 | } |
| 109 | |
| 110 | /// The command users can run to check their SSH key is accepted. |
| 111 | pub fn ssh_test_command(&self) -> String { |
| 112 | let host = self.ssh_display_host(); |
| 113 | match self.ssh_display_port() { |
| 114 | 22 => format!("ssh -T git@{host}"), |
| 115 | port => format!("ssh -T -p {port} git@{host}"), |
| 116 | } |
| 117 | } |
| 118 | } |