86 lines
2.8 KiB
Rust
86 lines
2.8 KiB
Rust
use crate::{
|
|
db::Dialect,
|
|
error::{AppError, AppResult},
|
|
routes,
|
|
state::AppState,
|
|
};
|
|
use axum::{
|
|
body::Body,
|
|
extract::State,
|
|
http::{HeaderMap, HeaderValue, header},
|
|
response::Response,
|
|
};
|
|
use axum_extra::extract::cookie::CookieJar;
|
|
use std::{process::Stdio, sync::Arc};
|
|
use tokio::process::Command;
|
|
|
|
pub async fn download(State(state): State<Arc<AppState>>, jar: CookieJar) -> AppResult<Response> {
|
|
let s = routes::session(&state, &jar)?;
|
|
s.require_global()?;
|
|
if !state.config.features.backup {
|
|
return Err(AppError::forbidden("Backup is disabled"));
|
|
}
|
|
if state.db.dialect() == Dialect::Sqlite {
|
|
let path = state
|
|
.config
|
|
.database
|
|
.path
|
|
.as_deref()
|
|
.unwrap_or(&state.config.database.name);
|
|
let bytes = tokio::fs::read(path).await?;
|
|
return response(bytes, "postfixadmin.sqlite");
|
|
}
|
|
let mut cmd = if state.db.dialect() == Dialect::Postgres {
|
|
let mut c = Command::new(&state.config.backup.pg_dump);
|
|
c.arg("--no-owner")
|
|
.arg("--no-privileges")
|
|
.arg("--host")
|
|
.arg(&state.config.database.host)
|
|
.arg("--port")
|
|
.arg(state.config.database.port.to_string())
|
|
.arg("--username")
|
|
.arg(&state.config.database.user)
|
|
.arg(&state.config.database.name);
|
|
c.env("PGPASSWORD", &state.config.database.password);
|
|
c
|
|
} else {
|
|
let mut c = Command::new(&state.config.backup.mysql_dump);
|
|
c.arg("--single-transaction")
|
|
.arg("--skip-lock-tables")
|
|
.arg("--host")
|
|
.arg(&state.config.database.host)
|
|
.arg("--port")
|
|
.arg(state.config.database.port.to_string())
|
|
.arg("--user")
|
|
.arg(&state.config.database.user)
|
|
.arg(&state.config.database.name);
|
|
c.env("MYSQL_PWD", &state.config.database.password);
|
|
c
|
|
};
|
|
let out = cmd
|
|
.stdin(Stdio::null())
|
|
.stdout(Stdio::piped())
|
|
.stderr(Stdio::piped())
|
|
.output()
|
|
.await?;
|
|
if !out.status.success() {
|
|
tracing::error!(stderr=%String::from_utf8_lossy(&out.stderr),"database dump failed");
|
|
return Err(AppError::config("Database dump command failed"));
|
|
}
|
|
response(out.stdout, "postfixadmin.sql")
|
|
}
|
|
fn response(bytes: Vec<u8>, name: &str) -> AppResult<Response> {
|
|
let mut headers = HeaderMap::new();
|
|
headers.insert(
|
|
header::CONTENT_TYPE,
|
|
HeaderValue::from_static("application/octet-stream"),
|
|
);
|
|
headers.insert(
|
|
header::CONTENT_DISPOSITION,
|
|
HeaderValue::from_str(&format!("attachment; filename=\"{name}\""))
|
|
.map_err(|_| AppError::config("invalid backup filename"))?,
|
|
);
|
|
Ok((headers, Body::from(bytes)).into_response())
|
|
}
|
|
use axum::response::IntoResponse;
|