Compare commits
28 Commits
325020338d
...
main
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
438724de3b | ||
|
|
f1e7682f21 | ||
|
|
c186453a28 | ||
|
|
aff73bda86 | ||
|
|
a162536585 | ||
|
|
f0171a9eb4 | ||
|
|
043e409930 | ||
|
|
0bd75402d1 | ||
|
|
c20c019048 | ||
|
|
5ffd365c43 | ||
|
|
e7bf2e11e2 | ||
|
|
b1c475da00 | ||
|
|
a5ab2c788a | ||
|
|
efcd4844e9 | ||
|
|
2cf0a8866c | ||
|
|
55cd576d53 | ||
|
|
beb17a2298 | ||
|
|
89ca3187df | ||
|
|
6be3e8fabf | ||
|
|
42ee83f433 | ||
|
|
0fb640d585 | ||
|
|
19368ee2c6 | ||
|
|
af55437bed | ||
|
|
43fdf116cc | ||
|
|
3a48c3265f | ||
|
|
8f42cc19c9 | ||
|
|
b22a8d14a7 | ||
|
|
bf6624b607 |
221
CLAUDE.md
@@ -1,19 +1,19 @@
|
||||
# agents.md — gniza Development Guide
|
||||
# agents.md — gniza4cp Development Guide
|
||||
|
||||
> Reference for AI coding agents working on gniza. Describes architecture, conventions, and key patterns.
|
||||
> Reference for AI coding agents working on gniza4cp. Describes architecture, conventions, and key patterns.
|
||||
|
||||
## Project Overview
|
||||
|
||||
gniza is a Bash CLI tool for cPanel server backup and disaster recovery. It runs `pkgacct` to export accounts, gzips SQL files, and transfers everything to one or more remote destinations using hardlink-based incremental snapshots. Supports three remote types: **SSH** (rsync with `--link-dest`), **Amazon S3** / S3-compatible (via rclone), and **Google Drive** (via rclone).
|
||||
gniza4cp is a Bash CLI tool for cPanel server backup and disaster recovery. It runs `pkgacct` to export accounts, gzips SQL files, and transfers everything to one or more remote destinations using hardlink-based incremental snapshots. Supports three remote types: **SSH** (rsync with `--link-dest`), **Amazon S3** / S3-compatible (via rclone), and **Google Drive** (via rclone).
|
||||
|
||||
**Language:** Bash (bash 4+, `set -euo pipefail`)
|
||||
**Target environment:** CentOS/AlmaLinux cPanel servers, running as root
|
||||
**Install path:** `/usr/local/gniza/` with symlink at `/usr/local/bin/gniza`
|
||||
**Install path:** `/usr/local/gniza4cp/` with symlink at `/usr/local/bin/gniza4cp`
|
||||
|
||||
## Repository Structure
|
||||
|
||||
```
|
||||
bin/gniza # CLI entrypoint — command routing, argument parsing
|
||||
bin/gniza4cp # CLI entrypoint — command routing, argument parsing
|
||||
lib/
|
||||
├── constants.sh # Version, exit codes, color codes, default values
|
||||
├── utils.sh # die(), require_root(), timestamp(), human_size/duration(), validate_timestamp/account_name()
|
||||
@@ -33,17 +33,17 @@ lib/
|
||||
├── remotes.sh # Multi-remote: list_remotes(), load_remote(), get_target_remotes()
|
||||
└── schedule.sh # Cron: decoupled schedules from schedules.d/
|
||||
etc/
|
||||
├── gniza.conf.example # Main config template
|
||||
├── gniza4cp.conf.example # Main config template
|
||||
├── remote.conf.example # Remote destination config template
|
||||
└── schedule.conf.example # Schedule config template
|
||||
scripts/
|
||||
├── install.sh # Install to /usr/local/gniza, create dirs/symlinks, WHM + cPanel plugins
|
||||
├── install.sh # Install to /usr/local/gniza4cp, create dirs/symlinks, WHM + cPanel plugins
|
||||
└── uninstall.sh # Remove install dir, symlink, cron entries, WHM + cPanel plugins
|
||||
tests/
|
||||
└── test_utils.sh # Unit tests for utils.sh, accounts.sh, config.sh
|
||||
whm/
|
||||
├── gniza-whm.conf # WHM AppConfig registration
|
||||
└── gniza-whm/
|
||||
├── gniza4cp-whm.conf # WHM AppConfig registration
|
||||
└── gniza4cp-whm/
|
||||
├── index.cgi # Dashboard — overview, quick links, auto-redirect if unconfigured
|
||||
├── setup.cgi # 3-step setup wizard (SSH key → remote → schedule)
|
||||
├── settings.cgi # Main config editor (local settings only)
|
||||
@@ -51,29 +51,29 @@ whm/
|
||||
├── schedules.cgi # Schedule CRUD — add/edit/delete with remote checkboxes
|
||||
├── restore.cgi # Restore workflow — 4-step form (account → snapshot → confirm → execute)
|
||||
├── assets/
|
||||
│ ├── gniza-whm.css # Built Tailwind/DaisyUI CSS (committed, ~58KB)
|
||||
│ ├── gniza-logo.svg # SVG logo (embedded as data URI in page header)
|
||||
│ ├── gniza4cp-whm.css # Built Tailwind/DaisyUI CSS (committed, ~58KB)
|
||||
│ ├── gniza4cp-logo.svg # SVG logo (embedded as data URI in page header)
|
||||
│ └── src/
|
||||
│ ├── input.css # Tailwind v4 entry point with DaisyUI plugin
|
||||
│ ├── safelist.html # Class safelist for Tailwind content scanner
|
||||
│ └── package.json # Build toolchain (tailwindcss + daisyui)
|
||||
└── lib/GnizaWHM/
|
||||
└── lib/Gniza4cpWHM/
|
||||
├── Config.pm # Pure Perl config parser/writer (KEY="value" files)
|
||||
├── Validator.pm # Input validation (mirrors lib/config.sh)
|
||||
├── Cron.pm # Cron read + allowlisted gniza schedule commands
|
||||
├── Cron.pm # Cron read + allowlisted gniza4cp schedule commands
|
||||
├── Runner.pm # Pattern-based safe CLI command runner for WHM
|
||||
└── UI.pm # Nav, flash, CSRF, HTML escaping, CSS delivery
|
||||
cpanel/
|
||||
├── gniza/
|
||||
├── gniza4cp/
|
||||
│ ├── index.live.cgi # Category grid — 8 restore type cards
|
||||
│ ├── restore.live.cgi # Multi-step restore workflow (4 steps)
|
||||
│ ├── install.json # cPanel plugin registration (Files section)
|
||||
│ ├── assets/
|
||||
│ │ ├── gniza-whm.css # Built CSS (copy of WHM CSS)
|
||||
│ │ └── gniza-logo.svg # Logo (copy of WHM logo)
|
||||
│ └── lib/GnizaCPanel/
|
||||
│ │ ├── gniza4cp-whm.css # Built CSS (copy of WHM CSS)
|
||||
│ │ └── gniza4cp-logo.svg # Logo (copy of WHM logo)
|
||||
│ └── lib/Gniza4cpCPanel/
|
||||
│ └── UI.pm # Page wrapper, CSRF, flash, CSS delivery
|
||||
└── admin/Gniza/
|
||||
└── admin/Gniza4cp/
|
||||
├── Restore # AdminBin module (runs as root, privilege escalation)
|
||||
└── Restore.conf # AdminBin config (mode=full)
|
||||
```
|
||||
@@ -87,7 +87,7 @@ All library functions (`ssh.sh`, `rclone.sh`, `transfer.sh`, `snapshot.sh`, `ret
|
||||
Rather than passing remote context through function arguments, `remotes.sh` provides:
|
||||
|
||||
- `_save_remote_globals()` — snapshot current globals
|
||||
- `load_remote(name)` — source `/etc/gniza/remotes.d/<name>.conf`, overriding REMOTE_* globals
|
||||
- `load_remote(name)` — source `/etc/gniza4cp/remotes.d/<name>.conf`, overriding REMOTE_* globals
|
||||
- `_restore_remote_globals()` — restore saved snapshot
|
||||
|
||||
This keeps the change set minimal — no existing function signatures needed modification.
|
||||
@@ -131,16 +131,16 @@ cmd_backup()
|
||||
|
||||
### Command Routing
|
||||
|
||||
`bin/gniza` main() parses the first arg and routes to `cmd_*()` functions. Each command handles its own `--config`, `--remote`, `--account` flags via `get_opt()` and `has_flag()`.
|
||||
`bin/gniza4cp` main() parses the first arg and routes to `cmd_*()` functions. Each command handles its own `--config`, `--remote`, `--account` flags via `get_opt()` and `has_flag()`.
|
||||
|
||||
Commands: `backup`, `restore`, `list`, `verify`, `status`, `remote`, `schedule`, `init`, `version`, `help`
|
||||
Commands: `backup`, `restore`, `list`, `verify`, `status`, `remote`, `schedule`, `version`, `help`
|
||||
|
||||
### Config Hierarchy
|
||||
|
||||
1. `lib/constants.sh` — `DEFAULT_*` readonly values
|
||||
2. `/etc/gniza/gniza.conf` — main config: local settings only (accounts, logging, notifications)
|
||||
3. `/etc/gniza/remotes.d/<name>.conf` — per-remote config (REMOTE_*, retention, transfer)
|
||||
4. `/etc/gniza/schedules.d/<name>.conf` — per-schedule config (timing, target remotes)
|
||||
2. `/etc/gniza4cp/gniza4cp.conf` — main config: local settings only (accounts, logging, notifications)
|
||||
3. `/etc/gniza4cp/remotes.d/<name>.conf` — per-remote config (REMOTE_*, retention, transfer)
|
||||
4. `/etc/gniza4cp/schedules.d/<name>.conf` — per-schedule config (timing, target remotes)
|
||||
5. CLI flags (`--debug`, `--config=PATH`)
|
||||
|
||||
### Snapshot Layout
|
||||
@@ -171,13 +171,13 @@ Commands: `backup`, `restore`, `list`, `verify`, `status`, `remote`, `schedule`,
|
||||
|
||||
### Decoupled Schedules
|
||||
|
||||
Schedules are independent 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.
|
||||
Schedules are independent 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.
|
||||
|
||||
Cron entries are tagged with `# gniza:<name>` comment lines. `install_schedules()` strips old tagged lines and appends new ones. Format:
|
||||
Cron entries are tagged with `# gniza4cp:<name>` comment lines. `install_schedules()` strips old tagged lines and appends new ones. Format:
|
||||
|
||||
```
|
||||
# gniza:nightly
|
||||
0 2 * * * /usr/local/bin/gniza backup --remote=nas,offsite >> /var/log/gniza/cron-nightly.log 2>&1
|
||||
# gniza4cp:nightly
|
||||
0 2 * * * /usr/local/bin/gniza4cp backup --remote=nas,offsite >> /var/log/gniza4cp/cron-nightly.log 2>&1
|
||||
```
|
||||
|
||||
### Comma-Separated Remote Targeting
|
||||
@@ -188,7 +188,7 @@ Cron entries are tagged with `# gniza:<name>` comment lines. `install_schedules(
|
||||
|
||||
Allows cPanel account owners to restore their own data (files, databases, email, etc.) without WHM admin access.
|
||||
|
||||
**Privilege escalation:** Uses cPanel's AdminBin framework. CGIs run as the logged-in cPanel user; the AdminBin module (`cpanel/admin/Gniza/Restore`) runs as root. The account parameter is always forced to `$ENV{'REMOTE_USER'}` (cPanel-authenticated), never from user input.
|
||||
**Privilege escalation:** Uses cPanel's AdminBin framework. CGIs run as the logged-in cPanel user; the AdminBin module (`cpanel/admin/Gniza4cp/Restore`) runs as root. The account parameter is always forced to `$ENV{'REMOTE_USER'}` (cPanel-authenticated), never from user input.
|
||||
|
||||
**CGI file naming:** cPanel Jupiter theme uses `.live.cgi` extension for CGI files (e.g., `index.live.cgi`, `restore.live.cgi`).
|
||||
|
||||
@@ -196,18 +196,18 @@ Allows cPanel account owners to restore their own data (files, databases, email,
|
||||
- Account isolation: AdminBin forces the authenticated username — users can only restore their own data
|
||||
- No `--terminate`: AdminBin never passes the terminate flag, preventing destructive full restores
|
||||
- Remote filtering: `USER_RESTORE_REMOTES` config controls which remotes users can access (`"all"`, comma-separated names, or empty to disable)
|
||||
- Strict regex validation on all arguments (mirrors `GnizaWHM::Runner` patterns)
|
||||
- Strict regex validation on all arguments (mirrors `Gniza4cpWHM::Runner` patterns)
|
||||
- Path traversal prevention: path regex uses negative lookahead to reject `..` — `qr/^(?!.*\.\.)[a-zA-Z0-9_.\/@ -]+$/`
|
||||
- Remote name regex: `qr/^[a-zA-Z0-9_-]+$/` (rejects special characters)
|
||||
- Per-user CSRF tokens at `/tmp/.gniza-cpanel-csrf-$user` (symlink-safe I/O)
|
||||
- Per-user CSRF tokens at `/tmp/.gniza4cp-cpanel-csrf-$user` (symlink-safe I/O)
|
||||
- Symlink-safe file operations: `_safe_write` uses `unlink` + `O_CREAT|O_EXCL` with fallback; `_safe_read` rejects symlinks via `-l` check
|
||||
- Flash message type validated against allowlist (`success`, `error`, `info`, `warning`)
|
||||
|
||||
**Install locations:**
|
||||
- CGIs: `/usr/local/cpanel/base/frontend/jupiter/gniza/`
|
||||
- AdminBin: `/usr/local/cpanel/bin/admin/Gniza/` (Restore is `0700`, Restore.conf is `0600`)
|
||||
- CGIs: `/usr/local/cpanel/base/frontend/jupiter/gniza4cp/`
|
||||
- AdminBin: `/usr/local/cpanel/bin/admin/Gniza4cp/` (Restore is `0700`, Restore.conf is `0600`)
|
||||
- Plugin registration: via `install_plugin` with tar.gz archive containing `install.json`
|
||||
- Assets: CSS and logo copied to `gniza/assets/` alongside CGIs
|
||||
- Assets: CSS and logo copied to `gniza4cp/assets/` alongside CGIs
|
||||
- `install.json` also copied to CGI directory for `uninstall_plugin` to reference
|
||||
|
||||
**Restore categories (8 types):**
|
||||
@@ -229,9 +229,9 @@ Allows cPanel account owners to restore their own data (files, databases, email,
|
||||
3. Confirmation summary with CSRF token
|
||||
4. Execute via AdminBin, display results
|
||||
|
||||
**cPanel plugin registration:** `install.json` is an array of plugin definitions passed to `install_plugin`/`uninstall_plugin` inside a **tar.gz archive** (with the icon file included). Required JSON fields per cPanel's `Cpanel::Themes::Assets::Link`: `type` ("link"), `id` (lowercase identifier), `name`, `group_id` (section: "files", "domains", etc.), `uri` (CGI path), `feature` (for Feature Manager), `order` (integer), `icon` (path relative to staging dir). The `feature` key (`gniza_restore`) allows admins to enable/disable per cPanel package.
|
||||
**cPanel plugin registration:** `install.json` is an array of plugin definitions passed to `install_plugin`/`uninstall_plugin` inside a **tar.gz archive** (with the icon file included). Required JSON fields per cPanel's `Cpanel::Themes::Assets::Link`: `type` ("link"), `id` (lowercase identifier), `name`, `group_id` (section: "files", "domains", etc.), `uri` (CGI path), `feature` (for Feature Manager), `order` (integer), `icon` (path relative to staging dir). The `feature` key (`gniza4cp_restore`) allows admins to enable/disable per cPanel package.
|
||||
|
||||
### GnizaCPanel::UI
|
||||
### Gniza4cpCPanel::UI
|
||||
|
||||
| Function | Description |
|
||||
|----------|-------------|
|
||||
@@ -239,21 +239,21 @@ Allows cPanel account owners to restore their own data (files, databases, email,
|
||||
| `get_current_user()` | Returns `$ENV{'REMOTE_USER'}` |
|
||||
| `_safe_write($file, $content)` | Symlink-safe write: `unlink` + `O_CREAT\|O_EXCL` (0600 perms) |
|
||||
| `_safe_read($file)` | Symlink-safe read: rejects symlinks (`-l` check) |
|
||||
| `page_header($title)` | Inline CSS + `data-theme="gniza"` wrapper + logo (base64 data URI) |
|
||||
| `page_header($title)` | Inline CSS + `data-theme="gniza4cp"` wrapper + logo (base64 data URI) |
|
||||
| `page_footer()` | Close wrapper div |
|
||||
| `set_flash($type, $text)` | Store flash message at `/tmp/.gniza-cpanel-flash-$user` |
|
||||
| `set_flash($type, $text)` | Store flash message at `/tmp/.gniza4cp-cpanel-flash-$user` |
|
||||
| `get_flash()` | Read and consume flash message |
|
||||
| `render_flash()` | Render flash as HTML alert (type validated against allowlist) |
|
||||
| `generate_csrf_token()` | Generate 64-char hex token from `/dev/urandom`, store at `/tmp/.gniza-cpanel-csrf-$user` |
|
||||
| `generate_csrf_token()` | Generate 64-char hex token from `/dev/urandom`, store at `/tmp/.gniza4cp-cpanel-csrf-$user` |
|
||||
| `verify_csrf_token($token)` | Validate + delete (single-use), 1-hour expiry, constant-time comparison |
|
||||
| `csrf_hidden_field()` | Generate CSRF token + hidden input |
|
||||
| `render_errors(\@errors)` | Render error list as HTML |
|
||||
| `_unwrap_layers($css)` | Strip `@layer` wrappers from Tailwind CSS |
|
||||
| `_scope_to_container($css)` | Scope CSS rules to `[data-theme="gniza"]` container |
|
||||
| `_scope_to_container($css)` | Scope CSS rules to `[data-theme="gniza4cp"]` container |
|
||||
|
||||
### AdminBin Module (Gniza::Restore)
|
||||
### AdminBin Module (Gniza4cp::Restore)
|
||||
|
||||
Runs as root via cPanel's AdminBin framework. Each action validates inputs with strict regex patterns before executing gniza CLI via `IPC::Open3` (list execution, no shell).
|
||||
Runs as root via cPanel's AdminBin framework. Each action validates inputs with strict regex patterns before executing gniza4cp CLI via `IPC::Open3` (list execution, no shell).
|
||||
|
||||
**Validation patterns:**
|
||||
|
||||
@@ -270,9 +270,9 @@ Runs as root via cPanel's AdminBin framework. Each action validates inputs with
|
||||
|
||||
**Actions:** `LIST_ALLOWED_REMOTES`, `LIST_SNAPSHOTS`, `LIST_DATABASES`, `LIST_MAILBOXES`, `LIST_FILES`, `LIST_DBUSERS`, `LIST_CRON`, `LIST_DNS`, `LIST_SSL`, `RESTORE_ACCOUNT`, `RESTORE_FILES`, `RESTORE_DATABASE`, `RESTORE_MAILBOX`, `RESTORE_CRON`, `RESTORE_DBUSERS`, `RESTORE_DOMAINS`, `RESTORE_SSL`
|
||||
|
||||
**Remote filtering:** `_get_allowed_remotes()` reads `USER_RESTORE_REMOTES` from `/etc/gniza/gniza.conf`. Returns `"all"` (default), comma-separated names, or empty string (disabled). `_is_remote_allowed()` and `_get_filtered_remotes()` enforce this on every action.
|
||||
**Remote filtering:** `_get_allowed_remotes()` reads `USER_RESTORE_REMOTES` from `/etc/gniza4cp/gniza4cp.conf`. Returns `"all"` (default), comma-separated names, or empty string (disabled). `_is_remote_allowed()` and `_get_filtered_remotes()` enforce this on every action.
|
||||
|
||||
Called from CGI via: `Cpanel::AdminBin::Call::call('Gniza', 'Restore', 'ACTION', @args)`
|
||||
Called from CGI via: `Cpanel::AdminBin::Call::call('Gniza4cp', 'Restore', 'ACTION', @args)`
|
||||
|
||||
## Coding Conventions
|
||||
|
||||
@@ -281,7 +281,7 @@ Called from CGI via: `Cpanel::AdminBin::Call::call('Gniza', 'Restore', 'ACTION',
|
||||
- `set -euo pipefail` at top of entrypoint
|
||||
- Functions use `local` for all variables
|
||||
- Error paths: `log_error` + `return 1` (library) or `die "message"` (CLI)
|
||||
- Guard-include pattern for constants: `[[ -n "${_GNIZA_CONSTANTS_LOADED:-}" ]] && return 0`
|
||||
- Guard-include pattern for constants: `[[ -n "${_GNIZA4CP_CONSTANTS_LOADED:-}" ]] && return 0`
|
||||
- `((count++)) || true` to avoid `set -e` traps on zero-to-one arithmetic
|
||||
|
||||
### Naming
|
||||
@@ -289,7 +289,7 @@ Called from CGI via: `Cpanel::AdminBin::Call::call('Gniza', 'Restore', 'ACTION',
|
||||
- Libraries: `lib/<module>.sh` — each file focuses on one responsibility
|
||||
- Public functions: `snake_case` (e.g., `transfer_pkgacct`, `list_remote_snapshots`)
|
||||
- Private/helper functions: `_prefixed` (e.g., `_backup_to_current_remote`, `_save_remote_globals`)
|
||||
- CLI commands: `cmd_<name>()` in `bin/gniza`
|
||||
- CLI commands: `cmd_<name>()` in `bin/gniza4cp`
|
||||
- Constants: `UPPER_SNAKE_CASE`, prefixed with `DEFAULT_` for defaults
|
||||
- Globals: `UPPER_SNAKE_CASE` (e.g., `REMOTE_HOST`, `LOG_LEVEL`)
|
||||
|
||||
@@ -299,7 +299,7 @@ Called from CGI via: `Cpanel::AdminBin::Call::call('Gniza', 'Restore', 'ACTION',
|
||||
- In multi-remote mode, failure on one remote doesn't block others
|
||||
- rsync retries with exponential backoff: `sleep $((attempt * 10))`
|
||||
- Exit codes: `0` OK, `1` fatal, `2` locked, `5` partial failure
|
||||
- Lock via `flock` on `/var/run/gniza.lock`
|
||||
- Lock via `flock` on `/var/run/gniza4cp.lock`
|
||||
|
||||
### cPanel API Policy
|
||||
|
||||
@@ -341,16 +341,16 @@ Called from CGI via: `Cpanel::AdminBin::Call::call('Gniza', 'Restore', 'ACTION',
|
||||
**CLI (Bash):**
|
||||
- **Config parsing:** `_safe_source_config()` in `lib/config.sh` reads KEY=VALUE lines via regex without `source`/`eval` — prevents command injection from malicious config files
|
||||
- **Password handling:** SSH passwords passed via `sshpass -e` (environment variable `SSHPASS`), never `-p` (visible in process list)
|
||||
- **File permissions:** `umask 077` set at startup in `bin/gniza`; `install.sh` sets config dirs to `chmod 700`
|
||||
- **File permissions:** `umask 077` set at startup in `bin/gniza4cp`; `install.sh` sets config dirs to `chmod 700`
|
||||
- **Safe rm:** `${var:?}` pattern prevents `rm -rf ""/\*` expansion on empty variables (SC2115)
|
||||
- **Input validation:** `validate_timestamp()` and `validate_account_name()` enforce strict regex patterns. Account names: `^[a-z][a-z0-9_-]{0,15}$`. Timestamps: `^[0-9]{4}-[0-9]{2}-[0-9]{2}T[0-9]{6}$`
|
||||
- **RSYNC_EXTRA_OPTS validation:** Both Perl (Validator.pm) and Bash (`validate_config`) reject shell metacharacters (`^[a-zA-Z0-9 ._=/,-]+$`)
|
||||
|
||||
**WHM Plugin:**
|
||||
- **CSRF:** All POST endpoints require CSRF token via `verify_csrf_token()`. Single-use tokens stored at `/var/cpanel/.gniza-whm-csrf/token`. AJAX endpoints (e.g., SMTP test) return a new token in JSON responses; JS updates both the AJAX variable and the main form hidden field to keep them in sync
|
||||
- **CSRF:** All POST endpoints require CSRF token via `verify_csrf_token()`. Single-use tokens stored at `/var/cpanel/.gniza4cp-whm-csrf/token`. AJAX endpoints (e.g., SMTP test) return a new token in JSON responses; JS updates both the AJAX variable and the main form hidden field to keep them in sync
|
||||
- **HTML escaping:** All user-controlled output passed through `esc()` (HTML entity encoding)
|
||||
- **Runner path traversal:** `GnizaWHM::Runner` rejects `--account` and `--path` values containing `..`
|
||||
- **Config file I/O:** `GnizaWHM::Config::save()` uses `flock(LOCK_EX)` with single file handle (open `+<` then seek+truncate) to prevent TOCTOU races
|
||||
- **Runner path traversal:** `Gniza4cpWHM::Runner` rejects `--account` and `--path` values containing `..`
|
||||
- **Config file I/O:** `Gniza4cpWHM::Config::save()` uses `flock(LOCK_EX)` with single file handle (open `+<` then seek+truncate) to prevent TOCTOU races
|
||||
- **Safe file I/O:** `_safe_write()` uses `unlink` + `O_CREAT|O_EXCL` with plain-write fallback; `_safe_read()` rejects symlinks. Used for CSRF token and flash message files
|
||||
- **Upgrade path:** `_ensure_dir()` removes stale plain files left by older versions before creating directories (old versions stored CSRF/flash as plain files at the directory path)
|
||||
|
||||
@@ -360,10 +360,10 @@ Called from CGI via: `Cpanel::AdminBin::Call::call('Gniza', 'Restore', 'ACTION',
|
||||
- **Remote filtering:** `USER_RESTORE_REMOTES` config controls which remotes users can access
|
||||
- **Strict regex validation:** All AdminBin arguments validated against regex patterns (see AdminBin Module section)
|
||||
- **Path traversal prevention:** Path regex uses negative lookahead: `qr/^(?!.*\.\.)[a-zA-Z0-9_.\/@ -]+$/`
|
||||
- **CSRF:** Per-user single-use tokens at `/tmp/.gniza-cpanel-csrf-$user`, generated from `/dev/urandom` (64-char hex), 1-hour expiry, constant-time comparison
|
||||
- **CSRF:** Per-user single-use tokens at `/tmp/.gniza4cp-cpanel-csrf-$user`, generated from `/dev/urandom` (64-char hex), 1-hour expiry, constant-time comparison
|
||||
- **Symlink-safe I/O:** `_safe_write()` (unlink + `O_CREAT|O_EXCL` with fallback) and `_safe_read()` (rejects symlinks) for all `/tmp/` files
|
||||
- **Flash type validation:** `render_flash()` validates type against allowlist (`success`, `error`, `info`, `warning`)
|
||||
- **Command execution:** gniza CLI called via `IPC::Open3` as list (no shell interpolation)
|
||||
- **Command execution:** gniza4cp CLI called via `IPC::Open3` as list (no shell interpolation)
|
||||
|
||||
### SSH/Rsync (REMOTE_TYPE=ssh)
|
||||
|
||||
@@ -386,27 +386,27 @@ Called from CGI via: `Cpanel::AdminBin::Call::call('Gniza', 'Restore', 'ACTION',
|
||||
|
||||
## Configuration Files
|
||||
|
||||
### Main Config (`/etc/gniza/gniza.conf`)
|
||||
### Main Config (`/etc/gniza4cp/gniza4cp.conf`)
|
||||
|
||||
Contains only local settings. Remote destinations are configured in `remotes.d/`.
|
||||
|
||||
| Variable | Required | Default | Description |
|
||||
|----------|----------|---------|-------------|
|
||||
| `TEMP_DIR` | No | `/usr/local/gniza/workdir` | Local working directory |
|
||||
| `TEMP_DIR` | No | `/usr/local/gniza4cp/workdir` | Local working directory |
|
||||
| `INCLUDE_ACCOUNTS` | No | (all) | Comma-separated account list |
|
||||
| `EXCLUDE_ACCOUNTS` | No | `nobody` | Comma-separated exclusions |
|
||||
| `LOG_DIR` | No | `/var/log/gniza` | Log directory |
|
||||
| `LOG_DIR` | No | `/var/log/gniza4cp` | Log directory |
|
||||
| `LOG_LEVEL` | No | `info` | `debug\|info\|warn\|error` |
|
||||
| `LOG_RETAIN` | No | `90` | Days to keep log files |
|
||||
| `NOTIFY_EMAIL` | No | (disabled) | Notification email |
|
||||
| `NOTIFY_ON` | No | `failure` | `always\|failure\|never` |
|
||||
| `LOCK_FILE` | No | `/var/run/gniza.lock` | Lock file path |
|
||||
| `LOCK_FILE` | No | `/var/run/gniza4cp.lock` | Lock file path |
|
||||
| `SSH_TIMEOUT` | No | `30` | SSH connection timeout (seconds) |
|
||||
| `SSH_RETRIES` | No | `3` | rsync retry attempts |
|
||||
| `RSYNC_EXTRA_OPTS` | No | (empty) | Extra rsync options |
|
||||
| `USER_RESTORE_REMOTES` | No | `all` | Remotes for cPanel user restore (`all`, comma-separated names, or empty to disable) |
|
||||
|
||||
### Remote Config (`/etc/gniza/remotes.d/<name>.conf`)
|
||||
### Remote Config (`/etc/gniza4cp/remotes.d/<name>.conf`)
|
||||
|
||||
**Common (all types):**
|
||||
|
||||
@@ -446,7 +446,7 @@ Contains only local settings. Remote destinations are configured in `remotes.d/`
|
||||
| `GDRIVE_SERVICE_ACCOUNT_FILE` | Yes | — | Path to service account JSON key file |
|
||||
| `GDRIVE_ROOT_FOLDER_ID` | No | (empty) | Root folder ID |
|
||||
|
||||
### Schedule Config (`/etc/gniza/schedules.d/<name>.conf`)
|
||||
### Schedule Config (`/etc/gniza4cp/schedules.d/<name>.conf`)
|
||||
|
||||
Schedules are decoupled from remotes. Each schedule targets one or more remotes.
|
||||
|
||||
@@ -502,7 +502,7 @@ Schedules are decoupled from remotes. Each schedule targets one or more remotes.
|
||||
|
||||
### schedule.sh
|
||||
|
||||
Reads schedules from `/etc/gniza/schedules.d/` (decoupled from remotes).
|
||||
Reads schedules from `/etc/gniza4cp/schedules.d/` (decoupled from remotes).
|
||||
|
||||
| Function | Description |
|
||||
|----------|-------------|
|
||||
@@ -510,10 +510,10 @@ Reads schedules from `/etc/gniza/schedules.d/` (decoupled from remotes).
|
||||
| `has_schedules()` | Check if any schedule configs exist |
|
||||
| `load_schedule(name)` | Source config, set SCHEDULE/SCHEDULE_REMOTES globals |
|
||||
| `schedule_to_cron(name)` | Convert SCHEDULE vars to 5-field cron expression |
|
||||
| `build_cron_line(name)` | Full cron line with gniza command, `--remote=` flag, and log redirect |
|
||||
| `install_schedules()` | Strip old gniza cron entries, add new from all `schedules.d/` |
|
||||
| `show_schedules()` | Display current gniza cron entries |
|
||||
| `remove_schedules()` | Remove all gniza cron entries |
|
||||
| `build_cron_line(name)` | Full cron line with gniza4cp command, `--remote=` flag, and log redirect |
|
||||
| `install_schedules()` | Strip old gniza4cp cron entries, add new from all `schedules.d/` |
|
||||
| `show_schedules()` | Display current gniza4cp cron entries |
|
||||
| `remove_schedules()` | Remove all gniza4cp cron entries |
|
||||
|
||||
### restore.sh
|
||||
|
||||
@@ -530,7 +530,7 @@ All restore functions dispatch by `_is_rclone_mode` — using `rclone_from_remot
|
||||
| `_rsync_download(src, dest)` | Download helper — dispatches rclone_from_remote or rsync |
|
||||
| `_detect_pkgacct_base(user, ts)` | Detect old vs new snapshot format (SSH or cloud) |
|
||||
|
||||
### bin/gniza (CLI helpers)
|
||||
### bin/gniza4cp (CLI helpers)
|
||||
|
||||
| Function | Description |
|
||||
|----------|-------------|
|
||||
@@ -541,20 +541,19 @@ All restore functions dispatch by `_is_rclone_mode` — using `rclone_from_remot
|
||||
| `_list_current_remote(account)` | Display listing for current remote context |
|
||||
| `_test_connection()` | Dispatch `test_rclone_connection` or `test_ssh_connection` by type |
|
||||
| `_status_ssh_and_disk()` | Connection test + disk/storage usage display (SSH: df, cloud: rclone about) |
|
||||
| `_init_remote(name)` | Interactive remote destination setup |
|
||||
| `cmd_remote()` | Remote management: list, delete |
|
||||
| `cmd_schedule()` | Schedule CRUD: add, delete, list, install, show, remove |
|
||||
|
||||
### GnizaWHM::UI (WHM plugin)
|
||||
### Gniza4cpWHM::UI (WHM plugin)
|
||||
|
||||
| Function | Description |
|
||||
|----------|-------------|
|
||||
| `is_configured()` | True if any remote configs exist in `remotes.d/` |
|
||||
| `detect_ssh_keys()` | Scan `/root/.ssh/` for key files, return arrayref of hashes |
|
||||
| `render_ssh_guidance()` | HTML block: detected keys + keygen/ssh-copy-id instructions |
|
||||
| `has_remotes()` | Check if `/etc/gniza/remotes.d/` has `.conf` files |
|
||||
| `has_remotes()` | Check if `/etc/gniza4cp/remotes.d/` has `.conf` files |
|
||||
| `list_remotes()` | Return sorted list of remote names |
|
||||
| `has_schedules()` | Check if `/etc/gniza/schedules.d/` has `.conf` files |
|
||||
| `has_schedules()` | Check if `/etc/gniza4cp/schedules.d/` has `.conf` files |
|
||||
| `list_schedules()` | Return sorted list of schedule names |
|
||||
| `schedule_conf_path($name)` | Return path to schedule config file |
|
||||
| `esc($str)` | HTML-escape a string |
|
||||
@@ -571,19 +570,19 @@ All restore functions dispatch by `_is_rclone_mode` — using `rclone_from_remot
|
||||
| `test_ssh_connection(%args)` | Test SSH connection via ssh (accepts named args or positional for backward compat) |
|
||||
| `test_rclone_connection(%args)` | Test S3/GDrive connection via rclone (generates temp config, runs `rclone lsd`) |
|
||||
|
||||
### GnizaWHM::Runner (WHM plugin)
|
||||
### Gniza4cpWHM::Runner (WHM plugin)
|
||||
|
||||
Pattern-based command runner for safe CLI execution from the WHM UI. Each allowed command has regex patterns per argument position.
|
||||
|
||||
| Function | Description |
|
||||
|----------|-------------|
|
||||
| `run($cmd, $subcmd, \@args, \%opts)` | Validate against allowlist and execute gniza CLI |
|
||||
| `run($cmd, $subcmd, \@args, \%opts)` | Validate against allowlist and execute gniza4cp CLI |
|
||||
|
||||
Allowed commands: `restore account/files/database/mailbox/list-databases/list-mailboxes`, `list`.
|
||||
Named option patterns: `--remote`, `--timestamp`, `--path`, `--account`, `--terminate`, `--exclude`.
|
||||
Path traversal prevention: `--account` and `--path` values containing `..` are rejected.
|
||||
|
||||
### GnizaWHM::Config
|
||||
### Gniza4cpWHM::Config
|
||||
|
||||
Pure Perl config parser/writer. Uses `flock(LOCK_EX)` with single file handle for TOCTOU-safe reads and writes.
|
||||
|
||||
@@ -597,7 +596,7 @@ Pure Perl config parser/writer. Uses `flock(LOCK_EX)` with single file handle fo
|
||||
| `@REMOTE_KEYS` | Remote config keys (REMOTE_TYPE, SSH, S3, GDrive, transfer, retention — no SCHEDULE*) |
|
||||
| `@SCHEDULE_KEYS` | Schedule config keys (SCHEDULE, SCHEDULE_TIME, SCHEDULE_DAY, SCHEDULE_CRON, REMOTES, SYSBACKUP, SKIP_SUSPENDED) |
|
||||
|
||||
### GnizaWHM::Validator
|
||||
### Gniza4cpWHM::Validator
|
||||
|
||||
| Function | Description |
|
||||
|----------|-------------|
|
||||
@@ -630,12 +629,12 @@ Tests use a simple `assert_eq`/`assert_ok`/`assert_fail` framework defined in `t
|
||||
### Adding a new library function
|
||||
|
||||
1. Add to the appropriate `lib/<module>.sh`
|
||||
2. Functions are automatically available — libraries are sourced in `bin/gniza`
|
||||
2. Functions are automatically available — libraries are sourced in `bin/gniza4cp`
|
||||
3. Use `local` for all variables, `log_*` for output, `return 1` for errors
|
||||
|
||||
### Adding a new command
|
||||
|
||||
1. Add `cmd_<name>()` function in `bin/gniza`
|
||||
1. Add `cmd_<name>()` function in `bin/gniza4cp`
|
||||
2. Add routing in `main()` case statement
|
||||
3. Update `cmd_usage()` help text
|
||||
4. Update `README.md` commands table
|
||||
@@ -645,7 +644,7 @@ Tests use a simple `assert_eq`/`assert_ok`/`assert_fail` framework defined in `t
|
||||
1. Add `DEFAULT_<NAME>` to `lib/constants.sh`
|
||||
2. Add to `load_config()` in `lib/config.sh` with fallback
|
||||
3. Add validation in `validate_config()` if needed
|
||||
4. Add to `etc/gniza.conf.example`
|
||||
4. Add to `etc/gniza4cp.conf.example`
|
||||
5. Document in `README.md` and this file
|
||||
|
||||
### Making a function remote-aware
|
||||
@@ -663,25 +662,25 @@ _restore_remote_globals
|
||||
|
||||
### Adding a new WHM plugin page
|
||||
|
||||
1. Create `whm/gniza-whm/<name>.cgi` following the pattern of existing CGIs
|
||||
2. Use same boilerplate: shebang, `use lib`, `Whostmgr::HTMLInterface`, `Cpanel::Form`, `GnizaWHM::UI`
|
||||
1. Create `whm/gniza4cp-whm/<name>.cgi` following the pattern of existing CGIs
|
||||
2. Use same boilerplate: shebang, `use lib`, `Whostmgr::HTMLInterface`, `Cpanel::Form`, `Gniza4cpWHM::UI`
|
||||
3. Route by `$form->{'action'}` or similar param
|
||||
4. Use `GnizaWHM::UI::page_header()`, `render_nav()`, `render_flash()`, `csrf_hidden_field()`, `page_footer()`
|
||||
4. Use `Gniza4cpWHM::UI::page_header()`, `render_nav()`, `render_flash()`, `csrf_hidden_field()`, `page_footer()`
|
||||
5. Validate POST with `verify_csrf_token()`, redirect with 302 after success
|
||||
6. No AppConfig change needed — `url=/cgi/gniza-whm/` covers all CGIs in the directory
|
||||
6. No AppConfig change needed — `url=/cgi/gniza4cp-whm/` covers all CGIs in the directory
|
||||
7. Add any new DaisyUI/Tailwind classes to `assets/src/safelist.html` and rebuild CSS
|
||||
8. Add the page to `@NAV_ITEMS` in `UI.pm` if it should appear in the tab bar
|
||||
|
||||
### Adding a new cPanel plugin page
|
||||
|
||||
1. Create `cpanel/gniza/<name>.live.cgi` (note `.live.cgi` extension for Jupiter theme)
|
||||
2. Use same boilerplate: shebang, `use lib` pointing to CGI lib dir, `Cpanel::Form`, `GnizaCPanel::UI`
|
||||
3. For privilege escalation, call AdminBin: `Cpanel::AdminBin::Call::call('Gniza', 'Restore', 'ACTION', @args)`
|
||||
4. Use `GnizaCPanel::UI::page_header()`, `csrf_hidden_field()`, `page_footer()`
|
||||
1. Create `cpanel/gniza4cp/<name>.live.cgi` (note `.live.cgi` extension for Jupiter theme)
|
||||
2. Use same boilerplate: shebang, `use lib` pointing to CGI lib dir, `Cpanel::Form`, `Gniza4cpCPanel::UI`
|
||||
3. For privilege escalation, call AdminBin: `Cpanel::AdminBin::Call::call('Gniza4cp', 'Restore', 'ACTION', @args)`
|
||||
4. Use `Gniza4cpCPanel::UI::page_header()`, `csrf_hidden_field()`, `page_footer()`
|
||||
5. Validate POST with `verify_csrf_token()`, redirect with 302 after success
|
||||
6. For new AdminBin actions: add the action method to `cpanel/admin/Gniza/Restore` and to `_actions()` list
|
||||
6. For new AdminBin actions: add the action method to `cpanel/admin/Gniza4cp/Restore` and to `_actions()` list
|
||||
7. Add the CGI copy command to `scripts/install.sh` in the cPanel section
|
||||
8. CSS is shared with WHM — same `gniza-whm.css` file, same DaisyUI classes
|
||||
8. CSS is shared with WHM — same `gniza4cp-whm.css` file, same DaisyUI classes
|
||||
|
||||
### WHM CSS Policy
|
||||
|
||||
@@ -689,7 +688,7 @@ _restore_remote_globals
|
||||
|
||||
### WHM Theme & Color Palette
|
||||
|
||||
The WHM plugin uses a custom DaisyUI theme named `gniza` (defined in `assets/src/input.css`). Light-only, no dark mode.
|
||||
The WHM plugin uses a custom DaisyUI theme named `gniza4cp` (defined in `assets/src/input.css`). Light-only, no dark mode.
|
||||
|
||||
| Role | OKLCH Value | Approx Color |
|
||||
|------|-------------|-------------|
|
||||
@@ -718,13 +717,13 @@ All WHM pages use Tailwind CSS v4 with DaisyUI v5 for styling. The CSS is built
|
||||
|
||||
**Build:**
|
||||
```bash
|
||||
cd whm/gniza-whm/assets && npm install && npm run build:css
|
||||
cd whm/gniza4cp-whm/assets && npm install && npm run build:css
|
||||
```
|
||||
|
||||
**Key files:**
|
||||
- `assets/src/input.css` — Tailwind entry point with DaisyUI plugin config
|
||||
- `assets/src/safelist.html` — Class safelist (required because Tailwind v4 scanner doesn't recognize `.cgi`/`.pm` file extensions)
|
||||
- `assets/gniza-whm.css` — Built output (committed to repo)
|
||||
- `assets/gniza4cp-whm.css` — Built output (committed to repo)
|
||||
|
||||
**WHM CSS delivery quirks:**
|
||||
- WHM's CGI directory cannot serve static files directly
|
||||
@@ -735,56 +734,56 @@ cd whm/gniza-whm/assets && npm install && npm run build:css
|
||||
|
||||
**Adding new CSS classes:**
|
||||
1. Add the class to `assets/src/safelist.html` (since Tailwind can't scan `.cgi`/`.pm` files)
|
||||
2. Rebuild: `cd whm/gniza-whm/assets && npm run build:css`
|
||||
3. Commit the updated `gniza-whm.css`
|
||||
2. Rebuild: `cd whm/gniza4cp-whm/assets && npm run build:css`
|
||||
3. Commit the updated `gniza4cp-whm.css`
|
||||
|
||||
### Install / Uninstall Scripts
|
||||
|
||||
**install.sh** (`scripts/install.sh`) — must be run as root. Detects whether running from a local clone or downloads via git. Installs to `/usr/local/gniza/`.
|
||||
**install.sh** (`scripts/install.sh`) — must be run as root. Detects whether running from a local clone or downloads via git. Installs to `/usr/local/gniza4cp/`.
|
||||
|
||||
Install steps:
|
||||
1. Copy `bin/`, `lib/`, `etc/` to `/usr/local/gniza/`
|
||||
2. Create symlink `/usr/local/bin/gniza` → `/usr/local/gniza/bin/gniza`
|
||||
3. Create working directory `/usr/local/gniza/workdir`
|
||||
4. Create config directories `/etc/gniza/remotes.d/` and `/etc/gniza/schedules.d/` (mode `0700`)
|
||||
5. Copy example configs to `/etc/gniza/`
|
||||
6. Create log directory `/var/log/gniza/`
|
||||
7. If WHM detected: copy `whm/gniza-whm/` to CGI dir, register via `register_appconfig`
|
||||
1. Copy `bin/`, `lib/`, `etc/` to `/usr/local/gniza4cp/`
|
||||
2. Create symlink `/usr/local/bin/gniza4cp` → `/usr/local/gniza4cp/bin/gniza4cp`
|
||||
3. Create working directory `/usr/local/gniza4cp/workdir`
|
||||
4. Create config directories `/etc/gniza4cp/remotes.d/` and `/etc/gniza4cp/schedules.d/` (mode `0700`)
|
||||
5. Copy example configs to `/etc/gniza4cp/`
|
||||
6. Create log directory `/var/log/gniza4cp/`
|
||||
7. If WHM detected: copy `whm/gniza4cp-whm/` to CGI dir, register via `register_appconfig`
|
||||
8. If cPanel detected: copy CGIs + lib + assets to Jupiter theme dir, install AdminBin module, register via `install_plugin`
|
||||
|
||||
**uninstall.sh** (`scripts/uninstall.sh`) — must be run as root. Also installed to `/usr/local/gniza/uninstall.sh`.
|
||||
**uninstall.sh** (`scripts/uninstall.sh`) — must be run as root. Also installed to `/usr/local/gniza4cp/uninstall.sh`.
|
||||
|
||||
Uninstall steps:
|
||||
1. Remove symlink and install directory
|
||||
2. Remove gniza cron entries (lines matching `# gniza:`)
|
||||
2. Remove gniza4cp cron entries (lines matching `# gniza4cp:`)
|
||||
3. If WHM plugin exists: unregister via `unregister_appconfig`, remove directory
|
||||
4. If cPanel plugin exists: unregister via `uninstall_plugin`, remove CGI directory and AdminBin module
|
||||
5. Print manual cleanup instructions for `/etc/gniza/`, `/var/log/gniza/`, `/var/run/gniza.lock`
|
||||
5. Print manual cleanup instructions for `/etc/gniza4cp/`, `/var/log/gniza4cp/`, `/var/run/gniza4cp.lock`
|
||||
|
||||
**cPanel plugin registration quirk:** Both `install_plugin` and `uninstall_plugin` expect a **tar.gz archive** containing `install.json` — not a raw JSON file path. Passing a JSON file directly prints usage help and does nothing. The scripts create a temporary tar.gz:
|
||||
|
||||
```bash
|
||||
PLUGIN_TMPDIR="$(mktemp -d)"
|
||||
cp "$SOURCE_DIR/cpanel/gniza/install.json" "$PLUGIN_TMPDIR/"
|
||||
tar -czf "$PLUGIN_TMPDIR/gniza-cpanel.tar.gz" -C "$PLUGIN_TMPDIR" install.json
|
||||
/usr/local/cpanel/scripts/install_plugin "$PLUGIN_TMPDIR/gniza-cpanel.tar.gz"
|
||||
cp "$SOURCE_DIR/cpanel/gniza4cp/install.json" "$PLUGIN_TMPDIR/"
|
||||
tar -czf "$PLUGIN_TMPDIR/gniza4cp-cpanel.tar.gz" -C "$PLUGIN_TMPDIR" install.json
|
||||
/usr/local/cpanel/scripts/install_plugin "$PLUGIN_TMPDIR/gniza4cp-cpanel.tar.gz"
|
||||
rm -rf "$PLUGIN_TMPDIR"
|
||||
```
|
||||
|
||||
`install.json` is also copied to the CGI directory (`$CPANEL_BASE/gniza/install.json`) so the uninstall script can find it.
|
||||
`install.json` is also copied to the CGI directory (`$CPANEL_BASE/gniza4cp/install.json`) so the uninstall script can find it.
|
||||
|
||||
### Upgrade Considerations
|
||||
|
||||
**CSRF/flash storage migration (WHM):** Older versions stored CSRF tokens and flash messages as plain files at `/var/cpanel/.gniza-whm-csrf` and `/var/cpanel/.gniza-whm-flash`. Current versions use these as **directories** containing token files. `_ensure_dir()` in `GnizaWHM::UI` handles this automatically — it removes stale plain files before creating directories. Without this, CSRF token writes fail silently and all form submissions show "Invalid or expired form token."
|
||||
**CSRF/flash storage migration (WHM):** Older versions stored CSRF tokens and flash messages as plain files at `/var/cpanel/.gniza4cp-whm-csrf` and `/var/cpanel/.gniza4cp-whm-flash`. Current versions use these as **directories** containing token files. `_ensure_dir()` in `Gniza4cpWHM::UI` handles this automatically — it removes stale plain files before creating directories. Without this, CSRF token writes fail silently and all form submissions show "Invalid or expired form token."
|
||||
|
||||
**CSRF token write robustness:** `generate_csrf_token()` uses `_safe_write()` (O_CREAT|O_EXCL) with a fallback to plain `open '>'` write. This ensures the token is always persisted even if the O_EXCL approach fails (e.g., race conditions, filesystem quirks).
|
||||
|
||||
**SMTP test + form token sync (WHM settings.cgi):** The SMTP test AJAX endpoint consumes the CSRF token and returns a new one. The JS handler updates both the AJAX variable (`gnizaCsrf`) and the main form's hidden `gniza_csrf` field. Without this sync, submitting the main form after an SMTP test would always fail CSRF validation.
|
||||
**SMTP test + form token sync (WHM settings.cgi):** The SMTP test AJAX endpoint consumes the CSRF token and returns a new one. The JS handler updates both the AJAX variable (`gniza4cpCsrf`) and the main form's hidden `gniza4cp_csrf` field. Without this sync, submitting the main form after an SMTP test would always fail CSRF validation.
|
||||
|
||||
### 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 |
|
||||
| **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/ |
|
||||
|
||||
177
README.md
@@ -1,4 +1,4 @@
|
||||
# gniza
|
||||
# GNIZA
|
||||
|
||||
cPanel Backup, Restore & Disaster Recovery tool.
|
||||
|
||||
@@ -8,103 +8,96 @@ Uses `pkgacct --nocompress --skiphomedir` for account backups, gzips SQL files i
|
||||
|
||||
| | 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 |
|
||||
| **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
|
||||
|
||||
```bash
|
||||
curl -sSL http://192.168.100.100:3001/shukivaknin/gniza/raw/branch/main/scripts/install.sh | sudo bash
|
||||
```
|
||||
|
||||
Or from a local clone:
|
||||
From a clone:
|
||||
|
||||
```bash
|
||||
git clone ssh://git@192.168.100.100:2222/shukivaknin/gniza.git
|
||||
cd gniza
|
||||
git clone https://git.linux-hosting.co.il/shukivaknin/gniza4cp.git
|
||||
cd gniza4cp
|
||||
sudo bash scripts/install.sh
|
||||
```
|
||||
|
||||
To uninstall:
|
||||
|
||||
```bash
|
||||
sudo bash /usr/local/gniza/uninstall.sh # from installed copy
|
||||
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/gniza/`) and logs (`/var/log/gniza/`) are preserved — remove manually if desired.
|
||||
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
|
||||
|
||||
```bash
|
||||
# Interactive setup (creates config + first remote + optional schedule)
|
||||
sudo gniza init
|
||||
|
||||
# Add additional remote destinations
|
||||
sudo gniza init remote offsite
|
||||
# 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 gniza backup --dry-run
|
||||
sudo gniza4cp backup --dry-run
|
||||
|
||||
# Run backup
|
||||
sudo gniza backup
|
||||
sudo gniza4cp backup
|
||||
|
||||
# Back up to specific remotes
|
||||
sudo gniza backup --remote=nas,offsite
|
||||
sudo gniza4cp backup --remote=nas,offsite
|
||||
```
|
||||
|
||||
## Commands
|
||||
|
||||
```
|
||||
gniza backup [--account=NAME] [--remote=NAME[,NAME2]] [--skip-suspended] [--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
|
||||
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/gniza/gniza.conf`) |
|
||||
| `--remote=NAME[,NAME2]` | Target specific remote(s) from `/etc/gniza/remotes.d/` (comma-separated) |
|
||||
| `--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/gniza/gniza.conf`
|
||||
**File:** `/etc/gniza4cp/gniza4cp.conf`
|
||||
|
||||
Controls local settings (accounts, logging, notifications). Remote destinations are configured in `/etc/gniza/remotes.d/`.
|
||||
Controls local settings (accounts, logging, notifications). Remote destinations are configured in `/etc/gniza4cp/remotes.d/`.
|
||||
|
||||
```bash
|
||||
# Local Settings
|
||||
TEMP_DIR="/usr/local/gniza/workdir" # Working dir for pkgacct output
|
||||
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/gniza"
|
||||
LOG_DIR="/var/log/gniza4cp"
|
||||
LOG_LEVEL="info" # debug, info, warn, error
|
||||
LOG_RETAIN=90 # Days to keep log files
|
||||
|
||||
@@ -113,39 +106,35 @@ NOTIFY_EMAIL="" # Email for notifications
|
||||
NOTIFY_ON="failure" # always, failure, never
|
||||
|
||||
# Advanced
|
||||
LOCK_FILE="/var/run/gniza.lock"
|
||||
LOCK_FILE="/var/run/gniza4cp.lock"
|
||||
SSH_TIMEOUT=30
|
||||
SSH_RETRIES=3
|
||||
RSYNC_EXTRA_OPTS=""
|
||||
```
|
||||
|
||||
See `etc/gniza.conf.example` for the full template.
|
||||
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/gniza/remotes.d/`.
|
||||
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
|
||||
|
||||
```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
|
||||
# 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 gniza remote list
|
||||
sudo gniza4cp remote list
|
||||
|
||||
# Delete a remote
|
||||
sudo gniza remote delete nas
|
||||
sudo gniza4cp remote delete nas
|
||||
```
|
||||
|
||||
#### Remote Config Format
|
||||
|
||||
Each file in `/etc/gniza/remotes.d/<name>.conf`:
|
||||
Each file in `/etc/gniza4cp/remotes.d/<name>.conf`:
|
||||
|
||||
```bash
|
||||
# Remote type: "ssh" (default), "s3", or "gdrive"
|
||||
@@ -187,22 +176,22 @@ Without `--remote`, backup/list/verify operate on **all** configured remotes. Re
|
||||
|
||||
```bash
|
||||
# Back up to all remotes
|
||||
sudo gniza backup
|
||||
sudo gniza4cp backup
|
||||
|
||||
# Back up to specific remote(s)
|
||||
sudo gniza backup --remote=nas
|
||||
sudo gniza backup --remote=nas,offsite
|
||||
sudo gniza4cp backup --remote=nas
|
||||
sudo gniza4cp backup --remote=nas,offsite
|
||||
|
||||
# List snapshots on a specific remote
|
||||
sudo gniza list --remote=offsite
|
||||
sudo gniza4cp list --remote=offsite
|
||||
|
||||
# Restore requires explicit remote
|
||||
sudo gniza restore account johndoe --remote=nas
|
||||
sudo gniza4cp 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.
|
||||
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
|
||||
|
||||
@@ -222,22 +211,22 @@ SKIP_SUSPENDED="" # "yes" to skip cPanel suspended accounts
|
||||
|
||||
```bash
|
||||
# Interactive schedule creation
|
||||
sudo gniza schedule add nightly
|
||||
sudo gniza4cp schedule add nightly
|
||||
|
||||
# List configured schedules
|
||||
sudo gniza schedule list
|
||||
sudo gniza4cp schedule list
|
||||
|
||||
# Delete a schedule
|
||||
sudo gniza schedule delete nightly
|
||||
sudo gniza4cp schedule delete nightly
|
||||
|
||||
# Install all schedules to crontab
|
||||
sudo gniza schedule install
|
||||
sudo gniza4cp schedule install
|
||||
|
||||
# Show current gniza cron entries
|
||||
sudo gniza schedule show
|
||||
# Show current GNIZA cron entries
|
||||
sudo gniza4cp schedule show
|
||||
|
||||
# Remove all gniza cron entries
|
||||
sudo gniza schedule remove
|
||||
# Remove all GNIZA cron entries
|
||||
sudo gniza4cp schedule remove
|
||||
```
|
||||
|
||||
#### Schedule Types
|
||||
@@ -253,8 +242,8 @@ sudo gniza schedule remove
|
||||
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
|
||||
# gniza4cp:nightly
|
||||
0 2 * * * /usr/local/bin/gniza4cp backup --remote=nas,offsite >> /var/log/gniza4cp/cron-nightly.log 2>&1
|
||||
```
|
||||
|
||||
## Remote Directory Structure
|
||||
@@ -342,8 +331,8 @@ All restore commands require `--remote=NAME` to specify the source.
|
||||
## File Layout
|
||||
|
||||
```
|
||||
/usr/local/gniza/ # Install directory
|
||||
├── bin/gniza # CLI entrypoint
|
||||
/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_*
|
||||
@@ -363,12 +352,12 @@ All restore commands require `--remote=NAME` to specify the source.
|
||||
│ ├── remotes.sh # Remote discovery and context switching
|
||||
│ └── schedule.sh # Cron management for decoupled schedules
|
||||
└── etc/
|
||||
├── gniza.conf.example # Main config template
|
||||
├── gniza4cp.conf.example # Main config template
|
||||
├── remote.conf.example # Remote destination template
|
||||
└── schedule.conf.example # Schedule template
|
||||
|
||||
/etc/gniza/ # Runtime configuration
|
||||
├── gniza.conf # Main config
|
||||
/etc/gniza4cp/ # Runtime configuration
|
||||
├── gniza4cp.conf # Main config
|
||||
├── remotes.d/ # Remote destination configs
|
||||
│ ├── nas.conf
|
||||
│ └── offsite.conf
|
||||
@@ -376,29 +365,29 @@ All restore commands require `--remote=NAME` to specify the source.
|
||||
├── nightly.conf
|
||||
└── weekly-offsite.conf
|
||||
|
||||
/var/log/gniza/ # Log files
|
||||
├── gniza-20260303-020000.log # Per-run logs
|
||||
/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.
|
||||
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**.
|
||||
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/`.
|
||||
Plugin files are deployed to `/usr/local/cpanel/whostmgr/docroot/cgi/gniza4cp-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**:
|
||||
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/gniza/remotes.d/`.
|
||||
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.
|
||||
|
||||
@@ -412,15 +401,15 @@ The wizard is also accessible anytime from the dashboard quick links ("Run Setup
|
||||
| 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`) |
|
||||
| Settings | `settings.cgi` | Edit main config (`/etc/gniza4cp/gniza4cp.conf`) |
|
||||
| Setup Wizard | `setup.cgi` | Guided initial configuration (3 steps) |
|
||||
|
||||
### Plugin File Layout
|
||||
|
||||
```
|
||||
whm/
|
||||
├── gniza-whm.conf # WHM AppConfig registration
|
||||
└── gniza-whm/
|
||||
├── gniza4cp-whm.conf # WHM AppConfig registration
|
||||
└── gniza4cp-whm/
|
||||
├── index.cgi # Dashboard
|
||||
├── setup.cgi # Setup wizard (3 steps)
|
||||
├── settings.cgi # Main config editor
|
||||
@@ -428,12 +417,12 @@ whm/
|
||||
├── schedules.cgi # Schedule CRUD + cron toggles
|
||||
├── restore.cgi # Restore workflow (account → remote → snapshot → type)
|
||||
├── assets/
|
||||
│ ├── gniza-whm.css # Built Tailwind/DaisyUI CSS (committed)
|
||||
│ ├── 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/GnizaWHM/
|
||||
└── lib/Gniza4cpWHM/
|
||||
├── Config.pm # Config parser/writer (pure Perl)
|
||||
├── Validator.pm # Input validation
|
||||
├── Cron.pm # Cron read + per-schedule install/remove
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
#!/usr/bin/env bash
|
||||
# gniza — cPanel Backup, Restore & Disaster Recovery
|
||||
# gniza4cp — cPanel Backup, Restore & Disaster Recovery
|
||||
# CLI entrypoint and command routing
|
||||
|
||||
set -euo pipefail
|
||||
@@ -256,7 +256,7 @@ cmd_backup() {
|
||||
[[ -n "$remote_flag" ]] && sysbackup_args+=(--remote="$remote_flag")
|
||||
[[ "$dry_run" == "true" ]] && sysbackup_args+=(--dry-run)
|
||||
# Run as subprocess so its exit doesn't kill our process
|
||||
/usr/local/bin/gniza sysbackup "${sysbackup_args[@]}" || log_error "System backup failed"
|
||||
/usr/local/bin/gniza4cp sysbackup "${sysbackup_args[@]}" || log_error "System backup failed"
|
||||
acquire_lock
|
||||
fi
|
||||
|
||||
@@ -301,7 +301,7 @@ cmd_restore() {
|
||||
account)
|
||||
local name="${1:-}"
|
||||
shift 2>/dev/null || true
|
||||
[[ -z "$name" ]] && die "Usage: gniza restore account <name> [--remote=NAME] [--timestamp=TS] [--terminate]"
|
||||
[[ -z "$name" ]] && die "Usage: gniza4cp restore account <name> [--remote=NAME] [--timestamp=TS] [--terminate]"
|
||||
validate_account_name "$name" || die "Invalid account name"
|
||||
|
||||
local config_file; config_file=$(get_opt config "$@" 2>/dev/null) || config_file="$DEFAULT_CONFIG_FILE"
|
||||
@@ -325,7 +325,7 @@ cmd_restore() {
|
||||
files)
|
||||
local name="${1:-}"
|
||||
shift 2>/dev/null || true
|
||||
[[ -z "$name" ]] && die "Usage: gniza restore files <name> [--remote=NAME] [--path=subpath] [--timestamp=TS]"
|
||||
[[ -z "$name" ]] && die "Usage: gniza4cp restore files <name> [--remote=NAME] [--path=subpath] [--timestamp=TS]"
|
||||
validate_account_name "$name" || die "Invalid account name"
|
||||
|
||||
local config_file; config_file=$(get_opt config "$@" 2>/dev/null) || config_file="$DEFAULT_CONFIG_FILE"
|
||||
@@ -347,7 +347,7 @@ cmd_restore() {
|
||||
database)
|
||||
local name="${1:-}"
|
||||
local dbname="${2:-}"
|
||||
[[ -z "$name" ]] && die "Usage: gniza restore database <name> [<dbname>] [--remote=NAME] [--timestamp=TS]"
|
||||
[[ -z "$name" ]] && die "Usage: gniza4cp restore database <name> [<dbname>] [--remote=NAME] [--timestamp=TS]"
|
||||
validate_account_name "$name" || die "Invalid account name"
|
||||
shift 2>/dev/null || true
|
||||
# If dbname looks like a flag, it's not a dbname
|
||||
@@ -378,7 +378,7 @@ cmd_restore() {
|
||||
mailbox)
|
||||
local name="${1:-}"
|
||||
local email="${2:-}"
|
||||
[[ -z "$name" ]] && die "Usage: gniza restore mailbox <name> [<email@domain>] [--remote=NAME] [--timestamp=TS]"
|
||||
[[ -z "$name" ]] && die "Usage: gniza4cp restore mailbox <name> [<email@domain>] [--remote=NAME] [--timestamp=TS]"
|
||||
validate_account_name "$name" || die "Invalid account name"
|
||||
shift 2>/dev/null || true
|
||||
# If email looks like a flag, it's not an email
|
||||
@@ -409,7 +409,7 @@ cmd_restore() {
|
||||
list-databases)
|
||||
local name="${1:-}"
|
||||
shift 2>/dev/null || true
|
||||
[[ -z "$name" ]] && die "Usage: gniza restore list-databases <name> [--remote=NAME] [--timestamp=TS]"
|
||||
[[ -z "$name" ]] && die "Usage: gniza4cp restore list-databases <name> [--remote=NAME] [--timestamp=TS]"
|
||||
|
||||
local config_file; config_file=$(get_opt config "$@" 2>/dev/null) || config_file="$DEFAULT_CONFIG_FILE"
|
||||
load_config "$config_file"
|
||||
@@ -427,7 +427,7 @@ cmd_restore() {
|
||||
list-mailboxes)
|
||||
local name="${1:-}"
|
||||
shift 2>/dev/null || true
|
||||
[[ -z "$name" ]] && die "Usage: gniza restore list-mailboxes <name> [--remote=NAME] [--timestamp=TS]"
|
||||
[[ -z "$name" ]] && die "Usage: gniza4cp restore list-mailboxes <name> [--remote=NAME] [--timestamp=TS]"
|
||||
|
||||
local config_file; config_file=$(get_opt config "$@" 2>/dev/null) || config_file="$DEFAULT_CONFIG_FILE"
|
||||
load_config "$config_file"
|
||||
@@ -445,7 +445,7 @@ cmd_restore() {
|
||||
list-files)
|
||||
local name="${1:-}"
|
||||
shift 2>/dev/null || true
|
||||
[[ -z "$name" ]] && die "Usage: gniza restore list-files <name> [--remote=NAME] [--timestamp=TS] [--path=subdir]"
|
||||
[[ -z "$name" ]] && die "Usage: gniza4cp restore list-files <name> [--remote=NAME] [--timestamp=TS] [--path=subdir]"
|
||||
|
||||
local config_file; config_file=$(get_opt config "$@" 2>/dev/null) || config_file="$DEFAULT_CONFIG_FILE"
|
||||
load_config "$config_file"
|
||||
@@ -464,7 +464,7 @@ cmd_restore() {
|
||||
list-dbusers)
|
||||
local name="${1:-}"
|
||||
shift 2>/dev/null || true
|
||||
[[ -z "$name" ]] && die "Usage: gniza restore list-dbusers <name> [--remote=NAME] [--timestamp=TS]"
|
||||
[[ -z "$name" ]] && die "Usage: gniza4cp restore list-dbusers <name> [--remote=NAME] [--timestamp=TS]"
|
||||
|
||||
local config_file; config_file=$(get_opt config "$@" 2>/dev/null) || config_file="$DEFAULT_CONFIG_FILE"
|
||||
load_config "$config_file"
|
||||
@@ -482,7 +482,7 @@ cmd_restore() {
|
||||
list-cron)
|
||||
local name="${1:-}"
|
||||
shift 2>/dev/null || true
|
||||
[[ -z "$name" ]] && die "Usage: gniza restore list-cron <name> [--remote=NAME] [--timestamp=TS]"
|
||||
[[ -z "$name" ]] && die "Usage: gniza4cp restore list-cron <name> [--remote=NAME] [--timestamp=TS]"
|
||||
|
||||
local config_file; config_file=$(get_opt config "$@" 2>/dev/null) || config_file="$DEFAULT_CONFIG_FILE"
|
||||
load_config "$config_file"
|
||||
@@ -500,7 +500,7 @@ cmd_restore() {
|
||||
list-dns)
|
||||
local name="${1:-}"
|
||||
shift 2>/dev/null || true
|
||||
[[ -z "$name" ]] && die "Usage: gniza restore list-dns <name> [--remote=NAME] [--timestamp=TS]"
|
||||
[[ -z "$name" ]] && die "Usage: gniza4cp restore list-dns <name> [--remote=NAME] [--timestamp=TS]"
|
||||
|
||||
local config_file; config_file=$(get_opt config "$@" 2>/dev/null) || config_file="$DEFAULT_CONFIG_FILE"
|
||||
load_config "$config_file"
|
||||
@@ -518,7 +518,7 @@ cmd_restore() {
|
||||
list-ssl)
|
||||
local name="${1:-}"
|
||||
shift 2>/dev/null || true
|
||||
[[ -z "$name" ]] && die "Usage: gniza restore list-ssl <name> [--remote=NAME] [--timestamp=TS]"
|
||||
[[ -z "$name" ]] && die "Usage: gniza4cp restore list-ssl <name> [--remote=NAME] [--timestamp=TS]"
|
||||
|
||||
local config_file; config_file=$(get_opt config "$@" 2>/dev/null) || config_file="$DEFAULT_CONFIG_FILE"
|
||||
load_config "$config_file"
|
||||
@@ -536,7 +536,7 @@ cmd_restore() {
|
||||
cron)
|
||||
local name="${1:-}"
|
||||
shift 2>/dev/null || true
|
||||
[[ -z "$name" ]] && die "Usage: gniza restore cron <name> [--remote=NAME] [--timestamp=TS]"
|
||||
[[ -z "$name" ]] && die "Usage: gniza4cp restore cron <name> [--remote=NAME] [--timestamp=TS]"
|
||||
|
||||
local config_file; config_file=$(get_opt config "$@" 2>/dev/null) || config_file="$DEFAULT_CONFIG_FILE"
|
||||
load_config "$config_file"
|
||||
@@ -554,7 +554,7 @@ cmd_restore() {
|
||||
dbusers)
|
||||
local name="${1:-}"
|
||||
local specific_dbuser="${2:-}"
|
||||
[[ -z "$name" ]] && die "Usage: gniza restore dbusers <name> [<dbuser>] [--remote=NAME] [--timestamp=TS]"
|
||||
[[ -z "$name" ]] && die "Usage: gniza4cp restore dbusers <name> [<dbuser>] [--remote=NAME] [--timestamp=TS]"
|
||||
shift 2>/dev/null || true
|
||||
if [[ -n "$specific_dbuser" && "$specific_dbuser" != --* ]]; then
|
||||
shift 2>/dev/null || true
|
||||
@@ -578,7 +578,7 @@ cmd_restore() {
|
||||
cpconfig)
|
||||
local name="${1:-}"
|
||||
shift 2>/dev/null || true
|
||||
[[ -z "$name" ]] && die "Usage: gniza restore cpconfig <name> [--remote=NAME] [--timestamp=TS]"
|
||||
[[ -z "$name" ]] && die "Usage: gniza4cp restore cpconfig <name> [--remote=NAME] [--timestamp=TS]"
|
||||
|
||||
local config_file; config_file=$(get_opt config "$@" 2>/dev/null) || config_file="$DEFAULT_CONFIG_FILE"
|
||||
load_config "$config_file"
|
||||
@@ -596,7 +596,7 @@ cmd_restore() {
|
||||
domains)
|
||||
local name="${1:-}"
|
||||
local specific_domain="${2:-}"
|
||||
[[ -z "$name" ]] && die "Usage: gniza restore domains <name> [<domain>] [--remote=NAME] [--timestamp=TS]"
|
||||
[[ -z "$name" ]] && die "Usage: gniza4cp restore domains <name> [<domain>] [--remote=NAME] [--timestamp=TS]"
|
||||
shift 2>/dev/null || true
|
||||
if [[ -n "$specific_domain" && "$specific_domain" != --* ]]; then
|
||||
shift 2>/dev/null || true
|
||||
@@ -620,7 +620,7 @@ cmd_restore() {
|
||||
ssl)
|
||||
local name="${1:-}"
|
||||
local specific_cert="${2:-}"
|
||||
[[ -z "$name" ]] && die "Usage: gniza restore ssl <name> [<cert>] [--remote=NAME] [--timestamp=TS]"
|
||||
[[ -z "$name" ]] && die "Usage: gniza4cp restore ssl <name> [<cert>] [--remote=NAME] [--timestamp=TS]"
|
||||
shift 2>/dev/null || true
|
||||
if [[ -n "$specific_cert" && "$specific_cert" != --* ]]; then
|
||||
shift 2>/dev/null || true
|
||||
@@ -656,7 +656,7 @@ cmd_restore() {
|
||||
restore_server "$timestamp"
|
||||
;;
|
||||
*)
|
||||
die "Unknown restore subcommand: $subcommand"$'\n'"Usage: gniza restore {account|files|database|mailbox|cron|dbusers|cpconfig|domains|ssl|list-databases|list-mailboxes|list-files|list-dbusers|list-cron|list-dns|list-ssl|server}"
|
||||
die "Unknown restore subcommand: $subcommand"$'\n'"Usage: gniza4cp restore {account|files|database|mailbox|cron|dbusers|cpconfig|domains|ssl|list-databases|list-mailboxes|list-files|list-dbusers|list-cron|list-dns|list-ssl|server}"
|
||||
;;
|
||||
esac
|
||||
}
|
||||
@@ -707,7 +707,7 @@ cmd_list() {
|
||||
shift
|
||||
local remote_flag=""
|
||||
remote_flag=$(get_opt remote "$@" 2>/dev/null) || true
|
||||
[[ -z "$remote_flag" ]] && die "Usage: gniza list accounts --remote=NAME"
|
||||
[[ -z "$remote_flag" ]] && die "Usage: gniza4cp list accounts --remote=NAME"
|
||||
local remotes; remotes=$(get_target_remotes "$remote_flag") || die "Invalid remote"
|
||||
local rname; rname=$(head -1 <<< "$remotes")
|
||||
_save_remote_globals
|
||||
@@ -847,7 +847,7 @@ cmd_status() {
|
||||
|
||||
local hostname; hostname=$(hostname -f)
|
||||
|
||||
echo "${C_BOLD}gniza v${GNIZA_VERSION}${C_RESET}"
|
||||
echo "${C_BOLD}gniza4cp v${GNIZA4CP_VERSION}${C_RESET}"
|
||||
echo ""
|
||||
echo "Hostname: $hostname"
|
||||
echo "Log level: ${LOG_LEVEL}"
|
||||
@@ -881,7 +881,7 @@ cmd_status() {
|
||||
done <<< "$remotes"
|
||||
_restore_remote_globals
|
||||
else
|
||||
echo "No remotes configured. Run 'gniza init remote <name>' to add one."
|
||||
echo "No remotes configured."
|
||||
fi
|
||||
|
||||
echo ""
|
||||
@@ -902,7 +902,7 @@ cmd_status() {
|
||||
# Last log
|
||||
local log_dir="${LOG_DIR:-$DEFAULT_LOG_DIR}"
|
||||
echo -n "Last log: "
|
||||
local last_log; last_log=$(ls -1t "$log_dir"/gniza-*.log 2>/dev/null | head -1)
|
||||
local last_log; last_log=$(ls -1t "$log_dir"/gniza4cp-*.log 2>/dev/null | head -1)
|
||||
if [[ -n "$last_log" ]]; then
|
||||
echo "$(basename "$last_log")"
|
||||
else
|
||||
@@ -918,7 +918,6 @@ cmd_remote() {
|
||||
list|ls|"")
|
||||
if ! has_remotes; then
|
||||
echo "No remotes configured."
|
||||
echo "Run 'gniza init remote <name>' to add one."
|
||||
return 0
|
||||
fi
|
||||
|
||||
@@ -950,7 +949,7 @@ cmd_remote() {
|
||||
delete|rm|remove)
|
||||
require_root
|
||||
local name="${1:-}"
|
||||
[[ -z "$name" ]] && die "Usage: gniza remote delete <name>"
|
||||
[[ -z "$name" ]] && die "Usage: gniza4cp remote delete <name>"
|
||||
|
||||
local conf="$REMOTES_DIR/${name}.conf"
|
||||
if [[ ! -f "$conf" ]]; then
|
||||
@@ -972,7 +971,7 @@ cmd_remote() {
|
||||
echo "Remote '$name' deleted."
|
||||
;;
|
||||
*)
|
||||
die "Unknown remote subcommand: $subcommand"$'\n'"Usage: gniza remote {list|delete <name>}"
|
||||
die "Unknown remote subcommand: $subcommand"$'\n'"Usage: gniza4cp remote {list|delete <name>}"
|
||||
;;
|
||||
esac
|
||||
}
|
||||
@@ -989,17 +988,17 @@ cmd_schedule() {
|
||||
case "$subcommand" in
|
||||
add)
|
||||
local name="${1:-}"
|
||||
[[ -z "$name" ]] && die "Usage: gniza schedule add <name>"
|
||||
[[ -z "$name" ]] && die "Usage: gniza4cp schedule add <name>"
|
||||
_schedule_add "$name"
|
||||
;;
|
||||
delete|rm|remove-schedule)
|
||||
local name="${1:-}"
|
||||
[[ -z "$name" ]] && die "Usage: gniza schedule delete <name>"
|
||||
[[ -z "$name" ]] && die "Usage: gniza4cp schedule delete <name>"
|
||||
_schedule_delete "$name"
|
||||
;;
|
||||
run)
|
||||
local name="${1:-}"
|
||||
[[ -z "$name" ]] && die "Usage: gniza schedule run <name>"
|
||||
[[ -z "$name" ]] && die "Usage: gniza4cp schedule run <name>"
|
||||
_schedule_run "$name"
|
||||
;;
|
||||
list|ls)
|
||||
@@ -1008,7 +1007,7 @@ cmd_schedule() {
|
||||
install) install_schedules ;;
|
||||
show) show_schedules ;;
|
||||
remove) remove_schedules ;;
|
||||
*) die "Unknown schedule subcommand: $subcommand"$'\n'"Usage: gniza schedule {add|delete|run|list|install|show|remove}" ;;
|
||||
*) die "Unknown schedule subcommand: $subcommand"$'\n'"Usage: gniza4cp schedule {add|delete|run|list|install|show|remove}" ;;
|
||||
esac
|
||||
}
|
||||
|
||||
@@ -1027,7 +1026,7 @@ _schedule_add() {
|
||||
[[ "$answer" =~ ^[Yy]$ ]] || { echo "Aborted."; exit 0; }
|
||||
fi
|
||||
|
||||
echo "${C_BOLD}gniza schedule add${C_RESET} — New schedule: ${C_BOLD}$name${C_RESET}"
|
||||
echo "${C_BOLD}gniza4cp schedule add${C_RESET} — New schedule: ${C_BOLD}$name${C_RESET}"
|
||||
echo ""
|
||||
|
||||
echo "Schedule options: hourly, daily, weekly, monthly, custom"
|
||||
@@ -1081,9 +1080,9 @@ _schedule_add() {
|
||||
# Write config
|
||||
mkdir -p "$SCHEDULES_DIR"
|
||||
cat > "$config_file" <<CONF
|
||||
# gniza schedule config: $name
|
||||
# Generated by 'gniza schedule add $name'
|
||||
# $(date -u +"%Y-%m-%d %H:%M:%S UTC")
|
||||
# gniza4cp schedule config: $name
|
||||
# Generated by 'gniza4cp schedule add $name'
|
||||
# $(date -u +"%d/%m/%Y %H:%M:%S UTC")
|
||||
|
||||
SCHEDULE="$sched_type"
|
||||
SCHEDULE_TIME="$sched_time"
|
||||
@@ -1095,7 +1094,7 @@ CONF
|
||||
|
||||
echo ""
|
||||
echo "${C_GREEN}Schedule '$name' created: $config_file${C_RESET}"
|
||||
echo "Run 'gniza schedule install' to activate cron entries."
|
||||
echo "Run 'gniza4cp schedule install' to activate cron entries."
|
||||
}
|
||||
|
||||
_schedule_delete() {
|
||||
@@ -1117,7 +1116,7 @@ _schedule_delete() {
|
||||
|
||||
rm -f "$config_file"
|
||||
echo "Schedule '$name' deleted."
|
||||
echo "Run 'gniza schedule install' to update cron entries."
|
||||
echo "Run 'gniza4cp schedule install' to update cron entries."
|
||||
}
|
||||
|
||||
_schedule_run() {
|
||||
@@ -1148,13 +1147,13 @@ _schedule_run() {
|
||||
echo ""
|
||||
|
||||
# Exec replaces this process with the backup command
|
||||
exec /usr/local/bin/gniza backup "${args[@]}"
|
||||
exec /usr/local/bin/gniza4cp backup "${args[@]}"
|
||||
}
|
||||
|
||||
_schedule_list() {
|
||||
if ! has_schedules; then
|
||||
echo "No schedules configured."
|
||||
echo "Run 'gniza schedule add <name>' to create one."
|
||||
echo "Run 'gniza4cp schedule add <name>' to create one."
|
||||
return 0
|
||||
fi
|
||||
|
||||
@@ -1176,170 +1175,6 @@ _schedule_list() {
|
||||
echo ""
|
||||
}
|
||||
|
||||
cmd_init() {
|
||||
local subcommand="${1:-}"
|
||||
|
||||
if [[ "$subcommand" == "remote" ]]; then
|
||||
shift
|
||||
_init_remote "$@"
|
||||
return
|
||||
fi
|
||||
|
||||
local config_dir="/etc/gniza"
|
||||
local config_file="$config_dir/gniza.conf"
|
||||
|
||||
echo "${C_BOLD}gniza init${C_RESET} — Setup wizard"
|
||||
echo ""
|
||||
|
||||
# Step 1: Create main config with local settings
|
||||
if [[ -f "$config_file" ]]; then
|
||||
echo "Config file already exists: $config_file"
|
||||
read -rp "Overwrite? [y/N] " answer
|
||||
[[ "$answer" =~ ^[Yy]$ ]] || {
|
||||
echo "Skipping main config."
|
||||
echo ""
|
||||
# Still offer to add a remote
|
||||
echo "Add a remote destination?"
|
||||
read -rp "Remote name (e.g. nas, offsite): " init_remote_name
|
||||
if [[ -n "$init_remote_name" ]]; then
|
||||
_init_remote "$init_remote_name"
|
||||
fi
|
||||
return
|
||||
}
|
||||
fi
|
||||
|
||||
read -rp "Notification email (empty to disable): " init_email
|
||||
|
||||
# Create config
|
||||
mkdir -p "$config_dir"
|
||||
mkdir -p "$config_dir/remotes.d"
|
||||
cat > "$config_file" <<CONF
|
||||
# gniza configuration — generated by 'gniza init'
|
||||
# $(date -u +"%Y-%m-%d %H:%M:%S UTC")
|
||||
#
|
||||
# Remote destinations are configured in /etc/gniza/remotes.d/<name>.conf
|
||||
# Run 'gniza init remote <name>' to add one.
|
||||
|
||||
TEMP_DIR="/usr/local/gniza/workdir"
|
||||
INCLUDE_ACCOUNTS=""
|
||||
EXCLUDE_ACCOUNTS="nobody"
|
||||
|
||||
LOG_DIR="/var/log/gniza"
|
||||
LOG_LEVEL="info"
|
||||
LOG_RETAIN=90
|
||||
|
||||
NOTIFY_EMAIL="$init_email"
|
||||
NOTIFY_ON="failure"
|
||||
|
||||
LOCK_FILE="/var/run/gniza.lock"
|
||||
SSH_TIMEOUT=30
|
||||
SSH_RETRIES=3
|
||||
RSYNC_EXTRA_OPTS=""
|
||||
CONF
|
||||
|
||||
echo ""
|
||||
echo "Config written to $config_file"
|
||||
|
||||
# Create log directory
|
||||
mkdir -p "${LOG_DIR:-$DEFAULT_LOG_DIR}"
|
||||
|
||||
# Step 2: Create first remote
|
||||
echo ""
|
||||
echo "Now let's set up your first remote destination."
|
||||
read -rp "Remote name (e.g. nas, offsite): " init_remote_name
|
||||
[[ -z "$init_remote_name" ]] && die "Remote name is required"
|
||||
|
||||
_init_remote "$init_remote_name"
|
||||
}
|
||||
|
||||
_init_remote() {
|
||||
local name="${1:-}"
|
||||
[[ -z "$name" ]] && die "Usage: gniza init remote <name>"
|
||||
|
||||
# Validate name (alphanumeric, hyphens, underscores)
|
||||
if ! [[ "$name" =~ ^[a-zA-Z0-9_-]+$ ]]; then
|
||||
die "Remote name must be alphanumeric (hyphens and underscores allowed): $name"
|
||||
fi
|
||||
|
||||
local config_dir="/etc/gniza/remotes.d"
|
||||
local config_file="$config_dir/${name}.conf"
|
||||
|
||||
echo "${C_BOLD}gniza init remote${C_RESET} — Remote setup: ${C_BOLD}$name${C_RESET}"
|
||||
echo ""
|
||||
|
||||
if [[ -f "$config_file" ]]; then
|
||||
echo "Remote config already exists: $config_file"
|
||||
read -rp "Overwrite? [y/N] " answer
|
||||
[[ "$answer" =~ ^[Yy]$ ]] || { echo "Aborted."; exit 0; }
|
||||
fi
|
||||
|
||||
read -rp "Remote host: " init_host
|
||||
[[ -z "$init_host" ]] && die "Remote host is required"
|
||||
|
||||
read -rp "Remote port [22]: " init_port
|
||||
init_port="${init_port:-22}"
|
||||
|
||||
read -rp "Remote user [root]: " init_user
|
||||
init_user="${init_user:-root}"
|
||||
|
||||
read -rp "SSH key path [/root/.ssh/id_rsa]: " init_key
|
||||
init_key="${init_key:-/root/.ssh/id_rsa}"
|
||||
|
||||
read -rp "Remote base directory [/backups]: " init_base
|
||||
init_base="${init_base:-/backups}"
|
||||
|
||||
read -rp "Retention count [30]: " init_retention
|
||||
init_retention="${init_retention:-30}"
|
||||
|
||||
read -rp "Bandwidth limit in KB/s [0 = unlimited]: " init_bwlimit
|
||||
init_bwlimit="${init_bwlimit:-0}"
|
||||
|
||||
# Create config
|
||||
mkdir -p "$config_dir"
|
||||
cat > "$config_file" <<CONF
|
||||
# gniza remote config: $name
|
||||
# Generated by 'gniza init remote $name'
|
||||
# $(date -u +"%Y-%m-%d %H:%M:%S UTC")
|
||||
|
||||
REMOTE_HOST="$init_host"
|
||||
REMOTE_PORT=$init_port
|
||||
REMOTE_USER="$init_user"
|
||||
REMOTE_KEY="$init_key"
|
||||
REMOTE_BASE="$init_base"
|
||||
|
||||
BWLIMIT=$init_bwlimit
|
||||
RETENTION_COUNT=$init_retention
|
||||
RSYNC_EXTRA_OPTS=""
|
||||
CONF
|
||||
|
||||
echo ""
|
||||
echo "Remote config written to $config_file"
|
||||
echo ""
|
||||
|
||||
# Test SSH
|
||||
# Load main config first for defaults, then load remote
|
||||
local main_config="/etc/gniza/gniza.conf"
|
||||
if [[ -f "$main_config" ]]; then
|
||||
load_config "$main_config"
|
||||
fi
|
||||
load_remote "$name"
|
||||
|
||||
echo "Testing SSH connection to $name..."
|
||||
if test_ssh_connection 2>/dev/null; then
|
||||
echo "${C_GREEN}SSH connection successful!${C_RESET}"
|
||||
echo "Creating remote base directory..."
|
||||
ensure_remote_dir "${REMOTE_BASE}/$(hostname -f)/accounts"
|
||||
echo "${C_GREEN}Remote directory created.${C_RESET}"
|
||||
else
|
||||
echo "${C_YELLOW}SSH connection failed. Check your settings in $config_file${C_RESET}"
|
||||
fi
|
||||
|
||||
echo ""
|
||||
echo "${C_GREEN}Remote '$name' configured!${C_RESET}"
|
||||
echo "Run 'gniza schedule add <name>' to set up a backup schedule."
|
||||
echo "Run 'gniza backup --remote=$name --dry-run' to test."
|
||||
}
|
||||
|
||||
# ── System Backup / Restore ───────────────────────────────────
|
||||
|
||||
# Transfer + finalize + retention for system backup on the current remote.
|
||||
@@ -1373,6 +1208,7 @@ cmd_sysbackup() {
|
||||
load_config "$config_file"
|
||||
validate_config || die "Invalid configuration"
|
||||
init_logging
|
||||
log_info "[TYPE:SYSBACKUP] System backup started"
|
||||
|
||||
local dry_run=false
|
||||
has_flag dry-run "$@" && dry_run=true
|
||||
@@ -1609,7 +1445,7 @@ cmd_stats() {
|
||||
local last_log=""
|
||||
local latest_log=""
|
||||
if [[ -d "$log_dir" ]]; then
|
||||
latest_log=$(ls -1t "$log_dir"/gniza-[0-9]*-[0-9]*.log 2>/dev/null | head -1) || true
|
||||
latest_log=$(ls -1t "$log_dir"/gniza4cp-[0-9]*-[0-9]*.log 2>/dev/null | head -1) || true
|
||||
fi
|
||||
if [[ -n "$latest_log" && -f "$latest_log" ]]; then
|
||||
last_log=$(basename "$latest_log")
|
||||
@@ -1621,7 +1457,7 @@ cmd_stats() {
|
||||
fi
|
||||
|
||||
# Build JSON
|
||||
local updated; updated=$(date -u +"%Y-%m-%dT%H%M%S")
|
||||
local updated; updated=$(date -u +"%d/%m/%Y %H:%M:%S")
|
||||
local remotes_json; remotes_json=$(IFS=','; echo "${remote_json_parts[*]}")
|
||||
local json="{\"updated\":\"$updated\",\"backed_up_accounts\":$total_accounts,\"snapshots\":$total_snapshots,\"remotes\":{$remotes_json},\"last_backup\":{\"status\":\"$last_status\",\"log\":\"$last_log\"}}"
|
||||
|
||||
@@ -1632,10 +1468,10 @@ cmd_stats() {
|
||||
|
||||
cmd_usage() {
|
||||
cat <<EOF
|
||||
${C_BOLD}gniza v${GNIZA_VERSION}${C_RESET} — cPanel Backup, Restore & Disaster Recovery
|
||||
${C_BOLD}gniza4cp v${GNIZA4CP_VERSION}${C_RESET} — cPanel Backup, Restore & Disaster Recovery
|
||||
|
||||
${C_BOLD}Usage:${C_RESET}
|
||||
gniza <command> [options]
|
||||
gniza4cp <command> [options]
|
||||
|
||||
${C_BOLD}Commands:${C_RESET}
|
||||
backup [--account=NAME] [--remote=NAME[,NAME2]] [--dry-run] [--sysbackup] [--skip-suspended]
|
||||
@@ -1665,34 +1501,30 @@ ${C_BOLD}Commands:${C_RESET}
|
||||
schedule list Show configured schedules
|
||||
schedule {install|show|remove} Manage cron entries
|
||||
stats Collect backup statistics
|
||||
init Setup config + first remote
|
||||
init remote <name> Add a remote destination
|
||||
version Show version
|
||||
|
||||
${C_BOLD}Global Options:${C_RESET}
|
||||
--config=PATH Use alternate config file (default: /etc/gniza/gniza.conf)
|
||||
--config=PATH Use alternate config file (default: /etc/gniza4cp/gniza4cp.conf)
|
||||
--remote=NAME Target specific remote(s), comma-separated
|
||||
--debug Enable debug logging
|
||||
|
||||
${C_BOLD}Examples:${C_RESET}
|
||||
gniza init
|
||||
gniza backup --dry-run
|
||||
gniza backup --account=johndoe
|
||||
gniza backup --remote=nas
|
||||
gniza backup --remote=nas,offsite
|
||||
gniza list --remote=offsite
|
||||
gniza restore files johndoe --remote=nas --path=public_html
|
||||
gniza restore database johndoe johndoe_wp --remote=nas
|
||||
gniza restore mailbox johndoe info@example.com --remote=nas
|
||||
gniza schedule add nightly
|
||||
gniza schedule list
|
||||
gniza schedule install
|
||||
gniza remote list
|
||||
gniza init remote nas
|
||||
gniza sysbackup --dry-run
|
||||
gniza sysbackup --remote=nas
|
||||
gniza sysrestore --remote=nas
|
||||
gniza sysrestore --remote=nas --phase=1 --dry-run
|
||||
gniza4cp backup --dry-run
|
||||
gniza4cp backup --account=johndoe
|
||||
gniza4cp backup --remote=nas
|
||||
gniza4cp backup --remote=nas,offsite
|
||||
gniza4cp list --remote=offsite
|
||||
gniza4cp restore files johndoe --remote=nas --path=public_html
|
||||
gniza4cp restore database johndoe johndoe_wp --remote=nas
|
||||
gniza4cp restore mailbox johndoe info@example.com --remote=nas
|
||||
gniza4cp schedule add nightly
|
||||
gniza4cp schedule list
|
||||
gniza4cp schedule install
|
||||
gniza4cp remote list
|
||||
gniza4cp sysbackup --dry-run
|
||||
gniza4cp sysbackup --remote=nas
|
||||
gniza4cp sysrestore --remote=nas
|
||||
gniza4cp sysrestore --remote=nas --phase=1 --dry-run
|
||||
EOF
|
||||
}
|
||||
|
||||
@@ -1701,7 +1533,7 @@ EOF
|
||||
main() {
|
||||
# Global --debug flag (used by config.sh load_config)
|
||||
# shellcheck disable=SC2034
|
||||
has_flag debug "$@" && GNIZA_DEBUG=true || GNIZA_DEBUG=false
|
||||
has_flag debug "$@" && GNIZA4CP_DEBUG=true || GNIZA4CP_DEBUG=false
|
||||
|
||||
local command="${1:-}"
|
||||
shift 2>/dev/null || true
|
||||
@@ -1717,11 +1549,10 @@ main() {
|
||||
remote) cmd_remote "$@" ;;
|
||||
schedule) cmd_schedule "$@" ;;
|
||||
stats) cmd_stats "$@" ;;
|
||||
init) cmd_init "$@" ;;
|
||||
version) echo "gniza v${GNIZA_VERSION}" ;;
|
||||
version) echo "gniza4cp v${GNIZA4CP_VERSION}" ;;
|
||||
help|-h|--help) cmd_usage ;;
|
||||
"") cmd_usage ;;
|
||||
*) die "Unknown command: $command"$'\n'"Run 'gniza help' for usage" ;;
|
||||
*) die "Unknown command: $command"$'\n'"Run 'gniza4cp help' for usage" ;;
|
||||
esac
|
||||
}
|
||||
|
||||
@@ -1,413 +0,0 @@
|
||||
#!/usr/local/cpanel/3rdparty/bin/perl
|
||||
package Cpanel::AdminBin::Script::Call::Gniza::Restore;
|
||||
|
||||
use strict;
|
||||
use warnings;
|
||||
use parent 'Cpanel::AdminBin::Script::Call';
|
||||
use IPC::Open3;
|
||||
use Symbol 'gensym';
|
||||
|
||||
my $GNIZA_BIN = '/usr/local/bin/gniza';
|
||||
my $MAIN_CONFIG = '/etc/gniza/gniza.conf';
|
||||
my $REMOTES_DIR = '/etc/gniza/remotes.d';
|
||||
|
||||
# Argument validation patterns (mirrors GnizaWHM::Runner)
|
||||
my %OPT_PATTERNS = (
|
||||
remote => qr/^[a-zA-Z0-9_,-]+$/,
|
||||
timestamp => qr/^\d{4}-\d{2}-\d{2}T\d{6}$/,
|
||||
path => qr/^(?!.*\.\.)[a-zA-Z0-9_.\/@ -]+$/,
|
||||
exclude => qr/^[a-zA-Z0-9_.,\/@ *?\[\]-]+$/,
|
||||
);
|
||||
|
||||
my $ACCOUNT_RE = qr/^[a-z][a-z0-9_-]*$/;
|
||||
my $REMOTE_RE = qr/^[a-zA-Z0-9_-]+$/;
|
||||
my $DBNAME_RE = qr/^[a-zA-Z0-9_]+$/;
|
||||
my $EMAIL_RE = qr/^[a-zA-Z0-9._+-]+\@[a-zA-Z0-9._-]+$/;
|
||||
my $DOMAIN_RE = qr/^[a-zA-Z0-9._-]+$/;
|
||||
my $TS_RE = qr/^\d{4}-\d{2}-\d{2}T\d{6}$/;
|
||||
|
||||
# ── Allowed remotes for user restore ──────────────────────────
|
||||
|
||||
sub _get_allowed_remotes {
|
||||
my $setting = '';
|
||||
if (open my $fh, '<', $MAIN_CONFIG) {
|
||||
while (my $line = <$fh>) {
|
||||
if ($line =~ /^USER_RESTORE_REMOTES=(?:"([^"]*)"|'([^']*)'|(\S*))$/) {
|
||||
$setting = defined $1 ? $1 : (defined $2 ? $2 : ($3 // ''));
|
||||
}
|
||||
}
|
||||
close $fh;
|
||||
}
|
||||
# Default to "all" if not set
|
||||
$setting = 'all' if !defined $setting || $setting eq '';
|
||||
|
||||
return $setting;
|
||||
}
|
||||
|
||||
sub _list_all_remotes {
|
||||
my @remotes;
|
||||
if (-d $REMOTES_DIR && opendir my $dh, $REMOTES_DIR) {
|
||||
while (my $entry = readdir $dh) {
|
||||
if ($entry =~ /^([a-zA-Z0-9_-]+)\.conf$/) {
|
||||
push @remotes, $1;
|
||||
}
|
||||
}
|
||||
closedir $dh;
|
||||
}
|
||||
return sort @remotes;
|
||||
}
|
||||
|
||||
sub _is_remote_allowed {
|
||||
my ($remote) = @_;
|
||||
my $setting = _get_allowed_remotes();
|
||||
return 0 if $setting eq ''; # disabled
|
||||
|
||||
if ($setting eq 'all') {
|
||||
# Check it actually exists
|
||||
return -f "$REMOTES_DIR/$remote.conf" ? 1 : 0;
|
||||
}
|
||||
|
||||
my %allowed = map { $_ => 1 } split /,/, $setting;
|
||||
return $allowed{$remote} ? 1 : 0;
|
||||
}
|
||||
|
||||
sub _get_filtered_remotes {
|
||||
my $setting = _get_allowed_remotes();
|
||||
return () if $setting eq '';
|
||||
|
||||
my @all = _list_all_remotes();
|
||||
return @all if $setting eq 'all';
|
||||
|
||||
my %allowed = map { $_ => 1 } split /,/, $setting;
|
||||
return grep { $allowed{$_} } @all;
|
||||
}
|
||||
|
||||
# ── Command execution ─────────────────────────────────────────
|
||||
|
||||
sub _run_gniza {
|
||||
my (@args) = @_;
|
||||
|
||||
my $err_fh = gensym;
|
||||
my ($in, $out);
|
||||
my $pid = eval { open3($in, $out, $err_fh, $GNIZA_BIN, @args) };
|
||||
unless ($pid) {
|
||||
return (0, '', "Failed to execute gniza: $@");
|
||||
}
|
||||
close $in if $in;
|
||||
|
||||
my $stdout = do { local $/; <$out> } // '';
|
||||
my $stderr = do { local $/; <$err_fh> } // '';
|
||||
close $out;
|
||||
close $err_fh;
|
||||
|
||||
waitpid($pid, 0);
|
||||
my $exit_code = $? >> 8;
|
||||
|
||||
return ($exit_code == 0, $stdout, $stderr);
|
||||
}
|
||||
|
||||
# ── Action dispatch ───────────────────────────────────────────
|
||||
|
||||
sub _actions {
|
||||
return qw(
|
||||
LIST_ALLOWED_REMOTES
|
||||
LIST_SNAPSHOTS
|
||||
LIST_DATABASES
|
||||
LIST_MAILBOXES
|
||||
LIST_FILES
|
||||
LIST_DBUSERS
|
||||
LIST_CRON
|
||||
LIST_DNS
|
||||
LIST_SSL
|
||||
RESTORE_ACCOUNT
|
||||
RESTORE_FILES
|
||||
RESTORE_DATABASE
|
||||
RESTORE_MAILBOX
|
||||
RESTORE_CRON
|
||||
RESTORE_DBUSERS
|
||||
RESTORE_DOMAINS
|
||||
RESTORE_SSL
|
||||
);
|
||||
}
|
||||
|
||||
sub LIST_ALLOWED_REMOTES {
|
||||
my ($self) = @_;
|
||||
my @remotes = _get_filtered_remotes();
|
||||
return join("\n", @remotes);
|
||||
}
|
||||
|
||||
sub LIST_SNAPSHOTS {
|
||||
my ($self, $remote) = @_;
|
||||
my $user = $self->get_caller_username() // '';
|
||||
|
||||
return "ERROR: Invalid user" unless $user =~ $ACCOUNT_RE;
|
||||
return "ERROR: Invalid remote" unless defined $remote && $remote =~ $REMOTE_RE;
|
||||
return "ERROR: Remote not allowed" unless _is_remote_allowed($remote);
|
||||
|
||||
my ($ok, $stdout, $stderr) = _run_gniza('list', "--remote=$remote", "--account=$user");
|
||||
return $ok ? $stdout : "ERROR: $stderr";
|
||||
}
|
||||
|
||||
sub LIST_DATABASES {
|
||||
my ($self, $remote, $timestamp) = @_;
|
||||
my $user = $self->get_caller_username() // '';
|
||||
|
||||
return "ERROR: Invalid user" unless $user =~ $ACCOUNT_RE;
|
||||
return "ERROR: Invalid remote" unless defined $remote && $remote =~ $REMOTE_RE;
|
||||
return "ERROR: Invalid timestamp" unless defined $timestamp && $timestamp =~ $TS_RE;
|
||||
return "ERROR: Remote not allowed" unless _is_remote_allowed($remote);
|
||||
|
||||
my ($ok, $stdout, $stderr) = _run_gniza('restore', 'list-databases', $user,
|
||||
"--remote=$remote", "--timestamp=$timestamp");
|
||||
return $ok ? $stdout : "ERROR: $stderr";
|
||||
}
|
||||
|
||||
sub LIST_MAILBOXES {
|
||||
my ($self, $remote, $timestamp) = @_;
|
||||
my $user = $self->get_caller_username() // '';
|
||||
|
||||
return "ERROR: Invalid user" unless $user =~ $ACCOUNT_RE;
|
||||
return "ERROR: Invalid remote" unless defined $remote && $remote =~ $REMOTE_RE;
|
||||
return "ERROR: Invalid timestamp" unless defined $timestamp && $timestamp =~ $TS_RE;
|
||||
return "ERROR: Remote not allowed" unless _is_remote_allowed($remote);
|
||||
|
||||
my ($ok, $stdout, $stderr) = _run_gniza('restore', 'list-mailboxes', $user,
|
||||
"--remote=$remote", "--timestamp=$timestamp");
|
||||
return $ok ? $stdout : "ERROR: $stderr";
|
||||
}
|
||||
|
||||
sub LIST_FILES {
|
||||
my ($self, $remote, $timestamp, $path) = @_;
|
||||
my $user = $self->get_caller_username() // '';
|
||||
|
||||
return "ERROR: Invalid user" unless $user =~ $ACCOUNT_RE;
|
||||
return "ERROR: Invalid remote" unless defined $remote && $remote =~ $REMOTE_RE;
|
||||
return "ERROR: Invalid timestamp" unless defined $timestamp && $timestamp =~ $TS_RE;
|
||||
return "ERROR: Remote not allowed" unless _is_remote_allowed($remote);
|
||||
|
||||
my @opts = ("--remote=$remote", "--timestamp=$timestamp");
|
||||
if (defined $path && $path ne '') {
|
||||
return "ERROR: Invalid path" unless $path =~ $OPT_PATTERNS{path};
|
||||
push @opts, "--path=$path";
|
||||
}
|
||||
|
||||
my ($ok, $stdout, $stderr) = _run_gniza('restore', 'list-files', $user, @opts);
|
||||
return $ok ? $stdout : "ERROR: $stderr";
|
||||
}
|
||||
|
||||
sub LIST_DBUSERS {
|
||||
my ($self, $remote, $timestamp) = @_;
|
||||
my $user = $self->get_caller_username() // '';
|
||||
|
||||
return "ERROR: Invalid user" unless $user =~ $ACCOUNT_RE;
|
||||
return "ERROR: Invalid remote" unless defined $remote && $remote =~ $REMOTE_RE;
|
||||
return "ERROR: Invalid timestamp" unless defined $timestamp && $timestamp =~ $TS_RE;
|
||||
return "ERROR: Remote not allowed" unless _is_remote_allowed($remote);
|
||||
|
||||
my ($ok, $stdout, $stderr) = _run_gniza('restore', 'list-dbusers', $user,
|
||||
"--remote=$remote", "--timestamp=$timestamp");
|
||||
return $ok ? $stdout : "ERROR: $stderr";
|
||||
}
|
||||
|
||||
sub LIST_CRON {
|
||||
my ($self, $remote, $timestamp) = @_;
|
||||
my $user = $self->get_caller_username() // '';
|
||||
|
||||
return "ERROR: Invalid user" unless $user =~ $ACCOUNT_RE;
|
||||
return "ERROR: Invalid remote" unless defined $remote && $remote =~ $REMOTE_RE;
|
||||
return "ERROR: Invalid timestamp" unless defined $timestamp && $timestamp =~ $TS_RE;
|
||||
return "ERROR: Remote not allowed" unless _is_remote_allowed($remote);
|
||||
|
||||
my ($ok, $stdout, $stderr) = _run_gniza('restore', 'list-cron', $user,
|
||||
"--remote=$remote", "--timestamp=$timestamp");
|
||||
return $ok ? $stdout : "ERROR: $stderr";
|
||||
}
|
||||
|
||||
sub LIST_DNS {
|
||||
my ($self, $remote, $timestamp) = @_;
|
||||
my $user = $self->get_caller_username() // '';
|
||||
|
||||
return "ERROR: Invalid user" unless $user =~ $ACCOUNT_RE;
|
||||
return "ERROR: Invalid remote" unless defined $remote && $remote =~ $REMOTE_RE;
|
||||
return "ERROR: Invalid timestamp" unless defined $timestamp && $timestamp =~ $TS_RE;
|
||||
return "ERROR: Remote not allowed" unless _is_remote_allowed($remote);
|
||||
|
||||
my ($ok, $stdout, $stderr) = _run_gniza('restore', 'list-dns', $user,
|
||||
"--remote=$remote", "--timestamp=$timestamp");
|
||||
return $ok ? $stdout : "ERROR: $stderr";
|
||||
}
|
||||
|
||||
sub LIST_SSL {
|
||||
my ($self, $remote, $timestamp) = @_;
|
||||
my $user = $self->get_caller_username() // '';
|
||||
|
||||
return "ERROR: Invalid user" unless $user =~ $ACCOUNT_RE;
|
||||
return "ERROR: Invalid remote" unless defined $remote && $remote =~ $REMOTE_RE;
|
||||
return "ERROR: Invalid timestamp" unless defined $timestamp && $timestamp =~ $TS_RE;
|
||||
return "ERROR: Remote not allowed" unless _is_remote_allowed($remote);
|
||||
|
||||
my ($ok, $stdout, $stderr) = _run_gniza('restore', 'list-ssl', $user,
|
||||
"--remote=$remote", "--timestamp=$timestamp");
|
||||
return $ok ? $stdout : "ERROR: $stderr";
|
||||
}
|
||||
|
||||
sub RESTORE_ACCOUNT {
|
||||
my ($self, $remote, $timestamp, $exclude) = @_;
|
||||
my $user = $self->get_caller_username() // '';
|
||||
|
||||
return "ERROR: Invalid user" unless $user =~ $ACCOUNT_RE;
|
||||
return "ERROR: Invalid remote" unless defined $remote && $remote =~ $REMOTE_RE;
|
||||
return "ERROR: Invalid timestamp" unless defined $timestamp && $timestamp =~ $TS_RE;
|
||||
return "ERROR: Remote not allowed" unless _is_remote_allowed($remote);
|
||||
|
||||
my @opts = ("--remote=$remote", "--timestamp=$timestamp");
|
||||
# NOTE: --terminate is NEVER passed for user restore
|
||||
if (defined $exclude && $exclude ne '') {
|
||||
return "ERROR: Invalid exclude" unless $exclude =~ $OPT_PATTERNS{exclude};
|
||||
push @opts, "--exclude=$exclude";
|
||||
}
|
||||
|
||||
my ($ok, $stdout, $stderr) = _run_gniza('restore', 'account', $user, @opts);
|
||||
return $ok ? "OK\n$stdout" : "ERROR: $stderr";
|
||||
}
|
||||
|
||||
sub RESTORE_FILES {
|
||||
my ($self, $remote, $timestamp, $path, $exclude) = @_;
|
||||
my $user = $self->get_caller_username() // '';
|
||||
|
||||
return "ERROR: Invalid user" unless $user =~ $ACCOUNT_RE;
|
||||
return "ERROR: Invalid remote" unless defined $remote && $remote =~ $REMOTE_RE;
|
||||
return "ERROR: Invalid timestamp" unless defined $timestamp && $timestamp =~ $TS_RE;
|
||||
return "ERROR: Remote not allowed" unless _is_remote_allowed($remote);
|
||||
|
||||
my @opts = ("--remote=$remote", "--timestamp=$timestamp");
|
||||
if (defined $path && $path ne '') {
|
||||
return "ERROR: Invalid path" unless $path =~ $OPT_PATTERNS{path};
|
||||
push @opts, "--path=$path";
|
||||
}
|
||||
if (defined $exclude && $exclude ne '') {
|
||||
return "ERROR: Invalid exclude" unless $exclude =~ $OPT_PATTERNS{exclude};
|
||||
push @opts, "--exclude=$exclude";
|
||||
}
|
||||
|
||||
my ($ok, $stdout, $stderr) = _run_gniza('restore', 'files', $user, @opts);
|
||||
return $ok ? "OK\n$stdout" : "ERROR: $stderr";
|
||||
}
|
||||
|
||||
sub RESTORE_DATABASE {
|
||||
my ($self, $remote, $timestamp, $dbname) = @_;
|
||||
my $user = $self->get_caller_username() // '';
|
||||
|
||||
return "ERROR: Invalid user" unless $user =~ $ACCOUNT_RE;
|
||||
return "ERROR: Invalid remote" unless defined $remote && $remote =~ $REMOTE_RE;
|
||||
return "ERROR: Invalid timestamp" unless defined $timestamp && $timestamp =~ $TS_RE;
|
||||
return "ERROR: Remote not allowed" unless _is_remote_allowed($remote);
|
||||
|
||||
my @args = ($user);
|
||||
if (defined $dbname && $dbname ne '') {
|
||||
return "ERROR: Invalid database name" unless $dbname =~ $DBNAME_RE;
|
||||
push @args, $dbname;
|
||||
}
|
||||
|
||||
my ($ok, $stdout, $stderr) = _run_gniza('restore', 'database', @args,
|
||||
"--remote=$remote", "--timestamp=$timestamp");
|
||||
return $ok ? "OK\n$stdout" : "ERROR: $stderr";
|
||||
}
|
||||
|
||||
sub RESTORE_MAILBOX {
|
||||
my ($self, $remote, $timestamp, $email) = @_;
|
||||
my $user = $self->get_caller_username() // '';
|
||||
|
||||
return "ERROR: Invalid user" unless $user =~ $ACCOUNT_RE;
|
||||
return "ERROR: Invalid remote" unless defined $remote && $remote =~ $REMOTE_RE;
|
||||
return "ERROR: Invalid timestamp" unless defined $timestamp && $timestamp =~ $TS_RE;
|
||||
return "ERROR: Remote not allowed" unless _is_remote_allowed($remote);
|
||||
|
||||
my @args = ($user);
|
||||
if (defined $email && $email ne '') {
|
||||
return "ERROR: Invalid email" unless $email =~ $EMAIL_RE;
|
||||
push @args, $email;
|
||||
}
|
||||
|
||||
my ($ok, $stdout, $stderr) = _run_gniza('restore', 'mailbox', @args,
|
||||
"--remote=$remote", "--timestamp=$timestamp");
|
||||
return $ok ? "OK\n$stdout" : "ERROR: $stderr";
|
||||
}
|
||||
|
||||
sub RESTORE_CRON {
|
||||
my ($self, $remote, $timestamp) = @_;
|
||||
my $user = $self->get_caller_username() // '';
|
||||
|
||||
return "ERROR: Invalid user" unless $user =~ $ACCOUNT_RE;
|
||||
return "ERROR: Invalid remote" unless defined $remote && $remote =~ $REMOTE_RE;
|
||||
return "ERROR: Invalid timestamp" unless defined $timestamp && $timestamp =~ $TS_RE;
|
||||
return "ERROR: Remote not allowed" unless _is_remote_allowed($remote);
|
||||
|
||||
my ($ok, $stdout, $stderr) = _run_gniza('restore', 'cron', $user,
|
||||
"--remote=$remote", "--timestamp=$timestamp");
|
||||
return $ok ? "OK\n$stdout" : "ERROR: $stderr";
|
||||
}
|
||||
|
||||
sub RESTORE_DBUSERS {
|
||||
my ($self, $remote, $timestamp, $dbuser) = @_;
|
||||
my $user = $self->get_caller_username() // '';
|
||||
|
||||
return "ERROR: Invalid user" unless $user =~ $ACCOUNT_RE;
|
||||
return "ERROR: Invalid remote" unless defined $remote && $remote =~ $REMOTE_RE;
|
||||
return "ERROR: Invalid timestamp" unless defined $timestamp && $timestamp =~ $TS_RE;
|
||||
return "ERROR: Remote not allowed" unless _is_remote_allowed($remote);
|
||||
|
||||
my @args = ($user);
|
||||
if (defined $dbuser && $dbuser ne '') {
|
||||
return "ERROR: Invalid database user" unless $dbuser =~ $DBNAME_RE;
|
||||
push @args, $dbuser;
|
||||
}
|
||||
|
||||
my ($ok, $stdout, $stderr) = _run_gniza('restore', 'dbusers', @args,
|
||||
"--remote=$remote", "--timestamp=$timestamp");
|
||||
return $ok ? "OK\n$stdout" : "ERROR: $stderr";
|
||||
}
|
||||
|
||||
sub RESTORE_DOMAINS {
|
||||
my ($self, $remote, $timestamp, $domain) = @_;
|
||||
my $user = $self->get_caller_username() // '';
|
||||
|
||||
return "ERROR: Invalid user" unless $user =~ $ACCOUNT_RE;
|
||||
return "ERROR: Invalid remote" unless defined $remote && $remote =~ $REMOTE_RE;
|
||||
return "ERROR: Invalid timestamp" unless defined $timestamp && $timestamp =~ $TS_RE;
|
||||
return "ERROR: Remote not allowed" unless _is_remote_allowed($remote);
|
||||
|
||||
my @args = ($user);
|
||||
if (defined $domain && $domain ne '') {
|
||||
return "ERROR: Invalid domain" unless $domain =~ $DOMAIN_RE;
|
||||
push @args, $domain;
|
||||
}
|
||||
|
||||
my ($ok, $stdout, $stderr) = _run_gniza('restore', 'domains', @args,
|
||||
"--remote=$remote", "--timestamp=$timestamp");
|
||||
return $ok ? "OK\n$stdout" : "ERROR: $stderr";
|
||||
}
|
||||
|
||||
sub RESTORE_SSL {
|
||||
my ($self, $remote, $timestamp, $domain) = @_;
|
||||
my $user = $self->get_caller_username() // '';
|
||||
|
||||
return "ERROR: Invalid user" unless $user =~ $ACCOUNT_RE;
|
||||
return "ERROR: Invalid remote" unless defined $remote && $remote =~ $REMOTE_RE;
|
||||
return "ERROR: Invalid timestamp" unless defined $timestamp && $timestamp =~ $TS_RE;
|
||||
return "ERROR: Remote not allowed" unless _is_remote_allowed($remote);
|
||||
|
||||
my @args = ($user);
|
||||
if (defined $domain && $domain ne '') {
|
||||
return "ERROR: Invalid domain" unless $domain =~ $DOMAIN_RE;
|
||||
push @args, $domain;
|
||||
}
|
||||
|
||||
my ($ok, $stdout, $stderr) = _run_gniza('restore', 'ssl', @args,
|
||||
"--remote=$remote", "--timestamp=$timestamp");
|
||||
return $ok ? "OK\n$stdout" : "ERROR: $stderr";
|
||||
}
|
||||
|
||||
__PACKAGE__->run() if !caller;
|
||||
|
||||
1;
|
||||
773
cpanel/admin/Gniza4cp/Restore
Normal file
@@ -0,0 +1,773 @@
|
||||
#!/usr/local/cpanel/3rdparty/bin/perl
|
||||
package Cpanel::AdminBin::Script::Call::Gniza4cp::Restore;
|
||||
|
||||
use strict;
|
||||
use warnings;
|
||||
use parent 'Cpanel::AdminBin::Script::Call';
|
||||
use IPC::Open3;
|
||||
use Symbol 'gensym';
|
||||
use POSIX qw(setsid);
|
||||
|
||||
my $GNIZA4CP_BIN = '/usr/local/bin/gniza4cp';
|
||||
my $MAIN_CONFIG = '/etc/gniza4cp/gniza4cp.conf';
|
||||
my $REMOTES_DIR = '/etc/gniza4cp/remotes.d';
|
||||
|
||||
# Argument validation patterns (mirrors Gniza4cpWHM::Runner)
|
||||
my %OPT_PATTERNS = (
|
||||
remote => qr/^[a-zA-Z0-9_,-]+$/,
|
||||
timestamp => qr/^\d{4}-\d{2}-\d{2}T\d{6}$/,
|
||||
path => qr/^(?!.*\.\.)[a-zA-Z0-9_.\/@ -]+$/,
|
||||
exclude => qr/^[a-zA-Z0-9_.,\/@ *?\[\]-]+$/,
|
||||
);
|
||||
|
||||
my $ACCOUNT_RE = qr/^[a-z][a-z0-9_-]*$/;
|
||||
my $REMOTE_RE = qr/^[a-zA-Z0-9_-]+$/;
|
||||
my $DBNAME_RE = qr/^[a-zA-Z0-9_]+$/;
|
||||
my $EMAIL_RE = qr/^[a-zA-Z0-9._+-]+\@[a-zA-Z0-9._-]+$/;
|
||||
my $DOMAIN_RE = qr/^[a-zA-Z0-9._-]+$/;
|
||||
my $TS_RE = qr/^\d{4}-\d{2}-\d{2}T\d{6}$/;
|
||||
|
||||
# ── Allowed remotes for user restore ──────────────────────────
|
||||
|
||||
sub _get_allowed_remotes {
|
||||
my $setting = '';
|
||||
if (open my $fh, '<', $MAIN_CONFIG) {
|
||||
while (my $line = <$fh>) {
|
||||
if ($line =~ /^USER_RESTORE_REMOTES=(?:"([^"]*)"|'([^']*)'|(\S*))$/) {
|
||||
$setting = defined $1 ? $1 : (defined $2 ? $2 : ($3 // ''));
|
||||
}
|
||||
}
|
||||
close $fh;
|
||||
}
|
||||
# Default to "all" if not set
|
||||
$setting = 'all' if !defined $setting || $setting eq '';
|
||||
|
||||
return $setting;
|
||||
}
|
||||
|
||||
sub _list_all_remotes {
|
||||
my @remotes;
|
||||
if (-d $REMOTES_DIR && opendir my $dh, $REMOTES_DIR) {
|
||||
while (my $entry = readdir $dh) {
|
||||
if ($entry =~ /^([a-zA-Z0-9_-]+)\.conf$/) {
|
||||
push @remotes, $1;
|
||||
}
|
||||
}
|
||||
closedir $dh;
|
||||
}
|
||||
return sort @remotes;
|
||||
}
|
||||
|
||||
sub _is_remote_allowed {
|
||||
my ($remote) = @_;
|
||||
my $setting = _get_allowed_remotes();
|
||||
return 0 if $setting eq ''; # disabled
|
||||
|
||||
if ($setting eq 'all') {
|
||||
# Check it actually exists
|
||||
return -f "$REMOTES_DIR/$remote.conf" ? 1 : 0;
|
||||
}
|
||||
|
||||
my %allowed = map { $_ => 1 } split /,/, $setting;
|
||||
return $allowed{$remote} ? 1 : 0;
|
||||
}
|
||||
|
||||
sub _get_filtered_remotes {
|
||||
my $setting = _get_allowed_remotes();
|
||||
return () if $setting eq '';
|
||||
|
||||
my @all = _list_all_remotes();
|
||||
return @all if $setting eq 'all';
|
||||
|
||||
my %allowed = map { $_ => 1 } split /,/, $setting;
|
||||
return grep { $allowed{$_} } @all;
|
||||
}
|
||||
|
||||
# ── Command execution ─────────────────────────────────────────
|
||||
|
||||
sub _run_gniza4cp {
|
||||
my (@args) = @_;
|
||||
|
||||
my $err_fh = gensym;
|
||||
my ($in, $out);
|
||||
my $pid = eval { open3($in, $out, $err_fh, $GNIZA4CP_BIN, @args) };
|
||||
unless ($pid) {
|
||||
return (0, '', "Failed to execute gniza4cp: $@");
|
||||
}
|
||||
close $in if $in;
|
||||
|
||||
my $stdout = do { local $/; <$out> } // '';
|
||||
my $stderr = do { local $/; <$err_fh> } // '';
|
||||
close $out;
|
||||
close $err_fh;
|
||||
|
||||
waitpid($pid, 0);
|
||||
my $exit_code = $? >> 8;
|
||||
|
||||
return ($exit_code == 0, $stdout, $stderr);
|
||||
}
|
||||
|
||||
# ── Action dispatch ───────────────────────────────────────────
|
||||
|
||||
# ── Per-user activity logging ─────────────────────────────────
|
||||
|
||||
my $ACTIVITY_ENTRY_RE = qr/^[0-9]+$/;
|
||||
|
||||
sub _get_log_dir {
|
||||
my $log_dir = '/var/log/gniza4cp';
|
||||
if (open my $fh, '<', $MAIN_CONFIG) {
|
||||
while (my $line = <$fh>) {
|
||||
if ($line =~ /^LOG_DIR=(?:"([^"]*)"|'([^']*)'|(\S*))$/) {
|
||||
my $val = defined $1 ? $1 : (defined $2 ? $2 : ($3 // ''));
|
||||
$log_dir = $val if $val ne '';
|
||||
}
|
||||
}
|
||||
close $fh;
|
||||
}
|
||||
return $log_dir;
|
||||
}
|
||||
|
||||
sub _activity_log_path {
|
||||
my ($user) = @_;
|
||||
my $log_dir = _get_log_dir();
|
||||
return "$log_dir/cpanel-$user.log";
|
||||
}
|
||||
|
||||
my %ACTION_LABELS = (
|
||||
RESTORE_ACCOUNT => 'Full Account',
|
||||
RESTORE_FILES => 'Home Directory',
|
||||
RESTORE_DATABASE => 'Database',
|
||||
RESTORE_MAILBOX => 'Email',
|
||||
RESTORE_CRON => 'Cron Jobs',
|
||||
RESTORE_DBUSERS => 'DB Users',
|
||||
RESTORE_DOMAINS => 'Domains',
|
||||
RESTORE_SSL => 'SSL Certificates',
|
||||
);
|
||||
|
||||
sub _log_activity {
|
||||
my ($user, $action, $details, $status, $output) = @_;
|
||||
my $log_file = _activity_log_path($user);
|
||||
my $log_dir = _get_log_dir();
|
||||
mkdir $log_dir, 0700 unless -d $log_dir;
|
||||
|
||||
my @t = gmtime(time);
|
||||
my $ts = sprintf('%04d-%02d-%02d %02d:%02d:%02d',
|
||||
$t[5]+1900, $t[4]+1, $t[3], $t[2], $t[1], $t[0]);
|
||||
my $label = $ACTION_LABELS{$action} // $action;
|
||||
|
||||
if (open my $fh, '>>', $log_file) {
|
||||
print $fh "--- ENTRY ---\n";
|
||||
print $fh "Date: $ts\n";
|
||||
print $fh "Action: $label\n";
|
||||
print $fh "Details: $details\n";
|
||||
print $fh "Status: $status\n";
|
||||
print $fh $output if defined $output && $output ne '';
|
||||
print $fh "--- END ---\n";
|
||||
close $fh;
|
||||
}
|
||||
}
|
||||
|
||||
sub _actions {
|
||||
return qw(
|
||||
LIST_ALLOWED_REMOTES
|
||||
LIST_SNAPSHOTS
|
||||
LIST_DATABASES
|
||||
LIST_MAILBOXES
|
||||
LIST_FILES
|
||||
LIST_DBUSERS
|
||||
LIST_CRON
|
||||
LIST_DNS
|
||||
LIST_SSL
|
||||
LIST_LOGS
|
||||
GET_LOG
|
||||
START_RESTORE
|
||||
RESTORE_ACCOUNT
|
||||
RESTORE_FILES
|
||||
RESTORE_DATABASE
|
||||
RESTORE_MAILBOX
|
||||
RESTORE_CRON
|
||||
RESTORE_DBUSERS
|
||||
RESTORE_DOMAINS
|
||||
RESTORE_SSL
|
||||
);
|
||||
}
|
||||
|
||||
sub LIST_LOGS {
|
||||
my ($self) = @_;
|
||||
my $user = $self->get_caller_username() // '';
|
||||
return "ERROR: Invalid user" unless $user =~ $ACCOUNT_RE;
|
||||
|
||||
my $log_file = _activity_log_path($user);
|
||||
return '' unless -f $log_file && !-l $log_file;
|
||||
|
||||
# Parse entries from the activity log (newest first)
|
||||
my @entries;
|
||||
if (open my $fh, '<', $log_file) {
|
||||
my $in_entry = 0;
|
||||
my %cur;
|
||||
while (my $line = <$fh>) {
|
||||
chomp $line;
|
||||
if ($line eq '--- ENTRY ---') {
|
||||
$in_entry = 1;
|
||||
%cur = ();
|
||||
} elsif ($line eq '--- END ---' && $in_entry) {
|
||||
push @entries, { %cur } if $cur{date};
|
||||
$in_entry = 0;
|
||||
} elsif ($in_entry) {
|
||||
if ($line =~ /^Date:\s+(.+)$/) { $cur{date} = $1; }
|
||||
elsif ($line =~ /^Action:\s+(.+)$/) { $cur{action} = $1; }
|
||||
elsif ($line =~ /^Details:\s+(.+)$/) { $cur{details} = $1; }
|
||||
elsif ($line =~ /^Status:\s+(.+)$/) { $cur{status} = $1; }
|
||||
}
|
||||
}
|
||||
close $fh;
|
||||
}
|
||||
|
||||
# Return newest first, one line per entry: index\tdate\taction\tdetails\tstatus
|
||||
my @lines;
|
||||
for my $i (reverse 0 .. $#entries) {
|
||||
my $e = $entries[$i];
|
||||
push @lines, join("\t", $i, $e->{date} // '', $e->{action} // '',
|
||||
$e->{details} // '', $e->{status} // '');
|
||||
}
|
||||
return join("\n", @lines);
|
||||
}
|
||||
|
||||
sub GET_LOG {
|
||||
my ($self, $entry_idx) = @_;
|
||||
my $user = $self->get_caller_username() // '';
|
||||
return "ERROR: Invalid user" unless $user =~ $ACCOUNT_RE;
|
||||
return "ERROR: Invalid entry" unless defined $entry_idx && $entry_idx =~ $ACTIVITY_ENTRY_RE;
|
||||
|
||||
my $log_file = _activity_log_path($user);
|
||||
return "ERROR: No activity log" unless -f $log_file && !-l $log_file;
|
||||
|
||||
# Parse the Nth entry
|
||||
my $idx = int($entry_idx);
|
||||
my @entries;
|
||||
if (open my $fh, '<', $log_file) {
|
||||
my $in_entry = 0;
|
||||
my @cur_lines;
|
||||
while (my $line = <$fh>) {
|
||||
chomp $line;
|
||||
if ($line eq '--- ENTRY ---') {
|
||||
$in_entry = 1;
|
||||
@cur_lines = ();
|
||||
} elsif ($line eq '--- END ---' && $in_entry) {
|
||||
push @entries, join("\n", @cur_lines);
|
||||
$in_entry = 0;
|
||||
} elsif ($in_entry) {
|
||||
push @cur_lines, $line;
|
||||
}
|
||||
}
|
||||
close $fh;
|
||||
}
|
||||
|
||||
return "ERROR: Entry not found" if $idx < 0 || $idx > $#entries;
|
||||
return $entries[$idx];
|
||||
}
|
||||
|
||||
sub LIST_ALLOWED_REMOTES {
|
||||
my ($self) = @_;
|
||||
my @remotes = _get_filtered_remotes();
|
||||
return join("\n", @remotes);
|
||||
}
|
||||
|
||||
sub LIST_SNAPSHOTS {
|
||||
my ($self, $remote) = @_;
|
||||
my $user = $self->get_caller_username() // '';
|
||||
|
||||
return "ERROR: Invalid user" unless $user =~ $ACCOUNT_RE;
|
||||
return "ERROR: Invalid remote" unless defined $remote && $remote =~ $REMOTE_RE;
|
||||
return "ERROR: Remote not allowed" unless _is_remote_allowed($remote);
|
||||
|
||||
my ($ok, $stdout, $stderr) = _run_gniza4cp('list', "--remote=$remote", "--account=$user");
|
||||
return $ok ? $stdout : "ERROR: $stderr";
|
||||
}
|
||||
|
||||
sub LIST_DATABASES {
|
||||
my ($self, $remote, $timestamp) = @_;
|
||||
my $user = $self->get_caller_username() // '';
|
||||
|
||||
return "ERROR: Invalid user" unless $user =~ $ACCOUNT_RE;
|
||||
return "ERROR: Invalid remote" unless defined $remote && $remote =~ $REMOTE_RE;
|
||||
return "ERROR: Invalid timestamp" unless defined $timestamp && $timestamp =~ $TS_RE;
|
||||
return "ERROR: Remote not allowed" unless _is_remote_allowed($remote);
|
||||
|
||||
my ($ok, $stdout, $stderr) = _run_gniza4cp('restore', 'list-databases', $user,
|
||||
"--remote=$remote", "--timestamp=$timestamp");
|
||||
return $ok ? $stdout : "ERROR: $stderr";
|
||||
}
|
||||
|
||||
sub LIST_MAILBOXES {
|
||||
my ($self, $remote, $timestamp) = @_;
|
||||
my $user = $self->get_caller_username() // '';
|
||||
|
||||
return "ERROR: Invalid user" unless $user =~ $ACCOUNT_RE;
|
||||
return "ERROR: Invalid remote" unless defined $remote && $remote =~ $REMOTE_RE;
|
||||
return "ERROR: Invalid timestamp" unless defined $timestamp && $timestamp =~ $TS_RE;
|
||||
return "ERROR: Remote not allowed" unless _is_remote_allowed($remote);
|
||||
|
||||
my ($ok, $stdout, $stderr) = _run_gniza4cp('restore', 'list-mailboxes', $user,
|
||||
"--remote=$remote", "--timestamp=$timestamp");
|
||||
return $ok ? $stdout : "ERROR: $stderr";
|
||||
}
|
||||
|
||||
sub LIST_FILES {
|
||||
my ($self, $remote, $timestamp, $path) = @_;
|
||||
my $user = $self->get_caller_username() // '';
|
||||
|
||||
return "ERROR: Invalid user" unless $user =~ $ACCOUNT_RE;
|
||||
return "ERROR: Invalid remote" unless defined $remote && $remote =~ $REMOTE_RE;
|
||||
return "ERROR: Invalid timestamp" unless defined $timestamp && $timestamp =~ $TS_RE;
|
||||
return "ERROR: Remote not allowed" unless _is_remote_allowed($remote);
|
||||
|
||||
my @opts = ("--remote=$remote", "--timestamp=$timestamp");
|
||||
if (defined $path && $path ne '') {
|
||||
return "ERROR: Invalid path" unless $path =~ $OPT_PATTERNS{path};
|
||||
push @opts, "--path=$path";
|
||||
}
|
||||
|
||||
my ($ok, $stdout, $stderr) = _run_gniza4cp('restore', 'list-files', $user, @opts);
|
||||
return $ok ? $stdout : "ERROR: $stderr";
|
||||
}
|
||||
|
||||
sub LIST_DBUSERS {
|
||||
my ($self, $remote, $timestamp) = @_;
|
||||
my $user = $self->get_caller_username() // '';
|
||||
|
||||
return "ERROR: Invalid user" unless $user =~ $ACCOUNT_RE;
|
||||
return "ERROR: Invalid remote" unless defined $remote && $remote =~ $REMOTE_RE;
|
||||
return "ERROR: Invalid timestamp" unless defined $timestamp && $timestamp =~ $TS_RE;
|
||||
return "ERROR: Remote not allowed" unless _is_remote_allowed($remote);
|
||||
|
||||
my ($ok, $stdout, $stderr) = _run_gniza4cp('restore', 'list-dbusers', $user,
|
||||
"--remote=$remote", "--timestamp=$timestamp");
|
||||
return $ok ? $stdout : "ERROR: $stderr";
|
||||
}
|
||||
|
||||
sub LIST_CRON {
|
||||
my ($self, $remote, $timestamp) = @_;
|
||||
my $user = $self->get_caller_username() // '';
|
||||
|
||||
return "ERROR: Invalid user" unless $user =~ $ACCOUNT_RE;
|
||||
return "ERROR: Invalid remote" unless defined $remote && $remote =~ $REMOTE_RE;
|
||||
return "ERROR: Invalid timestamp" unless defined $timestamp && $timestamp =~ $TS_RE;
|
||||
return "ERROR: Remote not allowed" unless _is_remote_allowed($remote);
|
||||
|
||||
my ($ok, $stdout, $stderr) = _run_gniza4cp('restore', 'list-cron', $user,
|
||||
"--remote=$remote", "--timestamp=$timestamp");
|
||||
return $ok ? $stdout : "ERROR: $stderr";
|
||||
}
|
||||
|
||||
sub LIST_DNS {
|
||||
my ($self, $remote, $timestamp) = @_;
|
||||
my $user = $self->get_caller_username() // '';
|
||||
|
||||
return "ERROR: Invalid user" unless $user =~ $ACCOUNT_RE;
|
||||
return "ERROR: Invalid remote" unless defined $remote && $remote =~ $REMOTE_RE;
|
||||
return "ERROR: Invalid timestamp" unless defined $timestamp && $timestamp =~ $TS_RE;
|
||||
return "ERROR: Remote not allowed" unless _is_remote_allowed($remote);
|
||||
|
||||
my ($ok, $stdout, $stderr) = _run_gniza4cp('restore', 'list-dns', $user,
|
||||
"--remote=$remote", "--timestamp=$timestamp");
|
||||
return $ok ? $stdout : "ERROR: $stderr";
|
||||
}
|
||||
|
||||
sub LIST_SSL {
|
||||
my ($self, $remote, $timestamp) = @_;
|
||||
my $user = $self->get_caller_username() // '';
|
||||
|
||||
return "ERROR: Invalid user" unless $user =~ $ACCOUNT_RE;
|
||||
return "ERROR: Invalid remote" unless defined $remote && $remote =~ $REMOTE_RE;
|
||||
return "ERROR: Invalid timestamp" unless defined $timestamp && $timestamp =~ $TS_RE;
|
||||
return "ERROR: Remote not allowed" unless _is_remote_allowed($remote);
|
||||
|
||||
my ($ok, $stdout, $stderr) = _run_gniza4cp('restore', 'list-ssl', $user,
|
||||
"--remote=$remote", "--timestamp=$timestamp");
|
||||
return $ok ? $stdout : "ERROR: $stderr";
|
||||
}
|
||||
|
||||
# ── Background restore ────────────────────────────────────────
|
||||
#
|
||||
# START_RESTORE($remote, $timestamp, $types_str, $path, $exclude_paths)
|
||||
#
|
||||
# $types_str encodes selected types and items, semicolon-separated:
|
||||
# account;files;database:db1,db2;mailbox:a@b.com;cron;dbusers:u1;domains:d1;ssl:d1
|
||||
#
|
||||
# Validates all inputs, forks a detached child that runs each gniza4cp
|
||||
# restore command and logs results via _log_activity(), then returns
|
||||
# immediately with "OK".
|
||||
|
||||
my %TYPE_ITEM_RE = (
|
||||
account => undef, # no items
|
||||
files => undef, # uses $path arg instead
|
||||
cron => undef, # no items
|
||||
database => $DBNAME_RE,
|
||||
dbusers => $DBNAME_RE,
|
||||
mailbox => $EMAIL_RE,
|
||||
domains => $DOMAIN_RE,
|
||||
ssl => $DOMAIN_RE,
|
||||
);
|
||||
|
||||
# Map type + item to gniza4cp CLI arguments (excluding --remote/--timestamp which are always added)
|
||||
sub _build_restore_args {
|
||||
my ($type, $user, $item, $path, $exclude) = @_;
|
||||
if ($type eq 'account') {
|
||||
my @args = ('restore', 'account', $user);
|
||||
push @args, "--exclude=$exclude" if defined $exclude && $exclude ne '';
|
||||
return @args;
|
||||
}
|
||||
elsif ($type eq 'files') {
|
||||
my @args = ('restore', 'files', $user);
|
||||
push @args, "--path=$path" if defined $path && $path ne '';
|
||||
push @args, "--exclude=$exclude" if defined $exclude && $exclude ne '';
|
||||
return @args;
|
||||
}
|
||||
elsif ($type eq 'cron') {
|
||||
return ('restore', 'cron', $user);
|
||||
}
|
||||
elsif ($type eq 'database') {
|
||||
my @args = ('restore', 'database', $user);
|
||||
push @args, $item if defined $item && $item ne '';
|
||||
return @args;
|
||||
}
|
||||
elsif ($type eq 'dbusers') {
|
||||
my @args = ('restore', 'dbusers', $user);
|
||||
push @args, $item if defined $item && $item ne '';
|
||||
return @args;
|
||||
}
|
||||
elsif ($type eq 'mailbox') {
|
||||
my @args = ('restore', 'mailbox', $user);
|
||||
push @args, $item if defined $item && $item ne '';
|
||||
return @args;
|
||||
}
|
||||
elsif ($type eq 'domains') {
|
||||
my @args = ('restore', 'domains', $user);
|
||||
push @args, $item if defined $item && $item ne '';
|
||||
return @args;
|
||||
}
|
||||
elsif ($type eq 'ssl') {
|
||||
my @args = ('restore', 'ssl', $user);
|
||||
push @args, $item if defined $item && $item ne '';
|
||||
return @args;
|
||||
}
|
||||
return ();
|
||||
}
|
||||
|
||||
# Map type to RESTORE_* action name for _log_activity
|
||||
my %TYPE_ACTION_MAP = (
|
||||
account => 'RESTORE_ACCOUNT',
|
||||
files => 'RESTORE_FILES',
|
||||
database => 'RESTORE_DATABASE',
|
||||
dbusers => 'RESTORE_DBUSERS',
|
||||
mailbox => 'RESTORE_MAILBOX',
|
||||
cron => 'RESTORE_CRON',
|
||||
domains => 'RESTORE_DOMAINS',
|
||||
ssl => 'RESTORE_SSL',
|
||||
);
|
||||
|
||||
sub START_RESTORE {
|
||||
my ($self, $remote, $timestamp, $types_str, $path, $exclude_paths) = @_;
|
||||
my $user = $self->get_caller_username() // '';
|
||||
|
||||
# ── Validate common args ──
|
||||
return "ERROR: Invalid user" unless $user =~ $ACCOUNT_RE;
|
||||
return "ERROR: Invalid remote" unless defined $remote && $remote =~ $REMOTE_RE;
|
||||
return "ERROR: Invalid timestamp" unless defined $timestamp && $timestamp =~ $TS_RE;
|
||||
return "ERROR: Remote not allowed" unless _is_remote_allowed($remote);
|
||||
|
||||
if (defined $path && $path ne '') {
|
||||
return "ERROR: Invalid path" unless $path =~ $OPT_PATTERNS{path};
|
||||
}
|
||||
if (defined $exclude_paths && $exclude_paths ne '') {
|
||||
return "ERROR: Invalid exclude" unless $exclude_paths =~ $OPT_PATTERNS{exclude};
|
||||
}
|
||||
|
||||
# ── Parse and validate types_str ──
|
||||
$types_str //= '';
|
||||
return "ERROR: No restore types specified" if $types_str eq '';
|
||||
|
||||
my @jobs; # [ { type => ..., items => [...] }, ... ]
|
||||
for my $part (split /;/, $types_str) {
|
||||
next if $part eq '';
|
||||
my ($type, $items_str) = split /:/, $part, 2;
|
||||
|
||||
return "ERROR: Invalid restore type: $type" unless exists $TYPE_ITEM_RE{$type};
|
||||
|
||||
my $item_re = $TYPE_ITEM_RE{$type};
|
||||
my @items;
|
||||
|
||||
if (defined $items_str && $items_str ne '') {
|
||||
return "ERROR: Type '$type' does not accept items" unless defined $item_re;
|
||||
for my $item (split /,/, $items_str) {
|
||||
next if $item eq '';
|
||||
return "ERROR: Invalid item for $type: $item" unless $item =~ $item_re;
|
||||
push @items, $item;
|
||||
return "ERROR: Too many items for $type (max 100)" if @items > 100;
|
||||
}
|
||||
}
|
||||
|
||||
push @jobs, { type => $type, items => \@items };
|
||||
}
|
||||
|
||||
return "ERROR: No valid restore types parsed" unless @jobs;
|
||||
|
||||
# ── Pre-build all command arg lists to validate before forking ──
|
||||
my @cmd_list; # [ { args => [...], action => ..., details => ... }, ... ]
|
||||
for my $job (@jobs) {
|
||||
my $type = $job->{type};
|
||||
my @items = @{$job->{items}};
|
||||
|
||||
if (@items) {
|
||||
# One command per item
|
||||
for my $item (@items) {
|
||||
my @args = _build_restore_args($type, $user, $item, $path, $exclude_paths);
|
||||
return "ERROR: Failed to build command for $type" unless @args;
|
||||
my $details = "remote=$remote snapshot=$timestamp";
|
||||
if ($type eq 'database') { $details .= " database=$item"; }
|
||||
elsif ($type eq 'dbusers') { $details .= " dbuser=$item"; }
|
||||
elsif ($type eq 'mailbox') { $details .= " email=$item"; }
|
||||
elsif ($type eq 'domains') { $details .= " domain=$item"; }
|
||||
elsif ($type eq 'ssl') { $details .= " domain=$item"; }
|
||||
push @cmd_list, { args => \@args, action => $TYPE_ACTION_MAP{$type}, details => $details };
|
||||
}
|
||||
} else {
|
||||
# Single command for this type
|
||||
my @args = _build_restore_args($type, $user, '', $path, $exclude_paths);
|
||||
return "ERROR: Failed to build command for $type" unless @args;
|
||||
my $details = "remote=$remote snapshot=$timestamp";
|
||||
$details .= " path=$path" if $type eq 'files' && defined $path && $path ne '';
|
||||
$details .= " exclude=$exclude_paths" if defined $exclude_paths && $exclude_paths ne '';
|
||||
push @cmd_list, { args => \@args, action => $TYPE_ACTION_MAP{$type}, details => $details };
|
||||
}
|
||||
}
|
||||
|
||||
# ── Fork detached child ──
|
||||
local $SIG{CHLD} = 'IGNORE';
|
||||
my $pid = fork();
|
||||
return "ERROR: Fork failed: $!" unless defined $pid;
|
||||
|
||||
if ($pid == 0) {
|
||||
# Child: detach from parent completely
|
||||
eval {
|
||||
setsid();
|
||||
open STDIN, '<', '/dev/null';
|
||||
open STDOUT, '>', '/dev/null';
|
||||
open STDERR, '>', '/dev/null';
|
||||
|
||||
for my $cmd (@cmd_list) {
|
||||
my @full_args = (@{$cmd->{args}}, "--remote=$remote", "--timestamp=$timestamp");
|
||||
my ($ok, $stdout, $stderr) = _run_gniza4cp(@full_args);
|
||||
_log_activity($user, $cmd->{action}, $cmd->{details},
|
||||
$ok ? 'OK' : 'Error', $ok ? $stdout : $stderr);
|
||||
}
|
||||
};
|
||||
# Ensure child exits even on error
|
||||
POSIX::_exit(0);
|
||||
}
|
||||
|
||||
# Parent: return immediately
|
||||
return "OK";
|
||||
}
|
||||
|
||||
sub RESTORE_ACCOUNT {
|
||||
my ($self, $remote, $timestamp, $exclude) = @_;
|
||||
my $user = $self->get_caller_username() // '';
|
||||
|
||||
return "ERROR: Invalid user" unless $user =~ $ACCOUNT_RE;
|
||||
return "ERROR: Invalid remote" unless defined $remote && $remote =~ $REMOTE_RE;
|
||||
return "ERROR: Invalid timestamp" unless defined $timestamp && $timestamp =~ $TS_RE;
|
||||
return "ERROR: Remote not allowed" unless _is_remote_allowed($remote);
|
||||
|
||||
my @opts = ("--remote=$remote", "--timestamp=$timestamp");
|
||||
# NOTE: --terminate is NEVER passed for user restore
|
||||
if (defined $exclude && $exclude ne '') {
|
||||
return "ERROR: Invalid exclude" unless $exclude =~ $OPT_PATTERNS{exclude};
|
||||
push @opts, "--exclude=$exclude";
|
||||
}
|
||||
|
||||
my $details = "remote=$remote snapshot=$timestamp";
|
||||
$details .= " exclude=$exclude" if defined $exclude && $exclude ne '';
|
||||
|
||||
my ($ok, $stdout, $stderr) = _run_gniza4cp('restore', 'account', $user, @opts);
|
||||
_log_activity($user, 'RESTORE_ACCOUNT', $details,
|
||||
$ok ? 'OK' : 'Error', $ok ? $stdout : $stderr);
|
||||
return $ok ? "OK\n$stdout" : "ERROR: $stderr";
|
||||
}
|
||||
|
||||
sub RESTORE_FILES {
|
||||
my ($self, $remote, $timestamp, $path, $exclude) = @_;
|
||||
my $user = $self->get_caller_username() // '';
|
||||
|
||||
return "ERROR: Invalid user" unless $user =~ $ACCOUNT_RE;
|
||||
return "ERROR: Invalid remote" unless defined $remote && $remote =~ $REMOTE_RE;
|
||||
return "ERROR: Invalid timestamp" unless defined $timestamp && $timestamp =~ $TS_RE;
|
||||
return "ERROR: Remote not allowed" unless _is_remote_allowed($remote);
|
||||
|
||||
my @opts = ("--remote=$remote", "--timestamp=$timestamp");
|
||||
if (defined $path && $path ne '') {
|
||||
return "ERROR: Invalid path" unless $path =~ $OPT_PATTERNS{path};
|
||||
push @opts, "--path=$path";
|
||||
}
|
||||
if (defined $exclude && $exclude ne '') {
|
||||
return "ERROR: Invalid exclude" unless $exclude =~ $OPT_PATTERNS{exclude};
|
||||
push @opts, "--exclude=$exclude";
|
||||
}
|
||||
|
||||
my $details = "remote=$remote snapshot=$timestamp";
|
||||
$details .= " path=$path" if defined $path && $path ne '';
|
||||
$details .= " exclude=$exclude" if defined $exclude && $exclude ne '';
|
||||
|
||||
my ($ok, $stdout, $stderr) = _run_gniza4cp('restore', 'files', $user, @opts);
|
||||
_log_activity($user, 'RESTORE_FILES', $details,
|
||||
$ok ? 'OK' : 'Error', $ok ? $stdout : $stderr);
|
||||
return $ok ? "OK\n$stdout" : "ERROR: $stderr";
|
||||
}
|
||||
|
||||
sub RESTORE_DATABASE {
|
||||
my ($self, $remote, $timestamp, $dbname) = @_;
|
||||
my $user = $self->get_caller_username() // '';
|
||||
|
||||
return "ERROR: Invalid user" unless $user =~ $ACCOUNT_RE;
|
||||
return "ERROR: Invalid remote" unless defined $remote && $remote =~ $REMOTE_RE;
|
||||
return "ERROR: Invalid timestamp" unless defined $timestamp && $timestamp =~ $TS_RE;
|
||||
return "ERROR: Remote not allowed" unless _is_remote_allowed($remote);
|
||||
|
||||
my @args = ($user);
|
||||
if (defined $dbname && $dbname ne '') {
|
||||
return "ERROR: Invalid database name" unless $dbname =~ $DBNAME_RE;
|
||||
push @args, $dbname;
|
||||
}
|
||||
|
||||
my $details = "remote=$remote snapshot=$timestamp";
|
||||
$details .= " database=$dbname" if defined $dbname && $dbname ne '';
|
||||
|
||||
my ($ok, $stdout, $stderr) = _run_gniza4cp('restore', 'database', @args,
|
||||
"--remote=$remote", "--timestamp=$timestamp");
|
||||
_log_activity($user, 'RESTORE_DATABASE', $details,
|
||||
$ok ? 'OK' : 'Error', $ok ? $stdout : $stderr);
|
||||
return $ok ? "OK\n$stdout" : "ERROR: $stderr";
|
||||
}
|
||||
|
||||
sub RESTORE_MAILBOX {
|
||||
my ($self, $remote, $timestamp, $email) = @_;
|
||||
my $user = $self->get_caller_username() // '';
|
||||
|
||||
return "ERROR: Invalid user" unless $user =~ $ACCOUNT_RE;
|
||||
return "ERROR: Invalid remote" unless defined $remote && $remote =~ $REMOTE_RE;
|
||||
return "ERROR: Invalid timestamp" unless defined $timestamp && $timestamp =~ $TS_RE;
|
||||
return "ERROR: Remote not allowed" unless _is_remote_allowed($remote);
|
||||
|
||||
my @args = ($user);
|
||||
if (defined $email && $email ne '') {
|
||||
return "ERROR: Invalid email" unless $email =~ $EMAIL_RE;
|
||||
push @args, $email;
|
||||
}
|
||||
|
||||
my $details = "remote=$remote snapshot=$timestamp";
|
||||
$details .= " email=$email" if defined $email && $email ne '';
|
||||
|
||||
my ($ok, $stdout, $stderr) = _run_gniza4cp('restore', 'mailbox', @args,
|
||||
"--remote=$remote", "--timestamp=$timestamp");
|
||||
_log_activity($user, 'RESTORE_MAILBOX', $details,
|
||||
$ok ? 'OK' : 'Error', $ok ? $stdout : $stderr);
|
||||
return $ok ? "OK\n$stdout" : "ERROR: $stderr";
|
||||
}
|
||||
|
||||
sub RESTORE_CRON {
|
||||
my ($self, $remote, $timestamp) = @_;
|
||||
my $user = $self->get_caller_username() // '';
|
||||
|
||||
return "ERROR: Invalid user" unless $user =~ $ACCOUNT_RE;
|
||||
return "ERROR: Invalid remote" unless defined $remote && $remote =~ $REMOTE_RE;
|
||||
return "ERROR: Invalid timestamp" unless defined $timestamp && $timestamp =~ $TS_RE;
|
||||
return "ERROR: Remote not allowed" unless _is_remote_allowed($remote);
|
||||
|
||||
my $details = "remote=$remote snapshot=$timestamp";
|
||||
|
||||
my ($ok, $stdout, $stderr) = _run_gniza4cp('restore', 'cron', $user,
|
||||
"--remote=$remote", "--timestamp=$timestamp");
|
||||
_log_activity($user, 'RESTORE_CRON', $details,
|
||||
$ok ? 'OK' : 'Error', $ok ? $stdout : $stderr);
|
||||
return $ok ? "OK\n$stdout" : "ERROR: $stderr";
|
||||
}
|
||||
|
||||
sub RESTORE_DBUSERS {
|
||||
my ($self, $remote, $timestamp, $dbuser) = @_;
|
||||
my $user = $self->get_caller_username() // '';
|
||||
|
||||
return "ERROR: Invalid user" unless $user =~ $ACCOUNT_RE;
|
||||
return "ERROR: Invalid remote" unless defined $remote && $remote =~ $REMOTE_RE;
|
||||
return "ERROR: Invalid timestamp" unless defined $timestamp && $timestamp =~ $TS_RE;
|
||||
return "ERROR: Remote not allowed" unless _is_remote_allowed($remote);
|
||||
|
||||
my @args = ($user);
|
||||
if (defined $dbuser && $dbuser ne '') {
|
||||
return "ERROR: Invalid database user" unless $dbuser =~ $DBNAME_RE;
|
||||
push @args, $dbuser;
|
||||
}
|
||||
|
||||
my $details = "remote=$remote snapshot=$timestamp";
|
||||
$details .= " dbuser=$dbuser" if defined $dbuser && $dbuser ne '';
|
||||
|
||||
my ($ok, $stdout, $stderr) = _run_gniza4cp('restore', 'dbusers', @args,
|
||||
"--remote=$remote", "--timestamp=$timestamp");
|
||||
_log_activity($user, 'RESTORE_DBUSERS', $details,
|
||||
$ok ? 'OK' : 'Error', $ok ? $stdout : $stderr);
|
||||
return $ok ? "OK\n$stdout" : "ERROR: $stderr";
|
||||
}
|
||||
|
||||
sub RESTORE_DOMAINS {
|
||||
my ($self, $remote, $timestamp, $domain) = @_;
|
||||
my $user = $self->get_caller_username() // '';
|
||||
|
||||
return "ERROR: Invalid user" unless $user =~ $ACCOUNT_RE;
|
||||
return "ERROR: Invalid remote" unless defined $remote && $remote =~ $REMOTE_RE;
|
||||
return "ERROR: Invalid timestamp" unless defined $timestamp && $timestamp =~ $TS_RE;
|
||||
return "ERROR: Remote not allowed" unless _is_remote_allowed($remote);
|
||||
|
||||
my @args = ($user);
|
||||
if (defined $domain && $domain ne '') {
|
||||
return "ERROR: Invalid domain" unless $domain =~ $DOMAIN_RE;
|
||||
push @args, $domain;
|
||||
}
|
||||
|
||||
my $details = "remote=$remote snapshot=$timestamp";
|
||||
$details .= " domain=$domain" if defined $domain && $domain ne '';
|
||||
|
||||
my ($ok, $stdout, $stderr) = _run_gniza4cp('restore', 'domains', @args,
|
||||
"--remote=$remote", "--timestamp=$timestamp");
|
||||
_log_activity($user, 'RESTORE_DOMAINS', $details,
|
||||
$ok ? 'OK' : 'Error', $ok ? $stdout : $stderr);
|
||||
return $ok ? "OK\n$stdout" : "ERROR: $stderr";
|
||||
}
|
||||
|
||||
sub RESTORE_SSL {
|
||||
my ($self, $remote, $timestamp, $domain) = @_;
|
||||
my $user = $self->get_caller_username() // '';
|
||||
|
||||
return "ERROR: Invalid user" unless $user =~ $ACCOUNT_RE;
|
||||
return "ERROR: Invalid remote" unless defined $remote && $remote =~ $REMOTE_RE;
|
||||
return "ERROR: Invalid timestamp" unless defined $timestamp && $timestamp =~ $TS_RE;
|
||||
return "ERROR: Remote not allowed" unless _is_remote_allowed($remote);
|
||||
|
||||
my @args = ($user);
|
||||
if (defined $domain && $domain ne '') {
|
||||
return "ERROR: Invalid domain" unless $domain =~ $DOMAIN_RE;
|
||||
push @args, $domain;
|
||||
}
|
||||
|
||||
my $details = "remote=$remote snapshot=$timestamp";
|
||||
$details .= " domain=$domain" if defined $domain && $domain ne '';
|
||||
|
||||
my ($ok, $stdout, $stderr) = _run_gniza4cp('restore', 'ssl', @args,
|
||||
"--remote=$remote", "--timestamp=$timestamp");
|
||||
_log_activity($user, 'RESTORE_SSL', $details,
|
||||
$ok ? 'OK' : 'Error', $ok ? $stdout : $stderr);
|
||||
return $ok ? "OK\n$stdout" : "ERROR: $stderr";
|
||||
}
|
||||
|
||||
__PACKAGE__->run() if !caller;
|
||||
|
||||
1;
|
||||
|
Before Width: | Height: | Size: 369 KiB |
@@ -1,13 +0,0 @@
|
||||
[
|
||||
{
|
||||
"type": "link",
|
||||
"id": "gniza",
|
||||
"name": "GNIZA Backups",
|
||||
"group_id": "files",
|
||||
"description": "Restore files, databases, email, and more from gniza backups",
|
||||
"uri": "gniza/index.live.cgi",
|
||||
"feature": "gniza_restore",
|
||||
"order": 1,
|
||||
"icon": "gniza/assets/gniza-cpanel-icon.png"
|
||||
}
|
||||
]
|
||||
|
Before Width: | Height: | Size: 720 B After Width: | Height: | Size: 720 B |
|
Before Width: | Height: | Size: 512 B After Width: | Height: | Size: 512 B |
|
Before Width: | Height: | Size: 685 B After Width: | Height: | Size: 685 B |
2
cpanel/gniza4cp/assets/gniza4cp-whm.css
Normal file
@@ -1,5 +1,5 @@
|
||||
#!/usr/local/cpanel/3rdparty/bin/perl
|
||||
# gniza cPanel Plugin — Step 1: Select Remote + Snapshot
|
||||
# gniza4cp cPanel Plugin — Step 1: Select Remote + Snapshot
|
||||
use strict;
|
||||
use warnings;
|
||||
|
||||
@@ -16,22 +16,23 @@ BEGIN {
|
||||
|
||||
use Cpanel::LiveAPI ();
|
||||
use Cpanel::AdminBin::Call ();
|
||||
use GnizaCPanel::UI;
|
||||
use Gniza4cpCPanel::UI;
|
||||
|
||||
my $cpanel = Cpanel::LiveAPI->new();
|
||||
print "Content-Type: text/html\r\n\r\n";
|
||||
print $cpanel->header('GNIZA Backups');
|
||||
print $cpanel->header('');
|
||||
|
||||
# Get allowed remotes via AdminBin
|
||||
my $remotes_raw = eval { Cpanel::AdminBin::Call::call('Gniza', 'Restore', 'LIST_ALLOWED_REMOTES') } // '';
|
||||
my $remotes_raw = eval { Cpanel::AdminBin::Call::call('Gniza4cp', 'Restore', 'LIST_ALLOWED_REMOTES') } // '';
|
||||
my @remotes = grep { $_ ne '' } split /\n/, $remotes_raw;
|
||||
|
||||
print GnizaCPanel::UI::page_header('GNIZA Backups');
|
||||
print GnizaCPanel::UI::render_flash();
|
||||
print Gniza4cpCPanel::UI::page_header('GNIZA Backups');
|
||||
print Gniza4cpCPanel::UI::render_nav('index.live.cgi');
|
||||
print Gniza4cpCPanel::UI::render_flash();
|
||||
|
||||
if (!@remotes) {
|
||||
print qq{<div class="alert alert-info mb-4">No backup remotes are available for restore. Please contact your server administrator.</div>\n};
|
||||
print GnizaCPanel::UI::page_footer();
|
||||
print Gniza4cpCPanel::UI::page_footer();
|
||||
print $cpanel->footer();
|
||||
$cpanel->end();
|
||||
exit;
|
||||
@@ -43,10 +44,10 @@ print qq{<h2 class="card-title text-sm">Select Backup Source</h2>\n};
|
||||
# Remote dropdown
|
||||
print qq{<div class="flex items-center gap-3 mb-2.5">\n};
|
||||
print qq{ <label class="w-36 font-medium text-sm" for="remote">Remote</label>\n};
|
||||
print qq{ <select class="select select-bordered select-sm w-full max-w-xs" id="remote" name="remote" onchange="gnizaLoadSnapshots()">\n};
|
||||
print qq{ <select class="select select-bordered select-sm w-full max-w-xs" id="remote" name="remote" onchange="gniza4cpLoadSnapshots()">\n};
|
||||
print qq{ <option value="">-- Select remote --</option>\n};
|
||||
for my $r (@remotes) {
|
||||
my $esc = GnizaCPanel::UI::esc($r);
|
||||
my $esc = Gniza4cpCPanel::UI::esc($r);
|
||||
print qq{ <option value="$esc">$esc</option>\n};
|
||||
}
|
||||
print qq{ </select>\n};
|
||||
@@ -63,13 +64,13 @@ print qq{</div>\n};
|
||||
print qq{</div>\n</div>\n};
|
||||
|
||||
print qq{<div class="flex items-center gap-2">\n};
|
||||
print qq{ <button type="button" class="btn btn-primary btn-sm" id="next-btn" disabled onclick="gnizaGoNext()">Next</button>\n};
|
||||
print qq{ <button type="button" class="btn btn-primary btn-sm" id="next-btn" disabled onclick="gniza4cpGoNext()">Next</button>\n};
|
||||
print qq{</div>\n};
|
||||
|
||||
# JavaScript for snapshot loading and navigation
|
||||
print <<"END_JS";
|
||||
print <<'END_JS';
|
||||
<script>
|
||||
function gnizaLoadSnapshots() {
|
||||
function gniza4cpLoadSnapshots() {
|
||||
var remote = document.getElementById('remote').value;
|
||||
var sel = document.getElementById('timestamp');
|
||||
var btn = document.getElementById('next-btn');
|
||||
@@ -120,17 +121,27 @@ function _setSelectPlaceholder(sel, text) {
|
||||
sel.appendChild(opt);
|
||||
}
|
||||
|
||||
function _formatTimestamp(ts) {
|
||||
// 2026-03-05T171516 → Mar 5, 2026 at 17:15:16
|
||||
var m = ts.match(/^(\d{4})-(\d{2})-(\d{2})T(\d{2})(\d{2})(\d{2})$/);
|
||||
if (!m) return ts;
|
||||
var months = ['Jan','Feb','Mar','Apr','May','Jun','Jul','Aug','Sep','Oct','Nov','Dec'];
|
||||
var day = parseInt(m[3], 10);
|
||||
var mon = months[parseInt(m[2], 10) - 1] || m[2];
|
||||
return mon + ' ' + day + ', ' + m[1] + ' at ' + m[4] + ':' + m[5] + ':' + m[6];
|
||||
}
|
||||
|
||||
function _populateSelect(sel, values) {
|
||||
while (sel.options.length) sel.remove(0);
|
||||
for (var i = 0; i < values.length; i++) {
|
||||
var opt = document.createElement('option');
|
||||
opt.value = values[i];
|
||||
opt.textContent = values[i];
|
||||
opt.textContent = _formatTimestamp(values[i]);
|
||||
sel.appendChild(opt);
|
||||
}
|
||||
}
|
||||
|
||||
function gnizaGoNext() {
|
||||
function gniza4cpGoNext() {
|
||||
var remote = document.getElementById('remote').value;
|
||||
var timestamp = document.getElementById('timestamp').value;
|
||||
if (!remote || !timestamp) return;
|
||||
@@ -143,6 +154,6 @@ function gnizaGoNext() {
|
||||
</script>
|
||||
END_JS
|
||||
|
||||
print GnizaCPanel::UI::page_footer();
|
||||
print Gniza4cpCPanel::UI::page_footer();
|
||||
print $cpanel->footer();
|
||||
$cpanel->end();
|
||||
13
cpanel/gniza4cp/install.json
Normal file
@@ -0,0 +1,13 @@
|
||||
[
|
||||
{
|
||||
"type": "link",
|
||||
"id": "gniza4cp",
|
||||
"name": "GNIZA Backups",
|
||||
"group_id": "files",
|
||||
"description": "Restore files, databases, email, and more from GNIZA backups",
|
||||
"uri": "gniza4cp/index.live.cgi",
|
||||
"feature": "gniza4cp_restore",
|
||||
"order": 1,
|
||||
"icon": "gniza4cp/assets/gniza4cp-cpanel-icon.png"
|
||||
}
|
||||
]
|
||||
@@ -1,13 +1,19 @@
|
||||
package GnizaCPanel::UI;
|
||||
# Shared UI helpers for the gniza cPanel user restore plugin.
|
||||
# Adapted from GnizaWHM::UI for cPanel context (runs as user, not root).
|
||||
package Gniza4cpCPanel::UI;
|
||||
# Shared UI helpers for the gniza4cp cPanel user restore plugin.
|
||||
# Adapted from Gniza4cpWHM::UI for cPanel context (runs as user, not root).
|
||||
|
||||
use strict;
|
||||
use warnings;
|
||||
use Fcntl qw(:DEFAULT);
|
||||
|
||||
my $CSS_FILE = '/usr/local/cpanel/base/frontend/jupiter/gniza/assets/gniza-whm.css';
|
||||
my $LOGO_FILE = '/usr/local/cpanel/base/frontend/jupiter/gniza/assets/gniza-logo.svg';
|
||||
my $CSS_FILE = '/usr/local/cpanel/base/frontend/jupiter/gniza4cp/assets/gniza4cp-whm.css';
|
||||
my $LOGO_FILE = '/usr/local/cpanel/base/frontend/jupiter/gniza4cp/assets/gniza4cp-logo.svg';
|
||||
my $_logo_data_uri = '';
|
||||
|
||||
my @NAV_ITEMS = (
|
||||
{ url => 'index.live.cgi', label => 'Restore' },
|
||||
{ url => 'logs.live.cgi', label => 'Logs' },
|
||||
);
|
||||
|
||||
# ── HTML Escaping ─────────────────────────────────────────────
|
||||
|
||||
@@ -22,6 +28,37 @@ sub esc {
|
||||
return $str;
|
||||
}
|
||||
|
||||
# ── Navigation ────────────────────────────────────────────────
|
||||
|
||||
sub render_nav {
|
||||
my ($current_page) = @_;
|
||||
my $logo = '';
|
||||
if ($_logo_data_uri) {
|
||||
$logo = qq{<img src="$_logo_data_uri" alt="" style="height:3rem;width:auto">}
|
||||
. qq{<span class="text-3xl font-bold leading-none">GNIZA <span class="text-secondary">Backup</span></span>};
|
||||
}
|
||||
my $menu_items = '';
|
||||
for my $item (@NAV_ITEMS) {
|
||||
my $is_active = ($item->{url} eq $current_page);
|
||||
my $label = esc($item->{label});
|
||||
my $style = $is_active
|
||||
? 'style="font-weight:600;color:inherit"'
|
||||
: 'style="color:inherit;opacity:0.7"';
|
||||
$menu_items .= qq{<a class="no-underline" href="$item->{url}" $style>$label</a>\n};
|
||||
}
|
||||
|
||||
# Use inline styles to avoid cPanel Jupiter CSS conflicts with DaisyUI navbar
|
||||
my $html = qq{<div class="bg-base-200 rounded-box mb-5" style="display:flex;align-items:center;justify-content:space-between;padding:0.5rem 1rem;min-height:4rem">\n};
|
||||
$html .= qq{ <div style="display:flex;align-items:center;gap:0.5rem">\n};
|
||||
$html .= qq{ $logo\n} if $logo;
|
||||
$html .= qq{ </div>\n};
|
||||
$html .= qq{ <div style="display:flex;align-items:center;gap:1.5rem;font-size:0.95rem">\n};
|
||||
$html .= qq{ $menu_items};
|
||||
$html .= qq{ </div>\n};
|
||||
$html .= qq{</div>\n};
|
||||
return $html;
|
||||
}
|
||||
|
||||
# ── Current User ─────────────────────────────────────────────
|
||||
|
||||
sub get_current_user {
|
||||
@@ -61,7 +98,7 @@ sub _safe_read {
|
||||
|
||||
sub _flash_file {
|
||||
my $user = get_current_user();
|
||||
return "/tmp/.gniza-cpanel-flash-$user";
|
||||
return "/tmp/.gniza4cp-cpanel-flash-$user";
|
||||
}
|
||||
|
||||
sub set_flash {
|
||||
@@ -101,7 +138,7 @@ my $_current_csrf_token;
|
||||
|
||||
sub _csrf_file {
|
||||
my $user = get_current_user();
|
||||
return "/tmp/.gniza-cpanel-csrf-$user";
|
||||
return "/tmp/.gniza4cp-cpanel-csrf-$user";
|
||||
}
|
||||
|
||||
sub generate_csrf_token {
|
||||
@@ -163,14 +200,14 @@ sub verify_csrf_token {
|
||||
|
||||
sub csrf_hidden_field {
|
||||
my $token = generate_csrf_token();
|
||||
return qq{<input type="hidden" name="gniza_csrf" value="} . esc($token) . qq{">};
|
||||
return qq{<input type="hidden" name="gniza4cp_csrf" value="} . esc($token) . qq{">};
|
||||
}
|
||||
|
||||
# ── Page Wrappers ────────────────────────────────────────────
|
||||
|
||||
sub page_header {
|
||||
my ($title) = @_;
|
||||
$title = esc($title // 'gniza Restore');
|
||||
$title = esc($title // 'GNIZA Restore');
|
||||
my $css = '';
|
||||
if (open my $fh, '<', $CSS_FILE) {
|
||||
local $/;
|
||||
@@ -180,20 +217,17 @@ sub page_header {
|
||||
$css = _unwrap_layers($css);
|
||||
$css = _scope_to_container($css);
|
||||
|
||||
# Inline logo as base64 data URI
|
||||
my $logo_html = '';
|
||||
if (open my $lfh, '<', $LOGO_FILE) {
|
||||
# Pre-compute logo data URI for render_nav()
|
||||
if (!$_logo_data_uri && open my $lfh, '<', $LOGO_FILE) {
|
||||
local $/;
|
||||
my $svg_data = <$lfh>;
|
||||
close $lfh;
|
||||
require MIME::Base64;
|
||||
my $b64 = MIME::Base64::encode_base64($svg_data, '');
|
||||
$logo_html = qq{<div class="flex items-center justify-center gap-3 mb-4"><img src="data:image/svg+xml;base64,$b64" alt="gniza" style="height:40px;width:auto"></div>\n};
|
||||
$_logo_data_uri = 'data:image/svg+xml;base64,' . MIME::Base64::encode_base64($svg_data, '');
|
||||
}
|
||||
|
||||
return qq{<style>$css</style>\n}
|
||||
. qq{<div data-theme="gniza" class="font-sans text-base" style="padding:20px 10px 10px 10px">\n}
|
||||
. $logo_html;
|
||||
. qq{<div data-theme="gniza4cp" class="font-sans text-base" style="padding:20px 10px 10px 10px">\n};
|
||||
}
|
||||
|
||||
sub page_footer {
|
||||
@@ -243,7 +277,7 @@ sub _scope_to_container {
|
||||
$css =~ s/:where\(:root\)/\&/g;
|
||||
$css =~ s/:root,\s*\[data-theme[^\]]*\]/\&/g;
|
||||
$css =~ s/\[data-theme=light\]/\&/g;
|
||||
$css =~ s/\[data-theme=gniza\]/\&/g;
|
||||
$css =~ s/\[data-theme=gniza4cp\]/\&/g;
|
||||
$css =~ s/:root:not\(span\)/\&/g;
|
||||
$css =~ s/:root:has\(/\&:has(/g;
|
||||
$css =~ s/:root\b/\&/g;
|
||||
@@ -275,7 +309,7 @@ sub _scope_to_container {
|
||||
$i++;
|
||||
}
|
||||
|
||||
return join('', @top_level) . '[data-theme="gniza"]{' . $scoped . '}';
|
||||
return join('', @top_level) . '[data-theme="gniza4cp"]{' . $scoped . '}';
|
||||
}
|
||||
|
||||
sub render_errors {
|
||||
223
cpanel/gniza4cp/logs.live.cgi
Normal file
@@ -0,0 +1,223 @@
|
||||
#!/usr/local/cpanel/3rdparty/bin/perl
|
||||
# gniza4cp cPanel Plugin — Activity Logs
|
||||
# Shows user-initiated restore actions and their results
|
||||
use strict;
|
||||
use warnings;
|
||||
|
||||
BEGIN {
|
||||
my $base;
|
||||
if ($0 =~ m{^(.*)/}) {
|
||||
$base = $1;
|
||||
} else {
|
||||
$base = '.';
|
||||
}
|
||||
unshift @INC, "$base/lib";
|
||||
}
|
||||
|
||||
use Cpanel::LiveAPI ();
|
||||
use Cpanel::AdminBin::Call ();
|
||||
use Cpanel::Form ();
|
||||
use Gniza4cpCPanel::UI;
|
||||
|
||||
my $cpanel = Cpanel::LiveAPI->new();
|
||||
END { $cpanel->end() if $cpanel }
|
||||
my $form = Cpanel::Form::parseform();
|
||||
my $entry = $form->{'entry'} // '';
|
||||
|
||||
if ($entry ne '') {
|
||||
show_entry($entry);
|
||||
} else {
|
||||
show_list();
|
||||
}
|
||||
|
||||
exit;
|
||||
|
||||
# ── List View ────────────────────────────────────────────────
|
||||
|
||||
sub show_list {
|
||||
print "Content-Type: text/html\r\n\r\n";
|
||||
print $cpanel->header('');
|
||||
print Gniza4cpCPanel::UI::page_header('GNIZA Activity Log');
|
||||
print Gniza4cpCPanel::UI::render_nav('logs.live.cgi');
|
||||
print Gniza4cpCPanel::UI::render_flash();
|
||||
|
||||
my $raw = eval { Cpanel::AdminBin::Call::call('Gniza4cp', 'Restore', 'LIST_LOGS') } // '';
|
||||
|
||||
if ($raw =~ /^ERROR: (.*)/) {
|
||||
print qq{<div class="alert alert-error mb-4">} . Gniza4cpCPanel::UI::esc($1) . qq{</div>\n};
|
||||
print Gniza4cpCPanel::UI::page_footer();
|
||||
print $cpanel->footer();
|
||||
return;
|
||||
}
|
||||
|
||||
my @entries;
|
||||
for my $line (split /\n/, $raw) {
|
||||
next if $line eq '';
|
||||
my ($idx, $date, $action, $details, $status) = split /\t/, $line;
|
||||
push @entries, {
|
||||
idx => $idx // '',
|
||||
date => $date // '',
|
||||
action => $action // '',
|
||||
details => $details // '',
|
||||
status => $status // '',
|
||||
};
|
||||
}
|
||||
|
||||
if (!@entries) {
|
||||
print qq{<div class="alert alert-info mb-4">No restore activity yet. Actions you perform in the Restore section will appear here.</div>\n};
|
||||
print Gniza4cpCPanel::UI::page_footer();
|
||||
print $cpanel->footer();
|
||||
return;
|
||||
}
|
||||
|
||||
# Pagination
|
||||
my $per_page = 25;
|
||||
my $total = scalar @entries;
|
||||
my $page = int($form->{'page'} // 1);
|
||||
$page = 1 if $page < 1;
|
||||
my $total_pages = int(($total + $per_page - 1) / $per_page);
|
||||
$page = $total_pages if $page > $total_pages;
|
||||
my $start = ($page - 1) * $per_page;
|
||||
my $end = $start + $per_page - 1;
|
||||
$end = $#entries if $end > $#entries;
|
||||
my @page_entries = @entries[$start .. $end];
|
||||
|
||||
print qq{<div class="overflow-x-auto rounded-box border border-base-content/5 bg-base-100">\n};
|
||||
print qq{<table class="table">\n};
|
||||
print qq{<thead><tr><th>Date (UTC)</th><th>Action</th><th>Details</th><th>Status</th><th></th></tr></thead>\n};
|
||||
print qq{<tbody>\n};
|
||||
|
||||
for my $e (@page_entries) {
|
||||
my $esc_date = Gniza4cpCPanel::UI::esc($e->{date});
|
||||
my $esc_action = Gniza4cpCPanel::UI::esc($e->{action});
|
||||
my $esc_details = Gniza4cpCPanel::UI::esc($e->{details});
|
||||
my $esc_status = Gniza4cpCPanel::UI::esc($e->{status});
|
||||
my $esc_idx = Gniza4cpCPanel::UI::esc($e->{idx});
|
||||
|
||||
my $status_badge = $e->{status} eq 'Error' ? 'badge-error' : 'badge-success';
|
||||
my $href = 'logs.live.cgi?entry=' . _uri_escape($e->{idx});
|
||||
|
||||
print qq{<tr>\n};
|
||||
print qq{ <td class="whitespace-nowrap">$esc_date</td>\n};
|
||||
print qq{ <td><span class="badge badge-info badge-sm">$esc_action</span></td>\n};
|
||||
print qq{ <td class="text-sm">$esc_details</td>\n};
|
||||
print qq{ <td><span class="badge $status_badge badge-sm">$esc_status</span></td>\n};
|
||||
print qq{ <td><button type="button" class="btn btn-secondary btn-sm" onclick="location.href='$href'">View</button></td>\n};
|
||||
print qq{</tr>\n};
|
||||
}
|
||||
|
||||
print qq{</tbody>\n</table>\n</div>\n};
|
||||
|
||||
# Pagination controls
|
||||
if ($total_pages > 1) {
|
||||
print qq{<div class="flex items-center justify-center gap-2 mt-4">\n};
|
||||
if ($page > 1) {
|
||||
my $prev = $page - 1;
|
||||
print qq{ <button type="button" class="btn btn-sm" onclick="location.href='logs.live.cgi?page=$prev'">« Prev</button>\n};
|
||||
}
|
||||
print qq{ <span class="text-sm">Page $page of $total_pages ($total entries)</span>\n};
|
||||
if ($page < $total_pages) {
|
||||
my $next = $page + 1;
|
||||
print qq{ <button type="button" class="btn btn-sm" onclick="location.href='logs.live.cgi?page=$next'">Next »</button>\n};
|
||||
}
|
||||
print qq{</div>\n};
|
||||
}
|
||||
|
||||
print Gniza4cpCPanel::UI::page_footer();
|
||||
print $cpanel->footer();
|
||||
}
|
||||
|
||||
# ── Entry Detail View ────────────────────────────────────────
|
||||
|
||||
sub show_entry {
|
||||
my ($entry_idx) = @_;
|
||||
|
||||
print "Content-Type: text/html\r\n\r\n";
|
||||
print $cpanel->header('');
|
||||
print Gniza4cpCPanel::UI::page_header('GNIZA Activity Detail');
|
||||
print Gniza4cpCPanel::UI::render_nav('logs.live.cgi');
|
||||
|
||||
# Validate entry index (numeric only)
|
||||
unless ($entry_idx =~ /^[0-9]+$/) {
|
||||
print qq{<div class="alert alert-error mb-4">Invalid entry.</div>\n};
|
||||
print qq{<p><a href="logs.live.cgi" class="link">← Back to activity log</a></p>\n};
|
||||
print Gniza4cpCPanel::UI::page_footer();
|
||||
print $cpanel->footer();
|
||||
return;
|
||||
}
|
||||
|
||||
my $content = eval { Cpanel::AdminBin::Call::call('Gniza4cp', 'Restore', 'GET_LOG', $entry_idx) } // '';
|
||||
|
||||
if ($content =~ /^ERROR: (.*)/) {
|
||||
print qq{<div class="alert alert-error mb-4">} . Gniza4cpCPanel::UI::esc($1) . qq{</div>\n};
|
||||
print qq{<p><a href="logs.live.cgi" class="link">← Back to activity log</a></p>\n};
|
||||
print Gniza4cpCPanel::UI::page_footer();
|
||||
print $cpanel->footer();
|
||||
return;
|
||||
}
|
||||
|
||||
# Parse header fields and output from entry content
|
||||
my ($date, $action, $details, $status, $output) = ('', '', '', '', '');
|
||||
my @lines = split /\n/, $content;
|
||||
my @output_lines;
|
||||
my $in_output = 0;
|
||||
for my $line (@lines) {
|
||||
if (!$in_output) {
|
||||
if ($line =~ /^Date:\s+(.+)$/) { $date = $1; }
|
||||
elsif ($line =~ /^Action:\s+(.+)$/) { $action = $1; }
|
||||
elsif ($line =~ /^Details:\s+(.+)$/) { $details = $1; }
|
||||
elsif ($line =~ /^Status:\s+(.+)$/) { $status = $1; $in_output = 1; }
|
||||
} else {
|
||||
push @output_lines, $line;
|
||||
}
|
||||
}
|
||||
|
||||
# Back link
|
||||
print qq{<p class="mb-4"><a href="logs.live.cgi" class="link">← Back to activity log</a></p>\n};
|
||||
|
||||
# Entry info card
|
||||
my $status_badge = $status eq 'Error' ? 'badge-error' : 'badge-success';
|
||||
print qq{<div class="card bg-base-200 shadow-sm border border-base-300 mb-4">\n};
|
||||
print qq{<div class="card-body py-3 px-4">\n};
|
||||
print qq{<div class="flex flex-wrap gap-4 items-center text-sm">\n};
|
||||
print qq{ <span><strong>Date:</strong> } . Gniza4cpCPanel::UI::esc($date) . qq{</span>\n};
|
||||
print qq{ <span><strong>Action:</strong> <span class="badge badge-info badge-sm">} . Gniza4cpCPanel::UI::esc($action) . qq{</span></span>\n};
|
||||
print qq{ <span><strong>Status:</strong> <span class="badge $status_badge badge-sm">} . Gniza4cpCPanel::UI::esc($status) . qq{</span></span>\n};
|
||||
print qq{</div>\n};
|
||||
print qq{<div class="text-sm mt-2"><strong>Details:</strong> } . Gniza4cpCPanel::UI::esc($details) . qq{</div>\n};
|
||||
print qq{</div>\n</div>\n};
|
||||
|
||||
# Output section
|
||||
if (@output_lines) {
|
||||
print qq{<h3 class="text-sm font-bold mb-2">Command Output</h3>\n};
|
||||
print qq{<pre class="bg-base-100 border border-base-300 rounded-lg p-4 text-sm font-mono overflow-x-auto leading-relaxed">};
|
||||
for my $line (@output_lines) {
|
||||
my $esc = Gniza4cpCPanel::UI::esc($line);
|
||||
if ($line =~ /\[ERROR\]/) {
|
||||
print qq{<span class="text-error font-bold">$esc</span>\n};
|
||||
} elsif ($line =~ /\[WARN\]/) {
|
||||
print qq{<span class="text-warning">$esc</span>\n};
|
||||
} elsif ($line =~ /\[DEBUG\]/) {
|
||||
print qq{<span class="text-base-content/60">$esc</span>\n};
|
||||
} else {
|
||||
print "$esc\n";
|
||||
}
|
||||
}
|
||||
print qq{</pre>\n};
|
||||
} else {
|
||||
print qq{<div class="alert alert-info">No output recorded for this action.</div>\n};
|
||||
}
|
||||
|
||||
print Gniza4cpCPanel::UI::page_footer();
|
||||
print $cpanel->footer();
|
||||
}
|
||||
|
||||
# ── Helpers ───────────────────────────────────────────────────
|
||||
|
||||
sub _uri_escape {
|
||||
my $str = shift // '';
|
||||
$str =~ s/([^A-Za-z0-9\-._~])/sprintf("%%%02X", ord($1))/ge;
|
||||
return $str;
|
||||
}
|
||||
|
||||
1;
|
||||
@@ -1,5 +1,5 @@
|
||||
#!/usr/local/cpanel/3rdparty/bin/perl
|
||||
# gniza cPanel Plugin — Restore Workflow
|
||||
# gniza4cp cPanel Plugin — Restore Workflow
|
||||
# Multi-step restore with dynamic dropdowns via AdminBin
|
||||
use strict;
|
||||
use warnings;
|
||||
@@ -17,7 +17,7 @@ BEGIN {
|
||||
use Cpanel::LiveAPI ();
|
||||
use Cpanel::AdminBin::Call ();
|
||||
use Cpanel::Form ();
|
||||
use GnizaCPanel::UI;
|
||||
use Gniza4cpCPanel::UI;
|
||||
|
||||
my $cpanel = Cpanel::LiveAPI->new();
|
||||
END { $cpanel->end() if $cpanel }
|
||||
@@ -66,7 +66,7 @@ sub _json_escape {
|
||||
|
||||
sub _adminbin_call {
|
||||
my ($action, @args) = @_;
|
||||
my $result = eval { Cpanel::AdminBin::Call::call('Gniza', 'Restore', $action, @args) };
|
||||
my $result = eval { Cpanel::AdminBin::Call::call('Gniza4cp', 'Restore', $action, @args) };
|
||||
if ($@) {
|
||||
return (0, '', "AdminBin call failed: $@");
|
||||
}
|
||||
@@ -176,7 +176,7 @@ sub handle_step2 {
|
||||
my $timestamp = $form->{'timestamp'} // '';
|
||||
|
||||
if ($remote eq '' || $timestamp eq '') {
|
||||
GnizaCPanel::UI::set_flash('error', 'Remote and snapshot are required.');
|
||||
Gniza4cpCPanel::UI::set_flash('error', 'Remote and snapshot are required.');
|
||||
print "Status: 302 Found\r\n";
|
||||
print "Location: index.live.cgi\r\n\r\n";
|
||||
exit;
|
||||
@@ -195,12 +195,13 @@ sub handle_step2 {
|
||||
}
|
||||
|
||||
print "Content-Type: text/html\r\n\r\n";
|
||||
print $cpanel->header('GNIZA Backups');
|
||||
print GnizaCPanel::UI::page_header('Restore Options');
|
||||
print GnizaCPanel::UI::render_flash();
|
||||
print $cpanel->header('');
|
||||
print Gniza4cpCPanel::UI::page_header('Restore Options');
|
||||
print Gniza4cpCPanel::UI::render_nav('restore.live.cgi');
|
||||
print Gniza4cpCPanel::UI::render_flash();
|
||||
|
||||
my $esc_remote = GnizaCPanel::UI::esc($remote);
|
||||
my $esc_timestamp = GnizaCPanel::UI::esc($timestamp);
|
||||
my $esc_remote = Gniza4cpCPanel::UI::esc($remote);
|
||||
my $esc_timestamp = Gniza4cpCPanel::UI::esc($timestamp);
|
||||
|
||||
print qq{<form method="GET" action="restore.live.cgi">\n};
|
||||
print qq{<input type="hidden" name="step" value="3">\n};
|
||||
@@ -214,9 +215,9 @@ sub handle_step2 {
|
||||
print qq{<div class="flex items-center gap-3 mb-2.5">\n};
|
||||
print qq{ <label class="w-36 font-medium text-sm" for="timestamp">Snapshot</label>\n};
|
||||
if (@snapshots) {
|
||||
print qq{ <select class="select select-bordered select-sm w-full max-w-xs" id="timestamp" name="timestamp" required onchange="gnizaSnapshotChanged()">\n};
|
||||
print qq{ <select class="select select-bordered select-sm w-full max-w-xs" id="timestamp" name="timestamp" required onchange="gniza4cpSnapshotChanged()">\n};
|
||||
for my $snap (sort { $b cmp $a } @snapshots) {
|
||||
my $esc = GnizaCPanel::UI::esc($snap);
|
||||
my $esc = Gniza4cpCPanel::UI::esc($snap);
|
||||
my $sel = ($snap eq $timestamp) ? ' selected' : '';
|
||||
print qq{ <option value="$esc"$sel>$esc</option>\n};
|
||||
}
|
||||
@@ -230,8 +231,8 @@ sub handle_step2 {
|
||||
print qq{<div class="flex items-center gap-3 mb-2.5">\n};
|
||||
print qq{ <label class="w-36 font-medium text-sm whitespace-nowrap">Restore Mode</label>\n};
|
||||
print qq{ <div class="join inline-flex items-stretch">\n};
|
||||
print qq{ <input type="radio" name="restore_mode" class="join-item btn btn-sm m-0" aria-label="Full Account" value="full" checked onchange="gnizaModeChanged()">\n};
|
||||
print qq{ <input type="radio" name="restore_mode" class="join-item btn btn-sm m-0" aria-label="Selective" value="selective" onchange="gnizaModeChanged()">\n};
|
||||
print qq{ <input type="radio" name="restore_mode" class="join-item btn btn-sm m-0" aria-label="Full Account" value="full" checked onchange="gniza4cpModeChanged()">\n};
|
||||
print qq{ <input type="radio" name="restore_mode" class="join-item btn btn-sm m-0" aria-label="Selective" value="selective" onchange="gniza4cpModeChanged()">\n};
|
||||
print qq{ </div>\n};
|
||||
print qq{</div>\n};
|
||||
|
||||
@@ -241,8 +242,8 @@ sub handle_step2 {
|
||||
print qq{ <h3 class="card-title text-sm">Directories and Files to Exclude</h3>\n};
|
||||
print qq{ <div class="flex items-center gap-2">\n};
|
||||
print qq{ <input type="text" class="input input-bordered input-sm flex-1 max-w-xs" id="exclude-input" placeholder="e.g. public_html/cache">\n};
|
||||
print qq{ <button type="button" class="btn btn-warning btn-sm" onclick="gnizaAddExclude()">Add Path</button>\n};
|
||||
print qq{ <button type="button" class="btn btn-warning btn-sm" onclick="gnizaOpenExcludeModal()">Insert Multiple</button>\n};
|
||||
print qq{ <button type="button" class="btn btn-warning btn-sm" onclick="gniza4cpAddExclude()">Add Path</button>\n};
|
||||
print qq{ <button type="button" class="btn btn-warning btn-sm" onclick="gniza4cpOpenExcludeModal()">Insert Multiple</button>\n};
|
||||
print qq{ </div>\n};
|
||||
print qq{ <p class="text-xs text-base-content/60">Exclude files and directories from restoration</p>\n};
|
||||
print qq{ <div id="exclude-tags" class="flex flex-wrap gap-1 mt-1"></div>\n};
|
||||
@@ -258,7 +259,7 @@ sub handle_step2 {
|
||||
print qq{ <p class="text-xs text-base-content/60 mt-1">* Separated by new line</p>\n};
|
||||
print qq{ <div class="modal-action">\n};
|
||||
print qq{ <button type="button" class="btn btn-sm" onclick="document.getElementById('exclude-modal').close()">Cancel</button>\n};
|
||||
print qq{ <button type="button" class="btn btn-warning btn-sm" onclick="gnizaExcludeModalOk()">OK</button>\n};
|
||||
print qq{ <button type="button" class="btn btn-warning btn-sm" onclick="gniza4cpExcludeModalOk()">OK</button>\n};
|
||||
print qq{ </div>\n};
|
||||
print qq{</div>\n};
|
||||
print qq{<div class="modal-backdrop" onclick="this.closest('dialog').close()"><button type="button">close</button></div>\n};
|
||||
@@ -284,7 +285,7 @@ sub handle_step2 {
|
||||
print qq{ <label class="w-36 font-medium text-sm">Restore Types</label>\n};
|
||||
print qq{ <div class="flex flex-wrap gap-1">\n};
|
||||
for my $t (@selective_types) {
|
||||
print qq{ <input type="checkbox" class="btn btn-sm" aria-label="$t->[1]" name="type_$t->[0]" value="1" onchange="gnizaTypesChanged()">\n};
|
||||
print qq{ <input type="checkbox" class="btn btn-sm" aria-label="$t->[1]" name="type_$t->[0]" value="1" onchange="gniza4cpTypesChanged()">\n};
|
||||
}
|
||||
print qq{ </div>\n};
|
||||
print qq{</div>\n};
|
||||
@@ -298,7 +299,7 @@ sub handle_step2 {
|
||||
print qq{ <label class="font-medium text-sm" for="path">Path</label>\n};
|
||||
print qq{ <div class="flex items-center gap-2 w-full max-w-xs">\n};
|
||||
print qq{ <input type="text" class="input input-bordered input-sm flex-1" id="path" name="path" placeholder="e.g. public_html/index.html">\n};
|
||||
print qq{ <button type="button" class="btn btn-secondary btn-sm" onclick="gnizaOpenFileBrowser()">Browse</button>\n};
|
||||
print qq{ <button type="button" class="btn btn-secondary btn-sm" onclick="gniza4cpOpenFileBrowser()">Browse</button>\n};
|
||||
print qq{ </div>\n};
|
||||
print qq{ </div>\n};
|
||||
print qq{ <p class="text-xs text-base-content/60">Leave empty to restore all files.</p>\n};
|
||||
@@ -390,7 +391,7 @@ sub handle_step2 {
|
||||
print qq{ <table class="table table-zebra w-full"><tbody id="fb-tbody"></tbody></table>\n};
|
||||
print qq{ </div>\n};
|
||||
print qq{ <div class="modal-action">\n};
|
||||
print qq{ <button type="button" id="fb-select-btn" class="btn btn-primary btn-sm" disabled onclick="gnizaSelectPath()">Select</button>\n};
|
||||
print qq{ <button type="button" id="fb-select-btn" class="btn btn-primary btn-sm" disabled onclick="gniza4cpSelectPath()">Select</button>\n};
|
||||
print qq{ <button type="button" class="btn btn-info btn-sm" onclick="document.getElementById('fb-modal').close()">Cancel</button>\n};
|
||||
print qq{ </div>\n};
|
||||
print qq{</div>\n};
|
||||
@@ -413,7 +414,7 @@ sub handle_step2 {
|
||||
# JavaScript for dynamic dropdowns and interactive elements
|
||||
_print_step2_js($esc_remote);
|
||||
|
||||
print GnizaCPanel::UI::page_footer();
|
||||
print Gniza4cpCPanel::UI::page_footer();
|
||||
print $cpanel->footer();
|
||||
}
|
||||
|
||||
@@ -421,24 +422,24 @@ sub _print_step2_js {
|
||||
my ($esc_remote) = @_;
|
||||
print <<"END_JS";
|
||||
<script>
|
||||
var gnizaCache = {};
|
||||
var gnizaRemote = '$esc_remote';
|
||||
var gniza4cpCache = {};
|
||||
var gniza4cpRemote = '$esc_remote';
|
||||
var fbCache = {};
|
||||
var fbSelected = '';
|
||||
|
||||
function gnizaSnapshotChanged() {
|
||||
gnizaCache = {};
|
||||
function gniza4cpSnapshotChanged() {
|
||||
gniza4cpCache = {};
|
||||
fbCache = {};
|
||||
gnizaModeChanged();
|
||||
gniza4cpModeChanged();
|
||||
}
|
||||
|
||||
function gnizaModeChanged() {
|
||||
function gniza4cpModeChanged() {
|
||||
var mode = document.querySelector('input[name="restore_mode"]:checked').value;
|
||||
var selective = mode === 'selective';
|
||||
document.getElementById('selective-panel').hidden = !selective;
|
||||
document.getElementById('type_account_hidden').disabled = selective;
|
||||
if (selective) {
|
||||
gnizaTypesChanged();
|
||||
gniza4cpTypesChanged();
|
||||
} else {
|
||||
var panels = ['field-path','field-dbname','field-email','field-dbusers','field-cron','field-domains','field-ssl'];
|
||||
for (var i = 0; i < panels.length; i++) {
|
||||
@@ -447,7 +448,7 @@ function gnizaModeChanged() {
|
||||
}
|
||||
}
|
||||
|
||||
function gnizaTypesChanged() {
|
||||
function gniza4cpTypesChanged() {
|
||||
var types = {
|
||||
files: 'field-path',
|
||||
database: 'field-dbname',
|
||||
@@ -462,20 +463,20 @@ function gnizaTypesChanged() {
|
||||
document.getElementById(types[t]).hidden = !(el && el.checked);
|
||||
}
|
||||
|
||||
if (document.querySelector('input[name="type_database"]').checked) { gnizaLoadOptions('database', 'dbname-list', 'dbnames'); }
|
||||
if (document.querySelector('input[name="type_mailbox"]').checked) { gnizaLoadOptions('mailbox', 'email-list', 'emails'); }
|
||||
if (document.querySelector('input[name="type_dbusers"]').checked) { gnizaLoadOptions('dbusers', 'dbusers-list', 'dbuser_names'); }
|
||||
if (document.querySelector('input[name="type_cron"]').checked) { gnizaLoadPreview('cron', 'cron-list'); }
|
||||
if (document.querySelector('input[name="type_domains"]').checked) { gnizaLoadOptions('domains', 'domains-list', 'domain_names'); }
|
||||
if (document.querySelector('input[name="type_ssl"]').checked) { gnizaLoadOptions('ssl', 'ssl-list', 'ssl_names'); }
|
||||
if (document.querySelector('input[name="type_database"]').checked) { gniza4cpLoadOptions('database', 'dbname-list', 'dbnames'); }
|
||||
if (document.querySelector('input[name="type_mailbox"]').checked) { gniza4cpLoadOptions('mailbox', 'email-list', 'emails'); }
|
||||
if (document.querySelector('input[name="type_dbusers"]').checked) { gniza4cpLoadOptions('dbusers', 'dbusers-list', 'dbuser_names'); }
|
||||
if (document.querySelector('input[name="type_cron"]').checked) { gniza4cpLoadPreview('cron', 'cron-list'); }
|
||||
if (document.querySelector('input[name="type_domains"]').checked) { gniza4cpLoadOptions('domains', 'domains-list', 'domain_names'); }
|
||||
if (document.querySelector('input[name="type_ssl"]').checked) { gniza4cpLoadOptions('ssl', 'ssl-list', 'ssl_names'); }
|
||||
}
|
||||
|
||||
function gnizaLoadOptions(type, containerId, hiddenId) {
|
||||
function gniza4cpLoadOptions(type, containerId, hiddenId) {
|
||||
var ts = document.getElementById('timestamp').value;
|
||||
var cacheKey = type + ':' + ts;
|
||||
|
||||
if (gnizaCache[cacheKey]) {
|
||||
gnizaPopulateChecklist(containerId, hiddenId, gnizaCache[cacheKey]);
|
||||
if (gniza4cpCache[cacheKey]) {
|
||||
gniza4cpPopulateChecklist(containerId, hiddenId, gniza4cpCache[cacheKey]);
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -491,7 +492,7 @@ function gnizaLoadOptions(type, containerId, hiddenId) {
|
||||
document.getElementById(hiddenId).value = '';
|
||||
|
||||
var url = 'restore.live.cgi?step=fetch_options'
|
||||
+ '&remote=' + encodeURIComponent(gnizaRemote)
|
||||
+ '&remote=' + encodeURIComponent(gniza4cpRemote)
|
||||
+ '×tamp=' + encodeURIComponent(ts)
|
||||
+ '&type=' + encodeURIComponent(type);
|
||||
|
||||
@@ -505,8 +506,8 @@ function gnizaLoadOptions(type, containerId, hiddenId) {
|
||||
if (data.error) {
|
||||
container.textContent = 'Error: ' + data.error;
|
||||
} else {
|
||||
gnizaCache[cacheKey] = data.options;
|
||||
gnizaPopulateChecklist(containerId, hiddenId, data.options);
|
||||
gniza4cpCache[cacheKey] = data.options;
|
||||
gniza4cpPopulateChecklist(containerId, hiddenId, data.options);
|
||||
}
|
||||
} catch(e) {
|
||||
container.textContent = 'Failed to parse response';
|
||||
@@ -518,7 +519,7 @@ function gnizaLoadOptions(type, containerId, hiddenId) {
|
||||
xhr.send();
|
||||
}
|
||||
|
||||
function gnizaPopulateChecklist(containerId, hiddenId, options) {
|
||||
function gniza4cpPopulateChecklist(containerId, hiddenId, options) {
|
||||
var container = document.getElementById(containerId);
|
||||
var hidden = document.getElementById(hiddenId);
|
||||
hidden.value = '';
|
||||
@@ -539,7 +540,7 @@ function gnizaPopulateChecklist(containerId, hiddenId, options) {
|
||||
allCb.type = 'checkbox';
|
||||
allCb.className = 'checkbox checkbox-sm';
|
||||
allCb.setAttribute('data-all', '1');
|
||||
allCb.onchange = function() { gnizaToggleAll(containerId, hiddenId, this.checked); };
|
||||
allCb.onchange = function() { gniza4cpToggleAll(containerId, hiddenId, this.checked); };
|
||||
allRow.appendChild(allCb);
|
||||
var allSpan = document.createElement('span');
|
||||
allSpan.className = 'text-sm font-semibold';
|
||||
@@ -556,7 +557,7 @@ function gnizaPopulateChecklist(containerId, hiddenId, options) {
|
||||
cb.className = 'checkbox checkbox-sm';
|
||||
cb.value = options[i];
|
||||
cb.setAttribute('data-item', '1');
|
||||
cb.onchange = (function(cId, hId) { return function() { gnizaSyncHidden(cId, hId); }; })(containerId, hiddenId);
|
||||
cb.onchange = (function(cId, hId) { return function() { gniza4cpSyncHidden(cId, hId); }; })(containerId, hiddenId);
|
||||
row.appendChild(cb);
|
||||
var span = document.createElement('span');
|
||||
span.className = 'text-sm';
|
||||
@@ -566,7 +567,7 @@ function gnizaPopulateChecklist(containerId, hiddenId, options) {
|
||||
}
|
||||
}
|
||||
|
||||
function gnizaToggleAll(containerId, hiddenId, checked) {
|
||||
function gniza4cpToggleAll(containerId, hiddenId, checked) {
|
||||
var container = document.getElementById(containerId);
|
||||
var hidden = document.getElementById(hiddenId);
|
||||
var items = container.querySelectorAll('input[data-item]');
|
||||
@@ -577,7 +578,7 @@ function gnizaToggleAll(containerId, hiddenId, checked) {
|
||||
hidden.value = checked ? '__ALL__' : '';
|
||||
}
|
||||
|
||||
function gnizaSyncHidden(containerId, hiddenId) {
|
||||
function gniza4cpSyncHidden(containerId, hiddenId) {
|
||||
var container = document.getElementById(containerId);
|
||||
var hidden = document.getElementById(hiddenId);
|
||||
var items = container.querySelectorAll('input[data-item]:checked');
|
||||
@@ -588,12 +589,12 @@ function gnizaSyncHidden(containerId, hiddenId) {
|
||||
hidden.value = vals.join(',');
|
||||
}
|
||||
|
||||
function gnizaLoadPreview(type, containerId) {
|
||||
function gniza4cpLoadPreview(type, containerId) {
|
||||
var ts = document.getElementById('timestamp').value;
|
||||
var cacheKey = type + ':' + ts;
|
||||
|
||||
if (gnizaCache[cacheKey]) {
|
||||
gnizaPopulatePreview(containerId, gnizaCache[cacheKey], type);
|
||||
if (gniza4cpCache[cacheKey]) {
|
||||
gniza4cpPopulatePreview(containerId, gniza4cpCache[cacheKey], type);
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -608,7 +609,7 @@ function gnizaLoadPreview(type, containerId) {
|
||||
container.appendChild(loadSpan);
|
||||
|
||||
var url = 'restore.live.cgi?step=fetch_options'
|
||||
+ '&remote=' + encodeURIComponent(gnizaRemote)
|
||||
+ '&remote=' + encodeURIComponent(gniza4cpRemote)
|
||||
+ '×tamp=' + encodeURIComponent(ts)
|
||||
+ '&type=' + encodeURIComponent(type);
|
||||
|
||||
@@ -622,8 +623,8 @@ function gnizaLoadPreview(type, containerId) {
|
||||
if (data.error) {
|
||||
container.textContent = 'Error: ' + data.error;
|
||||
} else {
|
||||
gnizaCache[cacheKey] = data.options;
|
||||
gnizaPopulatePreview(containerId, data.options, type);
|
||||
gniza4cpCache[cacheKey] = data.options;
|
||||
gniza4cpPopulatePreview(containerId, data.options, type);
|
||||
}
|
||||
} catch(e) {
|
||||
container.textContent = 'Failed to parse response';
|
||||
@@ -635,7 +636,7 @@ function gnizaLoadPreview(type, containerId) {
|
||||
xhr.send();
|
||||
}
|
||||
|
||||
function gnizaPopulatePreview(containerId, options, type) {
|
||||
function gniza4cpPopulatePreview(containerId, options, type) {
|
||||
var container = document.getElementById(containerId);
|
||||
container.textContent = '';
|
||||
if (!options || options.length === 0) {
|
||||
@@ -659,16 +660,16 @@ function gnizaPopulatePreview(containerId, options, type) {
|
||||
}
|
||||
}
|
||||
|
||||
function gnizaAddExclude() {
|
||||
function gniza4cpAddExclude() {
|
||||
var input = document.getElementById('exclude-input');
|
||||
var val = input.value.trim();
|
||||
if (!val) return;
|
||||
gnizaAddExcludeTag(val);
|
||||
gniza4cpAddExcludeTag(val);
|
||||
input.value = '';
|
||||
gnizaUpdateExcludeField();
|
||||
gniza4cpUpdateExcludeField();
|
||||
}
|
||||
|
||||
function gnizaAddExcludeTag(text) {
|
||||
function gniza4cpAddExcludeTag(text) {
|
||||
var container = document.getElementById('exclude-tags');
|
||||
var existing = container.querySelectorAll('.badge span');
|
||||
for (var i = 0; i < existing.length; i++) {
|
||||
@@ -683,12 +684,12 @@ function gnizaAddExcludeTag(text) {
|
||||
btn.type = 'button';
|
||||
btn.className = 'btn btn-xs btn-ghost btn-circle';
|
||||
btn.textContent = '\\u2715';
|
||||
btn.onclick = function() { badge.remove(); gnizaUpdateExcludeField(); };
|
||||
btn.onclick = function() { badge.remove(); gniza4cpUpdateExcludeField(); };
|
||||
badge.appendChild(btn);
|
||||
container.appendChild(badge);
|
||||
}
|
||||
|
||||
function gnizaUpdateExcludeField() {
|
||||
function gniza4cpUpdateExcludeField() {
|
||||
var tags = document.getElementById('exclude-tags').querySelectorAll('.badge span');
|
||||
var vals = [];
|
||||
for (var i = 0; i < tags.length; i++) {
|
||||
@@ -697,35 +698,35 @@ function gnizaUpdateExcludeField() {
|
||||
document.getElementById('exclude_paths').value = vals.join(',');
|
||||
}
|
||||
|
||||
function gnizaOpenExcludeModal() {
|
||||
function gniza4cpOpenExcludeModal() {
|
||||
document.getElementById('exclude-textarea').value = '';
|
||||
document.getElementById('exclude-modal').showModal();
|
||||
}
|
||||
|
||||
function gnizaExcludeModalOk() {
|
||||
function gniza4cpExcludeModalOk() {
|
||||
var text = document.getElementById('exclude-textarea').value;
|
||||
var lines = text.split('\\n');
|
||||
for (var i = 0; i < lines.length; i++) {
|
||||
var line = lines[i].trim();
|
||||
if (line) gnizaAddExcludeTag(line);
|
||||
if (line) gniza4cpAddExcludeTag(line);
|
||||
}
|
||||
gnizaUpdateExcludeField();
|
||||
gniza4cpUpdateExcludeField();
|
||||
document.getElementById('exclude-modal').close();
|
||||
}
|
||||
|
||||
function gnizaOpenFileBrowser() {
|
||||
function gniza4cpOpenFileBrowser() {
|
||||
fbSelected = '';
|
||||
document.getElementById('fb-select-btn').disabled = true;
|
||||
document.getElementById('fb-modal').showModal();
|
||||
gnizaLoadDir('');
|
||||
gniza4cpLoadDir('');
|
||||
}
|
||||
|
||||
function gnizaLoadDir(path) {
|
||||
function gniza4cpLoadDir(path) {
|
||||
var ts = document.getElementById('timestamp').value;
|
||||
var cacheKey = 'fb:' + ts + ':' + path;
|
||||
|
||||
if (fbCache[cacheKey]) {
|
||||
gnizaRenderFileList(path, fbCache[cacheKey]);
|
||||
gniza4cpRenderFileList(path, fbCache[cacheKey]);
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -734,7 +735,7 @@ function gnizaLoadDir(path) {
|
||||
document.getElementById('fb-tbody').textContent = '';
|
||||
|
||||
var url = 'restore.live.cgi?step=fetch_options'
|
||||
+ '&remote=' + encodeURIComponent(gnizaRemote)
|
||||
+ '&remote=' + encodeURIComponent(gniza4cpRemote)
|
||||
+ '×tamp=' + encodeURIComponent(ts)
|
||||
+ '&type=files'
|
||||
+ (path ? '&path=' + encodeURIComponent(path) : '');
|
||||
@@ -752,7 +753,7 @@ function gnizaLoadDir(path) {
|
||||
document.getElementById('fb-error').hidden = false;
|
||||
} else {
|
||||
fbCache[cacheKey] = data.options;
|
||||
gnizaRenderFileList(path, data.options);
|
||||
gniza4cpRenderFileList(path, data.options);
|
||||
}
|
||||
} catch(e) {
|
||||
document.getElementById('fb-error').textContent = 'Failed to parse response';
|
||||
@@ -766,13 +767,13 @@ function gnizaLoadDir(path) {
|
||||
xhr.send();
|
||||
}
|
||||
|
||||
function gnizaRenderBreadcrumbs(path) {
|
||||
function gniza4cpRenderBreadcrumbs(path) {
|
||||
var ul = document.createElement('ul');
|
||||
var li = document.createElement('li');
|
||||
var a = document.createElement('a');
|
||||
a.textContent = 'homedir';
|
||||
a.href = '#';
|
||||
a.onclick = function(e) { e.preventDefault(); gnizaLoadDir(''); };
|
||||
a.onclick = function(e) { e.preventDefault(); gniza4cpLoadDir(''); };
|
||||
li.appendChild(a);
|
||||
ul.appendChild(li);
|
||||
|
||||
@@ -786,7 +787,7 @@ function gnizaRenderBreadcrumbs(path) {
|
||||
a = document.createElement('a');
|
||||
a.textContent = parts[i];
|
||||
a.href = '#';
|
||||
(function(p) { a.onclick = function(e) { e.preventDefault(); gnizaLoadDir(p); }; })(built);
|
||||
(function(p) { a.onclick = function(e) { e.preventDefault(); gniza4cpLoadDir(p); }; })(built);
|
||||
li.appendChild(a);
|
||||
} else {
|
||||
li.textContent = parts[i];
|
||||
@@ -800,8 +801,8 @@ function gnizaRenderBreadcrumbs(path) {
|
||||
bc.appendChild(ul);
|
||||
}
|
||||
|
||||
function gnizaRenderFileList(currentPath, entries) {
|
||||
gnizaRenderBreadcrumbs(currentPath);
|
||||
function gniza4cpRenderFileList(currentPath, entries) {
|
||||
gniza4cpRenderBreadcrumbs(currentPath);
|
||||
fbSelected = '';
|
||||
document.getElementById('fb-select-btn').disabled = true;
|
||||
|
||||
@@ -834,9 +835,9 @@ function gnizaRenderFileList(currentPath, entries) {
|
||||
tr.appendChild(td);
|
||||
|
||||
(function(row, path, dir) {
|
||||
row.onclick = function() { gnizaHighlight(row, path); };
|
||||
row.onclick = function() { gniza4cpHighlight(row, path); };
|
||||
if (dir) {
|
||||
row.ondblclick = function() { gnizaLoadDir(path.replace(/\\/\$/, '')); };
|
||||
row.ondblclick = function() { gniza4cpLoadDir(path.replace(/\\/\$/, '')); };
|
||||
}
|
||||
})(tr, fullPath, isDir);
|
||||
|
||||
@@ -844,7 +845,7 @@ function gnizaRenderFileList(currentPath, entries) {
|
||||
}
|
||||
}
|
||||
|
||||
function gnizaHighlight(row, path) {
|
||||
function gniza4cpHighlight(row, path) {
|
||||
var rows = document.getElementById('fb-tbody').querySelectorAll('tr');
|
||||
for (var i = 0; i < rows.length; i++) {
|
||||
rows[i].classList.remove('bg-primary/10');
|
||||
@@ -854,14 +855,14 @@ function gnizaHighlight(row, path) {
|
||||
document.getElementById('fb-select-btn').disabled = false;
|
||||
}
|
||||
|
||||
function gnizaSelectPath() {
|
||||
function gniza4cpSelectPath() {
|
||||
if (fbSelected) {
|
||||
document.getElementById('path').value = fbSelected;
|
||||
}
|
||||
document.getElementById('fb-modal').close();
|
||||
}
|
||||
|
||||
gnizaModeChanged();
|
||||
gniza4cpModeChanged();
|
||||
</script>
|
||||
END_JS
|
||||
}
|
||||
@@ -887,29 +888,30 @@ sub handle_step3 {
|
||||
}
|
||||
|
||||
if ($remote eq '' || $timestamp eq '') {
|
||||
GnizaCPanel::UI::set_flash('error', 'Remote and snapshot are required.');
|
||||
Gniza4cpCPanel::UI::set_flash('error', 'Remote and snapshot are required.');
|
||||
print "Status: 302 Found\r\n";
|
||||
print "Location: index.live.cgi\r\n\r\n";
|
||||
exit;
|
||||
}
|
||||
|
||||
unless (@selected_types) {
|
||||
GnizaCPanel::UI::set_flash('error', 'Please select at least one restore type.');
|
||||
Gniza4cpCPanel::UI::set_flash('error', 'Please select at least one restore type.');
|
||||
print "Status: 302 Found\r\n";
|
||||
print "Location: restore.live.cgi?step=2&remote=" . _uri_escape($remote) . "×tamp=" . _uri_escape($timestamp) . "\r\n\r\n";
|
||||
exit;
|
||||
}
|
||||
|
||||
print "Content-Type: text/html\r\n\r\n";
|
||||
print $cpanel->header('GNIZA Backups');
|
||||
print GnizaCPanel::UI::page_header('Restore: Confirm');
|
||||
print GnizaCPanel::UI::render_flash();
|
||||
print $cpanel->header('');
|
||||
print Gniza4cpCPanel::UI::page_header('Restore: Confirm');
|
||||
print Gniza4cpCPanel::UI::render_nav('restore.live.cgi');
|
||||
print Gniza4cpCPanel::UI::render_flash();
|
||||
|
||||
my $esc_remote = GnizaCPanel::UI::esc($remote);
|
||||
my $esc_timestamp = GnizaCPanel::UI::esc($timestamp);
|
||||
my $user = GnizaCPanel::UI::esc(GnizaCPanel::UI::get_current_user());
|
||||
my $esc_remote = Gniza4cpCPanel::UI::esc($remote);
|
||||
my $esc_timestamp = Gniza4cpCPanel::UI::esc($timestamp);
|
||||
my $user = Gniza4cpCPanel::UI::esc(Gniza4cpCPanel::UI::get_current_user());
|
||||
|
||||
my $types_display = join(', ', map { GnizaCPanel::UI::esc($TYPE_LABELS{$_} // $_) } @selected_types);
|
||||
my $types_display = join(', ', map { Gniza4cpCPanel::UI::esc($TYPE_LABELS{$_} // $_) } @selected_types);
|
||||
|
||||
print qq{<div class="card bg-white shadow-sm border border-base-300 mb-6">\n<div class="card-body">\n};
|
||||
print qq{<h2 class="card-title text-sm">Step 3: Confirm Restore</h2>\n};
|
||||
@@ -921,37 +923,37 @@ sub handle_step3 {
|
||||
|
||||
# Show sub-field details for applicable types
|
||||
if (grep { $_ eq 'files' } @selected_types) {
|
||||
my $path_display = $path ne '' ? GnizaCPanel::UI::esc($path) : 'All files';
|
||||
my $path_display = $path ne '' ? Gniza4cpCPanel::UI::esc($path) : 'All files';
|
||||
print qq{<tr><td class="font-medium">Path</td><td>$path_display</td></tr>\n};
|
||||
}
|
||||
if (grep { $_ eq 'database' } @selected_types) {
|
||||
my $db_display = ($dbnames eq '' || $dbnames eq '__ALL__') ? 'All databases' : GnizaCPanel::UI::esc($dbnames);
|
||||
my $db_display = ($dbnames eq '' || $dbnames eq '__ALL__') ? 'All databases' : Gniza4cpCPanel::UI::esc($dbnames);
|
||||
$db_display =~ s/,/, /g;
|
||||
print qq{<tr><td class="font-medium">Database</td><td>$db_display</td></tr>\n};
|
||||
}
|
||||
if (grep { $_ eq 'dbusers' } @selected_types) {
|
||||
my $dbu_display = ($dbuser_names eq '' || $dbuser_names eq '__ALL__') ? 'All database users' : GnizaCPanel::UI::esc($dbuser_names);
|
||||
my $dbu_display = ($dbuser_names eq '' || $dbuser_names eq '__ALL__') ? 'All database users' : Gniza4cpCPanel::UI::esc($dbuser_names);
|
||||
$dbu_display =~ s/,/, /g;
|
||||
print qq{<tr><td class="font-medium">Database Users</td><td>$dbu_display</td></tr>\n};
|
||||
}
|
||||
if (grep { $_ eq 'mailbox' } @selected_types) {
|
||||
my $mb_display = ($emails eq '' || $emails eq '__ALL__') ? 'All mailboxes' : GnizaCPanel::UI::esc($emails);
|
||||
my $mb_display = ($emails eq '' || $emails eq '__ALL__') ? 'All mailboxes' : Gniza4cpCPanel::UI::esc($emails);
|
||||
$mb_display =~ s/,/, /g;
|
||||
print qq{<tr><td class="font-medium">Mailbox</td><td>$mb_display</td></tr>\n};
|
||||
}
|
||||
if (grep { $_ eq 'domains' } @selected_types) {
|
||||
my $dom_display = ($domain_names eq '' || $domain_names eq '__ALL__') ? 'All domains' : GnizaCPanel::UI::esc($domain_names);
|
||||
my $dom_display = ($domain_names eq '' || $domain_names eq '__ALL__') ? 'All domains' : Gniza4cpCPanel::UI::esc($domain_names);
|
||||
$dom_display =~ s/,/, /g;
|
||||
print qq{<tr><td class="font-medium">Domains</td><td>$dom_display</td></tr>\n};
|
||||
}
|
||||
if (grep { $_ eq 'ssl' } @selected_types) {
|
||||
my $ssl_display = ($ssl_names eq '' || $ssl_names eq '__ALL__') ? 'All certificates' : GnizaCPanel::UI::esc($ssl_names);
|
||||
my $ssl_display = ($ssl_names eq '' || $ssl_names eq '__ALL__') ? 'All certificates' : Gniza4cpCPanel::UI::esc($ssl_names);
|
||||
$ssl_display =~ s/,/, /g;
|
||||
print qq{<tr><td class="font-medium">SSL</td><td>$ssl_display</td></tr>\n};
|
||||
}
|
||||
|
||||
if ($exclude_paths ne '') {
|
||||
my $exclude_display = GnizaCPanel::UI::esc($exclude_paths);
|
||||
my $exclude_display = Gniza4cpCPanel::UI::esc($exclude_paths);
|
||||
$exclude_display =~ s/,/, /g;
|
||||
print qq{<tr><td class="font-medium">Exclude</td><td>$exclude_display</td></tr>\n};
|
||||
}
|
||||
@@ -966,14 +968,14 @@ sub handle_step3 {
|
||||
for my $t (@selected_types) {
|
||||
print qq{<input type="hidden" name="type_$t" value="1">\n};
|
||||
}
|
||||
print qq{<input type="hidden" name="path" value="} . GnizaCPanel::UI::esc($path) . qq{">\n};
|
||||
print qq{<input type="hidden" name="dbnames" value="} . GnizaCPanel::UI::esc($dbnames) . qq{">\n};
|
||||
print qq{<input type="hidden" name="dbuser_names" value="} . GnizaCPanel::UI::esc($dbuser_names) . qq{">\n};
|
||||
print qq{<input type="hidden" name="emails" value="} . GnizaCPanel::UI::esc($emails) . qq{">\n};
|
||||
print qq{<input type="hidden" name="domain_names" value="} . GnizaCPanel::UI::esc($domain_names) . qq{">\n};
|
||||
print qq{<input type="hidden" name="ssl_names" value="} . GnizaCPanel::UI::esc($ssl_names) . qq{">\n};
|
||||
print qq{<input type="hidden" name="exclude_paths" value="} . GnizaCPanel::UI::esc($exclude_paths) . qq{">\n};
|
||||
print GnizaCPanel::UI::csrf_hidden_field();
|
||||
print qq{<input type="hidden" name="path" value="} . Gniza4cpCPanel::UI::esc($path) . qq{">\n};
|
||||
print qq{<input type="hidden" name="dbnames" value="} . Gniza4cpCPanel::UI::esc($dbnames) . qq{">\n};
|
||||
print qq{<input type="hidden" name="dbuser_names" value="} . Gniza4cpCPanel::UI::esc($dbuser_names) . qq{">\n};
|
||||
print qq{<input type="hidden" name="emails" value="} . Gniza4cpCPanel::UI::esc($emails) . qq{">\n};
|
||||
print qq{<input type="hidden" name="domain_names" value="} . Gniza4cpCPanel::UI::esc($domain_names) . qq{">\n};
|
||||
print qq{<input type="hidden" name="ssl_names" value="} . Gniza4cpCPanel::UI::esc($ssl_names) . qq{">\n};
|
||||
print qq{<input type="hidden" name="exclude_paths" value="} . Gniza4cpCPanel::UI::esc($exclude_paths) . qq{">\n};
|
||||
print Gniza4cpCPanel::UI::csrf_hidden_field();
|
||||
|
||||
print qq{<div class="flex items-center gap-2">\n};
|
||||
print qq{ <button type="submit" class="btn btn-error btn-sm" onclick="return confirm('Are you sure? This may overwrite existing data.')">Execute Restore</button>\n};
|
||||
@@ -981,15 +983,15 @@ sub handle_step3 {
|
||||
print qq{</div>\n};
|
||||
print qq{</form>\n};
|
||||
|
||||
print GnizaCPanel::UI::page_footer();
|
||||
print Gniza4cpCPanel::UI::page_footer();
|
||||
print $cpanel->footer();
|
||||
}
|
||||
|
||||
# ── Step 4: Execute ───────────────────────────────────────────
|
||||
|
||||
sub handle_step4 {
|
||||
unless ($method eq 'POST' && GnizaCPanel::UI::verify_csrf_token($form->{'gniza_csrf'})) {
|
||||
GnizaCPanel::UI::set_flash('error', 'Invalid or expired form token.');
|
||||
unless ($method eq 'POST' && Gniza4cpCPanel::UI::verify_csrf_token($form->{'gniza4cp_csrf'})) {
|
||||
Gniza4cpCPanel::UI::set_flash('error', 'Invalid or expired form token.');
|
||||
print "Status: 302 Found\r\n";
|
||||
print "Location: index.live.cgi\r\n\r\n";
|
||||
exit;
|
||||
@@ -1013,126 +1015,52 @@ sub handle_step4 {
|
||||
}
|
||||
|
||||
unless (@selected_types) {
|
||||
GnizaCPanel::UI::set_flash('error', 'No restore types selected.');
|
||||
Gniza4cpCPanel::UI::set_flash('error', 'No restore types selected.');
|
||||
print "Status: 302 Found\r\n";
|
||||
print "Location: index.live.cgi\r\n\r\n";
|
||||
exit;
|
||||
}
|
||||
|
||||
print "Content-Type: text/html\r\n\r\n";
|
||||
print $cpanel->header('GNIZA Backups');
|
||||
print GnizaCPanel::UI::page_header('Restore Results');
|
||||
|
||||
print qq{<div class="card bg-white shadow-sm border border-base-300 mb-6">\n<div class="card-body">\n};
|
||||
print qq{<h2 class="card-title text-sm">Restore Results</h2>\n};
|
||||
|
||||
my @results;
|
||||
|
||||
# Build types_str encoding: type1;type2:item1,item2;type3
|
||||
my @type_parts;
|
||||
for my $type (@selected_types) {
|
||||
my $type_label = $TYPE_LABELS{$type} // $type;
|
||||
|
||||
if ($type eq 'account') {
|
||||
my ($ok, $stdout, $err) = _adminbin_call('RESTORE_ACCOUNT', $remote, $timestamp, $exclude_paths);
|
||||
push @results, { ok => $ok, label => $type_label, msg => $ok ? $stdout : $err };
|
||||
}
|
||||
elsif ($type eq 'files') {
|
||||
my ($ok, $stdout, $err) = _adminbin_call('RESTORE_FILES', $remote, $timestamp, $path, $exclude_paths);
|
||||
push @results, { ok => $ok, label => $type_label, msg => $ok ? $stdout : $err };
|
||||
}
|
||||
elsif ($type eq 'cron') {
|
||||
my ($ok, $stdout, $err) = _adminbin_call('RESTORE_CRON', $remote, $timestamp);
|
||||
push @results, { ok => $ok, label => $type_label, msg => $ok ? $stdout : $err };
|
||||
}
|
||||
elsif ($type eq 'database') {
|
||||
if ($dbnames eq '' || $dbnames eq '__ALL__') {
|
||||
my ($ok, $stdout, $err) = _adminbin_call('RESTORE_DATABASE', $remote, $timestamp, '');
|
||||
push @results, { ok => $ok, label => $type_label, msg => $ok ? $stdout : $err };
|
||||
} else {
|
||||
for my $item (split /,/, $dbnames) {
|
||||
next if $item eq '';
|
||||
my ($ok, $stdout, $err) = _adminbin_call('RESTORE_DATABASE', $remote, $timestamp, $item);
|
||||
push @results, { ok => $ok, label => $item, msg => $ok ? $stdout : $err };
|
||||
}
|
||||
}
|
||||
if ($type eq 'database') {
|
||||
my $items = ($dbnames eq '' || $dbnames eq '__ALL__') ? '' : $dbnames;
|
||||
push @type_parts, $items ne '' ? "database:$items" : 'database';
|
||||
}
|
||||
elsif ($type eq 'dbusers') {
|
||||
if ($dbuser_names eq '' || $dbuser_names eq '__ALL__') {
|
||||
my ($ok, $stdout, $err) = _adminbin_call('RESTORE_DBUSERS', $remote, $timestamp, '');
|
||||
push @results, { ok => $ok, label => $type_label, msg => $ok ? $stdout : $err };
|
||||
} else {
|
||||
for my $item (split /,/, $dbuser_names) {
|
||||
next if $item eq '';
|
||||
my ($ok, $stdout, $err) = _adminbin_call('RESTORE_DBUSERS', $remote, $timestamp, $item);
|
||||
push @results, { ok => $ok, label => $item, msg => $ok ? $stdout : $err };
|
||||
}
|
||||
}
|
||||
my $items = ($dbuser_names eq '' || $dbuser_names eq '__ALL__') ? '' : $dbuser_names;
|
||||
push @type_parts, $items ne '' ? "dbusers:$items" : 'dbusers';
|
||||
}
|
||||
elsif ($type eq 'mailbox') {
|
||||
if ($emails eq '' || $emails eq '__ALL__') {
|
||||
my ($ok, $stdout, $err) = _adminbin_call('RESTORE_MAILBOX', $remote, $timestamp, '');
|
||||
push @results, { ok => $ok, label => $type_label, msg => $ok ? $stdout : $err };
|
||||
} else {
|
||||
for my $item (split /,/, $emails) {
|
||||
next if $item eq '';
|
||||
my ($ok, $stdout, $err) = _adminbin_call('RESTORE_MAILBOX', $remote, $timestamp, $item);
|
||||
push @results, { ok => $ok, label => $item, msg => $ok ? $stdout : $err };
|
||||
}
|
||||
}
|
||||
my $items = ($emails eq '' || $emails eq '__ALL__') ? '' : $emails;
|
||||
push @type_parts, $items ne '' ? "mailbox:$items" : 'mailbox';
|
||||
}
|
||||
elsif ($type eq 'domains') {
|
||||
if ($domain_names eq '' || $domain_names eq '__ALL__') {
|
||||
my ($ok, $stdout, $err) = _adminbin_call('RESTORE_DOMAINS', $remote, $timestamp, '');
|
||||
push @results, { ok => $ok, label => $type_label, msg => $ok ? $stdout : $err };
|
||||
} else {
|
||||
for my $item (split /,/, $domain_names) {
|
||||
next if $item eq '';
|
||||
my ($ok, $stdout, $err) = _adminbin_call('RESTORE_DOMAINS', $remote, $timestamp, $item);
|
||||
push @results, { ok => $ok, label => $item, msg => $ok ? $stdout : $err };
|
||||
}
|
||||
}
|
||||
my $items = ($domain_names eq '' || $domain_names eq '__ALL__') ? '' : $domain_names;
|
||||
push @type_parts, $items ne '' ? "domains:$items" : 'domains';
|
||||
}
|
||||
elsif ($type eq 'ssl') {
|
||||
if ($ssl_names eq '' || $ssl_names eq '__ALL__') {
|
||||
my ($ok, $stdout, $err) = _adminbin_call('RESTORE_SSL', $remote, $timestamp, '');
|
||||
push @results, { ok => $ok, label => $type_label, msg => $ok ? $stdout : $err };
|
||||
my $items = ($ssl_names eq '' || $ssl_names eq '__ALL__') ? '' : $ssl_names;
|
||||
push @type_parts, $items ne '' ? "ssl:$items" : 'ssl';
|
||||
}
|
||||
else {
|
||||
push @type_parts, $type; # account, files, cron — no items
|
||||
}
|
||||
}
|
||||
my $types_str = join(';', @type_parts);
|
||||
|
||||
my ($ok, $stdout, $err) = _adminbin_call('START_RESTORE',
|
||||
$remote, $timestamp, $types_str, $path, $exclude_paths);
|
||||
|
||||
if ($ok) {
|
||||
Gniza4cpCPanel::UI::set_flash('success', 'Restore started. Watch progress in the activity log.');
|
||||
print "Status: 302 Found\r\n";
|
||||
print "Location: logs.live.cgi\r\n\r\n";
|
||||
} else {
|
||||
for my $item (split /,/, $ssl_names) {
|
||||
next if $item eq '';
|
||||
my ($ok, $stdout, $err) = _adminbin_call('RESTORE_SSL', $remote, $timestamp, $item);
|
||||
push @results, { ok => $ok, label => $item, msg => $ok ? $stdout : $err };
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
_render_results(\@results);
|
||||
|
||||
print qq{</div>\n</div>\n};
|
||||
|
||||
print qq{<a href="index.live.cgi" class="btn btn-info btn-sm">Back to Home</a>\n};
|
||||
|
||||
print GnizaCPanel::UI::page_footer();
|
||||
print $cpanel->footer();
|
||||
}
|
||||
|
||||
sub _render_results {
|
||||
my ($results) = @_;
|
||||
for my $r (@$results) {
|
||||
my $icon_class = $r->{ok} ? 'text-success' : 'text-error';
|
||||
my $icon = $r->{ok} ? '✓' : '✗';
|
||||
my $label = GnizaCPanel::UI::esc($r->{label});
|
||||
my $msg = GnizaCPanel::UI::esc($r->{msg} // '');
|
||||
# Clean up the "OK\n" prefix from successful results
|
||||
$msg =~ s/^OK\s*//;
|
||||
|
||||
print qq{<div class="flex items-start gap-2 mb-3 p-3 rounded-lg bg-base-200">\n};
|
||||
print qq{ <span class="$icon_class font-bold text-lg">$icon</span>\n};
|
||||
print qq{ <div>\n};
|
||||
print qq{ <div class="font-medium text-sm">$label</div>\n};
|
||||
if ($msg ne '') {
|
||||
print qq{ <pre class="text-xs mt-1 whitespace-pre-wrap max-h-32 overflow-y-auto">$msg</pre>\n};
|
||||
}
|
||||
print qq{ </div>\n};
|
||||
print qq{</div>\n};
|
||||
Gniza4cpCPanel::UI::set_flash('error', "Restore failed to start: $err");
|
||||
print "Status: 302 Found\r\n";
|
||||
print "Location: index.live.cgi\r\n\r\n";
|
||||
}
|
||||
exit;
|
||||
}
|
||||
@@ -1,18 +1,18 @@
|
||||
# gniza configuration
|
||||
# Copy to /etc/gniza/gniza.conf and edit
|
||||
# gniza4cp configuration
|
||||
# Copy to /etc/gniza4cp/gniza4cp.conf and edit
|
||||
#
|
||||
# Remote destinations: /etc/gniza/remotes.d/<name>.conf
|
||||
# Backup schedules: /etc/gniza/schedules.d/<name>.conf
|
||||
# Remote destinations: /etc/gniza4cp/remotes.d/<name>.conf
|
||||
# Backup schedules: /etc/gniza4cp/schedules.d/<name>.conf
|
||||
|
||||
# ── Local Settings ─────────────────────────────────────────────
|
||||
TEMP_DIR="/usr/local/gniza/workdir" # Working directory for pkgacct output
|
||||
TEMP_DIR="/usr/local/gniza4cp/workdir" # Working directory for pkgacct output
|
||||
|
||||
# ── Account Filtering ──────────────────────────────────────────
|
||||
INCLUDE_ACCOUNTS="" # Comma-separated list, empty = all accounts
|
||||
EXCLUDE_ACCOUNTS="nobody" # Comma-separated list of accounts to exclude
|
||||
|
||||
# ── Logging ────────────────────────────────────────────────────
|
||||
LOG_DIR="/var/log/gniza" # Log directory
|
||||
LOG_DIR="/var/log/gniza4cp" # Log directory
|
||||
LOG_LEVEL="info" # debug, info, warn, error
|
||||
LOG_RETAIN=90 # Days to keep log files
|
||||
|
||||
@@ -29,7 +29,7 @@ SMTP_FROM="" # From address (falls back to SMTP_USER)
|
||||
SMTP_SECURITY="tls" # tls (STARTTLS), ssl (implicit), none
|
||||
|
||||
# ── Advanced ───────────────────────────────────────────────────
|
||||
LOCK_FILE="/var/run/gniza.lock"
|
||||
LOCK_FILE="/var/run/gniza4cp.lock"
|
||||
SSH_TIMEOUT=30 # SSH connection timeout in seconds
|
||||
SSH_RETRIES=3 # Number of rsync retry attempts
|
||||
RSYNC_EXTRA_OPTS="" # Extra options to pass to rsync
|
||||
@@ -1,7 +1,7 @@
|
||||
# gniza remote destination config
|
||||
# Copy to /etc/gniza/remotes.d/<name>.conf and edit
|
||||
# gniza4cp remote destination config
|
||||
# Copy to /etc/gniza4cp/remotes.d/<name>.conf and edit
|
||||
#
|
||||
# Each file in /etc/gniza/remotes.d/ defines a remote backup destination.
|
||||
# Each file in /etc/gniza4cp/remotes.d/ defines a remote backup destination.
|
||||
# The filename (without .conf) is the remote name used with --remote=NAME.
|
||||
|
||||
# ── Remote Type ───────────────────────────────────────────────
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
# gniza schedule config
|
||||
# Copy to /etc/gniza/schedules.d/<name>.conf and edit
|
||||
# gniza4cp schedule config
|
||||
# Copy to /etc/gniza4cp/schedules.d/<name>.conf and edit
|
||||
#
|
||||
# Each file in /etc/gniza/schedules.d/ defines a backup schedule.
|
||||
# Each file in /etc/gniza4cp/schedules.d/ defines a backup schedule.
|
||||
# The filename (without .conf) is the schedule name.
|
||||
|
||||
# ── Schedule ──────────────────────────────────────────────────
|
||||
|
||||
|
Before Width: | Height: | Size: 685 B After Width: | Height: | Size: 685 B |
@@ -1,5 +1,5 @@
|
||||
#!/usr/bin/env bash
|
||||
# gniza/lib/accounts.sh — cPanel account discovery, include/exclude filtering
|
||||
# gniza4cp/lib/accounts.sh — cPanel account discovery, include/exclude filtering
|
||||
|
||||
get_all_accounts() {
|
||||
if [[ -f /etc/trueuserdomains ]]; then
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
#!/usr/bin/env bash
|
||||
# gniza/lib/config.sh — Shell-variable config loading & validation
|
||||
# gniza4cp/lib/config.sh — Shell-variable config loading & validation
|
||||
|
||||
# Safe config parser — reads KEY=VALUE lines without executing arbitrary code.
|
||||
# Only processes lines matching ^[A-Z_][A-Z_0-9]*= and strips surrounding quotes.
|
||||
@@ -28,7 +28,7 @@ load_config() {
|
||||
local config_file="${1:-$DEFAULT_CONFIG_FILE}"
|
||||
|
||||
if [[ ! -f "$config_file" ]]; then
|
||||
die "Config file not found: $config_file (run 'gniza init' to create one)"
|
||||
die "Config file not found: $config_file (create via WHM or copy gniza4cp.conf.example)"
|
||||
fi
|
||||
|
||||
# Parse the config (safe key=value reader, no code execution)
|
||||
@@ -56,7 +56,7 @@ load_config() {
|
||||
USER_RESTORE_REMOTES="${USER_RESTORE_REMOTES:-$DEFAULT_USER_RESTORE_REMOTES}"
|
||||
|
||||
# --debug flag overrides config
|
||||
[[ "${GNIZA_DEBUG:-false}" == "true" ]] && LOG_LEVEL="debug"
|
||||
[[ "${GNIZA4CP_DEBUG:-false}" == "true" ]] && LOG_LEVEL="debug"
|
||||
|
||||
export TEMP_DIR INCLUDE_ACCOUNTS EXCLUDE_ACCOUNTS BWLIMIT RETENTION_COUNT
|
||||
export LOG_DIR LOG_LEVEL LOG_RETAIN NOTIFY_EMAIL NOTIFY_ON
|
||||
|
||||
@@ -1,12 +1,12 @@
|
||||
#!/usr/bin/env bash
|
||||
# gniza/lib/constants.sh — Version, exit codes, colors
|
||||
# gniza4cp/lib/constants.sh — Version, exit codes, colors
|
||||
# shellcheck disable=SC2034 # constants are used by sourcing scripts
|
||||
|
||||
[[ -n "${_GNIZA_CONSTANTS_LOADED:-}" ]] && return 0
|
||||
_GNIZA_CONSTANTS_LOADED=1
|
||||
[[ -n "${_GNIZA4CP_CONSTANTS_LOADED:-}" ]] && return 0
|
||||
_GNIZA4CP_CONSTANTS_LOADED=1
|
||||
|
||||
readonly GNIZA_VERSION="0.1.0"
|
||||
readonly GNIZA_NAME="gniza"
|
||||
readonly GNIZA4CP_VERSION="0.1.0"
|
||||
readonly GNIZA4CP_NAME="gniza4cp"
|
||||
|
||||
# Exit codes
|
||||
readonly EXIT_OK=0
|
||||
@@ -36,15 +36,15 @@ readonly DEFAULT_REMOTE_AUTH_METHOD="key"
|
||||
readonly DEFAULT_REMOTE_PORT=22
|
||||
readonly DEFAULT_REMOTE_USER="root"
|
||||
readonly DEFAULT_REMOTE_BASE="/backups"
|
||||
readonly DEFAULT_TEMP_DIR="/usr/local/gniza/workdir"
|
||||
readonly DEFAULT_TEMP_DIR="/usr/local/gniza4cp/workdir"
|
||||
readonly DEFAULT_EXCLUDE_ACCOUNTS="nobody"
|
||||
readonly DEFAULT_BWLIMIT=0
|
||||
readonly DEFAULT_RETENTION_COUNT=30
|
||||
readonly DEFAULT_LOG_DIR="/var/log/gniza"
|
||||
readonly DEFAULT_LOG_DIR="/var/log/gniza4cp"
|
||||
readonly DEFAULT_LOG_LEVEL="info"
|
||||
readonly DEFAULT_LOG_RETAIN=90
|
||||
readonly DEFAULT_NOTIFY_ON="failure"
|
||||
readonly DEFAULT_LOCK_FILE="/var/run/gniza.lock"
|
||||
readonly DEFAULT_LOCK_FILE="/var/run/gniza4cp.lock"
|
||||
readonly DEFAULT_SSH_TIMEOUT=30
|
||||
readonly DEFAULT_SSH_RETRIES=3
|
||||
readonly DEFAULT_REMOTE_TYPE="ssh"
|
||||
@@ -52,4 +52,4 @@ readonly DEFAULT_S3_REGION="us-east-1"
|
||||
readonly DEFAULT_SMTP_PORT=587
|
||||
readonly DEFAULT_SMTP_SECURITY="tls"
|
||||
readonly DEFAULT_USER_RESTORE_REMOTES="all"
|
||||
readonly DEFAULT_CONFIG_FILE="/etc/gniza/gniza.conf"
|
||||
readonly DEFAULT_CONFIG_FILE="/etc/gniza4cp/gniza4cp.conf"
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
#!/usr/bin/env bash
|
||||
# gniza/lib/locking.sh — flock-based concurrency control
|
||||
# gniza4cp/lib/locking.sh — flock-based concurrency control
|
||||
|
||||
declare -g LOCK_FD=""
|
||||
|
||||
@@ -11,7 +11,7 @@ acquire_lock() {
|
||||
exec {LOCK_FD}>"$lock_file"
|
||||
|
||||
if ! flock -n "$LOCK_FD"; then
|
||||
die "Another gniza process is running (lock: $lock_file)" "$EXIT_LOCKED"
|
||||
die "Another gniza4cp process is running (lock: $lock_file)" "$EXIT_LOCKED"
|
||||
fi
|
||||
|
||||
echo $$ >&"$LOCK_FD"
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
#!/usr/bin/env bash
|
||||
# gniza/lib/logging.sh — Per-run log files, log_info/warn/error/debug
|
||||
# gniza4cp/lib/logging.sh — Per-run log files, log_info/warn/error/debug
|
||||
|
||||
declare -g LOG_FILE=""
|
||||
|
||||
@@ -17,32 +17,36 @@ init_logging() {
|
||||
local log_dir="${LOG_DIR:-$DEFAULT_LOG_DIR}"
|
||||
mkdir -p "$log_dir" || die "Cannot create log directory: $log_dir"
|
||||
|
||||
LOG_FILE="$log_dir/gniza-$(date -u +%Y%m%d-%H%M%S).log"
|
||||
LOG_FILE="$log_dir/gniza4cp-$(date -u +%Y%m%d-%H%M%S).log"
|
||||
touch "$LOG_FILE" || die "Cannot write to log file: $LOG_FILE"
|
||||
|
||||
# Clean old logs
|
||||
local retain="${LOG_RETAIN:-$DEFAULT_LOG_RETAIN}"
|
||||
find "$log_dir" -name "gniza-*.log" -mtime +"$retain" -delete 2>/dev/null || true
|
||||
find "$log_dir" -name "gniza4cp-*.log" -mtime +"$retain" -delete 2>/dev/null || true
|
||||
}
|
||||
|
||||
_log() {
|
||||
local level="$1"; shift
|
||||
local msg="$*"
|
||||
local configured_level="${LOG_LEVEL:-$DEFAULT_LOG_LEVEL}"
|
||||
|
||||
local level_num; level_num=$(_log_level_num "$level")
|
||||
local configured_num; configured_num=$(_log_level_num "$configured_level")
|
||||
|
||||
(( level_num < configured_num )) && return 0
|
||||
|
||||
local ts; ts=$(date -u +"%Y-%m-%d %H:%M:%S")
|
||||
local ts; ts=$(date -u +"%d/%m/%Y %H:%M:%S")
|
||||
local upper; upper=$(echo "$level" | tr '[:lower:]' '[:upper:]')
|
||||
local line="[$ts] [$upper] $msg"
|
||||
|
||||
# Always write to log file if initialized
|
||||
[[ -n "$LOG_FILE" ]] && echo "$line" >> "$LOG_FILE"
|
||||
local configured_level="${LOG_LEVEL:-$DEFAULT_LOG_LEVEL}"
|
||||
local level_num; level_num=$(_log_level_num "$level")
|
||||
local configured_num; configured_num=$(_log_level_num "$configured_level")
|
||||
|
||||
# Log file: always write info/warn/error; debug only when LOG_LEVEL=debug
|
||||
if [[ -n "$LOG_FILE" ]]; then
|
||||
if [[ "$level" != "debug" ]] || (( level_num >= configured_num )); then
|
||||
echo "$line" >> "$LOG_FILE"
|
||||
fi
|
||||
fi
|
||||
|
||||
# Console: only print if level meets configured threshold
|
||||
(( level_num < configured_num )) && return 0
|
||||
|
||||
# Print to stderr based on level
|
||||
case "$level" in
|
||||
error) echo "${C_RED}${line}${C_RESET}" >&2 ;;
|
||||
warn) echo "${C_YELLOW}${line}${C_RESET}" >&2 ;;
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
#!/usr/bin/env bash
|
||||
# gniza/lib/notify.sh — Email notifications (SMTP via curl or legacy mail/sendmail)
|
||||
# gniza4cp/lib/notify.sh — Email notifications (SMTP via curl or legacy mail/sendmail)
|
||||
|
||||
_send_via_smtp() {
|
||||
local subject="$1"
|
||||
@@ -116,7 +116,7 @@ send_notification() {
|
||||
esac
|
||||
|
||||
local hostname; hostname=$(hostname -f)
|
||||
local full_subject="[gniza] [$hostname] $subject"
|
||||
local full_subject="[gniza4cp] [$hostname] $subject"
|
||||
|
||||
log_debug "Sending notification to $NOTIFY_EMAIL: $full_subject"
|
||||
|
||||
@@ -155,7 +155,7 @@ send_backup_report() {
|
||||
body+="Backup Report: $status"$'\n'
|
||||
body+="=============================="$'\n'
|
||||
body+="Hostname: $(hostname -f)"$'\n'
|
||||
body+="Timestamp: $(date -u +"%Y-%m-%d %H:%M:%S UTC")"$'\n'
|
||||
body+="Timestamp: $(date -u +"%d/%m/%Y %H:%M:%S UTC")"$'\n'
|
||||
body+="Duration: $(human_duration "$duration")"$'\n'
|
||||
body+=""$'\n'
|
||||
body+="Accounts: $total total, $succeeded succeeded, $failed failed"$'\n'
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
#!/usr/bin/env bash
|
||||
# gniza/lib/pkgacct.sh — pkgacct execution, .sql gzipping, temp cleanup
|
||||
# gniza4cp/lib/pkgacct.sh — pkgacct execution, .sql gzipping, temp cleanup
|
||||
|
||||
run_pkgacct() {
|
||||
local user="$1"
|
||||
|
||||
@@ -1,8 +1,8 @@
|
||||
#!/usr/bin/env bash
|
||||
# gniza/lib/rclone.sh — Rclone transport layer for S3 and Google Drive remotes
|
||||
# gniza4cp/lib/rclone.sh — Rclone transport layer for S3 and Google Drive remotes
|
||||
|
||||
[[ -n "${_GNIZA_RCLONE_LOADED:-}" ]] && return 0
|
||||
_GNIZA_RCLONE_LOADED=1
|
||||
[[ -n "${_GNIZA4CP_RCLONE_LOADED:-}" ]] && return 0
|
||||
_GNIZA4CP_RCLONE_LOADED=1
|
||||
|
||||
# ── Mode Detection ────────────────────────────────────────────
|
||||
|
||||
@@ -17,7 +17,7 @@ _build_rclone_config() {
|
||||
local old_umask
|
||||
old_umask=$(umask)
|
||||
umask 077
|
||||
tmpfile=$(mktemp /tmp/gniza-rclone-XXXXXX.conf) || {
|
||||
tmpfile=$(mktemp /tmp/gniza4cp-rclone-XXXXXX.conf) || {
|
||||
umask "$old_umask"
|
||||
log_error "Failed to create temp rclone config"
|
||||
return 1
|
||||
|
||||
@@ -1,11 +1,11 @@
|
||||
#!/usr/bin/env bash
|
||||
# gniza/lib/remotes.sh — Remote discovery and context switching
|
||||
# gniza4cp/lib/remotes.sh — Remote discovery and context switching
|
||||
#
|
||||
# Remote destinations are configured in /etc/gniza/remotes.d/<name>.conf.
|
||||
# Remote destinations are configured in /etc/gniza4cp/remotes.d/<name>.conf.
|
||||
# Each config overrides REMOTE_* globals so existing functions (ssh,
|
||||
# transfer, snapshot, retention) work unchanged.
|
||||
|
||||
readonly REMOTES_DIR="/etc/gniza/remotes.d"
|
||||
readonly REMOTES_DIR="/etc/gniza4cp/remotes.d"
|
||||
|
||||
# ── Saved state for legacy globals ─────────────────────────────
|
||||
|
||||
@@ -132,7 +132,7 @@ load_remote() {
|
||||
GDRIVE_SERVICE_ACCOUNT_FILE="${GDRIVE_SERVICE_ACCOUNT_FILE:-}"
|
||||
GDRIVE_ROOT_FOLDER_ID="${GDRIVE_ROOT_FOLDER_ID:-}"
|
||||
|
||||
# shellcheck disable=SC2034 # used by bin/gniza
|
||||
# shellcheck disable=SC2034 # used by bin/gniza4cp
|
||||
CURRENT_REMOTE_NAME="$name"
|
||||
if [[ "$REMOTE_TYPE" == "ssh" ]]; then
|
||||
log_debug "Loaded remote '$name': ${REMOTE_USER}@${REMOTE_HOST}:${REMOTE_PORT} -> ${REMOTE_BASE}"
|
||||
@@ -266,6 +266,6 @@ get_target_remotes() {
|
||||
fi
|
||||
|
||||
# No remotes configured
|
||||
log_error "No remotes configured. Run 'gniza init remote <name>' to add one."
|
||||
log_error "No remotes configured. Add one via WHM → Remotes."
|
||||
return 1
|
||||
}
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
#!/usr/bin/env bash
|
||||
# gniza/lib/restore.sh — Full account, files, database, mailbox, server restores
|
||||
# gniza4cp/lib/restore.sh — Full account, files, database, mailbox, server restores
|
||||
|
||||
# Helper: build rsync download command args for SSH mode
|
||||
_rsync_download() {
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
#!/usr/bin/env bash
|
||||
# gniza/lib/retention.sh — Delete old snapshots beyond RETENTION_COUNT on remote
|
||||
# gniza4cp/lib/retention.sh — Delete old snapshots beyond RETENTION_COUNT on remote
|
||||
|
||||
enforce_retention() {
|
||||
local user="$1"
|
||||
|
||||
@@ -1,17 +1,17 @@
|
||||
#!/usr/bin/env bash
|
||||
# gniza/lib/schedule.sh — Cron management for decoupled schedules
|
||||
# gniza4cp/lib/schedule.sh — Cron management for decoupled schedules
|
||||
#
|
||||
# Schedules are defined in /etc/gniza/schedules.d/<name>.conf:
|
||||
# Schedules are defined in /etc/gniza4cp/schedules.d/<name>.conf:
|
||||
# SCHEDULE="hourly|daily|weekly|monthly|custom"
|
||||
# SCHEDULE_TIME="HH:MM"
|
||||
# SCHEDULE_DAY="" # dow (0-6) for weekly, dom (1-28) for monthly
|
||||
# SCHEDULE_CRON="" # full 5-field cron expr for custom
|
||||
# REMOTES="" # comma-separated remote names (empty = all)
|
||||
#
|
||||
# Cron lines are tagged with "# gniza:<name>" for clean install/remove.
|
||||
# Cron lines are tagged with "# gniza4cp:<name>" for clean install/remove.
|
||||
|
||||
readonly GNIZA_CRON_TAG="# gniza:"
|
||||
readonly SCHEDULES_DIR="/etc/gniza/schedules.d"
|
||||
readonly GNIZA4CP_CRON_TAG="# gniza4cp:"
|
||||
readonly SCHEDULES_DIR="/etc/gniza4cp/schedules.d"
|
||||
|
||||
# ── Discovery ─────────────────────────────────────────────────
|
||||
|
||||
@@ -148,13 +148,13 @@ build_cron_line() {
|
||||
extra_flags+=" --skip-suspended"
|
||||
fi
|
||||
|
||||
echo "$cron_expr /usr/local/bin/gniza backup${extra_flags} >/dev/null 2>&1"
|
||||
echo "$cron_expr /usr/local/bin/gniza4cp backup${extra_flags} >/dev/null 2>&1"
|
||||
}
|
||||
|
||||
# ── Crontab Management ────────────────────────────────────────
|
||||
|
||||
# Install cron entries for all schedules in schedules.d/.
|
||||
# Strips any existing gniza entries first, then appends new ones.
|
||||
# Strips any existing gniza4cp entries first, then appends new ones.
|
||||
install_schedules() {
|
||||
if ! has_schedules; then
|
||||
log_error "No schedules configured in $SCHEDULES_DIR"
|
||||
@@ -178,7 +178,7 @@ install_schedules() {
|
||||
local cron_line
|
||||
cron_line=$(build_cron_line "$sname") || { log_error "Skipping schedule '$sname': invalid schedule"; continue; }
|
||||
|
||||
new_lines+="${GNIZA_CRON_TAG}${sname}"$'\n'
|
||||
new_lines+="${GNIZA4CP_CRON_TAG}${sname}"$'\n'
|
||||
new_lines+="${cron_line}"$'\n'
|
||||
((count++)) || true
|
||||
done <<< "$schedules"
|
||||
@@ -188,14 +188,14 @@ install_schedules() {
|
||||
return 1
|
||||
fi
|
||||
|
||||
# Get current crontab, strip old gniza lines
|
||||
# Get current crontab, strip old gniza4cp lines
|
||||
local current_crontab=""
|
||||
current_crontab=$(crontab -l 2>/dev/null) || true
|
||||
|
||||
local filtered=""
|
||||
local skip_next=false
|
||||
while IFS= read -r line; do
|
||||
if [[ "$line" == "${GNIZA_CRON_TAG}"* ]]; then
|
||||
if [[ "$line" == "${GNIZA4CP_CRON_TAG}"* ]]; then
|
||||
skip_next=true
|
||||
continue
|
||||
fi
|
||||
@@ -206,6 +206,10 @@ install_schedules() {
|
||||
filtered+="$line"$'\n'
|
||||
done <<< "$current_crontab"
|
||||
|
||||
# Append daily stats collection (runs at 05:00 UTC)
|
||||
new_lines+="${GNIZA4CP_CRON_TAG}_stats"$'\n'
|
||||
new_lines+="0 5 * * * /usr/local/bin/gniza4cp stats >> /var/log/gniza4cp/cron-stats.log 2>&1"$'\n'
|
||||
|
||||
# Append new lines
|
||||
local final="${filtered}${new_lines}"
|
||||
|
||||
@@ -228,7 +232,7 @@ install_schedules() {
|
||||
done <<< "$schedules"
|
||||
}
|
||||
|
||||
# Display current gniza cron entries.
|
||||
# Display current gniza4cp cron entries.
|
||||
show_schedules() {
|
||||
local current_crontab=""
|
||||
current_crontab=$(crontab -l 2>/dev/null) || true
|
||||
@@ -242,15 +246,15 @@ show_schedules() {
|
||||
local next_is_command=false
|
||||
local current_tag=""
|
||||
while IFS= read -r line; do
|
||||
if [[ "$line" == "${GNIZA_CRON_TAG}"* ]]; then
|
||||
current_tag="${line#"$GNIZA_CRON_TAG"}"
|
||||
if [[ "$line" == "${GNIZA4CP_CRON_TAG}"* ]]; then
|
||||
current_tag="${line#"$GNIZA4CP_CRON_TAG"}"
|
||||
next_is_command=true
|
||||
continue
|
||||
fi
|
||||
if [[ "$next_is_command" == "true" ]]; then
|
||||
next_is_command=false
|
||||
if [[ "$found" == "false" ]]; then
|
||||
echo "Current gniza schedules:"
|
||||
echo "Current gniza4cp schedules:"
|
||||
echo ""
|
||||
found=true
|
||||
fi
|
||||
@@ -259,11 +263,11 @@ show_schedules() {
|
||||
done <<< "$current_crontab"
|
||||
|
||||
if [[ "$found" == "false" ]]; then
|
||||
echo "No gniza schedule entries in crontab."
|
||||
echo "No gniza4cp schedule entries in crontab."
|
||||
fi
|
||||
}
|
||||
|
||||
# Remove all gniza cron entries.
|
||||
# Remove all gniza4cp cron entries.
|
||||
remove_schedules() {
|
||||
local current_crontab=""
|
||||
current_crontab=$(crontab -l 2>/dev/null) || true
|
||||
@@ -277,7 +281,7 @@ remove_schedules() {
|
||||
local skip_next=false
|
||||
local removed=0
|
||||
while IFS= read -r line; do
|
||||
if [[ "$line" == "${GNIZA_CRON_TAG}"* ]]; then
|
||||
if [[ "$line" == "${GNIZA4CP_CRON_TAG}"* ]]; then
|
||||
skip_next=true
|
||||
((removed++)) || true
|
||||
continue
|
||||
@@ -290,7 +294,7 @@ remove_schedules() {
|
||||
done <<< "$current_crontab"
|
||||
|
||||
if (( removed == 0 )); then
|
||||
echo "No gniza schedule entries found in crontab."
|
||||
echo "No gniza4cp schedule entries found in crontab."
|
||||
return 0
|
||||
fi
|
||||
|
||||
@@ -299,5 +303,5 @@ remove_schedules() {
|
||||
return 1
|
||||
}
|
||||
|
||||
echo "Removed $removed gniza schedule(s) from crontab."
|
||||
echo "Removed $removed gniza4cp schedule(s) from crontab."
|
||||
}
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
#!/usr/bin/env bash
|
||||
# gniza/lib/snapshot.sh — Timestamp naming, list/resolve snapshots, latest symlink
|
||||
# gniza4cp/lib/snapshot.sh — Timestamp naming, list/resolve snapshots, latest symlink
|
||||
|
||||
get_remote_account_base() {
|
||||
local user="$1"
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
#!/usr/bin/env bash
|
||||
# gniza/lib/ssh.sh — SSH connectivity, remote exec, ssh_opts builder
|
||||
# gniza4cp/lib/ssh.sh — SSH connectivity, remote exec, ssh_opts builder
|
||||
|
||||
_is_password_mode() {
|
||||
[[ "${REMOTE_AUTH_METHOD:-key}" == "password" ]]
|
||||
|
||||
@@ -1,8 +1,8 @@
|
||||
#!/usr/bin/env bash
|
||||
# gniza/lib/sysbackup.sh — System-level WHM backup: API exports, file staging, snapshot lifecycle
|
||||
# gniza4cp/lib/sysbackup.sh — System-level WHM backup: API exports, file staging, snapshot lifecycle
|
||||
|
||||
[[ -n "${_GNIZA_SYSBACKUP_LOADED:-}" ]] && return 0
|
||||
_GNIZA_SYSBACKUP_LOADED=1
|
||||
[[ -n "${_GNIZA4CP_SYSBACKUP_LOADED:-}" ]] && return 0
|
||||
_GNIZA4CP_SYSBACKUP_LOADED=1
|
||||
|
||||
# ── Path Helpers ─────────────────────────────────────────────
|
||||
|
||||
@@ -349,8 +349,8 @@ readonly _SYSBACKUP_PATHS=(
|
||||
/etc/reservedipreasons
|
||||
/etc/sysconfig/network
|
||||
/etc/resolv.conf
|
||||
# gniza's own config
|
||||
/etc/gniza
|
||||
# gniza4cp's own config
|
||||
/etc/gniza4cp
|
||||
)
|
||||
|
||||
_stage_files() {
|
||||
|
||||
@@ -1,8 +1,8 @@
|
||||
#!/usr/bin/env bash
|
||||
# gniza/lib/sysrestore.sh — System-level WHM restore: phased restore of configs, services, DNS
|
||||
# gniza4cp/lib/sysrestore.sh — System-level WHM restore: phased restore of configs, services, DNS
|
||||
|
||||
[[ -n "${_GNIZA_SYSRESTORE_LOADED:-}" ]] && return 0
|
||||
_GNIZA_SYSRESTORE_LOADED=1
|
||||
[[ -n "${_GNIZA4CP_SYSRESTORE_LOADED:-}" ]] && return 0
|
||||
_GNIZA4CP_SYSRESTORE_LOADED=1
|
||||
|
||||
# ── Download ─────────────────────────────────────────────────
|
||||
|
||||
@@ -375,7 +375,7 @@ _restore_phase3_security() {
|
||||
log_info "[DRY RUN] Would restore CSF firewall config + csf -r"
|
||||
log_info "[DRY RUN] Would restore /root/.ssh/"
|
||||
log_info "[DRY RUN] Would restore root crontab"
|
||||
log_info "[DRY RUN] Would restore /etc/gniza/"
|
||||
log_info "[DRY RUN] Would restore /etc/gniza4cp/"
|
||||
return 0
|
||||
fi
|
||||
|
||||
@@ -412,9 +412,9 @@ _restore_phase3_security() {
|
||||
log_info "--- Restoring root crontab ---"
|
||||
_restore_file "$stage_dir" "var/spool/cron/root" || ((errors++)) || true
|
||||
|
||||
# gniza config
|
||||
log_info "--- Restoring gniza configuration ---"
|
||||
_restore_dir "$stage_dir" "etc/gniza" || ((errors++)) || true
|
||||
# gniza4cp config
|
||||
log_info "--- Restoring gniza4cp configuration ---"
|
||||
_restore_dir "$stage_dir" "etc/gniza4cp" || ((errors++)) || true
|
||||
|
||||
if (( errors > 0 )); then
|
||||
log_warn "Phase 3 completed with $errors error(s)"
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
#!/usr/bin/env bash
|
||||
# gniza/lib/transfer.sh — rsync --link-dest to remote, .partial atomicity, retries
|
||||
# gniza4cp/lib/transfer.sh — rsync --link-dest to remote, .partial atomicity, retries
|
||||
|
||||
rsync_to_remote() {
|
||||
local source_dir="$1"
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
#!/usr/bin/env bash
|
||||
# gniza/lib/utils.sh — Core utility functions
|
||||
# gniza4cp/lib/utils.sh — Core utility functions
|
||||
|
||||
die() {
|
||||
local code="${2:-$EXIT_FATAL}"
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
#!/usr/bin/env bash
|
||||
# gniza/lib/verify.sh — Remote backup integrity checks
|
||||
# gniza4cp/lib/verify.sh — Remote backup integrity checks
|
||||
|
||||
verify_account_backup() {
|
||||
local user="$1"
|
||||
|
||||
@@ -1,15 +1,12 @@
|
||||
#!/usr/bin/env bash
|
||||
# gniza installer — single-line install:
|
||||
# bash <(curl -sSL http://192.168.100.100:3001/shukivaknin/gniza/raw/branch/main/scripts/install.sh)
|
||||
#
|
||||
# Or from a local clone:
|
||||
# GNIZA installer — from a local clone:
|
||||
# bash scripts/install.sh
|
||||
|
||||
set -eo pipefail
|
||||
|
||||
INSTALL_DIR="/usr/local/gniza"
|
||||
BIN_LINK="/usr/local/bin/gniza"
|
||||
REPO_HTTP="http://192.168.100.100:3001/shukivaknin/gniza.git"
|
||||
INSTALL_DIR="/usr/local/gniza4cp"
|
||||
BIN_LINK="/usr/local/bin/gniza4cp"
|
||||
REPO_URL="https://git.linux-hosting.co.il/shukivaknin/gniza4cp.git"
|
||||
TMPDIR_CLONE=""
|
||||
|
||||
if [[ $EUID -ne 0 ]]; then
|
||||
@@ -19,19 +16,19 @@ fi
|
||||
|
||||
# Determine source directory — local clone or fresh git clone
|
||||
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]:-}")" 2>/dev/null && pwd)" || true
|
||||
if [[ -n "${SCRIPT_DIR:-}" && -f "$SCRIPT_DIR/../bin/gniza" ]]; then
|
||||
if [[ -n "${SCRIPT_DIR:-}" && -f "$SCRIPT_DIR/../bin/gniza4cp" ]]; then
|
||||
SOURCE_DIR="$(dirname "$SCRIPT_DIR")"
|
||||
else
|
||||
echo "Cloning gniza..."
|
||||
echo "Cloning GNIZA..."
|
||||
TMPDIR_CLONE="$(mktemp -d)"
|
||||
git clone --depth 1 "$REPO_HTTP" "$TMPDIR_CLONE" 2>&1
|
||||
git clone --depth 1 "$REPO_URL" "$TMPDIR_CLONE" 2>&1
|
||||
SOURCE_DIR="$TMPDIR_CLONE"
|
||||
fi
|
||||
|
||||
cleanup() { [[ -n "${TMPDIR_CLONE:-}" ]] && rm -rf "$TMPDIR_CLONE"; }
|
||||
trap cleanup EXIT
|
||||
|
||||
echo "Installing gniza to $INSTALL_DIR..."
|
||||
echo "Installing gniza4cp to $INSTALL_DIR..."
|
||||
|
||||
# Create install directory
|
||||
mkdir -p "$INSTALL_DIR"
|
||||
@@ -44,83 +41,84 @@ cp "$SOURCE_DIR/scripts/uninstall.sh" "$INSTALL_DIR/uninstall.sh"
|
||||
chmod +x "$INSTALL_DIR/uninstall.sh"
|
||||
|
||||
# Make bin executable
|
||||
chmod +x "$INSTALL_DIR/bin/gniza"
|
||||
chmod +x "$INSTALL_DIR/bin/gniza4cp"
|
||||
|
||||
# Create symlink
|
||||
ln -sf "$INSTALL_DIR/bin/gniza" "$BIN_LINK"
|
||||
ln -sf "$INSTALL_DIR/bin/gniza4cp" "$BIN_LINK"
|
||||
|
||||
# Create working directory
|
||||
mkdir -p "$INSTALL_DIR/workdir"
|
||||
|
||||
# Create config directory structure with restrictive permissions
|
||||
mkdir -p -m 700 /etc/gniza/remotes.d /etc/gniza/schedules.d
|
||||
chmod 700 /etc/gniza
|
||||
mkdir -p -m 700 /etc/gniza4cp/remotes.d /etc/gniza4cp/schedules.d
|
||||
chmod 700 /etc/gniza4cp
|
||||
|
||||
# Copy example configs if no config exists
|
||||
if [[ ! -f /etc/gniza/gniza.conf ]]; then
|
||||
cp "$INSTALL_DIR/etc/gniza.conf.example" /etc/gniza/gniza.conf.example
|
||||
echo "Example config copied to /etc/gniza/gniza.conf.example"
|
||||
if [[ ! -f /etc/gniza4cp/gniza4cp.conf ]]; then
|
||||
cp "$INSTALL_DIR/etc/gniza4cp.conf.example" /etc/gniza4cp/gniza4cp.conf.example
|
||||
echo "Example config copied to /etc/gniza4cp/gniza4cp.conf.example"
|
||||
fi
|
||||
cp "$INSTALL_DIR/etc/remote.conf.example" /etc/gniza/remote.conf.example
|
||||
cp "$INSTALL_DIR/etc/schedule.conf.example" /etc/gniza/schedule.conf.example
|
||||
cp "$INSTALL_DIR/etc/remote.conf.example" /etc/gniza4cp/remote.conf.example
|
||||
cp "$INSTALL_DIR/etc/schedule.conf.example" /etc/gniza4cp/schedule.conf.example
|
||||
|
||||
# Create log directory
|
||||
mkdir -p /var/log/gniza
|
||||
mkdir -p /var/log/gniza4cp
|
||||
|
||||
echo "gniza installed successfully!"
|
||||
echo "GNIZA installed successfully!"
|
||||
|
||||
# ── WHM Plugin (if cPanel/WHM is present) ─────────────────────
|
||||
WHM_CGI_DIR="/usr/local/cpanel/whostmgr/docroot/cgi"
|
||||
if [[ -d "$WHM_CGI_DIR" ]]; then
|
||||
echo "Installing WHM plugin..."
|
||||
# Remove old assets cruft (node_modules, src) if upgrading
|
||||
rm -rf "$WHM_CGI_DIR/gniza-whm/assets/node_modules" \
|
||||
"$WHM_CGI_DIR/gniza-whm/assets/src" \
|
||||
"$WHM_CGI_DIR/gniza-whm/assets/package.json" \
|
||||
"$WHM_CGI_DIR/gniza-whm/assets/package-lock.json" 2>/dev/null || true
|
||||
cp -r "$SOURCE_DIR/whm/gniza-whm" "$WHM_CGI_DIR/"
|
||||
cp "$SOURCE_DIR/whm/gniza-whm.conf" "$WHM_CGI_DIR/gniza-whm/"
|
||||
chmod +x "$WHM_CGI_DIR/gniza-whm/"*.cgi
|
||||
rm -rf "$WHM_CGI_DIR/gniza4cp-whm/assets/node_modules" \
|
||||
"$WHM_CGI_DIR/gniza4cp-whm/assets/src" \
|
||||
"$WHM_CGI_DIR/gniza4cp-whm/assets/package.json" \
|
||||
"$WHM_CGI_DIR/gniza4cp-whm/assets/package-lock.json" 2>/dev/null || true
|
||||
cp -r "$SOURCE_DIR/whm/gniza4cp-whm" "$WHM_CGI_DIR/"
|
||||
cp "$SOURCE_DIR/whm/gniza4cp-whm.conf" "$WHM_CGI_DIR/gniza4cp-whm/"
|
||||
chmod +x "$WHM_CGI_DIR/gniza4cp-whm/"*.cgi
|
||||
# Remove build artifacts that shouldn't be on the server
|
||||
rm -rf "$WHM_CGI_DIR/gniza-whm/assets/node_modules" \
|
||||
"$WHM_CGI_DIR/gniza-whm/assets/src" \
|
||||
"$WHM_CGI_DIR/gniza-whm/assets/package.json" \
|
||||
"$WHM_CGI_DIR/gniza-whm/assets/package-lock.json" 2>/dev/null || true
|
||||
/usr/local/cpanel/bin/register_appconfig "$WHM_CGI_DIR/gniza-whm/gniza-whm.conf"
|
||||
echo "WHM plugin installed — access via WHM > Plugins > gniza Backup Manager"
|
||||
rm -rf "$WHM_CGI_DIR/gniza4cp-whm/assets/node_modules" \
|
||||
"$WHM_CGI_DIR/gniza4cp-whm/assets/src" \
|
||||
"$WHM_CGI_DIR/gniza4cp-whm/assets/package.json" \
|
||||
"$WHM_CGI_DIR/gniza4cp-whm/assets/package-lock.json" 2>/dev/null || true
|
||||
/usr/local/cpanel/bin/register_appconfig "$WHM_CGI_DIR/gniza4cp-whm/gniza4cp-whm.conf"
|
||||
echo "WHM plugin installed — access via WHM > Plugins > GNIZA Backup Manager"
|
||||
else
|
||||
echo "WHM not detected, skipping WHM plugin installation."
|
||||
fi
|
||||
|
||||
# ── cPanel User Plugin (if cPanel is present) ────────────────
|
||||
CPANEL_BASE="/usr/local/cpanel/base/frontend/jupiter"
|
||||
ADMINBIN_DIR="/usr/local/cpanel/bin/admin/Gniza"
|
||||
ADMINBIN_DIR="/usr/local/cpanel/bin/admin/Gniza4cp"
|
||||
if [[ -d "$CPANEL_BASE" ]]; then
|
||||
echo "Installing cPanel user plugin..."
|
||||
# Copy CGI files + lib + assets
|
||||
mkdir -p "$CPANEL_BASE/gniza/lib/GnizaCPanel" "$CPANEL_BASE/gniza/assets"
|
||||
cp "$SOURCE_DIR/cpanel/gniza/index.live.cgi" "$CPANEL_BASE/gniza/"
|
||||
cp "$SOURCE_DIR/cpanel/gniza/restore.live.cgi" "$CPANEL_BASE/gniza/"
|
||||
chmod +x "$CPANEL_BASE/gniza/"*.cgi
|
||||
cp "$SOURCE_DIR/cpanel/gniza/lib/GnizaCPanel/UI.pm" "$CPANEL_BASE/gniza/lib/GnizaCPanel/"
|
||||
cp "$SOURCE_DIR/cpanel/gniza/assets/gniza-whm.css" "$CPANEL_BASE/gniza/assets/"
|
||||
cp "$SOURCE_DIR/cpanel/gniza/assets/gniza-logo.svg" "$CPANEL_BASE/gniza/assets/"
|
||||
cp "$SOURCE_DIR/cpanel/gniza/assets/gniza-cpanel-icon.png" "$CPANEL_BASE/gniza/assets/"
|
||||
cp "$SOURCE_DIR/cpanel/gniza/install.json" "$CPANEL_BASE/gniza/"
|
||||
mkdir -p "$CPANEL_BASE/gniza4cp/lib/Gniza4cpCPanel" "$CPANEL_BASE/gniza4cp/assets"
|
||||
cp "$SOURCE_DIR/cpanel/gniza4cp/index.live.cgi" "$CPANEL_BASE/gniza4cp/"
|
||||
cp "$SOURCE_DIR/cpanel/gniza4cp/restore.live.cgi" "$CPANEL_BASE/gniza4cp/"
|
||||
cp "$SOURCE_DIR/cpanel/gniza4cp/logs.live.cgi" "$CPANEL_BASE/gniza4cp/"
|
||||
chmod +x "$CPANEL_BASE/gniza4cp/"*.cgi
|
||||
cp "$SOURCE_DIR/cpanel/gniza4cp/lib/Gniza4cpCPanel/UI.pm" "$CPANEL_BASE/gniza4cp/lib/Gniza4cpCPanel/"
|
||||
cp "$SOURCE_DIR/cpanel/gniza4cp/assets/gniza4cp-whm.css" "$CPANEL_BASE/gniza4cp/assets/"
|
||||
cp "$SOURCE_DIR/cpanel/gniza4cp/assets/gniza4cp-logo.svg" "$CPANEL_BASE/gniza4cp/assets/"
|
||||
cp "$SOURCE_DIR/cpanel/gniza4cp/assets/gniza4cp-cpanel-icon.png" "$CPANEL_BASE/gniza4cp/assets/"
|
||||
cp "$SOURCE_DIR/cpanel/gniza4cp/install.json" "$CPANEL_BASE/gniza4cp/"
|
||||
# Install AdminBin module (runs as root)
|
||||
mkdir -p "$ADMINBIN_DIR"
|
||||
cp "$SOURCE_DIR/cpanel/admin/Gniza/Restore" "$ADMINBIN_DIR/"
|
||||
cp "$SOURCE_DIR/cpanel/admin/Gniza/Restore.conf" "$ADMINBIN_DIR/"
|
||||
cp "$SOURCE_DIR/cpanel/admin/Gniza4cp/Restore" "$ADMINBIN_DIR/"
|
||||
cp "$SOURCE_DIR/cpanel/admin/Gniza4cp/Restore.conf" "$ADMINBIN_DIR/"
|
||||
chmod 0700 "$ADMINBIN_DIR/Restore"
|
||||
chmod 0600 "$ADMINBIN_DIR/Restore.conf"
|
||||
# Register plugin in cPanel interface (install_plugin expects a tar.gz archive
|
||||
# containing install.json + the icon file referenced in it)
|
||||
PLUGIN_TMPDIR="$(mktemp -d)"
|
||||
mkdir -p "$PLUGIN_TMPDIR/gniza/assets"
|
||||
cp "$SOURCE_DIR/cpanel/gniza/install.json" "$PLUGIN_TMPDIR/"
|
||||
cp "$SOURCE_DIR/cpanel/gniza/assets/gniza-cpanel-icon.png" "$PLUGIN_TMPDIR/gniza/assets/"
|
||||
tar -czf "$PLUGIN_TMPDIR/gniza-cpanel.tar.gz" -C "$PLUGIN_TMPDIR" install.json gniza/assets/gniza-cpanel-icon.png
|
||||
/usr/local/cpanel/scripts/install_plugin "$PLUGIN_TMPDIR/gniza-cpanel.tar.gz" 2>/dev/null || true
|
||||
mkdir -p "$PLUGIN_TMPDIR/gniza4cp/assets"
|
||||
cp "$SOURCE_DIR/cpanel/gniza4cp/install.json" "$PLUGIN_TMPDIR/"
|
||||
cp "$SOURCE_DIR/cpanel/gniza4cp/assets/gniza4cp-cpanel-icon.png" "$PLUGIN_TMPDIR/gniza4cp/assets/"
|
||||
tar -czf "$PLUGIN_TMPDIR/gniza4cp-cpanel.tar.gz" -C "$PLUGIN_TMPDIR" install.json gniza4cp/assets/gniza4cp-cpanel-icon.png
|
||||
/usr/local/cpanel/scripts/install_plugin "$PLUGIN_TMPDIR/gniza4cp-cpanel.tar.gz" 2>/dev/null || true
|
||||
rm -rf "$PLUGIN_TMPDIR"
|
||||
# Rebuild icon sprites so the new icon appears in cPanel
|
||||
/usr/local/cpanel/bin/rebuild_sprites 2>/dev/null || true
|
||||
@@ -131,13 +129,11 @@ if [[ -d "$CPANEL_BASE" ]]; then
|
||||
[[ -n "$user" ]] && cpapi2 --user="$user" Branding gensprites 2>/dev/null || true
|
||||
done < /etc/trueuserdomains
|
||||
fi
|
||||
echo "cPanel user plugin installed — users will see gniza Restore in Files section"
|
||||
echo "cPanel user plugin installed — users will see GNIZA Restore in Files section"
|
||||
else
|
||||
echo "cPanel not detected, skipping cPanel user plugin installation."
|
||||
fi
|
||||
|
||||
echo ""
|
||||
echo "Next steps:"
|
||||
echo " 1. Run 'gniza init' to create your configuration"
|
||||
echo " 2. Or copy /etc/gniza/gniza.conf.example to /etc/gniza/gniza.conf"
|
||||
echo " 3. Run 'gniza status' to verify your setup"
|
||||
echo " Open WHM → GNIZA Backup Manager to configure via the setup wizard."
|
||||
|
||||
@@ -1,17 +1,17 @@
|
||||
#!/usr/bin/env bash
|
||||
# gniza uninstall script
|
||||
# GNIZA uninstall script
|
||||
|
||||
set -euo pipefail
|
||||
|
||||
INSTALL_DIR="/usr/local/gniza"
|
||||
BIN_LINK="/usr/local/bin/gniza"
|
||||
INSTALL_DIR="/usr/local/gniza4cp"
|
||||
BIN_LINK="/usr/local/bin/gniza4cp"
|
||||
|
||||
if [[ $EUID -ne 0 ]]; then
|
||||
echo "Error: uninstall.sh must be run as root" >&2
|
||||
exit 1
|
||||
fi
|
||||
|
||||
echo "Uninstalling gniza..."
|
||||
echo "Uninstalling GNIZA..."
|
||||
|
||||
# Remove symlink
|
||||
if [[ -L "$BIN_LINK" ]]; then
|
||||
@@ -26,38 +26,38 @@ if [[ -d "$INSTALL_DIR" ]]; then
|
||||
fi
|
||||
|
||||
# ── Remove cron entries ───────────────────────────────────────
|
||||
if crontab -l 2>/dev/null | grep -q '# gniza:'; then
|
||||
echo "Removing gniza cron entries..."
|
||||
crontab -l 2>/dev/null | grep -v '# gniza:' | grep -v '/usr/local/bin/gniza' | crontab -
|
||||
if crontab -l 2>/dev/null | grep -q '# gniza4cp:'; then
|
||||
echo "Removing GNIZA cron entries..."
|
||||
crontab -l 2>/dev/null | grep -v '# gniza4cp:' | grep -v '/usr/local/bin/gniza4cp' | crontab -
|
||||
echo "Cron entries removed."
|
||||
fi
|
||||
|
||||
# ── WHM Plugin ────────────────────────────────────────────────
|
||||
WHM_CGI_DIR="/usr/local/cpanel/whostmgr/docroot/cgi"
|
||||
if [[ -d "$WHM_CGI_DIR/gniza-whm" ]]; then
|
||||
if [[ -d "$WHM_CGI_DIR/gniza4cp-whm" ]]; then
|
||||
echo "Removing WHM plugin..."
|
||||
/usr/local/cpanel/bin/unregister_appconfig gniza-whm 2>/dev/null || true
|
||||
rm -rf "$WHM_CGI_DIR/gniza-whm"
|
||||
/usr/local/cpanel/bin/unregister_appconfig gniza4cp-whm 2>/dev/null || true
|
||||
rm -rf "$WHM_CGI_DIR/gniza4cp-whm"
|
||||
echo "WHM plugin removed."
|
||||
fi
|
||||
|
||||
# ── cPanel User Plugin ────────────────────────────────────────
|
||||
CPANEL_BASE="/usr/local/cpanel/base/frontend/jupiter"
|
||||
ADMINBIN_DIR="/usr/local/cpanel/bin/admin/Gniza"
|
||||
if [[ -d "$CPANEL_BASE/gniza" ]]; then
|
||||
ADMINBIN_DIR="/usr/local/cpanel/bin/admin/Gniza4cp"
|
||||
if [[ -d "$CPANEL_BASE/gniza4cp" ]]; then
|
||||
echo "Removing cPanel user plugin..."
|
||||
# uninstall_plugin expects a tar.gz archive with install.json + icon
|
||||
if [[ -f "$CPANEL_BASE/gniza/install.json" ]]; then
|
||||
if [[ -f "$CPANEL_BASE/gniza4cp/install.json" ]]; then
|
||||
PLUGIN_TMPDIR="$(mktemp -d)"
|
||||
mkdir -p "$PLUGIN_TMPDIR/gniza/assets"
|
||||
cp "$CPANEL_BASE/gniza/install.json" "$PLUGIN_TMPDIR/"
|
||||
cp "$CPANEL_BASE/gniza/assets/gniza-cpanel-icon.png" "$PLUGIN_TMPDIR/gniza/assets/" 2>/dev/null || true
|
||||
tar -czf "$PLUGIN_TMPDIR/gniza-cpanel.tar.gz" -C "$PLUGIN_TMPDIR" install.json gniza/assets/gniza-cpanel-icon.png 2>/dev/null || \
|
||||
tar -czf "$PLUGIN_TMPDIR/gniza-cpanel.tar.gz" -C "$PLUGIN_TMPDIR" install.json
|
||||
/usr/local/cpanel/scripts/uninstall_plugin "$PLUGIN_TMPDIR/gniza-cpanel.tar.gz" 2>/dev/null || true
|
||||
mkdir -p "$PLUGIN_TMPDIR/gniza4cp/assets"
|
||||
cp "$CPANEL_BASE/gniza4cp/install.json" "$PLUGIN_TMPDIR/"
|
||||
cp "$CPANEL_BASE/gniza4cp/assets/gniza4cp-cpanel-icon.png" "$PLUGIN_TMPDIR/gniza4cp/assets/" 2>/dev/null || true
|
||||
tar -czf "$PLUGIN_TMPDIR/gniza4cp-cpanel.tar.gz" -C "$PLUGIN_TMPDIR" install.json gniza4cp/assets/gniza4cp-cpanel-icon.png 2>/dev/null || \
|
||||
tar -czf "$PLUGIN_TMPDIR/gniza4cp-cpanel.tar.gz" -C "$PLUGIN_TMPDIR" install.json
|
||||
/usr/local/cpanel/scripts/uninstall_plugin "$PLUGIN_TMPDIR/gniza4cp-cpanel.tar.gz" 2>/dev/null || true
|
||||
rm -rf "$PLUGIN_TMPDIR"
|
||||
fi
|
||||
rm -rf "$CPANEL_BASE/gniza"
|
||||
rm -rf "$CPANEL_BASE/gniza4cp"
|
||||
echo "cPanel user plugin removed."
|
||||
fi
|
||||
if [[ -d "$ADMINBIN_DIR" ]]; then
|
||||
@@ -66,12 +66,12 @@ if [[ -d "$ADMINBIN_DIR" ]]; then
|
||||
fi
|
||||
|
||||
echo ""
|
||||
echo "gniza uninstalled."
|
||||
echo "GNIZA uninstalled."
|
||||
echo ""
|
||||
echo "The following were NOT removed (manual cleanup if desired):"
|
||||
echo " /etc/gniza/ (configuration + remotes.d/)"
|
||||
echo " /var/log/gniza/ (log files)"
|
||||
echo " /var/run/gniza.lock (lock file)"
|
||||
echo " /etc/gniza4cp/ (configuration + remotes.d/)"
|
||||
echo " /var/log/gniza4cp/ (log files)"
|
||||
echo " /var/run/gniza4cp.lock (lock file)"
|
||||
echo ""
|
||||
echo "To remove configs: rm -rf /etc/gniza/"
|
||||
echo "To remove logs: rm -rf /var/log/gniza/"
|
||||
echo "To remove configs: rm -rf /etc/gniza4cp/"
|
||||
echo "To remove logs: rm -rf /var/log/gniza4cp/"
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
#!/usr/bin/env bash
|
||||
# gniza tests — utility functions
|
||||
# gniza4cp tests — utility functions
|
||||
|
||||
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
||||
BASE_DIR="$(dirname "$SCRIPT_DIR")"
|
||||
|
||||
@@ -1,7 +0,0 @@
|
||||
name=gniza-whm
|
||||
service=whostmgr
|
||||
user=root
|
||||
url=/cgi/gniza-whm/
|
||||
acls=all
|
||||
displayname=gniza Backup Manager
|
||||
entryurl=gniza-whm/index.cgi
|
||||
@@ -1,12 +0,0 @@
|
||||
{
|
||||
"private": true,
|
||||
"scripts": {
|
||||
"build:css": "tailwindcss -i src/input.css -o gniza-whm.css --minify",
|
||||
"dev:css": "tailwindcss -i src/input.css -o gniza-whm.css --watch"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@tailwindcss/cli": "^4",
|
||||
"tailwindcss": "^4",
|
||||
"daisyui": "^5"
|
||||
}
|
||||
}
|
||||
7
whm/gniza4cp-whm.conf
Normal file
@@ -0,0 +1,7 @@
|
||||
name=gniza4cp-whm
|
||||
service=whostmgr
|
||||
user=root
|
||||
url=/cgi/gniza4cp-whm/
|
||||
acls=all
|
||||
displayname=GNIZA Backup Manager
|
||||
entryurl=gniza4cp-whm/index.cgi
|
||||
11
whm/gniza4cp-whm/assets/gniza4cp-logo.svg
Normal file
@@ -0,0 +1,11 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<svg width="48" height="48" version="1.1" viewBox="0 0 12.7 12.7" xmlns="http://www.w3.org/2000/svg">
|
||||
<defs>
|
||||
<clipPath id="clipPath7">
|
||||
<path d="m0 792h612v-792h-612z"/>
|
||||
</clipPath>
|
||||
</defs>
|
||||
<g transform="translate(-97.367 -139.17)">
|
||||
<path transform="matrix(.23378 0 0 -.23378 50.654 216.38)" d="m219.89 325.53h14.261v2.3h-14.261zm0-16.189h14.261v2.3h-14.261zm-14.721 4.14h43.703v-12.881h-43.703zm14.721-18.029h14.261v-2.3h-14.261zm7.13-19.157 2.663 4.609 2.024 3.507h17.165v12.881h-43.703v-12.881h17.166l2.023-3.507zm-21.851 53.375h43.703v-12.881h-43.703z" clip-path="url(#clipPath7)" fill="#f47216" fill-rule="evenodd"/>
|
||||
</g>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 685 B |
2
whm/gniza4cp-whm/assets/gniza4cp-whm.css
Normal file
1
whm/gniza4cp-whm/assets/node_modules/.bin/jiti
generated
vendored
Symbolic link
@@ -0,0 +1 @@
|
||||
../jiti/lib/jiti-cli.mjs
|
||||
1
whm/gniza4cp-whm/assets/node_modules/.bin/tailwindcss
generated
vendored
Symbolic link
@@ -0,0 +1 @@
|
||||
../@tailwindcss/cli/dist/index.mjs
|
||||
421
whm/gniza4cp-whm/assets/node_modules/.package-lock.json
generated
vendored
Normal file
@@ -0,0 +1,421 @@
|
||||
{
|
||||
"name": "assets",
|
||||
"lockfileVersion": 3,
|
||||
"requires": true,
|
||||
"packages": {
|
||||
"node_modules/@jridgewell/gen-mapping": {
|
||||
"version": "0.3.13",
|
||||
"resolved": "https://registry.npmjs.org/@jridgewell/gen-mapping/-/gen-mapping-0.3.13.tgz",
|
||||
"integrity": "sha512-2kkt/7niJ6MgEPxF0bYdQ6etZaA+fQvDcLKckhy1yIQOzaoKjBBjSj63/aLVjYE3qhRt5dvM+uUyfCg6UKCBbA==",
|
||||
"dev": true,
|
||||
"dependencies": {
|
||||
"@jridgewell/sourcemap-codec": "^1.5.0",
|
||||
"@jridgewell/trace-mapping": "^0.3.24"
|
||||
}
|
||||
},
|
||||
"node_modules/@jridgewell/remapping": {
|
||||
"version": "2.3.5",
|
||||
"resolved": "https://registry.npmjs.org/@jridgewell/remapping/-/remapping-2.3.5.tgz",
|
||||
"integrity": "sha512-LI9u/+laYG4Ds1TDKSJW2YPrIlcVYOwi2fUC6xB43lueCjgxV4lffOCZCtYFiH6TNOX+tQKXx97T4IKHbhyHEQ==",
|
||||
"dev": true,
|
||||
"dependencies": {
|
||||
"@jridgewell/gen-mapping": "^0.3.5",
|
||||
"@jridgewell/trace-mapping": "^0.3.24"
|
||||
}
|
||||
},
|
||||
"node_modules/@jridgewell/resolve-uri": {
|
||||
"version": "3.1.2",
|
||||
"resolved": "https://registry.npmjs.org/@jridgewell/resolve-uri/-/resolve-uri-3.1.2.tgz",
|
||||
"integrity": "sha512-bRISgCIjP20/tbWSPWMEi54QVPRZExkuD9lJL+UIxUKtwVJA8wW1Trb1jMs1RFXo1CBTNZ/5hpC9QvmKWdopKw==",
|
||||
"dev": true,
|
||||
"engines": {
|
||||
"node": ">=6.0.0"
|
||||
}
|
||||
},
|
||||
"node_modules/@jridgewell/sourcemap-codec": {
|
||||
"version": "1.5.5",
|
||||
"resolved": "https://registry.npmjs.org/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.5.5.tgz",
|
||||
"integrity": "sha512-cYQ9310grqxueWbl+WuIUIaiUaDcj7WOq5fVhEljNVgRfOUhY9fy2zTvfoqWsnebh8Sl70VScFbICvJnLKB0Og==",
|
||||
"dev": true
|
||||
},
|
||||
"node_modules/@jridgewell/trace-mapping": {
|
||||
"version": "0.3.31",
|
||||
"resolved": "https://registry.npmjs.org/@jridgewell/trace-mapping/-/trace-mapping-0.3.31.tgz",
|
||||
"integrity": "sha512-zzNR+SdQSDJzc8joaeP8QQoCQr8NuYx2dIIytl1QeBEZHJ9uW6hebsrYgbz8hJwUQao3TWCMtmfV8Nu1twOLAw==",
|
||||
"dev": true,
|
||||
"dependencies": {
|
||||
"@jridgewell/resolve-uri": "^3.1.0",
|
||||
"@jridgewell/sourcemap-codec": "^1.4.14"
|
||||
}
|
||||
},
|
||||
"node_modules/@parcel/watcher": {
|
||||
"version": "2.5.6",
|
||||
"resolved": "https://registry.npmjs.org/@parcel/watcher/-/watcher-2.5.6.tgz",
|
||||
"integrity": "sha512-tmmZ3lQxAe/k/+rNnXQRawJ4NjxO2hqiOLTHvWchtGZULp4RyFeh6aU4XdOYBFe2KE1oShQTv4AblOs2iOrNnQ==",
|
||||
"dev": true,
|
||||
"hasInstallScript": true,
|
||||
"dependencies": {
|
||||
"detect-libc": "^2.0.3",
|
||||
"is-glob": "^4.0.3",
|
||||
"node-addon-api": "^7.0.0",
|
||||
"picomatch": "^4.0.3"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">= 10.0.0"
|
||||
},
|
||||
"funding": {
|
||||
"type": "opencollective",
|
||||
"url": "https://opencollective.com/parcel"
|
||||
},
|
||||
"optionalDependencies": {
|
||||
"@parcel/watcher-android-arm64": "2.5.6",
|
||||
"@parcel/watcher-darwin-arm64": "2.5.6",
|
||||
"@parcel/watcher-darwin-x64": "2.5.6",
|
||||
"@parcel/watcher-freebsd-x64": "2.5.6",
|
||||
"@parcel/watcher-linux-arm-glibc": "2.5.6",
|
||||
"@parcel/watcher-linux-arm-musl": "2.5.6",
|
||||
"@parcel/watcher-linux-arm64-glibc": "2.5.6",
|
||||
"@parcel/watcher-linux-arm64-musl": "2.5.6",
|
||||
"@parcel/watcher-linux-x64-glibc": "2.5.6",
|
||||
"@parcel/watcher-linux-x64-musl": "2.5.6",
|
||||
"@parcel/watcher-win32-arm64": "2.5.6",
|
||||
"@parcel/watcher-win32-ia32": "2.5.6",
|
||||
"@parcel/watcher-win32-x64": "2.5.6"
|
||||
}
|
||||
},
|
||||
"node_modules/@parcel/watcher-linux-x64-glibc": {
|
||||
"version": "2.5.6",
|
||||
"resolved": "https://registry.npmjs.org/@parcel/watcher-linux-x64-glibc/-/watcher-linux-x64-glibc-2.5.6.tgz",
|
||||
"integrity": "sha512-kbT5wvNQlx7NaGjzPFu8nVIW1rWqV780O7ZtkjuWaPUgpv2NMFpjYERVi0UYj1msZNyCzGlaCWEtzc+exjMGbQ==",
|
||||
"cpu": [
|
||||
"x64"
|
||||
],
|
||||
"dev": true,
|
||||
"optional": true,
|
||||
"os": [
|
||||
"linux"
|
||||
],
|
||||
"engines": {
|
||||
"node": ">= 10.0.0"
|
||||
},
|
||||
"funding": {
|
||||
"type": "opencollective",
|
||||
"url": "https://opencollective.com/parcel"
|
||||
}
|
||||
},
|
||||
"node_modules/@parcel/watcher-linux-x64-musl": {
|
||||
"version": "2.5.6",
|
||||
"resolved": "https://registry.npmjs.org/@parcel/watcher-linux-x64-musl/-/watcher-linux-x64-musl-2.5.6.tgz",
|
||||
"integrity": "sha512-1JRFeC+h7RdXwldHzTsmdtYR/Ku8SylLgTU/reMuqdVD7CtLwf0VR1FqeprZ0eHQkO0vqsbvFLXUmYm/uNKJBg==",
|
||||
"cpu": [
|
||||
"x64"
|
||||
],
|
||||
"dev": true,
|
||||
"optional": true,
|
||||
"os": [
|
||||
"linux"
|
||||
],
|
||||
"engines": {
|
||||
"node": ">= 10.0.0"
|
||||
},
|
||||
"funding": {
|
||||
"type": "opencollective",
|
||||
"url": "https://opencollective.com/parcel"
|
||||
}
|
||||
},
|
||||
"node_modules/@tailwindcss/cli": {
|
||||
"version": "4.2.1",
|
||||
"resolved": "https://registry.npmjs.org/@tailwindcss/cli/-/cli-4.2.1.tgz",
|
||||
"integrity": "sha512-b7MGn51IA80oSG+7fuAgzfQ+7pZBgjzbqwmiv6NO7/+a1sev32cGqnwhscT7h0EcAvMa9r7gjRylqOH8Xhc4DA==",
|
||||
"dev": true,
|
||||
"dependencies": {
|
||||
"@parcel/watcher": "^2.5.1",
|
||||
"@tailwindcss/node": "4.2.1",
|
||||
"@tailwindcss/oxide": "4.2.1",
|
||||
"enhanced-resolve": "^5.19.0",
|
||||
"mri": "^1.2.0",
|
||||
"picocolors": "^1.1.1",
|
||||
"tailwindcss": "4.2.1"
|
||||
},
|
||||
"bin": {
|
||||
"tailwindcss": "dist/index.mjs"
|
||||
}
|
||||
},
|
||||
"node_modules/@tailwindcss/node": {
|
||||
"version": "4.2.1",
|
||||
"resolved": "https://registry.npmjs.org/@tailwindcss/node/-/node-4.2.1.tgz",
|
||||
"integrity": "sha512-jlx6sLk4EOwO6hHe1oCGm1Q4AN/s0rSrTTPBGPM0/RQ6Uylwq17FuU8IeJJKEjtc6K6O07zsvP+gDO6MMWo7pg==",
|
||||
"dev": true,
|
||||
"dependencies": {
|
||||
"@jridgewell/remapping": "^2.3.5",
|
||||
"enhanced-resolve": "^5.19.0",
|
||||
"jiti": "^2.6.1",
|
||||
"lightningcss": "1.31.1",
|
||||
"magic-string": "^0.30.21",
|
||||
"source-map-js": "^1.2.1",
|
||||
"tailwindcss": "4.2.1"
|
||||
}
|
||||
},
|
||||
"node_modules/@tailwindcss/oxide": {
|
||||
"version": "4.2.1",
|
||||
"resolved": "https://registry.npmjs.org/@tailwindcss/oxide/-/oxide-4.2.1.tgz",
|
||||
"integrity": "sha512-yv9jeEFWnjKCI6/T3Oq50yQEOqmpmpfzG1hcZsAOaXFQPfzWprWrlHSdGPEF3WQTi8zu8ohC9Mh9J470nT5pUw==",
|
||||
"dev": true,
|
||||
"engines": {
|
||||
"node": ">= 20"
|
||||
},
|
||||
"optionalDependencies": {
|
||||
"@tailwindcss/oxide-android-arm64": "4.2.1",
|
||||
"@tailwindcss/oxide-darwin-arm64": "4.2.1",
|
||||
"@tailwindcss/oxide-darwin-x64": "4.2.1",
|
||||
"@tailwindcss/oxide-freebsd-x64": "4.2.1",
|
||||
"@tailwindcss/oxide-linux-arm-gnueabihf": "4.2.1",
|
||||
"@tailwindcss/oxide-linux-arm64-gnu": "4.2.1",
|
||||
"@tailwindcss/oxide-linux-arm64-musl": "4.2.1",
|
||||
"@tailwindcss/oxide-linux-x64-gnu": "4.2.1",
|
||||
"@tailwindcss/oxide-linux-x64-musl": "4.2.1",
|
||||
"@tailwindcss/oxide-wasm32-wasi": "4.2.1",
|
||||
"@tailwindcss/oxide-win32-arm64-msvc": "4.2.1",
|
||||
"@tailwindcss/oxide-win32-x64-msvc": "4.2.1"
|
||||
}
|
||||
},
|
||||
"node_modules/@tailwindcss/oxide-linux-x64-gnu": {
|
||||
"version": "4.2.1",
|
||||
"resolved": "https://registry.npmjs.org/@tailwindcss/oxide-linux-x64-gnu/-/oxide-linux-x64-gnu-4.2.1.tgz",
|
||||
"integrity": "sha512-qMFzxI2YlBOLW5PhblzuSWlWfwLHaneBE0xHzLrBgNtqN6mWfs+qYbhryGSXQjFYB1Dzf5w+LN5qbUTPhW7Y5g==",
|
||||
"cpu": [
|
||||
"x64"
|
||||
],
|
||||
"dev": true,
|
||||
"optional": true,
|
||||
"os": [
|
||||
"linux"
|
||||
],
|
||||
"engines": {
|
||||
"node": ">= 20"
|
||||
}
|
||||
},
|
||||
"node_modules/@tailwindcss/oxide-linux-x64-musl": {
|
||||
"version": "4.2.1",
|
||||
"resolved": "https://registry.npmjs.org/@tailwindcss/oxide-linux-x64-musl/-/oxide-linux-x64-musl-4.2.1.tgz",
|
||||
"integrity": "sha512-5r1X2FKnCMUPlXTWRYpHdPYUY6a1Ar/t7P24OuiEdEOmms5lyqjDRvVY1yy9Rmioh+AunQ0rWiOTPE8F9A3v5g==",
|
||||
"cpu": [
|
||||
"x64"
|
||||
],
|
||||
"dev": true,
|
||||
"optional": true,
|
||||
"os": [
|
||||
"linux"
|
||||
],
|
||||
"engines": {
|
||||
"node": ">= 20"
|
||||
}
|
||||
},
|
||||
"node_modules/daisyui": {
|
||||
"version": "5.5.19",
|
||||
"resolved": "https://registry.npmjs.org/daisyui/-/daisyui-5.5.19.tgz",
|
||||
"integrity": "sha512-pbFAkl1VCEh/MPCeclKL61I/MqRIFFhNU7yiXoDDRapXN4/qNCoMxeCCswyxEEhqL5eiTTfwHvucFtOE71C9sA==",
|
||||
"dev": true,
|
||||
"funding": {
|
||||
"url": "https://github.com/saadeghi/daisyui?sponsor=1"
|
||||
}
|
||||
},
|
||||
"node_modules/detect-libc": {
|
||||
"version": "2.1.2",
|
||||
"resolved": "https://registry.npmjs.org/detect-libc/-/detect-libc-2.1.2.tgz",
|
||||
"integrity": "sha512-Btj2BOOO83o3WyH59e8MgXsxEQVcarkUOpEYrubB0urwnN10yQ364rsiByU11nZlqWYZm05i/of7io4mzihBtQ==",
|
||||
"dev": true,
|
||||
"engines": {
|
||||
"node": ">=8"
|
||||
}
|
||||
},
|
||||
"node_modules/enhanced-resolve": {
|
||||
"version": "5.20.0",
|
||||
"resolved": "https://registry.npmjs.org/enhanced-resolve/-/enhanced-resolve-5.20.0.tgz",
|
||||
"integrity": "sha512-/ce7+jQ1PQ6rVXwe+jKEg5hW5ciicHwIQUagZkp6IufBoY3YDgdTTY1azVs0qoRgVmvsNB+rbjLJxDAeHHtwsQ==",
|
||||
"dev": true,
|
||||
"dependencies": {
|
||||
"graceful-fs": "^4.2.4",
|
||||
"tapable": "^2.3.0"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=10.13.0"
|
||||
}
|
||||
},
|
||||
"node_modules/graceful-fs": {
|
||||
"version": "4.2.11",
|
||||
"resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.2.11.tgz",
|
||||
"integrity": "sha512-RbJ5/jmFcNNCcDV5o9eTnBLJ/HszWV0P73bc+Ff4nS/rJj+YaS6IGyiOL0VoBYX+l1Wrl3k63h/KrH+nhJ0XvQ==",
|
||||
"dev": true
|
||||
},
|
||||
"node_modules/is-extglob": {
|
||||
"version": "2.1.1",
|
||||
"resolved": "https://registry.npmjs.org/is-extglob/-/is-extglob-2.1.1.tgz",
|
||||
"integrity": "sha512-SbKbANkN603Vi4jEZv49LeVJMn4yGwsbzZworEoyEiutsN3nJYdbO36zfhGJ6QEDpOZIFkDtnq5JRxmvl3jsoQ==",
|
||||
"dev": true,
|
||||
"engines": {
|
||||
"node": ">=0.10.0"
|
||||
}
|
||||
},
|
||||
"node_modules/is-glob": {
|
||||
"version": "4.0.3",
|
||||
"resolved": "https://registry.npmjs.org/is-glob/-/is-glob-4.0.3.tgz",
|
||||
"integrity": "sha512-xelSayHH36ZgE7ZWhli7pW34hNbNl8Ojv5KVmkJD4hBdD3th8Tfk9vYasLM+mXWOZhFkgZfxhLSnrwRr4elSSg==",
|
||||
"dev": true,
|
||||
"dependencies": {
|
||||
"is-extglob": "^2.1.1"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=0.10.0"
|
||||
}
|
||||
},
|
||||
"node_modules/jiti": {
|
||||
"version": "2.6.1",
|
||||
"resolved": "https://registry.npmjs.org/jiti/-/jiti-2.6.1.tgz",
|
||||
"integrity": "sha512-ekilCSN1jwRvIbgeg/57YFh8qQDNbwDb9xT/qu2DAHbFFZUicIl4ygVaAvzveMhMVr3LnpSKTNnwt8PoOfmKhQ==",
|
||||
"dev": true,
|
||||
"bin": {
|
||||
"jiti": "lib/jiti-cli.mjs"
|
||||
}
|
||||
},
|
||||
"node_modules/lightningcss": {
|
||||
"version": "1.31.1",
|
||||
"resolved": "https://registry.npmjs.org/lightningcss/-/lightningcss-1.31.1.tgz",
|
||||
"integrity": "sha512-l51N2r93WmGUye3WuFoN5k10zyvrVs0qfKBhyC5ogUQ6Ew6JUSswh78mbSO+IU3nTWsyOArqPCcShdQSadghBQ==",
|
||||
"dev": true,
|
||||
"dependencies": {
|
||||
"detect-libc": "^2.0.3"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">= 12.0.0"
|
||||
},
|
||||
"funding": {
|
||||
"type": "opencollective",
|
||||
"url": "https://opencollective.com/parcel"
|
||||
},
|
||||
"optionalDependencies": {
|
||||
"lightningcss-android-arm64": "1.31.1",
|
||||
"lightningcss-darwin-arm64": "1.31.1",
|
||||
"lightningcss-darwin-x64": "1.31.1",
|
||||
"lightningcss-freebsd-x64": "1.31.1",
|
||||
"lightningcss-linux-arm-gnueabihf": "1.31.1",
|
||||
"lightningcss-linux-arm64-gnu": "1.31.1",
|
||||
"lightningcss-linux-arm64-musl": "1.31.1",
|
||||
"lightningcss-linux-x64-gnu": "1.31.1",
|
||||
"lightningcss-linux-x64-musl": "1.31.1",
|
||||
"lightningcss-win32-arm64-msvc": "1.31.1",
|
||||
"lightningcss-win32-x64-msvc": "1.31.1"
|
||||
}
|
||||
},
|
||||
"node_modules/lightningcss-linux-x64-gnu": {
|
||||
"version": "1.31.1",
|
||||
"resolved": "https://registry.npmjs.org/lightningcss-linux-x64-gnu/-/lightningcss-linux-x64-gnu-1.31.1.tgz",
|
||||
"integrity": "sha512-xGlFWRMl+0KvUhgySdIaReQdB4FNudfUTARn7q0hh/V67PVGCs3ADFjw+6++kG1RNd0zdGRlEKa+T13/tQjPMA==",
|
||||
"cpu": [
|
||||
"x64"
|
||||
],
|
||||
"dev": true,
|
||||
"optional": true,
|
||||
"os": [
|
||||
"linux"
|
||||
],
|
||||
"engines": {
|
||||
"node": ">= 12.0.0"
|
||||
},
|
||||
"funding": {
|
||||
"type": "opencollective",
|
||||
"url": "https://opencollective.com/parcel"
|
||||
}
|
||||
},
|
||||
"node_modules/lightningcss-linux-x64-musl": {
|
||||
"version": "1.31.1",
|
||||
"resolved": "https://registry.npmjs.org/lightningcss-linux-x64-musl/-/lightningcss-linux-x64-musl-1.31.1.tgz",
|
||||
"integrity": "sha512-eowF8PrKHw9LpoZii5tdZwnBcYDxRw2rRCyvAXLi34iyeYfqCQNA9rmUM0ce62NlPhCvof1+9ivRaTY6pSKDaA==",
|
||||
"cpu": [
|
||||
"x64"
|
||||
],
|
||||
"dev": true,
|
||||
"optional": true,
|
||||
"os": [
|
||||
"linux"
|
||||
],
|
||||
"engines": {
|
||||
"node": ">= 12.0.0"
|
||||
},
|
||||
"funding": {
|
||||
"type": "opencollective",
|
||||
"url": "https://opencollective.com/parcel"
|
||||
}
|
||||
},
|
||||
"node_modules/magic-string": {
|
||||
"version": "0.30.21",
|
||||
"resolved": "https://registry.npmjs.org/magic-string/-/magic-string-0.30.21.tgz",
|
||||
"integrity": "sha512-vd2F4YUyEXKGcLHoq+TEyCjxueSeHnFxyyjNp80yg0XV4vUhnDer/lvvlqM/arB5bXQN5K2/3oinyCRyx8T2CQ==",
|
||||
"dev": true,
|
||||
"dependencies": {
|
||||
"@jridgewell/sourcemap-codec": "^1.5.5"
|
||||
}
|
||||
},
|
||||
"node_modules/mri": {
|
||||
"version": "1.2.0",
|
||||
"resolved": "https://registry.npmjs.org/mri/-/mri-1.2.0.tgz",
|
||||
"integrity": "sha512-tzzskb3bG8LvYGFF/mDTpq3jpI6Q9wc3LEmBaghu+DdCssd1FakN7Bc0hVNmEyGq1bq3RgfkCb3cmQLpNPOroA==",
|
||||
"dev": true,
|
||||
"engines": {
|
||||
"node": ">=4"
|
||||
}
|
||||
},
|
||||
"node_modules/node-addon-api": {
|
||||
"version": "7.1.1",
|
||||
"resolved": "https://registry.npmjs.org/node-addon-api/-/node-addon-api-7.1.1.tgz",
|
||||
"integrity": "sha512-5m3bsyrjFWE1xf7nz7YXdN4udnVtXK6/Yfgn5qnahL6bCkf2yKt4k3nuTKAtT4r3IG8JNR2ncsIMdZuAzJjHQQ==",
|
||||
"dev": true
|
||||
},
|
||||
"node_modules/picocolors": {
|
||||
"version": "1.1.1",
|
||||
"resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.1.1.tgz",
|
||||
"integrity": "sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA==",
|
||||
"dev": true
|
||||
},
|
||||
"node_modules/picomatch": {
|
||||
"version": "4.0.3",
|
||||
"resolved": "https://registry.npmjs.org/picomatch/-/picomatch-4.0.3.tgz",
|
||||
"integrity": "sha512-5gTmgEY/sqK6gFXLIsQNH19lWb4ebPDLA4SdLP7dsWkIXHWlG66oPuVvXSGFPppYZz8ZDZq0dYYrbHfBCVUb1Q==",
|
||||
"dev": true,
|
||||
"engines": {
|
||||
"node": ">=12"
|
||||
},
|
||||
"funding": {
|
||||
"url": "https://github.com/sponsors/jonschlinkert"
|
||||
}
|
||||
},
|
||||
"node_modules/source-map-js": {
|
||||
"version": "1.2.1",
|
||||
"resolved": "https://registry.npmjs.org/source-map-js/-/source-map-js-1.2.1.tgz",
|
||||
"integrity": "sha512-UXWMKhLOwVKb728IUtQPXxfYU+usdybtUrK/8uGE8CQMvrhOpwvzDBwj0QhSL7MQc7vIsISBG8VQ8+IDQxpfQA==",
|
||||
"dev": true,
|
||||
"engines": {
|
||||
"node": ">=0.10.0"
|
||||
}
|
||||
},
|
||||
"node_modules/tailwindcss": {
|
||||
"version": "4.2.1",
|
||||
"resolved": "https://registry.npmjs.org/tailwindcss/-/tailwindcss-4.2.1.tgz",
|
||||
"integrity": "sha512-/tBrSQ36vCleJkAOsy9kbNTgaxvGbyOamC30PRePTQe/o1MFwEKHQk4Cn7BNGaPtjp+PuUrByJehM1hgxfq4sw==",
|
||||
"dev": true
|
||||
},
|
||||
"node_modules/tapable": {
|
||||
"version": "2.3.0",
|
||||
"resolved": "https://registry.npmjs.org/tapable/-/tapable-2.3.0.tgz",
|
||||
"integrity": "sha512-g9ljZiwki/LfxmQADO3dEY1CbpmXT5Hm2fJ+QaGKwSXUylMybePR7/67YW7jOrrvjEgL1Fmz5kzyAjWVWLlucg==",
|
||||
"dev": true,
|
||||
"engines": {
|
||||
"node": ">=6"
|
||||
},
|
||||
"funding": {
|
||||
"type": "opencollective",
|
||||
"url": "https://opencollective.com/webpack"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
19
whm/gniza4cp-whm/assets/node_modules/@jridgewell/gen-mapping/LICENSE
generated
vendored
Normal file
@@ -0,0 +1,19 @@
|
||||
Copyright 2024 Justin Ridgewell <justin@ridgewell.name>
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
of this software and associated documentation files (the "Software"), to deal
|
||||
in the Software without restriction, including without limitation the rights
|
||||
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
copies of the Software, and to permit persons to whom the Software is
|
||||
furnished to do so, subject to the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be included in
|
||||
all copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
SOFTWARE.
|
||||
227
whm/gniza4cp-whm/assets/node_modules/@jridgewell/gen-mapping/README.md
generated
vendored
Normal file
@@ -0,0 +1,227 @@
|
||||
# @jridgewell/gen-mapping
|
||||
|
||||
> Generate source maps
|
||||
|
||||
`gen-mapping` allows you to generate a source map during transpilation or minification.
|
||||
With a source map, you're able to trace the original location in the source file, either in Chrome's
|
||||
DevTools or using a library like [`@jridgewell/trace-mapping`][trace-mapping].
|
||||
|
||||
You may already be familiar with the [`source-map`][source-map] package's `SourceMapGenerator`. This
|
||||
provides the same `addMapping` and `setSourceContent` API.
|
||||
|
||||
## Installation
|
||||
|
||||
```sh
|
||||
npm install @jridgewell/gen-mapping
|
||||
```
|
||||
|
||||
## Usage
|
||||
|
||||
```typescript
|
||||
import { GenMapping, addMapping, setSourceContent, toEncodedMap, toDecodedMap } from '@jridgewell/gen-mapping';
|
||||
|
||||
const map = new GenMapping({
|
||||
file: 'output.js',
|
||||
sourceRoot: 'https://example.com/',
|
||||
});
|
||||
|
||||
setSourceContent(map, 'input.js', `function foo() {}`);
|
||||
|
||||
addMapping(map, {
|
||||
// Lines start at line 1, columns at column 0.
|
||||
generated: { line: 1, column: 0 },
|
||||
source: 'input.js',
|
||||
original: { line: 1, column: 0 },
|
||||
});
|
||||
|
||||
addMapping(map, {
|
||||
generated: { line: 1, column: 9 },
|
||||
source: 'input.js',
|
||||
original: { line: 1, column: 9 },
|
||||
name: 'foo',
|
||||
});
|
||||
|
||||
assert.deepEqual(toDecodedMap(map), {
|
||||
version: 3,
|
||||
file: 'output.js',
|
||||
names: ['foo'],
|
||||
sourceRoot: 'https://example.com/',
|
||||
sources: ['input.js'],
|
||||
sourcesContent: ['function foo() {}'],
|
||||
mappings: [
|
||||
[ [0, 0, 0, 0], [9, 0, 0, 9, 0] ]
|
||||
],
|
||||
});
|
||||
|
||||
assert.deepEqual(toEncodedMap(map), {
|
||||
version: 3,
|
||||
file: 'output.js',
|
||||
names: ['foo'],
|
||||
sourceRoot: 'https://example.com/',
|
||||
sources: ['input.js'],
|
||||
sourcesContent: ['function foo() {}'],
|
||||
mappings: 'AAAA,SAASA',
|
||||
});
|
||||
```
|
||||
|
||||
### Smaller Sourcemaps
|
||||
|
||||
Not everything needs to be added to a sourcemap, and needless markings can cause signficantly
|
||||
larger file sizes. `gen-mapping` exposes `maybeAddSegment`/`maybeAddMapping` APIs that will
|
||||
intelligently determine if this marking adds useful information. If not, the marking will be
|
||||
skipped.
|
||||
|
||||
```typescript
|
||||
import { maybeAddMapping } from '@jridgewell/gen-mapping';
|
||||
|
||||
const map = new GenMapping();
|
||||
|
||||
// Adding a sourceless marking at the beginning of a line isn't useful.
|
||||
maybeAddMapping(map, {
|
||||
generated: { line: 1, column: 0 },
|
||||
});
|
||||
|
||||
// Adding a new source marking is useful.
|
||||
maybeAddMapping(map, {
|
||||
generated: { line: 1, column: 0 },
|
||||
source: 'input.js',
|
||||
original: { line: 1, column: 0 },
|
||||
});
|
||||
|
||||
// But adding another marking pointing to the exact same original location isn't, even if the
|
||||
// generated column changed.
|
||||
maybeAddMapping(map, {
|
||||
generated: { line: 1, column: 9 },
|
||||
source: 'input.js',
|
||||
original: { line: 1, column: 0 },
|
||||
});
|
||||
|
||||
assert.deepEqual(toEncodedMap(map), {
|
||||
version: 3,
|
||||
names: [],
|
||||
sources: ['input.js'],
|
||||
sourcesContent: [null],
|
||||
mappings: 'AAAA',
|
||||
});
|
||||
```
|
||||
|
||||
## Benchmarks
|
||||
|
||||
```
|
||||
node v18.0.0
|
||||
|
||||
amp.js.map
|
||||
Memory Usage:
|
||||
gen-mapping: addSegment 5852872 bytes
|
||||
gen-mapping: addMapping 7716042 bytes
|
||||
source-map-js 6143250 bytes
|
||||
source-map-0.6.1 6124102 bytes
|
||||
source-map-0.8.0 6121173 bytes
|
||||
Smallest memory usage is gen-mapping: addSegment
|
||||
|
||||
Adding speed:
|
||||
gen-mapping: addSegment x 441 ops/sec ±2.07% (90 runs sampled)
|
||||
gen-mapping: addMapping x 350 ops/sec ±2.40% (86 runs sampled)
|
||||
source-map-js: addMapping x 169 ops/sec ±2.42% (80 runs sampled)
|
||||
source-map-0.6.1: addMapping x 167 ops/sec ±2.56% (80 runs sampled)
|
||||
source-map-0.8.0: addMapping x 168 ops/sec ±2.52% (80 runs sampled)
|
||||
Fastest is gen-mapping: addSegment
|
||||
|
||||
Generate speed:
|
||||
gen-mapping: decoded output x 150,824,370 ops/sec ±0.07% (102 runs sampled)
|
||||
gen-mapping: encoded output x 663 ops/sec ±0.22% (98 runs sampled)
|
||||
source-map-js: encoded output x 197 ops/sec ±0.45% (84 runs sampled)
|
||||
source-map-0.6.1: encoded output x 198 ops/sec ±0.33% (85 runs sampled)
|
||||
source-map-0.8.0: encoded output x 197 ops/sec ±0.06% (93 runs sampled)
|
||||
Fastest is gen-mapping: decoded output
|
||||
|
||||
|
||||
***
|
||||
|
||||
|
||||
babel.min.js.map
|
||||
Memory Usage:
|
||||
gen-mapping: addSegment 37578063 bytes
|
||||
gen-mapping: addMapping 37212897 bytes
|
||||
source-map-js 47638527 bytes
|
||||
source-map-0.6.1 47690503 bytes
|
||||
source-map-0.8.0 47470188 bytes
|
||||
Smallest memory usage is gen-mapping: addMapping
|
||||
|
||||
Adding speed:
|
||||
gen-mapping: addSegment x 31.05 ops/sec ±8.31% (43 runs sampled)
|
||||
gen-mapping: addMapping x 29.83 ops/sec ±7.36% (51 runs sampled)
|
||||
source-map-js: addMapping x 20.73 ops/sec ±6.22% (38 runs sampled)
|
||||
source-map-0.6.1: addMapping x 20.03 ops/sec ±10.51% (38 runs sampled)
|
||||
source-map-0.8.0: addMapping x 19.30 ops/sec ±8.27% (37 runs sampled)
|
||||
Fastest is gen-mapping: addSegment
|
||||
|
||||
Generate speed:
|
||||
gen-mapping: decoded output x 381,379,234 ops/sec ±0.29% (96 runs sampled)
|
||||
gen-mapping: encoded output x 95.15 ops/sec ±2.98% (72 runs sampled)
|
||||
source-map-js: encoded output x 15.20 ops/sec ±7.41% (33 runs sampled)
|
||||
source-map-0.6.1: encoded output x 16.36 ops/sec ±10.46% (31 runs sampled)
|
||||
source-map-0.8.0: encoded output x 16.06 ops/sec ±6.45% (31 runs sampled)
|
||||
Fastest is gen-mapping: decoded output
|
||||
|
||||
|
||||
***
|
||||
|
||||
|
||||
preact.js.map
|
||||
Memory Usage:
|
||||
gen-mapping: addSegment 416247 bytes
|
||||
gen-mapping: addMapping 419824 bytes
|
||||
source-map-js 1024619 bytes
|
||||
source-map-0.6.1 1146004 bytes
|
||||
source-map-0.8.0 1113250 bytes
|
||||
Smallest memory usage is gen-mapping: addSegment
|
||||
|
||||
Adding speed:
|
||||
gen-mapping: addSegment x 13,755 ops/sec ±0.15% (98 runs sampled)
|
||||
gen-mapping: addMapping x 13,013 ops/sec ±0.11% (101 runs sampled)
|
||||
source-map-js: addMapping x 4,564 ops/sec ±0.21% (98 runs sampled)
|
||||
source-map-0.6.1: addMapping x 4,562 ops/sec ±0.11% (99 runs sampled)
|
||||
source-map-0.8.0: addMapping x 4,593 ops/sec ±0.11% (100 runs sampled)
|
||||
Fastest is gen-mapping: addSegment
|
||||
|
||||
Generate speed:
|
||||
gen-mapping: decoded output x 379,864,020 ops/sec ±0.23% (93 runs sampled)
|
||||
gen-mapping: encoded output x 14,368 ops/sec ±4.07% (82 runs sampled)
|
||||
source-map-js: encoded output x 5,261 ops/sec ±0.21% (99 runs sampled)
|
||||
source-map-0.6.1: encoded output x 5,124 ops/sec ±0.58% (99 runs sampled)
|
||||
source-map-0.8.0: encoded output x 5,434 ops/sec ±0.33% (96 runs sampled)
|
||||
Fastest is gen-mapping: decoded output
|
||||
|
||||
|
||||
***
|
||||
|
||||
|
||||
react.js.map
|
||||
Memory Usage:
|
||||
gen-mapping: addSegment 975096 bytes
|
||||
gen-mapping: addMapping 1102981 bytes
|
||||
source-map-js 2918836 bytes
|
||||
source-map-0.6.1 2885435 bytes
|
||||
source-map-0.8.0 2874336 bytes
|
||||
Smallest memory usage is gen-mapping: addSegment
|
||||
|
||||
Adding speed:
|
||||
gen-mapping: addSegment x 4,772 ops/sec ±0.15% (100 runs sampled)
|
||||
gen-mapping: addMapping x 4,456 ops/sec ±0.13% (97 runs sampled)
|
||||
source-map-js: addMapping x 1,618 ops/sec ±0.24% (97 runs sampled)
|
||||
source-map-0.6.1: addMapping x 1,622 ops/sec ±0.12% (99 runs sampled)
|
||||
source-map-0.8.0: addMapping x 1,631 ops/sec ±0.12% (100 runs sampled)
|
||||
Fastest is gen-mapping: addSegment
|
||||
|
||||
Generate speed:
|
||||
gen-mapping: decoded output x 379,107,695 ops/sec ±0.07% (99 runs sampled)
|
||||
gen-mapping: encoded output x 5,421 ops/sec ±1.60% (89 runs sampled)
|
||||
source-map-js: encoded output x 2,113 ops/sec ±1.81% (98 runs sampled)
|
||||
source-map-0.6.1: encoded output x 2,126 ops/sec ±0.10% (100 runs sampled)
|
||||
source-map-0.8.0: encoded output x 2,176 ops/sec ±0.39% (98 runs sampled)
|
||||
Fastest is gen-mapping: decoded output
|
||||
```
|
||||
|
||||
[source-map]: https://www.npmjs.com/package/source-map
|
||||
[trace-mapping]: https://github.com/jridgewell/sourcemaps/tree/main/packages/trace-mapping
|
||||
292
whm/gniza4cp-whm/assets/node_modules/@jridgewell/gen-mapping/dist/gen-mapping.mjs
generated
vendored
Normal file
@@ -0,0 +1,292 @@
|
||||
// src/set-array.ts
|
||||
var SetArray = class {
|
||||
constructor() {
|
||||
this._indexes = { __proto__: null };
|
||||
this.array = [];
|
||||
}
|
||||
};
|
||||
function cast(set) {
|
||||
return set;
|
||||
}
|
||||
function get(setarr, key) {
|
||||
return cast(setarr)._indexes[key];
|
||||
}
|
||||
function put(setarr, key) {
|
||||
const index = get(setarr, key);
|
||||
if (index !== void 0) return index;
|
||||
const { array, _indexes: indexes } = cast(setarr);
|
||||
const length = array.push(key);
|
||||
return indexes[key] = length - 1;
|
||||
}
|
||||
function remove(setarr, key) {
|
||||
const index = get(setarr, key);
|
||||
if (index === void 0) return;
|
||||
const { array, _indexes: indexes } = cast(setarr);
|
||||
for (let i = index + 1; i < array.length; i++) {
|
||||
const k = array[i];
|
||||
array[i - 1] = k;
|
||||
indexes[k]--;
|
||||
}
|
||||
indexes[key] = void 0;
|
||||
array.pop();
|
||||
}
|
||||
|
||||
// src/gen-mapping.ts
|
||||
import {
|
||||
encode
|
||||
} from "@jridgewell/sourcemap-codec";
|
||||
import { TraceMap, decodedMappings } from "@jridgewell/trace-mapping";
|
||||
|
||||
// src/sourcemap-segment.ts
|
||||
var COLUMN = 0;
|
||||
var SOURCES_INDEX = 1;
|
||||
var SOURCE_LINE = 2;
|
||||
var SOURCE_COLUMN = 3;
|
||||
var NAMES_INDEX = 4;
|
||||
|
||||
// src/gen-mapping.ts
|
||||
var NO_NAME = -1;
|
||||
var GenMapping = class {
|
||||
constructor({ file, sourceRoot } = {}) {
|
||||
this._names = new SetArray();
|
||||
this._sources = new SetArray();
|
||||
this._sourcesContent = [];
|
||||
this._mappings = [];
|
||||
this.file = file;
|
||||
this.sourceRoot = sourceRoot;
|
||||
this._ignoreList = new SetArray();
|
||||
}
|
||||
};
|
||||
function cast2(map) {
|
||||
return map;
|
||||
}
|
||||
function addSegment(map, genLine, genColumn, source, sourceLine, sourceColumn, name, content) {
|
||||
return addSegmentInternal(
|
||||
false,
|
||||
map,
|
||||
genLine,
|
||||
genColumn,
|
||||
source,
|
||||
sourceLine,
|
||||
sourceColumn,
|
||||
name,
|
||||
content
|
||||
);
|
||||
}
|
||||
function addMapping(map, mapping) {
|
||||
return addMappingInternal(false, map, mapping);
|
||||
}
|
||||
var maybeAddSegment = (map, genLine, genColumn, source, sourceLine, sourceColumn, name, content) => {
|
||||
return addSegmentInternal(
|
||||
true,
|
||||
map,
|
||||
genLine,
|
||||
genColumn,
|
||||
source,
|
||||
sourceLine,
|
||||
sourceColumn,
|
||||
name,
|
||||
content
|
||||
);
|
||||
};
|
||||
var maybeAddMapping = (map, mapping) => {
|
||||
return addMappingInternal(true, map, mapping);
|
||||
};
|
||||
function setSourceContent(map, source, content) {
|
||||
const {
|
||||
_sources: sources,
|
||||
_sourcesContent: sourcesContent
|
||||
// _originalScopes: originalScopes,
|
||||
} = cast2(map);
|
||||
const index = put(sources, source);
|
||||
sourcesContent[index] = content;
|
||||
}
|
||||
function setIgnore(map, source, ignore = true) {
|
||||
const {
|
||||
_sources: sources,
|
||||
_sourcesContent: sourcesContent,
|
||||
_ignoreList: ignoreList
|
||||
// _originalScopes: originalScopes,
|
||||
} = cast2(map);
|
||||
const index = put(sources, source);
|
||||
if (index === sourcesContent.length) sourcesContent[index] = null;
|
||||
if (ignore) put(ignoreList, index);
|
||||
else remove(ignoreList, index);
|
||||
}
|
||||
function toDecodedMap(map) {
|
||||
const {
|
||||
_mappings: mappings,
|
||||
_sources: sources,
|
||||
_sourcesContent: sourcesContent,
|
||||
_names: names,
|
||||
_ignoreList: ignoreList
|
||||
// _originalScopes: originalScopes,
|
||||
// _generatedRanges: generatedRanges,
|
||||
} = cast2(map);
|
||||
removeEmptyFinalLines(mappings);
|
||||
return {
|
||||
version: 3,
|
||||
file: map.file || void 0,
|
||||
names: names.array,
|
||||
sourceRoot: map.sourceRoot || void 0,
|
||||
sources: sources.array,
|
||||
sourcesContent,
|
||||
mappings,
|
||||
// originalScopes,
|
||||
// generatedRanges,
|
||||
ignoreList: ignoreList.array
|
||||
};
|
||||
}
|
||||
function toEncodedMap(map) {
|
||||
const decoded = toDecodedMap(map);
|
||||
return Object.assign({}, decoded, {
|
||||
// originalScopes: decoded.originalScopes.map((os) => encodeOriginalScopes(os)),
|
||||
// generatedRanges: encodeGeneratedRanges(decoded.generatedRanges as GeneratedRange[]),
|
||||
mappings: encode(decoded.mappings)
|
||||
});
|
||||
}
|
||||
function fromMap(input) {
|
||||
const map = new TraceMap(input);
|
||||
const gen = new GenMapping({ file: map.file, sourceRoot: map.sourceRoot });
|
||||
putAll(cast2(gen)._names, map.names);
|
||||
putAll(cast2(gen)._sources, map.sources);
|
||||
cast2(gen)._sourcesContent = map.sourcesContent || map.sources.map(() => null);
|
||||
cast2(gen)._mappings = decodedMappings(map);
|
||||
if (map.ignoreList) putAll(cast2(gen)._ignoreList, map.ignoreList);
|
||||
return gen;
|
||||
}
|
||||
function allMappings(map) {
|
||||
const out = [];
|
||||
const { _mappings: mappings, _sources: sources, _names: names } = cast2(map);
|
||||
for (let i = 0; i < mappings.length; i++) {
|
||||
const line = mappings[i];
|
||||
for (let j = 0; j < line.length; j++) {
|
||||
const seg = line[j];
|
||||
const generated = { line: i + 1, column: seg[COLUMN] };
|
||||
let source = void 0;
|
||||
let original = void 0;
|
||||
let name = void 0;
|
||||
if (seg.length !== 1) {
|
||||
source = sources.array[seg[SOURCES_INDEX]];
|
||||
original = { line: seg[SOURCE_LINE] + 1, column: seg[SOURCE_COLUMN] };
|
||||
if (seg.length === 5) name = names.array[seg[NAMES_INDEX]];
|
||||
}
|
||||
out.push({ generated, source, original, name });
|
||||
}
|
||||
}
|
||||
return out;
|
||||
}
|
||||
function addSegmentInternal(skipable, map, genLine, genColumn, source, sourceLine, sourceColumn, name, content) {
|
||||
const {
|
||||
_mappings: mappings,
|
||||
_sources: sources,
|
||||
_sourcesContent: sourcesContent,
|
||||
_names: names
|
||||
// _originalScopes: originalScopes,
|
||||
} = cast2(map);
|
||||
const line = getIndex(mappings, genLine);
|
||||
const index = getColumnIndex(line, genColumn);
|
||||
if (!source) {
|
||||
if (skipable && skipSourceless(line, index)) return;
|
||||
return insert(line, index, [genColumn]);
|
||||
}
|
||||
assert(sourceLine);
|
||||
assert(sourceColumn);
|
||||
const sourcesIndex = put(sources, source);
|
||||
const namesIndex = name ? put(names, name) : NO_NAME;
|
||||
if (sourcesIndex === sourcesContent.length) sourcesContent[sourcesIndex] = content != null ? content : null;
|
||||
if (skipable && skipSource(line, index, sourcesIndex, sourceLine, sourceColumn, namesIndex)) {
|
||||
return;
|
||||
}
|
||||
return insert(
|
||||
line,
|
||||
index,
|
||||
name ? [genColumn, sourcesIndex, sourceLine, sourceColumn, namesIndex] : [genColumn, sourcesIndex, sourceLine, sourceColumn]
|
||||
);
|
||||
}
|
||||
function assert(_val) {
|
||||
}
|
||||
function getIndex(arr, index) {
|
||||
for (let i = arr.length; i <= index; i++) {
|
||||
arr[i] = [];
|
||||
}
|
||||
return arr[index];
|
||||
}
|
||||
function getColumnIndex(line, genColumn) {
|
||||
let index = line.length;
|
||||
for (let i = index - 1; i >= 0; index = i--) {
|
||||
const current = line[i];
|
||||
if (genColumn >= current[COLUMN]) break;
|
||||
}
|
||||
return index;
|
||||
}
|
||||
function insert(array, index, value) {
|
||||
for (let i = array.length; i > index; i--) {
|
||||
array[i] = array[i - 1];
|
||||
}
|
||||
array[index] = value;
|
||||
}
|
||||
function removeEmptyFinalLines(mappings) {
|
||||
const { length } = mappings;
|
||||
let len = length;
|
||||
for (let i = len - 1; i >= 0; len = i, i--) {
|
||||
if (mappings[i].length > 0) break;
|
||||
}
|
||||
if (len < length) mappings.length = len;
|
||||
}
|
||||
function putAll(setarr, array) {
|
||||
for (let i = 0; i < array.length; i++) put(setarr, array[i]);
|
||||
}
|
||||
function skipSourceless(line, index) {
|
||||
if (index === 0) return true;
|
||||
const prev = line[index - 1];
|
||||
return prev.length === 1;
|
||||
}
|
||||
function skipSource(line, index, sourcesIndex, sourceLine, sourceColumn, namesIndex) {
|
||||
if (index === 0) return false;
|
||||
const prev = line[index - 1];
|
||||
if (prev.length === 1) return false;
|
||||
return sourcesIndex === prev[SOURCES_INDEX] && sourceLine === prev[SOURCE_LINE] && sourceColumn === prev[SOURCE_COLUMN] && namesIndex === (prev.length === 5 ? prev[NAMES_INDEX] : NO_NAME);
|
||||
}
|
||||
function addMappingInternal(skipable, map, mapping) {
|
||||
const { generated, source, original, name, content } = mapping;
|
||||
if (!source) {
|
||||
return addSegmentInternal(
|
||||
skipable,
|
||||
map,
|
||||
generated.line - 1,
|
||||
generated.column,
|
||||
null,
|
||||
null,
|
||||
null,
|
||||
null,
|
||||
null
|
||||
);
|
||||
}
|
||||
assert(original);
|
||||
return addSegmentInternal(
|
||||
skipable,
|
||||
map,
|
||||
generated.line - 1,
|
||||
generated.column,
|
||||
source,
|
||||
original.line - 1,
|
||||
original.column,
|
||||
name,
|
||||
content
|
||||
);
|
||||
}
|
||||
export {
|
||||
GenMapping,
|
||||
addMapping,
|
||||
addSegment,
|
||||
allMappings,
|
||||
fromMap,
|
||||
maybeAddMapping,
|
||||
maybeAddSegment,
|
||||
setIgnore,
|
||||
setSourceContent,
|
||||
toDecodedMap,
|
||||
toEncodedMap
|
||||
};
|
||||
//# sourceMappingURL=gen-mapping.mjs.map
|
||||
6
whm/gniza4cp-whm/assets/node_modules/@jridgewell/gen-mapping/dist/gen-mapping.mjs.map
generated
vendored
Normal file
358
whm/gniza4cp-whm/assets/node_modules/@jridgewell/gen-mapping/dist/gen-mapping.umd.js
generated
vendored
Normal file
@@ -0,0 +1,358 @@
|
||||
(function (global, factory) {
|
||||
if (typeof exports === 'object' && typeof module !== 'undefined') {
|
||||
factory(module, require('@jridgewell/sourcemap-codec'), require('@jridgewell/trace-mapping'));
|
||||
module.exports = def(module);
|
||||
} else if (typeof define === 'function' && define.amd) {
|
||||
define(['module', '@jridgewell/sourcemap-codec', '@jridgewell/trace-mapping'], function(mod) {
|
||||
factory.apply(this, arguments);
|
||||
mod.exports = def(mod);
|
||||
});
|
||||
} else {
|
||||
const mod = { exports: {} };
|
||||
factory(mod, global.sourcemapCodec, global.traceMapping);
|
||||
global = typeof globalThis !== 'undefined' ? globalThis : global || self;
|
||||
global.genMapping = def(mod);
|
||||
}
|
||||
function def(m) { return 'default' in m.exports ? m.exports.default : m.exports; }
|
||||
})(this, (function (module, require_sourcemapCodec, require_traceMapping) {
|
||||
"use strict";
|
||||
var __create = Object.create;
|
||||
var __defProp = Object.defineProperty;
|
||||
var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
|
||||
var __getOwnPropNames = Object.getOwnPropertyNames;
|
||||
var __getProtoOf = Object.getPrototypeOf;
|
||||
var __hasOwnProp = Object.prototype.hasOwnProperty;
|
||||
var __commonJS = (cb, mod) => function __require() {
|
||||
return mod || (0, cb[__getOwnPropNames(cb)[0]])((mod = { exports: {} }).exports, mod), mod.exports;
|
||||
};
|
||||
var __export = (target, all) => {
|
||||
for (var name in all)
|
||||
__defProp(target, name, { get: all[name], enumerable: true });
|
||||
};
|
||||
var __copyProps = (to, from, except, desc) => {
|
||||
if (from && typeof from === "object" || typeof from === "function") {
|
||||
for (let key of __getOwnPropNames(from))
|
||||
if (!__hasOwnProp.call(to, key) && key !== except)
|
||||
__defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
|
||||
}
|
||||
return to;
|
||||
};
|
||||
var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__getProtoOf(mod)) : {}, __copyProps(
|
||||
// If the importer is in node compatibility mode or this is not an ESM
|
||||
// file that has been converted to a CommonJS file using a Babel-
|
||||
// compatible transform (i.e. "__esModule" has not been set), then set
|
||||
// "default" to the CommonJS "module.exports" for node compatibility.
|
||||
isNodeMode || !mod || !mod.__esModule ? __defProp(target, "default", { value: mod, enumerable: true }) : target,
|
||||
mod
|
||||
));
|
||||
var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
|
||||
|
||||
// umd:@jridgewell/sourcemap-codec
|
||||
var require_sourcemap_codec = __commonJS({
|
||||
"umd:@jridgewell/sourcemap-codec"(exports, module2) {
|
||||
module2.exports = require_sourcemapCodec;
|
||||
}
|
||||
});
|
||||
|
||||
// umd:@jridgewell/trace-mapping
|
||||
var require_trace_mapping = __commonJS({
|
||||
"umd:@jridgewell/trace-mapping"(exports, module2) {
|
||||
module2.exports = require_traceMapping;
|
||||
}
|
||||
});
|
||||
|
||||
// src/gen-mapping.ts
|
||||
var gen_mapping_exports = {};
|
||||
__export(gen_mapping_exports, {
|
||||
GenMapping: () => GenMapping,
|
||||
addMapping: () => addMapping,
|
||||
addSegment: () => addSegment,
|
||||
allMappings: () => allMappings,
|
||||
fromMap: () => fromMap,
|
||||
maybeAddMapping: () => maybeAddMapping,
|
||||
maybeAddSegment: () => maybeAddSegment,
|
||||
setIgnore: () => setIgnore,
|
||||
setSourceContent: () => setSourceContent,
|
||||
toDecodedMap: () => toDecodedMap,
|
||||
toEncodedMap: () => toEncodedMap
|
||||
});
|
||||
module.exports = __toCommonJS(gen_mapping_exports);
|
||||
|
||||
// src/set-array.ts
|
||||
var SetArray = class {
|
||||
constructor() {
|
||||
this._indexes = { __proto__: null };
|
||||
this.array = [];
|
||||
}
|
||||
};
|
||||
function cast(set) {
|
||||
return set;
|
||||
}
|
||||
function get(setarr, key) {
|
||||
return cast(setarr)._indexes[key];
|
||||
}
|
||||
function put(setarr, key) {
|
||||
const index = get(setarr, key);
|
||||
if (index !== void 0) return index;
|
||||
const { array, _indexes: indexes } = cast(setarr);
|
||||
const length = array.push(key);
|
||||
return indexes[key] = length - 1;
|
||||
}
|
||||
function remove(setarr, key) {
|
||||
const index = get(setarr, key);
|
||||
if (index === void 0) return;
|
||||
const { array, _indexes: indexes } = cast(setarr);
|
||||
for (let i = index + 1; i < array.length; i++) {
|
||||
const k = array[i];
|
||||
array[i - 1] = k;
|
||||
indexes[k]--;
|
||||
}
|
||||
indexes[key] = void 0;
|
||||
array.pop();
|
||||
}
|
||||
|
||||
// src/gen-mapping.ts
|
||||
var import_sourcemap_codec = __toESM(require_sourcemap_codec());
|
||||
var import_trace_mapping = __toESM(require_trace_mapping());
|
||||
|
||||
// src/sourcemap-segment.ts
|
||||
var COLUMN = 0;
|
||||
var SOURCES_INDEX = 1;
|
||||
var SOURCE_LINE = 2;
|
||||
var SOURCE_COLUMN = 3;
|
||||
var NAMES_INDEX = 4;
|
||||
|
||||
// src/gen-mapping.ts
|
||||
var NO_NAME = -1;
|
||||
var GenMapping = class {
|
||||
constructor({ file, sourceRoot } = {}) {
|
||||
this._names = new SetArray();
|
||||
this._sources = new SetArray();
|
||||
this._sourcesContent = [];
|
||||
this._mappings = [];
|
||||
this.file = file;
|
||||
this.sourceRoot = sourceRoot;
|
||||
this._ignoreList = new SetArray();
|
||||
}
|
||||
};
|
||||
function cast2(map) {
|
||||
return map;
|
||||
}
|
||||
function addSegment(map, genLine, genColumn, source, sourceLine, sourceColumn, name, content) {
|
||||
return addSegmentInternal(
|
||||
false,
|
||||
map,
|
||||
genLine,
|
||||
genColumn,
|
||||
source,
|
||||
sourceLine,
|
||||
sourceColumn,
|
||||
name,
|
||||
content
|
||||
);
|
||||
}
|
||||
function addMapping(map, mapping) {
|
||||
return addMappingInternal(false, map, mapping);
|
||||
}
|
||||
var maybeAddSegment = (map, genLine, genColumn, source, sourceLine, sourceColumn, name, content) => {
|
||||
return addSegmentInternal(
|
||||
true,
|
||||
map,
|
||||
genLine,
|
||||
genColumn,
|
||||
source,
|
||||
sourceLine,
|
||||
sourceColumn,
|
||||
name,
|
||||
content
|
||||
);
|
||||
};
|
||||
var maybeAddMapping = (map, mapping) => {
|
||||
return addMappingInternal(true, map, mapping);
|
||||
};
|
||||
function setSourceContent(map, source, content) {
|
||||
const {
|
||||
_sources: sources,
|
||||
_sourcesContent: sourcesContent
|
||||
// _originalScopes: originalScopes,
|
||||
} = cast2(map);
|
||||
const index = put(sources, source);
|
||||
sourcesContent[index] = content;
|
||||
}
|
||||
function setIgnore(map, source, ignore = true) {
|
||||
const {
|
||||
_sources: sources,
|
||||
_sourcesContent: sourcesContent,
|
||||
_ignoreList: ignoreList
|
||||
// _originalScopes: originalScopes,
|
||||
} = cast2(map);
|
||||
const index = put(sources, source);
|
||||
if (index === sourcesContent.length) sourcesContent[index] = null;
|
||||
if (ignore) put(ignoreList, index);
|
||||
else remove(ignoreList, index);
|
||||
}
|
||||
function toDecodedMap(map) {
|
||||
const {
|
||||
_mappings: mappings,
|
||||
_sources: sources,
|
||||
_sourcesContent: sourcesContent,
|
||||
_names: names,
|
||||
_ignoreList: ignoreList
|
||||
// _originalScopes: originalScopes,
|
||||
// _generatedRanges: generatedRanges,
|
||||
} = cast2(map);
|
||||
removeEmptyFinalLines(mappings);
|
||||
return {
|
||||
version: 3,
|
||||
file: map.file || void 0,
|
||||
names: names.array,
|
||||
sourceRoot: map.sourceRoot || void 0,
|
||||
sources: sources.array,
|
||||
sourcesContent,
|
||||
mappings,
|
||||
// originalScopes,
|
||||
// generatedRanges,
|
||||
ignoreList: ignoreList.array
|
||||
};
|
||||
}
|
||||
function toEncodedMap(map) {
|
||||
const decoded = toDecodedMap(map);
|
||||
return Object.assign({}, decoded, {
|
||||
// originalScopes: decoded.originalScopes.map((os) => encodeOriginalScopes(os)),
|
||||
// generatedRanges: encodeGeneratedRanges(decoded.generatedRanges as GeneratedRange[]),
|
||||
mappings: (0, import_sourcemap_codec.encode)(decoded.mappings)
|
||||
});
|
||||
}
|
||||
function fromMap(input) {
|
||||
const map = new import_trace_mapping.TraceMap(input);
|
||||
const gen = new GenMapping({ file: map.file, sourceRoot: map.sourceRoot });
|
||||
putAll(cast2(gen)._names, map.names);
|
||||
putAll(cast2(gen)._sources, map.sources);
|
||||
cast2(gen)._sourcesContent = map.sourcesContent || map.sources.map(() => null);
|
||||
cast2(gen)._mappings = (0, import_trace_mapping.decodedMappings)(map);
|
||||
if (map.ignoreList) putAll(cast2(gen)._ignoreList, map.ignoreList);
|
||||
return gen;
|
||||
}
|
||||
function allMappings(map) {
|
||||
const out = [];
|
||||
const { _mappings: mappings, _sources: sources, _names: names } = cast2(map);
|
||||
for (let i = 0; i < mappings.length; i++) {
|
||||
const line = mappings[i];
|
||||
for (let j = 0; j < line.length; j++) {
|
||||
const seg = line[j];
|
||||
const generated = { line: i + 1, column: seg[COLUMN] };
|
||||
let source = void 0;
|
||||
let original = void 0;
|
||||
let name = void 0;
|
||||
if (seg.length !== 1) {
|
||||
source = sources.array[seg[SOURCES_INDEX]];
|
||||
original = { line: seg[SOURCE_LINE] + 1, column: seg[SOURCE_COLUMN] };
|
||||
if (seg.length === 5) name = names.array[seg[NAMES_INDEX]];
|
||||
}
|
||||
out.push({ generated, source, original, name });
|
||||
}
|
||||
}
|
||||
return out;
|
||||
}
|
||||
function addSegmentInternal(skipable, map, genLine, genColumn, source, sourceLine, sourceColumn, name, content) {
|
||||
const {
|
||||
_mappings: mappings,
|
||||
_sources: sources,
|
||||
_sourcesContent: sourcesContent,
|
||||
_names: names
|
||||
// _originalScopes: originalScopes,
|
||||
} = cast2(map);
|
||||
const line = getIndex(mappings, genLine);
|
||||
const index = getColumnIndex(line, genColumn);
|
||||
if (!source) {
|
||||
if (skipable && skipSourceless(line, index)) return;
|
||||
return insert(line, index, [genColumn]);
|
||||
}
|
||||
assert(sourceLine);
|
||||
assert(sourceColumn);
|
||||
const sourcesIndex = put(sources, source);
|
||||
const namesIndex = name ? put(names, name) : NO_NAME;
|
||||
if (sourcesIndex === sourcesContent.length) sourcesContent[sourcesIndex] = content != null ? content : null;
|
||||
if (skipable && skipSource(line, index, sourcesIndex, sourceLine, sourceColumn, namesIndex)) {
|
||||
return;
|
||||
}
|
||||
return insert(
|
||||
line,
|
||||
index,
|
||||
name ? [genColumn, sourcesIndex, sourceLine, sourceColumn, namesIndex] : [genColumn, sourcesIndex, sourceLine, sourceColumn]
|
||||
);
|
||||
}
|
||||
function assert(_val) {
|
||||
}
|
||||
function getIndex(arr, index) {
|
||||
for (let i = arr.length; i <= index; i++) {
|
||||
arr[i] = [];
|
||||
}
|
||||
return arr[index];
|
||||
}
|
||||
function getColumnIndex(line, genColumn) {
|
||||
let index = line.length;
|
||||
for (let i = index - 1; i >= 0; index = i--) {
|
||||
const current = line[i];
|
||||
if (genColumn >= current[COLUMN]) break;
|
||||
}
|
||||
return index;
|
||||
}
|
||||
function insert(array, index, value) {
|
||||
for (let i = array.length; i > index; i--) {
|
||||
array[i] = array[i - 1];
|
||||
}
|
||||
array[index] = value;
|
||||
}
|
||||
function removeEmptyFinalLines(mappings) {
|
||||
const { length } = mappings;
|
||||
let len = length;
|
||||
for (let i = len - 1; i >= 0; len = i, i--) {
|
||||
if (mappings[i].length > 0) break;
|
||||
}
|
||||
if (len < length) mappings.length = len;
|
||||
}
|
||||
function putAll(setarr, array) {
|
||||
for (let i = 0; i < array.length; i++) put(setarr, array[i]);
|
||||
}
|
||||
function skipSourceless(line, index) {
|
||||
if (index === 0) return true;
|
||||
const prev = line[index - 1];
|
||||
return prev.length === 1;
|
||||
}
|
||||
function skipSource(line, index, sourcesIndex, sourceLine, sourceColumn, namesIndex) {
|
||||
if (index === 0) return false;
|
||||
const prev = line[index - 1];
|
||||
if (prev.length === 1) return false;
|
||||
return sourcesIndex === prev[SOURCES_INDEX] && sourceLine === prev[SOURCE_LINE] && sourceColumn === prev[SOURCE_COLUMN] && namesIndex === (prev.length === 5 ? prev[NAMES_INDEX] : NO_NAME);
|
||||
}
|
||||
function addMappingInternal(skipable, map, mapping) {
|
||||
const { generated, source, original, name, content } = mapping;
|
||||
if (!source) {
|
||||
return addSegmentInternal(
|
||||
skipable,
|
||||
map,
|
||||
generated.line - 1,
|
||||
generated.column,
|
||||
null,
|
||||
null,
|
||||
null,
|
||||
null,
|
||||
null
|
||||
);
|
||||
}
|
||||
assert(original);
|
||||
return addSegmentInternal(
|
||||
skipable,
|
||||
map,
|
||||
generated.line - 1,
|
||||
generated.column,
|
||||
source,
|
||||
original.line - 1,
|
||||
original.column,
|
||||
name,
|
||||
content
|
||||
);
|
||||
}
|
||||
}));
|
||||
//# sourceMappingURL=gen-mapping.umd.js.map
|
||||
6
whm/gniza4cp-whm/assets/node_modules/@jridgewell/gen-mapping/dist/gen-mapping.umd.js.map
generated
vendored
Normal file
88
whm/gniza4cp-whm/assets/node_modules/@jridgewell/gen-mapping/dist/types/gen-mapping.d.ts
generated
vendored
Normal file
@@ -0,0 +1,88 @@
|
||||
import type { SourceMapInput } from '@jridgewell/trace-mapping';
|
||||
import type { DecodedSourceMap, EncodedSourceMap, Pos, Mapping } from './types';
|
||||
export type { DecodedSourceMap, EncodedSourceMap, Mapping };
|
||||
export type Options = {
|
||||
file?: string | null;
|
||||
sourceRoot?: string | null;
|
||||
};
|
||||
/**
|
||||
* Provides the state to generate a sourcemap.
|
||||
*/
|
||||
export declare class GenMapping {
|
||||
private _names;
|
||||
private _sources;
|
||||
private _sourcesContent;
|
||||
private _mappings;
|
||||
private _ignoreList;
|
||||
file: string | null | undefined;
|
||||
sourceRoot: string | null | undefined;
|
||||
constructor({ file, sourceRoot }?: Options);
|
||||
}
|
||||
/**
|
||||
* A low-level API to associate a generated position with an original source position. Line and
|
||||
* column here are 0-based, unlike `addMapping`.
|
||||
*/
|
||||
export declare function addSegment(map: GenMapping, genLine: number, genColumn: number, source?: null, sourceLine?: null, sourceColumn?: null, name?: null, content?: null): void;
|
||||
export declare function addSegment(map: GenMapping, genLine: number, genColumn: number, source: string, sourceLine: number, sourceColumn: number, name?: null, content?: string | null): void;
|
||||
export declare function addSegment(map: GenMapping, genLine: number, genColumn: number, source: string, sourceLine: number, sourceColumn: number, name: string, content?: string | null): void;
|
||||
/**
|
||||
* A high-level API to associate a generated position with an original source position. Line is
|
||||
* 1-based, but column is 0-based, due to legacy behavior in `source-map` library.
|
||||
*/
|
||||
export declare function addMapping(map: GenMapping, mapping: {
|
||||
generated: Pos;
|
||||
source?: null;
|
||||
original?: null;
|
||||
name?: null;
|
||||
content?: null;
|
||||
}): void;
|
||||
export declare function addMapping(map: GenMapping, mapping: {
|
||||
generated: Pos;
|
||||
source: string;
|
||||
original: Pos;
|
||||
name?: null;
|
||||
content?: string | null;
|
||||
}): void;
|
||||
export declare function addMapping(map: GenMapping, mapping: {
|
||||
generated: Pos;
|
||||
source: string;
|
||||
original: Pos;
|
||||
name: string;
|
||||
content?: string | null;
|
||||
}): void;
|
||||
/**
|
||||
* Same as `addSegment`, but will only add the segment if it generates useful information in the
|
||||
* resulting map. This only works correctly if segments are added **in order**, meaning you should
|
||||
* not add a segment with a lower generated line/column than one that came before.
|
||||
*/
|
||||
export declare const maybeAddSegment: typeof addSegment;
|
||||
/**
|
||||
* Same as `addMapping`, but will only add the mapping if it generates useful information in the
|
||||
* resulting map. This only works correctly if mappings are added **in order**, meaning you should
|
||||
* not add a mapping with a lower generated line/column than one that came before.
|
||||
*/
|
||||
export declare const maybeAddMapping: typeof addMapping;
|
||||
/**
|
||||
* Adds/removes the content of the source file to the source map.
|
||||
*/
|
||||
export declare function setSourceContent(map: GenMapping, source: string, content: string | null): void;
|
||||
export declare function setIgnore(map: GenMapping, source: string, ignore?: boolean): void;
|
||||
/**
|
||||
* Returns a sourcemap object (with decoded mappings) suitable for passing to a library that expects
|
||||
* a sourcemap, or to JSON.stringify.
|
||||
*/
|
||||
export declare function toDecodedMap(map: GenMapping): DecodedSourceMap;
|
||||
/**
|
||||
* Returns a sourcemap object (with encoded mappings) suitable for passing to a library that expects
|
||||
* a sourcemap, or to JSON.stringify.
|
||||
*/
|
||||
export declare function toEncodedMap(map: GenMapping): EncodedSourceMap;
|
||||
/**
|
||||
* Constructs a new GenMapping, using the already present mappings of the input.
|
||||
*/
|
||||
export declare function fromMap(input: SourceMapInput): GenMapping;
|
||||
/**
|
||||
* Returns an array of high-level mapping objects for every recorded segment, which could then be
|
||||
* passed to the `source-map` library.
|
||||
*/
|
||||
export declare function allMappings(map: GenMapping): Mapping[];
|
||||
32
whm/gniza4cp-whm/assets/node_modules/@jridgewell/gen-mapping/dist/types/set-array.d.ts
generated
vendored
Normal file
@@ -0,0 +1,32 @@
|
||||
type Key = string | number | symbol;
|
||||
/**
|
||||
* SetArray acts like a `Set` (allowing only one occurrence of a string `key`), but provides the
|
||||
* index of the `key` in the backing array.
|
||||
*
|
||||
* This is designed to allow synchronizing a second array with the contents of the backing array,
|
||||
* like how in a sourcemap `sourcesContent[i]` is the source content associated with `source[i]`,
|
||||
* and there are never duplicates.
|
||||
*/
|
||||
export declare class SetArray<T extends Key = Key> {
|
||||
private _indexes;
|
||||
array: readonly T[];
|
||||
constructor();
|
||||
}
|
||||
/**
|
||||
* Gets the index associated with `key` in the backing array, if it is already present.
|
||||
*/
|
||||
export declare function get<T extends Key>(setarr: SetArray<T>, key: T): number | undefined;
|
||||
/**
|
||||
* Puts `key` into the backing array, if it is not already present. Returns
|
||||
* the index of the `key` in the backing array.
|
||||
*/
|
||||
export declare function put<T extends Key>(setarr: SetArray<T>, key: T): number;
|
||||
/**
|
||||
* Pops the last added item out of the SetArray.
|
||||
*/
|
||||
export declare function pop<T extends Key>(setarr: SetArray<T>): void;
|
||||
/**
|
||||
* Removes the key, if it exists in the set.
|
||||
*/
|
||||
export declare function remove<T extends Key>(setarr: SetArray<T>, key: T): void;
|
||||
export {};
|
||||
12
whm/gniza4cp-whm/assets/node_modules/@jridgewell/gen-mapping/dist/types/sourcemap-segment.d.ts
generated
vendored
Normal file
@@ -0,0 +1,12 @@
|
||||
type GeneratedColumn = number;
|
||||
type SourcesIndex = number;
|
||||
type SourceLine = number;
|
||||
type SourceColumn = number;
|
||||
type NamesIndex = number;
|
||||
export type SourceMapSegment = [GeneratedColumn] | [GeneratedColumn, SourcesIndex, SourceLine, SourceColumn] | [GeneratedColumn, SourcesIndex, SourceLine, SourceColumn, NamesIndex];
|
||||
export declare const COLUMN = 0;
|
||||
export declare const SOURCES_INDEX = 1;
|
||||
export declare const SOURCE_LINE = 2;
|
||||
export declare const SOURCE_COLUMN = 3;
|
||||
export declare const NAMES_INDEX = 4;
|
||||
export {};
|
||||
43
whm/gniza4cp-whm/assets/node_modules/@jridgewell/gen-mapping/dist/types/types.d.ts
generated
vendored
Normal file
@@ -0,0 +1,43 @@
|
||||
import type { SourceMapSegment } from './sourcemap-segment';
|
||||
export interface SourceMapV3 {
|
||||
file?: string | null;
|
||||
names: readonly string[];
|
||||
sourceRoot?: string;
|
||||
sources: readonly (string | null)[];
|
||||
sourcesContent?: readonly (string | null)[];
|
||||
version: 3;
|
||||
ignoreList?: readonly number[];
|
||||
}
|
||||
export interface EncodedSourceMap extends SourceMapV3 {
|
||||
mappings: string;
|
||||
}
|
||||
export interface DecodedSourceMap extends SourceMapV3 {
|
||||
mappings: readonly SourceMapSegment[][];
|
||||
}
|
||||
export interface Pos {
|
||||
line: number;
|
||||
column: number;
|
||||
}
|
||||
export interface OriginalPos extends Pos {
|
||||
source: string;
|
||||
}
|
||||
export interface BindingExpressionRange {
|
||||
start: Pos;
|
||||
expression: string;
|
||||
}
|
||||
export type Mapping = {
|
||||
generated: Pos;
|
||||
source: undefined;
|
||||
original: undefined;
|
||||
name: undefined;
|
||||
} | {
|
||||
generated: Pos;
|
||||
source: string;
|
||||
original: Pos;
|
||||
name: string;
|
||||
} | {
|
||||
generated: Pos;
|
||||
source: string;
|
||||
original: Pos;
|
||||
name: undefined;
|
||||
};
|
||||
67
whm/gniza4cp-whm/assets/node_modules/@jridgewell/gen-mapping/package.json
generated
vendored
Normal file
@@ -0,0 +1,67 @@
|
||||
{
|
||||
"name": "@jridgewell/gen-mapping",
|
||||
"version": "0.3.13",
|
||||
"description": "Generate source maps",
|
||||
"keywords": [
|
||||
"source",
|
||||
"map"
|
||||
],
|
||||
"main": "dist/gen-mapping.umd.js",
|
||||
"module": "dist/gen-mapping.mjs",
|
||||
"types": "types/gen-mapping.d.cts",
|
||||
"files": [
|
||||
"dist",
|
||||
"src",
|
||||
"types"
|
||||
],
|
||||
"exports": {
|
||||
".": [
|
||||
{
|
||||
"import": {
|
||||
"types": "./types/gen-mapping.d.mts",
|
||||
"default": "./dist/gen-mapping.mjs"
|
||||
},
|
||||
"default": {
|
||||
"types": "./types/gen-mapping.d.cts",
|
||||
"default": "./dist/gen-mapping.umd.js"
|
||||
}
|
||||
},
|
||||
"./dist/gen-mapping.umd.js"
|
||||
],
|
||||
"./package.json": "./package.json"
|
||||
},
|
||||
"scripts": {
|
||||
"benchmark": "run-s build:code benchmark:*",
|
||||
"benchmark:install": "cd benchmark && npm install",
|
||||
"benchmark:only": "node --expose-gc benchmark/index.js",
|
||||
"build": "run-s -n build:code build:types",
|
||||
"build:code": "node ../../esbuild.mjs gen-mapping.ts",
|
||||
"build:types": "run-s build:types:force build:types:emit build:types:mts",
|
||||
"build:types:force": "rimraf tsconfig.build.tsbuildinfo",
|
||||
"build:types:emit": "tsc --project tsconfig.build.json",
|
||||
"build:types:mts": "node ../../mts-types.mjs",
|
||||
"clean": "run-s -n clean:code clean:types",
|
||||
"clean:code": "tsc --build --clean tsconfig.build.json",
|
||||
"clean:types": "rimraf dist types",
|
||||
"test": "run-s -n test:types test:only test:format",
|
||||
"test:format": "prettier --check '{src,test}/**/*.ts'",
|
||||
"test:only": "mocha",
|
||||
"test:types": "eslint '{src,test}/**/*.ts'",
|
||||
"lint": "run-s -n lint:types lint:format",
|
||||
"lint:format": "npm run test:format -- --write",
|
||||
"lint:types": "npm run test:types -- --fix",
|
||||
"prepublishOnly": "npm run-s -n build test"
|
||||
},
|
||||
"homepage": "https://github.com/jridgewell/sourcemaps/tree/main/packages/gen-mapping",
|
||||
"repository": {
|
||||
"type": "git",
|
||||
"url": "git+https://github.com/jridgewell/sourcemaps.git",
|
||||
"directory": "packages/gen-mapping"
|
||||
},
|
||||
"author": "Justin Ridgewell <justin@ridgewell.name>",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@jridgewell/sourcemap-codec": "^1.5.0",
|
||||
"@jridgewell/trace-mapping": "^0.3.24"
|
||||
}
|
||||
}
|
||||
614
whm/gniza4cp-whm/assets/node_modules/@jridgewell/gen-mapping/src/gen-mapping.ts
generated
vendored
Normal file
@@ -0,0 +1,614 @@
|
||||
import { SetArray, put, remove } from './set-array';
|
||||
import {
|
||||
encode,
|
||||
// encodeGeneratedRanges,
|
||||
// encodeOriginalScopes
|
||||
} from '@jridgewell/sourcemap-codec';
|
||||
import { TraceMap, decodedMappings } from '@jridgewell/trace-mapping';
|
||||
|
||||
import {
|
||||
COLUMN,
|
||||
SOURCES_INDEX,
|
||||
SOURCE_LINE,
|
||||
SOURCE_COLUMN,
|
||||
NAMES_INDEX,
|
||||
} from './sourcemap-segment';
|
||||
|
||||
import type { SourceMapInput } from '@jridgewell/trace-mapping';
|
||||
// import type { OriginalScope, GeneratedRange } from '@jridgewell/sourcemap-codec';
|
||||
import type { SourceMapSegment } from './sourcemap-segment';
|
||||
import type {
|
||||
DecodedSourceMap,
|
||||
EncodedSourceMap,
|
||||
Pos,
|
||||
Mapping,
|
||||
// BindingExpressionRange,
|
||||
// OriginalPos,
|
||||
// OriginalScopeInfo,
|
||||
// GeneratedRangeInfo,
|
||||
} from './types';
|
||||
|
||||
export type { DecodedSourceMap, EncodedSourceMap, Mapping };
|
||||
|
||||
export type Options = {
|
||||
file?: string | null;
|
||||
sourceRoot?: string | null;
|
||||
};
|
||||
|
||||
const NO_NAME = -1;
|
||||
|
||||
/**
|
||||
* Provides the state to generate a sourcemap.
|
||||
*/
|
||||
export class GenMapping {
|
||||
declare private _names: SetArray<string>;
|
||||
declare private _sources: SetArray<string>;
|
||||
declare private _sourcesContent: (string | null)[];
|
||||
declare private _mappings: SourceMapSegment[][];
|
||||
// private declare _originalScopes: OriginalScope[][];
|
||||
// private declare _generatedRanges: GeneratedRange[];
|
||||
declare private _ignoreList: SetArray<number>;
|
||||
declare file: string | null | undefined;
|
||||
declare sourceRoot: string | null | undefined;
|
||||
|
||||
constructor({ file, sourceRoot }: Options = {}) {
|
||||
this._names = new SetArray();
|
||||
this._sources = new SetArray();
|
||||
this._sourcesContent = [];
|
||||
this._mappings = [];
|
||||
// this._originalScopes = [];
|
||||
// this._generatedRanges = [];
|
||||
this.file = file;
|
||||
this.sourceRoot = sourceRoot;
|
||||
this._ignoreList = new SetArray();
|
||||
}
|
||||
}
|
||||
|
||||
interface PublicMap {
|
||||
_names: GenMapping['_names'];
|
||||
_sources: GenMapping['_sources'];
|
||||
_sourcesContent: GenMapping['_sourcesContent'];
|
||||
_mappings: GenMapping['_mappings'];
|
||||
// _originalScopes: GenMapping['_originalScopes'];
|
||||
// _generatedRanges: GenMapping['_generatedRanges'];
|
||||
_ignoreList: GenMapping['_ignoreList'];
|
||||
}
|
||||
|
||||
/**
|
||||
* Typescript doesn't allow friend access to private fields, so this just casts the map into a type
|
||||
* with public access modifiers.
|
||||
*/
|
||||
function cast(map: unknown): PublicMap {
|
||||
return map as any;
|
||||
}
|
||||
|
||||
/**
|
||||
* A low-level API to associate a generated position with an original source position. Line and
|
||||
* column here are 0-based, unlike `addMapping`.
|
||||
*/
|
||||
export function addSegment(
|
||||
map: GenMapping,
|
||||
genLine: number,
|
||||
genColumn: number,
|
||||
source?: null,
|
||||
sourceLine?: null,
|
||||
sourceColumn?: null,
|
||||
name?: null,
|
||||
content?: null,
|
||||
): void;
|
||||
export function addSegment(
|
||||
map: GenMapping,
|
||||
genLine: number,
|
||||
genColumn: number,
|
||||
source: string,
|
||||
sourceLine: number,
|
||||
sourceColumn: number,
|
||||
name?: null,
|
||||
content?: string | null,
|
||||
): void;
|
||||
export function addSegment(
|
||||
map: GenMapping,
|
||||
genLine: number,
|
||||
genColumn: number,
|
||||
source: string,
|
||||
sourceLine: number,
|
||||
sourceColumn: number,
|
||||
name: string,
|
||||
content?: string | null,
|
||||
): void;
|
||||
export function addSegment(
|
||||
map: GenMapping,
|
||||
genLine: number,
|
||||
genColumn: number,
|
||||
source?: string | null,
|
||||
sourceLine?: number | null,
|
||||
sourceColumn?: number | null,
|
||||
name?: string | null,
|
||||
content?: string | null,
|
||||
): void {
|
||||
return addSegmentInternal(
|
||||
false,
|
||||
map,
|
||||
genLine,
|
||||
genColumn,
|
||||
source,
|
||||
sourceLine,
|
||||
sourceColumn,
|
||||
name,
|
||||
content,
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* A high-level API to associate a generated position with an original source position. Line is
|
||||
* 1-based, but column is 0-based, due to legacy behavior in `source-map` library.
|
||||
*/
|
||||
export function addMapping(
|
||||
map: GenMapping,
|
||||
mapping: {
|
||||
generated: Pos;
|
||||
source?: null;
|
||||
original?: null;
|
||||
name?: null;
|
||||
content?: null;
|
||||
},
|
||||
): void;
|
||||
export function addMapping(
|
||||
map: GenMapping,
|
||||
mapping: {
|
||||
generated: Pos;
|
||||
source: string;
|
||||
original: Pos;
|
||||
name?: null;
|
||||
content?: string | null;
|
||||
},
|
||||
): void;
|
||||
export function addMapping(
|
||||
map: GenMapping,
|
||||
mapping: {
|
||||
generated: Pos;
|
||||
source: string;
|
||||
original: Pos;
|
||||
name: string;
|
||||
content?: string | null;
|
||||
},
|
||||
): void;
|
||||
export function addMapping(
|
||||
map: GenMapping,
|
||||
mapping: {
|
||||
generated: Pos;
|
||||
source?: string | null;
|
||||
original?: Pos | null;
|
||||
name?: string | null;
|
||||
content?: string | null;
|
||||
},
|
||||
): void {
|
||||
return addMappingInternal(false, map, mapping as Parameters<typeof addMappingInternal>[2]);
|
||||
}
|
||||
|
||||
/**
|
||||
* Same as `addSegment`, but will only add the segment if it generates useful information in the
|
||||
* resulting map. This only works correctly if segments are added **in order**, meaning you should
|
||||
* not add a segment with a lower generated line/column than one that came before.
|
||||
*/
|
||||
export const maybeAddSegment: typeof addSegment = (
|
||||
map,
|
||||
genLine,
|
||||
genColumn,
|
||||
source,
|
||||
sourceLine,
|
||||
sourceColumn,
|
||||
name,
|
||||
content,
|
||||
) => {
|
||||
return addSegmentInternal(
|
||||
true,
|
||||
map,
|
||||
genLine,
|
||||
genColumn,
|
||||
source,
|
||||
sourceLine,
|
||||
sourceColumn,
|
||||
name,
|
||||
content,
|
||||
);
|
||||
};
|
||||
|
||||
/**
|
||||
* Same as `addMapping`, but will only add the mapping if it generates useful information in the
|
||||
* resulting map. This only works correctly if mappings are added **in order**, meaning you should
|
||||
* not add a mapping with a lower generated line/column than one that came before.
|
||||
*/
|
||||
export const maybeAddMapping: typeof addMapping = (map, mapping) => {
|
||||
return addMappingInternal(true, map, mapping as Parameters<typeof addMappingInternal>[2]);
|
||||
};
|
||||
|
||||
/**
|
||||
* Adds/removes the content of the source file to the source map.
|
||||
*/
|
||||
export function setSourceContent(map: GenMapping, source: string, content: string | null): void {
|
||||
const {
|
||||
_sources: sources,
|
||||
_sourcesContent: sourcesContent,
|
||||
// _originalScopes: originalScopes,
|
||||
} = cast(map);
|
||||
const index = put(sources, source);
|
||||
sourcesContent[index] = content;
|
||||
// if (index === originalScopes.length) originalScopes[index] = [];
|
||||
}
|
||||
|
||||
export function setIgnore(map: GenMapping, source: string, ignore = true) {
|
||||
const {
|
||||
_sources: sources,
|
||||
_sourcesContent: sourcesContent,
|
||||
_ignoreList: ignoreList,
|
||||
// _originalScopes: originalScopes,
|
||||
} = cast(map);
|
||||
const index = put(sources, source);
|
||||
if (index === sourcesContent.length) sourcesContent[index] = null;
|
||||
// if (index === originalScopes.length) originalScopes[index] = [];
|
||||
if (ignore) put(ignoreList, index);
|
||||
else remove(ignoreList, index);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns a sourcemap object (with decoded mappings) suitable for passing to a library that expects
|
||||
* a sourcemap, or to JSON.stringify.
|
||||
*/
|
||||
export function toDecodedMap(map: GenMapping): DecodedSourceMap {
|
||||
const {
|
||||
_mappings: mappings,
|
||||
_sources: sources,
|
||||
_sourcesContent: sourcesContent,
|
||||
_names: names,
|
||||
_ignoreList: ignoreList,
|
||||
// _originalScopes: originalScopes,
|
||||
// _generatedRanges: generatedRanges,
|
||||
} = cast(map);
|
||||
removeEmptyFinalLines(mappings);
|
||||
|
||||
return {
|
||||
version: 3,
|
||||
file: map.file || undefined,
|
||||
names: names.array,
|
||||
sourceRoot: map.sourceRoot || undefined,
|
||||
sources: sources.array,
|
||||
sourcesContent,
|
||||
mappings,
|
||||
// originalScopes,
|
||||
// generatedRanges,
|
||||
ignoreList: ignoreList.array,
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns a sourcemap object (with encoded mappings) suitable for passing to a library that expects
|
||||
* a sourcemap, or to JSON.stringify.
|
||||
*/
|
||||
export function toEncodedMap(map: GenMapping): EncodedSourceMap {
|
||||
const decoded = toDecodedMap(map);
|
||||
return Object.assign({}, decoded, {
|
||||
// originalScopes: decoded.originalScopes.map((os) => encodeOriginalScopes(os)),
|
||||
// generatedRanges: encodeGeneratedRanges(decoded.generatedRanges as GeneratedRange[]),
|
||||
mappings: encode(decoded.mappings as SourceMapSegment[][]),
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Constructs a new GenMapping, using the already present mappings of the input.
|
||||
*/
|
||||
export function fromMap(input: SourceMapInput): GenMapping {
|
||||
const map = new TraceMap(input);
|
||||
const gen = new GenMapping({ file: map.file, sourceRoot: map.sourceRoot });
|
||||
|
||||
putAll(cast(gen)._names, map.names);
|
||||
putAll(cast(gen)._sources, map.sources as string[]);
|
||||
cast(gen)._sourcesContent = map.sourcesContent || map.sources.map(() => null);
|
||||
cast(gen)._mappings = decodedMappings(map) as GenMapping['_mappings'];
|
||||
// TODO: implement originalScopes/generatedRanges
|
||||
if (map.ignoreList) putAll(cast(gen)._ignoreList, map.ignoreList);
|
||||
|
||||
return gen;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns an array of high-level mapping objects for every recorded segment, which could then be
|
||||
* passed to the `source-map` library.
|
||||
*/
|
||||
export function allMappings(map: GenMapping): Mapping[] {
|
||||
const out: Mapping[] = [];
|
||||
const { _mappings: mappings, _sources: sources, _names: names } = cast(map);
|
||||
|
||||
for (let i = 0; i < mappings.length; i++) {
|
||||
const line = mappings[i];
|
||||
for (let j = 0; j < line.length; j++) {
|
||||
const seg = line[j];
|
||||
|
||||
const generated = { line: i + 1, column: seg[COLUMN] };
|
||||
let source: string | undefined = undefined;
|
||||
let original: Pos | undefined = undefined;
|
||||
let name: string | undefined = undefined;
|
||||
|
||||
if (seg.length !== 1) {
|
||||
source = sources.array[seg[SOURCES_INDEX]];
|
||||
original = { line: seg[SOURCE_LINE] + 1, column: seg[SOURCE_COLUMN] };
|
||||
|
||||
if (seg.length === 5) name = names.array[seg[NAMES_INDEX]];
|
||||
}
|
||||
|
||||
out.push({ generated, source, original, name } as Mapping);
|
||||
}
|
||||
}
|
||||
|
||||
return out;
|
||||
}
|
||||
|
||||
// This split declaration is only so that terser can elminiate the static initialization block.
|
||||
function addSegmentInternal<S extends string | null | undefined>(
|
||||
skipable: boolean,
|
||||
map: GenMapping,
|
||||
genLine: number,
|
||||
genColumn: number,
|
||||
source: S,
|
||||
sourceLine: S extends string ? number : null | undefined,
|
||||
sourceColumn: S extends string ? number : null | undefined,
|
||||
name: S extends string ? string | null | undefined : null | undefined,
|
||||
content: S extends string ? string | null | undefined : null | undefined,
|
||||
): void {
|
||||
const {
|
||||
_mappings: mappings,
|
||||
_sources: sources,
|
||||
_sourcesContent: sourcesContent,
|
||||
_names: names,
|
||||
// _originalScopes: originalScopes,
|
||||
} = cast(map);
|
||||
const line = getIndex(mappings, genLine);
|
||||
const index = getColumnIndex(line, genColumn);
|
||||
|
||||
if (!source) {
|
||||
if (skipable && skipSourceless(line, index)) return;
|
||||
return insert(line, index, [genColumn]);
|
||||
}
|
||||
|
||||
// Sigh, TypeScript can't figure out sourceLine and sourceColumn aren't nullish if source
|
||||
// isn't nullish.
|
||||
assert<number>(sourceLine);
|
||||
assert<number>(sourceColumn);
|
||||
|
||||
const sourcesIndex = put(sources, source);
|
||||
const namesIndex = name ? put(names, name) : NO_NAME;
|
||||
if (sourcesIndex === sourcesContent.length) sourcesContent[sourcesIndex] = content ?? null;
|
||||
// if (sourcesIndex === originalScopes.length) originalScopes[sourcesIndex] = [];
|
||||
|
||||
if (skipable && skipSource(line, index, sourcesIndex, sourceLine, sourceColumn, namesIndex)) {
|
||||
return;
|
||||
}
|
||||
|
||||
return insert(
|
||||
line,
|
||||
index,
|
||||
name
|
||||
? [genColumn, sourcesIndex, sourceLine, sourceColumn, namesIndex]
|
||||
: [genColumn, sourcesIndex, sourceLine, sourceColumn],
|
||||
);
|
||||
}
|
||||
|
||||
function assert<T>(_val: unknown): asserts _val is T {
|
||||
// noop.
|
||||
}
|
||||
|
||||
function getIndex<T>(arr: T[][], index: number): T[] {
|
||||
for (let i = arr.length; i <= index; i++) {
|
||||
arr[i] = [];
|
||||
}
|
||||
return arr[index];
|
||||
}
|
||||
|
||||
function getColumnIndex(line: SourceMapSegment[], genColumn: number): number {
|
||||
let index = line.length;
|
||||
for (let i = index - 1; i >= 0; index = i--) {
|
||||
const current = line[i];
|
||||
if (genColumn >= current[COLUMN]) break;
|
||||
}
|
||||
return index;
|
||||
}
|
||||
|
||||
function insert<T>(array: T[], index: number, value: T) {
|
||||
for (let i = array.length; i > index; i--) {
|
||||
array[i] = array[i - 1];
|
||||
}
|
||||
array[index] = value;
|
||||
}
|
||||
|
||||
function removeEmptyFinalLines(mappings: SourceMapSegment[][]) {
|
||||
const { length } = mappings;
|
||||
let len = length;
|
||||
for (let i = len - 1; i >= 0; len = i, i--) {
|
||||
if (mappings[i].length > 0) break;
|
||||
}
|
||||
if (len < length) mappings.length = len;
|
||||
}
|
||||
|
||||
function putAll<T extends string | number>(setarr: SetArray<T>, array: T[]) {
|
||||
for (let i = 0; i < array.length; i++) put(setarr, array[i]);
|
||||
}
|
||||
|
||||
function skipSourceless(line: SourceMapSegment[], index: number): boolean {
|
||||
// The start of a line is already sourceless, so adding a sourceless segment to the beginning
|
||||
// doesn't generate any useful information.
|
||||
if (index === 0) return true;
|
||||
|
||||
const prev = line[index - 1];
|
||||
// If the previous segment is also sourceless, then adding another sourceless segment doesn't
|
||||
// genrate any new information. Else, this segment will end the source/named segment and point to
|
||||
// a sourceless position, which is useful.
|
||||
return prev.length === 1;
|
||||
}
|
||||
|
||||
function skipSource(
|
||||
line: SourceMapSegment[],
|
||||
index: number,
|
||||
sourcesIndex: number,
|
||||
sourceLine: number,
|
||||
sourceColumn: number,
|
||||
namesIndex: number,
|
||||
): boolean {
|
||||
// A source/named segment at the start of a line gives position at that genColumn
|
||||
if (index === 0) return false;
|
||||
|
||||
const prev = line[index - 1];
|
||||
|
||||
// If the previous segment is sourceless, then we're transitioning to a source.
|
||||
if (prev.length === 1) return false;
|
||||
|
||||
// If the previous segment maps to the exact same source position, then this segment doesn't
|
||||
// provide any new position information.
|
||||
return (
|
||||
sourcesIndex === prev[SOURCES_INDEX] &&
|
||||
sourceLine === prev[SOURCE_LINE] &&
|
||||
sourceColumn === prev[SOURCE_COLUMN] &&
|
||||
namesIndex === (prev.length === 5 ? prev[NAMES_INDEX] : NO_NAME)
|
||||
);
|
||||
}
|
||||
|
||||
function addMappingInternal<S extends string | null | undefined>(
|
||||
skipable: boolean,
|
||||
map: GenMapping,
|
||||
mapping: {
|
||||
generated: Pos;
|
||||
source: S;
|
||||
original: S extends string ? Pos : null | undefined;
|
||||
name: S extends string ? string | null | undefined : null | undefined;
|
||||
content: S extends string ? string | null | undefined : null | undefined;
|
||||
},
|
||||
) {
|
||||
const { generated, source, original, name, content } = mapping;
|
||||
if (!source) {
|
||||
return addSegmentInternal(
|
||||
skipable,
|
||||
map,
|
||||
generated.line - 1,
|
||||
generated.column,
|
||||
null,
|
||||
null,
|
||||
null,
|
||||
null,
|
||||
null,
|
||||
);
|
||||
}
|
||||
assert<Pos>(original);
|
||||
return addSegmentInternal(
|
||||
skipable,
|
||||
map,
|
||||
generated.line - 1,
|
||||
generated.column,
|
||||
source as string,
|
||||
original.line - 1,
|
||||
original.column,
|
||||
name,
|
||||
content,
|
||||
);
|
||||
}
|
||||
|
||||
/*
|
||||
export function addOriginalScope(
|
||||
map: GenMapping,
|
||||
data: {
|
||||
start: Pos;
|
||||
end: Pos;
|
||||
source: string;
|
||||
kind: string;
|
||||
name?: string;
|
||||
variables?: string[];
|
||||
},
|
||||
): OriginalScopeInfo {
|
||||
const { start, end, source, kind, name, variables } = data;
|
||||
const {
|
||||
_sources: sources,
|
||||
_sourcesContent: sourcesContent,
|
||||
_originalScopes: originalScopes,
|
||||
_names: names,
|
||||
} = cast(map);
|
||||
const index = put(sources, source);
|
||||
if (index === sourcesContent.length) sourcesContent[index] = null;
|
||||
if (index === originalScopes.length) originalScopes[index] = [];
|
||||
|
||||
const kindIndex = put(names, kind);
|
||||
const scope: OriginalScope = name
|
||||
? [start.line - 1, start.column, end.line - 1, end.column, kindIndex, put(names, name)]
|
||||
: [start.line - 1, start.column, end.line - 1, end.column, kindIndex];
|
||||
if (variables) {
|
||||
scope.vars = variables.map((v) => put(names, v));
|
||||
}
|
||||
const len = originalScopes[index].push(scope);
|
||||
return [index, len - 1, variables];
|
||||
}
|
||||
*/
|
||||
|
||||
// Generated Ranges
|
||||
/*
|
||||
export function addGeneratedRange(
|
||||
map: GenMapping,
|
||||
data: {
|
||||
start: Pos;
|
||||
isScope: boolean;
|
||||
originalScope?: OriginalScopeInfo;
|
||||
callsite?: OriginalPos;
|
||||
},
|
||||
): GeneratedRangeInfo {
|
||||
const { start, isScope, originalScope, callsite } = data;
|
||||
const {
|
||||
_originalScopes: originalScopes,
|
||||
_sources: sources,
|
||||
_sourcesContent: sourcesContent,
|
||||
_generatedRanges: generatedRanges,
|
||||
} = cast(map);
|
||||
|
||||
const range: GeneratedRange = [
|
||||
start.line - 1,
|
||||
start.column,
|
||||
0,
|
||||
0,
|
||||
originalScope ? originalScope[0] : -1,
|
||||
originalScope ? originalScope[1] : -1,
|
||||
];
|
||||
if (originalScope?.[2]) {
|
||||
range.bindings = originalScope[2].map(() => [[-1]]);
|
||||
}
|
||||
if (callsite) {
|
||||
const index = put(sources, callsite.source);
|
||||
if (index === sourcesContent.length) sourcesContent[index] = null;
|
||||
if (index === originalScopes.length) originalScopes[index] = [];
|
||||
range.callsite = [index, callsite.line - 1, callsite.column];
|
||||
}
|
||||
if (isScope) range.isScope = true;
|
||||
generatedRanges.push(range);
|
||||
|
||||
return [range, originalScope?.[2]];
|
||||
}
|
||||
|
||||
export function setEndPosition(range: GeneratedRangeInfo, pos: Pos) {
|
||||
range[0][2] = pos.line - 1;
|
||||
range[0][3] = pos.column;
|
||||
}
|
||||
|
||||
export function addBinding(
|
||||
map: GenMapping,
|
||||
range: GeneratedRangeInfo,
|
||||
variable: string,
|
||||
expression: string | BindingExpressionRange,
|
||||
) {
|
||||
const { _names: names } = cast(map);
|
||||
const bindings = (range[0].bindings ||= []);
|
||||
const vars = range[1];
|
||||
|
||||
const index = vars!.indexOf(variable);
|
||||
const binding = getIndex(bindings, index);
|
||||
|
||||
if (typeof expression === 'string') binding[0] = [put(names, expression)];
|
||||
else {
|
||||
const { start } = expression;
|
||||
binding.push([put(names, expression.expression), start.line - 1, start.column]);
|
||||
}
|
||||
}
|
||||
*/
|
||||
82
whm/gniza4cp-whm/assets/node_modules/@jridgewell/gen-mapping/src/set-array.ts
generated
vendored
Normal file
@@ -0,0 +1,82 @@
|
||||
type Key = string | number | symbol;
|
||||
|
||||
/**
|
||||
* SetArray acts like a `Set` (allowing only one occurrence of a string `key`), but provides the
|
||||
* index of the `key` in the backing array.
|
||||
*
|
||||
* This is designed to allow synchronizing a second array with the contents of the backing array,
|
||||
* like how in a sourcemap `sourcesContent[i]` is the source content associated with `source[i]`,
|
||||
* and there are never duplicates.
|
||||
*/
|
||||
export class SetArray<T extends Key = Key> {
|
||||
declare private _indexes: Record<T, number | undefined>;
|
||||
declare array: readonly T[];
|
||||
|
||||
constructor() {
|
||||
this._indexes = { __proto__: null } as any;
|
||||
this.array = [];
|
||||
}
|
||||
}
|
||||
|
||||
interface PublicSet<T extends Key> {
|
||||
array: T[];
|
||||
_indexes: SetArray<T>['_indexes'];
|
||||
}
|
||||
|
||||
/**
|
||||
* Typescript doesn't allow friend access to private fields, so this just casts the set into a type
|
||||
* with public access modifiers.
|
||||
*/
|
||||
function cast<T extends Key>(set: SetArray<T>): PublicSet<T> {
|
||||
return set as any;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the index associated with `key` in the backing array, if it is already present.
|
||||
*/
|
||||
export function get<T extends Key>(setarr: SetArray<T>, key: T): number | undefined {
|
||||
return cast(setarr)._indexes[key];
|
||||
}
|
||||
|
||||
/**
|
||||
* Puts `key` into the backing array, if it is not already present. Returns
|
||||
* the index of the `key` in the backing array.
|
||||
*/
|
||||
export function put<T extends Key>(setarr: SetArray<T>, key: T): number {
|
||||
// The key may or may not be present. If it is present, it's a number.
|
||||
const index = get(setarr, key);
|
||||
if (index !== undefined) return index;
|
||||
|
||||
const { array, _indexes: indexes } = cast(setarr);
|
||||
|
||||
const length = array.push(key);
|
||||
return (indexes[key] = length - 1);
|
||||
}
|
||||
|
||||
/**
|
||||
* Pops the last added item out of the SetArray.
|
||||
*/
|
||||
export function pop<T extends Key>(setarr: SetArray<T>): void {
|
||||
const { array, _indexes: indexes } = cast(setarr);
|
||||
if (array.length === 0) return;
|
||||
|
||||
const last = array.pop()!;
|
||||
indexes[last] = undefined;
|
||||
}
|
||||
|
||||
/**
|
||||
* Removes the key, if it exists in the set.
|
||||
*/
|
||||
export function remove<T extends Key>(setarr: SetArray<T>, key: T): void {
|
||||
const index = get(setarr, key);
|
||||
if (index === undefined) return;
|
||||
|
||||
const { array, _indexes: indexes } = cast(setarr);
|
||||
for (let i = index + 1; i < array.length; i++) {
|
||||
const k = array[i];
|
||||
array[i - 1] = k;
|
||||
indexes[k]!--;
|
||||
}
|
||||
indexes[key] = undefined;
|
||||
array.pop();
|
||||
}
|
||||
16
whm/gniza4cp-whm/assets/node_modules/@jridgewell/gen-mapping/src/sourcemap-segment.ts
generated
vendored
Normal file
@@ -0,0 +1,16 @@
|
||||
type GeneratedColumn = number;
|
||||
type SourcesIndex = number;
|
||||
type SourceLine = number;
|
||||
type SourceColumn = number;
|
||||
type NamesIndex = number;
|
||||
|
||||
export type SourceMapSegment =
|
||||
| [GeneratedColumn]
|
||||
| [GeneratedColumn, SourcesIndex, SourceLine, SourceColumn]
|
||||
| [GeneratedColumn, SourcesIndex, SourceLine, SourceColumn, NamesIndex];
|
||||
|
||||
export const COLUMN = 0;
|
||||
export const SOURCES_INDEX = 1;
|
||||
export const SOURCE_LINE = 2;
|
||||
export const SOURCE_COLUMN = 3;
|
||||
export const NAMES_INDEX = 4;
|
||||
61
whm/gniza4cp-whm/assets/node_modules/@jridgewell/gen-mapping/src/types.ts
generated
vendored
Normal file
@@ -0,0 +1,61 @@
|
||||
// import type { GeneratedRange, OriginalScope } from '@jridgewell/sourcemap-codec';
|
||||
import type { SourceMapSegment } from './sourcemap-segment';
|
||||
|
||||
export interface SourceMapV3 {
|
||||
file?: string | null;
|
||||
names: readonly string[];
|
||||
sourceRoot?: string;
|
||||
sources: readonly (string | null)[];
|
||||
sourcesContent?: readonly (string | null)[];
|
||||
version: 3;
|
||||
ignoreList?: readonly number[];
|
||||
}
|
||||
|
||||
export interface EncodedSourceMap extends SourceMapV3 {
|
||||
mappings: string;
|
||||
// originalScopes: string[];
|
||||
// generatedRanges: string;
|
||||
}
|
||||
|
||||
export interface DecodedSourceMap extends SourceMapV3 {
|
||||
mappings: readonly SourceMapSegment[][];
|
||||
// originalScopes: readonly OriginalScope[][];
|
||||
// generatedRanges: readonly GeneratedRange[];
|
||||
}
|
||||
|
||||
export interface Pos {
|
||||
line: number; // 1-based
|
||||
column: number; // 0-based
|
||||
}
|
||||
|
||||
export interface OriginalPos extends Pos {
|
||||
source: string;
|
||||
}
|
||||
|
||||
export interface BindingExpressionRange {
|
||||
start: Pos;
|
||||
expression: string;
|
||||
}
|
||||
|
||||
// export type OriginalScopeInfo = [number, number, string[] | undefined];
|
||||
// export type GeneratedRangeInfo = [GeneratedRange, string[] | undefined];
|
||||
|
||||
export type Mapping =
|
||||
| {
|
||||
generated: Pos;
|
||||
source: undefined;
|
||||
original: undefined;
|
||||
name: undefined;
|
||||
}
|
||||
| {
|
||||
generated: Pos;
|
||||
source: string;
|
||||
original: Pos;
|
||||
name: string;
|
||||
}
|
||||
| {
|
||||
generated: Pos;
|
||||
source: string;
|
||||
original: Pos;
|
||||
name: undefined;
|
||||
};
|
||||
89
whm/gniza4cp-whm/assets/node_modules/@jridgewell/gen-mapping/types/gen-mapping.d.cts
generated
vendored
Normal file
@@ -0,0 +1,89 @@
|
||||
import type { SourceMapInput } from '@jridgewell/trace-mapping';
|
||||
import type { DecodedSourceMap, EncodedSourceMap, Pos, Mapping } from './types.cts';
|
||||
export type { DecodedSourceMap, EncodedSourceMap, Mapping };
|
||||
export type Options = {
|
||||
file?: string | null;
|
||||
sourceRoot?: string | null;
|
||||
};
|
||||
/**
|
||||
* Provides the state to generate a sourcemap.
|
||||
*/
|
||||
export declare class GenMapping {
|
||||
private _names;
|
||||
private _sources;
|
||||
private _sourcesContent;
|
||||
private _mappings;
|
||||
private _ignoreList;
|
||||
file: string | null | undefined;
|
||||
sourceRoot: string | null | undefined;
|
||||
constructor({ file, sourceRoot }?: Options);
|
||||
}
|
||||
/**
|
||||
* A low-level API to associate a generated position with an original source position. Line and
|
||||
* column here are 0-based, unlike `addMapping`.
|
||||
*/
|
||||
export declare function addSegment(map: GenMapping, genLine: number, genColumn: number, source?: null, sourceLine?: null, sourceColumn?: null, name?: null, content?: null): void;
|
||||
export declare function addSegment(map: GenMapping, genLine: number, genColumn: number, source: string, sourceLine: number, sourceColumn: number, name?: null, content?: string | null): void;
|
||||
export declare function addSegment(map: GenMapping, genLine: number, genColumn: number, source: string, sourceLine: number, sourceColumn: number, name: string, content?: string | null): void;
|
||||
/**
|
||||
* A high-level API to associate a generated position with an original source position. Line is
|
||||
* 1-based, but column is 0-based, due to legacy behavior in `source-map` library.
|
||||
*/
|
||||
export declare function addMapping(map: GenMapping, mapping: {
|
||||
generated: Pos;
|
||||
source?: null;
|
||||
original?: null;
|
||||
name?: null;
|
||||
content?: null;
|
||||
}): void;
|
||||
export declare function addMapping(map: GenMapping, mapping: {
|
||||
generated: Pos;
|
||||
source: string;
|
||||
original: Pos;
|
||||
name?: null;
|
||||
content?: string | null;
|
||||
}): void;
|
||||
export declare function addMapping(map: GenMapping, mapping: {
|
||||
generated: Pos;
|
||||
source: string;
|
||||
original: Pos;
|
||||
name: string;
|
||||
content?: string | null;
|
||||
}): void;
|
||||
/**
|
||||
* Same as `addSegment`, but will only add the segment if it generates useful information in the
|
||||
* resulting map. This only works correctly if segments are added **in order**, meaning you should
|
||||
* not add a segment with a lower generated line/column than one that came before.
|
||||
*/
|
||||
export declare const maybeAddSegment: typeof addSegment;
|
||||
/**
|
||||
* Same as `addMapping`, but will only add the mapping if it generates useful information in the
|
||||
* resulting map. This only works correctly if mappings are added **in order**, meaning you should
|
||||
* not add a mapping with a lower generated line/column than one that came before.
|
||||
*/
|
||||
export declare const maybeAddMapping: typeof addMapping;
|
||||
/**
|
||||
* Adds/removes the content of the source file to the source map.
|
||||
*/
|
||||
export declare function setSourceContent(map: GenMapping, source: string, content: string | null): void;
|
||||
export declare function setIgnore(map: GenMapping, source: string, ignore?: boolean): void;
|
||||
/**
|
||||
* Returns a sourcemap object (with decoded mappings) suitable for passing to a library that expects
|
||||
* a sourcemap, or to JSON.stringify.
|
||||
*/
|
||||
export declare function toDecodedMap(map: GenMapping): DecodedSourceMap;
|
||||
/**
|
||||
* Returns a sourcemap object (with encoded mappings) suitable for passing to a library that expects
|
||||
* a sourcemap, or to JSON.stringify.
|
||||
*/
|
||||
export declare function toEncodedMap(map: GenMapping): EncodedSourceMap;
|
||||
/**
|
||||
* Constructs a new GenMapping, using the already present mappings of the input.
|
||||
*/
|
||||
export declare function fromMap(input: SourceMapInput): GenMapping;
|
||||
/**
|
||||
* Returns an array of high-level mapping objects for every recorded segment, which could then be
|
||||
* passed to the `source-map` library.
|
||||
*/
|
||||
export declare function allMappings(map: GenMapping): Mapping[];
|
||||
//# sourceMappingURL=gen-mapping.d.ts.map
|
||||
1
whm/gniza4cp-whm/assets/node_modules/@jridgewell/gen-mapping/types/gen-mapping.d.cts.map
generated
vendored
Normal file
@@ -0,0 +1 @@
|
||||
{"version":3,"file":"gen-mapping.d.ts","sourceRoot":"","sources":["../src/gen-mapping.ts"],"names":[],"mappings":"AAgBA,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,2BAA2B,CAAC;AAGhE,OAAO,KAAK,EACV,gBAAgB,EAChB,gBAAgB,EAChB,GAAG,EACH,OAAO,EAKR,MAAM,SAAS,CAAC;AAEjB,YAAY,EAAE,gBAAgB,EAAE,gBAAgB,EAAE,OAAO,EAAE,CAAC;AAE5D,MAAM,MAAM,OAAO,GAAG;IACpB,IAAI,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;IACrB,UAAU,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;CAC5B,CAAC;AAIF;;GAEG;AACH,qBAAa,UAAU;IACrB,QAAgB,MAAM,CAAmB;IACzC,QAAgB,QAAQ,CAAmB;IAC3C,QAAgB,eAAe,CAAoB;IACnD,QAAgB,SAAS,CAAuB;IAGhD,QAAgB,WAAW,CAAmB;IACtC,IAAI,EAAE,MAAM,GAAG,IAAI,GAAG,SAAS,CAAC;IAChC,UAAU,EAAE,MAAM,GAAG,IAAI,GAAG,SAAS,CAAC;gBAElC,EAAE,IAAI,EAAE,UAAU,EAAE,GAAE,OAAY;CAW/C;AAoBD;;;GAGG;AACH,wBAAgB,UAAU,CACxB,GAAG,EAAE,UAAU,EACf,OAAO,EAAE,MAAM,EACf,SAAS,EAAE,MAAM,EACjB,MAAM,CAAC,EAAE,IAAI,EACb,UAAU,CAAC,EAAE,IAAI,EACjB,YAAY,CAAC,EAAE,IAAI,EACnB,IAAI,CAAC,EAAE,IAAI,EACX,OAAO,CAAC,EAAE,IAAI,GACb,IAAI,CAAC;AACR,wBAAgB,UAAU,CACxB,GAAG,EAAE,UAAU,EACf,OAAO,EAAE,MAAM,EACf,SAAS,EAAE,MAAM,EACjB,MAAM,EAAE,MAAM,EACd,UAAU,EAAE,MAAM,EAClB,YAAY,EAAE,MAAM,EACpB,IAAI,CAAC,EAAE,IAAI,EACX,OAAO,CAAC,EAAE,MAAM,GAAG,IAAI,GACtB,IAAI,CAAC;AACR,wBAAgB,UAAU,CACxB,GAAG,EAAE,UAAU,EACf,OAAO,EAAE,MAAM,EACf,SAAS,EAAE,MAAM,EACjB,MAAM,EAAE,MAAM,EACd,UAAU,EAAE,MAAM,EAClB,YAAY,EAAE,MAAM,EACpB,IAAI,EAAE,MAAM,EACZ,OAAO,CAAC,EAAE,MAAM,GAAG,IAAI,GACtB,IAAI,CAAC;AAwBR;;;GAGG;AACH,wBAAgB,UAAU,CACxB,GAAG,EAAE,UAAU,EACf,OAAO,EAAE;IACP,SAAS,EAAE,GAAG,CAAC;IACf,MAAM,CAAC,EAAE,IAAI,CAAC;IACd,QAAQ,CAAC,EAAE,IAAI,CAAC;IAChB,IAAI,CAAC,EAAE,IAAI,CAAC;IACZ,OAAO,CAAC,EAAE,IAAI,CAAC;CAChB,GACA,IAAI,CAAC;AACR,wBAAgB,UAAU,CACxB,GAAG,EAAE,UAAU,EACf,OAAO,EAAE;IACP,SAAS,EAAE,GAAG,CAAC;IACf,MAAM,EAAE,MAAM,CAAC;IACf,QAAQ,EAAE,GAAG,CAAC;IACd,IAAI,CAAC,EAAE,IAAI,CAAC;IACZ,OAAO,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;CACzB,GACA,IAAI,CAAC;AACR,wBAAgB,UAAU,CACxB,GAAG,EAAE,UAAU,EACf,OAAO,EAAE;IACP,SAAS,EAAE,GAAG,CAAC;IACf,MAAM,EAAE,MAAM,CAAC;IACf,QAAQ,EAAE,GAAG,CAAC;IACd,IAAI,EAAE,MAAM,CAAC;IACb,OAAO,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;CACzB,GACA,IAAI,CAAC;AAcR;;;;GAIG;AACH,eAAO,MAAM,eAAe,EAAE,OAAO,UAqBpC,CAAC;AAEF;;;;GAIG;AACH,eAAO,MAAM,eAAe,EAAE,OAAO,UAEpC,CAAC;AAEF;;GAEG;AACH,wBAAgB,gBAAgB,CAAC,GAAG,EAAE,UAAU,EAAE,MAAM,EAAE,MAAM,EAAE,OAAO,EAAE,MAAM,GAAG,IAAI,GAAG,IAAI,CAS9F;AAED,wBAAgB,SAAS,CAAC,GAAG,EAAE,UAAU,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,UAAO,QAYvE;AAED;;;GAGG;AACH,wBAAgB,YAAY,CAAC,GAAG,EAAE,UAAU,GAAG,gBAAgB,CAwB9D;AAED;;;GAGG;AACH,wBAAgB,YAAY,CAAC,GAAG,EAAE,UAAU,GAAG,gBAAgB,CAO9D;AAED;;GAEG;AACH,wBAAgB,OAAO,CAAC,KAAK,EAAE,cAAc,GAAG,UAAU,CAYzD;AAED;;;GAGG;AACH,wBAAgB,WAAW,CAAC,GAAG,EAAE,UAAU,GAAG,OAAO,EAAE,CA0BtD"}
|
||||
89
whm/gniza4cp-whm/assets/node_modules/@jridgewell/gen-mapping/types/gen-mapping.d.mts
generated
vendored
Normal file
@@ -0,0 +1,89 @@
|
||||
import type { SourceMapInput } from '@jridgewell/trace-mapping';
|
||||
import type { DecodedSourceMap, EncodedSourceMap, Pos, Mapping } from './types.mts';
|
||||
export type { DecodedSourceMap, EncodedSourceMap, Mapping };
|
||||
export type Options = {
|
||||
file?: string | null;
|
||||
sourceRoot?: string | null;
|
||||
};
|
||||
/**
|
||||
* Provides the state to generate a sourcemap.
|
||||
*/
|
||||
export declare class GenMapping {
|
||||
private _names;
|
||||
private _sources;
|
||||
private _sourcesContent;
|
||||
private _mappings;
|
||||
private _ignoreList;
|
||||
file: string | null | undefined;
|
||||
sourceRoot: string | null | undefined;
|
||||
constructor({ file, sourceRoot }?: Options);
|
||||
}
|
||||
/**
|
||||
* A low-level API to associate a generated position with an original source position. Line and
|
||||
* column here are 0-based, unlike `addMapping`.
|
||||
*/
|
||||
export declare function addSegment(map: GenMapping, genLine: number, genColumn: number, source?: null, sourceLine?: null, sourceColumn?: null, name?: null, content?: null): void;
|
||||
export declare function addSegment(map: GenMapping, genLine: number, genColumn: number, source: string, sourceLine: number, sourceColumn: number, name?: null, content?: string | null): void;
|
||||
export declare function addSegment(map: GenMapping, genLine: number, genColumn: number, source: string, sourceLine: number, sourceColumn: number, name: string, content?: string | null): void;
|
||||
/**
|
||||
* A high-level API to associate a generated position with an original source position. Line is
|
||||
* 1-based, but column is 0-based, due to legacy behavior in `source-map` library.
|
||||
*/
|
||||
export declare function addMapping(map: GenMapping, mapping: {
|
||||
generated: Pos;
|
||||
source?: null;
|
||||
original?: null;
|
||||
name?: null;
|
||||
content?: null;
|
||||
}): void;
|
||||
export declare function addMapping(map: GenMapping, mapping: {
|
||||
generated: Pos;
|
||||
source: string;
|
||||
original: Pos;
|
||||
name?: null;
|
||||
content?: string | null;
|
||||
}): void;
|
||||
export declare function addMapping(map: GenMapping, mapping: {
|
||||
generated: Pos;
|
||||
source: string;
|
||||
original: Pos;
|
||||
name: string;
|
||||
content?: string | null;
|
||||
}): void;
|
||||
/**
|
||||
* Same as `addSegment`, but will only add the segment if it generates useful information in the
|
||||
* resulting map. This only works correctly if segments are added **in order**, meaning you should
|
||||
* not add a segment with a lower generated line/column than one that came before.
|
||||
*/
|
||||
export declare const maybeAddSegment: typeof addSegment;
|
||||
/**
|
||||
* Same as `addMapping`, but will only add the mapping if it generates useful information in the
|
||||
* resulting map. This only works correctly if mappings are added **in order**, meaning you should
|
||||
* not add a mapping with a lower generated line/column than one that came before.
|
||||
*/
|
||||
export declare const maybeAddMapping: typeof addMapping;
|
||||
/**
|
||||
* Adds/removes the content of the source file to the source map.
|
||||
*/
|
||||
export declare function setSourceContent(map: GenMapping, source: string, content: string | null): void;
|
||||
export declare function setIgnore(map: GenMapping, source: string, ignore?: boolean): void;
|
||||
/**
|
||||
* Returns a sourcemap object (with decoded mappings) suitable for passing to a library that expects
|
||||
* a sourcemap, or to JSON.stringify.
|
||||
*/
|
||||
export declare function toDecodedMap(map: GenMapping): DecodedSourceMap;
|
||||
/**
|
||||
* Returns a sourcemap object (with encoded mappings) suitable for passing to a library that expects
|
||||
* a sourcemap, or to JSON.stringify.
|
||||
*/
|
||||
export declare function toEncodedMap(map: GenMapping): EncodedSourceMap;
|
||||
/**
|
||||
* Constructs a new GenMapping, using the already present mappings of the input.
|
||||
*/
|
||||
export declare function fromMap(input: SourceMapInput): GenMapping;
|
||||
/**
|
||||
* Returns an array of high-level mapping objects for every recorded segment, which could then be
|
||||
* passed to the `source-map` library.
|
||||
*/
|
||||
export declare function allMappings(map: GenMapping): Mapping[];
|
||||
//# sourceMappingURL=gen-mapping.d.ts.map
|
||||
1
whm/gniza4cp-whm/assets/node_modules/@jridgewell/gen-mapping/types/gen-mapping.d.mts.map
generated
vendored
Normal file
@@ -0,0 +1 @@
|
||||
{"version":3,"file":"gen-mapping.d.ts","sourceRoot":"","sources":["../src/gen-mapping.ts"],"names":[],"mappings":"AAgBA,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,2BAA2B,CAAC;AAGhE,OAAO,KAAK,EACV,gBAAgB,EAChB,gBAAgB,EAChB,GAAG,EACH,OAAO,EAKR,MAAM,SAAS,CAAC;AAEjB,YAAY,EAAE,gBAAgB,EAAE,gBAAgB,EAAE,OAAO,EAAE,CAAC;AAE5D,MAAM,MAAM,OAAO,GAAG;IACpB,IAAI,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;IACrB,UAAU,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;CAC5B,CAAC;AAIF;;GAEG;AACH,qBAAa,UAAU;IACrB,QAAgB,MAAM,CAAmB;IACzC,QAAgB,QAAQ,CAAmB;IAC3C,QAAgB,eAAe,CAAoB;IACnD,QAAgB,SAAS,CAAuB;IAGhD,QAAgB,WAAW,CAAmB;IACtC,IAAI,EAAE,MAAM,GAAG,IAAI,GAAG,SAAS,CAAC;IAChC,UAAU,EAAE,MAAM,GAAG,IAAI,GAAG,SAAS,CAAC;gBAElC,EAAE,IAAI,EAAE,UAAU,EAAE,GAAE,OAAY;CAW/C;AAoBD;;;GAGG;AACH,wBAAgB,UAAU,CACxB,GAAG,EAAE,UAAU,EACf,OAAO,EAAE,MAAM,EACf,SAAS,EAAE,MAAM,EACjB,MAAM,CAAC,EAAE,IAAI,EACb,UAAU,CAAC,EAAE,IAAI,EACjB,YAAY,CAAC,EAAE,IAAI,EACnB,IAAI,CAAC,EAAE,IAAI,EACX,OAAO,CAAC,EAAE,IAAI,GACb,IAAI,CAAC;AACR,wBAAgB,UAAU,CACxB,GAAG,EAAE,UAAU,EACf,OAAO,EAAE,MAAM,EACf,SAAS,EAAE,MAAM,EACjB,MAAM,EAAE,MAAM,EACd,UAAU,EAAE,MAAM,EAClB,YAAY,EAAE,MAAM,EACpB,IAAI,CAAC,EAAE,IAAI,EACX,OAAO,CAAC,EAAE,MAAM,GAAG,IAAI,GACtB,IAAI,CAAC;AACR,wBAAgB,UAAU,CACxB,GAAG,EAAE,UAAU,EACf,OAAO,EAAE,MAAM,EACf,SAAS,EAAE,MAAM,EACjB,MAAM,EAAE,MAAM,EACd,UAAU,EAAE,MAAM,EAClB,YAAY,EAAE,MAAM,EACpB,IAAI,EAAE,MAAM,EACZ,OAAO,CAAC,EAAE,MAAM,GAAG,IAAI,GACtB,IAAI,CAAC;AAwBR;;;GAGG;AACH,wBAAgB,UAAU,CACxB,GAAG,EAAE,UAAU,EACf,OAAO,EAAE;IACP,SAAS,EAAE,GAAG,CAAC;IACf,MAAM,CAAC,EAAE,IAAI,CAAC;IACd,QAAQ,CAAC,EAAE,IAAI,CAAC;IAChB,IAAI,CAAC,EAAE,IAAI,CAAC;IACZ,OAAO,CAAC,EAAE,IAAI,CAAC;CAChB,GACA,IAAI,CAAC;AACR,wBAAgB,UAAU,CACxB,GAAG,EAAE,UAAU,EACf,OAAO,EAAE;IACP,SAAS,EAAE,GAAG,CAAC;IACf,MAAM,EAAE,MAAM,CAAC;IACf,QAAQ,EAAE,GAAG,CAAC;IACd,IAAI,CAAC,EAAE,IAAI,CAAC;IACZ,OAAO,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;CACzB,GACA,IAAI,CAAC;AACR,wBAAgB,UAAU,CACxB,GAAG,EAAE,UAAU,EACf,OAAO,EAAE;IACP,SAAS,EAAE,GAAG,CAAC;IACf,MAAM,EAAE,MAAM,CAAC;IACf,QAAQ,EAAE,GAAG,CAAC;IACd,IAAI,EAAE,MAAM,CAAC;IACb,OAAO,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;CACzB,GACA,IAAI,CAAC;AAcR;;;;GAIG;AACH,eAAO,MAAM,eAAe,EAAE,OAAO,UAqBpC,CAAC;AAEF;;;;GAIG;AACH,eAAO,MAAM,eAAe,EAAE,OAAO,UAEpC,CAAC;AAEF;;GAEG;AACH,wBAAgB,gBAAgB,CAAC,GAAG,EAAE,UAAU,EAAE,MAAM,EAAE,MAAM,EAAE,OAAO,EAAE,MAAM,GAAG,IAAI,GAAG,IAAI,CAS9F;AAED,wBAAgB,SAAS,CAAC,GAAG,EAAE,UAAU,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,UAAO,QAYvE;AAED;;;GAGG;AACH,wBAAgB,YAAY,CAAC,GAAG,EAAE,UAAU,GAAG,gBAAgB,CAwB9D;AAED;;;GAGG;AACH,wBAAgB,YAAY,CAAC,GAAG,EAAE,UAAU,GAAG,gBAAgB,CAO9D;AAED;;GAEG;AACH,wBAAgB,OAAO,CAAC,KAAK,EAAE,cAAc,GAAG,UAAU,CAYzD;AAED;;;GAGG;AACH,wBAAgB,WAAW,CAAC,GAAG,EAAE,UAAU,GAAG,OAAO,EAAE,CA0BtD"}
|
||||
33
whm/gniza4cp-whm/assets/node_modules/@jridgewell/gen-mapping/types/set-array.d.cts
generated
vendored
Normal file
@@ -0,0 +1,33 @@
|
||||
type Key = string | number | symbol;
|
||||
/**
|
||||
* SetArray acts like a `Set` (allowing only one occurrence of a string `key`), but provides the
|
||||
* index of the `key` in the backing array.
|
||||
*
|
||||
* This is designed to allow synchronizing a second array with the contents of the backing array,
|
||||
* like how in a sourcemap `sourcesContent[i]` is the source content associated with `source[i]`,
|
||||
* and there are never duplicates.
|
||||
*/
|
||||
export declare class SetArray<T extends Key = Key> {
|
||||
private _indexes;
|
||||
array: readonly T[];
|
||||
constructor();
|
||||
}
|
||||
/**
|
||||
* Gets the index associated with `key` in the backing array, if it is already present.
|
||||
*/
|
||||
export declare function get<T extends Key>(setarr: SetArray<T>, key: T): number | undefined;
|
||||
/**
|
||||
* Puts `key` into the backing array, if it is not already present. Returns
|
||||
* the index of the `key` in the backing array.
|
||||
*/
|
||||
export declare function put<T extends Key>(setarr: SetArray<T>, key: T): number;
|
||||
/**
|
||||
* Pops the last added item out of the SetArray.
|
||||
*/
|
||||
export declare function pop<T extends Key>(setarr: SetArray<T>): void;
|
||||
/**
|
||||
* Removes the key, if it exists in the set.
|
||||
*/
|
||||
export declare function remove<T extends Key>(setarr: SetArray<T>, key: T): void;
|
||||
export {};
|
||||
//# sourceMappingURL=set-array.d.ts.map
|
||||
1
whm/gniza4cp-whm/assets/node_modules/@jridgewell/gen-mapping/types/set-array.d.cts.map
generated
vendored
Normal file
@@ -0,0 +1 @@
|
||||
{"version":3,"file":"set-array.d.ts","sourceRoot":"","sources":["../src/set-array.ts"],"names":[],"mappings":"AAAA,KAAK,GAAG,GAAG,MAAM,GAAG,MAAM,GAAG,MAAM,CAAC;AAEpC;;;;;;;GAOG;AACH,qBAAa,QAAQ,CAAC,CAAC,SAAS,GAAG,GAAG,GAAG;IACvC,QAAgB,QAAQ,CAAgC;IAChD,KAAK,EAAE,SAAS,CAAC,EAAE,CAAC;;CAM7B;AAeD;;GAEG;AACH,wBAAgB,GAAG,CAAC,CAAC,SAAS,GAAG,EAAE,MAAM,EAAE,QAAQ,CAAC,CAAC,CAAC,EAAE,GAAG,EAAE,CAAC,GAAG,MAAM,GAAG,SAAS,CAElF;AAED;;;GAGG;AACH,wBAAgB,GAAG,CAAC,CAAC,SAAS,GAAG,EAAE,MAAM,EAAE,QAAQ,CAAC,CAAC,CAAC,EAAE,GAAG,EAAE,CAAC,GAAG,MAAM,CAStE;AAED;;GAEG;AACH,wBAAgB,GAAG,CAAC,CAAC,SAAS,GAAG,EAAE,MAAM,EAAE,QAAQ,CAAC,CAAC,CAAC,GAAG,IAAI,CAM5D;AAED;;GAEG;AACH,wBAAgB,MAAM,CAAC,CAAC,SAAS,GAAG,EAAE,MAAM,EAAE,QAAQ,CAAC,CAAC,CAAC,EAAE,GAAG,EAAE,CAAC,GAAG,IAAI,CAYvE"}
|
||||
33
whm/gniza4cp-whm/assets/node_modules/@jridgewell/gen-mapping/types/set-array.d.mts
generated
vendored
Normal file
@@ -0,0 +1,33 @@
|
||||
type Key = string | number | symbol;
|
||||
/**
|
||||
* SetArray acts like a `Set` (allowing only one occurrence of a string `key`), but provides the
|
||||
* index of the `key` in the backing array.
|
||||
*
|
||||
* This is designed to allow synchronizing a second array with the contents of the backing array,
|
||||
* like how in a sourcemap `sourcesContent[i]` is the source content associated with `source[i]`,
|
||||
* and there are never duplicates.
|
||||
*/
|
||||
export declare class SetArray<T extends Key = Key> {
|
||||
private _indexes;
|
||||
array: readonly T[];
|
||||
constructor();
|
||||
}
|
||||
/**
|
||||
* Gets the index associated with `key` in the backing array, if it is already present.
|
||||
*/
|
||||
export declare function get<T extends Key>(setarr: SetArray<T>, key: T): number | undefined;
|
||||
/**
|
||||
* Puts `key` into the backing array, if it is not already present. Returns
|
||||
* the index of the `key` in the backing array.
|
||||
*/
|
||||
export declare function put<T extends Key>(setarr: SetArray<T>, key: T): number;
|
||||
/**
|
||||
* Pops the last added item out of the SetArray.
|
||||
*/
|
||||
export declare function pop<T extends Key>(setarr: SetArray<T>): void;
|
||||
/**
|
||||
* Removes the key, if it exists in the set.
|
||||
*/
|
||||
export declare function remove<T extends Key>(setarr: SetArray<T>, key: T): void;
|
||||
export {};
|
||||
//# sourceMappingURL=set-array.d.ts.map
|
||||
1
whm/gniza4cp-whm/assets/node_modules/@jridgewell/gen-mapping/types/set-array.d.mts.map
generated
vendored
Normal file
@@ -0,0 +1 @@
|
||||
{"version":3,"file":"set-array.d.ts","sourceRoot":"","sources":["../src/set-array.ts"],"names":[],"mappings":"AAAA,KAAK,GAAG,GAAG,MAAM,GAAG,MAAM,GAAG,MAAM,CAAC;AAEpC;;;;;;;GAOG;AACH,qBAAa,QAAQ,CAAC,CAAC,SAAS,GAAG,GAAG,GAAG;IACvC,QAAgB,QAAQ,CAAgC;IAChD,KAAK,EAAE,SAAS,CAAC,EAAE,CAAC;;CAM7B;AAeD;;GAEG;AACH,wBAAgB,GAAG,CAAC,CAAC,SAAS,GAAG,EAAE,MAAM,EAAE,QAAQ,CAAC,CAAC,CAAC,EAAE,GAAG,EAAE,CAAC,GAAG,MAAM,GAAG,SAAS,CAElF;AAED;;;GAGG;AACH,wBAAgB,GAAG,CAAC,CAAC,SAAS,GAAG,EAAE,MAAM,EAAE,QAAQ,CAAC,CAAC,CAAC,EAAE,GAAG,EAAE,CAAC,GAAG,MAAM,CAStE;AAED;;GAEG;AACH,wBAAgB,GAAG,CAAC,CAAC,SAAS,GAAG,EAAE,MAAM,EAAE,QAAQ,CAAC,CAAC,CAAC,GAAG,IAAI,CAM5D;AAED;;GAEG;AACH,wBAAgB,MAAM,CAAC,CAAC,SAAS,GAAG,EAAE,MAAM,EAAE,QAAQ,CAAC,CAAC,CAAC,EAAE,GAAG,EAAE,CAAC,GAAG,IAAI,CAYvE"}
|
||||
13
whm/gniza4cp-whm/assets/node_modules/@jridgewell/gen-mapping/types/sourcemap-segment.d.cts
generated
vendored
Normal file
@@ -0,0 +1,13 @@
|
||||
type GeneratedColumn = number;
|
||||
type SourcesIndex = number;
|
||||
type SourceLine = number;
|
||||
type SourceColumn = number;
|
||||
type NamesIndex = number;
|
||||
export type SourceMapSegment = [GeneratedColumn] | [GeneratedColumn, SourcesIndex, SourceLine, SourceColumn] | [GeneratedColumn, SourcesIndex, SourceLine, SourceColumn, NamesIndex];
|
||||
export declare const COLUMN = 0;
|
||||
export declare const SOURCES_INDEX = 1;
|
||||
export declare const SOURCE_LINE = 2;
|
||||
export declare const SOURCE_COLUMN = 3;
|
||||
export declare const NAMES_INDEX = 4;
|
||||
export {};
|
||||
//# sourceMappingURL=sourcemap-segment.d.ts.map
|
||||
1
whm/gniza4cp-whm/assets/node_modules/@jridgewell/gen-mapping/types/sourcemap-segment.d.cts.map
generated
vendored
Normal file
@@ -0,0 +1 @@
|
||||
{"version":3,"file":"sourcemap-segment.d.ts","sourceRoot":"","sources":["../src/sourcemap-segment.ts"],"names":[],"mappings":"AAAA,KAAK,eAAe,GAAG,MAAM,CAAC;AAC9B,KAAK,YAAY,GAAG,MAAM,CAAC;AAC3B,KAAK,UAAU,GAAG,MAAM,CAAC;AACzB,KAAK,YAAY,GAAG,MAAM,CAAC;AAC3B,KAAK,UAAU,GAAG,MAAM,CAAC;AAEzB,MAAM,MAAM,gBAAgB,GACxB,CAAC,eAAe,CAAC,GACjB,CAAC,eAAe,EAAE,YAAY,EAAE,UAAU,EAAE,YAAY,CAAC,GACzD,CAAC,eAAe,EAAE,YAAY,EAAE,UAAU,EAAE,YAAY,EAAE,UAAU,CAAC,CAAC;AAE1E,eAAO,MAAM,MAAM,IAAI,CAAC;AACxB,eAAO,MAAM,aAAa,IAAI,CAAC;AAC/B,eAAO,MAAM,WAAW,IAAI,CAAC;AAC7B,eAAO,MAAM,aAAa,IAAI,CAAC;AAC/B,eAAO,MAAM,WAAW,IAAI,CAAC"}
|
||||
13
whm/gniza4cp-whm/assets/node_modules/@jridgewell/gen-mapping/types/sourcemap-segment.d.mts
generated
vendored
Normal file
@@ -0,0 +1,13 @@
|
||||
type GeneratedColumn = number;
|
||||
type SourcesIndex = number;
|
||||
type SourceLine = number;
|
||||
type SourceColumn = number;
|
||||
type NamesIndex = number;
|
||||
export type SourceMapSegment = [GeneratedColumn] | [GeneratedColumn, SourcesIndex, SourceLine, SourceColumn] | [GeneratedColumn, SourcesIndex, SourceLine, SourceColumn, NamesIndex];
|
||||
export declare const COLUMN = 0;
|
||||
export declare const SOURCES_INDEX = 1;
|
||||
export declare const SOURCE_LINE = 2;
|
||||
export declare const SOURCE_COLUMN = 3;
|
||||
export declare const NAMES_INDEX = 4;
|
||||
export {};
|
||||
//# sourceMappingURL=sourcemap-segment.d.ts.map
|
||||
1
whm/gniza4cp-whm/assets/node_modules/@jridgewell/gen-mapping/types/sourcemap-segment.d.mts.map
generated
vendored
Normal file
@@ -0,0 +1 @@
|
||||
{"version":3,"file":"sourcemap-segment.d.ts","sourceRoot":"","sources":["../src/sourcemap-segment.ts"],"names":[],"mappings":"AAAA,KAAK,eAAe,GAAG,MAAM,CAAC;AAC9B,KAAK,YAAY,GAAG,MAAM,CAAC;AAC3B,KAAK,UAAU,GAAG,MAAM,CAAC;AACzB,KAAK,YAAY,GAAG,MAAM,CAAC;AAC3B,KAAK,UAAU,GAAG,MAAM,CAAC;AAEzB,MAAM,MAAM,gBAAgB,GACxB,CAAC,eAAe,CAAC,GACjB,CAAC,eAAe,EAAE,YAAY,EAAE,UAAU,EAAE,YAAY,CAAC,GACzD,CAAC,eAAe,EAAE,YAAY,EAAE,UAAU,EAAE,YAAY,EAAE,UAAU,CAAC,CAAC;AAE1E,eAAO,MAAM,MAAM,IAAI,CAAC;AACxB,eAAO,MAAM,aAAa,IAAI,CAAC;AAC/B,eAAO,MAAM,WAAW,IAAI,CAAC;AAC7B,eAAO,MAAM,aAAa,IAAI,CAAC;AAC/B,eAAO,MAAM,WAAW,IAAI,CAAC"}
|
||||
44
whm/gniza4cp-whm/assets/node_modules/@jridgewell/gen-mapping/types/types.d.cts
generated
vendored
Normal file
@@ -0,0 +1,44 @@
|
||||
import type { SourceMapSegment } from './sourcemap-segment.cts';
|
||||
export interface SourceMapV3 {
|
||||
file?: string | null;
|
||||
names: readonly string[];
|
||||
sourceRoot?: string;
|
||||
sources: readonly (string | null)[];
|
||||
sourcesContent?: readonly (string | null)[];
|
||||
version: 3;
|
||||
ignoreList?: readonly number[];
|
||||
}
|
||||
export interface EncodedSourceMap extends SourceMapV3 {
|
||||
mappings: string;
|
||||
}
|
||||
export interface DecodedSourceMap extends SourceMapV3 {
|
||||
mappings: readonly SourceMapSegment[][];
|
||||
}
|
||||
export interface Pos {
|
||||
line: number;
|
||||
column: number;
|
||||
}
|
||||
export interface OriginalPos extends Pos {
|
||||
source: string;
|
||||
}
|
||||
export interface BindingExpressionRange {
|
||||
start: Pos;
|
||||
expression: string;
|
||||
}
|
||||
export type Mapping = {
|
||||
generated: Pos;
|
||||
source: undefined;
|
||||
original: undefined;
|
||||
name: undefined;
|
||||
} | {
|
||||
generated: Pos;
|
||||
source: string;
|
||||
original: Pos;
|
||||
name: string;
|
||||
} | {
|
||||
generated: Pos;
|
||||
source: string;
|
||||
original: Pos;
|
||||
name: undefined;
|
||||
};
|
||||
//# sourceMappingURL=types.d.ts.map
|
||||
1
whm/gniza4cp-whm/assets/node_modules/@jridgewell/gen-mapping/types/types.d.cts.map
generated
vendored
Normal file
@@ -0,0 +1 @@
|
||||
{"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../src/types.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,gBAAgB,EAAE,MAAM,qBAAqB,CAAC;AAE5D,MAAM,WAAW,WAAW;IAC1B,IAAI,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;IACrB,KAAK,EAAE,SAAS,MAAM,EAAE,CAAC;IACzB,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,OAAO,EAAE,SAAS,CAAC,MAAM,GAAG,IAAI,CAAC,EAAE,CAAC;IACpC,cAAc,CAAC,EAAE,SAAS,CAAC,MAAM,GAAG,IAAI,CAAC,EAAE,CAAC;IAC5C,OAAO,EAAE,CAAC,CAAC;IACX,UAAU,CAAC,EAAE,SAAS,MAAM,EAAE,CAAC;CAChC;AAED,MAAM,WAAW,gBAAiB,SAAQ,WAAW;IACnD,QAAQ,EAAE,MAAM,CAAC;CAGlB;AAED,MAAM,WAAW,gBAAiB,SAAQ,WAAW;IACnD,QAAQ,EAAE,SAAS,gBAAgB,EAAE,EAAE,CAAC;CAGzC;AAED,MAAM,WAAW,GAAG;IAClB,IAAI,EAAE,MAAM,CAAC;IACb,MAAM,EAAE,MAAM,CAAC;CAChB;AAED,MAAM,WAAW,WAAY,SAAQ,GAAG;IACtC,MAAM,EAAE,MAAM,CAAC;CAChB;AAED,MAAM,WAAW,sBAAsB;IACrC,KAAK,EAAE,GAAG,CAAC;IACX,UAAU,EAAE,MAAM,CAAC;CACpB;AAKD,MAAM,MAAM,OAAO,GACf;IACE,SAAS,EAAE,GAAG,CAAC;IACf,MAAM,EAAE,SAAS,CAAC;IAClB,QAAQ,EAAE,SAAS,CAAC;IACpB,IAAI,EAAE,SAAS,CAAC;CACjB,GACD;IACE,SAAS,EAAE,GAAG,CAAC;IACf,MAAM,EAAE,MAAM,CAAC;IACf,QAAQ,EAAE,GAAG,CAAC;IACd,IAAI,EAAE,MAAM,CAAC;CACd,GACD;IACE,SAAS,EAAE,GAAG,CAAC;IACf,MAAM,EAAE,MAAM,CAAC;IACf,QAAQ,EAAE,GAAG,CAAC;IACd,IAAI,EAAE,SAAS,CAAC;CACjB,CAAC"}
|
||||
44
whm/gniza4cp-whm/assets/node_modules/@jridgewell/gen-mapping/types/types.d.mts
generated
vendored
Normal file
@@ -0,0 +1,44 @@
|
||||
import type { SourceMapSegment } from './sourcemap-segment.mts';
|
||||
export interface SourceMapV3 {
|
||||
file?: string | null;
|
||||
names: readonly string[];
|
||||
sourceRoot?: string;
|
||||
sources: readonly (string | null)[];
|
||||
sourcesContent?: readonly (string | null)[];
|
||||
version: 3;
|
||||
ignoreList?: readonly number[];
|
||||
}
|
||||
export interface EncodedSourceMap extends SourceMapV3 {
|
||||
mappings: string;
|
||||
}
|
||||
export interface DecodedSourceMap extends SourceMapV3 {
|
||||
mappings: readonly SourceMapSegment[][];
|
||||
}
|
||||
export interface Pos {
|
||||
line: number;
|
||||
column: number;
|
||||
}
|
||||
export interface OriginalPos extends Pos {
|
||||
source: string;
|
||||
}
|
||||
export interface BindingExpressionRange {
|
||||
start: Pos;
|
||||
expression: string;
|
||||
}
|
||||
export type Mapping = {
|
||||
generated: Pos;
|
||||
source: undefined;
|
||||
original: undefined;
|
||||
name: undefined;
|
||||
} | {
|
||||
generated: Pos;
|
||||
source: string;
|
||||
original: Pos;
|
||||
name: string;
|
||||
} | {
|
||||
generated: Pos;
|
||||
source: string;
|
||||
original: Pos;
|
||||
name: undefined;
|
||||
};
|
||||
//# sourceMappingURL=types.d.ts.map
|
||||
1
whm/gniza4cp-whm/assets/node_modules/@jridgewell/gen-mapping/types/types.d.mts.map
generated
vendored
Normal file
@@ -0,0 +1 @@
|
||||
{"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../src/types.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,gBAAgB,EAAE,MAAM,qBAAqB,CAAC;AAE5D,MAAM,WAAW,WAAW;IAC1B,IAAI,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;IACrB,KAAK,EAAE,SAAS,MAAM,EAAE,CAAC;IACzB,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,OAAO,EAAE,SAAS,CAAC,MAAM,GAAG,IAAI,CAAC,EAAE,CAAC;IACpC,cAAc,CAAC,EAAE,SAAS,CAAC,MAAM,GAAG,IAAI,CAAC,EAAE,CAAC;IAC5C,OAAO,EAAE,CAAC,CAAC;IACX,UAAU,CAAC,EAAE,SAAS,MAAM,EAAE,CAAC;CAChC;AAED,MAAM,WAAW,gBAAiB,SAAQ,WAAW;IACnD,QAAQ,EAAE,MAAM,CAAC;CAGlB;AAED,MAAM,WAAW,gBAAiB,SAAQ,WAAW;IACnD,QAAQ,EAAE,SAAS,gBAAgB,EAAE,EAAE,CAAC;CAGzC;AAED,MAAM,WAAW,GAAG;IAClB,IAAI,EAAE,MAAM,CAAC;IACb,MAAM,EAAE,MAAM,CAAC;CAChB;AAED,MAAM,WAAW,WAAY,SAAQ,GAAG;IACtC,MAAM,EAAE,MAAM,CAAC;CAChB;AAED,MAAM,WAAW,sBAAsB;IACrC,KAAK,EAAE,GAAG,CAAC;IACX,UAAU,EAAE,MAAM,CAAC;CACpB;AAKD,MAAM,MAAM,OAAO,GACf;IACE,SAAS,EAAE,GAAG,CAAC;IACf,MAAM,EAAE,SAAS,CAAC;IAClB,QAAQ,EAAE,SAAS,CAAC;IACpB,IAAI,EAAE,SAAS,CAAC;CACjB,GACD;IACE,SAAS,EAAE,GAAG,CAAC;IACf,MAAM,EAAE,MAAM,CAAC;IACf,QAAQ,EAAE,GAAG,CAAC;IACd,IAAI,EAAE,MAAM,CAAC;CACd,GACD;IACE,SAAS,EAAE,GAAG,CAAC;IACf,MAAM,EAAE,MAAM,CAAC;IACf,QAAQ,EAAE,GAAG,CAAC;IACd,IAAI,EAAE,SAAS,CAAC;CACjB,CAAC"}
|
||||
19
whm/gniza4cp-whm/assets/node_modules/@jridgewell/remapping/LICENSE
generated
vendored
Normal file
@@ -0,0 +1,19 @@
|
||||
Copyright 2024 Justin Ridgewell <justin@ridgewell.name>
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
of this software and associated documentation files (the "Software"), to deal
|
||||
in the Software without restriction, including without limitation the rights
|
||||
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
copies of the Software, and to permit persons to whom the Software is
|
||||
furnished to do so, subject to the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be included in
|
||||
all copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
SOFTWARE.
|
||||
218
whm/gniza4cp-whm/assets/node_modules/@jridgewell/remapping/README.md
generated
vendored
Normal file
@@ -0,0 +1,218 @@
|
||||
# @jridgewell/remapping
|
||||
|
||||
> Remap sequential sourcemaps through transformations to point at the original source code
|
||||
|
||||
Remapping allows you to take the sourcemaps generated through transforming your code and "remap"
|
||||
them to the original source locations. Think "my minified code, transformed with babel and bundled
|
||||
with webpack", all pointing to the correct location in your original source code.
|
||||
|
||||
With remapping, none of your source code transformations need to be aware of the input's sourcemap,
|
||||
they only need to generate an output sourcemap. This greatly simplifies building custom
|
||||
transformations (think a find-and-replace).
|
||||
|
||||
## Installation
|
||||
|
||||
```sh
|
||||
npm install @jridgewell/remapping
|
||||
```
|
||||
|
||||
## Usage
|
||||
|
||||
```typescript
|
||||
function remapping(
|
||||
map: SourceMap | SourceMap[],
|
||||
loader: (file: string, ctx: LoaderContext) => (SourceMap | null | undefined),
|
||||
options?: { excludeContent: boolean, decodedMappings: boolean }
|
||||
): SourceMap;
|
||||
|
||||
// LoaderContext gives the loader the importing sourcemap, tree depth, the ability to override the
|
||||
// "source" location (where child sources are resolved relative to, or the location of original
|
||||
// source), and the ability to override the "content" of an original source for inclusion in the
|
||||
// output sourcemap.
|
||||
type LoaderContext = {
|
||||
readonly importer: string;
|
||||
readonly depth: number;
|
||||
source: string;
|
||||
content: string | null | undefined;
|
||||
}
|
||||
```
|
||||
|
||||
`remapping` takes the final output sourcemap, and a `loader` function. For every source file pointer
|
||||
in the sourcemap, the `loader` will be called with the resolved path. If the path itself represents
|
||||
a transformed file (it has a sourcmap associated with it), then the `loader` should return that
|
||||
sourcemap. If not, the path will be treated as an original, untransformed source code.
|
||||
|
||||
```js
|
||||
// Babel transformed "helloworld.js" into "transformed.js"
|
||||
const transformedMap = JSON.stringify({
|
||||
file: 'transformed.js',
|
||||
// 1st column of 2nd line of output file translates into the 1st source
|
||||
// file, line 3, column 2
|
||||
mappings: ';CAEE',
|
||||
sources: ['helloworld.js'],
|
||||
version: 3,
|
||||
});
|
||||
|
||||
// Uglify minified "transformed.js" into "transformed.min.js"
|
||||
const minifiedTransformedMap = JSON.stringify({
|
||||
file: 'transformed.min.js',
|
||||
// 0th column of 1st line of output file translates into the 1st source
|
||||
// file, line 2, column 1.
|
||||
mappings: 'AACC',
|
||||
names: [],
|
||||
sources: ['transformed.js'],
|
||||
version: 3,
|
||||
});
|
||||
|
||||
const remapped = remapping(
|
||||
minifiedTransformedMap,
|
||||
(file, ctx) => {
|
||||
|
||||
// The "transformed.js" file is an transformed file.
|
||||
if (file === 'transformed.js') {
|
||||
// The root importer is empty.
|
||||
console.assert(ctx.importer === '');
|
||||
// The depth in the sourcemap tree we're currently loading.
|
||||
// The root `minifiedTransformedMap` is depth 0, and its source children are depth 1, etc.
|
||||
console.assert(ctx.depth === 1);
|
||||
|
||||
return transformedMap;
|
||||
}
|
||||
|
||||
// Loader will be called to load transformedMap's source file pointers as well.
|
||||
console.assert(file === 'helloworld.js');
|
||||
// `transformed.js`'s sourcemap points into `helloworld.js`.
|
||||
console.assert(ctx.importer === 'transformed.js');
|
||||
// This is a source child of `transformed`, which is a source child of `minifiedTransformedMap`.
|
||||
console.assert(ctx.depth === 2);
|
||||
return null;
|
||||
}
|
||||
);
|
||||
|
||||
console.log(remapped);
|
||||
// {
|
||||
// file: 'transpiled.min.js',
|
||||
// mappings: 'AAEE',
|
||||
// sources: ['helloworld.js'],
|
||||
// version: 3,
|
||||
// };
|
||||
```
|
||||
|
||||
In this example, `loader` will be called twice:
|
||||
|
||||
1. `"transformed.js"`, the first source file pointer in the `minifiedTransformedMap`. We return the
|
||||
associated sourcemap for it (its a transformed file, after all) so that sourcemap locations can
|
||||
be traced through it into the source files it represents.
|
||||
2. `"helloworld.js"`, our original, unmodified source code. This file does not have a sourcemap, so
|
||||
we return `null`.
|
||||
|
||||
The `remapped` sourcemap now points from `transformed.min.js` into locations in `helloworld.js`. If
|
||||
you were to read the `mappings`, it says "0th column of the first line output line points to the 1st
|
||||
column of the 2nd line of the file `helloworld.js`".
|
||||
|
||||
### Multiple transformations of a file
|
||||
|
||||
As a convenience, if you have multiple single-source transformations of a file, you may pass an
|
||||
array of sourcemap files in the order of most-recent transformation sourcemap first. Note that this
|
||||
changes the `importer` and `depth` of each call to our loader. So our above example could have been
|
||||
written as:
|
||||
|
||||
```js
|
||||
const remapped = remapping(
|
||||
[minifiedTransformedMap, transformedMap],
|
||||
() => null
|
||||
);
|
||||
|
||||
console.log(remapped);
|
||||
// {
|
||||
// file: 'transpiled.min.js',
|
||||
// mappings: 'AAEE',
|
||||
// sources: ['helloworld.js'],
|
||||
// version: 3,
|
||||
// };
|
||||
```
|
||||
|
||||
### Advanced control of the loading graph
|
||||
|
||||
#### `source`
|
||||
|
||||
The `source` property can overridden to any value to change the location of the current load. Eg,
|
||||
for an original source file, it allows us to change the location to the original source regardless
|
||||
of what the sourcemap source entry says. And for transformed files, it allows us to change the
|
||||
relative resolving location for child sources of the loaded sourcemap.
|
||||
|
||||
```js
|
||||
const remapped = remapping(
|
||||
minifiedTransformedMap,
|
||||
(file, ctx) => {
|
||||
|
||||
if (file === 'transformed.js') {
|
||||
// We pretend the transformed.js file actually exists in the 'src/' directory. When the nested
|
||||
// source files are loaded, they will now be relative to `src/`.
|
||||
ctx.source = 'src/transformed.js';
|
||||
return transformedMap;
|
||||
}
|
||||
|
||||
console.assert(file === 'src/helloworld.js');
|
||||
// We could futher change the source of this original file, eg, to be inside a nested directory
|
||||
// itself. This will be reflected in the remapped sourcemap.
|
||||
ctx.source = 'src/nested/transformed.js';
|
||||
return null;
|
||||
}
|
||||
);
|
||||
|
||||
console.log(remapped);
|
||||
// {
|
||||
// …,
|
||||
// sources: ['src/nested/helloworld.js'],
|
||||
// };
|
||||
```
|
||||
|
||||
|
||||
#### `content`
|
||||
|
||||
The `content` property can be overridden when we encounter an original source file. Eg, this allows
|
||||
you to manually provide the source content of the original file regardless of whether the
|
||||
`sourcesContent` field is present in the parent sourcemap. It can also be set to `null` to remove
|
||||
the source content.
|
||||
|
||||
```js
|
||||
const remapped = remapping(
|
||||
minifiedTransformedMap,
|
||||
(file, ctx) => {
|
||||
|
||||
if (file === 'transformed.js') {
|
||||
// transformedMap does not include a `sourcesContent` field, so usually the remapped sourcemap
|
||||
// would not include any `sourcesContent` values.
|
||||
return transformedMap;
|
||||
}
|
||||
|
||||
console.assert(file === 'helloworld.js');
|
||||
// We can read the file to provide the source content.
|
||||
ctx.content = fs.readFileSync(file, 'utf8');
|
||||
return null;
|
||||
}
|
||||
);
|
||||
|
||||
console.log(remapped);
|
||||
// {
|
||||
// …,
|
||||
// sourcesContent: [
|
||||
// 'console.log("Hello world!")',
|
||||
// ],
|
||||
// };
|
||||
```
|
||||
|
||||
### Options
|
||||
|
||||
#### excludeContent
|
||||
|
||||
By default, `excludeContent` is `false`. Passing `{ excludeContent: true }` will exclude the
|
||||
`sourcesContent` field from the returned sourcemap. This is mainly useful when you want to reduce
|
||||
the size out the sourcemap.
|
||||
|
||||
#### decodedMappings
|
||||
|
||||
By default, `decodedMappings` is `false`. Passing `{ decodedMappings: true }` will leave the
|
||||
`mappings` field in a [decoded state](https://github.com/rich-harris/sourcemap-codec) instead of
|
||||
encoding into a VLQ string.
|
||||
144
whm/gniza4cp-whm/assets/node_modules/@jridgewell/remapping/dist/remapping.mjs
generated
vendored
Normal file
@@ -0,0 +1,144 @@
|
||||
// src/build-source-map-tree.ts
|
||||
import { TraceMap } from "@jridgewell/trace-mapping";
|
||||
|
||||
// src/source-map-tree.ts
|
||||
import { GenMapping, maybeAddSegment, setIgnore, setSourceContent } from "@jridgewell/gen-mapping";
|
||||
import { traceSegment, decodedMappings } from "@jridgewell/trace-mapping";
|
||||
var SOURCELESS_MAPPING = /* @__PURE__ */ SegmentObject("", -1, -1, "", null, false);
|
||||
var EMPTY_SOURCES = [];
|
||||
function SegmentObject(source, line, column, name, content, ignore) {
|
||||
return { source, line, column, name, content, ignore };
|
||||
}
|
||||
function Source(map, sources, source, content, ignore) {
|
||||
return {
|
||||
map,
|
||||
sources,
|
||||
source,
|
||||
content,
|
||||
ignore
|
||||
};
|
||||
}
|
||||
function MapSource(map, sources) {
|
||||
return Source(map, sources, "", null, false);
|
||||
}
|
||||
function OriginalSource(source, content, ignore) {
|
||||
return Source(null, EMPTY_SOURCES, source, content, ignore);
|
||||
}
|
||||
function traceMappings(tree) {
|
||||
const gen = new GenMapping({ file: tree.map.file });
|
||||
const { sources: rootSources, map } = tree;
|
||||
const rootNames = map.names;
|
||||
const rootMappings = decodedMappings(map);
|
||||
for (let i = 0; i < rootMappings.length; i++) {
|
||||
const segments = rootMappings[i];
|
||||
for (let j = 0; j < segments.length; j++) {
|
||||
const segment = segments[j];
|
||||
const genCol = segment[0];
|
||||
let traced = SOURCELESS_MAPPING;
|
||||
if (segment.length !== 1) {
|
||||
const source2 = rootSources[segment[1]];
|
||||
traced = originalPositionFor(
|
||||
source2,
|
||||
segment[2],
|
||||
segment[3],
|
||||
segment.length === 5 ? rootNames[segment[4]] : ""
|
||||
);
|
||||
if (traced == null) continue;
|
||||
}
|
||||
const { column, line, name, content, source, ignore } = traced;
|
||||
maybeAddSegment(gen, i, genCol, source, line, column, name);
|
||||
if (source && content != null) setSourceContent(gen, source, content);
|
||||
if (ignore) setIgnore(gen, source, true);
|
||||
}
|
||||
}
|
||||
return gen;
|
||||
}
|
||||
function originalPositionFor(source, line, column, name) {
|
||||
if (!source.map) {
|
||||
return SegmentObject(source.source, line, column, name, source.content, source.ignore);
|
||||
}
|
||||
const segment = traceSegment(source.map, line, column);
|
||||
if (segment == null) return null;
|
||||
if (segment.length === 1) return SOURCELESS_MAPPING;
|
||||
return originalPositionFor(
|
||||
source.sources[segment[1]],
|
||||
segment[2],
|
||||
segment[3],
|
||||
segment.length === 5 ? source.map.names[segment[4]] : name
|
||||
);
|
||||
}
|
||||
|
||||
// src/build-source-map-tree.ts
|
||||
function asArray(value) {
|
||||
if (Array.isArray(value)) return value;
|
||||
return [value];
|
||||
}
|
||||
function buildSourceMapTree(input, loader) {
|
||||
const maps = asArray(input).map((m) => new TraceMap(m, ""));
|
||||
const map = maps.pop();
|
||||
for (let i = 0; i < maps.length; i++) {
|
||||
if (maps[i].sources.length > 1) {
|
||||
throw new Error(
|
||||
`Transformation map ${i} must have exactly one source file.
|
||||
Did you specify these with the most recent transformation maps first?`
|
||||
);
|
||||
}
|
||||
}
|
||||
let tree = build(map, loader, "", 0);
|
||||
for (let i = maps.length - 1; i >= 0; i--) {
|
||||
tree = MapSource(maps[i], [tree]);
|
||||
}
|
||||
return tree;
|
||||
}
|
||||
function build(map, loader, importer, importerDepth) {
|
||||
const { resolvedSources, sourcesContent, ignoreList } = map;
|
||||
const depth = importerDepth + 1;
|
||||
const children = resolvedSources.map((sourceFile, i) => {
|
||||
const ctx = {
|
||||
importer,
|
||||
depth,
|
||||
source: sourceFile || "",
|
||||
content: void 0,
|
||||
ignore: void 0
|
||||
};
|
||||
const sourceMap = loader(ctx.source, ctx);
|
||||
const { source, content, ignore } = ctx;
|
||||
if (sourceMap) return build(new TraceMap(sourceMap, source), loader, source, depth);
|
||||
const sourceContent = content !== void 0 ? content : sourcesContent ? sourcesContent[i] : null;
|
||||
const ignored = ignore !== void 0 ? ignore : ignoreList ? ignoreList.includes(i) : false;
|
||||
return OriginalSource(source, sourceContent, ignored);
|
||||
});
|
||||
return MapSource(map, children);
|
||||
}
|
||||
|
||||
// src/source-map.ts
|
||||
import { toDecodedMap, toEncodedMap } from "@jridgewell/gen-mapping";
|
||||
var SourceMap = class {
|
||||
constructor(map, options) {
|
||||
const out = options.decodedMappings ? toDecodedMap(map) : toEncodedMap(map);
|
||||
this.version = out.version;
|
||||
this.file = out.file;
|
||||
this.mappings = out.mappings;
|
||||
this.names = out.names;
|
||||
this.ignoreList = out.ignoreList;
|
||||
this.sourceRoot = out.sourceRoot;
|
||||
this.sources = out.sources;
|
||||
if (!options.excludeContent) {
|
||||
this.sourcesContent = out.sourcesContent;
|
||||
}
|
||||
}
|
||||
toString() {
|
||||
return JSON.stringify(this);
|
||||
}
|
||||
};
|
||||
|
||||
// src/remapping.ts
|
||||
function remapping(input, loader, options) {
|
||||
const opts = typeof options === "object" ? options : { excludeContent: !!options, decodedMappings: false };
|
||||
const tree = buildSourceMapTree(input, loader);
|
||||
return new SourceMap(traceMappings(tree), opts);
|
||||
}
|
||||
export {
|
||||
remapping as default
|
||||
};
|
||||
//# sourceMappingURL=remapping.mjs.map
|
||||
6
whm/gniza4cp-whm/assets/node_modules/@jridgewell/remapping/dist/remapping.mjs.map
generated
vendored
Normal file
@@ -0,0 +1,6 @@
|
||||
{
|
||||
"version": 3,
|
||||
"sources": ["../src/build-source-map-tree.ts", "../src/source-map-tree.ts", "../src/source-map.ts", "../src/remapping.ts"],
|
||||
"mappings": ";AAAA,SAAS,gBAAgB;;;ACAzB,SAAS,YAAY,iBAAiB,WAAW,wBAAwB;AACzE,SAAS,cAAc,uBAAuB;AA+B9C,IAAM,qBAAqC,8BAAc,IAAI,IAAI,IAAI,IAAI,MAAM,KAAK;AACpF,IAAM,gBAA2B,CAAC;AAElC,SAAS,cACP,QACA,MACA,QACA,MACA,SACA,QACwB;AACxB,SAAO,EAAE,QAAQ,MAAM,QAAQ,MAAM,SAAS,OAAO;AACvD;AAgBA,SAAS,OACP,KACA,SACA,QACA,SACA,QACS;AACT,SAAO;AAAA,IACL;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,EACF;AACF;AAMO,SAAS,UAAU,KAAe,SAA+B;AACtE,SAAO,OAAO,KAAK,SAAS,IAAI,MAAM,KAAK;AAC7C;AAMO,SAAS,eACd,QACA,SACA,QACgB;AAChB,SAAO,OAAO,MAAM,eAAe,QAAQ,SAAS,MAAM;AAC5D;AAMO,SAAS,cAAc,MAA6B;AAGzD,QAAM,MAAM,IAAI,WAAW,EAAE,MAAM,KAAK,IAAI,KAAK,CAAC;AAClD,QAAM,EAAE,SAAS,aAAa,IAAI,IAAI;AACtC,QAAM,YAAY,IAAI;AACtB,QAAM,eAAe,gBAAgB,GAAG;AAExC,WAAS,IAAI,GAAG,IAAI,aAAa,QAAQ,KAAK;AAC5C,UAAM,WAAW,aAAa,CAAC;AAE/B,aAAS,IAAI,GAAG,IAAI,SAAS,QAAQ,KAAK;AACxC,YAAM,UAAU,SAAS,CAAC;AAC1B,YAAM,SAAS,QAAQ,CAAC;AACxB,UAAI,SAAwC;AAI5C,UAAI,QAAQ,WAAW,GAAG;AACxB,cAAMA,UAAS,YAAY,QAAQ,CAAC,CAAC;AACrC,iBAAS;AAAA,UACPA;AAAA,UACA,QAAQ,CAAC;AAAA,UACT,QAAQ,CAAC;AAAA,UACT,QAAQ,WAAW,IAAI,UAAU,QAAQ,CAAC,CAAC,IAAI;AAAA,QACjD;AAIA,YAAI,UAAU,KAAM;AAAA,MACtB;AAEA,YAAM,EAAE,QAAQ,MAAM,MAAM,SAAS,QAAQ,OAAO,IAAI;AAExD,sBAAgB,KAAK,GAAG,QAAQ,QAAQ,MAAM,QAAQ,IAAI;AAC1D,UAAI,UAAU,WAAW,KAAM,kBAAiB,KAAK,QAAQ,OAAO;AACpE,UAAI,OAAQ,WAAU,KAAK,QAAQ,IAAI;AAAA,IACzC;AAAA,EACF;AAEA,SAAO;AACT;AAMO,SAAS,oBACd,QACA,MACA,QACA,MAC+B;AAC/B,MAAI,CAAC,OAAO,KAAK;AACf,WAAO,cAAc,OAAO,QAAQ,MAAM,QAAQ,MAAM,OAAO,SAAS,OAAO,MAAM;AAAA,EACvF;AAEA,QAAM,UAAU,aAAa,OAAO,KAAK,MAAM,MAAM;AAGrD,MAAI,WAAW,KAAM,QAAO;AAG5B,MAAI,QAAQ,WAAW,EAAG,QAAO;AAEjC,SAAO;AAAA,IACL,OAAO,QAAQ,QAAQ,CAAC,CAAC;AAAA,IACzB,QAAQ,CAAC;AAAA,IACT,QAAQ,CAAC;AAAA,IACT,QAAQ,WAAW,IAAI,OAAO,IAAI,MAAM,QAAQ,CAAC,CAAC,IAAI;AAAA,EACxD;AACF;;;ADpKA,SAAS,QAAW,OAAqB;AACvC,MAAI,MAAM,QAAQ,KAAK,EAAG,QAAO;AACjC,SAAO,CAAC,KAAK;AACf;AAae,SAAR,mBACL,OACA,QACe;AACf,QAAM,OAAO,QAAQ,KAAK,EAAE,IAAI,CAAC,MAAM,IAAI,SAAS,GAAG,EAAE,CAAC;AAC1D,QAAM,MAAM,KAAK,IAAI;AAErB,WAAS,IAAI,GAAG,IAAI,KAAK,QAAQ,KAAK;AACpC,QAAI,KAAK,CAAC,EAAE,QAAQ,SAAS,GAAG;AAC9B,YAAM,IAAI;AAAA,QACR,sBAAsB,CAAC;AAAA;AAAA,MAEzB;AAAA,IACF;AAAA,EACF;AAEA,MAAI,OAAO,MAAM,KAAK,QAAQ,IAAI,CAAC;AACnC,WAAS,IAAI,KAAK,SAAS,GAAG,KAAK,GAAG,KAAK;AACzC,WAAO,UAAU,KAAK,CAAC,GAAG,CAAC,IAAI,CAAC;AAAA,EAClC;AACA,SAAO;AACT;AAEA,SAAS,MACP,KACA,QACA,UACA,eACe;AACf,QAAM,EAAE,iBAAiB,gBAAgB,WAAW,IAAI;AAExD,QAAM,QAAQ,gBAAgB;AAC9B,QAAM,WAAW,gBAAgB,IAAI,CAAC,YAA2B,MAAuB;AAKtF,UAAM,MAAqB;AAAA,MACzB;AAAA,MACA;AAAA,MACA,QAAQ,cAAc;AAAA,MACtB,SAAS;AAAA,MACT,QAAQ;AAAA,IACV;AAIA,UAAM,YAAY,OAAO,IAAI,QAAQ,GAAG;AAExC,UAAM,EAAE,QAAQ,SAAS,OAAO,IAAI;AAGpC,QAAI,UAAW,QAAO,MAAM,IAAI,SAAS,WAAW,MAAM,GAAG,QAAQ,QAAQ,KAAK;AAMlF,UAAM,gBACJ,YAAY,SAAY,UAAU,iBAAiB,eAAe,CAAC,IAAI;AACzE,UAAM,UAAU,WAAW,SAAY,SAAS,aAAa,WAAW,SAAS,CAAC,IAAI;AACtF,WAAO,eAAe,QAAQ,eAAe,OAAO;AAAA,EACtD,CAAC;AAED,SAAO,UAAU,KAAK,QAAQ;AAChC;;;AExFA,SAAS,cAAc,oBAAoB;AAS3C,IAAqB,YAArB,MAA+B;AAAA,EAU7B,YAAY,KAAiB,SAAkB;AAC7C,UAAM,MAAM,QAAQ,kBAAkB,aAAa,GAAG,IAAI,aAAa,GAAG;AAC1E,SAAK,UAAU,IAAI;AACnB,SAAK,OAAO,IAAI;AAChB,SAAK,WAAW,IAAI;AACpB,SAAK,QAAQ,IAAI;AACjB,SAAK,aAAa,IAAI;AACtB,SAAK,aAAa,IAAI;AAEtB,SAAK,UAAU,IAAI;AACnB,QAAI,CAAC,QAAQ,gBAAgB;AAC3B,WAAK,iBAAiB,IAAI;AAAA,IAC5B;AAAA,EACF;AAAA,EAEA,WAAmB;AACjB,WAAO,KAAK,UAAU,IAAI;AAAA,EAC5B;AACF;;;ACLe,SAAR,UACL,OACA,QACA,SACW;AACX,QAAM,OACJ,OAAO,YAAY,WAAW,UAAU,EAAE,gBAAgB,CAAC,CAAC,SAAS,iBAAiB,MAAM;AAC9F,QAAM,OAAO,mBAAmB,OAAO,MAAM;AAC7C,SAAO,IAAI,UAAU,cAAc,IAAI,GAAG,IAAI;AAChD;",
|
||||
"names": ["source"]
|
||||
}
|
||||
212
whm/gniza4cp-whm/assets/node_modules/@jridgewell/remapping/dist/remapping.umd.js
generated
vendored
Normal file
@@ -0,0 +1,212 @@
|
||||
(function (global, factory) {
|
||||
if (typeof exports === 'object' && typeof module !== 'undefined') {
|
||||
factory(module, require('@jridgewell/gen-mapping'), require('@jridgewell/trace-mapping'));
|
||||
module.exports = def(module);
|
||||
} else if (typeof define === 'function' && define.amd) {
|
||||
define(['module', '@jridgewell/gen-mapping', '@jridgewell/trace-mapping'], function(mod) {
|
||||
factory.apply(this, arguments);
|
||||
mod.exports = def(mod);
|
||||
});
|
||||
} else {
|
||||
const mod = { exports: {} };
|
||||
factory(mod, global.genMapping, global.traceMapping);
|
||||
global = typeof globalThis !== 'undefined' ? globalThis : global || self;
|
||||
global.remapping = def(mod);
|
||||
}
|
||||
function def(m) { return 'default' in m.exports ? m.exports.default : m.exports; }
|
||||
})(this, (function (module, require_genMapping, require_traceMapping) {
|
||||
"use strict";
|
||||
var __create = Object.create;
|
||||
var __defProp = Object.defineProperty;
|
||||
var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
|
||||
var __getOwnPropNames = Object.getOwnPropertyNames;
|
||||
var __getProtoOf = Object.getPrototypeOf;
|
||||
var __hasOwnProp = Object.prototype.hasOwnProperty;
|
||||
var __commonJS = (cb, mod) => function __require() {
|
||||
return mod || (0, cb[__getOwnPropNames(cb)[0]])((mod = { exports: {} }).exports, mod), mod.exports;
|
||||
};
|
||||
var __export = (target, all) => {
|
||||
for (var name in all)
|
||||
__defProp(target, name, { get: all[name], enumerable: true });
|
||||
};
|
||||
var __copyProps = (to, from, except, desc) => {
|
||||
if (from && typeof from === "object" || typeof from === "function") {
|
||||
for (let key of __getOwnPropNames(from))
|
||||
if (!__hasOwnProp.call(to, key) && key !== except)
|
||||
__defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
|
||||
}
|
||||
return to;
|
||||
};
|
||||
var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__getProtoOf(mod)) : {}, __copyProps(
|
||||
// If the importer is in node compatibility mode or this is not an ESM
|
||||
// file that has been converted to a CommonJS file using a Babel-
|
||||
// compatible transform (i.e. "__esModule" has not been set), then set
|
||||
// "default" to the CommonJS "module.exports" for node compatibility.
|
||||
isNodeMode || !mod || !mod.__esModule ? __defProp(target, "default", { value: mod, enumerable: true }) : target,
|
||||
mod
|
||||
));
|
||||
var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
|
||||
|
||||
// umd:@jridgewell/trace-mapping
|
||||
var require_trace_mapping = __commonJS({
|
||||
"umd:@jridgewell/trace-mapping"(exports, module2) {
|
||||
module2.exports = require_traceMapping;
|
||||
}
|
||||
});
|
||||
|
||||
// umd:@jridgewell/gen-mapping
|
||||
var require_gen_mapping = __commonJS({
|
||||
"umd:@jridgewell/gen-mapping"(exports, module2) {
|
||||
module2.exports = require_genMapping;
|
||||
}
|
||||
});
|
||||
|
||||
// src/remapping.ts
|
||||
var remapping_exports = {};
|
||||
__export(remapping_exports, {
|
||||
default: () => remapping
|
||||
});
|
||||
module.exports = __toCommonJS(remapping_exports);
|
||||
|
||||
// src/build-source-map-tree.ts
|
||||
var import_trace_mapping2 = __toESM(require_trace_mapping());
|
||||
|
||||
// src/source-map-tree.ts
|
||||
var import_gen_mapping = __toESM(require_gen_mapping());
|
||||
var import_trace_mapping = __toESM(require_trace_mapping());
|
||||
var SOURCELESS_MAPPING = /* @__PURE__ */ SegmentObject("", -1, -1, "", null, false);
|
||||
var EMPTY_SOURCES = [];
|
||||
function SegmentObject(source, line, column, name, content, ignore) {
|
||||
return { source, line, column, name, content, ignore };
|
||||
}
|
||||
function Source(map, sources, source, content, ignore) {
|
||||
return {
|
||||
map,
|
||||
sources,
|
||||
source,
|
||||
content,
|
||||
ignore
|
||||
};
|
||||
}
|
||||
function MapSource(map, sources) {
|
||||
return Source(map, sources, "", null, false);
|
||||
}
|
||||
function OriginalSource(source, content, ignore) {
|
||||
return Source(null, EMPTY_SOURCES, source, content, ignore);
|
||||
}
|
||||
function traceMappings(tree) {
|
||||
const gen = new import_gen_mapping.GenMapping({ file: tree.map.file });
|
||||
const { sources: rootSources, map } = tree;
|
||||
const rootNames = map.names;
|
||||
const rootMappings = (0, import_trace_mapping.decodedMappings)(map);
|
||||
for (let i = 0; i < rootMappings.length; i++) {
|
||||
const segments = rootMappings[i];
|
||||
for (let j = 0; j < segments.length; j++) {
|
||||
const segment = segments[j];
|
||||
const genCol = segment[0];
|
||||
let traced = SOURCELESS_MAPPING;
|
||||
if (segment.length !== 1) {
|
||||
const source2 = rootSources[segment[1]];
|
||||
traced = originalPositionFor(
|
||||
source2,
|
||||
segment[2],
|
||||
segment[3],
|
||||
segment.length === 5 ? rootNames[segment[4]] : ""
|
||||
);
|
||||
if (traced == null) continue;
|
||||
}
|
||||
const { column, line, name, content, source, ignore } = traced;
|
||||
(0, import_gen_mapping.maybeAddSegment)(gen, i, genCol, source, line, column, name);
|
||||
if (source && content != null) (0, import_gen_mapping.setSourceContent)(gen, source, content);
|
||||
if (ignore) (0, import_gen_mapping.setIgnore)(gen, source, true);
|
||||
}
|
||||
}
|
||||
return gen;
|
||||
}
|
||||
function originalPositionFor(source, line, column, name) {
|
||||
if (!source.map) {
|
||||
return SegmentObject(source.source, line, column, name, source.content, source.ignore);
|
||||
}
|
||||
const segment = (0, import_trace_mapping.traceSegment)(source.map, line, column);
|
||||
if (segment == null) return null;
|
||||
if (segment.length === 1) return SOURCELESS_MAPPING;
|
||||
return originalPositionFor(
|
||||
source.sources[segment[1]],
|
||||
segment[2],
|
||||
segment[3],
|
||||
segment.length === 5 ? source.map.names[segment[4]] : name
|
||||
);
|
||||
}
|
||||
|
||||
// src/build-source-map-tree.ts
|
||||
function asArray(value) {
|
||||
if (Array.isArray(value)) return value;
|
||||
return [value];
|
||||
}
|
||||
function buildSourceMapTree(input, loader) {
|
||||
const maps = asArray(input).map((m) => new import_trace_mapping2.TraceMap(m, ""));
|
||||
const map = maps.pop();
|
||||
for (let i = 0; i < maps.length; i++) {
|
||||
if (maps[i].sources.length > 1) {
|
||||
throw new Error(
|
||||
`Transformation map ${i} must have exactly one source file.
|
||||
Did you specify these with the most recent transformation maps first?`
|
||||
);
|
||||
}
|
||||
}
|
||||
let tree = build(map, loader, "", 0);
|
||||
for (let i = maps.length - 1; i >= 0; i--) {
|
||||
tree = MapSource(maps[i], [tree]);
|
||||
}
|
||||
return tree;
|
||||
}
|
||||
function build(map, loader, importer, importerDepth) {
|
||||
const { resolvedSources, sourcesContent, ignoreList } = map;
|
||||
const depth = importerDepth + 1;
|
||||
const children = resolvedSources.map((sourceFile, i) => {
|
||||
const ctx = {
|
||||
importer,
|
||||
depth,
|
||||
source: sourceFile || "",
|
||||
content: void 0,
|
||||
ignore: void 0
|
||||
};
|
||||
const sourceMap = loader(ctx.source, ctx);
|
||||
const { source, content, ignore } = ctx;
|
||||
if (sourceMap) return build(new import_trace_mapping2.TraceMap(sourceMap, source), loader, source, depth);
|
||||
const sourceContent = content !== void 0 ? content : sourcesContent ? sourcesContent[i] : null;
|
||||
const ignored = ignore !== void 0 ? ignore : ignoreList ? ignoreList.includes(i) : false;
|
||||
return OriginalSource(source, sourceContent, ignored);
|
||||
});
|
||||
return MapSource(map, children);
|
||||
}
|
||||
|
||||
// src/source-map.ts
|
||||
var import_gen_mapping2 = __toESM(require_gen_mapping());
|
||||
var SourceMap = class {
|
||||
constructor(map, options) {
|
||||
const out = options.decodedMappings ? (0, import_gen_mapping2.toDecodedMap)(map) : (0, import_gen_mapping2.toEncodedMap)(map);
|
||||
this.version = out.version;
|
||||
this.file = out.file;
|
||||
this.mappings = out.mappings;
|
||||
this.names = out.names;
|
||||
this.ignoreList = out.ignoreList;
|
||||
this.sourceRoot = out.sourceRoot;
|
||||
this.sources = out.sources;
|
||||
if (!options.excludeContent) {
|
||||
this.sourcesContent = out.sourcesContent;
|
||||
}
|
||||
}
|
||||
toString() {
|
||||
return JSON.stringify(this);
|
||||
}
|
||||
};
|
||||
|
||||
// src/remapping.ts
|
||||
function remapping(input, loader, options) {
|
||||
const opts = typeof options === "object" ? options : { excludeContent: !!options, decodedMappings: false };
|
||||
const tree = buildSourceMapTree(input, loader);
|
||||
return new SourceMap(traceMappings(tree), opts);
|
||||
}
|
||||
}));
|
||||
//# sourceMappingURL=remapping.umd.js.map
|
||||
6
whm/gniza4cp-whm/assets/node_modules/@jridgewell/remapping/dist/remapping.umd.js.map
generated
vendored
Normal file
@@ -0,0 +1,6 @@
|
||||
{
|
||||
"version": 3,
|
||||
"sources": ["umd:@jridgewell/trace-mapping", "umd:@jridgewell/gen-mapping", "../src/remapping.ts", "../src/build-source-map-tree.ts", "../src/source-map-tree.ts", "../src/source-map.ts"],
|
||||
"mappings": ";;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;AAAA;AAAA,2CAAAA,SAAA;AAAA,IAAAA,QAAO,UAAU;AAAA;AAAA;;;ACAjB;AAAA,yCAAAC,SAAA;AAAA,IAAAA,QAAO,UAAU;AAAA;AAAA;;;ACAjB;AAAA;AAAA;AAAA;AAAA;;;ACAA,IAAAC,wBAAyB;;;ACAzB,yBAAyE;AACzE,2BAA8C;AA+B9C,IAAM,qBAAqC,8BAAc,IAAI,IAAI,IAAI,IAAI,MAAM,KAAK;AACpF,IAAM,gBAA2B,CAAC;AAElC,SAAS,cACP,QACA,MACA,QACA,MACA,SACA,QACwB;AACxB,SAAO,EAAE,QAAQ,MAAM,QAAQ,MAAM,SAAS,OAAO;AACvD;AAgBA,SAAS,OACP,KACA,SACA,QACA,SACA,QACS;AACT,SAAO;AAAA,IACL;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,EACF;AACF;AAMO,SAAS,UAAU,KAAe,SAA+B;AACtE,SAAO,OAAO,KAAK,SAAS,IAAI,MAAM,KAAK;AAC7C;AAMO,SAAS,eACd,QACA,SACA,QACgB;AAChB,SAAO,OAAO,MAAM,eAAe,QAAQ,SAAS,MAAM;AAC5D;AAMO,SAAS,cAAc,MAA6B;AAGzD,QAAM,MAAM,IAAI,8BAAW,EAAE,MAAM,KAAK,IAAI,KAAK,CAAC;AAClD,QAAM,EAAE,SAAS,aAAa,IAAI,IAAI;AACtC,QAAM,YAAY,IAAI;AACtB,QAAM,mBAAe,sCAAgB,GAAG;AAExC,WAAS,IAAI,GAAG,IAAI,aAAa,QAAQ,KAAK;AAC5C,UAAM,WAAW,aAAa,CAAC;AAE/B,aAAS,IAAI,GAAG,IAAI,SAAS,QAAQ,KAAK;AACxC,YAAM,UAAU,SAAS,CAAC;AAC1B,YAAM,SAAS,QAAQ,CAAC;AACxB,UAAI,SAAwC;AAI5C,UAAI,QAAQ,WAAW,GAAG;AACxB,cAAMC,UAAS,YAAY,QAAQ,CAAC,CAAC;AACrC,iBAAS;AAAA,UACPA;AAAA,UACA,QAAQ,CAAC;AAAA,UACT,QAAQ,CAAC;AAAA,UACT,QAAQ,WAAW,IAAI,UAAU,QAAQ,CAAC,CAAC,IAAI;AAAA,QACjD;AAIA,YAAI,UAAU,KAAM;AAAA,MACtB;AAEA,YAAM,EAAE,QAAQ,MAAM,MAAM,SAAS,QAAQ,OAAO,IAAI;AAExD,8CAAgB,KAAK,GAAG,QAAQ,QAAQ,MAAM,QAAQ,IAAI;AAC1D,UAAI,UAAU,WAAW,KAAM,0CAAiB,KAAK,QAAQ,OAAO;AACpE,UAAI,OAAQ,mCAAU,KAAK,QAAQ,IAAI;AAAA,IACzC;AAAA,EACF;AAEA,SAAO;AACT;AAMO,SAAS,oBACd,QACA,MACA,QACA,MAC+B;AAC/B,MAAI,CAAC,OAAO,KAAK;AACf,WAAO,cAAc,OAAO,QAAQ,MAAM,QAAQ,MAAM,OAAO,SAAS,OAAO,MAAM;AAAA,EACvF;AAEA,QAAM,cAAU,mCAAa,OAAO,KAAK,MAAM,MAAM;AAGrD,MAAI,WAAW,KAAM,QAAO;AAG5B,MAAI,QAAQ,WAAW,EAAG,QAAO;AAEjC,SAAO;AAAA,IACL,OAAO,QAAQ,QAAQ,CAAC,CAAC;AAAA,IACzB,QAAQ,CAAC;AAAA,IACT,QAAQ,CAAC;AAAA,IACT,QAAQ,WAAW,IAAI,OAAO,IAAI,MAAM,QAAQ,CAAC,CAAC,IAAI;AAAA,EACxD;AACF;;;ADpKA,SAAS,QAAW,OAAqB;AACvC,MAAI,MAAM,QAAQ,KAAK,EAAG,QAAO;AACjC,SAAO,CAAC,KAAK;AACf;AAae,SAAR,mBACL,OACA,QACe;AACf,QAAM,OAAO,QAAQ,KAAK,EAAE,IAAI,CAAC,MAAM,IAAI,+BAAS,GAAG,EAAE,CAAC;AAC1D,QAAM,MAAM,KAAK,IAAI;AAErB,WAAS,IAAI,GAAG,IAAI,KAAK,QAAQ,KAAK;AACpC,QAAI,KAAK,CAAC,EAAE,QAAQ,SAAS,GAAG;AAC9B,YAAM,IAAI;AAAA,QACR,sBAAsB,CAAC;AAAA;AAAA,MAEzB;AAAA,IACF;AAAA,EACF;AAEA,MAAI,OAAO,MAAM,KAAK,QAAQ,IAAI,CAAC;AACnC,WAAS,IAAI,KAAK,SAAS,GAAG,KAAK,GAAG,KAAK;AACzC,WAAO,UAAU,KAAK,CAAC,GAAG,CAAC,IAAI,CAAC;AAAA,EAClC;AACA,SAAO;AACT;AAEA,SAAS,MACP,KACA,QACA,UACA,eACe;AACf,QAAM,EAAE,iBAAiB,gBAAgB,WAAW,IAAI;AAExD,QAAM,QAAQ,gBAAgB;AAC9B,QAAM,WAAW,gBAAgB,IAAI,CAAC,YAA2B,MAAuB;AAKtF,UAAM,MAAqB;AAAA,MACzB;AAAA,MACA;AAAA,MACA,QAAQ,cAAc;AAAA,MACtB,SAAS;AAAA,MACT,QAAQ;AAAA,IACV;AAIA,UAAM,YAAY,OAAO,IAAI,QAAQ,GAAG;AAExC,UAAM,EAAE,QAAQ,SAAS,OAAO,IAAI;AAGpC,QAAI,UAAW,QAAO,MAAM,IAAI,+BAAS,WAAW,MAAM,GAAG,QAAQ,QAAQ,KAAK;AAMlF,UAAM,gBACJ,YAAY,SAAY,UAAU,iBAAiB,eAAe,CAAC,IAAI;AACzE,UAAM,UAAU,WAAW,SAAY,SAAS,aAAa,WAAW,SAAS,CAAC,IAAI;AACtF,WAAO,eAAe,QAAQ,eAAe,OAAO;AAAA,EACtD,CAAC;AAED,SAAO,UAAU,KAAK,QAAQ;AAChC;;;AExFA,IAAAC,sBAA2C;AAS3C,IAAqB,YAArB,MAA+B;AAAA,EAU7B,YAAY,KAAiB,SAAkB;AAC7C,UAAM,MAAM,QAAQ,sBAAkB,kCAAa,GAAG,QAAI,kCAAa,GAAG;AAC1E,SAAK,UAAU,IAAI;AACnB,SAAK,OAAO,IAAI;AAChB,SAAK,WAAW,IAAI;AACpB,SAAK,QAAQ,IAAI;AACjB,SAAK,aAAa,IAAI;AACtB,SAAK,aAAa,IAAI;AAEtB,SAAK,UAAU,IAAI;AACnB,QAAI,CAAC,QAAQ,gBAAgB;AAC3B,WAAK,iBAAiB,IAAI;AAAA,IAC5B;AAAA,EACF;AAAA,EAEA,WAAmB;AACjB,WAAO,KAAK,UAAU,IAAI;AAAA,EAC5B;AACF;;;AHLe,SAAR,UACL,OACA,QACA,SACW;AACX,QAAM,OACJ,OAAO,YAAY,WAAW,UAAU,EAAE,gBAAgB,CAAC,CAAC,SAAS,iBAAiB,MAAM;AAC9F,QAAM,OAAO,mBAAmB,OAAO,MAAM;AAC7C,SAAO,IAAI,UAAU,cAAc,IAAI,GAAG,IAAI;AAChD;",
|
||||
"names": ["module", "module", "import_trace_mapping", "source", "import_gen_mapping"]
|
||||
}
|
||||
71
whm/gniza4cp-whm/assets/node_modules/@jridgewell/remapping/package.json
generated
vendored
Normal file
@@ -0,0 +1,71 @@
|
||||
{
|
||||
"name": "@jridgewell/remapping",
|
||||
"version": "2.3.5",
|
||||
"description": "Remap sequential sourcemaps through transformations to point at the original source code",
|
||||
"keywords": [
|
||||
"source",
|
||||
"map",
|
||||
"remap"
|
||||
],
|
||||
"main": "dist/remapping.umd.js",
|
||||
"module": "dist/remapping.mjs",
|
||||
"types": "types/remapping.d.cts",
|
||||
"files": [
|
||||
"dist",
|
||||
"src",
|
||||
"types"
|
||||
],
|
||||
"exports": {
|
||||
".": [
|
||||
{
|
||||
"import": {
|
||||
"types": "./types/remapping.d.mts",
|
||||
"default": "./dist/remapping.mjs"
|
||||
},
|
||||
"default": {
|
||||
"types": "./types/remapping.d.cts",
|
||||
"default": "./dist/remapping.umd.js"
|
||||
}
|
||||
},
|
||||
"./dist/remapping.umd.js"
|
||||
],
|
||||
"./package.json": "./package.json"
|
||||
},
|
||||
"scripts": {
|
||||
"benchmark": "run-s build:code benchmark:*",
|
||||
"benchmark:install": "cd benchmark && npm install",
|
||||
"benchmark:only": "node --expose-gc benchmark/index.js",
|
||||
"build": "run-s -n build:code build:types",
|
||||
"build:code": "node ../../esbuild.mjs remapping.ts",
|
||||
"build:types": "run-s build:types:force build:types:emit build:types:mts",
|
||||
"build:types:force": "rimraf tsconfig.build.tsbuildinfo",
|
||||
"build:types:emit": "tsc --project tsconfig.build.json",
|
||||
"build:types:mts": "node ../../mts-types.mjs",
|
||||
"clean": "run-s -n clean:code clean:types",
|
||||
"clean:code": "tsc --build --clean tsconfig.build.json",
|
||||
"clean:types": "rimraf dist types",
|
||||
"test": "run-s -n test:types test:only test:format",
|
||||
"test:format": "prettier --check '{src,test}/**/*.ts'",
|
||||
"test:only": "mocha",
|
||||
"test:types": "eslint '{src,test}/**/*.ts'",
|
||||
"lint": "run-s -n lint:types lint:format",
|
||||
"lint:format": "npm run test:format -- --write",
|
||||
"lint:types": "npm run test:types -- --fix",
|
||||
"prepublishOnly": "npm run-s -n build test"
|
||||
},
|
||||
"homepage": "https://github.com/jridgewell/sourcemaps/tree/main/packages/remapping",
|
||||
"repository": {
|
||||
"type": "git",
|
||||
"url": "git+https://github.com/jridgewell/sourcemaps.git",
|
||||
"directory": "packages/remapping"
|
||||
},
|
||||
"author": "Justin Ridgewell <justin@ridgewell.name>",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@jridgewell/gen-mapping": "^0.3.5",
|
||||
"@jridgewell/trace-mapping": "^0.3.24"
|
||||
},
|
||||
"devDependencies": {
|
||||
"source-map": "0.6.1"
|
||||
}
|
||||
}
|
||||
89
whm/gniza4cp-whm/assets/node_modules/@jridgewell/remapping/src/build-source-map-tree.ts
generated
vendored
Normal file
@@ -0,0 +1,89 @@
|
||||
import { TraceMap } from '@jridgewell/trace-mapping';
|
||||
|
||||
import { OriginalSource, MapSource } from './source-map-tree';
|
||||
|
||||
import type { Sources, MapSource as MapSourceType } from './source-map-tree';
|
||||
import type { SourceMapInput, SourceMapLoader, LoaderContext } from './types';
|
||||
|
||||
function asArray<T>(value: T | T[]): T[] {
|
||||
if (Array.isArray(value)) return value;
|
||||
return [value];
|
||||
}
|
||||
|
||||
/**
|
||||
* Recursively builds a tree structure out of sourcemap files, with each node
|
||||
* being either an `OriginalSource` "leaf" or a `SourceMapTree` composed of
|
||||
* `OriginalSource`s and `SourceMapTree`s.
|
||||
*
|
||||
* Every sourcemap is composed of a collection of source files and mappings
|
||||
* into locations of those source files. When we generate a `SourceMapTree` for
|
||||
* the sourcemap, we attempt to load each source file's own sourcemap. If it
|
||||
* does not have an associated sourcemap, it is considered an original,
|
||||
* unmodified source file.
|
||||
*/
|
||||
export default function buildSourceMapTree(
|
||||
input: SourceMapInput | SourceMapInput[],
|
||||
loader: SourceMapLoader,
|
||||
): MapSourceType {
|
||||
const maps = asArray(input).map((m) => new TraceMap(m, ''));
|
||||
const map = maps.pop()!;
|
||||
|
||||
for (let i = 0; i < maps.length; i++) {
|
||||
if (maps[i].sources.length > 1) {
|
||||
throw new Error(
|
||||
`Transformation map ${i} must have exactly one source file.\n` +
|
||||
'Did you specify these with the most recent transformation maps first?',
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
let tree = build(map, loader, '', 0);
|
||||
for (let i = maps.length - 1; i >= 0; i--) {
|
||||
tree = MapSource(maps[i], [tree]);
|
||||
}
|
||||
return tree;
|
||||
}
|
||||
|
||||
function build(
|
||||
map: TraceMap,
|
||||
loader: SourceMapLoader,
|
||||
importer: string,
|
||||
importerDepth: number,
|
||||
): MapSourceType {
|
||||
const { resolvedSources, sourcesContent, ignoreList } = map;
|
||||
|
||||
const depth = importerDepth + 1;
|
||||
const children = resolvedSources.map((sourceFile: string | null, i: number): Sources => {
|
||||
// The loading context gives the loader more information about why this file is being loaded
|
||||
// (eg, from which importer). It also allows the loader to override the location of the loaded
|
||||
// sourcemap/original source, or to override the content in the sourcesContent field if it's
|
||||
// an unmodified source file.
|
||||
const ctx: LoaderContext = {
|
||||
importer,
|
||||
depth,
|
||||
source: sourceFile || '',
|
||||
content: undefined,
|
||||
ignore: undefined,
|
||||
};
|
||||
|
||||
// Use the provided loader callback to retrieve the file's sourcemap.
|
||||
// TODO: We should eventually support async loading of sourcemap files.
|
||||
const sourceMap = loader(ctx.source, ctx);
|
||||
|
||||
const { source, content, ignore } = ctx;
|
||||
|
||||
// If there is a sourcemap, then we need to recurse into it to load its source files.
|
||||
if (sourceMap) return build(new TraceMap(sourceMap, source), loader, source, depth);
|
||||
|
||||
// Else, it's an unmodified source file.
|
||||
// The contents of this unmodified source file can be overridden via the loader context,
|
||||
// allowing it to be explicitly null or a string. If it remains undefined, we fall back to
|
||||
// the importing sourcemap's `sourcesContent` field.
|
||||
const sourceContent =
|
||||
content !== undefined ? content : sourcesContent ? sourcesContent[i] : null;
|
||||
const ignored = ignore !== undefined ? ignore : ignoreList ? ignoreList.includes(i) : false;
|
||||
return OriginalSource(source, sourceContent, ignored);
|
||||
});
|
||||
|
||||
return MapSource(map, children);
|
||||
}
|
||||
42
whm/gniza4cp-whm/assets/node_modules/@jridgewell/remapping/src/remapping.ts
generated
vendored
Normal file
@@ -0,0 +1,42 @@
|
||||
import buildSourceMapTree from './build-source-map-tree';
|
||||
import { traceMappings } from './source-map-tree';
|
||||
import SourceMap from './source-map';
|
||||
|
||||
import type { SourceMapInput, SourceMapLoader, Options } from './types';
|
||||
export type {
|
||||
SourceMapSegment,
|
||||
EncodedSourceMap,
|
||||
EncodedSourceMap as RawSourceMap,
|
||||
DecodedSourceMap,
|
||||
SourceMapInput,
|
||||
SourceMapLoader,
|
||||
LoaderContext,
|
||||
Options,
|
||||
} from './types';
|
||||
export type { SourceMap };
|
||||
|
||||
/**
|
||||
* Traces through all the mappings in the root sourcemap, through the sources
|
||||
* (and their sourcemaps), all the way back to the original source location.
|
||||
*
|
||||
* `loader` will be called every time we encounter a source file. If it returns
|
||||
* a sourcemap, we will recurse into that sourcemap to continue the trace. If
|
||||
* it returns a falsey value, that source file is treated as an original,
|
||||
* unmodified source file.
|
||||
*
|
||||
* Pass `excludeContent` to exclude any self-containing source file content
|
||||
* from the output sourcemap.
|
||||
*
|
||||
* Pass `decodedMappings` to receive a SourceMap with decoded (instead of
|
||||
* VLQ encoded) mappings.
|
||||
*/
|
||||
export default function remapping(
|
||||
input: SourceMapInput | SourceMapInput[],
|
||||
loader: SourceMapLoader,
|
||||
options?: boolean | Options,
|
||||
): SourceMap {
|
||||
const opts =
|
||||
typeof options === 'object' ? options : { excludeContent: !!options, decodedMappings: false };
|
||||
const tree = buildSourceMapTree(input, loader);
|
||||
return new SourceMap(traceMappings(tree), opts);
|
||||
}
|
||||
172
whm/gniza4cp-whm/assets/node_modules/@jridgewell/remapping/src/source-map-tree.ts
generated
vendored
Normal file
@@ -0,0 +1,172 @@
|
||||
import { GenMapping, maybeAddSegment, setIgnore, setSourceContent } from '@jridgewell/gen-mapping';
|
||||
import { traceSegment, decodedMappings } from '@jridgewell/trace-mapping';
|
||||
|
||||
import type { TraceMap } from '@jridgewell/trace-mapping';
|
||||
|
||||
export type SourceMapSegmentObject = {
|
||||
column: number;
|
||||
line: number;
|
||||
name: string;
|
||||
source: string;
|
||||
content: string | null;
|
||||
ignore: boolean;
|
||||
};
|
||||
|
||||
export type OriginalSource = {
|
||||
map: null;
|
||||
sources: Sources[];
|
||||
source: string;
|
||||
content: string | null;
|
||||
ignore: boolean;
|
||||
};
|
||||
|
||||
export type MapSource = {
|
||||
map: TraceMap;
|
||||
sources: Sources[];
|
||||
source: string;
|
||||
content: null;
|
||||
ignore: false;
|
||||
};
|
||||
|
||||
export type Sources = OriginalSource | MapSource;
|
||||
|
||||
const SOURCELESS_MAPPING = /* #__PURE__ */ SegmentObject('', -1, -1, '', null, false);
|
||||
const EMPTY_SOURCES: Sources[] = [];
|
||||
|
||||
function SegmentObject(
|
||||
source: string,
|
||||
line: number,
|
||||
column: number,
|
||||
name: string,
|
||||
content: string | null,
|
||||
ignore: boolean,
|
||||
): SourceMapSegmentObject {
|
||||
return { source, line, column, name, content, ignore };
|
||||
}
|
||||
|
||||
function Source(
|
||||
map: TraceMap,
|
||||
sources: Sources[],
|
||||
source: '',
|
||||
content: null,
|
||||
ignore: false,
|
||||
): MapSource;
|
||||
function Source(
|
||||
map: null,
|
||||
sources: Sources[],
|
||||
source: string,
|
||||
content: string | null,
|
||||
ignore: boolean,
|
||||
): OriginalSource;
|
||||
function Source(
|
||||
map: TraceMap | null,
|
||||
sources: Sources[],
|
||||
source: string | '',
|
||||
content: string | null,
|
||||
ignore: boolean,
|
||||
): Sources {
|
||||
return {
|
||||
map,
|
||||
sources,
|
||||
source,
|
||||
content,
|
||||
ignore,
|
||||
} as any;
|
||||
}
|
||||
|
||||
/**
|
||||
* MapSource represents a single sourcemap, with the ability to trace mappings into its child nodes
|
||||
* (which may themselves be SourceMapTrees).
|
||||
*/
|
||||
export function MapSource(map: TraceMap, sources: Sources[]): MapSource {
|
||||
return Source(map, sources, '', null, false);
|
||||
}
|
||||
|
||||
/**
|
||||
* A "leaf" node in the sourcemap tree, representing an original, unmodified source file. Recursive
|
||||
* segment tracing ends at the `OriginalSource`.
|
||||
*/
|
||||
export function OriginalSource(
|
||||
source: string,
|
||||
content: string | null,
|
||||
ignore: boolean,
|
||||
): OriginalSource {
|
||||
return Source(null, EMPTY_SOURCES, source, content, ignore);
|
||||
}
|
||||
|
||||
/**
|
||||
* traceMappings is only called on the root level SourceMapTree, and begins the process of
|
||||
* resolving each mapping in terms of the original source files.
|
||||
*/
|
||||
export function traceMappings(tree: MapSource): GenMapping {
|
||||
// TODO: Eventually support sourceRoot, which has to be removed because the sources are already
|
||||
// fully resolved. We'll need to make sources relative to the sourceRoot before adding them.
|
||||
const gen = new GenMapping({ file: tree.map.file });
|
||||
const { sources: rootSources, map } = tree;
|
||||
const rootNames = map.names;
|
||||
const rootMappings = decodedMappings(map);
|
||||
|
||||
for (let i = 0; i < rootMappings.length; i++) {
|
||||
const segments = rootMappings[i];
|
||||
|
||||
for (let j = 0; j < segments.length; j++) {
|
||||
const segment = segments[j];
|
||||
const genCol = segment[0];
|
||||
let traced: SourceMapSegmentObject | null = SOURCELESS_MAPPING;
|
||||
|
||||
// 1-length segments only move the current generated column, there's no source information
|
||||
// to gather from it.
|
||||
if (segment.length !== 1) {
|
||||
const source = rootSources[segment[1]];
|
||||
traced = originalPositionFor(
|
||||
source,
|
||||
segment[2],
|
||||
segment[3],
|
||||
segment.length === 5 ? rootNames[segment[4]] : '',
|
||||
);
|
||||
|
||||
// If the trace is invalid, then the trace ran into a sourcemap that doesn't contain a
|
||||
// respective segment into an original source.
|
||||
if (traced == null) continue;
|
||||
}
|
||||
|
||||
const { column, line, name, content, source, ignore } = traced;
|
||||
|
||||
maybeAddSegment(gen, i, genCol, source, line, column, name);
|
||||
if (source && content != null) setSourceContent(gen, source, content);
|
||||
if (ignore) setIgnore(gen, source, true);
|
||||
}
|
||||
}
|
||||
|
||||
return gen;
|
||||
}
|
||||
|
||||
/**
|
||||
* originalPositionFor is only called on children SourceMapTrees. It recurses down into its own
|
||||
* child SourceMapTrees, until we find the original source map.
|
||||
*/
|
||||
export function originalPositionFor(
|
||||
source: Sources,
|
||||
line: number,
|
||||
column: number,
|
||||
name: string,
|
||||
): SourceMapSegmentObject | null {
|
||||
if (!source.map) {
|
||||
return SegmentObject(source.source, line, column, name, source.content, source.ignore);
|
||||
}
|
||||
|
||||
const segment = traceSegment(source.map, line, column);
|
||||
|
||||
// If we couldn't find a segment, then this doesn't exist in the sourcemap.
|
||||
if (segment == null) return null;
|
||||
// 1-length segments only move the current generated column, there's no source information
|
||||
// to gather from it.
|
||||
if (segment.length === 1) return SOURCELESS_MAPPING;
|
||||
|
||||
return originalPositionFor(
|
||||
source.sources[segment[1]],
|
||||
segment[2],
|
||||
segment[3],
|
||||
segment.length === 5 ? source.map.names[segment[4]] : name,
|
||||
);
|
||||
}
|
||||
38
whm/gniza4cp-whm/assets/node_modules/@jridgewell/remapping/src/source-map.ts
generated
vendored
Normal file
@@ -0,0 +1,38 @@
|
||||
import { toDecodedMap, toEncodedMap } from '@jridgewell/gen-mapping';
|
||||
|
||||
import type { GenMapping } from '@jridgewell/gen-mapping';
|
||||
import type { DecodedSourceMap, EncodedSourceMap, Options } from './types';
|
||||
|
||||
/**
|
||||
* A SourceMap v3 compatible sourcemap, which only includes fields that were
|
||||
* provided to it.
|
||||
*/
|
||||
export default class SourceMap {
|
||||
declare file?: string | null;
|
||||
declare mappings: EncodedSourceMap['mappings'] | DecodedSourceMap['mappings'];
|
||||
declare sourceRoot?: string;
|
||||
declare names: string[];
|
||||
declare sources: (string | null)[];
|
||||
declare sourcesContent?: (string | null)[];
|
||||
declare version: 3;
|
||||
declare ignoreList: number[] | undefined;
|
||||
|
||||
constructor(map: GenMapping, options: Options) {
|
||||
const out = options.decodedMappings ? toDecodedMap(map) : toEncodedMap(map);
|
||||
this.version = out.version; // SourceMap spec says this should be first.
|
||||
this.file = out.file;
|
||||
this.mappings = out.mappings as SourceMap['mappings'];
|
||||
this.names = out.names as SourceMap['names'];
|
||||
this.ignoreList = out.ignoreList as SourceMap['ignoreList'];
|
||||
this.sourceRoot = out.sourceRoot;
|
||||
|
||||
this.sources = out.sources as SourceMap['sources'];
|
||||
if (!options.excludeContent) {
|
||||
this.sourcesContent = out.sourcesContent as SourceMap['sourcesContent'];
|
||||
}
|
||||
}
|
||||
|
||||
toString(): string {
|
||||
return JSON.stringify(this);
|
||||
}
|
||||
}
|
||||
27
whm/gniza4cp-whm/assets/node_modules/@jridgewell/remapping/src/types.ts
generated
vendored
Normal file
@@ -0,0 +1,27 @@
|
||||
import type { SourceMapInput } from '@jridgewell/trace-mapping';
|
||||
|
||||
export type {
|
||||
SourceMapSegment,
|
||||
DecodedSourceMap,
|
||||
EncodedSourceMap,
|
||||
} from '@jridgewell/trace-mapping';
|
||||
|
||||
export type { SourceMapInput };
|
||||
|
||||
export type LoaderContext = {
|
||||
readonly importer: string;
|
||||
readonly depth: number;
|
||||
source: string;
|
||||
content: string | null | undefined;
|
||||
ignore: boolean | undefined;
|
||||
};
|
||||
|
||||
export type SourceMapLoader = (
|
||||
file: string,
|
||||
ctx: LoaderContext,
|
||||
) => SourceMapInput | null | undefined | void;
|
||||
|
||||
export type Options = {
|
||||
excludeContent?: boolean;
|
||||
decodedMappings?: boolean;
|
||||
};
|
||||
15
whm/gniza4cp-whm/assets/node_modules/@jridgewell/remapping/types/build-source-map-tree.d.cts
generated
vendored
Normal file
@@ -0,0 +1,15 @@
|
||||
import type { MapSource as MapSourceType } from './source-map-tree.cts';
|
||||
import type { SourceMapInput, SourceMapLoader } from './types.cts';
|
||||
/**
|
||||
* Recursively builds a tree structure out of sourcemap files, with each node
|
||||
* being either an `OriginalSource` "leaf" or a `SourceMapTree` composed of
|
||||
* `OriginalSource`s and `SourceMapTree`s.
|
||||
*
|
||||
* Every sourcemap is composed of a collection of source files and mappings
|
||||
* into locations of those source files. When we generate a `SourceMapTree` for
|
||||
* the sourcemap, we attempt to load each source file's own sourcemap. If it
|
||||
* does not have an associated sourcemap, it is considered an original,
|
||||
* unmodified source file.
|
||||
*/
|
||||
export = function buildSourceMapTree(input: SourceMapInput | SourceMapInput[], loader: SourceMapLoader): MapSourceType;
|
||||
//# sourceMappingURL=build-source-map-tree.d.ts.map
|
||||
1
whm/gniza4cp-whm/assets/node_modules/@jridgewell/remapping/types/build-source-map-tree.d.cts.map
generated
vendored
Normal file
@@ -0,0 +1 @@
|
||||
{"version":3,"file":"build-source-map-tree.d.ts","sourceRoot":"","sources":["../src/build-source-map-tree.ts"],"names":[],"mappings":"AAIA,OAAO,KAAK,EAAW,SAAS,IAAI,aAAa,EAAE,MAAM,mBAAmB,CAAC;AAC7E,OAAO,KAAK,EAAE,cAAc,EAAE,eAAe,EAAiB,MAAM,SAAS,CAAC;AAO9E;;;;;;;;;;GAUG;AACH,MAAM,CAAC,OAAO,UAAU,kBAAkB,CACxC,KAAK,EAAE,cAAc,GAAG,cAAc,EAAE,EACxC,MAAM,EAAE,eAAe,GACtB,aAAa,CAkBf"}
|
||||
15
whm/gniza4cp-whm/assets/node_modules/@jridgewell/remapping/types/build-source-map-tree.d.mts
generated
vendored
Normal file
@@ -0,0 +1,15 @@
|
||||
import type { MapSource as MapSourceType } from './source-map-tree.mts';
|
||||
import type { SourceMapInput, SourceMapLoader } from './types.mts';
|
||||
/**
|
||||
* Recursively builds a tree structure out of sourcemap files, with each node
|
||||
* being either an `OriginalSource` "leaf" or a `SourceMapTree` composed of
|
||||
* `OriginalSource`s and `SourceMapTree`s.
|
||||
*
|
||||
* Every sourcemap is composed of a collection of source files and mappings
|
||||
* into locations of those source files. When we generate a `SourceMapTree` for
|
||||
* the sourcemap, we attempt to load each source file's own sourcemap. If it
|
||||
* does not have an associated sourcemap, it is considered an original,
|
||||
* unmodified source file.
|
||||
*/
|
||||
export default function buildSourceMapTree(input: SourceMapInput | SourceMapInput[], loader: SourceMapLoader): MapSourceType;
|
||||
//# sourceMappingURL=build-source-map-tree.d.ts.map
|
||||
1
whm/gniza4cp-whm/assets/node_modules/@jridgewell/remapping/types/build-source-map-tree.d.mts.map
generated
vendored
Normal file
@@ -0,0 +1 @@
|
||||
{"version":3,"file":"build-source-map-tree.d.ts","sourceRoot":"","sources":["../src/build-source-map-tree.ts"],"names":[],"mappings":"AAIA,OAAO,KAAK,EAAW,SAAS,IAAI,aAAa,EAAE,MAAM,mBAAmB,CAAC;AAC7E,OAAO,KAAK,EAAE,cAAc,EAAE,eAAe,EAAiB,MAAM,SAAS,CAAC;AAO9E;;;;;;;;;;GAUG;AACH,MAAM,CAAC,OAAO,UAAU,kBAAkB,CACxC,KAAK,EAAE,cAAc,GAAG,cAAc,EAAE,EACxC,MAAM,EAAE,eAAe,GACtB,aAAa,CAkBf"}
|
||||