gitway_lib/config.rs
1// SPDX-License-Identifier: GPL-3.0-or-later
2// Rust guideline compliant 2026-03-30
3//! Configuration builder for a Gitway session.
4//!
5//! # Examples
6//!
7//! ```rust
8//! use gitway_lib::GitwayConfig;
9//! use std::time::Duration;
10//!
11//! // Connect to GitHub (default):
12//! let config = GitwayConfig::github();
13//!
14//! // Connect to GitLab:
15//! let config = GitwayConfig::gitlab();
16//!
17//! // Connect to Codeberg:
18//! let config = GitwayConfig::codeberg();
19//!
20//! // Connect to any host with a custom port:
21//! let config = GitwayConfig::builder("git.example.com")
22//! .port(22)
23//! .username("git")
24//! .inactivity_timeout(Duration::from_secs(60))
25//! .build();
26//! ```
27
28use std::path::PathBuf;
29use std::time::Duration;
30
31use crate::hostkey::{
32 DEFAULT_CODEBERG_HOST, DEFAULT_GITHUB_HOST, DEFAULT_GITLAB_HOST, DEFAULT_PORT,
33 FALLBACK_PORT, GITHUB_FALLBACK_HOST, GITLAB_FALLBACK_HOST,
34};
35
36// ── Public config type ────────────────────────────────────────────────────────
37
38/// Immutable configuration for a [`GitwaySession`](crate::GitwaySession).
39///
40/// Construct via [`GitwayConfig::builder`], or use one of the convenience
41/// constructors ([`github`](Self::github), [`gitlab`](Self::gitlab),
42/// [`codeberg`](Self::codeberg)) for the most common targets.
43#[derive(Debug, Clone)]
44pub struct GitwayConfig {
45 /// Primary SSH host (e.g. `github.com`, `gitlab.com`, `codeberg.org`).
46 pub host: String,
47 /// Primary SSH port (default: 22).
48 pub port: u16,
49 /// Remote username (always `git` for hosted services; FR-13).
50 pub username: String,
51 /// Explicit identity file path supplied via `--identity`.
52 pub identity_file: Option<PathBuf>,
53 /// OpenSSH certificate path supplied via `--cert`.
54 pub cert_file: Option<PathBuf>,
55 /// When `true`, skip host-key verification (FR-8).
56 pub skip_host_check: bool,
57 /// Inactivity timeout for the SSH session (FR-5).
58 ///
59 /// GitHub's idle threshold is around 60 s; this is the configured
60 /// client-side inactivity timeout, not a per-packet deadline.
61 pub inactivity_timeout: Duration,
62 /// Path to a `known_hosts`-style file for custom or self-hosted instances
63 /// (FR-7). Format: one `hostname SHA256:<fp>` entry per line.
64 pub custom_known_hosts: Option<PathBuf>,
65 /// Enable verbose debug logging when `true`.
66 pub verbose: bool,
67 /// Optional fallback host when port 22 is unavailable (FR-1).
68 ///
69 /// GitHub: `ssh.github.com:443`. GitLab: `altssh.gitlab.com:443`.
70 /// Codeberg has no published port-443 fallback.
71 pub fallback: Option<(String, u16)>,
72}
73
74impl GitwayConfig {
75 /// Begin building a config targeting `host`.
76 ///
77 /// All optional fields default to sensible values. No fallback host is
78 /// set by default; use the provider-specific convenience constructors
79 /// ([`github`](Self::github), [`gitlab`](Self::gitlab)) if you want the
80 /// port-443 fallback pre-configured.
81 pub fn builder(host: impl Into<String>) -> GitwayConfigBuilder {
82 GitwayConfigBuilder::new(host.into())
83 }
84
85 /// Convenience constructor for the default GitHub target (`github.com:22`).
86 ///
87 /// Includes the `ssh.github.com:443` fallback pre-configured.
88 #[must_use]
89 pub fn github() -> Self {
90 Self::builder(DEFAULT_GITHUB_HOST)
91 .fallback(Some((GITHUB_FALLBACK_HOST.to_owned(), FALLBACK_PORT)))
92 .build()
93 }
94
95 /// Convenience constructor for the default GitLab target (`gitlab.com:22`).
96 ///
97 /// Includes the `altssh.gitlab.com:443` fallback pre-configured.
98 #[must_use]
99 pub fn gitlab() -> Self {
100 Self::builder(DEFAULT_GITLAB_HOST)
101 .fallback(Some((GITLAB_FALLBACK_HOST.to_owned(), FALLBACK_PORT)))
102 .build()
103 }
104
105 /// Convenience constructor for Codeberg (`codeberg.org:22`).
106 ///
107 /// Codeberg has no published port-443 SSH fallback; no fallback is set.
108 #[must_use]
109 pub fn codeberg() -> Self {
110 Self::builder(DEFAULT_CODEBERG_HOST).build()
111 }
112}
113
114// ── Builder ───────────────────────────────────────────────────────────────────
115
116/// Builder for [`GitwayConfig`].
117///
118/// Obtained via [`GitwayConfig::builder`].
119#[derive(Debug)]
120#[must_use]
121pub struct GitwayConfigBuilder {
122 host: String,
123 port: u16,
124 username: String,
125 identity_file: Option<PathBuf>,
126 cert_file: Option<PathBuf>,
127 skip_host_check: bool,
128 inactivity_timeout: Duration,
129 custom_known_hosts: Option<PathBuf>,
130 verbose: bool,
131 fallback: Option<(String, u16)>,
132}
133
134impl GitwayConfigBuilder {
135 fn new(host: String) -> Self {
136 Self {
137 host,
138 port: DEFAULT_PORT,
139 username: "git".to_owned(),
140 identity_file: None,
141 cert_file: None,
142 skip_host_check: false,
143 // 60 seconds — large enough to survive slow host responses.
144 // Changing this below ~10 s risks spurious timeouts on congested
145 // links.
146 inactivity_timeout: Duration::from_secs(60),
147 custom_known_hosts: None,
148 verbose: false,
149 // No fallback by default; provider-specific convenience
150 // constructors set this when a known fallback exists.
151 fallback: None,
152 }
153 }
154
155 /// Override the target SSH port (default: 22, FR-1).
156 pub fn port(mut self, port: u16) -> Self {
157 self.port = port;
158 self
159 }
160
161 /// Override the remote username (default: `"git"`, FR-13).
162 pub fn username(mut self, username: impl Into<String>) -> Self {
163 self.username = username.into();
164 self
165 }
166
167 /// Set an explicit identity file path (FR-9 — highest priority).
168 pub fn identity_file(mut self, path: impl Into<PathBuf>) -> Self {
169 self.identity_file = Some(path.into());
170 self
171 }
172
173 /// Set an OpenSSH certificate path (FR-12).
174 pub fn cert_file(mut self, path: impl Into<PathBuf>) -> Self {
175 self.cert_file = Some(path.into());
176 self
177 }
178
179 /// Disable host-key verification. **Use only for emergencies** (FR-8).
180 pub fn skip_host_check(mut self, skip: bool) -> Self {
181 self.skip_host_check = skip;
182 self
183 }
184
185 /// Override the session inactivity timeout (FR-5).
186 pub fn inactivity_timeout(mut self, timeout: Duration) -> Self {
187 self.inactivity_timeout = timeout;
188 self
189 }
190
191 /// Path to a custom `known_hosts`-style file for self-hosted instances
192 /// (FR-7).
193 pub fn custom_known_hosts(mut self, path: impl Into<PathBuf>) -> Self {
194 self.custom_known_hosts = Some(path.into());
195 self
196 }
197
198 /// Enable verbose debug logging.
199 pub fn verbose(mut self, verbose: bool) -> Self {
200 self.verbose = verbose;
201 self
202 }
203
204 /// Override the fallback host/port. Pass `None` to disable fallback.
205 pub fn fallback(mut self, fallback: Option<(String, u16)>) -> Self {
206 self.fallback = fallback;
207 self
208 }
209
210 /// Finalise and return the [`GitwayConfig`].
211 #[must_use]
212 pub fn build(self) -> GitwayConfig {
213 GitwayConfig {
214 host: self.host,
215 port: self.port,
216 username: self.username,
217 identity_file: self.identity_file,
218 cert_file: self.cert_file,
219 skip_host_check: self.skip_host_check,
220 inactivity_timeout: self.inactivity_timeout,
221 custom_known_hosts: self.custom_known_hosts,
222 verbose: self.verbose,
223 fallback: self.fallback,
224 }
225 }
226}