From: Arthur Bied-Charreton <a.bied-charreton@proxmox.com>
To: Shannon Sterz <s.sterz@proxmox.com>
Cc: pdm-devel@lists.proxmox.com
Subject: Re: [PATCH datacenter-manager v2] Add notifications backend
Date: Thu, 9 Apr 2026 12:18:51 +0200 [thread overview]
Message-ID: <rdlu775bvlgn67zjqajellikbah3miifpslv544im3646fu3wo@j5exolomzeeh> (raw)
In-Reply-To: <DHOIHCWY4Q5O.2DKUHLI84R8R@proxmox.com>
On Thu, Apr 09, 2026 at 11:20:22AM +0200, Shannon Sterz wrote:
Hey, thanks for the feedback! As a general answer, here is my reasoning
as to why I did not make any improvemenets when porting the
notifications stack from PBS to PDM.
A possible goal Lukas and I discussed would be for proxmox-notify to
include the router, to prevent the ~1000 lines of code duplication from
spreading across 3 products.
This is currently not doable nicely because PVE defines its router on
the Perl side, meaning we would still need to have 2 different entry
points. Keeping this in mind though, I wanted to avoid the PDM router
implementation diverging from the PBS one too much in order to make an
eventual migration less painful.
I understand that this might be thinking too far into a future that will
possibly never happen, so I am of course happy to incorporate the
changes you proposed.
> On Thu Apr 9, 2026 at 6:57 AM CEST, Arthur Bied-Charreton wrote:
>
> nit: imo a commit message like "server: add notification backend" would
> be more appropriate. usually it is recommended to try add a tag prefix
> of what your code touches [1].
>
> [1]: https://pve.proxmox.com/wiki/Developer_Documentation#Commits_and_Commit_Messages
>
> > Ideally, the whole router should be in proxmox-notify. However, after
> > discussing this quite extensively off-list with Lukas, we came to the
> > conclusion that doing so would require a large refactor that is
> > especially tricky given the fact that PVE would still need a different
> > entry point into proxmox-notify than PBS and PDM, since it defines its
> > router on the Perl side.
>
> nit: usually mentions of offline discussions would be more appropriate
> in the notes of a patch or a cover letter, than the commit message.
Noted, thanks!
> generally, it may make sense to split this up a little and add a cover
> letter imo.
>
Yes, since this is currently 1:1 copy-pasted from PBS I figured it might
make sense to have it all in the same commit, but if we iterate on this,
I will of course split it up.
> > For this reason, factoring the routing logic out into proxmox-notify is
> > offset to a future where PVE also has a Rust-based router, and for now
> > it is just copied over from PBS (commit: fbf8d1b2) with the adaptations
> > described below.
> >
> > The `proxmox_notify::context::Context` trait implementation is added to
> > PDM directly instead of creating a pdm.rs module in proxmox-notify to
> > avoid adding even more product-specific logic to it. It uses
> > `proxmox-access-control`, which is pulled in by PDM anyway, for getting
> > the user config instead of reading the file directly.
> >
> > The `matcher-fields` and `matcher-field-values` endpoints currently
> > return empty vecs, they will be updated in a future commit introducing
> > actual notifications. The same applies for the notification templates.
>
> any reason to not go ahead and add at least one notification in this
> series too? being able to test that notifications function, but not
> having anything to be notified about seems a little odd to me. one easy
> notification you could add is the `send_certificate_renewal_mail` in
> `api::nodes::certificates::spawn_certificate_worker()`. that should
> essentially be identical to pbs anyway.
>
Yes that would have been better, I just wanted to get this out of the
way while I worked on adding notifications. Will add that in v2.
[...]
> > +use anyhow::Error;
> > +
> > +use proxmox_notify::Config;
> > +
> > +use pdm_buildcfg::configdir;
> > +use proxmox_product_config::{open_api_lockfile, replace_config, ApiLockGuard};
> > +use proxmox_sys::fs::file_read_optional_string;
> > +
> > +/// Configuration file location for notification targets/matchers.
> > +pub const NOTIFICATION_CONFIG_PATH: &str = configdir!("/notifications.cfg");
> > +
> > +/// Private configuration file location for secrets - only readable by `root`.
> > +pub const NOTIFICATION_PRIV_CONFIG_PATH: &str = configdir!("/notifications-priv.cfg");
> > +
> > +/// Lockfile to prevent concurrent write access.
> > +pub const NOTIFICATION_LOCK_FILE: &str = configdir!("/.notifications.lck");
> > +
>
> you might want to move these to their own "notifications" sub-folder or
> similar (maybe just "notify"?). in pdm we tend to put config files in
> topically fitting sub-folders (e.g. access, acme, auth)
>
ACK, notify sounds good to me
> > +/// Get exclusive lock for `notifications.cfg`.
> > +pub fn lock_config() -> Result<ApiLockGuard, Error> {
> > + open_api_lockfile(NOTIFICATION_LOCK_FILE, None, true)
> > +}
> > +
> > +/// Load notification config.
> > +pub fn config() -> Result<Config, Error> {
> > + let content = file_read_optional_string(NOTIFICATION_CONFIG_PATH)?.unwrap_or_default();
> > +
> > + let priv_content =
> > + file_read_optional_string(NOTIFICATION_PRIV_CONFIG_PATH)?.unwrap_or_default();
> > +
> > + Ok(Config::new(&content, &priv_content)?)
>
> you might want to consider returning a `ConfigDigest` here too.
> returning that encourages validating the digest as its less easily
> forgotten.
>
As written above, I made no improvements while porting this over from
PBS. I agree this would be better though. If we are okay diverging from
PBS I will be happy to add this to v2. (Note that I did not answer all
ConfigDigest-related comments on the handlers directly, please consider
those all acknowledged, if we go with this I will of course address all of
them).
> > +}
> > +
> > +/// Save notification config.
> > +pub fn save_config(config: Config) -> Result<(), Error> {
> > + let (cfg, priv_cfg) = config.write()?;
> > + replace_config(NOTIFICATION_CONFIG_PATH, cfg.as_bytes())?;
> > + replace_config(NOTIFICATION_PRIV_CONFIG_PATH, priv_cfg.as_bytes())?;
>
> the privileged config should probably be saved with higher permission
> than the general config. consider using `replace_secret_config` here.
>
Yes that's what I initially had, but the remotes.cfg (which also stores
sensitive data like the remote access tokens) is also stored with 0640
and www-data:www-data, so I figured it would make sense to do the same
here, what do you think?
[...]
> > +/// Get a gotify endpoint.
> > +pub fn get_endpoint(name: String, rpcenv: &mut dyn RpcEnvironment) -> Result<GotifyConfig, Error> {
> > + let config = pdm_config::notifications::config()?;
> > + let endpoint = proxmox_notify::api::gotify::get_endpoint(&config, &name)?;
> > +
> > + rpcenv["digest"] = hex::encode(config.digest()).into();
>
>
> if you return the digest as described above this could become:
>
> let (config, digest) = pdm_config::notifications::config()?;
> let endpoint = proxmox_notify::api::gotify::get_endpoint(&config, &name)?;
>
> rpcenv["digest"] = digest.to_hex().into();
>
ACK, see general answer on ConfigDigest above
[...]
> > +/// Add a new gotify endpoint.
> > +pub fn add_endpoint(
> > + endpoint: GotifyConfig,
> > + token: String,
> > + _rpcenv: &mut dyn RpcEnvironment,
> > +) -> Result<(), Error> {
> > + let _lock = pdm_config::notifications::lock_config()?;
> > + let mut config = pdm_config::notifications::config()?;
>
> this should probably verify the digest. if you return a config
> digest as described above this could become:
>
> let mut (config, expected_digest) = domains::config()?;
> expected_digest.detect_modification(digest.as_ref())?;
>
> assuming that you take the digest as an input parameter to this api call
> with:
>
> digest: {
> optional: true,
> type: ConfigDigest,
> }
>
> and:
>
> digest: Option<ConfigDigest>
>
ACK, see general answer on ConfigDigest above
[...]
> > +#[api(
> > + input: {
> > + properties: {}
> > + },
> > + returns: {
> > + description: "List of known metadata fields.",
> > + type: Array,
> > + items: {type: MatchableField},
> > + },
> > + access: {permission: &Permission::Privilege(&["system", "notifications"], PRIV_SYS_AUDIT, false)},
> > +)]
> > +/// Get all known metadata fields.
> > +pub fn get_fields() -> Result<Vec<MatchableField>, Error> {
> > + Ok(vec![])
>
> if you decide to add the acme notification here in a next version, you
> can add hostname here too imo.
>
ACK, will do
[...]
> > + };
> > +
> > + let path = Path::new(base)
> > + .join(namespace.unwrap_or("default"))
> > + .join(filename);
> > +
> > + error!("{path:?}");
>
> is this intentional? i don't think unconditionally emitting an error
> event here makes sense.
>
No, I used that for debugging and forgot to remove it, sorry about
that...
> > +
> > + proxmox_sys::fs::file_read_optional_string(path)
> > + .map_err(|err| Error::Generic(format!("could not load template: {err}")))
> > + }
> > +}
> > +
> > +/// Initialize `proxmox-notify` by registering the PDM context.
> > +pub fn init() {
> > + proxmox_notify::context::set_context(&PDM_CONTEXT);
> > +}
> > diff --git a/templates/Makefile b/templates/Makefile
> > new file mode 100644
> > index 0000000..294fa7b
> > --- /dev/null
> > +++ b/templates/Makefile
> > @@ -0,0 +1,14 @@
> > +include ../defines.mk
> > +
> > +NOTIFICATION_TEMPLATES= \
> > + default/test-body.txt.hbs \
> > + default/test-subject.txt.hbs \
> > +
> > +all:
> > +
> > +clean:
> > +
> > +install:
> > + install -dm755 $(DESTDIR)$(DATAROOTDIR)/proxmox-datacenter-manager/templates/default
> > + $(foreach i,$(NOTIFICATION_TEMPLATES), \
> > + install -m644 $(i) $(DESTDIR)$(DATAROOTDIR)/proxmox-datacenter-manager/templates/$(i) ;)
> > diff --git a/templates/default/test-body.txt.hbs b/templates/default/test-body.txt.hbs
> > new file mode 100644
> > index 0000000..2445443
> > --- /dev/null
> > +++ b/templates/default/test-body.txt.hbs
> > @@ -0,0 +1 @@
> > +This is a test of the notification target '{{target}}'.
> > diff --git a/templates/default/test-subject.txt.hbs b/templates/default/test-subject.txt.hbs
> > new file mode 100644
> > index 0000000..cb8e132
> > --- /dev/null
> > +++ b/templates/default/test-subject.txt.hbs
> > @@ -0,0 +1 @@
> > +Test notification
>
next prev parent reply other threads:[~2026-04-09 10:18 UTC|newest]
Thread overview: 8+ messages / expand[flat|nested] mbox.gz Atom feed top
2026-04-09 4:57 Arthur Bied-Charreton
2026-04-09 9:20 ` Shannon Sterz
2026-04-09 10:18 ` Arthur Bied-Charreton [this message]
2026-04-09 11:13 ` Shannon Sterz
2026-04-09 12:07 ` Lukas Wagner
2026-04-09 12:39 ` Shannon Sterz
2026-04-09 18:26 ` Thomas Lamprecht
2026-04-10 12:00 ` 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=rdlu775bvlgn67zjqajellikbah3miifpslv544im3646fu3wo@j5exolomzeeh \
--to=a.bied-charreton@proxmox.com \
--cc=pdm-devel@lists.proxmox.com \
--cc=s.sterz@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