Skip to main content

rns_ctl/
config.rs

1use crate::args::Args;
2
3/// Configuration for rns-ctl HTTP server.
4pub struct CtlConfig {
5    /// Bind host (default: "127.0.0.1").
6    pub host: String,
7    /// HTTP port (default: 8080).
8    pub port: u16,
9    /// Bearer token for auth. If None and !disable_auth, a random token is generated.
10    pub auth_token: Option<String>,
11    /// Skip auth entirely.
12    pub disable_auth: bool,
13    /// Path to RNS config directory.
14    pub config_path: Option<String>,
15    /// Connect as shared instance client (--daemon).
16    pub daemon_mode: bool,
17    /// TLS certificate path.
18    pub tls_cert: Option<String>,
19    /// TLS private key path.
20    pub tls_key: Option<String>,
21}
22
23impl Default for CtlConfig {
24    fn default() -> Self {
25        CtlConfig {
26            host: "127.0.0.1".into(),
27            port: 8080,
28            auth_token: None,
29            disable_auth: false,
30            config_path: None,
31            daemon_mode: false,
32            tls_cert: None,
33            tls_key: None,
34        }
35    }
36}
37
38/// Build CtlConfig from CLI args + environment variables.
39pub fn from_args_and_env(args: &Args) -> CtlConfig {
40    let mut cfg = CtlConfig::default();
41
42    // CLI args take precedence over env vars
43    cfg.host = args
44        .get("host")
45        .or_else(|| args.get("H"))
46        .map(String::from)
47        .or_else(|| std::env::var("RNSCTL_HOST").ok())
48        .unwrap_or(cfg.host);
49
50    cfg.port = args
51        .get("port")
52        .or_else(|| args.get("p"))
53        .and_then(|s| s.parse().ok())
54        .or_else(|| {
55            std::env::var("RNSCTL_HTTP_PORT")
56                .ok()
57                .and_then(|s| s.parse().ok())
58        })
59        .unwrap_or(cfg.port);
60
61    cfg.auth_token = args
62        .get("token")
63        .or_else(|| args.get("t"))
64        .map(String::from)
65        .or_else(|| std::env::var("RNSCTL_AUTH_TOKEN").ok());
66
67    cfg.disable_auth = args.has("disable-auth")
68        || std::env::var("RNSCTL_DISABLE_AUTH")
69            .map(|v| v == "true" || v == "1")
70            .unwrap_or(false);
71
72    cfg.config_path = args
73        .get("config")
74        .or_else(|| args.get("c"))
75        .map(String::from)
76        .or_else(|| std::env::var("RNSCTL_CONFIG_PATH").ok());
77
78    cfg.daemon_mode = args.has("daemon") || args.has("d");
79
80    cfg.tls_cert = args
81        .get("tls-cert")
82        .map(String::from)
83        .or_else(|| std::env::var("RNSCTL_TLS_CERT").ok());
84
85    cfg.tls_key = args
86        .get("tls-key")
87        .map(String::from)
88        .or_else(|| std::env::var("RNSCTL_TLS_KEY").ok());
89
90    cfg
91}
92
93#[cfg(test)]
94mod tests {
95    use super::*;
96
97    fn args(s: &[&str]) -> Args {
98        Args::parse_from(s.iter().map(|s| s.to_string()).collect())
99    }
100
101    #[test]
102    fn parse_basic() {
103        let a = args(&["--port", "9090", "--host", "0.0.0.0", "-vv"]);
104        assert_eq!(a.get("port"), Some("9090"));
105        assert_eq!(a.get("host"), Some("0.0.0.0"));
106        assert_eq!(a.verbosity, 2);
107    }
108
109    #[test]
110    fn parse_short_config() {
111        let a = args(&["-c", "/tmp/rns"]);
112        assert_eq!(a.get("c"), Some("/tmp/rns"));
113    }
114
115    #[test]
116    fn parse_daemon_short() {
117        let a = args(&["-d"]);
118        assert!(a.has("d"));
119        let cfg = from_args_and_env(&a);
120        assert!(cfg.daemon_mode);
121    }
122
123    #[test]
124    fn parse_daemon_long() {
125        let a = args(&["--daemon"]);
126        assert!(a.has("daemon"));
127        let cfg = from_args_and_env(&a);
128        assert!(cfg.daemon_mode);
129    }
130
131    #[test]
132    fn parse_disable_auth() {
133        let a = args(&["--disable-auth"]);
134        assert!(a.has("disable-auth"));
135    }
136
137    #[test]
138    fn parse_help() {
139        let a = args(&["--help"]);
140        assert!(a.has("help"));
141        let a = args(&["-h"]);
142        assert!(a.has("help"));
143    }
144
145    #[test]
146    fn config_from_args() {
147        let a = args(&[
148            "--port", "3000", "--host", "0.0.0.0", "--token", "secret", "--daemon",
149        ]);
150        let cfg = from_args_and_env(&a);
151        assert_eq!(cfg.port, 3000);
152        assert_eq!(cfg.host, "0.0.0.0");
153        assert_eq!(cfg.auth_token.as_deref(), Some("secret"));
154        assert!(cfg.daemon_mode);
155    }
156}