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