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

  1. Load main config (TEMP_DIR, LOG_DIR, accounts, notifications)
  2. Resolve target remotes (--remote=NAME[,NAME2] or all from remotes.d/)
  3. Test connectivity to all targets upfront (SSH or rclone by type)
  4. For each account:
    • pkgacct ONCE
    • 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: .complete marker + latest.txt)
      • Enforce retention
    • Cleanup local temp
  5. 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: 0 success, 1 fatal, 2 locked, 5 partial failure
  • .partial directories 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:

  1. 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. Shows ssh-keygen and ssh-copy-id commands for creating new keys.

  2. 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/.

  3. 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

Description
server-side backup system for web hosting
Readme MIT 11 MiB
Languages
Perl 59.9%
Shell 39.6%
HTML 0.3%
CSS 0.2%