]>
Commit | Line | Data |
---|---|---|
3eeba687 | 1 | use anyhow::{Error}; |
f486e9e5 | 2 | use lazy_static::lazy_static; |
b4900286 DM |
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 | }; | |
f486e9e5 | 15 | |
b4900286 | 16 | use proxmox::tools::{fs::replace_file, fs::CreateOptions}; |
f486e9e5 | 17 | |
b4900286 | 18 | use crate::api2::types::*; |
f486e9e5 | 19 | |
b4900286 DM |
20 | lazy_static! { |
21 | static ref CONFIG: SectionConfig = init(); | |
f486e9e5 DM |
22 | } |
23 | ||
b4900286 DM |
24 | #[api( |
25 | properties: { | |
26 | id: { | |
27 | schema: JOB_ID_SCHEMA, | |
28 | }, | |
29 | store: { | |
30 | schema: DATASTORE_SCHEMA, | |
31 | }, | |
f1694b06 FG |
32 | "owner": { |
33 | type: Authid, | |
34 | optional: true, | |
35 | }, | |
b4900286 DM |
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, | |
2888b27f | 52 | schema: SYNC_SCHEDULE_SCHEMA, |
b4900286 DM |
53 | }, |
54 | } | |
55 | )] | |
56 | #[serde(rename_all="kebab-case")] | |
9e733dae | 57 | #[derive(Serialize,Deserialize,Clone)] |
6f652b1b DM |
58 | /// Sync Job |
59 | pub struct SyncJobConfig { | |
b4900286 DM |
60 | pub id: String, |
61 | pub store: String, | |
f1694b06 FG |
62 | #[serde(skip_serializing_if="Option::is_none")] |
63 | pub owner: Option<Authid>, | |
b4900286 DM |
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>, | |
f486e9e5 DM |
72 | } |
73 | ||
59af9ca9 FG |
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.clone(), | |
83 | comment: job_status.comment.clone(), | |
84 | schedule: job_status.schedule.clone(), | |
85 | } | |
86 | } | |
87 | } | |
88 | ||
997d7e19 DC |
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 | }, | |
f1694b06 FG |
98 | owner: { |
99 | type: Authid, | |
100 | optional: true, | |
101 | }, | |
997d7e19 DC |
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, | |
f1694b06 FG |
148 | #[serde(skip_serializing_if="Option::is_none")] |
149 | pub owner: Option<Authid>, | |
997d7e19 DC |
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 | ||
b4900286 | 168 | fn init() -> SectionConfig { |
6f652b1b | 169 | let obj_schema = match SyncJobConfig::API_SCHEMA { |
b4900286 DM |
170 | Schema::Object(ref obj_schema) => obj_schema, |
171 | _ => unreachable!(), | |
172 | }; | |
f486e9e5 | 173 | |
6f652b1b | 174 | let plugin = SectionConfigPlugin::new("sync".to_string(), Some(String::from("id")), obj_schema); |
b4900286 DM |
175 | let mut config = SectionConfig::new(&JOB_ID_SCHEMA); |
176 | config.register_plugin(plugin); | |
f486e9e5 | 177 | |
b4900286 DM |
178 | config |
179 | } | |
180 | ||
6f652b1b DM |
181 | pub const SYNC_CFG_FILENAME: &str = "/etc/proxmox-backup/sync.cfg"; |
182 | pub const SYNC_CFG_LOCKFILE: &str = "/etc/proxmox-backup/.sync.lck"; | |
b4900286 DM |
183 | |
184 | pub fn config() -> Result<(SectionConfigData, [u8;32]), Error> { | |
3eeba687 DM |
185 | |
186 | let content = proxmox::tools::fs::file_read_optional_string(SYNC_CFG_FILENAME)?; | |
187 | let content = content.unwrap_or(String::from("")); | |
f486e9e5 | 188 | |
b4900286 | 189 | let digest = openssl::sha::sha256(content.as_bytes()); |
6f652b1b | 190 | let data = CONFIG.parse(SYNC_CFG_FILENAME, &content)?; |
b4900286 | 191 | Ok((data, digest)) |
f486e9e5 DM |
192 | } |
193 | ||
b4900286 | 194 | pub fn save_config(config: &SectionConfigData) -> Result<(), Error> { |
6f652b1b | 195 | let raw = CONFIG.write(SYNC_CFG_FILENAME, &config)?; |
f486e9e5 | 196 | |
b4900286 DM |
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); | |
f486e9e5 | 205 | |
6f652b1b | 206 | replace_file(SYNC_CFG_FILENAME, raw.as_bytes(), options)?; |
f486e9e5 | 207 | |
b4900286 | 208 | Ok(()) |
f486e9e5 DM |
209 | } |
210 | ||
b4900286 | 211 | // shell completion helper |
6f652b1b | 212 | pub fn complete_sync_job_id(_arg: &str, _param: &HashMap<String, String>) -> Vec<String> { |
b4900286 DM |
213 | match config() { |
214 | Ok((data, _digest)) => data.sections.iter().map(|(id, _)| id.to_string()).collect(), | |
215 | Err(_) => return vec![], | |
f486e9e5 | 216 | } |
f486e9e5 | 217 | } |