Files
gniza4cp/README.md
shuki eb93333b9f Update documentation: git repo info, schedules, restore, WHM pages
- Add Repository section with SSH/HTTP/Web UI URLs
- Rewrite Schedules section: decoupled from remotes, hourly/daily/weekly/monthly/custom types
- Add schedule types table with cron patterns
- Add mailbox restore to commands and restore workflows
- Update commands list with full schedule CRUD and comma-separated remotes
- Update WHM plugin docs: restore page, schedule toggles, Runner.pm
- Update file layout: schedules.d/, rclone.sh, schedule.conf.example
- Add uninstall instructions
- Remove empty Production Server heading

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-04 02:57:47 +02:00

451 lines
17 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.
## Repository
| | URL |
|---|-----|
| **Git (SSH)** | `ssh://git@192.168.100.100:2222/shukivaknin/gniza.git` |
| **Git (HTTP)** | `http://192.168.100.100:3001/shukivaknin/gniza.git` |
| **Web UI** | http://192.168.100.100:3001/shukivaknin/gniza |
## Installation
```bash
curl -sSL http://192.168.100.100:3001/shukivaknin/gniza/raw/branch/main/scripts/install.sh | sudo bash
```
Or from a local clone:
```bash
git clone ssh://git@192.168.100.100:2222/shukivaknin/gniza.git
cd gniza
sudo bash scripts/install.sh
```
To uninstall:
```bash
sudo bash /usr/local/gniza/scripts/uninstall.sh # from installed copy
# or
sudo bash scripts/uninstall.sh # from repo clone
```
The uninstall script removes the CLI, WHM plugin, and symlink. Config (`/etc/gniza/`), logs (`/var/log/gniza/`), and cron entries are left for manual cleanup.
## Quick Start
```bash
# Interactive setup (creates config + first remote + optional schedule)
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
# Back up to specific remotes
sudo gniza backup --remote=nas,offsite
```
## Commands
```
gniza backup [--account=NAME] [--remote=NAME[,NAME2]] [--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 mailbox <name> <email@domain> --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 add <name>
gniza schedule delete <name>
gniza schedule list
gniza schedule install
gniza schedule show
gniza schedule 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[,NAME2]` | Target specific remote(s) from `/etc/gniza/remotes.d/` (comma-separated) |
| `--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 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 always requires `--remote` to specify the source.
```bash
# Back up to all remotes
sudo gniza backup
# Back up to specific remote(s)
sudo gniza backup --remote=nas
sudo gniza backup --remote=nas,offsite
# List snapshots on a specific remote
sudo gniza list --remote=offsite
# Restore requires explicit remote
sudo gniza restore account johndoe --remote=nas
```
### Schedules
Schedules are **decoupled from remotes**. Each schedule lives in `/etc/gniza/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
```bash
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)
```
#### Managing Schedules
```bash
# Interactive schedule creation
sudo gniza schedule add nightly
# List configured schedules
sudo gniza schedule list
# Delete a schedule
sudo gniza schedule delete nightly
# Install all schedules to crontab
sudo gniza schedule install
# Show current gniza cron entries
sudo gniza schedule show
# Remove all gniza cron entries
sudo gniza 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:
```
# gniza:nightly
0 2 * * * /usr/local/bin/gniza backup --remote=nas,offsite >> /var/log/gniza/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/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
│ ├── 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/
├── gniza.conf.example # Main config template
├── remote.conf.example # Remote destination template
└── schedule.conf.example # Schedule template
/etc/gniza/ # Runtime configuration
├── gniza.conf # Main config
├── remotes.d/ # Remote destination configs
│ ├── nas.conf
│ └── offsite.conf
└── schedules.d/ # Schedule configs
├── nightly.conf
└── weekly-offsite.conf
/var/log/gniza/ # Log files
├── gniza-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/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, type (SSH/S3/GDrive), connection details, base path, bandwidth limit, and retention count. Tests the connection before saving. 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 |
| 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/gniza/gniza.conf`) |
| Setup Wizard | `setup.cgi` | Guided initial configuration (3 steps) |
### 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 (SSH/S3/GDrive)
├── schedules.cgi # Schedule CRUD + cron toggles
├── restore.cgi # Restore workflow (account → remote → snapshot → type)
├── assets/
│ ├── gniza-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/GnizaWHM/
├── 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
bash tests/test_utils.sh
```
## License
MIT