[pbs-devel] [RFC proxmox-backup 1/1] use proxmox_jobstate and proxmox_cert_management crates

Dominik Csapak d.csapak at proxmox.com
Wed Oct 18 12:39:11 CEST 2023


and remove the old code here.

For this to work now, we have to use the ServerConfig setup, so that the
directories/users are accessible globally.

Signed-off-by: Dominik Csapak <d.csapak at proxmox.com>
---
 Cargo.toml                        |   8 +
 pbs-api-types/Cargo.toml          |   1 +
 pbs-api-types/src/jobs.rs         |  41 +---
 src/auth_helpers.rs               | 184 +---------------
 src/bin/proxmox-backup-api.rs     |   8 +-
 src/bin/proxmox-backup-manager.rs |   2 +
 src/bin/proxmox-backup-proxy.rs   |   2 +
 src/server/jobstate.rs            | 347 +-----------------------------
 src/server/mod.rs                 |  21 ++
 9 files changed, 52 insertions(+), 562 deletions(-)

diff --git a/Cargo.toml b/Cargo.toml
index cfbf2ba1..de0c8138 100644
--- a/Cargo.toml
+++ b/Cargo.toml
@@ -58,11 +58,13 @@ proxmox-apt = "0.10.5"
 proxmox-async = "0.4"
 proxmox-auth-api = "0.3"
 proxmox-borrow = "1"
+proxmox-cert-management = "0.1"
 proxmox-compression = "0.2"
 proxmox-fuse = "0.1.3"
 proxmox-http = { version = "0.9.0", features = [ "client", "http-helpers", "websocket" ] } # see below
 proxmox-human-byte = "0.1"
 proxmox-io = "1.0.1" # tools and client use "tokio" feature
+proxmox-jobstate = "0.1"
 proxmox-lang = "1.1"
 proxmox-ldap = "0.2.1"
 proxmox-metrics = "0.3"
@@ -74,6 +76,7 @@ proxmox-router = { version = "2.0.0", default_features = false }
 proxmox-schema = "2.0.0"
 proxmox-section-config = "2"
 proxmox-serde = "0.1.1"
+proxmox-server-config = "0.1"
 proxmox-shared-memory = "0.3.0"
 proxmox-sortable-macro = "0.1.2"
 proxmox-subscription = { version = "0.4", features = [ "api-types" ] }
@@ -202,10 +205,12 @@ zstd.workspace = true
 proxmox-apt.workspace = true
 proxmox-async.workspace = true
 proxmox-auth-api = { workspace = true, features = [ "api", "pam-authenticator" ] }
+proxmox-cert-management.workspace = true
 proxmox-compression.workspace = true
 proxmox-http = { workspace = true, features = [ "client-trait", "proxmox-async", "rate-limited-stream" ] } # pbs-client doesn't use these
 proxmox-human-byte.workspace = true
 proxmox-io.workspace = true
+proxmox-jobstate.workspace = true
 proxmox-lang.workspace = true
 proxmox-ldap.workspace = true
 proxmox-metrics.workspace = true
@@ -215,6 +220,7 @@ proxmox-router = { workspace = true, features = [ "cli", "server"] }
 proxmox-schema = { workspace = true, features = [ "api-macro" ] }
 proxmox-section-config.workspace = true
 proxmox-serde = { workspace = true, features = [ "serde_json" ] }
+proxmox-server-config.workspace = true
 proxmox-shared-memory.workspace = true
 proxmox-sortable-macro.workspace = true
 proxmox-subscription.workspace = true
@@ -252,6 +258,7 @@ proxmox-rrd.workspace = true
 #proxmox-http = { path = "../proxmox/proxmox-http" }
 #proxmox-human-byte = { path = "../proxmox/proxmox-human-byte" }
 #proxmox-io = { path = "../proxmox/proxmox-io" }
+#proxmox-jobstate = { path = "../proxmox/proxmox-jobstate" }
 #proxmox-lang = { path = "../proxmox/proxmox-lang" }
 #proxmox-ldap = { path = "../proxmox/proxmox-ldap" }
 #proxmox-metrics = { path = "../proxmox/proxmox-metrics" }
@@ -261,6 +268,7 @@ proxmox-rrd.workspace = true
 #proxmox-schema = { path = "../proxmox/proxmox-schema" }
 #proxmox-section-config = { path = "../proxmox/proxmox-section-config" }
 #proxmox-serde = { path = "../proxmox/proxmox-serde" }
