Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
36 commits
Select commit Hold shift + click to select a range
75ad134
docs upgrade - dual collection
tis24dev Apr 19, 2026
dcc283b
docs upgrade
tis24dev Apr 19, 2026
dd3b6ab
Update README.md
tis24dev Apr 19, 2026
4a4f4b7
ci: bump the actions-updates group across 1 directory with 2 updates …
dependabot[bot] Apr 22, 2026
9f1d0ee
Add native Pushover support as a webhook format (#199)
paolostivanin Apr 22, 2026
e9a0b72
Stabilize orchestrator tests and fix TUI race
tis24dev Apr 24, 2026
03ae548
Use safeexec and CommandSpec for external commands
tis24dev Apr 29, 2026
04745a4
Refactor rollback tests, add safeexec cases
tis24dev Apr 29, 2026
2f45d9a
Refactor backup orchestration and validation
tis24dev Apr 29, 2026
7220bd2
Refactor backup bricks into modular files
tis24dev Apr 29, 2026
06dbc3b
Refactor main into modular run pipeline
tis24dev Apr 30, 2026
a526040
Split restore logic into multiple files
tis24dev Apr 30, 2026
e1db44b
Encode email bodies as quoted-printable
tis24dev May 5, 2026
743b24e
deps(deps): bump github.com/gdamore/tcell/v2 from 2.13.8 to 2.13.9 in…
dependabot[bot] May 5, 2026
c7b1caa
Merge branch 'dev' of https://github.com/tis24dev/proxsave into dev
tis24dev May 5, 2026
323b45e
Validate and resolve hardlink targets
tis24dev May 5, 2026
f89bdce
Add additional orchestrator tests
tis24dev May 5, 2026
481a2d6
Improve PVE collection, mail path & restore abort
tis24dev May 5, 2026
28071ad
Strip build metadata in version compare; doc fixes
tis24dev May 5, 2026
140222e
Improve restore extraction summary and guards
tis24dev May 5, 2026
2529ebd
Add FS utimes/lchown and refactor command handling
tis24dev May 5, 2026
3048e3c
Refactor command execution and runtime handling
tis24dev May 5, 2026
a165aba
Return cloud FS errors and collect mode errors
tis24dev May 5, 2026
5457ec5
Propagate context to ZFS restore checks
tis24dev May 5, 2026
5ea720d
Treat decompression readers as ReadClosers
tis24dev May 5, 2026
1e1610d
Convert storage/VM config apply to pvesh flag args
tis24dev May 5, 2026
156b14e
Add ExitEncryptionError and classify encryption failures
tis24dev May 5, 2026
2b70b80
Return error from checksum write; add tests
tis24dev May 5, 2026
532ebe4
Propagate context cancellation in collectors
tis24dev May 5, 2026
16a7b69
Add completion timeout to TUI simulation tests
tis24dev May 5, 2026
daddefa
Update decrypt_tui_e2e_helpers_test.go
tis24dev May 6, 2026
da7a7e7
tui: handle Stop-before-Run and test helpers
tis24dev May 6, 2026
2a44bba
Refactor TUI test harness and improve task start/stop
tis24dev May 6, 2026
5e36f47
Pin actions; fix update check, PBS & tests
tis24dev May 6, 2026
df70bb4
Add PBS/restore helpers, improve panic handling & tests
tis24dev May 6, 2026
60de173
Refactor webhook endpoint validation
tis24dev May 6, 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
71 changes: 71 additions & 0 deletions .github/instructions/codacy.instructions.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,71 @@
---
description: Configuration for AI behavior when interacting with Codacy's MCP Server
applyTo: '**'
---
# Codacy Rules
Configuration for AI behavior when interacting with Codacy's MCP Server

## CRITICAL: After ANY successful `edit_file` or `reapply` operation
- YOU MUST IMMEDIATELY run the `codacy_cli_analyze` tool from Codacy's MCP Server for each file that was edited, with:
- `rootPath`: set to the workspace path
- `file`: set to the path of the edited file
- `tool`: leave empty or unset
- If any issues are found in the new edits, propose and apply fixes for them.
- > NOTE: Failure to follow this rule is considered a critical error.

## When you tried to run the `codacy_cli_analyze` tool and the Codacy CLI is not installed
- Ask the user 'Codacy CLI is not installed. Would you like me to install it now?'
- If the user responds with "yes", run the `codacy_cli_install` tool and then continue with the original task
- If the user responds with "no", instruct the user that they can disable automatic analysis in the extension settings
- Wait for the user to respond before proceeding with any other actions

## After every response
- If you made any file edits in this conversation, verify you ran `codacy_cli_analyze` tool from Codacy's MCP Server

## When there are no Codacy MCP Server tools available, or the MCP Server is not reachable
- Suggest the user the following troubleshooting steps:
- Try to reset the MCP on the extension
- If the user is using VSCode, suggest them to review their Copilot > MCP settings in Github, under their organization or personal account. Refer them to Settings > Copilot > Enable MCP servers in Copilot. Suggested URL (https://github.com/settings/copilot/features) or https://github.com/organizations/{organization-name}/settings/copilot/features (This can only be done by their organization admins / owners)
- If none of the above steps work, suggest the user to contact Codacy support

## Trying to call a tool that needs a rootPath as a parameter
- Always use the standard, non-URL-encoded file system path

## CRITICAL: Dependencies and Security Checks
- IMMEDIATELY after ANY of these actions:
- Running npm/yarn/pnpm install
- Adding dependencies to package.json
- Adding requirements to requirements.txt
- Adding dependencies to pom.xml
- Adding dependencies to build.gradle
- Any other package manager operations
- You MUST run the `codacy_cli_analyze` tool with:
- `rootPath`: set to the workspace path
- `tool`: set to "trivy"
- `file`: leave empty or unset
- If any vulnerabilities are found because of the newly added packages:
- Stop all other operations
- Propose and apply fixes for the security issues
- Only continue with the original task after security issues are resolved
- EXAMPLE:
- After: npm install react-markdown
- Do: Run codacy_cli_analyze with trivy
- Before: Continuing with any other tasks

## General
- Repeat the relevant steps for each modified file.
- "Propose fixes" means to both suggest and, if possible, automatically apply the fixes.
- You MUST NOT wait for the user to ask for analysis or remind you to run the tool.
- Do not run `codacy_cli_analyze` looking for changes in duplicated code or code complexity metrics.
- Complexity metrics are different from complexity issues. When trying to fix complexity in a repository or file, focus on solving the complexity issues and ignore the complexity metric.
- Do not run `codacy_cli_analyze` looking for changes in code coverage.
- Do not try to manually install Codacy CLI using either brew, npm, npx, or any other package manager.
- If the Codacy CLI is not installed, just run the `codacy_cli_analyze` tool from Codacy's MCP Server.
- When calling `codacy_cli_analyze`, only send provider, organization and repository if the project is a git repository.

## Whenever a call to a Codacy tool that uses `repository` or `organization` as a parameter returns a 404 error
- Offer to run the `codacy_setup_repository` tool to add the repository to Codacy
- If the user accepts, run the `codacy_setup_repository` tool
- Do not ever try to run the `codacy_setup_repository` tool on your own
- After setup, immediately retry the action that failed (only retry once)
---
2 changes: 1 addition & 1 deletion .github/workflows/codecov.yml
Original file line number Diff line number Diff line change
Expand Up @@ -34,7 +34,7 @@ jobs:
go test $(go list ./... | grep -v -E '/cmd/|/pbs$|/bech32$|^github.com/tis24dev/proxsave$') -coverprofile=coverage.out

- name: Upload coverage reports to Codecov
uses: codecov/codecov-action@v5
uses: codecov/codecov-action@v6
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🔴 HIGH RISK

Suggestion: Pin this action to a full commit SHA to ensure immutability and improve the security posture of the workflow.

This might be a simple fix:

Suggested change
uses: codecov/codecov-action@v6
uses: codecov/codecov-action@0565863a31f2c772f959ad3c858c5142a28b07ca # v6.0.0

See Issue in Codacy

with:
token: ${{ secrets.CODECOV_TOKEN }}
files: coverage.out
Expand Down
7 changes: 6 additions & 1 deletion .github/workflows/dependabot-automerge.yml
Original file line number Diff line number Diff line change
Expand Up @@ -16,9 +16,14 @@ jobs:
if: github.actor == 'dependabot[bot]'

steps:
- name: Set up Node.js 24
uses: actions/setup-node@49933ea5288caeca8642d1e84afbd3f7d6820020 # pinned from actions/setup-node@v4
with:
node-version: '24'

- name: Fetch Dependabot metadata
id: metadata
uses: dependabot/fetch-metadata@v2
uses: dependabot/fetch-metadata@25dd0e34f4fe68f24cc83900b1fe3fe149efef98 # pinned from dependabot/fetch-metadata@v3
with:
github-token: "${{ secrets.GITHUB_TOKEN }}"

Expand Down
3 changes: 2 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ Proxmox PBS & PVE System Files Backup
[![rclone](https://img.shields.io/badge/rclone-1.60+-136C9E.svg)](https://rclone.org/)
[![💖 Sponsor](https://img.shields.io/badge/Sponsor-GitHub%20Sponsors-pink?logo=github)](https://github.com/sponsors/tis24dev)
[![☕ Buy Me a Coffee](https://img.shields.io/badge/Buy%20Me%20a%20Coffee-tis24dev-yellow?logo=buymeacoffee)](https://github.com/sponsors/tis24dev)
[![💸 Donate](https://img.shields.io/badge/Donate-PayPal-blue?logo=paypal)](https://paypal.me/DNoventa)
</div>

## About the Project
Expand Down Expand Up @@ -104,4 +105,4 @@ A special thanks to the community members who help by testing releases and repor
<br clear="all" />

## Repo Activity
![Alt](https://repobeats.axiom.co/api/embed/d9565d6d1ed8222a5da5fedf25c18a9c8beab382.svg "Repobeats analytics image")
![Alt](https://repobeats.axiom.co/api/embed/d9565d6d1ed8222a5da5fedf25c18a9c8beab382.svg "Repobeats analytics image")
149 changes: 149 additions & 0 deletions cmd/proxsave/backup_execution.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,149 @@
// Package main contains the proxsave command entrypoint.
package main

import (
"context"
"errors"
"fmt"
"strings"
"time"

"github.com/tis24dev/proxsave/internal/logging"
"github.com/tis24dev/proxsave/internal/notify"
"github.com/tis24dev/proxsave/internal/orchestrator"
"github.com/tis24dev/proxsave/internal/types"
)

func runConfiguredBackup(opts backupModeOptions, orch *orchestrator.Orchestrator) (*orchestrator.BackupStats, *orchestrator.EarlyErrorState, int) {
if !opts.cfg.BackupEnabled {
logging.Warning("Backup is disabled in configuration")
return nil, nil, types.ExitSuccess.Int()
}

if earlyErrorState, exitCode := runPreBackupChecks(opts, orch); earlyErrorState != nil {
return nil, earlyErrorState, exitCode
}

logging.Step("Start Go backup orchestration")
hostname := resolveHostname()
backupDone := logging.DebugStart(opts.logger, "backup run", "proxmox=%s host=%s", opts.envInfo.Type, hostname)
stats, err := orch.RunGoBackup(opts.ctx, opts.envInfo, hostname)
if err != nil {
backupDone(err)
return handleBackupRunError(opts.ctx, orch, stats, err)
}
backupDone(nil)

persistBackupStats(orch, stats)
logBackupStatistics(stats)
logging.Info("✓ Go backup orchestration completed")
logServerIdentityValues(opts.serverIDValue, opts.serverMACValue)

if opts.heapProfilePath != "" {
logging.Info("Heap profiling saved: %s", opts.heapProfilePath)
}

logBackupExitStatus(stats.ExitCode)
return stats, nil, stats.ExitCode
}

func runPreBackupChecks(opts backupModeOptions, orch *orchestrator.Orchestrator) (*orchestrator.EarlyErrorState, int) {
preCheckDone := logging.DebugStart(opts.logger, "pre-backup checks", "")
if err := orch.RunPreBackupChecks(opts.ctx); err != nil {
preCheckDone(err)
logging.Error("Pre-backup validation failed: %v", err)
return &orchestrator.EarlyErrorState{
Phase: "pre_backup_checks",
Error: err,
ExitCode: types.ExitBackupError,
Timestamp: time.Now(),
}, types.ExitBackupError.Int()
}
preCheckDone(nil)
fmt.Println()
return nil, types.ExitSuccess.Int()
}

func handleBackupRunError(ctx context.Context, orch *orchestrator.Orchestrator, stats *orchestrator.BackupStats, err error) (*orchestrator.BackupStats, *orchestrator.EarlyErrorState, int) {
if ctx.Err() == context.Canceled {
logging.Warning("Backup was canceled")
orch.FinalizeAfterRun(ctx, stats)
return stats, nil, exitCodeInterrupted
}

var backupErr *orchestrator.BackupError
if errors.As(err, &backupErr) {
logging.Error("Backup %s failed: %v", backupErr.Phase, backupErr.Err)
orch.FinalizeAfterRun(ctx, stats)
return stats, nil, backupErr.Code.Int()
}

logging.Error("Backup orchestration failed: %v", err)
orch.FinalizeAfterRun(ctx, stats)
return stats, nil, types.ExitBackupError.Int()
}

func persistBackupStats(orch *orchestrator.Orchestrator, stats *orchestrator.BackupStats) {
if err := orch.SaveStatsReport(stats); err != nil {
logging.Warning("Failed to persist backup statistics: %v", err)
} else if stats.ReportPath != "" {
logging.Info("✓ Statistics report saved to %s", stats.ReportPath)
}
}

func logBackupStatistics(stats *orchestrator.BackupStats) {
fmt.Println()
logging.Info("=== Backup Statistics ===")
logging.Info("Files collected: %d", stats.FilesCollected)
if stats.FilesFailed > 0 {
logging.Warning("Files failed: %d", stats.FilesFailed)
}
logging.Info("Directories created: %d", stats.DirsCreated)
logging.Info("Data collected: %s", formatBytes(stats.BytesCollected))
logging.Info("Archive size: %s", formatBytes(stats.ArchiveSize))
logCompressionRatio(stats)
logging.Info("Compression used: %s (level %d, mode %s)", stats.Compression, stats.CompressionLevel, stats.CompressionMode)
if stats.RequestedCompression != stats.Compression {
logging.Info("Requested compression: %s", stats.RequestedCompression)
}
logging.Info("Duration: %s", formatDuration(stats.Duration))
logBackupArtifactPaths(stats)
fmt.Println()
}

func logCompressionRatio(stats *orchestrator.BackupStats) {
switch {
case stats.CompressionSavingsPercent > 0:
logging.Info("Compression ratio: %.1f%%", stats.CompressionSavingsPercent)
case stats.CompressionRatioPercent > 0:
logging.Info("Compression ratio: %.1f%%", stats.CompressionRatioPercent)
case stats.BytesCollected > 0:
ratio := float64(stats.ArchiveSize) / float64(stats.BytesCollected) * 100
logging.Info("Compression ratio: %.1f%%", ratio)
default:
logging.Info("Compression ratio: N/A")
}
}

func logBackupArtifactPaths(stats *orchestrator.BackupStats) {
if stats.BundleCreated {
logging.Info("Bundle path: %s", stats.ArchivePath)
logging.Info("Bundle contents: archive + checksum + metadata")
return
}

logging.Info("Archive path: %s", stats.ArchivePath)
if stats.ManifestPath != "" {
logging.Info("Manifest path: %s", stats.ManifestPath)
}
if stats.Checksum != "" {
logging.Info("Archive checksum (SHA256): %s", stats.Checksum)
}
}

func logBackupExitStatus(exitCode int) {
status := notify.StatusFromExitCode(exitCode)
statusLabel := strings.ToUpper(status.String())
emoji := notify.GetStatusEmoji(status)
logging.Info("Exit status: %s %s (code=%d)", emoji, statusLabel, exitCode)
}
Loading
Loading