1 use std
::collections
::HashSet
;
3 use anyhow
::{bail, Error}
;
4 use serde
::{Deserialize, Serialize}
;
7 use proxmox
::api
::schema
::{ApiStringFormat, ApiType, Updater}
;
9 use proxmox_http
::ProxyConfig
;
11 use pbs_buildcfg
::configdir
;
12 use pbs_config
::{open_backup_lockfile, BackupLockGuard}
;
14 use crate::acme
::AcmeClient
;
15 use crate::api2
::types
::{
16 AcmeAccountName
, AcmeDomain
, ACME_DOMAIN_PROPERTY_SCHEMA
, HTTP_PROXY_SCHEMA
,
19 const CONF_FILE
: &str = configdir
!("/node.cfg");
20 const LOCK_FILE
: &str = configdir
!("/.node.lck");
22 pub fn lock() -> Result
<BackupLockGuard
, Error
> {
23 open_backup_lockfile(LOCK_FILE
, None
, true)
26 /// Read the Node Config.
27 pub fn config() -> Result
<(NodeConfig
, [u8; 32]), Error
> {
29 proxmox
::tools
::fs
::file_read_optional_string(CONF_FILE
)?
.unwrap_or_else(|| "".to_string());
31 let digest
= openssl
::sha
::sha256(content
.as_bytes());
32 let data
: NodeConfig
= crate::tools
::config
::from_str(&content
, &NodeConfig
::API_SCHEMA
)?
;
37 /// Write the Node Config, requires the write lock to be held.
38 pub fn save_config(config
: &NodeConfig
) -> Result
<(), Error
> {
41 let raw
= crate::tools
::config
::to_bytes(config
, &NodeConfig
::API_SCHEMA
)?
;
42 pbs_config
::replace_backup_config(CONF_FILE
, &raw
)
47 account
: { type: AcmeAccountName }
,
50 #[derive(Deserialize, Serialize)]
51 /// The ACME configuration.
53 /// Currently only contains the name of the account use.
54 pub struct AcmeConfig
{
55 /// Account to use to acquire ACME certificates.
56 account
: AcmeAccountName
,
64 format
: &ApiStringFormat
::PropertyString(&AcmeConfig
::API_SCHEMA
),
67 schema
: ACME_DOMAIN_PROPERTY_SCHEMA
,
71 schema
: ACME_DOMAIN_PROPERTY_SCHEMA
,
75 schema
: ACME_DOMAIN_PROPERTY_SCHEMA
,
79 schema
: ACME_DOMAIN_PROPERTY_SCHEMA
,
83 schema
: ACME_DOMAIN_PROPERTY_SCHEMA
,
87 schema
: HTTP_PROXY_SCHEMA
,
92 #[derive(Deserialize, Serialize, Updater)]
93 #[serde(rename_all = "kebab-case")]
94 /// Node specific configuration.
95 pub struct NodeConfig
{
96 /// The acme account to use on this node.
97 #[serde(skip_serializing_if = "Option::is_none")]
98 pub acme
: Option
<String
>,
100 #[serde(skip_serializing_if = "Option::is_none")]
101 pub acmedomain0
: Option
<String
>,
103 #[serde(skip_serializing_if = "Option::is_none")]
104 pub acmedomain1
: Option
<String
>,
106 #[serde(skip_serializing_if = "Option::is_none")]
107 pub acmedomain2
: Option
<String
>,
109 #[serde(skip_serializing_if = "Option::is_none")]
110 pub acmedomain3
: Option
<String
>,
112 #[serde(skip_serializing_if = "Option::is_none")]
113 pub acmedomain4
: Option
<String
>,
115 #[serde(skip_serializing_if = "Option::is_none")]
116 pub http_proxy
: Option
<String
>,
120 pub fn acme_config(&self) -> Option
<Result
<AcmeConfig
, Error
>> {
121 self.acme
.as_deref().map(|config
| -> Result
<_
, Error
> {
122 Ok(crate::tools
::config
::from_property_string(
124 &AcmeConfig
::API_SCHEMA
,
129 pub async
fn acme_client(&self) -> Result
<AcmeClient
, Error
> {
130 let account
= if let Some(cfg
) = self.acme_config().transpose()?
{
133 AcmeAccountName
::from_string("default".to_string())?
// should really not happen
135 AcmeClient
::load(&account
).await
138 pub fn acme_domains(&self) -> AcmeDomainIter
{
139 AcmeDomainIter
::new(self)
142 /// Returns the parsed ProxyConfig
143 pub fn http_proxy(&self) -> Option
<ProxyConfig
> {
144 if let Some(http_proxy
) = &self.http_proxy
{
145 match ProxyConfig
::parse_proxy_url(&http_proxy
) {
146 Ok(proxy
) => Some(proxy
),
154 /// Sets the HTTP proxy configuration
155 pub fn set_http_proxy(&mut self, http_proxy
: Option
<String
>) {
156 self.http_proxy
= http_proxy
;
159 /// Validate the configuration.
160 pub fn validate(&self) -> Result
<(), Error
> {
161 let mut domains
= HashSet
::new();
162 for domain
in self.acme_domains() {
163 let domain
= domain?
;
164 if !domains
.insert(domain
.domain
.to_lowercase()) {
165 bail
!("duplicate domain '{}' in ACME config", domain
.domain
);
173 pub struct AcmeDomainIter
<'a
> {
174 config
: &'a NodeConfig
,
178 impl<'a
> AcmeDomainIter
<'a
> {
179 fn new(config
: &'a NodeConfig
) -> Self {
180 Self { config, index: 0 }
184 impl<'a
> Iterator
for AcmeDomainIter
<'a
> {
185 type Item
= Result
<AcmeDomain
, Error
>;
187 fn next(&mut self) -> Option
<Self::Item
> {
189 let index
= self.index
;
192 let domain
= match index
{
193 0 => self.config
.acmedomain0
.as_deref(),
194 1 => self.config
.acmedomain1
.as_deref(),
195 2 => self.config
.acmedomain2
.as_deref(),
196 3 => self.config
.acmedomain3
.as_deref(),
197 4 => self.config
.acmedomain4
.as_deref(),
201 if let Some(domain
) = domain
{
206 Some(crate::tools
::config
::from_property_string(
208 &AcmeDomain
::API_SCHEMA
,