public inbox for pdm-devel@lists.proxmox.com
 help / color / mirror / Atom feed
From: Lukas Wagner <l.wagner@proxmox.com>
To: pdm-devel@lists.proxmox.com
Subject: [PATCH proxmox v3 04/21] product-config: add ProductConfig type
Date: Thu, 27 Aug 2026 13:42:27 +0200	[thread overview]
Message-ID: <20260827114244.424784-5-l.wagner@proxmox.com> (raw)
In-Reply-To: <20260827114244.424784-1-l.wagner@proxmox.com>

This type bundles user information for priv/unpriv users and common
runtime paths. This type should be instantiated once and then live in
the application code base, either in a OnceLock or an application
context handle.

Some of the methods are based on the existing helpers from
filesystem_helpers.rs, with some additional ones to account for
permission differences between directories and files (execute bit is set
for directories).

Signed-off-by: Lukas Wagner <l.wagner@proxmox.com>
---
 proxmox-product-config/src/lib.rs            |   3 +
 proxmox-product-config/src/product_config.rs | 205 +++++++++++++++++++
 2 files changed, 208 insertions(+)
 create mode 100644 proxmox-product-config/src/product_config.rs

diff --git a/proxmox-product-config/src/lib.rs b/proxmox-product-config/src/lib.rs
index 9576a08d..48a6ba56 100644
--- a/proxmox-product-config/src/lib.rs
+++ b/proxmox-product-config/src/lib.rs
@@ -5,3 +5,6 @@ pub use filesystem_helpers::*;
 
 mod init;
 pub use init::*;
