From: Arthur Bied-Charreton <a.bied-charreton@proxmox.com>
To: pve-devel@lists.proxmox.com
Subject: [PATCH proxmox 2/5] notify: Add state file handling
Date: Wed, 4 Feb 2026 17:13:41 +0100 [thread overview]
Message-ID: <20260204161354.458814-3-a.bied-charreton@proxmox.com> (raw)
In-Reply-To: <20260204161354.458814-1-a.bied-charreton@proxmox.com>
Add State struct abstracting state file deserialization, updates and
persistence, as well as an EndpointState marker trait stateful endpoints
may implement.
Also add a state_file_path method to the crate's Context trait, which
allows
tests to build their own context instead of depending on statics.
As far as SMTP endpoints are concerned, file locks are not necessary.
Old Microsoft tokens stay valid for 90 days after refreshes [1], and
Google
tokens' lifetime is just extended at every use [2], so concurrent reads
should not
be an issue here.
[1]
https://learn.microsoft.com/en-us/entra/identity-platform/refresh-tokens#token-lifetime
[2]
https://stackoverflow.com/questions/8953983/do-google-refresh-tokens-expire
Signed-off-by: Arthur Bied-Charreton <a.bied-charreton@proxmox.com>
---
proxmox-notify/Cargo.toml | 1 +
proxmox-notify/debian/control | 2 +
proxmox-notify/src/context/mod.rs | 2 +
proxmox-notify/src/context/pbs.rs | 4 ++
proxmox-notify/src/context/pve.rs | 4 ++
proxmox-notify/src/context/test.rs | 4 ++
proxmox-notify/src/lib.rs | 60 ++++++++++++++++++++++++++++++
7 files changed, 77 insertions(+)
diff --git a/proxmox-notify/Cargo.toml b/proxmox-notify/Cargo.toml
index 52493ef7..daa10296 100644
--- a/proxmox-notify/Cargo.toml
+++ b/proxmox-notify/Cargo.toml
@@ -40,6 +40,7 @@ proxmox-sendmail = { workspace = true, optional = true }
proxmox-sys = { workspace = true, optional = true }
proxmox-time.workspace = true
proxmox-uuid = { workspace = true, features = ["serde"] }
+nix.workspace = true
[features]
default = ["sendmail", "gotify", "smtp", "webhook"]
diff --git a/proxmox-notify/debian/control b/proxmox-notify/debian/control
index 7770f5ee..76b8a1fa 100644
--- a/proxmox-notify/debian/control
+++ b/proxmox-notify/debian/control
@@ -11,6 +11,7 @@ Build-Depends-Arch: cargo:native <!nocheck>,
librust-handlebars-5+default-dev <!nocheck>,
librust-http-1+default-dev <!nocheck>,
librust-lettre-0.11+default-dev (>= 0.11.1-~~) <!nocheck>,
+ librust-nix-0.29+default-dev <!nocheck>,
librust-oauth2-5+default-dev <!nocheck>,
librust-openssl-0.10+default-dev <!nocheck>,
librust-percent-encoding-2+default-dev (>= 2.1-~~) <!nocheck>,
@@ -52,6 +53,7 @@ Depends:
librust-anyhow-1+default-dev,
librust-const-format-0.2+default-dev,
librust-handlebars-5+default-dev,
+ librust-nix-0.29+default-dev,
librust-oauth2-5+default-dev,
librust-openssl-0.10+default-dev,
librust-proxmox-http-error-1+default-dev,
diff --git a/proxmox-notify/src/context/mod.rs b/proxmox-notify/src/context/mod.rs
index 8b6e2c43..86130409 100644
--- a/proxmox-notify/src/context/mod.rs
+++ b/proxmox-notify/src/context/mod.rs
@@ -32,6 +32,8 @@ pub trait Context: Send + Sync + Debug {
namespace: Option<&str>,
source: TemplateSource,
) -> Result<Option<String>, Error>;
+ /// Return the state file, or None if no state file exists for this context.
+ fn state_file_path(&self) -> &'static str;
}
#[cfg(not(test))]
diff --git a/proxmox-notify/src/context/pbs.rs b/proxmox-notify/src/context/pbs.rs
index 3e5da59c..67010060 100644
--- a/proxmox-notify/src/context/pbs.rs
+++ b/proxmox-notify/src/context/pbs.rs
@@ -125,6 +125,10 @@ impl Context for PBSContext {
.map_err(|err| Error::Generic(format!("could not load template: {err}")))?;
Ok(template_string)
}
+
+ fn state_file_path(&self) -> &'static str {
+ "/etc/proxmox-backup/notifications.state.json"
+ }
}
#[cfg(test)]
diff --git a/proxmox-notify/src/context/pve.rs b/proxmox-notify/src/context/pve.rs
index a97cce26..0dffbb11 100644
--- a/proxmox-notify/src/context/pve.rs
+++ b/proxmox-notify/src/context/pve.rs
@@ -74,6 +74,10 @@ impl Context for PVEContext {
.map_err(|err| Error::Generic(format!("could not load template: {err}")))?;
Ok(template_string)
}
+
+ fn state_file_path(&self) -> &'static str {
+ "/etc/pve/priv/notifications.state.json"
+ }
}
pub static PVE_CONTEXT: PVEContext = PVEContext;
diff --git a/proxmox-notify/src/context/test.rs b/proxmox-notify/src/context/test.rs
index 2c236b4c..e0236b9c 100644
--- a/proxmox-notify/src/context/test.rs
+++ b/proxmox-notify/src/context/test.rs
@@ -40,4 +40,8 @@ impl Context for TestContext {
) -> Result<Option<String>, Error> {
Ok(Some(String::new()))
}
+
+ fn state_file_path(&self) -> &'static str {
+ "/tmp/notifications.state.json"
+ }
}
diff --git a/proxmox-notify/src/lib.rs b/proxmox-notify/src/lib.rs
index 1134027c..a40342cc 100644
--- a/proxmox-notify/src/lib.rs
+++ b/proxmox-notify/src/lib.rs
@@ -6,6 +6,7 @@ use std::fmt::Display;
use std::str::FromStr;
use context::context;
+use serde::de::DeserializeOwned;
use serde::{Deserialize, Serialize};
use serde_json::json;
use serde_json::Value;
@@ -272,6 +273,65 @@ impl Notification {
}
}
+#[derive(Debug, Clone, Serialize, Deserialize, Default, PartialEq, Eq)]
+pub struct State {
+ #[serde(flatten)]
+ pub sections: HashMap<String, Value>,
+}
+
+impl FromStr for State {
+ type Err = Error;
+
+ fn from_str(s: &str) -> Result<Self, Self::Err> {
+ serde_json::from_str(s).map_err(|e| Error::ConfigDeserialization(e.into()))
+ }
+}
+
+/// Marker trait to be implemented by the state structs for stateful endpoints.
+pub trait EndpointState: Serialize + DeserializeOwned + Default {}
+
+impl State {
+ pub fn from_path<P: AsRef<std::path::Path>>(path: P) -> Result<Self, Error> {
+ let contents = proxmox_sys::fs::file_read_string(path)
+ .map_err(|e| Error::ConfigDeserialization(e.into()))?;
+ Self::from_str(&contents)
+ }
+
+ pub fn persist<P: AsRef<std::path::Path>>(&self, path: P) -> Result<(), Error> {
+ let state_str =
+ serde_json::to_string_pretty(self).map_err(|e| Error::ConfigSerialization(e.into()))?;
+
+ let mode = nix::sys::stat::Mode::from_bits_truncate(0o600);
+ let options = proxmox_sys::fs::CreateOptions::new().perm(mode);
+
+ proxmox_sys::fs::replace_file(path, state_str.as_bytes(), options, true)
+ .map_err(|e| Error::ConfigSerialization(e.into()))
+ }
+
+ pub fn get<S: EndpointState>(&self, name: &str) -> Result<Option<S>, Error> {
+ match self.sections.get(name) {
+ Some(v) => Ok(Some(
+ S::deserialize(v).map_err(|e| Error::ConfigDeserialization(e.into()))?,
+ )),
+ None => Ok(None),
+ }
+ }
+
+ pub fn get_or_default<S: EndpointState>(&self, name: &str) -> Result<S, Error> {
+ Ok(self.get(name)?.unwrap_or_default())
+ }
+
+ pub fn set<S: EndpointState>(&mut self, name: &str, state: &S) -> Result<(), Error> {
+ let v = serde_json::to_value(state).map_err(|e| Error::ConfigSerialization(e.into()))?;
+ self.sections.insert(name.to_string(), v);
+ Ok(())
+ }
+
+ pub fn remove(&mut self, name: &str) {
+ self.sections.remove(name);
+ }
+}
+
/// Notification configuration
#[derive(Debug, Clone)]
pub struct Config {
--
2.47.3
next prev parent reply other threads:[~2026-02-04 16:14 UTC|newest]
Thread overview: 16+ messages / expand[flat|nested] mbox.gz Atom feed top
2026-02-04 16:13 [RFC cluster/docs/manager/proxmox{,-perl-rs,-widget-toolkit} 00/15] fix #7238: Add XOAUTH2 authentication support for SMTP notification targets Arthur Bied-Charreton
2026-02-04 16:13 ` [PATCH proxmox 1/5] notify: Introduce xoauth2 module Arthur Bied-Charreton
2026-02-04 16:13 ` Arthur Bied-Charreton [this message]
2026-02-04 16:13 ` [PATCH proxmox 3/5] notify: Update Endpoint trait and Bus to use State Arthur Bied-Charreton
2026-02-04 16:13 ` [PATCH proxmox 4/5] notify: smtp: add OAuth2/XOAUTH2 authentication support Arthur Bied-Charreton
2026-02-04 16:13 ` [PATCH proxmox 5/5] notify: Add test for State Arthur Bied-Charreton
2026-02-04 16:13 ` [PATCH proxmox-perl-rs 1/1] notify: update bindings with new OAuth2 parameters Arthur Bied-Charreton
2026-02-04 16:13 ` [PATCH proxmox-widget-toolkit 1/2] utils: Add OAuth2 flow handlers Arthur Bied-Charreton
2026-02-04 16:13 ` [PATCH proxmox-widget-toolkit 2/2] notifications: Add opt-in OAuth2 support for SMTP targets Arthur Bied-Charreton
2026-02-04 16:13 ` [PATCH pve-manager 1/5] notifications: Add OAuth2 parameters to schema and add/update endpoints Arthur Bied-Charreton
2026-02-04 16:13 ` [PATCH pve-manager 2/5] notifications: Add refresh-targets endpoint Arthur Bied-Charreton
2026-02-04 16:13 ` [PATCH pve-manager 3/5] notifications: Trigger notification target refresh in pveupdate Arthur Bied-Charreton
2026-02-04 16:13 ` [PATCH pve-manager 4/5] notifications: Handle OAuth2 callback in login handler Arthur Bied-Charreton
2026-02-04 16:13 ` [PATCH pve-manager 5/5] notifications: Opt into OAuth2 authentication Arthur Bied-Charreton
2026-02-04 16:13 ` [PATCH pve-cluster 1/1] notifications: Add refresh_targets subroutine to PVE::Notify Arthur Bied-Charreton
2026-02-04 16:13 ` [PATCH pve-docs 1/1] notifications: Add section about OAuth2 to SMTP targets docs Arthur Bied-Charreton
Reply instructions:
You may reply publicly to this message via plain-text email
using any one of the following methods:
* Save the following mbox file, import it into your mail client,
and reply-to-all from there: mbox
Avoid top-posting and favor interleaved quoting:
https://en.wikipedia.org/wiki/Posting_style#Interleaved_style
* Reply using the --to, --cc, and --in-reply-to
switches of git-send-email(1):
git send-email \
--in-reply-to=20260204161354.458814-3-a.bied-charreton@proxmox.com \
--to=a.bied-charreton@proxmox.com \
--cc=pve-devel@lists.proxmox.com \
/path/to/YOUR_REPLY
https://kernel.org/pub/software/scm/git/docs/git-send-email.html
* If your mail client supports setting the In-Reply-To header
via mailto: links, try the mailto: link
Be sure your reply has a Subject: header at the top and a blank line
before the message body.
This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox