Seperated Code into different Files and functions Part 2
Dieser Commit ist enthalten in:
Ursprung
dd026c901f
Commit
bd3f0ad2ac
6 geänderte Dateien mit 225 neuen und 220 gelöschten Zeilen
138
src/main.rs
138
src/main.rs
|
@ -5,17 +5,22 @@
|
||||||
|
|
||||||
pub(crate) mod consts;
|
pub(crate) mod consts;
|
||||||
pub(crate) mod macros;
|
pub(crate) mod macros;
|
||||||
|
pub(crate) mod prelude;
|
||||||
pub(crate) mod process;
|
pub(crate) mod process;
|
||||||
pub(crate) mod types;
|
pub(crate) mod types;
|
||||||
pub(crate) mod utils;
|
pub(crate) mod utils;
|
||||||
|
|
||||||
use crate::{
|
use crate::{
|
||||||
consts::*,
|
consts::*,
|
||||||
macros::match_error,
|
prelude::*,
|
||||||
|
process::{
|
||||||
|
process_accounts,
|
||||||
|
process_site,
|
||||||
|
services,
|
||||||
|
},
|
||||||
types::{
|
types::{
|
||||||
config,
|
|
||||||
config::{
|
config::{
|
||||||
CA,
|
GeneralConfig,
|
||||||
SiteConfig,
|
SiteConfig,
|
||||||
},
|
},
|
||||||
structs::{
|
structs::{
|
||||||
|
@ -23,14 +28,8 @@ use crate::{
|
||||||
ProcessorArgs,
|
ProcessorArgs,
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
utils::prefix_emails,
|
|
||||||
};
|
|
||||||
use acme2_eab::{
|
|
||||||
Account,
|
|
||||||
AccountBuilder,
|
|
||||||
Directory,
|
|
||||||
DirectoryBuilder,
|
|
||||||
};
|
};
|
||||||
|
use acme2_eab::Directory;
|
||||||
use async_scoped::TokioScope;
|
use async_scoped::TokioScope;
|
||||||
use clap::Parser;
|
use clap::Parser;
|
||||||
use env_logger::init as log_init;
|
use env_logger::init as log_init;
|
||||||
|
@ -43,14 +42,7 @@ use openssl::{
|
||||||
Private,
|
Private,
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
use process::{
|
use reqwest::tls::Version;
|
||||||
process_site,
|
|
||||||
services,
|
|
||||||
};
|
|
||||||
use reqwest::{
|
|
||||||
Client,
|
|
||||||
tls::Version,
|
|
||||||
};
|
|
||||||
use std::{
|
use std::{
|
||||||
collections::{
|
collections::{
|
||||||
HashMap,
|
HashMap,
|
||||||
|
@ -69,20 +61,13 @@ use tokio::{
|
||||||
create_dir_all,
|
create_dir_all,
|
||||||
read_dir,
|
read_dir,
|
||||||
},
|
},
|
||||||
io::{
|
io::AsyncReadExt,
|
||||||
AsyncReadExt,
|
|
||||||
AsyncWriteExt,
|
|
||||||
},
|
|
||||||
sync::Mutex,
|
sync::Mutex,
|
||||||
};
|
};
|
||||||
use tokio_stream::{
|
use tokio_stream::{
|
||||||
StreamExt,
|
StreamExt,
|
||||||
wrappers::ReadDirStream,
|
wrappers::ReadDirStream,
|
||||||
};
|
};
|
||||||
use types::{
|
|
||||||
config::GeneralConfig,
|
|
||||||
traits::FromFile as _,
|
|
||||||
};
|
|
||||||
|
|
||||||
|
|
||||||
fn default_client() -> reqwest::Client {
|
fn default_client() -> reqwest::Client {
|
||||||
|
@ -105,107 +90,6 @@ async fn load_privkey(path: PathBuf) -> Result<PKey<Private>, ()> {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
async fn process_accounts(
|
|
||||||
name: &String,
|
|
||||||
ca: &CA,
|
|
||||||
directories: &mut HashMap<String, Arc<Directory>>,
|
|
||||||
accounts: &mut HashMap<String, Arc<Account>>,
|
|
||||||
client: &Client,
|
|
||||||
accountpath: PathBuf,
|
|
||||||
) {
|
|
||||||
let directory = match directories.get(&ca.directory) {
|
|
||||||
Some(directory) => directory.to_owned(),
|
|
||||||
None => {
|
|
||||||
match DirectoryBuilder::new(ca.directory.clone()).http_client(client.clone()).build().await {
|
|
||||||
Ok(dir) => {
|
|
||||||
directories.insert(ca.directory.clone(), Arc::clone(&dir));
|
|
||||||
dir
|
|
||||||
},
|
|
||||||
Err(error) => {
|
|
||||||
error!("Failed to initialize directory for ca {name}: {error}");
|
|
||||||
return;
|
|
||||||
},
|
|
||||||
}
|
|
||||||
},
|
|
||||||
};
|
|
||||||
let mut ac = AccountBuilder::new(Arc::clone(&directory));
|
|
||||||
match ca.email_addresses.clone() {
|
|
||||||
Some(addr) => {
|
|
||||||
ac.contact(prefix_emails(addr));
|
|
||||||
},
|
|
||||||
None => {
|
|
||||||
ac.contact(Vec::new());
|
|
||||||
debug!("No Email address given")
|
|
||||||
},
|
|
||||||
}
|
|
||||||
let accountkey = accountpath.join("file.pem").with_file_name(name.clone());
|
|
||||||
let mut accountkeyfile = None;
|
|
||||||
if accountkey.exists() {
|
|
||||||
if let Ok(key) = load_privkey(accountkey).await {
|
|
||||||
ac.private_key(key);
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
info!("Registering for the CA {}", name.clone());
|
|
||||||
accountkeyfile = match FILE_MODE_WRITE.open(accountkey).await {
|
|
||||||
Ok(file) => Some(file),
|
|
||||||
Err(error) => {
|
|
||||||
error!("Failed to open the file for the accountkey: {error}");
|
|
||||||
return;
|
|
||||||
},
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if let Some(meta) = &directory.meta {
|
|
||||||
// Collecting the errors about the metadata before annoying the admin about errors at different stages
|
|
||||||
let mut errors = false;
|
|
||||||
if let Some(tos) = &meta.terms_of_service {
|
|
||||||
if !ca.tos_accepted {
|
|
||||||
error!("Terms of Services were not agreed into: {tos}");
|
|
||||||
errors = true;
|
|
||||||
} else {
|
|
||||||
ac.terms_of_service_agreed(true);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if meta.external_account_required.unwrap_or(false) {
|
|
||||||
if let Some(eab) = &ca.eab {
|
|
||||||
match eab.key() {
|
|
||||||
Ok(private) => {
|
|
||||||
trace!("EAB Key info: Type={:?} Bits={}, Security-Bits={}", private.id(), private.bits(), private.security_bits());
|
|
||||||
ac.external_account_binding(eab.token.clone(), private);
|
|
||||||
},
|
|
||||||
Err(error) => {
|
|
||||||
error!("{error}");
|
|
||||||
errors = true;
|
|
||||||
},
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
error!("eab_token and/or eab_key are unset, but the CA requires those.");
|
|
||||||
errors = true;
|
|
||||||
}
|
|
||||||
} else if ca.eab.is_some() {
|
|
||||||
warn!("The CA doesn't need EAB Tokens but they were configured")
|
|
||||||
}
|
|
||||||
if errors {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
let account = match ac.build().await {
|
|
||||||
Ok(account) => {
|
|
||||||
accounts.insert(name.clone(), Arc::clone(&account));
|
|
||||||
account
|
|
||||||
},
|
|
||||||
Err(error) => {
|
|
||||||
error!("Failed to get/create account: {error}");
|
|
||||||
return;
|
|
||||||
},
|
|
||||||
};
|
|
||||||
if let Some(mut keyfile) = accountkeyfile {
|
|
||||||
let keydata = match_error!(account.private_key().private_key_to_pem_pkcs8()=>Err(error)-> "Failed to convert the private key to an pem: {error}");
|
|
||||||
if let Err(error) = keyfile.write(keydata.as_slice()).await {
|
|
||||||
error!("Failed to write the accountkey: {error}");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
async fn racme(flags: Arguments) {
|
async fn racme(flags: Arguments) {
|
||||||
let client = default_client();
|
let client = default_client();
|
||||||
let systemd_access = daemon::booted();
|
let systemd_access = daemon::booted();
|
||||||
|
|
8
src/prelude.rs
Normale Datei
8
src/prelude.rs
Normale Datei
|
@ -0,0 +1,8 @@
|
||||||
|
pub(crate) use crate::{
|
||||||
|
macros::*,
|
||||||
|
types::traits::{
|
||||||
|
FromFile as _,
|
||||||
|
MatchAlgorithm as _,
|
||||||
|
MatchX509 as _,
|
||||||
|
},
|
||||||
|
};
|
140
src/process.rs
140
src/process.rs
|
@ -1,8 +1,12 @@
|
||||||
use std::{
|
use std::{
|
||||||
collections::HashSet,
|
collections::{
|
||||||
|
HashMap,
|
||||||
|
HashSet,
|
||||||
|
},
|
||||||
fs::Permissions,
|
fs::Permissions,
|
||||||
os::unix::fs::PermissionsExt,
|
os::unix::fs::PermissionsExt,
|
||||||
path::PathBuf,
|
path::PathBuf,
|
||||||
|
sync::Arc,
|
||||||
};
|
};
|
||||||
|
|
||||||
use crate::{
|
use crate::{
|
||||||
|
@ -13,25 +17,31 @@ use crate::{
|
||||||
WAIT_TIME,
|
WAIT_TIME,
|
||||||
},
|
},
|
||||||
load_privkey,
|
load_privkey,
|
||||||
macros::match_error,
|
prelude::*,
|
||||||
types::{
|
types::{
|
||||||
config::Dns,
|
config::{
|
||||||
|
CA,
|
||||||
|
Dns,
|
||||||
|
},
|
||||||
cryptography::Algorithm,
|
cryptography::Algorithm,
|
||||||
structs::{
|
structs::{
|
||||||
ProcessorArgs,
|
ProcessorArgs,
|
||||||
San,
|
San,
|
||||||
},
|
},
|
||||||
traits::{
|
|
||||||
MatchAlgorithm as _,
|
|
||||||
MatchX509,
|
|
||||||
},
|
},
|
||||||
|
utils::{
|
||||||
|
gen_key,
|
||||||
|
prefix_emails,
|
||||||
},
|
},
|
||||||
utils::gen_key,
|
|
||||||
};
|
};
|
||||||
use acme2_eab::{
|
use acme2_eab::{
|
||||||
|
Account,
|
||||||
|
AccountBuilder,
|
||||||
Authorization,
|
Authorization,
|
||||||
ChallengeStatus,
|
ChallengeStatus,
|
||||||
Csr,
|
Csr,
|
||||||
|
Directory,
|
||||||
|
DirectoryBuilder,
|
||||||
Identifier,
|
Identifier,
|
||||||
OrderBuilder,
|
OrderBuilder,
|
||||||
OrderStatus,
|
OrderStatus,
|
||||||
|
@ -54,6 +64,7 @@ use openssl::{
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
use reqwest::Client;
|
||||||
use tokio::{
|
use tokio::{
|
||||||
fs::{
|
fs::{
|
||||||
create_dir_all,
|
create_dir_all,
|
||||||
|
@ -86,6 +97,107 @@ fn gen_stack(args: &ProcessorArgs, context: X509v3Context) -> Stack<X509Extensio
|
||||||
stack
|
stack
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub async fn process_accounts(
|
||||||
|
name: &String,
|
||||||
|
ca: &CA,
|
||||||
|
directories: &mut HashMap<String, Arc<Directory>>,
|
||||||
|
accounts: &mut HashMap<String, Arc<Account>>,
|
||||||
|
client: &Client,
|
||||||
|
accountpath: PathBuf,
|
||||||
|
) {
|
||||||
|
let directory = match directories.get(&ca.directory) {
|
||||||
|
Some(directory) => directory.to_owned(),
|
||||||
|
None => {
|
||||||
|
match DirectoryBuilder::new(ca.directory.clone()).http_client(client.clone()).build().await {
|
||||||
|
Ok(dir) => {
|
||||||
|
directories.insert(ca.directory.clone(), Arc::clone(&dir));
|
||||||
|
dir
|
||||||
|
},
|
||||||
|
Err(error) => {
|
||||||
|
error!("Failed to initialize directory for ca {name}: {error}");
|
||||||
|
return;
|
||||||
|
},
|
||||||
|
}
|
||||||
|
},
|
||||||
|
};
|
||||||
|
let mut ac = AccountBuilder::new(Arc::clone(&directory));
|
||||||
|
match ca.email_addresses.clone() {
|
||||||
|
Some(addr) => {
|
||||||
|
ac.contact(prefix_emails(addr));
|
||||||
|
},
|
||||||
|
None => {
|
||||||
|
ac.contact(Vec::new());
|
||||||
|
debug!("No Email address given")
|
||||||
|
},
|
||||||
|
}
|
||||||
|
let accountkey = accountpath.join("file.pem").with_file_name(name.clone());
|
||||||
|
let mut accountkeyfile = None;
|
||||||
|
if accountkey.exists() {
|
||||||
|
if let Ok(key) = load_privkey(accountkey).await {
|
||||||
|
ac.private_key(key);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
info!("Registering for the CA {}", name.clone());
|
||||||
|
accountkeyfile = match FILE_MODE_WRITE.open(accountkey).await {
|
||||||
|
Ok(file) => Some(file),
|
||||||
|
Err(error) => {
|
||||||
|
error!("Failed to open the file for the accountkey: {error}");
|
||||||
|
return;
|
||||||
|
},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if let Some(meta) = &directory.meta {
|
||||||
|
// Collecting the errors about the metadata before annoying the admin about errors at different stages
|
||||||
|
let mut errors = false;
|
||||||
|
if let Some(tos) = &meta.terms_of_service {
|
||||||
|
if !ca.tos_accepted {
|
||||||
|
error!("Terms of Services were not agreed into: {tos}");
|
||||||
|
errors = true;
|
||||||
|
} else {
|
||||||
|
ac.terms_of_service_agreed(true);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if meta.external_account_required.unwrap_or(false) {
|
||||||
|
if let Some(eab) = &ca.eab {
|
||||||
|
match eab.key() {
|
||||||
|
Ok(private) => {
|
||||||
|
trace!("EAB Key info: Type={:?} Bits={}, Security-Bits={}", private.id(), private.bits(), private.security_bits());
|
||||||
|
ac.external_account_binding(eab.token.clone(), private);
|
||||||
|
},
|
||||||
|
Err(error) => {
|
||||||
|
error!("{error}");
|
||||||
|
errors = true;
|
||||||
|
},
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
error!("eab_token and/or eab_key are unset, but the CA requires those.");
|
||||||
|
errors = true;
|
||||||
|
}
|
||||||
|
} else if ca.eab.is_some() {
|
||||||
|
warn!("The CA doesn't need EAB Tokens but they were configured")
|
||||||
|
}
|
||||||
|
if errors {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
let account = match ac.build().await {
|
||||||
|
Ok(account) => {
|
||||||
|
accounts.insert(name.clone(), Arc::clone(&account));
|
||||||
|
account
|
||||||
|
},
|
||||||
|
Err(error) => {
|
||||||
|
error!("Failed to get/create account: {error}");
|
||||||
|
return;
|
||||||
|
},
|
||||||
|
};
|
||||||
|
if let Some(mut keyfile) = accountkeyfile {
|
||||||
|
let keydata = match_error!(account.private_key().private_key_to_pem_pkcs8()=>Err(error)-> "Failed to convert the private key to an pem: {error}");
|
||||||
|
if let Err(error) = keyfile.write(keydata.as_slice()).await {
|
||||||
|
error!("Failed to write the accountkey: {error}");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
pub async fn process_site(args: ProcessorArgs<'_>) {
|
pub async fn process_site(args: ProcessorArgs<'_>) {
|
||||||
let mut cert_renew = false;
|
let mut cert_renew = false;
|
||||||
|
@ -98,8 +210,10 @@ pub async fn process_site(args: ProcessorArgs<'_>) {
|
||||||
};
|
};
|
||||||
cert_renew = true;
|
cert_renew = true;
|
||||||
}
|
}
|
||||||
let private_key_file = directory.join("privkey.pem");
|
|
||||||
let mut private_key;
|
let mut private_key;
|
||||||
|
// Private key block
|
||||||
|
{
|
||||||
|
let private_key_file = directory.join("privkey.pem");
|
||||||
let mut write_pkey = false;
|
let mut write_pkey = false;
|
||||||
if !private_key_file.exists() {
|
if !private_key_file.exists() {
|
||||||
cert_renew = true;
|
cert_renew = true;
|
||||||
|
@ -124,6 +238,7 @@ pub async fn process_site(args: ProcessorArgs<'_>) {
|
||||||
let mut file = match_error!(FILE_MODE_WRITE.open(private_key_file.clone()).await=>Err(error)-> "Failed to write new private key: {error}");
|
let mut file = match_error!(FILE_MODE_WRITE.open(private_key_file.clone()).await=>Err(error)-> "Failed to write new private key: {error}");
|
||||||
match_error!(file.write_all(&pkey).await=>Err(error)->"Failed to write new private key: {error}");
|
match_error!(file.write_all(&pkey).await=>Err(error)->"Failed to write new private key: {error}");
|
||||||
}
|
}
|
||||||
|
}
|
||||||
let pubkey_filename = directory.join("pubkey.pem");
|
let pubkey_filename = directory.join("pubkey.pem");
|
||||||
if pubkey_filename.exists() {
|
if pubkey_filename.exists() {
|
||||||
let mut file = match_error!(FILE_MODE.open(pubkey_filename.clone()).await=>Err(error)-> "Failed to open publickey. Aborting processing: {error}");
|
let mut file = match_error!(FILE_MODE.open(pubkey_filename.clone()).await=>Err(error)-> "Failed to open publickey. Aborting processing: {error}");
|
||||||
|
@ -151,10 +266,13 @@ pub async fn process_site(args: ProcessorArgs<'_>) {
|
||||||
info!("Site {} doesn't need an update for the certificate.", args.name());
|
info!("Site {} doesn't need an update for the certificate.", args.name());
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
// Requesting a new cert
|
||||||
|
let mut order;
|
||||||
|
{
|
||||||
info!("Renewing Certificate for site {}", args.name());
|
info!("Renewing Certificate for site {}", args.name());
|
||||||
let mut builder = OrderBuilder::new(args.account());
|
let mut builder = OrderBuilder::new(args.account());
|
||||||
builder.set_identifiers(args.san().iter().map(|s| s.to_owned().into()).collect::<Vec<Identifier>>());
|
builder.set_identifiers(args.san().iter().map(|s| s.to_owned().into()).collect::<Vec<Identifier>>());
|
||||||
let mut order = match_error!(builder.build().await=>Err(error)-> "Failed order the certificate: {error}");
|
order = match_error!(builder.build().await=>Err(error)-> "Failed order the certificate: {error}");
|
||||||
let authorizations = match_error!(order.authorizations().await=>Err(error)-> "Failed to get the authorizations: {error}");
|
let authorizations = match_error!(order.authorizations().await=>Err(error)-> "Failed to get the authorizations: {error}");
|
||||||
let (_, result) = tokio::join! {
|
let (_, result) = tokio::join! {
|
||||||
unsafe {
|
unsafe {
|
||||||
|
@ -197,7 +315,8 @@ pub async fn process_site(args: ProcessorArgs<'_>) {
|
||||||
debug!("Received {} certificates.", certs.len());
|
debug!("Received {} certificates.", certs.len());
|
||||||
let mut pubkey_file = match_error!(FILE_MODE_WRITE.open(pubkey_filename).await=>Err(error)-> "Failed to open the file for the publickey: {error}");
|
let mut pubkey_file = match_error!(FILE_MODE_WRITE.open(pubkey_filename).await=>Err(error)-> "Failed to open the file for the publickey: {error}");
|
||||||
match_error!(pubkey_file.write_all(&certs[0].to_pem().unwrap()).await=>Err(error)-> "Failed to write the publickey: {error}");
|
match_error!(pubkey_file.write_all(&certs[0].to_pem().unwrap()).await=>Err(error)-> "Failed to write the publickey: {error}");
|
||||||
let mut fullchain = match_error!(FILE_MODE_WRITE.open(directory.join("fullchain.pem")).await=>Err(error)-> "failed to open the fullchain.pem: {error}");
|
let mut fullchain =
|
||||||
|
match_error!(FILE_MODE_WRITE.open(directory.join("fullchain.pem")).await=>Err(error)-> "failed to open the fullchain.pem: {error}");
|
||||||
for cert in certs.clone() {
|
for cert in certs.clone() {
|
||||||
let _ = fullchain.write_all(&cert.to_pem().unwrap()).await;
|
let _ = fullchain.write_all(&cert.to_pem().unwrap()).await;
|
||||||
}
|
}
|
||||||
|
@ -205,6 +324,7 @@ pub async fn process_site(args: ProcessorArgs<'_>) {
|
||||||
let _ = bundle.write_all(&private_key.private_key_to_pem_pkcs8().unwrap()).await;
|
let _ = bundle.write_all(&private_key.private_key_to_pem_pkcs8().unwrap()).await;
|
||||||
let _ = bundle.write_all(&certs[0].to_pem().unwrap()).await;
|
let _ = bundle.write_all(&certs[0].to_pem().unwrap()).await;
|
||||||
info!("Processing of {} successful", args.name());
|
info!("Processing of {} successful", args.name());
|
||||||
|
}
|
||||||
let mut services = args.reload_list().await;
|
let mut services = args.reload_list().await;
|
||||||
for service in &args.reload_services() {
|
for service in &args.reload_services() {
|
||||||
services.insert(service.to_owned());
|
services.insert(service.to_owned());
|
||||||
|
|
|
@ -1,8 +1,5 @@
|
||||||
use crate::{
|
use crate::{
|
||||||
macros::{
|
prelude::*,
|
||||||
DefDer,
|
|
||||||
match_error,
|
|
||||||
},
|
|
||||||
types::{
|
types::{
|
||||||
VString,
|
VString,
|
||||||
cryptography::{
|
cryptography::{
|
||||||
|
|
|
@ -1,7 +1,7 @@
|
||||||
use macro_rules_attribute::macro_rules_derive;
|
use macro_rules_attribute::macro_rules_derive;
|
||||||
use serde::Deserialize;
|
use serde::Deserialize;
|
||||||
|
|
||||||
use crate::macros::DefDer;
|
use crate::prelude::*;
|
||||||
|
|
||||||
|
|
||||||
#[macro_rules_derive(DefDer)]
|
#[macro_rules_derive(DefDer)]
|
||||||
|
|
|
@ -11,14 +11,10 @@ use macro_rules_attribute::macro_rules_derive;
|
||||||
use tokio::sync::MutexGuard;
|
use tokio::sync::MutexGuard;
|
||||||
|
|
||||||
use crate::{
|
use crate::{
|
||||||
config::SiteConfig,
|
prelude::*,
|
||||||
macros::{
|
|
||||||
DefDer,
|
|
||||||
Hashable,
|
|
||||||
attr_function,
|
|
||||||
},
|
|
||||||
types::{
|
types::{
|
||||||
SafeSet,
|
SafeSet,
|
||||||
|
config::SiteConfig,
|
||||||
cryptography::{
|
cryptography::{
|
||||||
Algorithm,
|
Algorithm,
|
||||||
Strength,
|
Strength,
|
||||||
|
@ -26,7 +22,7 @@ use crate::{
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
use super::config::Dns;
|
use crate::types::config::Dns;
|
||||||
|
|
||||||
|
|
||||||
#[macro_rules_derive(DefDer)]
|
#[macro_rules_derive(DefDer)]
|
||||||
|
|
Laden …
Tabelle hinzufügen
In neuem Issue referenzieren