Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
34 commits
Select commit Hold shift + click to select a range
47dfb69
Handle context deadline in input functions and add tests
tis24dev Jan 22, 2026
3382838
refactor: Implement selective feature-based exclusions for config sna…
tis24dev Jan 23, 2026
3859656
feat: Apply exclusion patterns to archive packing and metadata
tis24dev Jan 23, 2026
d8cc105
Improve update check logging in main.go
tis24dev Jan 23, 2026
deab170
Improve log line classification and add debug filtering
tis24dev Jan 23, 2026
7014f53
Expand restore categories with staged apply for access control
tis24dev Jan 24, 2026
e88aeff
Improve error handling in backup and restore workflows
tis24dev Jan 24, 2026
f1cbd13
refactor: split metrics, add storage inventory and fix test resource …
tis24dev Jan 25, 2026
7e4d2ab
Reorganize backup categories and archive layout; centralize diagnostics
tis24dev Jan 25, 2026
35c2178
Extend PBS restore coverage (host/integrations, tape, S3) and align …
tis24dev Jan 26, 2026
8744ec3
add PBS mount guards and fstab merge ordering
tis24dev Jan 26, 2026
b80ccee
Improve fstab restore with stable device remapping
tis24dev Jan 26, 2026
0a62d74
Document PBS mount guard and smart fstab restore
tis24dev Jan 26, 2026
d5432d8
Enable restore of PBS proxy and SSL configuration
tis24dev Jan 26, 2026
b75dbd6
Add staged apply for PVE configs and mount guards
tis24dev Jan 26, 2026
02ee183
Add PVE storage mount guard logic and improve parsing
tis24dev Jan 26, 2026
f7f6a08
Refactor PVE access control restore to 1:1 file apply
tis24dev Jan 27, 2026
2af64aa
Enhance PBS access control restore with root safety rail
tis24dev Jan 27, 2026
56b3c49
Improve TFA/WebAuthn restore guidance and workflow
tis24dev Jan 27, 2026
c3a437c
Add PVE firewall restore with rollback and glob support
tis24dev Jan 28, 2026
ac5ee82
Add transactional restore support for PVE HA configuration
tis24dev Jan 29, 2026
178696a
fix
tis24dev Jan 29, 2026
c492e22
fix minor
tis24dev Jan 29, 2026
f1dbcce
Add PVE SDN restore handling and tests
tis24dev Jan 31, 2026
53e1ffc
PVE SAFE-apply: mappings, pools and ACL rollback
tis24dev Feb 1, 2026
b0d1151
Add HTTP helpers and robust release tag fetch
tis24dev Feb 2, 2026
436c0f3
deps(deps): bump github.com/gdamore/tcell/v2 from 2.13.5 to 2.13.8 in…
dependabot[bot] Feb 2, 2026
2060dd9
deps(deps): bump golang.org/x/term from 0.38.0 to 0.39.0 (#125)
dependabot[bot] Feb 2, 2026
aa05731
deps(deps): bump golang.org/x/crypto from 0.46.0 to 0.47.0 (#123)
dependabot[bot] Feb 2, 2026
5183d90
deps(deps): bump golang.org/x/text from 0.32.0 to 0.33.0 (#124)
dependabot[bot] Feb 2, 2026
f2388c5
Refine unescapeProcPath and add tests
tis24dev Feb 2, 2026
5e072dc
Handle existing PVE pools and ensure comments
tis24dev Feb 2, 2026
8f667af
Redact secrets when applying PVE notifications
tis24dev Feb 2, 2026
6745ff8
Refactor guard cleanup and add tests
tis24dev Feb 2, 2026
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
63 changes: 58 additions & 5 deletions cmd/proxsave/main.go
Original file line number Diff line number Diff line change
Expand Up @@ -121,6 +121,54 @@
return types.ExitSuccess.Int()
}

if args.CleanupGuards {
incompatible := make([]string, 0, 8)
if args.Support {
incompatible = append(incompatible, "--support")
}
if args.Restore {
incompatible = append(incompatible, "--restore")
}
if args.Decrypt {
incompatible = append(incompatible, "--decrypt")
}
if args.Install {
incompatible = append(incompatible, "--install")
}
if args.NewInstall {
incompatible = append(incompatible, "--new-install")
}
if args.Upgrade {
incompatible = append(incompatible, "--upgrade")
}
if args.ForceNewKey {
incompatible = append(incompatible, "--newkey")
}
if args.EnvMigration || args.EnvMigrationDry {
incompatible = append(incompatible, "--env-migration/--env-migration-dry-run")
}
if args.UpgradeConfig || args.UpgradeConfigDry {
incompatible = append(incompatible, "--upgrade-config/--upgrade-config-dry-run")
}

if len(incompatible) > 0 {
bootstrap.Error("--cleanup-guards cannot be combined with: %s", strings.Join(incompatible, ", "))
return types.ExitConfigError.Int()
}

level := types.LogLevelInfo
if args.LogLevel != types.LogLevelNone {
level = args.LogLevel
}
logger := logging.New(level, false)

if err := orchestrator.CleanupMountGuards(ctx, logger, args.DryRun); err != nil {
bootstrap.Error("ERROR: %v", err)
return types.ExitGenericError.Int()
}
return types.ExitSuccess.Int()
}

// Validate support mode compatibility with other CLI modes
logging.DebugStepBootstrap(bootstrap, "main run", "support_mode=%v", args.Support)
if args.Support {
Expand Down Expand Up @@ -560,7 +608,6 @@
// If the installed version is up to date, nothing is printed at INFO/WARNING level
// (only a DEBUG message is logged). If a newer version exists, a WARNING is emitted
// suggesting the use of --upgrade.
logging.DebugStep(logger, "main", "checking for updates")
updateInfo := checkForUpdates(ctx, logger, toolVersion)

// Apply backup permissions (optional, Bash-compatible behavior)
Expand Down Expand Up @@ -990,7 +1037,7 @@
}
}()

logging.Debug("✓ Pre-backup checks configured")
logging.Info("✓ Pre-backup checks configured")
fmt.Println()

// Initialize storage backends
Expand Down Expand Up @@ -1478,7 +1525,7 @@
}
fmt.Printf("\r Remaining: %ds ", int(remaining.Seconds()))

