Service agnostic DDNS
This commit is contained in:
parent
deeb3e8a05
commit
316f5c0219
8 changed files with 46 additions and 92 deletions
1
Cargo.lock
generated
1
Cargo.lock
generated
|
@ -1390,7 +1390,6 @@ dependencies = [
|
|||
"axum-extra",
|
||||
"axum-range",
|
||||
"axum-test",
|
||||
"base64 0.22.1",
|
||||
"bitcode",
|
||||
"branca",
|
||||
"bytes",
|
||||
|
|
|
@ -12,7 +12,6 @@ ui = ["native-windows-gui", "native-windows-derive"]
|
|||
ape = "0.5"
|
||||
axum-extra = { version = "0.9.3", features = ["typed-header"] }
|
||||
axum-range = "0.4.0"
|
||||
base64 = "0.22.1"
|
||||
bitcode = { version = "0.6.3", features = ["serde"] }
|
||||
branca = "0.10.1"
|
||||
chumsky = "0.9.3"
|
||||
|
|
|
@ -82,6 +82,8 @@ pub enum Error {
|
|||
MiscSettingsNotFound,
|
||||
#[error("Index album art pattern is not a valid regex")]
|
||||
IndexAlbumArtPatternInvalid,
|
||||
#[error("DDNS update URL is invalid")]
|
||||
DDNSUpdateURLInvalid,
|
||||
|
||||
#[error(transparent)]
|
||||
Toml(#[from] toml::de::Error),
|
||||
|
@ -138,6 +140,7 @@ pub struct App {
|
|||
pub port: u16,
|
||||
pub web_dir_path: PathBuf,
|
||||
pub swagger_dir_path: PathBuf,
|
||||
pub ddns_manager: ddns::Manager,
|
||||
pub scanner: scanner::Scanner,
|
||||
pub index_manager: index::Manager,
|
||||
pub config_manager: config::Manager,
|
||||
|
@ -168,6 +171,7 @@ impl App {
|
|||
let auth_secret = Self::get_or_create_auth_secret(&auth_secret_file_path).await?;
|
||||
|
||||
let config_manager = config::Manager::new(&paths.config_file_path, auth_secret).await?;
|
||||
let ddns_manager = ddns::Manager::new(config_manager.clone());
|
||||
let ndb_manager = ndb::Manager::new(&paths.data_dir_path)?;
|
||||
let index_manager = index::Manager::new(&paths.data_dir_path).await?;
|
||||
let scanner = scanner::Scanner::new(index_manager.clone(), config_manager.clone()).await?;
|
||||
|
@ -179,6 +183,7 @@ impl App {
|
|||
port,
|
||||
web_dir_path: paths.web_dir_path,
|
||||
swagger_dir_path: paths.swagger_dir_path,
|
||||
ddns_manager,
|
||||
scanner,
|
||||
index_manager,
|
||||
config_manager,
|
||||
|
|
|
@ -22,8 +22,8 @@ use super::auth;
|
|||
#[derive(Default)]
|
||||
pub struct Config {
|
||||
pub reindex_every_n_seconds: Option<u64>,
|
||||
pub album_art_pattern: Option<String>,
|
||||
pub ddns_url: Option<String>,
|
||||
pub album_art_pattern: Option<Regex>,
|
||||
pub ddns_url: Option<http::Uri>,
|
||||
pub mount_dirs: Vec<MountDir>,
|
||||
pub users: HashMap<String, User>,
|
||||
}
|
||||
|
@ -45,10 +45,22 @@ impl TryFrom<storage::Config> for Config {
|
|||
.filter_map(|m| m.try_into().ok())
|
||||
.collect();
|
||||
|
||||
let ddns_url = match c.ddns_url.map(http::Uri::try_from) {
|
||||
Some(Ok(u)) => Some(u),
|
||||
Some(Err(_)) => return Err(Error::DDNSUpdateURLInvalid),
|
||||
None => None,
|
||||
};
|
||||
|
||||
let album_art_pattern = match c.album_art_pattern.as_deref().map(Regex::new) {
|
||||
Some(Ok(u)) => Some(u),
|
||||
Some(Err(_)) => return Err(Error::IndexAlbumArtPatternInvalid),
|
||||
None => None,
|
||||
};
|
||||
|
||||
Ok(Config {
|
||||
reindex_every_n_seconds: c.reindex_every_n_seconds, // TODO validate and warn
|
||||
album_art_pattern: c.album_art_pattern, // TODO validate and warn
|
||||
ddns_url: c.ddns_url, // TODO validate and warn
|
||||
album_art_pattern,
|
||||
ddns_url,
|
||||
mount_dirs,
|
||||
users,
|
||||
})
|
||||
|
@ -92,25 +104,25 @@ impl Manager {
|
|||
// TODO persistence
|
||||
}
|
||||
|
||||
pub async fn get_index_album_art_pattern(&self) -> String {
|
||||
pub async fn get_index_album_art_pattern(&self) -> Regex {
|
||||
let config = self.config.read().await;
|
||||
let pattern = config.album_art_pattern.clone();
|
||||
pattern.unwrap_or("Folder.(jpeg|jpg|png)".to_owned())
|
||||
pattern.unwrap_or_else(|| Regex::new("Folder.(jpeg|jpg|png)").unwrap())
|
||||
}
|
||||
|
||||
pub async fn set_index_album_art_pattern(&self, regex: Regex) {
|
||||
let mut config = self.config.write().await;
|
||||
config.album_art_pattern = Some(regex.as_str().to_owned());
|
||||
config.album_art_pattern = Some(regex);
|
||||
// TODO persistence
|
||||
}
|
||||
|
||||
pub async fn get_ddns_update_url(&self) -> Option<String> {
|
||||
pub async fn get_ddns_update_url(&self) -> Option<http::Uri> {
|
||||
self.config.read().await.ddns_url.clone()
|
||||
}
|
||||
|
||||
pub async fn set_ddns_update_url(&self, url: http::Uri) {
|
||||
let mut config = self.config.write().await;
|
||||
config.ddns_url = Some(url.to_string());
|
||||
config.ddns_url = Some(url);
|
||||
// TODO persistence
|
||||
}
|
||||
|
||||
|
|
|
@ -1,45 +1,26 @@
|
|||
use base64::prelude::*;
|
||||
use log::{debug, error};
|
||||
use serde::{Deserialize, Serialize};
|
||||
use std::time::Duration;
|
||||
|
||||
use crate::app::Error;
|
||||
use crate::db::DB;
|
||||
|
||||
const DDNS_UPDATE_URL: &str = "https://ydns.io/api/v1/update/";
|
||||
|
||||
#[derive(Clone, Debug, Deserialize, PartialEq, Eq, Serialize)]
|
||||
pub struct Config {
|
||||
pub ddns_host: String,
|
||||
pub ddns_username: String,
|
||||
pub ddns_password: String,
|
||||
}
|
||||
use crate::app::{config, Error};
|
||||
|
||||
#[derive(Clone)]
|
||||
pub struct Manager {
|
||||
db: DB,
|
||||
config_manager: config::Manager,
|
||||
}
|
||||
|
||||
impl Manager {
|
||||
pub fn new(db: DB) -> Self {
|
||||
Self { db }
|
||||
pub fn new(config_manager: config::Manager) -> Self {
|
||||
Self { config_manager }
|
||||
}
|
||||
|
||||
async fn update_my_ip(&self) -> Result<(), Error> {
|
||||
let config = self.config().await?;
|
||||
if config.ddns_host.is_empty() || config.ddns_username.is_empty() {
|
||||
async fn update_ddns(&self) -> Result<(), Error> {
|
||||
let url = self.config_manager.get_ddns_update_url().await;
|
||||
let Some(url) = url else {
|
||||
debug!("Skipping DDNS update because credentials are missing");
|
||||
return Ok(());
|
||||
}
|
||||
};
|
||||
|
||||
let full_url = format!("{}?host={}", DDNS_UPDATE_URL, &config.ddns_host);
|
||||
let credentials = format!("{}:{}", &config.ddns_username, &config.ddns_password);
|
||||
let response = ureq::get(full_url.as_str())
|
||||
.set(
|
||||
"Authorization",
|
||||
&format!("Basic {}", BASE64_STANDARD_NO_PAD.encode(credentials)),
|
||||
)
|
||||
.call();
|
||||
let response = ureq::get(&url.to_string()).call();
|
||||
|
||||
match response {
|
||||
Ok(_) => Ok(()),
|
||||
|
@ -48,33 +29,12 @@ impl Manager {
|
|||
}
|
||||
}
|
||||
|
||||
pub async fn config(&self) -> Result<Config, Error> {
|
||||
Ok(sqlx::query_as!(
|
||||
Config,
|
||||
"SELECT ddns_host, ddns_username, ddns_password FROM config"
|
||||
)
|
||||
.fetch_one(self.db.connect().await?.as_mut())
|
||||
.await?)
|
||||
}
|
||||
|
||||
pub async fn set_config(&self, new_config: &Config) -> Result<(), Error> {
|
||||
sqlx::query!(
|
||||
"UPDATE config SET ddns_host = $1, ddns_username = $2, ddns_password = $3",
|
||||
new_config.ddns_host,
|
||||
new_config.ddns_username,
|
||||
new_config.ddns_password
|
||||
)
|
||||
.execute(self.db.connect().await?.as_mut())
|
||||
.await?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub fn begin_periodic_updates(&self) {
|
||||
tokio::spawn({
|
||||
let ddns = self.clone();
|
||||
async move {
|
||||
loop {
|
||||
if let Err(e) = ddns.update_my_ip().await {
|
||||
if let Err(e) = ddns.update_ddns().await {
|
||||
error!("Dynamic DNS update error: {:?}", e);
|
||||
}
|
||||
tokio::time::sleep(Duration::from_secs(60 * 30)).await;
|
||||
|
|
|
@ -102,12 +102,17 @@ async fn get_settings(
|
|||
State(config_manager): State<config::Manager>,
|
||||
) -> Result<Json<dto::Settings>, APIError> {
|
||||
let settings = dto::Settings {
|
||||
album_art_pattern: config_manager.get_index_album_art_pattern().await,
|
||||
album_art_pattern: config_manager
|
||||
.get_index_album_art_pattern()
|
||||
.await
|
||||
.as_str()
|
||||
.to_owned(),
|
||||
reindex_every_n_seconds: config_manager.get_index_sleep_duration().await.as_secs(),
|
||||
ddns_update_url: config_manager
|
||||
.get_ddns_update_url()
|
||||
.await
|
||||
.unwrap_or_default(),
|
||||
.unwrap_or_default()
|
||||
.to_string(),
|
||||
};
|
||||
Ok(Json(settings))
|
||||
}
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
use serde::{Deserialize, Serialize};
|
||||
|
||||
use crate::app::{config, ddns, index, thumbnail};
|
||||
use crate::app::{config, index, thumbnail};
|
||||
use std::{convert::From, path::PathBuf};
|
||||
|
||||
#[derive(PartialEq, Eq, Debug, Serialize, Deserialize)]
|
||||
|
@ -95,33 +95,6 @@ pub struct UserUpdate {
|
|||
pub new_is_admin: Option<bool>,
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug, Deserialize, PartialEq, Eq, Serialize)]
|
||||
pub struct DDNSConfig {
|
||||
pub host: String,
|
||||
pub username: String,
|
||||
pub password: String,
|
||||
}
|
||||
|
||||
impl From<DDNSConfig> for ddns::Config {
|
||||
fn from(c: DDNSConfig) -> Self {
|
||||
Self {
|
||||
ddns_host: c.host,
|
||||
ddns_username: c.username,
|
||||
ddns_password: c.password,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl From<ddns::Config> for DDNSConfig {
|
||||
fn from(c: ddns::Config) -> Self {
|
||||
Self {
|
||||
host: c.ddns_host,
|
||||
username: c.ddns_username,
|
||||
password: c.ddns_password,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug, Deserialize, PartialEq, Eq, Serialize)]
|
||||
pub struct MountDir {
|
||||
pub source: PathBuf,
|
||||
|
|
|
@ -120,7 +120,8 @@ impl From<app::Error> for APIError {
|
|||
app::Error::AuthenticationSecretNotFound => APIError::Internal,
|
||||
app::Error::AuthenticationSecretInvalid => APIError::Internal,
|
||||
app::Error::MiscSettingsNotFound => APIError::Internal,
|
||||
app::Error::IndexAlbumArtPatternInvalid => APIError::Internal,
|
||||
app::Error::DDNSUpdateURLInvalid => APIError::InvalidDDNSURL,
|
||||
app::Error::IndexAlbumArtPatternInvalid => APIError::InvalidAlbumArtPattern,
|
||||
|
||||
app::Error::Toml(_) => APIError::Internal,
|
||||
app::Error::IndexDeserializationError => APIError::Internal,
|
||||
|
|
Loading…
Add table
Reference in a new issue