]> git.proxmox.com Git - proxmox-backup.git/blob - src/config/remote.rs
tfa: remember recovery indices
[proxmox-backup.git] / src / config / remote.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 pub const REMOTE_PASSWORD_SCHEMA: Schema = StringSchema::new("Password or auth token for remote host.")
25 .format(&PASSWORD_FORMAT)
26 .min_length(1)
27 .max_length(1024)
28 .schema();
29
30 #[api(
31 properties: {
32 name: {
33 schema: REMOTE_ID_SCHEMA,
34 },
35 comment: {
36 optional: true,
37 schema: SINGLE_LINE_COMMENT_SCHEMA,
38 },
39 host: {
40 schema: DNS_NAME_OR_IP_SCHEMA,
41 },
42 port: {
43 optional: true,
44 description: "The (optional) port",
45 type: u16,
46 },
47 "auth-id": {
48 type: Authid,
49 },
50 password: {
51 schema: REMOTE_PASSWORD_SCHEMA,
52 },
53 fingerprint: {
54 optional: true,
55 schema: CERT_FINGERPRINT_SHA256_SCHEMA,
56 },
57 }
58 )]
59 #[derive(Serialize,Deserialize)]
60 #[serde(rename_all = "kebab-case")]
61 /// Remote properties.
62 pub struct Remote {
63 pub name: String,
64 #[serde(skip_serializing_if="Option::is_none")]
65 pub comment: Option<String>,
66 pub host: String,
67 #[serde(skip_serializing_if="Option::is_none")]
68 pub port: Option<u16>,
69 pub auth_id: Authid,
70 #[serde(skip_serializing_if="String::is_empty")]
71 #[serde(with = "proxmox::tools::serde::string_as_base64")]
72 pub password: String,
73 #[serde(skip_serializing_if="Option::is_none")]
74 pub fingerprint: Option<String>,
75 }
76
77 fn init() -> SectionConfig {
78 let obj_schema = match Remote::API_SCHEMA {
79 Schema::Object(ref obj_schema) => obj_schema,
80 _ => unreachable!(),
81 };
82
83 let plugin = SectionConfigPlugin::new("remote".to_string(), Some("name".to_string()), obj_schema);
84 let mut config = SectionConfig::new(&REMOTE_ID_SCHEMA);
85 config.register_plugin(plugin);
86
87 config
88 }
89
90 pub const REMOTE_CFG_FILENAME: &str = "/etc/proxmox-backup/remote.cfg";
91 pub const REMOTE_CFG_LOCKFILE: &str = "/etc/proxmox-backup/.remote.lck";
92
93 pub fn config() -> Result<(SectionConfigData, [u8;32]), Error> {
94
95 let content = proxmox::tools::fs::file_read_optional_string(REMOTE_CFG_FILENAME)?;
96 let content = content.unwrap_or(String::from(""));
97
98 let digest = openssl::sha::sha256(content.as_bytes());
99 let data = CONFIG.parse(REMOTE_CFG_FILENAME, &content)?;
100 Ok((data, digest))
101 }
102
103 pub fn save_config(config: &SectionConfigData) -> Result<(), Error> {
104 let raw = CONFIG.write(REMOTE_CFG_FILENAME, &config)?;
105
106 let backup_user = crate::backup::backup_user()?;
107 let mode = nix::sys::stat::Mode::from_bits_truncate(0o0640);
108 // set the correct owner/group/permissions while saving file
109 // owner(rw) = root, group(r)= backup
110 let options = CreateOptions::new()
111 .perm(mode)
112 .owner(nix::unistd::ROOT)
113 .group(backup_user.gid);
114
115 replace_file(REMOTE_CFG_FILENAME, raw.as_bytes(), options)?;
116
117 Ok(())
118 }
119
120 // shell completion helper
121 pub fn complete_remote_name(_arg: &str, _param: &HashMap<String, String>) -> Vec<String> {
122 match config() {
123 Ok((data, _digest)) => data.sections.iter().map(|(id, _)| id.to_string()).collect(),
124 Err(_) => return vec![],
125 }
126 }