samjeston / openhub Public
Sam Jeston Working vertical 9aa3c45 · History
118 lines· 3.7 KB ·Rust
Raw
1use std::net::SocketAddr;
2use std::path::PathBuf;
3
4use clap::Parser;
5
6/// Openhub: a minimal, self-hostable git server.
7#[derive(Parser, Debug, Clone)]
8#[command(name = "openhub", version, about)]
9pub 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
47impl 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}