diff --git a/client/src/definitions.ts b/client/src/definitions.ts index 183148d..2f4cc5e 100644 --- a/client/src/definitions.ts +++ b/client/src/definitions.ts @@ -310,6 +310,7 @@ export const editTypes: { [key: string]: { [key: string]: any } } = { name: 'NTP Server', idType: 'Number', fields: { + name: { is: 'TextBox', label: 'Name'}, interface: { is: 'SingleSelect', label: 'Interface', props: { searchProvider: GetInterfaces} }, comment: { is: 'MultilineTextBox', label: 'Comment'}, }, diff --git a/src/apply/chrony.rs b/src/apply/chrony.rs new file mode 100644 index 0000000..8c5081d --- /dev/null +++ b/src/apply/chrony.rs @@ -0,0 +1,65 @@ +use super::ApplyError; +use crate::{ + definitions::{config::Config, network::AddressingMode}, + templates, +}; +use ipnet::IpNet; +use std::process::Command; +use std::str::FromStr; +use std::{error::Error, io::Write}; +use tera::Context; +use tracing::{error, info}; + +const CHRONY_CONFIG_PATH: &str = "/etc/chrony.conf"; +const CHRONY_TEMPLATE_PATH: &str = "chrony/chrony.conf"; + +pub fn apply_chrony(pending_config: Config, _current_config: Config) -> Result<(), ApplyError> { + let config_data; + let mut context = Context::new(); + let mut subnets = vec![]; + + for server in &pending_config.service.ntp_servers { + if let AddressingMode::Static { address } = + &server.interface(pending_config.clone()).addressing_mode + { + subnets.push(IpNet::from_str(address)?.network().to_string()); + } + } + context.insert("subnets", &subnets); + + match templates::TEMPLATES.render(CHRONY_TEMPLATE_PATH, &context) { + Ok(s) => config_data = s, + Err(e) => { + error!("Error: {}", e); + let mut cause = e.source(); + while let Some(e) = cause { + error!("Reason: {}", e); + cause = e.source(); + } + return Err(ApplyError::TemplateError(e)); + } + } + + info!("Deleting old Chrony Config"); + std::fs::remove_file(CHRONY_CONFIG_PATH)?; + + info!("Writing new Chrony Config"); + let mut f = std::fs::File::create(CHRONY_CONFIG_PATH)?; + f.write_all(config_data.as_bytes())?; + + info!("Restarting Chrony"); + match Command::new("systemctl") + .arg("restart") + .arg("chronyd") + .output() + { + Ok(out) => { + if out.status.success() { + Ok(()) + } else { + Err(ApplyError::ServiceRestartFailed) + } + } + Err(err) => Err(ApplyError::IOError(err)), + } +} diff --git a/src/apply/mod.rs b/src/apply/mod.rs index 8022320..cb332c6 100644 --- a/src/apply/mod.rs +++ b/src/apply/mod.rs @@ -1,5 +1,6 @@ use thiserror::Error; +pub mod chrony; pub mod networkd; #[derive(Error, Debug)] @@ -10,6 +11,9 @@ pub enum ApplyError { #[error(transparent)] IOError(#[from] std::io::Error), + #[error(transparent)] + AddrParseError(#[from] ipnet::AddrParseError), + #[error("Service Restart Failed")] ServiceRestartFailed, } diff --git a/src/config_manager.rs b/src/config_manager.rs index 32acd62..bb8109c 100644 --- a/src/config_manager.rs +++ b/src/config_manager.rs @@ -35,7 +35,10 @@ pub const PENDING_CONFIG_PATH: &str = "pending.json"; static APPLY_FUNCTIONS: &'static [fn( pending_config: Config, current_config: Config, -) -> Result<(), super::apply::ApplyError>] = &[super::apply::networkd::apply_networkd]; +) -> Result<(), super::apply::ApplyError>] = &[ + super::apply::networkd::apply_networkd, + super::apply::chrony::apply_chrony, +]; #[derive(Clone)] pub struct ConfigManager { diff --git a/src/templates/chrony/chrony.conf b/src/templates/chrony/chrony.conf new file mode 100644 index 0000000..4f5de3f --- /dev/null +++ b/src/templates/chrony/chrony.conf @@ -0,0 +1,13 @@ +pool pool.ntp.org iburst +driftfile /var/lib/chrony/drift +makestep 1.0 3 +rtcsync +keyfile /etc/chrony.keys +ntsdumpdir /var/lib/chrony +leapsectz right/UTC +logdir /var/log/chrony + +# Allowed Networks +{% for subnet in subnets -%} +allow {{ subnet }} +{% endfor -%} \ No newline at end of file diff --git a/src/templates/mod.rs b/src/templates/mod.rs index 2432dcc..a7ff0b7 100644 --- a/src/templates/mod.rs +++ b/src/templates/mod.rs @@ -2,7 +2,7 @@ use tera::Tera; lazy_static! { pub static ref TEMPLATES: Tera = { - let tera = match Tera::new("src/templates/**/*.net*") { + let tera = match Tera::new("src/templates/**/*.*") { Ok(t) => t, Err(e) => { println!("Parsing error(s): {}", e);