GNIZA
cPanel Backup, Restore & Disaster Recovery tool.
Uses pkgacct --nocompress --skiphomedir for account backups, gzips SQL files individually, and transfers everything (including homedirs) to remote destinations with incremental snapshots. Supports three remote types: SSH (rsync with hardlink-based --link-dest), Amazon S3 / S3-compatible (via rclone), and Google Drive (via rclone). Supports multiple remote destinations with independent schedules and retention policies.
Repository
| URL | |
|---|---|
| Git (SSH) | gitea:shukivaknin/gniza4cp.git (uses Host gitea from ~/.ssh/config) |
| Git (HTTPS) | https://git.linux-hosting.co.il/shukivaknin/gniza4cp.git |
| Web UI | https://git.linux-hosting.co.il/shukivaknin/gniza4cp/ |
Installation
From a clone:
git clone https://git.linux-hosting.co.il/shukivaknin/gniza4cp.git
cd gniza4cp
sudo bash scripts/install.sh
To uninstall:
sudo bash /usr/local/gniza4cp/uninstall.sh # from installed copy
# or
sudo bash scripts/uninstall.sh # from repo clone
The uninstall script removes the CLI, symlink, cron entries, and WHM plugin. Config (/etc/gniza4cp/) and logs (/var/log/gniza4cp/) are preserved — remove manually if desired.
Quick Start
# Configure via WHM → GNIZA Backup Manager (setup wizard)
# Or copy example configs manually:
sudo cp /etc/gniza4cp/gniza4cp.conf.example /etc/gniza4cp/gniza4cp.conf
sudo cp /etc/gniza4cp/remote.conf.example /etc/gniza4cp/remotes.d/nas.conf
# Test backup (dry run)
sudo gniza4cp backup --dry-run
# Run backup
sudo gniza4cp backup
# Back up to specific remotes
sudo gniza4cp backup --remote=nas,offsite
Commands
gniza4cp backup [--account=NAME] [--remote=NAME[,NAME2]] [--skip-suspended] [--dry-run]
gniza4cp restore account <name> --remote=NAME [--timestamp=TS] [--force]
gniza4cp restore files <name> --remote=NAME [--path=subpath] [--timestamp=TS]
gniza4cp restore database <name> <dbname> --remote=NAME [--timestamp=TS]
gniza4cp restore mailbox <name> <email@domain> --remote=NAME [--timestamp=TS]
gniza4cp restore server --remote=NAME [--timestamp=TS]
gniza4cp list [--account=NAME] [--remote=NAME]
gniza4cp verify [--account=NAME] [--remote=NAME]
gniza4cp status
gniza4cp remote list
gniza4cp remote delete <name>
gniza4cp schedule add <name>
gniza4cp schedule delete <name>
gniza4cp schedule list
gniza4cp schedule install
gniza4cp schedule show
gniza4cp schedule remove
gniza4cp version
gniza4cp help
Global Options
| Option | Description |
|---|---|
--config=PATH |
Alternate config file (default: /etc/gniza4cp/gniza4cp.conf) |
--remote=NAME[,NAME2] |
Target specific remote(s) from /etc/gniza4cp/remotes.d/ (comma-separated) |
--debug |
Enable debug logging |
Configuration
Main Config
File: /etc/gniza4cp/gniza4cp.conf
Controls local settings (accounts, logging, notifications). Remote destinations are configured in /etc/gniza4cp/remotes.d/.
# Local Settings
TEMP_DIR="/usr/local/gniza4cp/workdir" # Working dir for pkgacct output
INCLUDE_ACCOUNTS="" # Comma-separated, empty = all
EXCLUDE_ACCOUNTS="nobody" # Comma-separated exclusions
# Logging
LOG_DIR="/var/log/gniza4cp"
LOG_LEVEL="info" # debug, info, warn, error
LOG_RETAIN=90 # Days to keep log files
# Notifications
NOTIFY_EMAIL="" # Email for notifications
NOTIFY_ON="failure" # always, failure, never
# Advanced
LOCK_FILE="/var/run/gniza4cp.lock"
SSH_TIMEOUT=30
SSH_RETRIES=3
RSYNC_EXTRA_OPTS=""
See etc/gniza4cp.conf.example for the full template.
Remote Destinations
Back up to one or more destinations with independent retention policies and bandwidth limits. Supports SSH, Amazon S3 (and S3-compatible services like MinIO, Wasabi, Backblaze B2), and Google Drive. Remote destinations are configured as individual files in /etc/gniza4cp/remotes.d/.
Setup
# Configure via WHM → Remotes, or copy the template manually
sudo cp /etc/gniza4cp/remote.conf.example /etc/gniza4cp/remotes.d/nas.conf
sudo vi /etc/gniza4cp/remotes.d/nas.conf
# List configured remotes
sudo gniza4cp remote list
# Delete a remote
sudo gniza4cp remote delete nas
Remote Config Format
Each file in /etc/gniza4cp/remotes.d/<name>.conf:
# Remote type: "ssh" (default), "s3", or "gdrive"
REMOTE_TYPE="ssh"
# ── SSH Remote ──────────────────────────────────
REMOTE_HOST="192.168.1.100" # Required (SSH only)
REMOTE_PORT=22
REMOTE_USER="root"
REMOTE_AUTH_METHOD="key" # "key" or "password"
REMOTE_KEY="/root/.ssh/id_rsa" # Required for key auth
REMOTE_PASSWORD="" # Required for password auth (needs sshpass)
# ── S3 Remote ───────────────────────────────────
S3_ACCESS_KEY_ID="" # Required (S3 only)
S3_SECRET_ACCESS_KEY="" # Required (S3 only)
S3_REGION="us-east-1"
S3_ENDPOINT="" # For S3-compatible services
S3_BUCKET="" # Required (S3 only)
# ── Google Drive Remote ─────────────────────────
GDRIVE_SERVICE_ACCOUNT_FILE="" # Required (GDrive only)
GDRIVE_ROOT_FOLDER_ID="" # Optional
# ── Common ──────────────────────────────────────
REMOTE_BASE="/backups"
BWLIMIT=0
RETENTION_COUNT=30
RSYNC_EXTRA_OPTS="" # SSH only
S3 and Google Drive remotes require rclone installed on the server.
See etc/remote.conf.example for the full template.
Usage
Without --remote, backup/list/verify operate on all configured remotes. Restore always requires --remote to specify the source.
# Back up to all remotes
sudo gniza4cp backup
# Back up to specific remote(s)
sudo gniza4cp backup --remote=nas
sudo gniza4cp backup --remote=nas,offsite
# List snapshots on a specific remote
sudo gniza4cp list --remote=offsite
# Restore requires explicit remote
sudo gniza4cp restore account johndoe --remote=nas
Schedules
Schedules are decoupled from remotes. Each schedule lives in /etc/gniza4cp/schedules.d/<name>.conf and defines when backups run and which remotes to target. This allows multiple schedules targeting different sets of remotes.
Schedule Config Format
SCHEDULE="daily" # hourly, daily, weekly, monthly, custom
SCHEDULE_TIME="02:00" # HH:MM (24-hour)
SCHEDULE_DAY="" # hours between backups (1-23) for hourly
# day-of-week (0=Sun..6=Sat) for weekly
# day-of-month (1-28) for monthly
SCHEDULE_CRON="" # Full cron expression for SCHEDULE=custom
REMOTES="" # Comma-separated remote names (empty = all)
SYSBACKUP="" # "yes" to include system backup
SKIP_SUSPENDED="" # "yes" to skip cPanel suspended accounts
Managing Schedules
# Interactive schedule creation
sudo gniza4cp schedule add nightly
# List configured schedules
sudo gniza4cp schedule list
# Delete a schedule
sudo gniza4cp schedule delete nightly
# Install all schedules to crontab
sudo gniza4cp schedule install
# Show current GNIZA cron entries
sudo gniza4cp schedule show
# Remove all GNIZA cron entries
sudo gniza4cp schedule remove
Schedule Types
| Type | SCHEDULE_DAY | Cron Pattern | Example |
|---|---|---|---|
hourly |
Hours interval (1-23) | <min> */<interval> * * * |
Every 2 hours at :15 → 15 */2 * * * |
daily |
— | <min> <hour> * * * |
Daily at 02:00 → 0 2 * * * |
weekly |
Day-of-week (0-6) | <min> <hour> * * <dow> |
Sundays at 03:00 → 0 3 * * 0 |
monthly |
Day-of-month (1-28) | <min> <hour> <dom> * * |
1st at 02:00 → 0 2 1 * * |
custom |
— | SCHEDULE_CRON (5-field) |
*/30 * * * * |
Each schedule gets a tagged cron entry for clean install/remove:
# gniza4cp:nightly
0 2 * * * /usr/local/bin/gniza4cp backup --remote=nas,offsite >> /var/log/gniza4cp/cron-nightly.log 2>&1
Remote Directory Structure
SSH Remotes
$REMOTE_BASE/<hostname>/accounts/<user>/
├── snapshots/
│ ├── 2026-03-03T020000/ # Completed snapshot
│ │ ├── mysql/ # SQL dumps (*.sql.gz)
│ │ ├── mysql.sql # Database grants
│ │ ├── cp/ # cPanel metadata
│ │ ├── ... # Other pkgacct files
│ │ └── homedir/ # Full home directory
│ ├── 2026-03-02T020000/ # Previous (hardlinked unchanged files)
│ └── 2026-03-01T020000.partial/ # Incomplete (failed/in-progress)
└── latest -> snapshots/2026-03-03T020000
Cloud Remotes (S3/GDrive)
$REMOTE_BASE/<hostname>/accounts/<user>/snapshots/
├── 2026-03-03T020000/ # Completed snapshot
│ ├── .complete # Completion marker (empty file)
│ ├── mysql/ # SQL dumps (*.sql.gz)
│ ├── ... # Other pkgacct files
│ └── homedir/ # Full home directory
├── 2026-03-02T020000/ # Previous snapshot
│ └── .complete
├── 2026-03-01T020000/ # Partial (no .complete → purged on next run)
└── latest.txt # Contains timestamp of newest snapshot
Cloud storage has no atomic rename or symlinks, so .complete markers and latest.txt replace the .partial suffix and latest symlink used by SSH remotes.
pkgacct output is stored directly in the snapshot root (no wrapper subdirectory). The homedir/ sits alongside it.
Ownership & Permissions
All rsync operations use --rsync-path="rsync --fake-super" to preserve file ownership and permissions even when the remote SSH user is not root. The real uid/gid/permissions are stored as extended attributes (user.rsync.%stat) on the remote filesystem. On restore, the same flag reads the xattrs back, allowing the local root process to set the correct ownership.
Backup Flow
- Load main config (TEMP_DIR, LOG_DIR, accounts, notifications)
- Resolve target remotes (
--remote=NAME[,NAME2]or all fromremotes.d/) - Test connectivity to all targets upfront (SSH or rclone by type)
- For each account:
pkgacctONCE- Gzip SQL ONCE
- For each remote:
load_remote(name)— swaps REMOTE_/S3_/GDRIVE_* globals- Clean partials, get previous snapshot
- Transfer pkgacct content (rsync for SSH, rclone for cloud)
- Transfer homedir (rsync for SSH, rclone for cloud)
- Finalize snapshot (SSH: rename
.partial+ symlink; cloud:.completemarker +latest.txt) - Enforce retention
- Cleanup local temp
- Summary + notification
If one remote fails for an account, other remotes still receive the backup.
Restore Workflows
| Workflow | Command | Description |
|---|---|---|
| Full account | restore account <user> |
Downloads pkgacct data, decompresses SQL, runs /scripts/restorepkg, rsyncs homedir, fixes ownership |
| Selective files | restore files <user> --path=... |
Rsyncs specific path from remote homedir backup |
| Single database | restore database <user> <dbname> |
Downloads SQL dump + grants, imports via mysql |
| Mailbox | restore mailbox <user> <email@domain> |
Restores individual email account from mail/<domain>/<mailbox>/ |
| Full server | restore server |
Restores all accounts found on the remote |
All restore commands require --remote=NAME to specify the source.
Error Handling
- Single account failures don't abort the run
- Exit codes:
0success,1fatal,2locked,5partial failure .partialdirectories mark incomplete snapshots- rsync retries with exponential backoff (configurable via
SSH_RETRIES) flock-based concurrency control prevents parallel runs- In multi-remote mode, failure on one remote doesn't block others
File Layout
/usr/local/gniza4cp/ # Install directory
├── bin/gniza4cp # CLI entrypoint
├── lib/ # Shell libraries
│ ├── constants.sh # Version, exit codes, colors, defaults
│ ├── utils.sh # die(), require_root(), timestamp, human_*
│ ├── logging.sh # Per-run log files, log_info/warn/error/debug
│ ├── config.sh # Config loading and validation
│ ├── locking.sh # flock-based concurrency control
│ ├── ssh.sh # SSH connectivity, remote_exec, rsync SSH cmd
│ ├── rclone.sh # Rclone transport layer for S3/GDrive
│ ├── accounts.sh # cPanel account discovery and filtering
│ ├── pkgacct.sh # pkgacct execution, SQL gzip, temp cleanup
│ ├── snapshot.sh # Timestamp naming, list/resolve snapshots
│ ├── transfer.sh # rsync --link-dest transfers, finalize
│ ├── retention.sh # Prune old snapshots beyond RETENTION_COUNT
│ ├── verify.sh # Remote backup integrity checks
│ ├── notify.sh # Email notifications
│ ├── restore.sh # Full account, files, database, mailbox, server restore
│ ├── remotes.sh # Remote discovery and context switching
│ └── schedule.sh # Cron management for decoupled schedules
└── etc/
├── gniza4cp.conf.example # Main config template
├── remote.conf.example # Remote destination template
└── schedule.conf.example # Schedule template
/etc/gniza4cp/ # Runtime configuration
├── gniza4cp.conf # Main config
├── remotes.d/ # Remote destination configs
│ ├── nas.conf
│ └── offsite.conf
└── schedules.d/ # Schedule configs
├── nightly.conf
└── weekly-offsite.conf
/var/log/gniza4cp/ # Log files
├── gniza4cp-20260303-020000.log # Per-run logs
├── cron-nightly.log # Per-schedule cron output
└── cron-weekly-offsite.log
WHM Plugin
GNIZA includes a WHM plugin for managing backups through the cPanel/WHM web interface. All pages use Tailwind CSS v4 with DaisyUI v5 for styling.
Installation
The plugin is installed automatically by scripts/install.sh. It registers with WHM at Plugins > GNIZA Backup Manager.
Plugin files are deployed to /usr/local/cpanel/whostmgr/docroot/cgi/gniza4cp-whm/.
Setup Wizard
When gniza4cp is not yet configured (no remotes in /etc/gniza4cp/remotes.d/), the dashboard automatically redirects to a 3-step setup wizard:
-
SSH Key — Detects existing keys in
/root/.ssh/(id_ed25519,id_rsa,id_ecdsa,id_dsa). Lets you select one or enter a custom path. Showsssh-keygenandssh-copy-idcommands for creating new keys. -
Remote Destination — Configure the first remote: name, type (SSH/S3/GDrive), connection details, base path, bandwidth limit, and retention count. Tests the connection before saving. Creates a config file in
/etc/gniza4cp/remotes.d/. -
Schedule — Optionally set a backup schedule (hourly/daily/weekly/monthly/custom) for the new remote. Installs the cron entry automatically. Can be skipped.
The wizard is also accessible anytime from the dashboard quick links ("Run Setup Wizard").
Pages
| Page | URL | Description |
|---|---|---|
| Dashboard | index.cgi |
Overview, remote listing, cron status, quick links |
| Remotes | remotes.cgi |
Add/edit/delete remote destinations (SSH/S3/GDrive) with connection testing |
| Schedules | schedules.cgi |
Add/edit/delete schedules, per-schedule cron toggle |
| Restore | restore.cgi |
Restore workflow: select account, remote, snapshot, then restore type (full/files/database/mailbox) |
| Settings | settings.cgi |
Edit main config (/etc/gniza4cp/gniza4cp.conf) |
| Setup Wizard | setup.cgi |
Guided initial configuration (3 steps) |
Plugin File Layout
whm/
├── gniza4cp-whm.conf # WHM AppConfig registration
└── gniza4cp-whm/
├── index.cgi # Dashboard
├── setup.cgi # Setup wizard (3 steps)
├── settings.cgi # Main config editor
├── remotes.cgi # Remote CRUD (SSH/S3/GDrive)
├── schedules.cgi # Schedule CRUD + cron toggles
├── restore.cgi # Restore workflow (account → remote → snapshot → type)
├── assets/
│ ├── gniza4cp-whm.css # Built Tailwind/DaisyUI CSS (committed)
│ └── src/
│ ├── input.css # Tailwind v4 entry point
│ ├── safelist.html # Class safelist for Tailwind scanner
│ └── package.json # Build toolchain
└── lib/Gniza4cpWHM/
├── Config.pm # Config parser/writer (pure Perl)
├── Validator.pm # Input validation
├── Cron.pm # Cron read + per-schedule install/remove
├── Runner.pm # Pattern-based safe CLI command runner
└── UI.pm # Navigation, flash, CSRF, HTML helpers, CSS delivery
Running Tests
bash tests/test_utils.sh
License
MIT