Skip to main content

rns_ctl/
config.rs

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