+
+mod product_config;
+pub use product_config::{ProductConfig, ProductConfigParams};
diff --git a/proxmox-product-config/src/product_config.rs b/proxmox-product-config/src/product_config.rs
new file mode 100644
index 00000000..d38764ec
--- /dev/null
+++ b/proxmox-product-config/src/product_config.rs
@@ -0,0 +1,205 @@
+use std::path::{Path, PathBuf};
+
+use nix::sys::stat::Mode;
+use nix::unistd::User;
+
+use proxmox_sys::fs::CreateOptions;
+
+/// Parameter type for [`ProductConfig::new`].
+///
+/// This type is used to avoid having long lists of parameters with the same type, while also
+/// keeping the members of the final type, [`ProductConfig`] private.
+#[derive(Clone, Debug)]
+pub struct ProductConfigParams {
+    /// The user the unprivileged API daemon runs as.
+    pub api_user: User,
+
+    /// The user the privileged API daemon runs as.
+    pub priv_user: User,
+
+    /// Directory for configuration files.
+    ///
+    /// This is typically in `/etc/<product>`.
+    pub config_dir: PathBuf,
+
+    /// Directory for persistent state.
+    ///
+    /// This is typically in `/var/lib/<product>`.
+    pub state_dir: PathBuf,
+
+    /// Directory for runtime data, not persisted across reboots.
+    ///
+    /// This is typically in `/run/<product>`.
+    pub run_dir: PathBuf,
+
+    /// Directory for cached data, which can be regenerated if lost.
+    ///
+    /// This is typically in `/var/cache/<product>`.
+    pub cache_dir: PathBuf,
+}
+
+/// Product-specific configuration, such as the users the product runs as and the directories it
+/// stores its files in.
+///
+/// # Examples
+///
+/// ```no_run
+/// use nix::unistd::User;
+///
+/// use proxmox_product_config::{ProductConfig, ProductConfigParams};
+///
+/// # fn main() -> Result<(), anyhow::Error> {
+/// let config = ProductConfig::new(ProductConfigParams {
+///     api_user: User::from_name("www-data")?.expect("www-data user exists"),
+///     priv_user: User::from_name("root")?.expect("root user exists"),
+///     config_dir: "/etc/proxmox-product".into(),
+///     state_dir: "/var/lib/proxmox-product".into(),
+///     run_dir: "/run/proxmox-product".into(),
+///     cache_dir: "/var/cache/proxmox-product".into(),
+/// });
+///
+/// // Write a config file as `www-data:www-data` with mode 0640.
+/// let config_file = config.config_dir().join("product.conf");
+/// let options = config.default_file_create_options();
+/// proxmox_sys::fs::replace_file(&config_file, b"key: value\n", options, true)?;
+///
+/// // Files holding secrets are only accessible to the privileged daemon: `root:root`, mode 0600.
+/// let key_file = config.config_dir().join("auth.key");
+/// let options = config.secret_file_create_options();
+/// proxmox_sys::fs::replace_file(&key_file, b"secret\n", options, true)?;
+/// # Ok(())
+/// # }
+/// ```
+#[derive(Clone, Debug)]
+pub struct ProductConfig(ProductConfigParams);
+
+impl ProductConfig {
+    /// Create a new [`ProductConfig`] from the provided
+    /// [`ProductConfigParams`].
+    pub fn new(params: ProductConfigParams) -> Self {
+        Self(params)
+    }
+
+    /// The user the unprivileged API daemon runs as.
+    pub fn api_user(&self) -> &User {
+        &self.0.api_user
+    }
+
+    /// The user the privileged API daemon runs as.
+    pub fn priv_user(&self) -> &User {
+        &self.0.priv_user
+    }
+
+    /// Directory for configuration files.
+    ///
+    /// This is typically in `/etc/<product>`.
+    pub fn config_dir(&self) -> &Path {
+        &self.0.config_dir
+    }
+
+    /// Directory for persistent state.
+    ///
+    /// This is typically in `/var/lib/<product>`.
+    pub fn state_dir(&self) -> &Path {
+        &self.0.state_dir
+    }
+
+    /// Directory for runtime data, not persisted across reboots.
+    ///
+    /// This is typically in `/run/<product>`.
+    pub fn run_dir(&self) -> &Path {
+        &self.0.run_dir
+    }
+
+    /// Directory for cached data, which can be regenerated if lost.
+    ///
+    /// This is typically in `/var/cache/<product>`.
+    pub fn cache_dir(&self) -> &Path {
+        &self.0.cache_dir
+    }
+
+    /// Default options for creating files: mode 0640, owned by the API user.
+    pub fn default_file_create_options(&self) -> CreateOptions {
+        let api_user = self.api_user();
+        let mode = Mode::from_bits_truncate(0o0640);
+
+        CreateOptions::new()
+            .perm(mode)
+            .owner(api_user.uid)
+            .group(api_user.gid)
+    }
+
+    /// Default options for creating directories: mode 0750, owned by the API user.
+    pub fn default_dir_create_options(&self) -> CreateOptions {
+        let api_user = self.api_user();
+        let mode = Mode::from_bits_truncate(0o0750);
+
+        CreateOptions::new()
+            .perm(mode)
+            .owner(api_user.uid)
+            .group(api_user.gid)
+    }
+
+    /// Return [CreateOptions] for files owned by `priv_user.uid:api_user.gid` with permission `0640`.
+    ///
+    /// Only `priv_user` can write those files, but group `api_user.gid` can read them.
+    pub fn privileged_file_create_options(&self) -> CreateOptions {
+        let api_user = self.api_user();
+        let priv_user = self.priv_user();
+        let mode = Mode::from_bits_truncate(0o0640);
+
+        CreateOptions::new()
+            .perm(mode)
+            .owner(priv_user.uid)
+            .group(api_user.gid)
+    }
+
+    /// Return [CreateOptions] for files owned by `priv_user.uid:api_user.gid` with permission `0750`.
+    ///
+    /// Only `priv_user` can write those files, but group `api_user.gid` can read them.
+    pub fn privileged_dir_create_options(&self) -> CreateOptions {
+        let api_user = self.api_user();
+        let priv_user = self.priv_user();
+        let mode = Mode::from_bits_truncate(0o0750);
+
+        CreateOptions::new()
+            .perm(mode)
+            .owner(priv_user.uid)
+            .group(api_user.gid)
+    }
+
+    /// Return [CreateOptions] for files owned by `priv_user.uid:priv_user.gid` with permission `0600`.
+    ///
+    /// Only `priv_user` can read and write those files.
+    pub fn secret_file_create_options(&self) -> CreateOptions {
+        let priv_user = self.priv_user();
+        let mode = Mode::from_bits_truncate(0o0600);
+
+        CreateOptions::new()
+            .perm(mode)
+            .owner(priv_user.uid)
+            .group(priv_user.gid)
+    }
+
+    /// Return [CreateOptions] for directories owned by `priv_user.uid:priv_user.gid` with permission `0600`.
+    ///
+    /// Only `priv_user` can read and write those files.
+    pub fn secret_dir_create_options(&self) -> CreateOptions {
+        let priv_user = self.priv_user();
+        let mode = Mode::from_bits_truncate(0o0700);
+
+        CreateOptions::new()
+            .perm(mode)
+            .owner(priv_user.uid)
+            .group(priv_user.gid)
+    }
+
+    /// Return [CreateOptions] for lock files, owner `api_user.uid/api_user.gid` and mode `0660`.
+    pub fn lockfile_create_options(&self) -> CreateOptions {
+        let api_user = self.api_user();
+        CreateOptions::new()
+            .perm(Mode::from_bits_truncate(0o660))
+            .owner(api_user.uid)
+            .group(api_user.gid)
+    }
+}
-- 
2.47.3





  parent reply	other threads:[~2026-08-27 11:42 UTC|newest]