+#proxmox-server-config = { path = "../proxmox/proxmox-server-config" }
 #proxmox-shared-memory = { path = "../proxmox/proxmox-shared-memory" }
 #proxmox-sortable-macro = { path = "../proxmox/proxmox-sortable-macro" }
 #proxmox-subscription = { path = "../proxmox/proxmox-subscription" }
diff --git a/pbs-api-types/Cargo.toml b/pbs-api-types/Cargo.toml
index 31b69f62..13f7d76b 100644
--- a/pbs-api-types/Cargo.toml
+++ b/pbs-api-types/Cargo.toml
@@ -16,6 +16,7 @@ serde_plain.workspace = true
 
 proxmox-auth-api = { workspace = true, features = [ "api-types" ] }
 proxmox-human-byte.workspace = true
+proxmox-jobstate.workspace=true
 proxmox-lang.workspace=true
 proxmox-schema = { workspace = true, features = [ "api-macro" ] }
 proxmox-serde.workspace = true
diff --git a/pbs-api-types/src/jobs.rs b/pbs-api-types/src/jobs.rs
index 23e19b7b..a932c29d 100644
--- a/pbs-api-types/src/jobs.rs
+++ b/pbs-api-types/src/jobs.rs
@@ -13,6 +13,9 @@ use crate::{
     SINGLE_LINE_COMMENT_SCHEMA,
 };
 
+// re-exported for compatibility
+pub use proxmox_jobstate::JobScheduleStatus;
+
 const_regex! {
 
     /// Regex for verification jobs 'DATASTORE:ACTUAL_JOB_ID'
@@ -63,44 +66,6 @@ pub const REMOVE_VANISHED_BACKUPS_SCHEMA: Schema = BooleanSchema::new(
 .default(false)
 .schema();
 
-#[api(
-    properties: {
-        "next-run": {
-            description: "Estimated time of the next run (UNIX epoch).",
-            optional: true,
-            type: Integer,
-        },
-        "last-run-state": {
-            description: "Result of the last run.",
-            optional: true,
-            type: String,
-        },
-        "last-run-upid": {
-            description: "Task UPID of the last run.",
-            optional: true,
-            type: String,
-        },
-        "last-run-endtime": {
-            description: "Endtime of the last run.",
-            optional: true,
-            type: Integer,
-        },
-    }
-)]
-#[derive(Serialize, Deserialize, Default, Clone, PartialEq)]
-#[serde(rename_all = "kebab-case")]
-/// Job Scheduling Status
-pub struct JobScheduleStatus {
-    #[serde(skip_serializing_if = "Option::is_none")]
-    pub next_run: Option<i64>,
-    #[serde(skip_serializing_if = "Option::is_none")]
-    pub last_run_state: Option<String>,
-    #[serde(skip_serializing_if = "Option::is_none")]
-    pub last_run_upid: Option<String>,
-    #[serde(skip_serializing_if = "Option::is_none")]
-    pub last_run_endtime: Option<i64>,
-}
-
 #[api()]
 #[derive(Debug, Copy, Clone, PartialEq, Eq, Serialize, Deserialize)]
 #[serde(rename_all = "lowercase")]
diff --git a/src/auth_helpers.rs b/src/auth_helpers.rs
index c2eaaef1..41c3c569 100644
--- a/src/auth_helpers.rs
+++ b/src/auth_helpers.rs
@@ -1,189 +1,17 @@
-use std::path::PathBuf;
-
-use anyhow::{bail, format_err, Error};
-use lazy_static::lazy_static;
-use openssl::pkey::{PKey, Private, Public};
-use openssl::rsa::Rsa;
-use openssl::sha;
+use anyhow::Error;
 
 use pbs_config::BackupLockGuard;
-use proxmox_lang::try_block;
-use proxmox_sys::fs::{file_get_contents, replace_file, CreateOptions};
 
-use pbs_api_types::Userid;
 use pbs_buildcfg::configdir;
 use serde_json::json;
 
 pub use crate::auth::setup_auth_context;
 
