anvilsign in

collin/anvil

1//! Server configuration: loaded from a TOML file with sensible defaults.
2
3use std::path::{
4 Path,
5 PathBuf,
6};
7
8use serde::{
9 Deserialize,
10 Serialize,
11};
12
13use crate::error::{
14 Error,
15 Result,
16};
17
18/// Top-level anvil configuration.
19///
20/// Load with [`Config::load`] (from a TOML file) or [`Config::default`].
21#[derive(Debug, Clone, Serialize, Deserialize)]
22#[serde(default)]
23pub struct Config {
24 /// Root directory holding all server state (database + repositories).
25 pub data_dir: PathBuf,
26 /// HTTP server settings.
27 pub http: HttpConfig,
28 /// SSH server settings.
29 pub ssh: SshConfig,
30}
31
32#[derive(Debug, Clone, Serialize, Deserialize)]
33#[serde(default)]
34pub struct HttpConfig {
35 /// Address the HTTP server binds to, e.g. `127.0.0.1:3000`.
36 pub listen: String,
37 /// Externally visible base URL, used when constructing clone URLs.
38 pub base_url: String,
39}
40
41#[derive(Debug, Clone, Serialize, Deserialize)]
42#[serde(default)]
43pub struct SshConfig {
44 /// Whether the SSH git transport is enabled.
45 pub enabled: bool,
46 /// Address the SSH server binds to internally, e.g. `0.0.0.0:2222`. Under
47 /// Docker this is the in-container bind, which may differ from the
48 /// externally forwarded port — see the `clone_*` fields below.
49 pub listen: String,
50 /// Hostname shown in SSH clone URLs (what users actually connect to).
51 pub clone_host: String,
52 /// Port shown in SSH clone URLs. Set this to the *externally forwarded*
53 /// port when it differs from the internal bind (e.g. Docker `-p 2200:2222`).
54 pub clone_port: u16,
55 /// Username shown in SSH clone URLs (conventionally `git`).
56 pub clone_user: String,
57}
58
59impl Default for Config {
60 fn default() -> Self {
61 Self {
62 data_dir: PathBuf::from("data"),
63 http: HttpConfig::default(),
64 ssh: SshConfig::default(),
65 }
66 }
67}
68
69impl Default for HttpConfig {
70 fn default() -> Self {
71 Self {
72 listen: "127.0.0.1:3000".to_string(),
73 base_url: "http://localhost:3000".to_string(),
74 }
75 }
76}
77
78impl Default for SshConfig {
79 fn default() -> Self {
80 Self {
81 enabled: false,
82 listen: "127.0.0.1:2222".to_string(),
83 clone_host: "localhost".to_string(),
84 clone_port: 2222,
85 clone_user: "git".to_string(),
86 }
87 }
88}
89
90impl Config {
91 /// Load configuration from a TOML file. Missing fields fall back to defaults.
92 pub fn load(path: impl AsRef<Path>) -> Result<Self> {
93 let path = path.as_ref();
94 let text = std::fs::read_to_string(path)
95 .map_err(|e| Error::Config(format!("reading {}: {e}", path.display())))?;
96 toml::from_str(&text).map_err(|e| Error::Config(format!("parsing {}: {e}", path.display())))
97 }
98
99 /// Load from `path` if it exists, otherwise return defaults.
100 pub fn load_or_default(path: impl AsRef<Path>) -> Result<Self> {
101 let path = path.as_ref();
102 if path.exists() {
103 Self::load(path)
104 } else {
105 Ok(Self::default())
106 }
107 }
108
109 /// Filesystem path to the SQLite database file.
110 pub fn database_path(&self) -> PathBuf {
111 self.data_dir.join("anvil.db")
112 }
113
114 /// Root directory under which bare repositories are stored.
115 pub fn repositories_dir(&self) -> PathBuf {
116 self.data_dir.join("repositories")
117 }
118
119 /// The HTTP clone URL for `<owner>/<name>`, e.g.
120 /// `http://localhost:3000/alice/hello.git`.
121 pub fn http_clone_url(&self, owner: &str, name: &str) -> String {
122 format!(
123 "{}/{owner}/{name}.git",
124 self.http.base_url.trim_end_matches('/')
125 )
126 }
127
128 /// The SSH clone URL for `<owner>/<name>`, using the externally advertised
129 /// host/port/user (which may differ from the internal bind under Docker).
130 /// The port is omitted when it is the SSH default (22).
131 pub fn ssh_clone_url(&self, owner: &str, name: &str) -> String {
132 let ssh = &self.ssh;
133 if ssh.clone_port == 22 {
134 format!(
135 "ssh://{}@{}/{owner}/{name}.git",
136 ssh.clone_user, ssh.clone_host
137 )
138 } else {
139 format!(
140 "ssh://{}@{}:{}/{owner}/{name}.git",
141 ssh.clone_user, ssh.clone_host, ssh.clone_port
142 )
143 }
144 }
145}