MrZaKaRiAGit

Virtualmin File Backup

A hardened, point-and-click Webmin & Virtualmin module for backing up every website on your server Auto-discovers every public_html across your domains and sub-domains, archives each one into its own .zip named after the domain, with integrity checksums, encryption,…

Created: 6/23/2026Last pushed: 6/23/2026
Virtualmin File Backup

Overview

A hardened, point-and-click Webmin & Virtualmin module for backing up every website on your server Auto-discovers every public_html across your domains and sub-domains, archives each one into its own .zip named after the domain, with integrity checksums, encryption,…

Tags & Topics
CLIPerl

🗂️ Virtualmin File Backup

A hardened, point-and-click Webmin & Virtualmin module for backing up every website on your server

Auto-discovers every public_html across your domains and sub-domains, archives each one into its own .zip named after the domain, with integrity checksums, encryption, retention, cloud upload (S3 · Dropbox · Google Drive) and scheduled jobs. It detects what's already on your system so you click instead of type.

License: GPL v2 Webmin module Virtualmin Version Hardened


Why another backup tool? Because the usual answer — a zip -r loop over /home/*/public_html with the cloud key in the script and a find … -exec rm -rf for cleanup — runs as root, leaks credentials into ps, silently ships a half-written archive when a site fails, and can wipe the wrong directory. This module fixes all of that and gives you a UI on top: it discovers your web-roots for you, builds every command from constant flags plus individually escaped values, never puts a secret on a command line, and only ever deletes its own timestamped backup sets.

Live under Webmin / Virtualmin? This fits right in — under Servers → File Backup.

✨ Highlights

  • 🧲 Finds your sites for you — discovers every public_html across domains and sub-domains (Virtualmin, cPanel and Plesk layouts), and uses virtualmin list-domains for exact domain names when available.
  • 🗜️ One .zip per domain — each web-root becomes domain.zip / sub.domain.zip, stored relative to the web-root for a clean restore.
  • ☁️ Cloud built in — upload the whole set to Amazon S3 (and S3-compatible), Dropbox, or Google Drive via rclone, with safe copy or mirroring sync. Picks up your existing rclone remotes.
  • 🔒 Secrets never touch argv — GnuPG passphrases via --passphrase-fd; rclone keys/tokens written to a 0600 config we own, never where ps can see them.
  • 🔐 Safe by construction — SHA-256 sums, optional GnuPG encryption at rest, and retention that can only delete its own backup sets — no rm -rf, no find -exec.
  • Scheduled — cron jobs created through Webmin, with an "all web-roots" mode so newly added domains are backed up automatically.
  • 👥 Granular ACLs & audited — per-user permissions, every change POST-only and written to the Webmin Actions Log (never logging a secret).

📸 Screenshots

Drop your own PNGs into docs/screenshots/ — they'll render here.

Dashboard Discovered web-roots Back up & schedule
Dashboard Web-roots Backup

🚀 Features

Discovery — detected, not typed

  • Every public_html found automatically across domains & sub-domains
  • Virtualmin / cPanel (/home/*/…) and Plesk (/var/www/vhosts/…) layouts
  • Exact domain names from virtualmin list-domains when present
  • Configurable scan patterns and extra paths; destination directory browser

Backups

  • One .zip per web-root, named after the domain/sub-domain
  • Timestamped sets, manifest.json + SHA256SUMS for every set
  • Configurable zip compression level and exclude patterns
  • Optional GnuPG AES-256 encryption at rest
  • Empty web-roots are skipped, not failed

Retention

  • Keep at most N sets and/or delete sets older than D days
  • Deletes only timestamp-named sets it created — symlink-safe

Cloud upload (rclone)

  • Amazon S3 & S3-compatible (MinIO, Wasabi, Ceph, DO Spaces…)
  • Dropbox · Google Drive (paste an OAuth token)
  • Import remotes already in the server's rclone.conf
  • The whole set goes into one folder per run — safe copy or sync

Browse · verify · restore

  • List sets, verify checksums, download, delete
  • Restore a domain's .zip back into its web-root (with optional clean-restore)

Scheduling

  • Cron jobs via Webmin; non-zero exit on failure so cron emails you
  • "All web-roots" mode auto-includes new domains; standalone filebackup-run.pl

Access control

  • Per-user: view · backup · restore · remotes · schedule · delete

🔁 Replaces the classic backup script

