1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
use crate::auth::AuthConfig;
use crate::errors::*;
use serde::Deserialize;
use std::collections::HashMap;
use std::fs;
use std::path::{Path, PathBuf};

pub const IDLE_DELAY: u64 = 180;
pub const PING_DEADLINE: i64 = IDLE_DELAY as i64 + 20;
pub const PING_INTERVAL: u64 = 60;
pub const WORKER_DELAY: u64 = 3;
pub const API_ERROR_DELAY: u64 = 30;

pub const DEFAULT_RETRY_DELAY_BASE: i64 = 24;

pub fn load<P: AsRef<Path>>(path: Option<P>) -> Result<ConfigFile> {
    let mut config = ConfigFile::default();

    if let Some(c) = load_from("/etc/rebuilderd.conf")? {
        config.update(c);
    }

    if let Ok(path) = config_path() {
        if let Some(c) = load_from(path)? {
            config.update(c);
        }
    }

    if let Some(path) = path {
        let c = load_from(path)?
            .ok_or_else(|| format_err!("Failed to read config file"))?;
        config.update(c);
    }

    Ok(config)
}

fn config_path() -> Result<PathBuf> {
    let config_dir = dirs_next::config_dir()
        .ok_or_else(|| format_err!("Failed to find config dir"))?;
    Ok(config_dir.join("rebuilderd.conf"))
}

fn load_from<P: AsRef<Path>>(path: P) -> Result<Option<ConfigFile>> {
    if let Ok(buf) = fs::read_to_string(path.as_ref()) {
        debug!("loading config file {:?}", path.as_ref());
        let config = toml::from_str(&buf)
            .context("Failed to load config")?;
        Ok(Some(config))
    } else {
        Ok(None)
    }
}

#[derive(Debug, Default, Clone, Deserialize)]
pub struct ConfigFile {
    #[serde(default)]
    pub http: HttpConfig,
    #[serde(default)]
    pub auth: AuthConfig,
    #[serde(default)]
    pub endpoints: HashMap<String, EndpointConfig>,
    #[serde(default)]
    pub worker: WorkerConfig,
    #[serde(default)]
    pub schedule: ScheduleConfig,
}

impl ConfigFile {
    pub fn update(&mut self, c: ConfigFile) {
        self.http.update(c.http);
        self.auth.update(c.auth);
        for (k, v) in c.endpoints {
            if let Some(o) = self.endpoints.get_mut(&k) {
                o.update(v);
            } else {
                self.endpoints.insert(k, v);
            }
        }
        self.worker.update(c.worker);
        self.schedule.update(c.schedule);
    }
}

#[derive(Debug, Default, Clone, Deserialize)]
pub struct HttpConfig {
    pub bind_addr: Option<String>,
    pub real_ip_header: Option<String>,
    pub post_body_size_limit: Option<usize>,
    pub endpoint: Option<String>,
}

impl HttpConfig {
    pub fn update(&mut self, c: HttpConfig) {
        if c.bind_addr.is_some() {
            self.bind_addr = c.bind_addr;
        }
        if c.real_ip_header.is_some() {
            self.real_ip_header = c.real_ip_header;
        }
        if c.endpoint.is_some() {
            self.endpoint = c.endpoint;
        }
    }
}

#[derive(Debug, Default, Clone, Deserialize)]
pub struct EndpointConfig {
    pub cookie: String,
}

impl EndpointConfig {
    pub fn update(&mut self, c: EndpointConfig) {
        self.cookie = c.cookie;
    }
}

#[derive(Debug, Default, Clone, Deserialize)]
pub struct WorkerConfig {
    #[serde(default)]
    pub authorized_workers: Vec<String>,
    pub signup_secret: Option<String>,
}

impl WorkerConfig {
    pub fn update(&mut self, c: WorkerConfig) {
        if !c.authorized_workers.is_empty() {
            self.authorized_workers = c.authorized_workers;
        }
        if c.signup_secret.is_some() {
            self.signup_secret = c.signup_secret;
        }
    }
}

#[derive(Debug, Default, Clone, Deserialize)]
pub struct ScheduleConfig {
    retry_delay_base: Option<i64>,
}

impl ScheduleConfig {
    pub fn update(&mut self, c: ScheduleConfig) {
        if c.retry_delay_base.is_some() {
            self.retry_delay_base = c.retry_delay_base;
        }
    }

    pub fn retry_delay_base(&self) -> i64 {
        self.retry_delay_base.unwrap_or(DEFAULT_RETRY_DELAY_BASE)
    }
}