-fn compute_csrf_secret_digest(timestamp: i64, secret: &[u8], userid: &Userid) -> String {
-    let mut hasher = sha::Sha256::new();
-    let data = format!("{:08X}:{}:", timestamp, userid);
-    hasher.update(data.as_bytes());
-    hasher.update(secret);
-
-    base64::encode_config(hasher.finish(), base64::STANDARD_NO_PAD)
-}
-
-pub fn assemble_csrf_prevention_token(secret: &[u8], userid: &Userid) -> String {
-    let epoch = proxmox_time::epoch_i64();
-
-    let digest = compute_csrf_secret_digest(epoch, secret, userid);
-
-    format!("{:08X}:{}", epoch, digest)
-}
-
-pub fn verify_csrf_prevention_token(
-    secret: &[u8],
-    userid: &Userid,
-    token: &str,
-    min_age: i64,
-    max_age: i64,
-) -> Result<i64, Error> {
-    use std::collections::VecDeque;
-
-    let mut parts: VecDeque<&str> = token.split(':').collect();
-
-    try_block!({
-        if parts.len() != 2 {
-            bail!("format error - wrong number of parts.");
-        }
-
-        let timestamp = parts.pop_front().unwrap();
-        let sig = parts.pop_front().unwrap();
-
-        let ttime = i64::from_str_radix(timestamp, 16)
-            .map_err(|err| format_err!("timestamp format error - {}", err))?;
-
-        let digest = compute_csrf_secret_digest(ttime, secret, userid);
-
-        if digest != sig {
-            bail!("invalid signature.");
-        }
-
-        let now = proxmox_time::epoch_i64();
-
-        let age = now - ttime;
-        if age < min_age {
-            bail!("timestamp newer than expected.");
-        }
-
-        if age > max_age {
-            bail!("timestamp too old.");
-        }
-
-        Ok(age)
-    })
-    .map_err(|err| format_err!("invalid csrf token - {}", err))
-}
-
-pub fn generate_csrf_key() -> Result<(), Error> {
-    let path = PathBuf::from(configdir!("/csrf.key"));
-
-    if path.exists() {
-        return Ok(());
-    }
-
-    let rsa = Rsa::generate(2048).unwrap();
-
-    let pem = rsa.private_key_to_pem()?;
-
-    use nix::sys::stat::Mode;
-
-    let backup_user = pbs_config::backup_user()?;
-
-    replace_file(
-        &path,
-        &pem,
-        CreateOptions::new()
-            .perm(Mode::from_bits_truncate(0o0640))
-            .owner(nix::unistd::ROOT)
-            .group(backup_user.gid),
-        true,
-    )?;
-
-    Ok(())
-}
-
-pub fn generate_auth_key() -> Result<(), Error> {
-    let priv_path = PathBuf::from(configdir!("/authkey.key"));
-
-    let mut public_path = priv_path.clone();
-    public_path.set_extension("pub");
-
-    if priv_path.exists() && public_path.exists() {
-        return Ok(());
-    }
-
-    let rsa = Rsa::generate(4096).unwrap();
-
-    let priv_pem = rsa.private_key_to_pem()?;
-
-    use nix::sys::stat::Mode;
-
-    replace_file(
-        &priv_path,
-        &priv_pem,
-        CreateOptions::new().perm(Mode::from_bits_truncate(0o0600)),
-        true,
-    )?;
-
-    let public_pem = rsa.public_key_to_pem()?;
-
-    let backup_user = pbs_config::backup_user()?;
-
-    replace_file(
-        &public_path,
-        &public_pem,
-        CreateOptions::new()
-            .perm(Mode::from_bits_truncate(0o0640))
-            .owner(nix::unistd::ROOT)
-            .group(backup_user.gid),
-        true,
-    )?;
-
-    Ok(())
-}
-
-pub fn csrf_secret() -> &'static [u8] {
-    lazy_static! {
-        static ref SECRET: Vec<u8> = file_get_contents(configdir!("/csrf.key")).unwrap();
-    }
-
-    &SECRET
-}
-
-fn load_public_auth_key() -> Result<PKey<Public>, Error> {
-    let pem = file_get_contents(configdir!("/authkey.pub"))?;
-    let rsa = Rsa::public_key_from_pem(&pem)?;
-    let key = PKey::from_rsa(rsa)?;
-
-    Ok(key)
-}
-
-pub fn public_auth_key() -> &'static PKey<Public> {
-    lazy_static! {
-        static ref KEY: PKey<Public> = load_public_auth_key().unwrap();
-    }
-
-    &KEY
-}
-
-fn load_private_auth_key() -> Result<PKey<Private>, Error> {
-    let pem = file_get_contents(configdir!("/authkey.key"))?;
-    let rsa = Rsa::private_key_from_pem(&pem)?;
-    let key = PKey::from_rsa(rsa)?;
-
-    Ok(key)
-}
-
-pub fn private_auth_key() -> &'static PKey<Private> {
-    lazy_static! {
-        static ref KEY: PKey<Private> = load_private_auth_key().unwrap();
-    }
-
-    &KEY
-}
+// re-exported
+pub use proxmox_cert_management::{
+    assemble_csrf_prevention_token, csrf_secret, generate_auth_key, generate_csrf_key,
+    private_auth_key, public_auth_key,
+};
 
 const LDAP_PASSWORDS_FILENAME: &str = configdir!("/ldap_passwords.json");
 
