feat: implemented the /backup/create [POST] endpoint and the 'NginxConfig' backup configuration
This commit is contained in:
parent
3f12b92e9e
commit
05ee74eb7b
2
.env
2
.env
|
@ -1 +1,3 @@
|
||||||
DATABASE_URL="sqlite:store.db"
|
DATABASE_URL="sqlite:store.db"
|
||||||
|
NC_AW_BACKUP_PATH=test
|
||||||
|
NC_AW_HOST_PATH=/
|
|
@ -1,3 +1,4 @@
|
||||||
.idea
|
.idea
|
||||||
/target
|
/target
|
||||||
store.db
|
store.db
|
||||||
|
test/
|
|
@ -55,7 +55,7 @@ dependencies = [
|
||||||
"tokio",
|
"tokio",
|
||||||
"tokio-util",
|
"tokio-util",
|
||||||
"tracing",
|
"tracing",
|
||||||
"zstd",
|
"zstd 0.12.4",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
|
@ -218,12 +218,26 @@ dependencies = [
|
||||||
"actix-web",
|
"actix-web",
|
||||||
"actix-web-httpauth",
|
"actix-web-httpauth",
|
||||||
"anyhow",
|
"anyhow",
|
||||||
|
"chrono",
|
||||||
"env_logger",
|
"env_logger",
|
||||||
"log",
|
"log",
|
||||||
"serde",
|
"serde",
|
||||||
"serde_json",
|
"serde_json",
|
||||||
"sqlx",
|
"sqlx",
|
||||||
"tokio",
|
"tokio",
|
||||||
|
"walkdir",
|
||||||
|
"zip",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "aes"
|
||||||
|
version = "0.8.3"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "ac1f845298e95f983ff1944b728ae08b8cebab80d684f0a832ed0fc74dfa27e2"
|
||||||
|
dependencies = [
|
||||||
|
"cfg-if",
|
||||||
|
"cipher",
|
||||||
|
"cpufeatures",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
|
@ -404,6 +418,27 @@ dependencies = [
|
||||||
"bytes",
|
"bytes",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "bzip2"
|
||||||
|
version = "0.4.4"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "bdb116a6ef3f6c3698828873ad02c3014b3c85cadb88496095628e3ef1e347f8"
|
||||||
|
dependencies = [
|
||||||
|
"bzip2-sys",
|
||||||
|
"libc",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "bzip2-sys"
|
||||||
|
version = "0.1.11+1.0.8"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "736a955f3fa7875102d57c82b8cac37ec45224a07fd32d58f9f7a186b6cd4cdc"
|
||||||
|
dependencies = [
|
||||||
|
"cc",
|
||||||
|
"libc",
|
||||||
|
"pkg-config",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "cc"
|
name = "cc"
|
||||||
version = "1.0.83"
|
version = "1.0.83"
|
||||||
|
@ -428,16 +463,34 @@ checksum = "7f2c685bad3eb3d45a01354cedb7d5faa66194d1d58ba6e267a8de788f79db38"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"android-tzdata",
|
"android-tzdata",
|
||||||
"iana-time-zone",
|
"iana-time-zone",
|
||||||
|
"js-sys",
|
||||||
"num-traits",
|
"num-traits",
|
||||||
|
"wasm-bindgen",
|
||||||
"windows-targets",
|
"windows-targets",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "cipher"
|
||||||
|
version = "0.4.4"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "773f3b9af64447d2ce9850330c473515014aa235e6a783b02db81ff39e4a3dad"
|
||||||
|
dependencies = [
|
||||||
|
"crypto-common",
|
||||||
|
"inout",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "const-oid"
|
name = "const-oid"
|
||||||
version = "0.9.5"
|
version = "0.9.5"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "28c122c3980598d243d63d9a704629a2d748d101f278052ff068be5a4423ab6f"
|
checksum = "28c122c3980598d243d63d9a704629a2d748d101f278052ff068be5a4423ab6f"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "constant_time_eq"
|
||||||
|
version = "0.1.5"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "245097e9a4535ee1e3e3931fcfcd55a796a44c643e8596ff6566d68f09b87bbc"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "convert_case"
|
name = "convert_case"
|
||||||
version = "0.4.0"
|
version = "0.4.0"
|
||||||
|
@ -959,6 +1012,15 @@ dependencies = [
|
||||||
"hashbrown 0.14.2",
|
"hashbrown 0.14.2",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "inout"
|
||||||
|
version = "0.1.3"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "a0c10553d664a4d0bcff9f4215d0aac67a639cc68ef660840afe309b807bc9f5"
|
||||||
|
dependencies = [
|
||||||
|
"generic-array",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "is-terminal"
|
name = "is-terminal"
|
||||||
version = "0.4.9"
|
version = "0.4.9"
|
||||||
|
@ -1235,12 +1297,35 @@ dependencies = [
|
||||||
"windows-targets",
|
"windows-targets",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "password-hash"
|
||||||
|
version = "0.4.2"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "7676374caaee8a325c9e7a2ae557f216c5563a171d6997b0ef8a65af35147700"
|
||||||
|
dependencies = [
|
||||||
|
"base64ct",
|
||||||
|
"rand_core",
|
||||||
|
"subtle",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "paste"
|
name = "paste"
|
||||||
version = "1.0.14"
|
version = "1.0.14"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "de3145af08024dea9fa9914f381a17b8fc6034dfb00f3a84013f7ff43f29ed4c"
|
checksum = "de3145af08024dea9fa9914f381a17b8fc6034dfb00f3a84013f7ff43f29ed4c"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "pbkdf2"
|
||||||
|
version = "0.11.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "83a0692ec44e4cf1ef28ca317f14f8f07da2d95ec3fa01f86e4467b725e60917"
|
||||||
|
dependencies = [
|
||||||
|
"digest",
|
||||||
|
"hmac",
|
||||||
|
"password-hash",
|
||||||
|
"sha2",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "pem-rfc7468"
|
name = "pem-rfc7468"
|
||||||
version = "0.7.0"
|
version = "0.7.0"
|
||||||
|
@ -1447,6 +1532,15 @@ version = "1.0.15"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "1ad4cc8da4ef723ed60bced201181d83791ad433213d8c24efffda1eec85d741"
|
checksum = "1ad4cc8da4ef723ed60bced201181d83791ad433213d8c24efffda1eec85d741"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "same-file"
|
||||||
|
version = "1.0.6"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "93fc1dc3aaa9bfed95e02e6eadabb4baf7e3078b0bd1b4d7b6b0b68378900502"
|
||||||
|
dependencies = [
|
||||||
|
"winapi-util",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "scopeguard"
|
name = "scopeguard"
|
||||||
version = "1.2.0"
|
version = "1.2.0"
|
||||||
|
@ -2077,6 +2171,16 @@ version = "0.9.4"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "49874b5167b65d7193b8aba1567f5c7d93d001cafc34600cee003eda787e483f"
|
checksum = "49874b5167b65d7193b8aba1567f5c7d93d001cafc34600cee003eda787e483f"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "walkdir"
|
||||||
|
version = "2.4.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "d71d857dc86794ca4c280d616f7da00d2dbfd8cd788846559a6813e6aa4b54ee"
|
||||||
|
dependencies = [
|
||||||
|
"same-file",
|
||||||
|
"winapi-util",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "wasi"
|
name = "wasi"
|
||||||
version = "0.11.0+wasi-snapshot-preview1"
|
version = "0.11.0+wasi-snapshot-preview1"
|
||||||
|
@ -2275,13 +2379,52 @@ version = "1.6.0"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "2a0956f1ba7c7909bfb66c2e9e4124ab6f6482560f6628b5aaeba39207c9aad9"
|
checksum = "2a0956f1ba7c7909bfb66c2e9e4124ab6f6482560f6628b5aaeba39207c9aad9"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "zip"
|
||||||
|
version = "0.6.6"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "760394e246e4c28189f19d488c058bf16f564016aefac5d32bb1f3b51d5e9261"
|
||||||
|
dependencies = [
|
||||||
|
"aes",
|
||||||
|
"byteorder",
|
||||||
|
"bzip2",
|
||||||
|
"constant_time_eq",
|
||||||
|
"crc32fast",
|
||||||
|
"crossbeam-utils",
|
||||||
|
"flate2",
|
||||||
|
"hmac",
|
||||||
|
"pbkdf2",
|
||||||
|
"sha1",
|
||||||
|
"time",
|
||||||
|
"zstd 0.11.2+zstd.1.5.2",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "zstd"
|
||||||
|
version = "0.11.2+zstd.1.5.2"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "20cc960326ece64f010d2d2107537f26dc589a6573a316bd5b1dba685fa5fde4"
|
||||||
|
dependencies = [
|
||||||
|
"zstd-safe 5.0.2+zstd.1.5.2",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "zstd"
|
name = "zstd"
|
||||||
version = "0.12.4"
|
version = "0.12.4"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "1a27595e173641171fc74a1232b7b1c7a7cb6e18222c11e9dfb9888fa424c53c"
|
checksum = "1a27595e173641171fc74a1232b7b1c7a7cb6e18222c11e9dfb9888fa424c53c"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"zstd-safe",
|
"zstd-safe 6.0.6",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "zstd-safe"
|
||||||
|
version = "5.0.2+zstd.1.5.2"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "1d2a5585e04f9eea4b2a3d1eca508c4dee9592a89ef6f450c11719da0726f4db"
|
||||||
|
dependencies = [
|
||||||
|
"libc",
|
||||||
|
"zstd-sys",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
|
|
|
@ -12,6 +12,9 @@ serde = { version = "1.0.183", features = ["derive"] }
|
||||||
serde_json = "1.0.108"
|
serde_json = "1.0.108"
|
||||||
env_logger = "0.10"
|
env_logger = "0.10"
|
||||||
log = "0.4"
|
log = "0.4"
|
||||||
|
chrono = "0.4.31"
|
||||||
|
zip = "0.6.6"
|
||||||
|
walkdir = "2.4.0"
|
||||||
sqlx = { version = "0.7.1", features = ["runtime-tokio", "sqlite", "chrono"] }
|
sqlx = { version = "0.7.1", features = ["runtime-tokio", "sqlite", "chrono"] }
|
||||||
actix-web = "4"
|
actix-web = "4"
|
||||||
actix-web-httpauth = "0.8.0"
|
actix-web-httpauth = "0.8.0"
|
||||||
|
|
|
@ -3,21 +3,21 @@ use actix_web::{delete, get, post, web, HttpResponse, Responder};
|
||||||
use actix_web_httpauth::extractors::bearer::BearerAuth;
|
use actix_web_httpauth::extractors::bearer::BearerAuth;
|
||||||
use log::error;
|
use log::error;
|
||||||
|
|
||||||
// #[post("/backup/create")]
|
#[post("/backup/create")]
|
||||||
// async fn backup_create(
|
async fn backup_create(
|
||||||
// data: web::Data<State>,
|
data: web::Data<State>,
|
||||||
// body: web::Json<BackupCreateRequest>,
|
body: web::Json<BackupCreateRequest>,
|
||||||
// ) -> impl Responder {
|
) -> impl Responder {
|
||||||
// match handlers::backup_create(&data.pool, body.into_inner()).await {
|
match handlers::backup_create(&data.pool, data.backup_tx.clone(), body.into_inner()).await {
|
||||||
// Ok(resp) => match resp {
|
Ok(resp) => match resp {
|
||||||
// BackupCreateResponse::Success => HttpResponse::Ok().finish(),
|
BackupCreateResponse::Success => HttpResponse::Ok().finish(),
|
||||||
// },
|
},
|
||||||
// Err(e) => {
|
Err(e) => {
|
||||||
// error!("While handling /backup/create [POST] request: {e}");
|
error!("While handling /backup/create [POST] request: {e}");
|
||||||
// HttpResponse::InternalServerError().finish()
|
HttpResponse::InternalServerError().finish()
|
||||||
// }
|
}
|
||||||
// }
|
}
|
||||||
// }
|
}
|
||||||
|
|
||||||
#[post("/backup/preset")]
|
#[post("/backup/preset")]
|
||||||
async fn backup_preset_post(
|
async fn backup_preset_post(
|
||||||
|
|
|
@ -1,15 +1,28 @@
|
||||||
use crate::api::data::*;
|
use crate::api::data::*;
|
||||||
use crate::backend::backup;
|
use crate::backend::backup;
|
||||||
use anyhow::Result;
|
use anyhow::{Context, Result};
|
||||||
use log::warn;
|
use log::warn;
|
||||||
use sqlx::sqlite::SqlitePool;
|
use sqlx::sqlite::SqlitePool;
|
||||||
|
use tokio::sync::mpsc;
|
||||||
|
|
||||||
// pub async fn backup_create(
|
pub async fn backup_create(
|
||||||
// pool: &SqlitePool,
|
pool: &SqlitePool,
|
||||||
// request: BackupCreateRequest,
|
tx: mpsc::Sender<backup::Backup>,
|
||||||
// ) -> Result<BackupCreateResponse> {
|
request: BackupCreateRequest,
|
||||||
// Ok(BackupCreateResponse::Success)
|
) -> Result<BackupCreateResponse> {
|
||||||
// }
|
let config = match request {
|
||||||
|
BackupCreateRequest::Preset(preset) => backup::preset::Preset::load(pool, &preset)
|
||||||
|
.await?
|
||||||
|
.context(format!("Failed to look up preset '{preset}' !"))?
|
||||||
|
.config()
|
||||||
|
.to_owned(),
|
||||||
|
BackupCreateRequest::Config(cfg) => cfg.into(),
|
||||||
|
};
|
||||||
|
|
||||||
|
let backup = backup::Backup::new(pool, tx, config).await?;
|
||||||
|
|
||||||
|
Ok(BackupCreateResponse::Success)
|
||||||
|
}
|
||||||
|
|
||||||
pub async fn backup_preset_post(
|
pub async fn backup_preset_post(
|
||||||
pool: &SqlitePool,
|
pool: &SqlitePool,
|
||||||
|
|
|
@ -2,19 +2,27 @@ pub mod calls;
|
||||||
pub mod data;
|
pub mod data;
|
||||||
mod handlers;
|
mod handlers;
|
||||||
|
|
||||||
|
use crate::backend::backup;
|
||||||
use actix_web::{web, App, HttpServer};
|
use actix_web::{web, App, HttpServer};
|
||||||
use anyhow::Result;
|
use anyhow::Result;
|
||||||
use sqlx::sqlite::SqlitePool;
|
use sqlx::sqlite::SqlitePool;
|
||||||
|
use tokio::sync::mpsc;
|
||||||
|
|
||||||
pub struct State {
|
pub struct State {
|
||||||
pool: SqlitePool,
|
pool: SqlitePool,
|
||||||
token: String,
|
token: String,
|
||||||
|
backup_tx: mpsc::Sender<backup::Backup>,
|
||||||
}
|
}
|
||||||
|
|
||||||
pub async fn start(port: u16, pool: SqlitePool, token: String) -> Result<()> {
|
pub async fn start(
|
||||||
|
port: u16,
|
||||||
|
pool: SqlitePool,
|
||||||
|
token: String,
|
||||||
|
backup_tx: mpsc::Sender<backup::Backup>,
|
||||||
|
) -> Result<()> {
|
||||||
let _ = HttpServer::new(move || {
|
let _ = HttpServer::new(move || {
|
||||||
App::new()
|
App::new()
|
||||||
// .service(calls::backup_create)
|
.service(calls::backup_create)
|
||||||
.service(calls::backup_preset_post)
|
.service(calls::backup_preset_post)
|
||||||
.service(calls::backup_preset_get)
|
.service(calls::backup_preset_get)
|
||||||
.service(calls::backup_preset_id_get)
|
.service(calls::backup_preset_id_get)
|
||||||
|
@ -22,6 +30,7 @@ pub async fn start(port: u16, pool: SqlitePool, token: String) -> Result<()> {
|
||||||
.app_data(web::Data::new(State {
|
.app_data(web::Data::new(State {
|
||||||
pool: pool.clone(),
|
pool: pool.clone(),
|
||||||
token: token.to_owned(),
|
token: token.to_owned(),
|
||||||
|
backup_tx: backup_tx.to_owned(),
|
||||||
}))
|
}))
|
||||||
})
|
})
|
||||||
.bind(("0.0.0.0", port))?
|
.bind(("0.0.0.0", port))?
|
||||||
|
|
|
@ -1,12 +1,18 @@
|
||||||
pub mod preset;
|
pub mod preset;
|
||||||
|
pub mod worker;
|
||||||
|
|
||||||
use crate::api;
|
use crate::api;
|
||||||
|
use anyhow::{Context, Error, Result};
|
||||||
|
use chrono::NaiveDateTime;
|
||||||
use serde::{Deserialize, Serialize};
|
use serde::{Deserialize, Serialize};
|
||||||
|
use sqlx::SqlitePool;
|
||||||
|
use std::path::PathBuf;
|
||||||
|
use tokio::sync::mpsc;
|
||||||
|
|
||||||
#[derive(Serialize, Deserialize, Clone)]
|
#[derive(Debug, Serialize, Deserialize, Clone)]
|
||||||
struct DockerConfig {}
|
struct DockerConfig {}
|
||||||
|
|
||||||
#[derive(Serialize, Deserialize, Clone)]
|
#[derive(Debug, Serialize, Deserialize, Clone)]
|
||||||
pub struct Config {
|
pub struct Config {
|
||||||
nginx_config: bool,
|
nginx_config: bool,
|
||||||
mail_server: bool,
|
mail_server: bool,
|
||||||
|
@ -52,3 +58,109 @@ impl From<Config> for api::data::BackupConfig {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, Clone, Deserialize, Serialize)]
|
||||||
|
pub enum Status {
|
||||||
|
Pending,
|
||||||
|
Processing,
|
||||||
|
Finished,
|
||||||
|
Failed,
|
||||||
|
}
|
||||||
|
|
||||||
|
pub struct RawBackup {
|
||||||
|
id: i64,
|
||||||
|
time: i64,
|
||||||
|
config: String,
|
||||||
|
path: String,
|
||||||
|
status: String,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, Clone)]
|
||||||
|
pub struct Backup {
|
||||||
|
id: u64,
|
||||||
|
time: NaiveDateTime,
|
||||||
|
config: Config,
|
||||||
|
path: PathBuf,
|
||||||
|
status: Status,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl TryFrom<RawBackup> for Backup {
|
||||||
|
type Error = Error;
|
||||||
|
|
||||||
|
fn try_from(value: RawBackup) -> std::result::Result<Self, Self::Error> {
|
||||||
|
Ok(Self {
|
||||||
|
id: value.id as u64,
|
||||||
|
time: NaiveDateTime::from_timestamp_opt(value.time, 0)
|
||||||
|
.context("Failed to convert timestamp into NaiveTime")?,
|
||||||
|
config: serde_json::from_str(&value.config)?,
|
||||||
|
path: value.path.into(),
|
||||||
|
status: serde_json::from_str(&value.status)?,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Backup {
|
||||||
|
pub async fn new(pool: &SqlitePool, tx: mpsc::Sender<Backup>, config: Config) -> Result<Self> {
|
||||||
|
let time = chrono::Utc::now().naive_utc();
|
||||||
|
let timestamp = time.timestamp();
|
||||||
|
let config = serde_json::to_string(&config)?;
|
||||||
|
let status = serde_json::to_string(&Status::Pending)?;
|
||||||
|
|
||||||
|
let id = sqlx::query!(
|
||||||
|
r#"INSERT INTO Backups (time, config, path, status) VALUES($1, $2, $3, $4) RETURNING id;"#,
|
||||||
|
timestamp,
|
||||||
|
config,
|
||||||
|
"",
|
||||||
|
status
|
||||||
|
)
|
||||||
|
.fetch_one(pool)
|
||||||
|
.await?
|
||||||
|
.id;
|
||||||
|
|
||||||
|
let path = format!(
|
||||||
|
"{}/{}.bak",
|
||||||
|
env!("NC_AW_BACKUP_PATH"),
|
||||||
|
time.format("%H-%M-%S_%d-%m-%Y")
|
||||||
|
);
|
||||||
|
|
||||||
|
sqlx::query!(r#"UPDATE Backups SET path = $1 WHERE id = $2;"#, path, id)
|
||||||
|
.execute(pool)
|
||||||
|
.await?;
|
||||||
|
|
||||||
|
let backup = Self::load(pool, id)
|
||||||
|
.await?
|
||||||
|
.context("The just created backup record can't be found in the database!")?;
|
||||||
|
|
||||||
|
tx.send(backup.clone()).await?;
|
||||||
|
|
||||||
|
Ok(backup)
|
||||||
|
}
|
||||||
|
|
||||||
|
pub async fn load(pool: &SqlitePool, id: i64) -> Result<Option<Backup>> {
|
||||||
|
let query_result =
|
||||||
|
sqlx::query_as!(RawBackup, r#"SELECT * FROM Backups WHERE id = $1;"#, id)
|
||||||
|
.fetch_one(pool)
|
||||||
|
.await;
|
||||||
|
|
||||||
|
match query_result {
|
||||||
|
Ok(raw) => Ok(Some(raw.try_into()?)),
|
||||||
|
Err(sqlx::Error::RowNotFound) => Ok(None),
|
||||||
|
Err(e) => Err(Error::new(e)),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub async fn set_status(&self, pool: &SqlitePool, status: Status) -> Result<()> {
|
||||||
|
let status = serde_json::to_string(&status)?;
|
||||||
|
let id = self.id as i64;
|
||||||
|
|
||||||
|
sqlx::query!(
|
||||||
|
r#"UPDATE Backups SET status = $1 WHERE id = $2;"#,
|
||||||
|
status,
|
||||||
|
id
|
||||||
|
)
|
||||||
|
.execute(pool)
|
||||||
|
.await?;
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
|
@ -87,6 +87,10 @@ impl Preset {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn config(&self) -> &backup::Config {
|
||||||
|
&self.config
|
||||||
|
}
|
||||||
|
|
||||||
pub async fn delete(&self, pool: &SqlitePool) -> Result<()> {
|
pub async fn delete(&self, pool: &SqlitePool) -> Result<()> {
|
||||||
sqlx::query!(r#"DELETE FROM Presets WHERE id = $1;"#, self.id)
|
sqlx::query!(r#"DELETE FROM Presets WHERE id = $1;"#, self.id)
|
||||||
.execute(pool)
|
.execute(pool)
|
||||||
|
|
|
@ -0,0 +1,126 @@
|
||||||
|
use crate::backend::backup;
|
||||||
|
use anyhow::{bail, Context, Result};
|
||||||
|
use log::{error, info};
|
||||||
|
use sqlx::SqlitePool;
|
||||||
|
use std::fs::File;
|
||||||
|
use std::io::{copy, BufReader, Write};
|
||||||
|
use std::path::Path;
|
||||||
|
use tokio::sync::mpsc;
|
||||||
|
use walkdir::{DirEntry, WalkDir};
|
||||||
|
use zip::{write::FileOptions, CompressionMethod, ZipWriter};
|
||||||
|
|
||||||
|
fn add_dir_to_archive(
|
||||||
|
archive: &mut ZipWriter<File>,
|
||||||
|
dir: &Path,
|
||||||
|
options: FileOptions,
|
||||||
|
) -> Result<()> {
|
||||||
|
for entry in WalkDir::new(dir)
|
||||||
|
.follow_links(true)
|
||||||
|
.into_iter()
|
||||||
|
.filter_map(|e| e.ok())
|
||||||
|
{
|
||||||
|
let fname = entry.file_name();
|
||||||
|
let ftype = entry.file_type();
|
||||||
|
|
||||||
|
if ftype.is_dir() {
|
||||||
|
archive.add_directory(entry.path().to_string_lossy(), options)?;
|
||||||
|
} else if ftype.is_file() {
|
||||||
|
archive.start_file(entry.path().to_string_lossy(), options)?;
|
||||||
|
let mut reader = BufReader::new(File::open(entry.path())?);
|
||||||
|
copy(&mut reader, archive)?;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
fn perform_backup(backup: backup::Backup, pool: SqlitePool) -> Result<()> {
|
||||||
|
let host = env!("NC_AW_HOST_PATH");
|
||||||
|
let archive_file = File::create(&backup.path)?;
|
||||||
|
let mut archive = ZipWriter::new(archive_file);
|
||||||
|
let options = FileOptions::default()
|
||||||
|
.compression_method(CompressionMethod::Bzip2)
|
||||||
|
.compression_level(Some(9));
|
||||||
|
|
||||||
|
info!("Starting backup...");
|
||||||
|
|
||||||
|
if backup.config.nginx_config {
|
||||||
|
info!("Starting NGINX config backup...");
|
||||||
|
add_dir_to_archive(
|
||||||
|
&mut archive,
|
||||||
|
&Path::new(&format!("{host}/etc/nginx")),
|
||||||
|
options,
|
||||||
|
)?;
|
||||||
|
}
|
||||||
|
|
||||||
|
if backup.config.mail_server {
|
||||||
|
info!("Starting mail server backup...");
|
||||||
|
bail!("The config option 'mail_server' is not implemented yet!");
|
||||||
|
}
|
||||||
|
|
||||||
|
if let Some(cfg) = &backup.config.docker {
|
||||||
|
info!("Starting docker backup...");
|
||||||
|
bail!("The config option 'docker' is not implemented yet!");
|
||||||
|
}
|
||||||
|
|
||||||
|
archive.finish()?;
|
||||||
|
|
||||||
|
info!("Finished backup.");
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
pub async fn start(pool: SqlitePool) -> Result<mpsc::Sender<backup::Backup>> {
|
||||||
|
let (tx, mut rx) = mpsc::channel(128);
|
||||||
|
|
||||||
|
tokio::task::spawn(async move {
|
||||||
|
loop {
|
||||||
|
let backup: backup::Backup = rx
|
||||||
|
.recv()
|
||||||
|
.await
|
||||||
|
.expect("Failed to read from the backup workers internal MPSC channel!");
|
||||||
|
|
||||||
|
let cloned_backup = backup.clone();
|
||||||
|
|
||||||
|
let result: Result<()> = async {
|
||||||
|
backup
|
||||||
|
.set_status(&pool, backup::Status::Processing)
|
||||||
|
.await
|
||||||
|
.expect("Failed to set backup status!");
|
||||||
|
|
||||||
|
let cloned_backup = backup.clone();
|
||||||
|
let cloned_pool = pool.clone();
|
||||||
|
match tokio::task::spawn_blocking(|| perform_backup(cloned_backup, cloned_pool))
|
||||||
|
.await?
|
||||||
|
{
|
||||||
|
Ok(_) => (),
|
||||||
|
Err(e) => {
|
||||||
|
tokio::fs::remove_file(backup.path).await?;
|
||||||
|
return Err(e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
backup
|
||||||
|
.set_status(&pool, backup::Status::Finished)
|
||||||
|
.await
|
||||||
|
.context("Failed to set backup status!")?;
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
.await;
|
||||||
|
|
||||||
|
match result {
|
||||||
|
Ok(_) => (),
|
||||||
|
Err(e) => {
|
||||||
|
match cloned_backup
|
||||||
|
.set_status(&pool, backup::Status::Failed)
|
||||||
|
.await
|
||||||
|
{
|
||||||
|
Ok(_) => (),
|
||||||
|
Err(e) => error!("Failed to set backup failure status: {e}"),
|
||||||
|
}
|
||||||
|
error!("The backup worker thread failed: {e}");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
Ok(tx)
|
||||||
|
}
|
|
@ -16,5 +16,24 @@ pub async fn prepare(pool: &SqlitePool) -> Result<()> {
|
||||||
)
|
)
|
||||||
.execute(pool)
|
.execute(pool)
|
||||||
.await?;
|
.await?;
|
||||||
|
|
||||||
|
sqlx::query!(
|
||||||
|
r#"
|
||||||
|
CREATE TABLE IF NOT EXISTS Backups (
|
||||||
|
id INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL,
|
||||||
|
time INTEGER NOT NULL,
|
||||||
|
config TEXT NOT NULL,
|
||||||
|
path TEXT NOT NULL,
|
||||||
|
status TEXT NOT NULL
|
||||||
|
);
|
||||||
|
"#
|
||||||
|
)
|
||||||
|
.execute(pool)
|
||||||
|
.await?;
|
||||||
|
|
||||||
|
if !tokio::fs::try_exists(env!("NC_AW_BACKUP_PATH")).await? {
|
||||||
|
tokio::fs::create_dir(env!("NC_AW_BACKUP_PATH")).await?;
|
||||||
|
}
|
||||||
|
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,6 +1,7 @@
|
||||||
mod api;
|
mod api;
|
||||||
mod backend;
|
mod backend;
|
||||||
|
|
||||||
|
use crate::backend::backup;
|
||||||
use anyhow::Result;
|
use anyhow::Result;
|
||||||
use log::info;
|
use log::info;
|
||||||
use sqlx::SqlitePool;
|
use sqlx::SqlitePool;
|
||||||
|
@ -24,7 +25,9 @@ async fn main() -> Result<()> {
|
||||||
.await?;
|
.await?;
|
||||||
|
|
||||||
backend::prepare(&pool).await?;
|
backend::prepare(&pool).await?;
|
||||||
api::start(6969, pool, token.to_owned()).await?;
|
let backup_tx = backup::worker::start(pool.clone()).await?;
|
||||||
|
|
||||||
|
api::start(6969, pool, token.to_owned(), backup_tx).await?;
|
||||||
|
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in New Issue