Full-featured cPanel backup tool with SSH, S3, and Google Drive support. Includes WHM plugin with Tailwind/DaisyUI UI, multi-remote management, decoupled schedules, and account restore workflows. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
382 lines
14 KiB
Markdown
382 lines
14 KiB
Markdown
# 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.
|
|
|
|
## Quick Start
|
|
|
|
```bash
|
|
# Install
|
|
sudo bash scripts/install.sh
|
|
|
|
# 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/`.
|
|
|
|
```bash
|
|
# 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
|
|
|
|
```bash
|
|
# 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`:
|
|
|
|
```bash
|
|
# 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.
|
|
|
|
```bash
|
|
# 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.
|
|
|
|
```bash
|
|
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
|
|
bash tests/test_utils.sh
|
|
```
|
|
|
|
## License
|
|
|
|
MIT
|