The old way The problem This module
for d in /home/*/public_html; do zip … Misses sub-domains & non-standard layouts Discovers every public_html, names each by its real domain
RCLONE_KEY="…" in the script Secret in plaintext and in ps Keys/tokens in a 0600 config we own — never on argv
zip -r out.zip $d (ignore errors) A failed archive still "succeeds" Archive → verify exit + non-empty → checksum; empty sites skipped cleanly
find $OUT -mtime +30 -exec rm -rf {} \; Can wipe the wrong directory Deletes only timestamp-named sets, never follows symlinks
rclone sync to one fixed remote sync can delete remote data Per-job copy (default) or sync, across S3 / Dropbox / Drive

🔒 Security model

This is the whole point of the module.

Guarantee How
No secret on a command line GnuPG via --passphrase-fd; rclone keys/tokens written to a 0600 config we own — never on argv.
No shell injection Web-root discovery uses Perl's File::Glob (no shell); every value from a form, the config or a schedule is single-quote-escaped (sq()); only constant flags stay literal.
Targets are allow-listed A backup or restore only ever touches a public_html discovery currently reports, or a path the admin explicitly configured — never an arbitrary path. Restore refuses unknown targets and symlinked web-roots.
Destruction stays in its lane Retention/delete only ever touches timestamp-named child directories of the backup folder; lstat rejects symlinks; no rm -rf, no find -exec.
POST-only, ACL-gated, audited All changes go through one dispatcher (act.cgi), reached only by POST, permission-checked, and logged with webmin_log — secrets are never logged or reflected into a page.
Integrity SHA-256 for every archive; optional GnuPG AES-256 encryption at rest.

📦 Requirements

  • A host running Webmin (or Virtualmin)
  • zip (to create backups) and unzip (to restore)
  • gpg for optional encryption
  • rclone for cloud upload (optional)
  • Perl JSON::PP (ships with Perl 5.14+)

⚙️ Installation

  1. Download filebackup.wbm.gz from the latest release.
  2. In Webmin go to Webmin Configuration → Webmin Modules → From uploaded file, choose the package, and click Install Module.
  3. Open it under Servers → File Backup.

Build it yourself from a checkout:

tar -czf filebackup.wbm.gz filebackup     # the top-level dir in the archive must be "filebackup"

🧭 Quick start

  1. Open File Backup — the dashboard lists every public_html it discovered.
  2. Back up now — tick the domains/sub-domains to archive (or all of them), choose the destination (Browse), compression, retention, encryption, and a cloud remote.
  3. Cloud remotes → Detected remotes → Import to reuse an existing rclone setup, or add an S3 / Dropbox / Drive remote.
  4. Schedules — add a cron job; leave it on "all web-roots" and new domains are picked up automatically.
  5. Browse a set to verify checksums, download a .zip, or Restore it.

🗂️ How backups are laid out

<backup dir>/
  2026-06-23_031500_nightly/        # timestamp[_label]
    example.com.zip                 # primary domain
    blog.example.com.zip            # sub-domain
    shop.example.com.zip            # sub-domain
    SHA256SUMS
    manifest.json

☁️ Cloud remotes

  • Amazon S3 / S3-compatible — fully non-interactive: access key, secret, region (endpoint for non-AWS).
  • Dropbox / Google Drive — run rclone authorize "dropbox" (or "drive") on any machine with a browser and paste the JSON token.
  • Import — any remote already in root's ~/.config/rclone/rclone.conf is offered for one-click import (keys/tokens come along).

Keys and tokens are written only to the module's own 0600 config — never passed to rclone on the command line.

👥 Access control

Under Webmin Users → user → File Backup, grant or deny each capability independently: view · backup · restore · remotes · schedule · delete. Default is full access (you're the admin); tighten per user as needed.

🗂️ Module layout

File Purpose
filebackup-lib.pl Hardened core: discovery, shell quoting, validation, zip/unzip, encryption, checksums, retention, rclone, cron
index.cgi Dashboard: status, discovered web-roots, quick backup
backup.cgi Choose web-roots / path / compression / retention / cloud and run
remotes.cgi Cloud remotes: S3 / Dropbox / Drive + import from system rclone
browse.cgi · restore.cgi Browse / verify / download / delete · restore a domain into its web-root
schedule.cgi · filebackup-run.pl Cron scheduling · unattended runner
act.cgi The single POST action dispatcher (ACL-gated, audited)
acl_security.pl · defaultacl · install_check.pl Per-user access control · auto-detection

🤝 Contributing

Issues and pull requests welcome. Every .cgi/.pl file is validated with perl -c in CI; changes to command construction must keep the sq() + allow-list + secrets-off-argv discipline.

📜 License

Copyright © 2026 MrZaKaRiA.

This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License version 2 as published by the Free Software Foundation. It is distributed without any warranty — see the license for details.


Built for the Webmin & Virtualmin community.

Repository Stats

Stars
0
Forks
0
Watchers
0
Size
75 KB
Primary Language
Perl
LicenseGPL-2.0
Issues0 Open