]> git.proxmox.com Git - proxmox-backup.git/blob - src/config/sync.rs
clippy: remove unnecessary clones
[proxmox-backup.git] / src / config / sync.rs
1 use anyhow::{Error};
2 use lazy_static::lazy_static;
3 use std::collections::HashMap;
4 use serde::{Serialize, Deserialize};
5
6 use proxmox::api::{
7 api,
8 schema::*,
9 section_config::{
10 SectionConfig,
11 SectionConfigData,
12 SectionConfigPlugin,
13 }
14 };
15
16 use proxmox::tools::{fs::replace_file, fs::CreateOptions};
17
18 use crate::api2::types::*;
19
20 lazy_static! {
21 static ref CONFIG: SectionConfig = init();
22 }
23
24 #[api(
25 properties: {
26 id: {
27 schema: JOB_ID_SCHEMA,
28 },
29 store: {
30 schema: DATASTORE_SCHEMA,
31 },
32 "owner": {
33 type: Authid,
34 optional: true,
35 },
36 remote: {
37 schema: REMOTE_ID_SCHEMA,
38 },
39 "remote-store": {
40 schema: DATASTORE_SCHEMA,
41 },
42 "remove-vanished": {
43 schema: REMOVE_VANISHED_BACKUPS_SCHEMA,
44 optional: true,
45 },
46 comment: {
47 optional: true,
48 schema: SINGLE_LINE_COMMENT_SCHEMA,
49 },
50 schedule: {
51 optional: true,
52 schema: SYNC_SCHEDULE_SCHEMA,
53 },
54 }
55 )]
56 #[serde(rename_all="kebab-case")]
57 #[derive(Serialize,Deserialize,Clone)]
58 /// Sync Job
59 pub struct SyncJobConfig {
60 pub id: String,
61 pub store: String,
62 #[serde(skip_serializing_if="Option::is_none")]
63 pub owner: Option<Authid>,
64 pub remote: String,
65 pub remote_store: String,
66 #[serde(skip_serializing_if="Option::is_none")]
67 pub remove_vanished: Option<bool>,
68 #[serde(skip_serializing_if="Option::is_none")]
69 pub comment: Option<String>,
70 #[serde(skip_serializing_if="Option::is_none")]
71 pub schedule: Option<String>,
72 }
73
74 impl From<&SyncJobStatus> for SyncJobConfig {
75 fn from(job_status: &SyncJobStatus) -> Self {
76 Self {
77 id: job_status.id.clone(),
78 store: job_status.store.clone(),
79 owner: job_status.owner.clone(),
80 remote: job_status.remote.clone(),
81 remote_store: job_status.remote_store.clone(),
82 remove_vanished: job_status.remove_vanished,
83 comment: job_status.comment.clone(),
84 schedule: job_status.schedule.clone(),
85 }
86 }
87 }
88
89 // FIXME: generate duplicate schemas/structs from one listing?
90 #[api(
91 properties: {
92 id: {
93 schema: JOB_ID_SCHEMA,
94 },
95 store: {
96 schema: DATASTORE_SCHEMA,
97 },
98 owner: {
99 type: Authid,
100 optional: true,
101 },
102 remote: {
103 schema: REMOTE_ID_SCHEMA,
104 },
105 "remote-store": {
106 schema: DATASTORE_SCHEMA,
107 },
108 "remove-vanished": {
109 schema: REMOVE_VANISHED_BACKUPS_SCHEMA,
110 optional: true,
111 },
112 comment: {
113 optional: true,
114 schema: SINGLE_LINE_COMMENT_SCHEMA,
115 },
116 schedule: {
117 optional: true,
118 schema: SYNC_SCHEDULE_SCHEMA,
119 },
120 "next-run": {
121 description: "Estimated time of the next run (UNIX epoch).",
122 optional: true,
123 type: Integer,
124 },
125 "last-run-state": {
126 description: "Result of the last run.",
127 optional: true,
128 type: String,
129 },
130 "last-run-upid": {
131 description: "Task UPID of the last run.",
132 optional: true,
133 type: String,
134 },
135 "last-run-endtime": {
136 description: "Endtime of the last run.",
137 optional: true,
138 type: Integer,
139 },
140 }
141 )]
142 #[serde(rename_all="kebab-case")]
143 #[derive(Serialize,Deserialize)]
144 /// Status of Sync Job
145 pub struct SyncJobStatus {
146 pub id: String,
147 pub store: String,
148 #[serde(skip_serializing_if="Option::is_none")]
149 pub owner: Option<Authid>,
150 pub remote: String,
151 pub remote_store: String,
152 #[serde(skip_serializing_if="Option::is_none")]
153 pub remove_vanished: Option<bool>,
154 #[serde(skip_serializing_if="Option::is_none")]
155 pub comment: Option<String>,
156 #[serde(skip_serializing_if="Option::is_none")]
157 pub schedule: Option<String>,
158 #[serde(skip_serializing_if="Option::is_none")]
159 pub next_run: Option<i64>,
160 #[serde(skip_serializing_if="Option::is_none")]
161 pub last_run_state: Option<String>,
162 #[serde(skip_serializing_if="Option::is_none")]
163 pub last_run_upid: Option<String>,
164 #[serde(skip_serializing_if="Option::is_none")]
165 pub last_run_endtime: Option<i64>,
166 }
167
168 fn init() -> SectionConfig {
169 let obj_schema = match SyncJobConfig::API_SCHEMA {
170 Schema::Object(ref obj_schema) => obj_schema,
171 _ => unreachable!(),
172 };
173
174 let plugin = SectionConfigPlugin::new("sync".to_string(), Some(String::from("id")), obj_schema);
175 let mut config = SectionConfig::new(&JOB_ID_SCHEMA);
176 config.register_plugin(plugin);
177
178 config
179 }
180
181 pub const SYNC_CFG_FILENAME: &str = "/etc/proxmox-backup/sync.cfg";
182 pub const SYNC_CFG_LOCKFILE: &str = "/etc/proxmox-backup/.sync.lck";
183
184 pub fn config() -> Result<(SectionConfigData, [u8;32]), Error> {
185
186 let content = proxmox::tools::fs::file_read_optional_string(SYNC_CFG_FILENAME)?;
187 let content = content.unwrap_or(String::from(""));
188
189 let digest = openssl::sha::sha256(content.as_bytes());
190 let data = CONFIG.parse(SYNC_CFG_FILENAME, &content)?;
191 Ok((data, digest))
192 }
193
194 pub fn save_config(config: &SectionConfigData) -> Result<(), Error> {
195 let raw = CONFIG.write(SYNC_CFG_FILENAME, &config)?;
196
197 let backup_user = crate::backup::backup_user()?;
198 let mode = nix::sys::stat::Mode::from_bits_truncate(0o0640);
199 // set the correct owner/group/permissions while saving file
200 // owner(rw) = root, group(r)= backup
201 let options = CreateOptions::new()
202 .perm(mode)
203 .owner(nix::unistd::ROOT)
204 .group(backup_user.gid);
205
206 replace_file(SYNC_CFG_FILENAME, raw.as_bytes(), options)?;
207
208 Ok(())
209 }
210
211 // shell completion helper
212 pub fn complete_sync_job_id(_arg: &str, _param: &HashMap<String, String>) -> Vec<String> {
213 match config() {
214 Ok((data, _digest)) => data.sections.iter().map(|(id, _)| id.to_string()).collect(),
215 Err(_) => return vec![],
216 }
217 }