first commit
This commit is contained in:
commit
2eb8aac027
91 files changed
+20139
No files matched your search
@@ -0,0 +1,85 @@
|
||||
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;
|
||||
Reference in new issue
Block a user