diff --git a/src/bin/proxmox-backup-api.rs b/src/bin/proxmox-backup-api.rs
index c6c24449..4d0837eb 100644
--- a/src/bin/proxmox-backup-api.rs
+++ b/src/bin/proxmox-backup-api.rs
@@ -6,6 +6,7 @@ use futures::*;
 use http::Response;
 use hyper::{Body, StatusCode};
 
+use proxmox_backup::server::setup_server_config;
 use proxmox_lang::try_block;
 use proxmox_router::RpcEnvironmentType;
 use proxmox_sys::fs::CreateOptions;
@@ -48,13 +49,12 @@ async fn run() -> Result<(), Error> {
         bail!("unable to inititialize syslog - {}", err);
     }
 
-    config::create_configdir()?;
+    setup_server_config()?;
+
+    config::check_configdir_permissions()?;
 
     config::update_self_signed_cert(false)?;
 
-    proxmox_backup::server::create_run_dir()?;
-    proxmox_backup::server::create_state_dir()?;
-    proxmox_backup::server::create_active_operations_dir()?;
     proxmox_backup::server::jobstate::create_jobstate_dir()?;
     proxmox_backup::tape::create_tape_status_dir()?;
     proxmox_backup::tape::create_drive_state_dir()?;
diff --git a/src/bin/proxmox-backup-manager.rs b/src/bin/proxmox-backup-manager.rs
index b4cb6cb3..a5d777bb 100644
--- a/src/bin/proxmox-backup-manager.rs
+++ b/src/bin/proxmox-backup-manager.rs
@@ -507,6 +507,8 @@ async fn run() -> Result<(), Error> {
 fn main() -> Result<(), Error> {
     proxmox_backup::tools::setup_safe_path_env();
 
+    proxmox_backup::server::setup_server_config()?;
+
     proxmox_async::runtime::main(run())
 }
 
diff --git a/src/bin/proxmox-backup-proxy.rs b/src/bin/proxmox-backup-proxy.rs
index f38a02bd..7eafdbc4 100644
--- a/src/bin/proxmox-backup-proxy.rs
+++ b/src/bin/proxmox-backup-proxy.rs
@@ -197,6 +197,8 @@ async fn run() -> Result<(), Error> {
         bail!("unable to inititialize syslog - {err}");
     }
 
+    server::setup_server_config()?;
+
     proxmox_backup::auth_helpers::setup_auth_context(false);
 
     let rrd_cache = initialize_rrd_cache()?;
diff --git a/src/server/jobstate.rs b/src/server/jobstate.rs
index be9dac42..e4a74c65 100644
--- a/src/server/jobstate.rs
+++ b/src/server/jobstate.rs
@@ -1,343 +1,6 @@
-//! Generic JobState handling
-//!
-//! A 'Job' can have 3 states
-//!  - Created, when a schedule was created but never executed
-//!  - Started, when a job is running right now
-//!  - Finished, when a job was running in the past
-//!
-//! and is identified by 2 values: jobtype and jobname (e.g. 'syncjob' and 'myfirstsyncjob')
-//!
-//! This module Provides 2 helper structs to handle those coniditons
-//! 'Job' which handles locking and writing to a file
-//! 'JobState' which is the actual state
-//!
-//! an example usage would be
-//! ```no_run
-//! # use anyhow::{bail, Error};
-//! # use proxmox_rest_server::TaskState;
-//! # use proxmox_backup::server::jobstate::*;
-//! # fn some_code() -> TaskState { TaskState::OK { endtime: 0 } }
-//! # fn code() -> Result<(), Error> {
-//! // locks the correct file under /var/lib
-//! // or fails if someone else holds the lock
-//! let mut job = match Job::new("jobtype", "jobname") {
-//!     Ok(job) => job,
-//!     Err(err) => bail!("could not lock jobstate"),
-//! };
-//!
-//! // job holds the lock, we can start it
-//! job.start("someupid")?;
-//! // do something
-//! let task_state = some_code();
-//! job.finish(task_state)?;
-//!
-//! // release the lock
-//! drop(job);
-//! # Ok(())
-//! # }
-//!
-//! ```
-use std::path::{Path, PathBuf};
+//! Generic JobState handling. Deprecated and reexported from [proxmox_jobstate](proxmox_jobstate)
 
-use anyhow::{bail, format_err, Error};
-use serde::{Deserialize, Serialize};
-
-use proxmox_sys::fs::{create_path, file_read_optional_string, replace_file, CreateOptions};
-
-use proxmox_time::CalendarEvent;
-
-use pbs_api_types::{JobScheduleStatus, UPID};
-use pbs_buildcfg::PROXMOX_BACKUP_STATE_DIR_M;
-use pbs_config::{open_backup_lockfile, BackupLockGuard};
-
-use proxmox_rest_server::{upid_read_status, worker_is_active_local, TaskState};
-
-#[derive(Serialize, Deserialize)]
-#[serde(rename_all = "kebab-case")]
-/// Represents the State of a specific Job
-pub enum JobState {
-    /// A job was created at 'time', but never started/finished
-    Created { time: i64 },
-    /// The Job was last started in 'upid',
-    Started { upid: String },
-    /// The Job was last started in 'upid', which finished with 'state', and was last updated at 'updated'
-    Finished {
-        upid: String,
-        state: TaskState,
-        updated: Option<i64>,
-    },
-}
-
-/// Represents a Job and holds the correct lock
-pub struct Job {
-    jobtype: String,
-    jobname: String,
-    /// The State of the job
-    pub state: JobState,
-    _lock: BackupLockGuard,
-}
-
-const JOB_STATE_BASEDIR: &str = concat!(PROXMOX_BACKUP_STATE_DIR_M!(), "/jobstates");
-
-/// Create jobstate stat dir with correct permission
-pub fn create_jobstate_dir() -> Result<(), Error> {
-    let backup_user = pbs_config::backup_user()?;
-
-    let opts = CreateOptions::new()
-        .owner(backup_user.uid)
-        .group(backup_user.gid);
-
-    create_path(JOB_STATE_BASEDIR, Some(opts.clone()), Some(opts))
-        .map_err(|err: Error| format_err!("unable to create job state dir - {err}"))?;
-
-    Ok(())
-}
-
-fn get_path(jobtype: &str, jobname: &str) -> PathBuf {
-    let mut path = PathBuf::from(JOB_STATE_BASEDIR);
-    path.push(format!("{jobtype}-{jobname}.json"));
-    path
-}
-
-fn get_lock<P>(path: P) -> Result<BackupLockGuard, Error>
-where
-    P: AsRef<Path>,
-{
-    let mut path = path.as_ref().to_path_buf();
-    path.set_extension("lck");
-    open_backup_lockfile(&path, None, true)
-}
-
-/// Removes the statefile of a job, this is useful if we delete a job
-pub fn remove_state_file(jobtype: &str, jobname: &str) -> Result<(), Error> {
-    let mut path = get_path(jobtype, jobname);
-    let _lock = get_lock(&path)?;
-    if let Err(err) = std::fs::remove_file(&path) {
-        if err.kind() != std::io::ErrorKind::NotFound {
-            bail!("cannot remove statefile for {jobtype} - {jobname}: {err}");
-        }
-    }
-    path.set_extension("lck");
-    if let Err(err) = std::fs::remove_file(&path) {
-        if err.kind() != std::io::ErrorKind::NotFound {
-            bail!("cannot remove lockfile for {jobtype} - {jobname}: {err}");
-        }
-    }
-    Ok(())
-}
-
-/// Creates the statefile with the state 'Created'
-/// overwrites if it exists already
-pub fn create_state_file(jobtype: &str, jobname: &str) -> Result<(), Error> {
-    let mut job = Job::new(jobtype, jobname)?;
-    job.write_state()
-}
-
-/// Tries to update the state file with the current time
-/// if the job is currently running, does nothing.
-/// Intended for use when the schedule changes.
-pub fn update_job_last_run_time(jobtype: &str, jobname: &str) -> Result<(), Error> {
-    let mut job = match Job::new(jobtype, jobname) {
-        Ok(job) => job,
-        Err(_) => return Ok(()), // was locked (running), so do not update
-    };
-    let time = proxmox_time::epoch_i64();
-
-    job.state = match JobState::load(jobtype, jobname)? {
-        JobState::Created { .. } => JobState::Created { time },
-        JobState::Started { .. } => return Ok(()), // currently running (without lock?)
-        JobState::Finished {
-            upid,
-            state,
-            updated: _,
-        } => JobState::Finished {
-            upid,
-            state,
-            updated: Some(time),
-        },
-    };
-    job.write_state()
-}
-
-/// Returns the last run time of a job by reading the statefile
-/// Note that this is not locked
-pub fn last_run_time(jobtype: &str, jobname: &str) -> Result<i64, Error> {
-    match JobState::load(jobtype, jobname)? {
-        JobState::Created { time } => Ok(time),
-        JobState::Finished {
-            updated: Some(time),
-            ..
-        } => Ok(time),
-        JobState::Started { upid }
-        | JobState::Finished {
-            upid,
-            state: _,
-            updated: None,
-        } => {
-            let upid: UPID = upid
-                .parse()
-                .map_err(|err| format_err!("could not parse upid from state: {err}"))?;
-            Ok(upid.starttime)
-        }
-    }
-}
-
-impl JobState {
-    /// Loads and deserializes the jobstate from type and name.
-    /// When the loaded state indicates a started UPID,
-    /// we go and check if it has already stopped, and
-    /// returning the correct state.
-    ///
-    /// This does not update the state in the file.
-    pub fn load(jobtype: &str, jobname: &str) -> Result<Self, Error> {
-        if let Some(state) = file_read_optional_string(get_path(jobtype, jobname))? {
-            match serde_json::from_str(&state)? {
-                JobState::Started { upid } => {
-                    let parsed: UPID = upid
-                        .parse()
-                        .map_err(|err| format_err!("error parsing upid: {err}"))?;
-
-                    if !worker_is_active_local(&parsed) {
-                        let state = upid_read_status(&parsed).unwrap_or(TaskState::Unknown {
-                            endtime: parsed.starttime,
-                        });
-
-                        Ok(JobState::Finished {
-                            upid,
-                            state,
-                            updated: None,
-                        })
-                    } else {
-                        Ok(JobState::Started { upid })
-                    }
-                }
-                other => Ok(other),
-            }
-        } else {
-            Ok(JobState::Created {
-                time: proxmox_time::epoch_i64() - 30,
-            })
-        }
-    }
-}
-
-impl Job {
-    /// Creates a new instance of a job with the correct lock held
-    /// (will be hold until the job is dropped again).
-    ///
-    /// This does not load the state from the file, to do that,
-    /// 'load' must be called
-    pub fn new(jobtype: &str, jobname: &str) -> Result<Self, Error> {
-        let path = get_path(jobtype, jobname);
-
-        let _lock = get_lock(path)?;
-
-        Ok(Self {
-            jobtype: jobtype.to_string(),
-            jobname: jobname.to_string(),
-            state: JobState::Created {
-                time: proxmox_time::epoch_i64(),
-            },
-            _lock,
-        })
-    }
-
-    /// Start the job and update the statefile accordingly
-    /// Fails if the job was already started
-    pub fn start(&mut self, upid: &str) -> Result<(), Error> {
-        if let JobState::Started { .. } = self.state {
-            bail!("cannot start job that is started!");
-        }
-
-        self.state = JobState::Started {
-            upid: upid.to_string(),
-        };
-
-        self.write_state()
-    }
-
-    /// Finish the job and update the statefile accordingly with the given taskstate
-    /// Fails if the job was not yet started
-    pub fn finish(&mut self, state: TaskState) -> Result<(), Error> {
-        let upid = match &self.state {
-            JobState::Created { .. } => bail!("cannot finish when not started"),
-            JobState::Started { upid } => upid,
-            JobState::Finished { upid, .. } => upid,
-        }
-        .to_string();
-
-        self.state = JobState::Finished {
-            upid,
-            state,
-            updated: None,
-        };
-
-        self.write_state()
-    }
-
-    pub fn jobtype(&self) -> &str {
-        &self.jobtype
-    }
-
-    pub fn jobname(&self) -> &str {
-        &self.jobname
-    }
-
-    fn write_state(&mut self) -> Result<(), Error> {
-        let serialized = serde_json::to_string(&self.state)?;
-        let path = get_path(&self.jobtype, &self.jobname);
-
-        let backup_user = pbs_config::backup_user()?;
-        let mode = nix::sys::stat::Mode::from_bits_truncate(0o0644);
-        // set the correct owner/group/permissions while saving file
-        // owner(rw) = backup, group(r)= backup
-        let options = CreateOptions::new()
-            .perm(mode)
-            .owner(backup_user.uid)
-            .group(backup_user.gid);
-
-        replace_file(path, serialized.as_bytes(), options, false)
-    }
-}
-
-pub fn compute_schedule_status(
-    job_state: &JobState,
-    schedule: Option<&str>,
-) -> Result<JobScheduleStatus, Error> {
-    let (upid, endtime, state, last) = match job_state {
-        JobState::Created { time } => (None, None, None, *time),
-        JobState::Started { upid } => {
-            let parsed_upid: UPID = upid.parse()?;
-            (Some(upid), None, None, parsed_upid.starttime)
-        }
-        JobState::Finished {
-            upid,
-            state,
-            updated,
-        } => {
-            let last = updated.unwrap_or_else(|| state.endtime());
-            (
-                Some(upid),
-                Some(state.endtime()),
-                Some(state.to_string()),
-                last,
-            )
-        }
-    };
-
-    let mut status = JobScheduleStatus {
-        last_run_upid: upid.map(String::from),
-        last_run_state: state,
-        last_run_endtime: endtime,
-        ..Default::default()
-    };
-
-    if let Some(schedule) = schedule {
-        if let Ok(event) = schedule.parse::<CalendarEvent>() {
-            // ignore errors
-            status.next_run = event.compute_next_event(last).unwrap_or(None);
-        }
-    }
-
-    Ok(status)
-}
+pub use proxmox_jobstate::{
+    compute_schedule_status, create_jobstate_dir, create_state_file, last_run_time,
+    remove_state_file, update_job_last_run_time, Job, JobState,
+};
diff --git a/src/server/mod.rs b/src/server/mod.rs
index 4e3b68ac..06b1d92e 100644
--- a/src/server/mod.rs
+++ b/src/server/mod.rs
@@ -5,6 +5,7 @@
 //! tokio/hyper.
 
 use anyhow::{format_err, Error};
+use proxmox_server_config::ServerConfig;
 use serde_json::Value;
 
 use proxmox_sys::fs::{create_path, CreateOptions};
@@ -92,3 +93,23 @@ pub fn create_active_operations_dir() -> Result<(), Error> {
         .map_err(|err: Error| format_err!("unable to create active operations dir - {err}"))?;
     Ok(())
 }
+
+/// Setup basic directories and users configs
+pub fn setup_server_config() -> Result<(), Error> {
+    let root = nix::unistd::User::from_uid(nix::unistd::ROOT)
+        .unwrap()
+        .unwrap();
+
+    ServerConfig::new(
+        "proxmox-backup-api",
+        "/var/lib/proxmox-backup",
+        pbs_config::backup_user()?,
+    )?
+    .with_privileged_user(root)?
+    .with_log_dir(pbs_buildcfg::PROXMOX_BACKUP_LOG_DIR_M!())?
+    .with_state_dir(pbs_buildcfg::PROXMOX_BACKUP_STATE_DIR_M!())?
+    .with_run_dir(pbs_buildcfg::PROXMOX_BACKUP_RUN_DIR_M!())?
+    .with_config_dir(pbs_buildcfg::CONFIGDIR)?
+    .with_cert_dir(pbs_buildcfg::CONFIGDIR)?
+    .setup()
+}
-- 
2.30.2






More information about the pbs-devel mailing list