* [pdm-devel] [RFC datacenter-manager 0/3] PDM subscription status
@ 2025-12-01 11:39 Fabian Grünbichler
2025-12-01 11:39 ` [pdm-devel] [RFC datacenter-manager 1/3] subscription: add serverid field to node subscription info Fabian Grünbichler
` (4 more replies)
0 siblings, 5 replies; 12+ messages in thread
From: Fabian Grünbichler @ 2025-12-01 11:39 UTC (permalink / raw)
To: pdm-devel
this RFC series adds subscription endpoints and enterprise APT repo
handling for PDM itself.. there's considerable overlap with Dominik's
series[0], which is why I sent this now as RFC so we can discuss which
approach to take forward and merge the two series.
the patches here are largely untested, other than returning the right
error message when querying a system with not enough valid
subscriptions..
0: https://lore.proxmox.com/pdm-devel/20251201103917.1357369-1-d.csapak@proxmox.com
Fabian Grünbichler (3):
subscription: add serverid field to node subscription info
api types: add new SubscriptionStatistics
api: add subscription endpoints
lib/pdm-api-types/src/subscription.rs | 17 +++
server/src/api/nodes/mod.rs | 2 +
server/src/api/nodes/subscription.rs | 191 ++++++++++++++++++++++++++
server/src/api/resources.rs | 4 +-
4 files changed, 213 insertions(+), 1 deletion(-)
create mode 100644 server/src/api/nodes/subscription.rs
--
2.47.3
_______________________________________________
pdm-devel mailing list
pdm-devel@lists.proxmox.com
https://lists.proxmox.com/cgi-bin/mailman/listinfo/pdm-devel
^ permalink raw reply [flat|nested] 12+ messages in thread
* [pdm-devel] [RFC datacenter-manager 1/3] subscription: add serverid field to node subscription info
2025-12-01 11:39 [pdm-devel] [RFC datacenter-manager 0/3] PDM subscription status Fabian Grünbichler
@ 2025-12-01 11:39 ` Fabian Grünbichler
2025-12-01 11:39 ` [pdm-devel] [RFC datacenter-manager 2/3] api types: add new SubscriptionStatistics Fabian Grünbichler
` (3 subsequent siblings)
4 siblings, 0 replies; 12+ messages in thread
From: Fabian Grünbichler @ 2025-12-01 11:39 UTC (permalink / raw)
To: pdm-devel
needed to re-use it internally for enterprise repository access.
intentionally not serialized to avoid leaking it.
Signed-off-by: Fabian Grünbichler <f.gruenbichler@proxmox.com>
---
lib/pdm-api-types/src/subscription.rs | 4 ++++
server/src/api/resources.rs | 2 ++
2 files changed, 6 insertions(+)
diff --git a/lib/pdm-api-types/src/subscription.rs b/lib/pdm-api-types/src/subscription.rs
index 35910bc..64167cb 100644
--- a/lib/pdm-api-types/src/subscription.rs
+++ b/lib/pdm-api-types/src/subscription.rs
@@ -110,6 +110,10 @@ pub struct NodeSubscriptionInfo {
/// The subscription level of the node
pub level: SubscriptionLevel,
+
+ /// Serverid of the node, if accessible
+ #[serde(skip_serializing)]
+ pub serverid: Option<String>,
}
#[api(
diff --git a/server/src/api/resources.rs b/server/src/api/resources.rs
index 55056e1..4beaa54 100644
--- a/server/src/api/resources.rs
+++ b/server/src/api/resources.rs
@@ -891,6 +891,7 @@ async fn fetch_remote_subscription_info(
status,
sockets: info.sockets,
key: info.key,
+ serverid: info.serverid,
level: info
.level
.and_then(|level| level.parse().ok())
@@ -910,6 +911,7 @@ async fn fetch_remote_subscription_info(
sockets: None,
key: info.key,
level,
+ serverid: info.serverid,
}
});
--
2.47.3
_______________________________________________
pdm-devel mailing list
pdm-devel@lists.proxmox.com
https://lists.proxmox.com/cgi-bin/mailman/listinfo/pdm-devel
^ permalink raw reply [flat|nested] 12+ messages in thread
* [pdm-devel] [RFC datacenter-manager 2/3] api types: add new SubscriptionStatistics
2025-12-01 11:39 [pdm-devel] [RFC datacenter-manager 0/3] PDM subscription status Fabian Grünbichler
2025-12-01 11:39 ` [pdm-devel] [RFC datacenter-manager 1/3] subscription: add serverid field to node subscription info Fabian Grünbichler
@ 2025-12-01 11:39 ` Fabian Grünbichler
2025-12-01 12:36 ` Dominik Csapak
2025-12-01 11:39 ` [pdm-devel] [RFC datacenter-manager 3/3] api: add subscription endpoints Fabian Grünbichler
` (2 subsequent siblings)
4 siblings, 1 reply; 12+ messages in thread
From: Fabian Grünbichler @ 2025-12-01 11:39 UTC (permalink / raw)
To: pdm-devel
this is a different view for the global subscription status - instead of
looking at the lowest level per remote, count all nodes.
Signed-off-by: Fabian Grünbichler <f.gruenbichler@proxmox.com>
---
this clashes with dominik's patches which return these counts in the
remote subscription endpoints.. since we only need them for the global
PDM-level status decision I think this is cleaner, but this could of
course also be re-based on top of the other patches..
lib/pdm-api-types/src/subscription.rs | 13 +++++++++++++
1 file changed, 13 insertions(+)
diff --git a/lib/pdm-api-types/src/subscription.rs b/lib/pdm-api-types/src/subscription.rs
index 64167cb..d38611e 100644
--- a/lib/pdm-api-types/src/subscription.rs
+++ b/lib/pdm-api-types/src/subscription.rs
@@ -143,3 +143,16 @@ pub struct RemoteSubscriptions {
pub state: RemoteSubscriptionState,
}
+
+#[api]
+#[derive(Default, Serialize, Deserialize, Clone, PartialEq)]
+#[serde(rename_all = "kebab-case")]
+/// Lists the subscription level per node for the remote
+pub struct SubscriptionStatistics {
+ /// Total number of nodes across all remotes
+ pub total_nodes: usize,
+ /// Total number of active subscriptions across all remotes
+ pub active_subscriptions: usize,
+ /// Total number of community level subscriptions across all remotes
+ pub community: usize,
+}
--
2.47.3
_______________________________________________
pdm-devel mailing list
pdm-devel@lists.proxmox.com
https://lists.proxmox.com/cgi-bin/mailman/listinfo/pdm-devel
^ permalink raw reply [flat|nested] 12+ messages in thread
* [pdm-devel] [RFC datacenter-manager 3/3] api: add subscription endpoints
2025-12-01 11:39 [pdm-devel] [RFC datacenter-manager 0/3] PDM subscription status Fabian Grünbichler
2025-12-01 11:39 ` [pdm-devel] [RFC datacenter-manager 1/3] subscription: add serverid field to node subscription info Fabian Grünbichler
2025-12-01 11:39 ` [pdm-devel] [RFC datacenter-manager 2/3] api types: add new SubscriptionStatistics Fabian Grünbichler
@ 2025-12-01 11:39 ` Fabian Grünbichler
2025-12-01 12:42 ` Dominik Csapak
2025-12-01 12:38 ` [pdm-devel] [RFC datacenter-manager 0/3] PDM subscription status Dominik Csapak
2025-12-01 13:15 ` [pdm-devel] superseded: " Fabian Grünbichler
4 siblings, 1 reply; 12+ messages in thread
From: Fabian Grünbichler @ 2025-12-01 11:39 UTC (permalink / raw)
To: pdm-devel
for the PDM system itself, by proxy of how many of the remote nodes have valid
subscriptions above a certain level.
Signed-off-by: Fabian Grünbichler <f.gruenbichler@proxmox.com>
---
the thresholds here are rather arbitrary of course, as is the refresh
interval for cached subscription information..
server/src/api/nodes/mod.rs | 2 +
server/src/api/nodes/subscription.rs | 191 +++++++++++++++++++++++++++
server/src/api/resources.rs | 2 +-
3 files changed, 194 insertions(+), 1 deletion(-)
create mode 100644 server/src/api/nodes/subscription.rs
diff --git a/server/src/api/nodes/mod.rs b/server/src/api/nodes/mod.rs
index a0fe14a..3a795d6 100644
--- a/server/src/api/nodes/mod.rs
+++ b/server/src/api/nodes/mod.rs
@@ -11,6 +11,7 @@ pub mod journal;
pub mod network;
pub mod rrddata;
pub mod status;
+pub mod subscription;
pub mod syslog;
pub mod tasks;
pub mod termproxy;
@@ -45,6 +46,7 @@ pub const SUBDIRS: SubdirMap = &sorted!([
("journal", &journal::ROUTER),
("network", &network::ROUTER),
("rrdata", &rrddata::ROUTER),
+ ("subscription", &subscription::ROUTER),
("status", &status::ROUTER),
("syslog", &syslog::ROUTER),
("tasks", &tasks::ROUTER),
diff --git a/server/src/api/nodes/subscription.rs b/server/src/api/nodes/subscription.rs
new file mode 100644
index 0000000..343a2c8
--- /dev/null
+++ b/server/src/api/nodes/subscription.rs
@@ -0,0 +1,191 @@
+use std::collections::HashMap;
+
+use anyhow::{bail, Error};
+
+use proxmox_router::{Permission, Router};
+use proxmox_schema::api;
+use proxmox_schema::api_types::NODE_SCHEMA;
+use proxmox_subscription::files::update_apt_auth;
+use proxmox_subscription::{SubscriptionInfo, SubscriptionStatus};
+use proxmox_sys::fs::CreateOptions;
+
+use pdm_api_types::remotes::RemoteType;
+use pdm_api_types::subscription::{
+ NodeSubscriptionInfo, SubscriptionLevel, SubscriptionStatistics,
+};
+use pdm_api_types::PRIV_SYS_MODIFY;
+
+use crate::api::resources::get_subscription_info_for_remote;
+
+const PRODUCT_URL: &str = "https://www.proxmox.com/en/proxmox-datacenter-maanger/pricing";
+const APT_AUTH_FN: &str = "/etc/apt/auth.conf.d/pdm.conf";
+const APT_AUTH_URL: &str = "enterprise.proxmox.com/debian/pdm";
+
+// minimum ratio of nodes with active subscriptions
+const SUBSCRIPTION_THRESHOLD: f64 = 0.9;
+// max ratio of nodes with community subscriptions, among nodes with subscriptions
+const COMMUNITY_THRESHOLD: f64 = 0.4;
+
+fn apt_auth_file_opts() -> CreateOptions {
+ let mode = nix::sys::stat::Mode::from_bits_truncate(0o0600);
+ CreateOptions::new().perm(mode).owner(nix::unistd::ROOT)
+}
+
+async fn get_all_subscription_infos(
+) -> Result<HashMap<String, (RemoteType, HashMap<String, Option<NodeSubscriptionInfo>>)>, Error> {
+ let (remotes_config, _digest) = pdm_config::remotes::config()?;
+
+ let mut subscription_info = HashMap::new();
+ for (remote_name, remote) in remotes_config.iter() {
+ match get_subscription_info_for_remote(remote, 24 * 60 * 60).await {
+ Ok(info) => {
+ subscription_info.insert(remote_name.to_string(), (remote.ty, info));
+ }
+ Err(err) => {
+ log::debug!("Failed to get subscription info for remote {remote_name} - {err}");
+ subscription_info.insert(remote_name.to_string(), (remote.ty, HashMap::new()));
+ }
+ }
+ }
+ Ok(subscription_info)
+}
+
+fn count_subscriptions(
+ subscription_infos: &HashMap<
+ String,
+ (RemoteType, HashMap<String, Option<NodeSubscriptionInfo>>),
+ >,
+) -> SubscriptionStatistics {
+ let mut stats = SubscriptionStatistics::default();
+ for (_remote, (_remote_type, remote_infos)) in subscription_infos.iter() {
+ if remote_infos.is_empty() {
+ // count remotes without info as at least one node
+ stats.total_nodes += 1;
+ continue;
+ }
+ for (_node, node_info) in remote_infos.iter() {
+ stats.total_nodes += 1;
+ if let Some(info) = node_info {
+ if info.status == SubscriptionStatus::Active {
+ stats.active_subscriptions += 1;
+ if info.level == SubscriptionLevel::Community {
+ stats.community += 1;
+ }
+ }
+ }
+ }
+ }
+ stats
+}
+
+fn check_counts(stats: SubscriptionStatistics) -> Result<(), Error> {
+ let subscribed_ratio = stats.active_subscriptions as f64 / stats.total_nodes as f64;
+ let community_ratio = stats.community as f64 / stats.active_subscriptions as f64;
+
+ if subscribed_ratio > SUBSCRIPTION_THRESHOLD {
+ if community_ratio < COMMUNITY_THRESHOLD {
+ return Ok(());
+ } else {
+ bail!("Too many remote nodes with community level subscription!");
+ }
+ } else {
+ bail!("Too many remote nodes without active subscription!");
+ }
+}
+
+#[api(
+ access: { permission: &Permission::Anybody, },
+ input: {
+ properties: {
+ node: {
+ schema: NODE_SCHEMA,
+ },
+ },
+ },
+ returns: {
+ type: SubscriptionInfo,
+ }
+)]
+/// Return subscription status
+pub async fn get_subscription() -> Result<SubscriptionInfo, Error> {
+ let infos = get_all_subscription_infos().await?;
+
+ let stats = count_subscriptions(&infos);
+
+ if let Err(err) = check_counts(stats) {
+ Ok(SubscriptionInfo {
+ status: SubscriptionStatus::Invalid,
+ message: Some(format!("{err}")),
+ serverid: None,
+ url: Some(PRODUCT_URL.into()),
+ ..Default::default()
+ })
+ } else {
+ Ok(SubscriptionInfo {
+ status: SubscriptionStatus::Active,
+ url: Some(PRODUCT_URL.into()),
+ ..Default::default()
+ })
+ }
+}
+
+#[api(
+ input: {
+ properties: {
+ node: {
+ schema: NODE_SCHEMA,
+ },
+ },
+ },
+ protected: true,
+ access: {
+ permission: &Permission::Privilege(&["system"], PRIV_SYS_MODIFY, false),
+ },
+)]
+/// Update subscription information
+pub async fn check_subscription() -> Result<(), Error> {
+ let infos = get_all_subscription_infos().await?;
+ let stats = count_subscriptions(&infos);
+
+ if let Err(err) = check_counts(stats) {
+ update_apt_auth(APT_AUTH_FN, apt_auth_file_opts(), APT_AUTH_URL, None, None)?;
+ return Err(err);
+ }
+
+ let mut found = false;
+ 'outer: for (remote, (remote_type, remote_info)) in infos.iter() {
+ if *remote_type != RemoteType::Pve {
+ continue;
+ }
+ for (node, node_info) in remote_info.iter() {
+ if let Some(info) = node_info {
+ if info.status == SubscriptionStatus::Active
+ && info.key.is_some()
+ && info.serverid.is_some()
+ {
+ log::info!("Using subscription of node '{node}' of remote '{remote}' for enterprise repository access");
+ update_apt_auth(
+ APT_AUTH_FN,
+ apt_auth_file_opts(),
+ APT_AUTH_URL,
+ info.key.clone(),
+ info.serverid.clone(),
+ )?;
+ found = true;
+ break 'outer;
+ }
+ }
+ }
+ }
+
+ if !found {
+ log::warn!("No valid subscription found for configuring enterprise repository access..");
+ update_apt_auth(APT_AUTH_FN, apt_auth_file_opts(), APT_AUTH_URL, None, None)?;
+ }
+
+ Ok(())
+}
+
+pub const ROUTER: Router = Router::new()
+ .get(&API_METHOD_GET_SUBSCRIPTION)
+ .post(&API_METHOD_CHECK_SUBSCRIPTION);
diff --git a/server/src/api/resources.rs b/server/src/api/resources.rs
index 4beaa54..adab021 100644
--- a/server/src/api/resources.rs
+++ b/server/src/api/resources.rs
@@ -767,7 +767,7 @@ static SUBSCRIPTION_CACHE: LazyLock<RwLock<HashMap<String, CachedSubscriptionSta
///
/// If recent enough cached data is available, it is returned
/// instead of calling out to the remote.
-async fn get_subscription_info_for_remote(
+pub async fn get_subscription_info_for_remote(
remote: &Remote,
max_age: u64,
) -> Result<HashMap<String, Option<NodeSubscriptionInfo>>, Error> {
--
2.47.3
_______________________________________________
pdm-devel mailing list
pdm-devel@lists.proxmox.com
https://lists.proxmox.com/cgi-bin/mailman/listinfo/pdm-devel
^ permalink raw reply [flat|nested] 12+ messages in thread
* Re: [pdm-devel] [RFC datacenter-manager 2/3] api types: add new SubscriptionStatistics
2025-12-01 11:39 ` [pdm-devel] [RFC datacenter-manager 2/3] api types: add new SubscriptionStatistics Fabian Grünbichler
@ 2025-12-01 12:36 ` Dominik Csapak
2025-12-01 12:39 ` Thomas Lamprecht
2025-12-01 12:40 ` Fabian Grünbichler
0 siblings, 2 replies; 12+ messages in thread
From: Dominik Csapak @ 2025-12-01 12:36 UTC (permalink / raw)
To: Fabian Grünbichler, pdm-devel
AFAICS this type is only ever used internally and never returned from
the api, so IMHO there is no need to use the api macro here
(or derive (de)serialize)
On 12/1/25 12:38 PM, Fabian Grünbichler wrote:
> this is a different view for the global subscription status - instead of
> looking at the lowest level per remote, count all nodes.
>
> Signed-off-by: Fabian Grünbichler <f.gruenbichler@proxmox.com>
> ---
> this clashes with dominik's patches which return these counts in the
> remote subscription endpoints.. since we only need them for the global
> PDM-level status decision I think this is cleaner, but this could of
> course also be re-based on top of the other patches..
>
> lib/pdm-api-types/src/subscription.rs | 13 +++++++++++++
> 1 file changed, 13 insertions(+)
>
> diff --git a/lib/pdm-api-types/src/subscription.rs b/lib/pdm-api-types/src/subscription.rs
> index 64167cb..d38611e 100644
> --- a/lib/pdm-api-types/src/subscription.rs
> +++ b/lib/pdm-api-types/src/subscription.rs
> @@ -143,3 +143,16 @@ pub struct RemoteSubscriptions {
>
> pub state: RemoteSubscriptionState,
> }
> +
> +#[api]
> +#[derive(Default, Serialize, Deserialize, Clone, PartialEq)]
> +#[serde(rename_all = "kebab-case")]
> +/// Lists the subscription level per node for the remote
> +pub struct SubscriptionStatistics {
> + /// Total number of nodes across all remotes
> + pub total_nodes: usize,
> + /// Total number of active subscriptions across all remotes
> + pub active_subscriptions: usize,
> + /// Total number of community level subscriptions across all remotes
> + pub community: usize,
> +}
_______________________________________________
pdm-devel mailing list
pdm-devel@lists.proxmox.com
https://lists.proxmox.com/cgi-bin/mailman/listinfo/pdm-devel
^ permalink raw reply [flat|nested] 12+ messages in thread
* Re: [pdm-devel] [RFC datacenter-manager 0/3] PDM subscription status
2025-12-01 11:39 [pdm-devel] [RFC datacenter-manager 0/3] PDM subscription status Fabian Grünbichler
` (2 preceding siblings ...)
2025-12-01 11:39 ` [pdm-devel] [RFC datacenter-manager 3/3] api: add subscription endpoints Fabian Grünbichler
@ 2025-12-01 12:38 ` Dominik Csapak
2025-12-01 13:15 ` [pdm-devel] superseded: " Fabian Grünbichler
4 siblings, 0 replies; 12+ messages in thread
From: Dominik Csapak @ 2025-12-01 12:38 UTC (permalink / raw)
To: Fabian Grünbichler, pdm-devel
just to note:
if we use this approach, a large portion of my patches are not necessary
anymore, since we can then simply ask /nodes/localhost/subscription
for the global check, and we don't need to query the endpoint
that returns the remotes subscription info
(it's probably a better approach)
On 12/1/25 12:38 PM, Fabian Grünbichler wrote:
> this RFC series adds subscription endpoints and enterprise APT repo
> handling for PDM itself.. there's considerable overlap with Dominik's
> series[0], which is why I sent this now as RFC so we can discuss which
> approach to take forward and merge the two series.
>
> the patches here are largely untested, other than returning the right
> error message when querying a system with not enough valid
> subscriptions..
>
> 0: https://lore.proxmox.com/pdm-devel/20251201103917.1357369-1-d.csapak@proxmox.com
>
> Fabian Grünbichler (3):
> subscription: add serverid field to node subscription info
> api types: add new SubscriptionStatistics
> api: add subscription endpoints
>
> lib/pdm-api-types/src/subscription.rs | 17 +++
> server/src/api/nodes/mod.rs | 2 +
> server/src/api/nodes/subscription.rs | 191 ++++++++++++++++++++++++++
> server/src/api/resources.rs | 4 +-
> 4 files changed, 213 insertions(+), 1 deletion(-)
> create mode 100644 server/src/api/nodes/subscription.rs
>
_______________________________________________
pdm-devel mailing list
pdm-devel@lists.proxmox.com
https://lists.proxmox.com/cgi-bin/mailman/listinfo/pdm-devel
^ permalink raw reply [flat|nested] 12+ messages in thread
* Re: [pdm-devel] [RFC datacenter-manager 2/3] api types: add new SubscriptionStatistics
2025-12-01 12:36 ` Dominik Csapak
@ 2025-12-01 12:39 ` Thomas Lamprecht
2025-12-01 12:43 ` Dominik Csapak
2025-12-01 12:40 ` Fabian Grünbichler
1 sibling, 1 reply; 12+ messages in thread
From: Thomas Lamprecht @ 2025-12-01 12:39 UTC (permalink / raw)
To: Proxmox Datacenter Manager development discussion,
Dominik Csapak, Fabian Grünbichler
Am 01.12.25 um 13:36 schrieb Dominik Csapak:
> AFAICS this type is only ever used internally and never returned from the api, so IMHO there is no need to use the api macro here
> (or derive (de)serialize)
Does not hurt though? And might be something that we could use for the always
displayed status in the main workspaces top-bar?
_______________________________________________
pdm-devel mailing list
pdm-devel@lists.proxmox.com
https://lists.proxmox.com/cgi-bin/mailman/listinfo/pdm-devel
^ permalink raw reply [flat|nested] 12+ messages in thread
* Re: [pdm-devel] [RFC datacenter-manager 2/3] api types: add new SubscriptionStatistics
2025-12-01 12:36 ` Dominik Csapak
2025-12-01 12:39 ` Thomas Lamprecht
@ 2025-12-01 12:40 ` Fabian Grünbichler
1 sibling, 0 replies; 12+ messages in thread
From: Fabian Grünbichler @ 2025-12-01 12:40 UTC (permalink / raw)
To: Dominik Csapak, pdm-devel
On December 1, 2025 1:36 pm, Dominik Csapak wrote:
> AFAICS this type is only ever used internally and never returned from
> the api, so IMHO there is no need to use the api macro here
> (or derive (de)serialize)
yes, I originally had this as return type in patch 3, but then switched
to returning a SubscriptionInfo since I figured that's easier to handle
in line with existing code.. so unless we revert back to returning the
counts and delegating the decision how to display that to the client,
this can be an internal type.
>
> On 12/1/25 12:38 PM, Fabian Grünbichler wrote:
>> this is a different view for the global subscription status - instead of
>> looking at the lowest level per remote, count all nodes.
>>
>> Signed-off-by: Fabian Grünbichler <f.gruenbichler@proxmox.com>
>> ---
>> this clashes with dominik's patches which return these counts in the
>> remote subscription endpoints.. since we only need them for the global
>> PDM-level status decision I think this is cleaner, but this could of
>> course also be re-based on top of the other patches..
>>
>> lib/pdm-api-types/src/subscription.rs | 13 +++++++++++++
>> 1 file changed, 13 insertions(+)
>>
>> diff --git a/lib/pdm-api-types/src/subscription.rs b/lib/pdm-api-types/src/subscription.rs
>> index 64167cb..d38611e 100644
>> --- a/lib/pdm-api-types/src/subscription.rs
>> +++ b/lib/pdm-api-types/src/subscription.rs
>> @@ -143,3 +143,16 @@ pub struct RemoteSubscriptions {
>>
>> pub state: RemoteSubscriptionState,
>> }
>> +
>> +#[api]
>> +#[derive(Default, Serialize, Deserialize, Clone, PartialEq)]
>> +#[serde(rename_all = "kebab-case")]
>> +/// Lists the subscription level per node for the remote
>> +pub struct SubscriptionStatistics {
>> + /// Total number of nodes across all remotes
>> + pub total_nodes: usize,
>> + /// Total number of active subscriptions across all remotes
>> + pub active_subscriptions: usize,
>> + /// Total number of community level subscriptions across all remotes
>> + pub community: usize,
>> +}
>
>
_______________________________________________
pdm-devel mailing list
pdm-devel@lists.proxmox.com
https://lists.proxmox.com/cgi-bin/mailman/listinfo/pdm-devel
^ permalink raw reply [flat|nested] 12+ messages in thread
* Re: [pdm-devel] [RFC datacenter-manager 3/3] api: add subscription endpoints
2025-12-01 11:39 ` [pdm-devel] [RFC datacenter-manager 3/3] api: add subscription endpoints Fabian Grünbichler
@ 2025-12-01 12:42 ` Dominik Csapak
2025-12-01 12:52 ` Fabian Grünbichler
0 siblings, 1 reply; 12+ messages in thread
From: Dominik Csapak @ 2025-12-01 12:42 UTC (permalink / raw)
To: Fabian Grünbichler, pdm-devel
some comments inline
rest LGTM
On 12/1/25 12:38 PM, Fabian Grünbichler wrote:
> for the PDM system itself, by proxy of how many of the remote nodes have valid
> subscriptions above a certain level.
>
> Signed-off-by: Fabian Grünbichler <f.gruenbichler@proxmox.com>
> ---
> the thresholds here are rather arbitrary of course, as is the refresh
> interval for cached subscription information..
>
> server/src/api/nodes/mod.rs | 2 +
> server/src/api/nodes/subscription.rs | 191 +++++++++++++++++++++++++++
> server/src/api/resources.rs | 2 +-
> 3 files changed, 194 insertions(+), 1 deletion(-)
> create mode 100644 server/src/api/nodes/subscription.rs
>
> diff --git a/server/src/api/nodes/mod.rs b/server/src/api/nodes/mod.rs
> index a0fe14a..3a795d6 100644
> --- a/server/src/api/nodes/mod.rs
> +++ b/server/src/api/nodes/mod.rs
> @@ -11,6 +11,7 @@ pub mod journal;
> pub mod network;
> pub mod rrddata;
> pub mod status;
> +pub mod subscription;
> pub mod syslog;
> pub mod tasks;
> pub mod termproxy;
> @@ -45,6 +46,7 @@ pub const SUBDIRS: SubdirMap = &sorted!([
> ("journal", &journal::ROUTER),
> ("network", &network::ROUTER),
> ("rrdata", &rrddata::ROUTER),
> + ("subscription", &subscription::ROUTER),
> ("status", &status::ROUTER),
> ("syslog", &syslog::ROUTER),
> ("tasks", &tasks::ROUTER),
> diff --git a/server/src/api/nodes/subscription.rs b/server/src/api/nodes/subscription.rs
> new file mode 100644
> index 0000000..343a2c8
> --- /dev/null
> +++ b/server/src/api/nodes/subscription.rs
> @@ -0,0 +1,191 @@
> +use std::collections::HashMap;
> +
> +use anyhow::{bail, Error};
> +
> +use proxmox_router::{Permission, Router};
> +use proxmox_schema::api;
> +use proxmox_schema::api_types::NODE_SCHEMA;
> +use proxmox_subscription::files::update_apt_auth;
> +use proxmox_subscription::{SubscriptionInfo, SubscriptionStatus};
> +use proxmox_sys::fs::CreateOptions;
> +
> +use pdm_api_types::remotes::RemoteType;
> +use pdm_api_types::subscription::{
> + NodeSubscriptionInfo, SubscriptionLevel, SubscriptionStatistics,
> +};
> +use pdm_api_types::PRIV_SYS_MODIFY;
> +
> +use crate::api::resources::get_subscription_info_for_remote;
> +
> +const PRODUCT_URL: &str = "https://www.proxmox.com/en/proxmox-datacenter-maanger/pricing";
typo:
s/maanger/manager/
> +const APT_AUTH_FN: &str = "/etc/apt/auth.conf.d/pdm.conf";
> +const APT_AUTH_URL: &str = "enterprise.proxmox.com/debian/pdm";
> +
> +// minimum ratio of nodes with active subscriptions
> +const SUBSCRIPTION_THRESHOLD: f64 = 0.9;
> +// max ratio of nodes with community subscriptions, among nodes with subscriptions
> +const COMMUNITY_THRESHOLD: f64 = 0.4;
> +
> +fn apt_auth_file_opts() -> CreateOptions {
> + let mode = nix::sys::stat::Mode::from_bits_truncate(0o0600);
> + CreateOptions::new().perm(mode).owner(nix::unistd::ROOT)
> +}
> +
> +async fn get_all_subscription_infos(
> +) -> Result<HashMap<String, (RemoteType, HashMap<String, Option<NodeSubscriptionInfo>>)>, Error> {
> + let (remotes_config, _digest) = pdm_config::remotes::config()?;
> +
> + let mut subscription_info = HashMap::new();
> + for (remote_name, remote) in remotes_config.iter() {
> + match get_subscription_info_for_remote(remote, 24 * 60 * 60).await {
> + Ok(info) => {
> + subscription_info.insert(remote_name.to_string(), (remote.ty, info));
> + }
> + Err(err) => {
> + log::debug!("Failed to get subscription info for remote {remote_name} - {err}");
> + subscription_info.insert(remote_name.to_string(), (remote.ty, HashMap::new()));
> + }
> + }
> + }
> + Ok(subscription_info)
> +}
> +
> +fn count_subscriptions(
> + subscription_infos: &HashMap<
> + String,
> + (RemoteType, HashMap<String, Option<NodeSubscriptionInfo>>),
> + >,
> +) -> SubscriptionStatistics {
> + let mut stats = SubscriptionStatistics::default();
> + for (_remote, (_remote_type, remote_infos)) in subscription_infos.iter() {
> + if remote_infos.is_empty() {
> + // count remotes without info as at least one node
> + stats.total_nodes += 1;
> + continue;
this would count an unreachable remote as one not subscribed node, or?
> + }
> + for (_node, node_info) in remote_infos.iter() {
> + stats.total_nodes += 1;
> + if let Some(info) = node_info {
> + if info.status == SubscriptionStatus::Active {
> + stats.active_subscriptions += 1;
> + if info.level == SubscriptionLevel::Community {
> + stats.community += 1;
> + }
> + }
> + }
> + }
> + }
> + stats
> +}
> +
> +fn check_counts(stats: SubscriptionStatistics) -> Result<(), Error> {
> + let subscribed_ratio = stats.active_subscriptions as f64 / stats.total_nodes as f64;
> + let community_ratio = stats.community as f64 / stats.active_subscriptions as f64;
> +
> + if subscribed_ratio > SUBSCRIPTION_THRESHOLD {
> + if community_ratio < COMMUNITY_THRESHOLD {
> + return Ok(());
> + } else {
> + bail!("Too many remote nodes with community level subscription!");
> + }
> + } else {
> + bail!("Too many remote nodes without active subscription!");
> + }
> +}
> +
> +#[api(
> + access: { permission: &Permission::Anybody, },
> + input: {
> + properties: {
> + node: {
> + schema: NODE_SCHEMA,
> + },
> + },
> + },
> + returns: {
> + type: SubscriptionInfo,
> + }
> +)]
> +/// Return subscription status
> +pub async fn get_subscription() -> Result<SubscriptionInfo, Error> {
> + let infos = get_all_subscription_infos().await?;
> +
> + let stats = count_subscriptions(&infos);
> +
> + if let Err(err) = check_counts(stats) {
> + Ok(SubscriptionInfo {
> + status: SubscriptionStatus::Invalid,
> + message: Some(format!("{err}")),
> + serverid: None,
> + url: Some(PRODUCT_URL.into()),
> + ..Default::default()
> + })
> + } else {
> + Ok(SubscriptionInfo {
> + status: SubscriptionStatus::Active,
> + url: Some(PRODUCT_URL.into()),
> + ..Default::default()
> + })
> + }
> +}
> +
> +#[api(
> + input: {
> + properties: {
> + node: {
> + schema: NODE_SCHEMA,
> + },
> + },
> + },
> + protected: true,
> + access: {
> + permission: &Permission::Privilege(&["system"], PRIV_SYS_MODIFY, false),
> + },
> +)]
> +/// Update subscription information
> +pub async fn check_subscription() -> Result<(), Error> {
> + let infos = get_all_subscription_infos().await?;
> + let stats = count_subscriptions(&infos);
> +
> + if let Err(err) = check_counts(stats) {
> + update_apt_auth(APT_AUTH_FN, apt_auth_file_opts(), APT_AUTH_URL, None, None)?;
> + return Err(err);
> + }
> +
> + let mut found = false;
> + 'outer: for (remote, (remote_type, remote_info)) in infos.iter() {
> + if *remote_type != RemoteType::Pve {
> + continue;
> + }
> + for (node, node_info) in remote_info.iter() {
> + if let Some(info) = node_info {
> + if info.status == SubscriptionStatus::Active
> + && info.key.is_some()
> + && info.serverid.is_some()
> + {
> + log::info!("Using subscription of node '{node}' of remote '{remote}' for enterprise repository access");
> + update_apt_auth(
> + APT_AUTH_FN,
> + apt_auth_file_opts(),
> + APT_AUTH_URL,
> + info.key.clone(),
> + info.serverid.clone(),
> + )?;
> + found = true;
> + break 'outer;
> + }
> + }
> + }
> + }
> +
> + if !found {
> + log::warn!("No valid subscription found for configuring enterprise repository access..");
> + update_apt_auth(APT_AUTH_FN, apt_auth_file_opts(), APT_AUTH_URL, None, None)?;
> + }
> +
> + Ok(())
> +}
> +
> +pub const ROUTER: Router = Router::new()
> + .get(&API_METHOD_GET_SUBSCRIPTION)
> + .post(&API_METHOD_CHECK_SUBSCRIPTION);
> diff --git a/server/src/api/resources.rs b/server/src/api/resources.rs
> index 4beaa54..adab021 100644
> --- a/server/src/api/resources.rs
> +++ b/server/src/api/resources.rs
> @@ -767,7 +767,7 @@ static SUBSCRIPTION_CACHE: LazyLock<RwLock<HashMap<String, CachedSubscriptionSta
> ///
> /// If recent enough cached data is available, it is returned
> /// instead of calling out to the remote.
> -async fn get_subscription_info_for_remote(
> +pub async fn get_subscription_info_for_remote(
> remote: &Remote,
> max_age: u64,
> ) -> Result<HashMap<String, Option<NodeSubscriptionInfo>>, Error> {
_______________________________________________
pdm-devel mailing list
pdm-devel@lists.proxmox.com
https://lists.proxmox.com/cgi-bin/mailman/listinfo/pdm-devel
^ permalink raw reply [flat|nested] 12+ messages in thread
* Re: [pdm-devel] [RFC datacenter-manager 2/3] api types: add new SubscriptionStatistics
2025-12-01 12:39 ` Thomas Lamprecht
@ 2025-12-01 12:43 ` Dominik Csapak
0 siblings, 0 replies; 12+ messages in thread
From: Dominik Csapak @ 2025-12-01 12:43 UTC (permalink / raw)
To: Thomas Lamprecht,
Proxmox Datacenter Manager development discussion,
Fabian Grünbichler
On 12/1/25 1:39 PM, Thomas Lamprecht wrote:
> Am 01.12.25 um 13:36 schrieb Dominik Csapak:
>> AFAICS this type is only ever used internally and never returned from the api, so IMHO there is no need to use the api macro here
>> (or derive (de)serialize)
>
> Does not hurt though? And might be something that we could use for the always
> displayed status in the main workspaces top-bar?
>
no, ofc not, just wanted to point it out that it does
not do anything currently
_______________________________________________
pdm-devel mailing list
pdm-devel@lists.proxmox.com
https://lists.proxmox.com/cgi-bin/mailman/listinfo/pdm-devel
^ permalink raw reply [flat|nested] 12+ messages in thread
* Re: [pdm-devel] [RFC datacenter-manager 3/3] api: add subscription endpoints
2025-12-01 12:42 ` Dominik Csapak
@ 2025-12-01 12:52 ` Fabian Grünbichler
0 siblings, 0 replies; 12+ messages in thread
From: Fabian Grünbichler @ 2025-12-01 12:52 UTC (permalink / raw)
To: Dominik Csapak, pdm-devel
On December 1, 2025 1:42 pm, Dominik Csapak wrote:
> some comments inline
>
> rest LGTM
>
> On 12/1/25 12:38 PM, Fabian Grünbichler wrote:
>> for the PDM system itself, by proxy of how many of the remote nodes have valid
>> subscriptions above a certain level.
>>
>> Signed-off-by: Fabian Grünbichler <f.gruenbichler@proxmox.com>
>> ---
>> the thresholds here are rather arbitrary of course, as is the refresh
>> interval for cached subscription information..
>>
>> server/src/api/nodes/mod.rs | 2 +
>> server/src/api/nodes/subscription.rs | 191 +++++++++++++++++++++++++++
>> server/src/api/resources.rs | 2 +-
>> 3 files changed, 194 insertions(+), 1 deletion(-)
>> create mode 100644 server/src/api/nodes/subscription.rs
>>
>> diff --git a/server/src/api/nodes/mod.rs b/server/src/api/nodes/mod.rs
>> index a0fe14a..3a795d6 100644
>> --- a/server/src/api/nodes/mod.rs
>> +++ b/server/src/api/nodes/mod.rs
>> @@ -11,6 +11,7 @@ pub mod journal;
>> pub mod network;
>> pub mod rrddata;
>> pub mod status;
>> +pub mod subscription;
>> pub mod syslog;
>> pub mod tasks;
>> pub mod termproxy;
>> @@ -45,6 +46,7 @@ pub const SUBDIRS: SubdirMap = &sorted!([
>> ("journal", &journal::ROUTER),
>> ("network", &network::ROUTER),
>> ("rrdata", &rrddata::ROUTER),
>> + ("subscription", &subscription::ROUTER),
>> ("status", &status::ROUTER),
>> ("syslog", &syslog::ROUTER),
>> ("tasks", &tasks::ROUTER),
>> diff --git a/server/src/api/nodes/subscription.rs b/server/src/api/nodes/subscription.rs
>> new file mode 100644
>> index 0000000..343a2c8
>> --- /dev/null
>> +++ b/server/src/api/nodes/subscription.rs
>> @@ -0,0 +1,191 @@
>> +use std::collections::HashMap;
>> +
>> +use anyhow::{bail, Error};
>> +
>> +use proxmox_router::{Permission, Router};
>> +use proxmox_schema::api;
>> +use proxmox_schema::api_types::NODE_SCHEMA;
>> +use proxmox_subscription::files::update_apt_auth;
>> +use proxmox_subscription::{SubscriptionInfo, SubscriptionStatus};
>> +use proxmox_sys::fs::CreateOptions;
>> +
>> +use pdm_api_types::remotes::RemoteType;
>> +use pdm_api_types::subscription::{
>> + NodeSubscriptionInfo, SubscriptionLevel, SubscriptionStatistics,
>> +};
>> +use pdm_api_types::PRIV_SYS_MODIFY;
>> +
>> +use crate::api::resources::get_subscription_info_for_remote;
>> +
>> +const PRODUCT_URL: &str = "https://www.proxmox.com/en/proxmox-datacenter-maanger/pricing";
>
> typo:
>
> s/maanger/manager/
yes. not sure what the URL is supposed to be in any case? ;)
>
>> +const APT_AUTH_FN: &str = "/etc/apt/auth.conf.d/pdm.conf";
>> +const APT_AUTH_URL: &str = "enterprise.proxmox.com/debian/pdm";
>> +
>> +// minimum ratio of nodes with active subscriptions
>> +const SUBSCRIPTION_THRESHOLD: f64 = 0.9;
>> +// max ratio of nodes with community subscriptions, among nodes with subscriptions
>> +const COMMUNITY_THRESHOLD: f64 = 0.4;
>> +
>> +fn apt_auth_file_opts() -> CreateOptions {
>> + let mode = nix::sys::stat::Mode::from_bits_truncate(0o0600);
>> + CreateOptions::new().perm(mode).owner(nix::unistd::ROOT)
>> +}
>> +
>> +async fn get_all_subscription_infos(
>> +) -> Result<HashMap<String, (RemoteType, HashMap<String, Option<NodeSubscriptionInfo>>)>, Error> {
>> + let (remotes_config, _digest) = pdm_config::remotes::config()?;
>> +
>> + let mut subscription_info = HashMap::new();
>> + for (remote_name, remote) in remotes_config.iter() {
>> + match get_subscription_info_for_remote(remote, 24 * 60 * 60).await {
>> + Ok(info) => {
>> + subscription_info.insert(remote_name.to_string(), (remote.ty, info));
>> + }
>> + Err(err) => {
>> + log::debug!("Failed to get subscription info for remote {remote_name} - {err}");
>> + subscription_info.insert(remote_name.to_string(), (remote.ty, HashMap::new()));
>> + }
>> + }
>> + }
>> + Ok(subscription_info)
>> +}
>> +
>> +fn count_subscriptions(
>> + subscription_infos: &HashMap<
>> + String,
>> + (RemoteType, HashMap<String, Option<NodeSubscriptionInfo>>),
>> + >,
>> +) -> SubscriptionStatistics {
>> + let mut stats = SubscriptionStatistics::default();
>> + for (_remote, (_remote_type, remote_infos)) in subscription_infos.iter() {
>> + if remote_infos.is_empty() {
>> + // count remotes without info as at least one node
>> + stats.total_nodes += 1;
>> + continue;
>
> this would count an unreachable remote as one not subscribed node, or?
I set the cache expiry quite long for that reason, but yeah, counting
remotes that throw an error for subscription info requests is a bit
problematic now matter what you do:
- count them as without subscription (conservative, but if a lot are
unreachable might treat PDM as without subscription)
- ignore them (might allow easily "cheating")
- count them as with subscription (plain wrong ^^)
we could count them separately and add another threshold for the max.
number of remotes with "unknown" status?
>
>> + }
>> + for (_node, node_info) in remote_infos.iter() {
>> + stats.total_nodes += 1;
>> + if let Some(info) = node_info {
>> + if info.status == SubscriptionStatus::Active {
>> + stats.active_subscriptions += 1;
>> + if info.level == SubscriptionLevel::Community {
>> + stats.community += 1;
>> + }
>> + }
>> + }
>> + }
>> + }
>> + stats
>> +}
>> +
>> +fn check_counts(stats: SubscriptionStatistics) -> Result<(), Error> {
>> + let subscribed_ratio = stats.active_subscriptions as f64 / stats.total_nodes as f64;
>> + let community_ratio = stats.community as f64 / stats.active_subscriptions as f64;
>> +
>> + if subscribed_ratio > SUBSCRIPTION_THRESHOLD {
>> + if community_ratio < COMMUNITY_THRESHOLD {
>> + return Ok(());
>> + } else {
>> + bail!("Too many remote nodes with community level subscription!");
>> + }
>> + } else {
>> + bail!("Too many remote nodes without active subscription!");
>> + }
>> +}
>> +
>> +#[api(
>> + access: { permission: &Permission::Anybody, },
>> + input: {
>> + properties: {
>> + node: {
>> + schema: NODE_SCHEMA,
>> + },
>> + },
>> + },
>> + returns: {
>> + type: SubscriptionInfo,
>> + }
>> +)]
>> +/// Return subscription status
>> +pub async fn get_subscription() -> Result<SubscriptionInfo, Error> {
>> + let infos = get_all_subscription_infos().await?;
>> +
>> + let stats = count_subscriptions(&infos);
>> +
>> + if let Err(err) = check_counts(stats) {
>> + Ok(SubscriptionInfo {
>> + status: SubscriptionStatus::Invalid,
>> + message: Some(format!("{err}")),
>> + serverid: None,
>> + url: Some(PRODUCT_URL.into()),
>> + ..Default::default()
>> + })
>> + } else {
>> + Ok(SubscriptionInfo {
>> + status: SubscriptionStatus::Active,
>> + url: Some(PRODUCT_URL.into()),
>> + ..Default::default()
>> + })
>> + }
>> +}
>> +
>> +#[api(
>> + input: {
>> + properties: {
>> + node: {
>> + schema: NODE_SCHEMA,
>> + },
>> + },
>> + },
>> + protected: true,
>> + access: {
>> + permission: &Permission::Privilege(&["system"], PRIV_SYS_MODIFY, false),
>> + },
>> +)]
>> +/// Update subscription information
>> +pub async fn check_subscription() -> Result<(), Error> {
>> + let infos = get_all_subscription_infos().await?;
>> + let stats = count_subscriptions(&infos);
>> +
>> + if let Err(err) = check_counts(stats) {
>> + update_apt_auth(APT_AUTH_FN, apt_auth_file_opts(), APT_AUTH_URL, None, None)?;
>> + return Err(err);
>> + }
>> +
>> + let mut found = false;
>> + 'outer: for (remote, (remote_type, remote_info)) in infos.iter() {
>> + if *remote_type != RemoteType::Pve {
>> + continue;
>> + }
>> + for (node, node_info) in remote_info.iter() {
>> + if let Some(info) = node_info {
>> + if info.status == SubscriptionStatus::Active
>> + && info.key.is_some()
>> + && info.serverid.is_some()
>> + {
>> + log::info!("Using subscription of node '{node}' of remote '{remote}' for enterprise repository access");
>> + update_apt_auth(
>> + APT_AUTH_FN,
>> + apt_auth_file_opts(),
>> + APT_AUTH_URL,
>> + info.key.clone(),
>> + info.serverid.clone(),
>> + )?;
>> + found = true;
>> + break 'outer;
>> + }
>> + }
>> + }
>> + }
>> +
>> + if !found {
>> + log::warn!("No valid subscription found for configuring enterprise repository access..");
>> + update_apt_auth(APT_AUTH_FN, apt_auth_file_opts(), APT_AUTH_URL, None, None)?;
>> + }
>> +
>> + Ok(())
>> +}
>> +
>> +pub const ROUTER: Router = Router::new()
>> + .get(&API_METHOD_GET_SUBSCRIPTION)
>> + .post(&API_METHOD_CHECK_SUBSCRIPTION);
>> diff --git a/server/src/api/resources.rs b/server/src/api/resources.rs
>> index 4beaa54..adab021 100644
>> --- a/server/src/api/resources.rs
>> +++ b/server/src/api/resources.rs
>> @@ -767,7 +767,7 @@ static SUBSCRIPTION_CACHE: LazyLock<RwLock<HashMap<String, CachedSubscriptionSta
>> ///
>> /// If recent enough cached data is available, it is returned
>> /// instead of calling out to the remote.
>> -async fn get_subscription_info_for_remote(
>> +pub async fn get_subscription_info_for_remote(
>> remote: &Remote,
>> max_age: u64,
>> ) -> Result<HashMap<String, Option<NodeSubscriptionInfo>>, Error> {
>
>
_______________________________________________
pdm-devel mailing list
pdm-devel@lists.proxmox.com
https://lists.proxmox.com/cgi-bin/mailman/listinfo/pdm-devel
^ permalink raw reply [flat|nested] 12+ messages in thread
* [pdm-devel] superseded: [RFC datacenter-manager 0/3] PDM subscription status
2025-12-01 11:39 [pdm-devel] [RFC datacenter-manager 0/3] PDM subscription status Fabian Grünbichler
` (3 preceding siblings ...)
2025-12-01 12:38 ` [pdm-devel] [RFC datacenter-manager 0/3] PDM subscription status Dominik Csapak
@ 2025-12-01 13:15 ` Fabian Grünbichler
4 siblings, 0 replies; 12+ messages in thread
From: Fabian Grünbichler @ 2025-12-01 13:15 UTC (permalink / raw)
To: Proxmox Datacenter Manager development discussion
by https://lore.proxmox.com/pdm-devel/20251201131453.711514-1-f.gruenbichler@proxmox.com
On December 1, 2025 12:39 pm, Fabian Grünbichler wrote:
> this RFC series adds subscription endpoints and enterprise APT repo
> handling for PDM itself.. there's considerable overlap with Dominik's
> series[0], which is why I sent this now as RFC so we can discuss which
> approach to take forward and merge the two series.
>
> the patches here are largely untested, other than returning the right
> error message when querying a system with not enough valid
> subscriptions..
>
> 0: https://lore.proxmox.com/pdm-devel/20251201103917.1357369-1-d.csapak@proxmox.com
>
> Fabian Grünbichler (3):
> subscription: add serverid field to node subscription info
> api types: add new SubscriptionStatistics
> api: add subscription endpoints
>
> lib/pdm-api-types/src/subscription.rs | 17 +++
> server/src/api/nodes/mod.rs | 2 +
> server/src/api/nodes/subscription.rs | 191 ++++++++++++++++++++++++++
> server/src/api/resources.rs | 4 +-
> 4 files changed, 213 insertions(+), 1 deletion(-)
> create mode 100644 server/src/api/nodes/subscription.rs
>
> --
> 2.47.3
>
>
>
> _______________________________________________
> pdm-devel mailing list
> pdm-devel@lists.proxmox.com
> https://lists.proxmox.com/cgi-bin/mailman/listinfo/pdm-devel
>
_______________________________________________
pdm-devel mailing list
pdm-devel@lists.proxmox.com
https://lists.proxmox.com/cgi-bin/mailman/listinfo/pdm-devel
^ permalink raw reply [flat|nested] 12+ messages in thread
end of thread, other threads:[~2025-12-01 13:17 UTC | newest]
Thread overview: 12+ messages (download: mbox.gz / follow: Atom feed)
-- links below jump to the message on this page --
2025-12-01 11:39 [pdm-devel] [RFC datacenter-manager 0/3] PDM subscription status Fabian Grünbichler
2025-12-01 11:39 ` [pdm-devel] [RFC datacenter-manager 1/3] subscription: add serverid field to node subscription info Fabian Grünbichler
2025-12-01 11:39 ` [pdm-devel] [RFC datacenter-manager 2/3] api types: add new SubscriptionStatistics Fabian Grünbichler
2025-12-01 12:36 ` Dominik Csapak
2025-12-01 12:39 ` Thomas Lamprecht
2025-12-01 12:43 ` Dominik Csapak
2025-12-01 12:40 ` Fabian Grünbichler
2025-12-01 11:39 ` [pdm-devel] [RFC datacenter-manager 3/3] api: add subscription endpoints Fabian Grünbichler
2025-12-01 12:42 ` Dominik Csapak
2025-12-01 12:52 ` Fabian Grünbichler
2025-12-01 12:38 ` [pdm-devel] [RFC datacenter-manager 0/3] PDM subscription status Dominik Csapak
2025-12-01 13:15 ` [pdm-devel] superseded: " Fabian Grünbichler
This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox