14 KiB
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:3001/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
- Load main config (TEMP_DIR, LOG_DIR, accounts, notifications)
- Resolve target remotes (
--remote=NAMEor 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 | 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:
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/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:
-
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, 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/. -
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
Running Tests
bash tests/test_utils.sh
License
MIT