feat(daemon): storage::policy + PolicyChanged signal + inotify watcher
Lands plan tasks 2.6 (PolicyStore wraps load_from_dir / save_local), 2.7
(PolicyChanged D-Bus signal on the AuthForge interface), and 2.8 (notify-based
inotify watcher in main.rs that emits the signal on any change in the policy.d
directory). Bundled because watcher → emit signal → wraps PolicyStore is one
data flow.
- daemon/src/storage/{mod,policy}.rs — PolicyStore::{load,save,watch}; watch
returns a (RecommendedWatcher, watch::Receiver) so the caller keeps the
watcher alive.
- daemon/src/dbus.rs — adds #[zbus(signal)] policy_changed; integration test
via p2p connection asserts the signal arrives within 2s.
- daemon/src/main.rs — spawns a task that ticks PolicyChanged on every
rx.changed(), keyed off AUTHFORGE_POLICY_DIR env var (default
/etc/authforge/policy.d). Watcher leaked via std::mem::forget; daemon
lifetime = process lifetime.
Test count: 17/17 daemon (was 14) + 13/13 common.
This commit is contained in:
@@ -1,10 +1,12 @@
|
||||
use anyhow::{Context, Result};
|
||||
use std::path::PathBuf;
|
||||
use std::sync::Arc;
|
||||
use tracing::{info, warn};
|
||||
|
||||
mod dbus;
|
||||
mod polkit;
|
||||
mod state;
|
||||
mod storage;
|
||||
|
||||
const BUS_NAME: &str = "io.dangerousthings.AuthForge";
|
||||
const OBJECT_PATH: &str = "/io/dangerousthings/AuthForge";
|
||||
@@ -42,6 +44,41 @@ async fn main() -> Result<()> {
|
||||
|
||||
info!("listening on D-Bus as {BUS_NAME} at {OBJECT_PATH}");
|
||||
|
||||
// Inotify watcher → PolicyChanged signal.
|
||||
let policy_dir = std::env::var_os("AUTHFORGE_POLICY_DIR")
|
||||
.map(PathBuf::from)
|
||||
.unwrap_or_else(|| PathBuf::from("/etc/authforge/policy.d"));
|
||||
let policy_store = storage::policy::PolicyStore::new(policy_dir.clone());
|
||||
match policy_store.watch() {
|
||||
Ok((watcher, mut rx)) => {
|
||||
let conn_for_signal = conn.clone();
|
||||
tokio::spawn(async move {
|
||||
while rx.changed().await.is_ok() {
|
||||
let iface_ref = match conn_for_signal
|
||||
.object_server()
|
||||
.interface::<_, dbus::AuthForge>(OBJECT_PATH)
|
||||
.await
|
||||
{
|
||||
Ok(r) => r,
|
||||
Err(e) => {
|
||||
warn!(error=%e, "no AuthForge interface; skipping signal");
|
||||
continue;
|
||||
}
|
||||
};
|
||||
if let Err(e) =
|
||||
dbus::AuthForge::policy_changed(iface_ref.signal_context()).await
|
||||
{
|
||||
warn!(error=%e, "PolicyChanged emit failed");
|
||||
}
|
||||
}
|
||||
});
|
||||
// Daemon lives until SIGTERM; deliberately leak to keep watcher alive.
|
||||
std::mem::forget(watcher);
|
||||
info!("watching {} for policy changes", policy_dir.display());
|
||||
}
|
||||
Err(e) => warn!(error=%e, "failed to start policy.d watcher"),
|
||||
}
|
||||
|
||||
// Park forever. systemd will SIGTERM the process when stopping the unit.
|
||||
std::future::pending::<()>().await;
|
||||
Ok(())
|
||||
|
||||
Reference in New Issue
Block a user