// SPDX-FileCopyrightText: 2025 Tomasz Kramkowski // SPDX-License-Identifier: GPL-3.0-or-later use std::{ collections::HashMap, fs::File, io::Read, os::unix::fs::PermissionsExt, path::Path, process, time::Duration, }; use anyhow::bail; use rumqttc::{AsyncClient, EventLoop, MqttOptions}; use serde::Deserialize; use crate::PROGRAM; #[derive(Deserialize, Debug)] pub struct Credentials { pub username: String, pub password: String, } fn default_host() -> String { "localhost".to_string() } fn default_port() -> u16 { 1883 } fn default_id() -> String { PROGRAM.to_string() } #[derive(Deserialize, Debug)] pub struct Config { #[serde(default = "default_host")] pub host: String, #[serde(default = "default_port")] pub port: u16, pub credentials: Option, #[serde(default = "default_id")] pub id: String, // TODO: Figure out a way to allow arbitrary unix paths (arbitrary // non-unicode) without base64 pub routes: HashMap>>, } impl Config { pub fn mqtt_client(&self) -> (AsyncClient, EventLoop) { let client_id = format!("{}_{}", self.id, process::id()); let mut options = MqttOptions::new(client_id, &self.host, self.port); if let Some(credentials) = &self.credentials { options.set_credentials(&credentials.username, &credentials.password); } options.set_keep_alive(Duration::from_secs(5)); options.set_max_packet_size(10 * 1024 * 1024, 10 * 1024 * 1024); AsyncClient::new(options, 10) } } pub fn load>(path: P) -> anyhow::Result { let mut f = File::open(path)?; let mut config = String::new(); f.read_to_string(&mut config)?; let config: Config = toml::from_str(&config)?; if config.credentials.is_some() { let mode = f.metadata()?.permissions().mode(); if mode & 0o044 != 0o000 { bail!("Config file contains credentials while being group or world readable."); } } Ok(config) }