select {

Check failure on line 1528 in cmd/proxsave/main.go

View workflow job for this annotation

GitHub Actions / security

should use a simple channel send/receive instead of select with a single case (S1000)

Check failure on line 1528 in cmd/proxsave/main.go

View workflow job for this annotation

GitHub Actions / security

should use a simple channel send/receive instead of select with a single case (S1000)

Check failure on line 1528 in cmd/proxsave/main.go

View workflow job for this annotation

GitHub Actions / security

should use a simple channel send/receive instead of select with a single case (S1000)
case <-ticker.C:
continue
}
Expand Down Expand Up @@ -1688,9 +1735,14 @@
checkCtx, cancel := context.WithTimeout(ctx, 5*time.Second)
defer cancel()

logger.Debug("Checking for ProxSave updates (current: %s)", currentVersion)

apiURL := fmt.Sprintf("https://api.github.com/repos/%s/releases/latest", githubRepo)
logger.Debug("Fetching latest release from GitHub: %s", apiURL)

_, latestVersion, err := fetchLatestRelease(checkCtx)
if err != nil {
logger.Debug("Update check skipped (GitHub unreachable): %v", err)
logger.Debug("Update check skipped: GitHub unreachable: %v", err)
return &UpdateInfo{
NewVersion: false,
Current: currentVersion,
Expand All @@ -1707,15 +1759,16 @@
}

if !isNewerVersion(currentVersion, latestVersion) {
logger.Debug("Update check: current version (%s) is up to date (latest: %s)", currentVersion, latestVersion)
logger.Debug("Update check completed: latest=%s current=%s (up to date)", latestVersion, currentVersion)
return &UpdateInfo{
NewVersion: false,
Current: currentVersion,
Latest: latestVersion,
}
}

logger.Warning("A newer ProxSave version is available %s (current %s): consider running 'proxsave --upgrade' to install the latest release.", latestVersion, currentVersion)
logger.Debug("Update check completed: latest=%s current=%s (new version available)", latestVersion, currentVersion)
logger.Warning("New ProxSave version %s (current %s): run 'proxsave --upgrade' to install.", latestVersion, currentVersion)

return &UpdateInfo{
NewVersion: true,
Expand Down
16 changes: 16 additions & 0 deletions docs/CLI_REFERENCE.md
Original file line number Diff line number Diff line change
Expand Up @@ -414,6 +414,7 @@ Next step: ./build/proxsave --dry-run
```

**Use `--cli` when**: TUI rendering issues occur or advanced debugging is needed.
**Note**: CLI and TUI run the same workflow logic; `--cli` only changes the interface (prompts/progress rendering), not the restore/decrypt behavior.

**`--restore` workflow** (14 phases):
1. Scans configured storage locations (local/secondary/cloud)
Expand Down Expand Up @@ -449,9 +450,24 @@ Next step: ./build/proxsave --dry-run
| Flag | Description |
|------|-------------|
| `--restore` | Run interactive restore workflow (select bundle, decrypt if needed, apply to system) |
| `--cleanup-guards` | Cleanup ProxSave mount guards under `/var/lib/proxsave/guards` (useful after restores with offline mountpoints; use with `--dry-run` to preview) |

---

### Cleanup Mount Guards (Optional)

During some restores (notably PBS datastores on mountpoints under `/mnt`), ProxSave may apply **mount guards** to prevent accidental writes to `/` when the underlying storage is offline/not mounted yet.

If you want to remove those guards manually (optional):

```bash
# Preview (no changes)
./build/proxsave --cleanup-guards --dry-run --log-level debug

# Apply cleanup (requires root)
./build/proxsave --cleanup-guards
```

## Logging

### Set Log Level
Expand Down
6 changes: 3 additions & 3 deletions docs/CLOUD_STORAGE.md
Original file line number Diff line number Diff line change
Expand Up @@ -383,7 +383,7 @@ RETENTION_YEARLY=3
| `CLOUD_PARALLEL_MAX_JOBS` | `2` | Max concurrent uploads (parallel mode) |
| `CLOUD_PARALLEL_VERIFICATION` | `true` | Verify checksums after upload |
| `CLOUD_WRITE_HEALTHCHECK` | `false` | Use write test for connectivity check |
| `RCLONE_TIMEOUT_CONNECTION` | `30` | Connection timeout (seconds). Also used by restore/decrypt when scanning cloud backups (list + manifest read). |
| `RCLONE_TIMEOUT_CONNECTION` | `30` | Per-command timeout (seconds). Also used by restore/decrypt cloud scan (`rclone lsf` + per-backup manifest/metadata read). |
| `RCLONE_TIMEOUT_OPERATION` | `300` | Operation timeout (seconds) |
| `RCLONE_BANDWIDTH_LIMIT` | _(empty)_ | Upload rate limit (e.g., `5M` = 5 MB/s) |
| `RCLONE_TRANSFERS` | `4` | Number of parallel transfers |
Expand Down Expand Up @@ -437,7 +437,7 @@ path inside the remote, and uses that consistently for:
- **uploads** (cloud backend);
- **cloud retention**;
- **restore / decrypt menus** (entry “Cloud backups (rclone)”).
- Restore/decrypt cloud scanning is protected by `RCLONE_TIMEOUT_CONNECTION` (increase it on slow remotes or very large directories).
- Restore/decrypt cloud scanning applies `RCLONE_TIMEOUT_CONNECTION` per rclone command (the timer resets on each `lsf`/manifest read).

You can choose the style you prefer; they are equivalent from the tool’s point of view.

Expand Down Expand Up @@ -617,7 +617,7 @@ rm /tmp/test*.txt
| `couldn't find configuration section 'gdrive'` | Remote not configured | `rclone config` → create remote |
| `401 unauthorized` | Credentials expired | `rclone config reconnect gdrive` or regenerate keys |
| `connection timeout (30s)` | Slow network | Increase `RCLONE_TIMEOUT_CONNECTION=60` |
| `Timed out while scanning ... (rclone)` | Slow remote / huge directory | Increase `RCLONE_TIMEOUT_CONNECTION` and re-try restore/decrypt scan |
| `Timed out while scanning ... (rclone)` | Slow remote / huge directory | Increase `RCLONE_TIMEOUT_CONNECTION` and ensure the remote path points to the directory that contains the backups (scan is non-recursive) |
| `operation timeout (300s exceeded)` | Large file + slow network | Increase `RCLONE_TIMEOUT_OPERATION=900` |
| `429 Too Many Requests` | API rate limiting | Reduce `RCLONE_TRANSFERS=2`, increase `CLOUD_BATCH_PAUSE=3` |
| `directory not found` | Path doesn't exist | `rclone mkdir gdrive:pbs-backups` |
Expand Down
18 changes: 16 additions & 2 deletions docs/CONFIGURATION.md
Original file line number Diff line number Diff line change
Expand Up @@ -352,6 +352,14 @@ BACKUP_EXCLUDE_PATTERNS="*/cache/**, /var/tmp/**, *.log"
- `**`: Match any directory recursively
- Example: `*/cache/**` excludes all `cache/` subdirectories

### Exclusion Behavior (Guaranteed)

- Exclusions are enforced consistently for anything that would end up inside the backup archive (files/directories copied from the host, full PVE/PBS snapshots, command outputs under `var/lib/proxsave-info/commands/`, and generated metadata such as `manifest.json` and `var/lib/proxsave-info/backup_metadata.txt`).
- Patterns are matched against both:
- the original host path (e.g. `/etc/pve/nodes/node1/qemu-server/100.conf`), and
- the path inside the backup archive (e.g. `etc/pve/nodes/node1/qemu-server/100.conf`).
This means you can write patterns with or without a leading `/` and get consistent results.

---

## Secondary Storage
Expand Down Expand Up @@ -548,7 +556,7 @@ Quick comparison to help you choose the right storage configuration:

```bash
# Connection timeout (seconds)
RCLONE_TIMEOUT_CONNECTION=30 # Remote accessibility check (also used for restore/decrypt cloud scan)
RCLONE_TIMEOUT_CONNECTION=30 # Remote accessibility check (also used as per-command timeout for restore/decrypt cloud scan)

# Operation timeout (seconds)
RCLONE_TIMEOUT_OPERATION=300 # Upload/download operations (5 minutes default)
Expand All @@ -571,7 +579,7 @@ RCLONE_FLAGS="--checkers=4 --stats=0 --drive-use-trash=false --drive-pacer-min-s

### Timeout Tuning

- **CONNECTION**: Short timeout for quick accessibility check (default 30s); also caps restore/decrypt cloud scanning (listing backups + reading manifests)
- **CONNECTION**: Short timeout for quick accessibility check (default 30s); also applies per rclone command during restore/decrypt cloud scanning (listing backups + reading manifests)
- **OPERATION**: Long timeout for large file uploads (increase for slow networks)

### Bandwidth Limit Format
Expand Down Expand Up @@ -918,6 +926,8 @@ CEPH_CONFIG_PATH=/etc/ceph # Ceph config directory
BACKUP_VM_CONFIGS=true # VM/CT config files
```

**Note (PVE snapshot behavior)**: ProxSave snapshots `PVE_CONFIG_PATH` for completeness. When a PVE feature is disabled, proxsave also excludes its well-known files from that snapshot to avoid “still included via full directory copy” surprises (e.g. `qemu-server/` + `lxc/` for `BACKUP_VM_CONFIGS=false`, `firewall/` + `host.fw` for `BACKUP_PVE_FIREWALL=false`, `user.cfg`/`acl.cfg`/`domains.cfg` for `BACKUP_PVE_ACL=false`, `jobs.cfg` + `vzdump.cron` for `BACKUP_PVE_JOBS=false`, `corosync.conf` (and `config.db` capture) for `BACKUP_CLUSTER_CONFIG=false`).

### PBS-Specific

```bash
Expand Down Expand Up @@ -955,6 +965,8 @@ PXAR_FILE_INCLUDE_PATTERN= # Include patterns (default: *.pxar, catalog.
PXAR_FILE_EXCLUDE_PATTERN= # Exclude patterns (e.g., *.tmp, *.lock)
```

**Note (PBS snapshot behavior)**: ProxSave snapshots `PBS_CONFIG_PATH` (`/etc/proxmox-backup`) for completeness. When a PBS feature is disabled, proxsave excludes the corresponding well-known config files from that snapshot (for example, `remote.cfg` is excluded when `BACKUP_REMOTE_CONFIGS=false`) and also skips the related command outputs.

**PXAR scanning**: Collects metadata from Proxmox Backup Server .pxar archives.

### Override Collection Paths
Expand Down Expand Up @@ -1030,6 +1042,8 @@ BACKUP_SCRIPT_REPOSITORY=false # Include .git directory
BACKUP_CONFIG_FILE=true # Include this backup.env configuration file in the backup
```

**Note (SSH keys)**: `BACKUP_SSH_KEYS=false` also suppresses `.ssh/` directories when collecting home directories (root and users), so keys aren’t included indirectly via `BACKUP_ROOT_HOME`/home collection.

**Note**: `BACKUP_CONFIG_FILE=true` automatically includes the `configs/backup.env` file in the backup archive. This is highly recommended for disaster recovery, as it allows you to restore your exact backup configuration along with the system files. If you have sensitive credentials in `backup.env`, ensure your backups are encrypted (`ENCRYPT_ARCHIVE=true`).

---
Expand Down
12 changes: 6 additions & 6 deletions docs/RESTORE_DIAGRAMS.md
Original file line number Diff line number Diff line change
Expand Up @@ -98,15 +98,15 @@ flowchart TD
Mode -->|4. CUSTOM| Custom[CUSTOM Mode]

Full --> SystemFull{System Type?}
SystemFull -->|PVE| PVEFull[PVE Categories:<br/>- pve_cluster<br/>- storage_pve<br/>- pve_jobs<br/>- corosync<br/>- ceph<br/>+ Common]
SystemFull -->|PBS| PBSFull[PBS Categories:<br/>- pbs_config<br/>- datastore_pbs<br/>- pbs_jobs<br/>+ Common]
SystemFull -->|Unknown| CommonFull[Common Only:<br/>- network<br/>- ssl<br/>- ssh<br/>- scripts<br/>- crontabs<br/>- services<br/>- zfs]
SystemFull -->|PVE| PVEFull[PVE Categories:<br/>- pve_cluster<br/>- storage_pve<br/>- pve_jobs<br/>- pve_notifications<br/>- pve_access_control<br/>- pve_firewall<br/>- pve_ha<br/>- pve_sdn<br/>- corosync<br/>- ceph<br/>+ Common]
SystemFull -->|PBS| PBSFull[PBS Categories:<br/>- pbs_host<br/>- datastore_pbs<br/>- maintenance_pbs<br/>- pbs_jobs<br/>- pbs_remotes<br/>- pbs_notifications<br/>- pbs_access_control<br/>- pbs_tape<br/>+ Common]
SystemFull -->|Unknown| CommonFull[Common Only:<br/>- filesystem<br/>- storage_stack<br/>- network<br/>- ssl<br/>- ssh<br/>- scripts<br/>- crontabs<br/>- services<br/>- user_data<br/>- zfs<br/>- proxsave_info]

Storage --> SystemStorage{System Type?}
SystemStorage -->|PVE| PVEStorage[- pve_cluster<br/>- storage_pve<br/>- pve_jobs<br/>- zfs]
SystemStorage -->|PBS| PBSStorage[- pbs_config<br/>- datastore_pbs<br/>- pbs_jobs<br/>- zfs]
SystemStorage -->|PVE| PVEStorage[- pve_cluster<br/>- storage_pve<br/>- pve_jobs<br/>- filesystem<br/>- storage_stack<br/>- zfs]
SystemStorage -->|PBS| PBSStorage[- datastore_pbs<br/>- maintenance_pbs<br/>- pbs_jobs<br/>- pbs_remotes<br/>- filesystem<br/>- storage_stack<br/>- zfs]

Base --> BaseCats[- network<br/>- ssl<br/>- ssh<br/>- services]
Base --> BaseCats[- network<br/>- ssl<br/>- ssh<br/>- services<br/>- filesystem]

Custom --> CheckboxMenu[Interactive Menu]
CheckboxMenu --> ToggleLoop{Toggle Categories}
Expand Down
Loading
Loading