tuwunel_service/config/
mod.rs1#[cfg(all(feature = "systemd", target_os = "linux"))]
2use std::borrow::Cow;
3use std::{iter::empty, ops::Deref, path::Path, sync::Arc};
4
5use async_trait::async_trait;
6#[cfg(all(feature = "systemd", target_os = "linux"))]
7use sd_notify::{NotifyState, notify};
8#[cfg(all(feature = "systemd", target_os = "linux"))]
9use tuwunel_core::itertools::Itertools;
10use tuwunel_core::{
11 Result, Server,
12 config::{Config, check},
13 error, implement,
14};
15
16pub struct Service {
17 server: Arc<Server>,
18}
19
20const SIGNAL: &str = "SIGUSR1";
21
22#[cfg(all(feature = "systemd", target_os = "linux"))]
24const STATUS_MAX: usize = 192;
25
26#[async_trait]
27impl crate::Service for Service {
28 fn build(args: &crate::Args<'_>) -> Result<Arc<Self>> {
29 Ok(Arc::new(Self { server: args.server.clone() }))
30 }
31
32 async fn worker(self: Arc<Self>) -> Result {
33 let mut signaled = self.server.signal.subscribe();
34 while self.server.is_running() {
35 tokio::select! {
36 () = self.server.until_shutdown() => break,
37 signal = signaled.recv() => if signal != Ok(SIGNAL) { continue; },
38 }
39
40 if let Err(e) = self.handle_reload() {
41 error!("Failed to reload config: {e}");
42 }
43 }
44
45 Ok(())
46 }
47
48 fn name(&self) -> &str { crate::service::make_name(std::module_path!()) }
49}
50
51impl Deref for Service {
52 type Target = Arc<Config>;
53
54 #[inline]
55 fn deref(&self) -> &Self::Target { &self.server.config }
56}
57
58#[implement(Service)]
59fn handle_reload(&self) -> Result {
60 #[cfg(all(feature = "systemd", target_os = "linux"))]
63 NotifyState::monotonic_usec_now()
64 .and_then(|monotonic| notify(&[NotifyState::Reloading, monotonic]))
65 .inspect_err(|e| error!(%e, "failed to notify systemd of reloading state"))
66 .ok();
67
68 let reloaded = self
69 .server
70 .config
71 .config_reload_signal
72 .then(|| self.reload(empty()))
73 .transpose();
74
75 #[cfg(all(feature = "systemd", target_os = "linux"))]
78 {
79 let status: Cow<'_, str> = match &reloaded {
80 | Ok(Some(_)) => "Configuration reloaded".into(),
81 | Ok(None) => "Configuration reloading is disabled".into(),
82 | Err(e) => format!("Configuration rejected: {e}").into(),
83 };
84
85 notify(&[NotifyState::Ready, NotifyState::Status(&one_line(&status))])
86 .inspect_err(|e| error!(%e, "failed to notify systemd of ready state"))
87 .ok();
88 };
89
90 reloaded?;
91
92 Ok(())
93}
94
95#[cfg(all(feature = "systemd", target_os = "linux"))]
98fn one_line(status: &str) -> String {
99 status
100 .split_whitespace()
101 .join(" ")
102 .chars()
103 .take(STATUS_MAX)
104 .collect()
105}
106
107#[implement(Service)]
108pub fn reload<'a, I>(&'a self, paths: I) -> Result<Arc<Config>>
109where
110 I: Iterator<Item = &'a Path>,
111{
112 let old = self.server.config.clone();
113
114 let new = self
116 .server
117 .config_sources
118 .load(paths)
119 .and_then(|raw| Config::new(&raw))?;
120
121 check::reload(&old, &new)?;
122 self.server.config.update(new)
123}