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.

Installation

curl -sSL http://192.168.100.100:3000/shukivaknin/gniza/raw/branch/main/scripts/install.sh | sudo bash

Or from a local clone:

git clone ssh://git@192.168.100.100:2222/shukivaknin/gniza.git
cd gniza
sudo bash scripts/install.sh

Quick Start


# Interactive setup (creates config + first remote)
sudo gniza init

# Add additional remote destinations
sudo gniza init remote offsite

# Test backup (dry run)
sudo gniza backup --dry-run

# Run backup
sudo gniza backup

# Install cron schedules for all remotes
sudo gniza schedule install

Commands

gniza backup    [--account=NAME] [--remote=NAME] [--dry-run]
gniza restore   account <name> [--remote=NAME] [--timestamp=TS] [--force]
gniza restore   files <name> [--remote=NAME] [--path=subpath] [--timestamp=TS]
gniza restore   database <name> <dbname> [--remote=NAME] [--timestamp=TS]
gniza restore   server [--remote=NAME] [--timestamp=TS]
gniza list      [--account=NAME] [--remote=NAME]
gniza verify    [--account=NAME] [--remote=NAME]
gniza status
gniza remote    list
gniza remote    delete <name>
gniza schedule  {install|show|remove}
gniza init
gniza init      remote <name>
gniza version
gniza help

Global Options

Option Description
--config=PATH Alternate config file (default: /etc/gniza/gniza.conf)
--remote=NAME Target a specific remote from /etc/gniza/remotes.d/
--debug Enable debug logging

Configuration

Main Config

File: /etc/gniza/gniza.conf

Controls local settings (accounts, logging, notifications). Remote destinations are configured in /etc/gniza/remotes.d/.

# Local Settings
TEMP_DIR="/usr/local/gniza/workdir"  # Working dir for pkgacct output
INCLUDE_ACCOUNTS=""               # Comma-separated, empty = all
EXCLUDE_ACCOUNTS="nobody"         # Comma-separated exclusions

# Logging
LOG_DIR="/var/log/gniza"
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/gniza.lock"
SSH_TIMEOUT=30
SSH_RETRIES=3
RSYNC_EXTRA_OPTS=""

See etc/gniza.conf.example for the full template.

Remote Destinations

Back up to one or more destinations with independent schedules, 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/gniza/remotes.d/.

Setup

# Interactive setup (recommended)
sudo gniza init remote nas
sudo gniza init remote offsite

# Or copy the template manually
sudo cp /etc/gniza/remote.conf.example /etc/gniza/remotes.d/nas.conf
sudo vi /etc/gniza/remotes.d/nas.conf

# List configured remotes
sudo gniza remote list

# Delete a remote
sudo gniza remote delete nas

Remote Config Format

Each file in /etc/gniza/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 requires --remote to specify the source.

# Back up to all remotes
sudo gniza backup

# Back up to a specific remote
sudo gniza backup --remote=nas

# List snapshots on a specific remote
sudo gniza list --remote=offsite

# Restore requires explicit remote
sudo gniza restore account johndoe --remote=nas

Schedule Management

Manage cron entries based on each remote's SCHEDULE settings. Each remote gets a tagged cron entry for clean install/remove.

sudo gniza schedule install    # Install cron entries for all remotes
sudo gniza schedule show       # Show current gniza cron entries
sudo gniza schedule remove     # Remove all gniza cron entries

Example crontab entries:

# gniza:nas
0 2 * * * /usr/local/bin/gniza backup --remote=nas >> /var/log/gniza/cron-nas.log 2>&1
# gniza:offsite
0 3 * * 0 /usr/local/bin/gniza backup --remote=offsite >> /var/log/gniza/cron-offsite.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 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 Description
Full account Downloads pkgacct data, decompresses SQL, runs /scripts/restorepkg, rsyncs homedir, fixes ownership
Selective files Rsyncs specific path from remote homedir backup
Single database Downloads SQL dump + grants, imports via mysql
Full server rebuild 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/gniza/              # Install directory
├── bin/gniza                  # 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
│   ├── 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, server restore
│   ├── remotes.sh             # Remote discovery and context switching
│   └── schedule.sh            # Cron management for multi-remote schedules
└── etc/
    ├── gniza.conf.example     # Main config template
    └── remote.conf.example    # Remote destination template

/etc/gniza/                    # Runtime configuration
├── gniza.conf                 # Main config
└── remotes.d/                 # Remote destination configs
    ├── nas.conf
    └── offsite.conf

/var/log/gniza/                # Log files
├── gniza-20260303-020000.log  # Per-run logs
├── cron-nas.log               # Per-remote cron output
└── cron-offsite.log

WHM Plugin

gniza includes a WHM plugin for managing backups through the cPanel/WHM web interface.

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/gniza-whm/.

Setup Wizard

When gniza is not yet configured (no remotes in /etc/gniza/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, host, port, user, SSH key (pre-filled from step 1), base path, bandwidth limit, and retention count. Creates a config file in /etc/gniza/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
Settings settings.cgi Edit main config (/etc/gniza/gniza.conf)
Remotes remotes.cgi Add/edit/delete remote destinations
Schedules schedules.cgi View and manage cron schedules
Setup Wizard setup.cgi Guided initial configuration

SSH Key Guidance

When adding a new remote (via Remotes > Add Remote), an SSH key guidance block is displayed above the form showing:

  • Detected existing keys on the server
  • Commands to generate a new key and copy it to the remote

Plugin File Layout

whm/
├── gniza-whm.conf                    # WHM AppConfig registration
└── gniza-whm/
    ├── index.cgi                     # Dashboard
    ├── setup.cgi                     # Setup wizard (3 steps)
    ├── settings.cgi                  # Main config editor
    ├── remotes.cgi                   # Remote CRUD
    ├── schedules.cgi                 # Cron management
    ├── assets/
    │   └── gniza-whm.css             # Shared styles
    └── lib/GnizaWHM/
        ├── Config.pm                 # Config parser/writer (pure Perl)
        ├── Validator.pm              # Input validation
        ├── Cron.pm                   # Cron read + allowlisted gniza commands
        └── UI.pm                     # Navigation, flash, CSRF, HTML helpers

Production Server

gniza is deployed on the following server:

Server Host SSH Port
Production (cPanel) 192.168.100.13 2223

Hostname: 181-79-81-251.cprapid.com

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%