summaryrefslogtreecommitdiffstats
path: root/backup
diff options
context:
space:
mode:
Diffstat (limited to 'backup')
-rw-r--r--backup/Cargo.toml14
-rw-r--r--backup/src/main.rs148
2 files changed, 162 insertions, 0 deletions
diff --git a/backup/Cargo.toml b/backup/Cargo.toml
new file mode 100644
index 0000000..699fe53
--- /dev/null
+++ b/backup/Cargo.toml
@@ -0,0 +1,14 @@
+[package]
+name = "backup"
+version = "0.1.0"
+edition = "2021"
+
+[dependencies]
+serde = { version = "1.0", features = ["derive"] }
+serde_json = "1.0"
+walkdir = "2.4.0"
+tar = "0.4"
+zstd = "0.13"
+chrono = "0.4"
+regex = "1"
+duct = "0.13" # für shell-like commands \ No newline at end of file
diff --git a/backup/src/main.rs b/backup/src/main.rs
new file mode 100644
index 0000000..d2210af
--- /dev/null
+++ b/backup/src/main.rs
@@ -0,0 +1,148 @@
+use chrono::Local;
+use duct::cmd;
+use regex::Regex;
+use serde::Deserialize;
+use std::{
+ env,
+ error::Error,
+ fs::{self, File},
+ io::{BufRead, BufReader},
+ path::{Path, PathBuf},
+ process::Command,
+};
+
+#[derive(Debug, Deserialize)]
+struct BackupConfig {
+ backup_dir: String,
+ rotate_dir: String,
+ timestamp_file: String,
+ source_file: String,
+ exclude_file: String,
+}
+
+#[derive(Debug, Deserialize)]
+struct Config {
+ backup: BackupConfig,
+}
+
+fn read_lines<P: AsRef<Path>>(path: P) -> Result<Vec<String>, Box<dyn Error>> {
+ let file = File::open(path)?;
+ let reader = BufReader::new(file);
+ Ok(reader.lines().filter_map(Result::ok).filter(|l| !l.trim().is_empty()).collect())
+}
+
+fn get_backup_number(backup_dir: &Path) -> Result<u32, Box<dyn Error>> {
+ let mut highest = 0;
+ let pattern = Regex::new(r"backup-(\d{2})\.tar\.zst")?;
+
+ for entry in fs::read_dir(backup_dir)? {
+ let path = entry?.path();
+ if let Some(fname) = path.file_name().and_then(|n| n.to_str()) {
+ if let Some(caps) = pattern.captures(fname) {
+ if let Ok(num) = caps[1].parse::<u32>() {
+ if num > highest {
+ highest = num;
+ }
+ }
+ }
+ }
+ }
+
+ Ok(highest)
+}
+
+fn main() -> Result<(), Box<dyn Error>> {
+ let base_dir = PathBuf::from("/root/scripts");
+ let config_path = base_dir.join("config.json");
+ let config_str = fs::read_to_string(&config_path)?;
+ let config: Config = serde_json::from_str(&config_str)?;
+
+ let backup_dir = Path::new(&config.backup.backup_dir);
+ let rotate_dir = Path::new(&config.backup.rotate_dir);
+ let timestamp_file = Path::new(&config.backup.timestamp_file);
+ let source_list = read_lines(&config.backup.source_file)?;
+ let exclude_list = read_lines(&config.backup.exclude_file)?;
+
+ fs::create_dir_all(backup_dir)?;
+
+ let mut source_paths: Vec<String> = vec![];
+ for s in &source_list {
+ if Path::new(s).exists() {
+ source_paths.push(s.clone());
+ } else {
+ eprintln!("Warnung: '{}' existiert nicht", s);
+ }
+ }
+
+ let mut exclude_args: Vec<String> = vec![];
+ for e in &exclude_list {
+ if Path::new(e).exists() {
+ exclude_args.push(format!("--exclude={}", e));
+ } else {
+ eprintln!("Warnung: '{}' existiert nicht", e);
+ }
+ }
+
+ let mut backup_nr = get_backup_number(backup_dir)? + 1;
+ if backup_nr > 30 {
+ backup_nr = 1;
+
+ let now = Local::now();
+ let rotate_subdir = rotate_dir.join(format!("{}-{}", now.format("%Y-%m-%d"), now.format("%H-%M-%S-%3f")));
+ fs::create_dir_all(&rotate_subdir)?;
+
+ for entry in fs::read_dir(backup_dir)? {
+ let entry = entry?;
+ let dest = rotate_subdir.join(entry.file_name());
+ fs::rename(entry.path(), dest)?;
+ }
+
+ // pg_dumpall via duct (ähnlich wie Bash pipe)
+ let dump1 = rotate_subdir.join("backup_mastodon-db-1.sql.zst");
+ let dump2 = rotate_subdir.join("backup_db.sql.zst");
+
+ cmd!("docker", "exec", "mastodon-db-1", "pg_dumpall", "-U", "postgres")
+ .pipe(cmd!("zstd", "-9"))
+ .stdout_file(File::create(dump1)?)
+ .run()?;
+
+ cmd!("pg_dumpall", "-U", "postgres")
+ .pipe(cmd!("zstd", "-9"))
+ .stdout_file(File::create(dump2)?)
+ .run()?;
+ }
+
+ let backup_filename = format!("backup-{:02}.tar.zst", backup_nr);
+ let backup_path = backup_dir.join(&backup_filename);
+
+ let mut tar_cmd = Command::new("tar");
+ tar_cmd
+ .arg("-cvp")
+ .arg("-g")
+ .arg(timestamp_file)
+ .arg("-f")
+ .arg(&backup_path)
+ .arg("-I")
+ .arg("zstd -9 -T1");
+
+ for excl in &exclude_args {
+ tar_cmd.arg(excl);
+ }
+ for src in &source_paths {
+ tar_cmd.arg(src);
+ }
+
+ let tar_status = tar_cmd.status()?;
+ if !tar_status.success() {
+ return Err("Fehler beim Erstellen des Tar-Backups".into());
+ }
+
+ // Log-Dateien behandeln
+ cmd!("find", "/var/log", "-type", "f", "-name", "*.log", "-exec", "truncate", "-s", "0", "{}", ";")
+ .run()?;
+ cmd!("find", "/var/log", "-type", "f", "-name", "*.gz", "-exec", "rm", "-f", "{}", ";")
+ .run()?;
+
+ println!("Backup erfolgreich als {}", backup_path.display());
+ Ok(())
+}