Thread overview: 22+ messages / expand[flat|nested]  mbox.gz  Atom feed  top
2026-08-27 11:42 [PATCH datacenter-manager/proxmox v3 00/21] inject application context via API macro for easier integration testing Lukas Wagner
2026-08-27 11:42 ` [PATCH proxmox v3 01/21] router: introduce shared state Lukas Wagner
2026-08-27 11:42 ` [PATCH proxmox v3 02/21] rest-server: allow to inject " Lukas Wagner
2026-08-27 11:42 ` [PATCH proxmox v3 03/21] api-macro: support shared state extraction type Lukas Wagner
2026-08-27 11:42 ` Lukas Wagner [this message]
2026-08-27 11:42 ` [PATCH datacenter-manager v3 05/21] context: promote context to a dir-style module Lukas Wagner
2026-08-27 11:42 ` [PATCH datacenter-manager v3 06/21] pdm-config: remotes: rename trait methods to read/write/lock Lukas Wagner
2026-08-27 11:42 ` [PATCH datacenter-manager v3 07/21] pdm-config: subscriptions: " Lukas Wagner
2026-08-27 11:42 ` [PATCH datacenter-manager v3 08/21] remote iterator: pass remote config reader explicitly Lukas Wagner
2026-08-27 11:42 ` [PATCH datacenter-manager v3 09/21] context: introduce a ContextFactory to build application context Lukas Wagner
2026-08-27 11:42 ` [PATCH datacenter-manager v3 10/21] context: establish PdmApplication object Lukas Wagner
2026-08-27 11:42 ` [PATCH datacenter-manager v3 11/21] context: register PdmApplication in router Lukas Wagner
2026-08-27 11:42 ` [PATCH datacenter-manager v3 12/21] connection: use client factory from PdmApplication handle Lukas Wagner
2026-08-27 11:42 ` [PATCH datacenter-manager v3 13/21] parallel fetcher: pass arguments to closure in a single type Lukas Wagner
2026-08-27 11:42 ` [PATCH datacenter-manager v3 14/21] server: migrate existing ParallelFetcher users to use PdmApplication Lukas Wagner
2026-08-27 11:42 ` [PATCH datacenter-manager v3 15/21] tests: add helpers for building API-handler-level integration tests Lukas Wagner
2026-08-27 11:42 ` [PATCH datacenter-manager v3 16/21] tests: add example tests for SDN API routes Lukas Wagner
2026-08-27 11:42 ` [PATCH datacenter-manager v3 17/21] api-cache: add wrapper type Lukas Wagner
2026-08-27 11:42 ` [PATCH datacenter-manager v3 18/21] context: provide api-cache on the app object Lukas Wagner
2026-08-27 11:42 ` [PATCH datacenter-manager v3 19/21] api: subscriptions: use PdmApplication instead of globals Lukas Wagner
2026-08-27 11:42 ` [PATCH datacenter-manager v3 20/21] pdm-config: subscriptions: drop unused accessor functions Lukas Wagner
2026-08-27 11:42 ` [PATCH datacenter-manager v3 21/21] tests: add example tests for remote subscription management Lukas Wagner

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=20260827114244.424784-5-l.wagner@proxmox.com \
    --to=l.wagner@proxmox.com \
    --cc=pdm-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
Service provided by Proxmox Server Solutions GmbH | Privacy | Legal