Skip to content

Harden restore, storage, and post-install error handling, and more#212

Merged
tis24dev merged 31 commits into
mainfrom
dev
May 13, 2026
Merged

Harden restore, storage, and post-install error handling, and more#212
tis24dev merged 31 commits into
mainfrom
dev

Conversation

@tis24dev
Copy link
Copy Markdown
Owner

@tis24dev tis24dev commented May 13, 2026

  • Fix post-install TUI Disable All action
  • Fix post-install TUI and email delivery defaults
  • Fix post-install TUI, email fallback, and filesystem checks
  • Make BASE_DIR runtime-detected
  • fix: Deferred cleanup won't execute when close fails
  • fix restore UI EOF normalization
  • fix restore cleanup after prepare errors & fix TFA prompt with nil logger
  • fix restore service cleanup on prepare abort
  • fix PBS notification nil logger warnings
  • fix mount guard double probe race
  • fix PVE storage subdir error propagation
  • fix PBS datastore subdir error propagation
  • fsync PBS datastore config writes
  • Tighten ZFS mount path heuristics
  • Fix datastore mode enforcement without backup user
  • Preserve spaces in parsed config paths
  • Remove partial restore files on write failures
  • Sanitize email address headers
  • Clear stale log file handle on close failure
  • Escape email HTML status values
  • Cancel PXAR workers on first error
  • Return compressor start errors
  • Use form wrapper in password field test
  • Rename rollback UI error logger
  • Name network apply confirm timeout
  • Make interface name detection tests deterministic
  • Normalize safeexec factory formatting
  • Retry permission checks on close EIO
  • Defer temp symlink cleanup
  • Avoid duplicate lock close logging
  • Document lock close failure test setup

Summary by CodeRabbit

Release Notes

  • New Features

    • Base directory is now auto-detected from the installed executable; BASE_DIR environment variable is no longer user-configurable and will be ignored.
    • Email delivery method can be selected during installation (relay, sendmail, or Proxmox Notifications) with automatic sendmail fallback.
  • Bug Fixes

    • Improved error handling in backup operations with better cleanup of partially written files.
    • Enhanced filesystem compatibility detection for CIFS/SMB and FUSE mounts.
    • Fixed transient I/O errors during permission checking and compressor operations.
  • Documentation

    • Updated configuration guides to reflect base directory auto-detection and email delivery options.

tis24dev added 30 commits May 12, 2026 16:54
Make the post-install audit TUI apply all suggested disables when selecting
"Disable all" instead of only marking rows as selected.

Add shared disable-application handling, improve focus navigation between the
suggestion list and action buttons, and cover Disable all, Disable selected,
and navigation behavior with regression tests.
Fix the post-install audit TUI so “Disable all” applies all suggested disables immediately, records the applied keys, shows the completion state, and improves focus navigation in the review screen.

Restore the expected email delivery behavior: new installs default to the TIS24 relay with local sendmail failover, while manually selected PMF falls back through relay and then sendmail. Stop writing EMAIL_FALLBACK_PMF, keep it only as a transitional read alias, update installer prompts/templates/docs, and normalize the relay script version header to avoid INVALID_SCRIPT_VERSION failures from dev/local builds.

Add regression coverage for the TUI action path, installer defaults, email fallback routing, config aliases, dependency checks, and relay version normalization.
Fix the post-install audit TUI so “Disable all” applies all suggested changes immediately, records applied keys, opens the completion state, and avoids review-screen focus traps.

Restore email delivery defaults to relay with sendmail failover, keep PMF as an explicit manual method with relay/sendmail fallback, and normalize relay script-version headers.

Fix early-error notifications by preserving ServerID, ServerMAC, and ScriptVersion when pre-backup checks fail, so relay delivery has the required identity data.

Make backup/log permission checks filesystem-aware, skipping POSIX ownership warnings on CIFS/SMB/NTFS/FUSE-style filesystems, and update docs, templates, and regression tests.
Make BASE_DIR derive from the installed proxsave executable instead of backup.env or the parent environment.

Add a shared base-dir resolver, load configs with the detected runtime base directory, and ignore deprecated BASE_DIR values from config/env with explicit runtime warnings.

Align CLI and TUI install behavior so runtime-derived keys are removed consistently, keep upgrade/env migration from reintroducing BASE_DIR, and document the new behavior in templates and docs.

Add regression coverage for resolver behavior, ignored BASE_DIR overrides, CLI/TUI config cleanup, upgrade, and legacy migration.
Use errors.Is to detect EOF in restore UI error normalization so wrapped EOF
errors are treated as restore aborts.

Keep the existing warning message and abort behavior unchanged, and add coverage
for wrapped EOF handling.
…gger

Register prepared bundle cleanup immediately after prepareBundleAndPlan so it
also runs when planning returns an error after setting w.prepared.

Keep cleanup guarded for nil prepared bundles and remove the internal planning
cleanup path to avoid double cleanup across the shared CLI/TUI restore flow.

Ensure TFA compatibility recommendations are still prompted when no logger is
available.

Keep logger usage limited to emitting the optional re-enrollment warning, and
add coverage for confirmed and declined nil-logger flows.
Run already-registered restore service cleanups when a later service preparation
step returns an error or ErrRestoreAborted.

This ensures services stopped by preparePVEClusterRestore are restarted if
preparePBSServices aborts, while keeping the shared CLI/TUI restore flow aligned.
Guard PBS notification API warning logs when logger is nil.

Skip unknown sections and malformed endpoints without panicking, while preserving
existing warning output whenever a logger is provided.
Probe mount state only once in guardMountPoint and use that result to decide
whether to skip or create the guard bind mount.

Return the mounted status from ensureGuardTargetUnmounted and remove the second
isAlreadyMounted probe to avoid a race between mount checks.
Return and propagate errors from PVE storage subdirectory creation.

Aggregate per-storage failures in RecreateStorageDirectories so partial
directory recreation is reported upstream while preserving best-effort handling
for remaining storage entries.
Return errors from PBS datastore subdirectory creation instead of logging and
continuing.

Stop initialization on the first .chunks/.index creation failure and propagate
PBS datastore recreation errors upstream while preserving best-effort handling
for later datastore entries.
Sync normalized PBS datastore.cfg writes before replacing the target file.

Flush the temporary file before close/rename and fsync the containing directory
after rename so the updated datastore.cfg entry is persisted on stable storage.
Restrict common ZFS mount path detection to mount-like paths and full path segments.

The previous heuristic matched any path containing "backup" or "datastore", which could classify unrelated directories such as mybackup or datastore2 as ZFS mount points. The updated logic keeps /mnt/ handling and only treats backup/datastore as complete absolute path segments.

Added focused tests for valid mount-style paths and substring false positives.
Ensure PBS datastore directory permissions are applied even when backup ownership lookup does not find the backup user.
Parse storage and datastore path lines by trimming the path prefix instead of splitting fields, preserving paths that contain spaces.
Clean up partially restored files when restore copy or close fails, logging cleanup errors and preserving existing restore flow.
Remove CR/LF from generated To and From headers to prevent MIME header injection while preserving existing sendmail and PMF flows.
Ensure OpenLogFile clears the existing logFile reference even when closing the previous file returns an error.
Escape header, storage summary, and footer values before rendering the email HTML template to prevent raw HTML injection
Use a child cancellable context for PBS PXAR worker runs so sibling datastore workers stop promptly after the first failure
Preserve compressor Start failures when tar writer pipe cleanup reports a closed-pipe error
Keep the form component test on the wrapper API and assert the password field type before checking its label
Avoid a confusing error method name in network rollback UI flow while preserving the existing logger forwarding behavior.
Extract the network apply confirmation timeout into a package-level constant next to the rollback timeout
Extract pure bridge and wireless name detection helpers and test them directly without depending on Linux sysfs state
Use a consistent multi-line style for every allowed command factory without changing the command allowlist behavior
Treat transient EIO errors from closing permission test files like create failures, retrying before reporting filesystem I/O failure
Ensure temporary symlink checks are cleaned up with a deferred removal even when inline cleanup fails
Return lock file close failures through CheckResult without also logging a duplicate warning
Clarify why the lock-file test closes during sync to exercise the subsequent production Close failure path
@coderabbitai
Copy link
Copy Markdown

coderabbitai Bot commented May 13, 2026

📝 Walkthrough

Walkthrough

Executable-path base dir detection is introduced and propagated into config loading across commands. Email delivery is normalized with a sendmail-centric fallback chain and sanitized headers. Backup/orchestrator flows gain cancellation/cleanup and fsyncs. Filesystem checks skip POSIX on CIFS/FUSE. Installer/TUI and extensive tests/docs are updated.

Changes

Runtime BASE_DIR detection and config/path handling

Layer / File(s) Summary
Executable BASE_DIR detection and helpers
cmd/proxsave/runtime_helpers.go
Resolves BaseDir from executable using build-layout/marker scans with fallback helpers.
CLI/TUI commands load config with detected base dir
cmd/proxsave/*
Switch to LoadConfigWithBaseDir and export detected BASE_DIR; upgrade paths take base dir.
Deterministic base-dir in tests
cmd/proxsave/*_test.go
Adds forceDetectedBaseDirForTest and new resolveBaseDir tests.
Config loader, expansion, deprecated BASE_DIR handling
internal/config/config.go
Adds loader with detected base dir, runtime ${BASE_DIR} expansion, ignored-BASE_DIR accessors.
Env-template mutation, migration, upgrade
internal/config/*
Removes runtime-derived keys; ignores legacy BASE_DIR during migration; upgrade validates via detected base dir.
Config tests updated
internal/config/*_test.go
Use LoadConfigWithBaseDir, validate expansion, ignored config/env base dir.
Docs: BASE_DIR detection and precedence
docs/*
Documents BASE_DIR auto-detection and precedence exception.
Template and misc docs updates
internal/config/templates/backup.env, docs/*
Adds BASE_DIR guidance and related notes.

Email delivery normalization, fallback chain, installer/TUI updates

Layer / File(s) Summary
Email notifier fallback chain and header sanitization
internal/notify/*
Implements relay→sendmail and PMF→(relay)→(sendmail) fallback; sanitizes headers; updates method docs.
Email HTML templates escape
internal/notify/templates.go
Pre-escapes variables across header, storage sections, and footer.
Notifier/relay tests
internal/notify/*_test.go
Cover fallbacks, recipient cases, script-version normalization, and descriptions.
Network helpers use normalized method
cmd/proxsave/main_network.go
Decide relay network need via normalized method.
Installer/TUI selection and defaults
cmd/proxsave/install*.go, internal/tui/wizard/*
Prompt/dropdown for method; default relay with sendmail fallback; preserve edits; tests updated.
Docs mapping/behaviors
docs/*
Update EMAIL_* mappings, aliases, and troubleshooting.
Runtime features/tests reflect fallback
cmd/proxsave/helpers_test.go
Expect method switch to sendmail when relay is disabled.

Orchestrator hardening: directory creation, restore cleanup, TUI review

Layer / File(s) Summary
PVE/PBS directory recreation, config writing
internal/orchestrator/*recreation*
Aggregate errors, enforce subdir creation, fsync temp and dir, refine parsing/ownership/paths.
Directory recreation tests
internal/orchestrator/*_test.go
Validate errors, atomic write content/mode, parser behavior, ZFS segment rules.
Restore workflow cleanup/EOF
internal/orchestrator/restore_workflow_ui*.go
Run partial cleanups, defer prepared cleanup, handle wrapped EOF.
Restore tests
internal/orchestrator/*_test.go
Confirm cleanup behavior and EOF warning mapping.
Mount guard
internal/orchestrator/mount_guard*.go
Short-circuit when already mounted; minimize mountinfo reads.
Network apply UI tweaks
internal/orchestrator/network_apply*.go
Use constants for timeout; rename logging helper.
PBS notifications apply
internal/orchestrator/pbs_notifications_api_apply*.go
Nil-logger safe warnings; test added.
Early-error stats
internal/orchestrator/extensions*.go, .../notification_adapter_test.go
Add ScriptVersion and trimmed IDs; adapter preserves IDs.
Backup safety cleanup
internal/orchestrator/backup_safety*.go
Remove partial files on copy error; test added.
Post-install audit TUI
internal/tui/wizard/post_install_audit_tui*.go
Helper for applying disables and keyboard navigation; tests cover actions and focus.

Backup archiver start-failure handling and PBS collector cancellation

Layer / File(s) Summary
Archiver drains on start failure
internal/backup/archiver.go
Close pipe with error and drain writer to avoid hangs.
Archiver tests
internal/backup/archiver_test.go
Surface start errors, not closed-pipe artifacts.
PBS PXAR cancellation
internal/backup/collector_pbs_datastore.go
Cancel pending workers on first error via child context.
PBS cancellation test
internal/backup/collector_pxar_datastore_test.go
Verify single callback and first error return.

Security dependencies and filesystem capability-based checks

Layer / File(s) Summary
Checker deps and POSIX gating
internal/security/security.go
Normalize email deps; skip POSIX checks based on filesystem support.
Security tests
internal/security/security_test.go
Expect sendmail optional dep; verify CIFS path skips warnings.
Filesystem detection/auto-exclude
internal/storage/*
Recognize fuse.*, defer temp cleanup; auto-exclude FUSE/SMB/CIFS; tests updated.

Misc fixes: identity, logging, safeexec, checks, telegram bootstrap, TUI component test

Layer / File(s) Summary
Identity helpers/tests
internal/identity/*
Non-Linux detection via ByName helpers; tests updated.
Logger handle clearing
internal/logging/*
Nil logFile after Close even on error; test asserts.
Safeexec formatting
internal/safeexec/safeexec.go
Reformat factories; behavior unchanged.
Checks improvements
internal/checks/*
Close indirection and EIO retry; symlink cleanup; tests added.
Telegram bootstrap
internal/orchestrator/telegram_setup_bootstrap*.go
Loader signature includes baseDir; tests updated.
TUI component test
internal/tui/components/form_test.go
Explicit type assertion for form item.

Estimated code review effort

🎯 5 (Critical) | ⏱️ ~120 minutes

Possibly related PRs

  • tis24dev/proxsave#179 — Adds initial encryption setup; this PR updates that flow to use LoadConfigWithBaseDir and detected base dir.

Poem

I sniffed out BASE_DIR beneath the hill,
Let configs bloom by runtime will.
When relay trips, sendmail springs,
PMF may call before it wings.
Pipes drained, ghosts cleaned in the night—
My warren’s tidy, humming bright. 🐇✨

✨ Finishing Touches
📝 Generate docstrings
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch dev

@codacy-production
Copy link
Copy Markdown

Not up to standards ⛔

🔴 Issues 6 critical · 13 high · 10 medium

Alerts:
⚠ 29 issues (≤ 0 issues of at least minor severity)

Results:
29 new issues

Category Results
Security 6 critical
13 high
Complexity 10 medium

View in Codacy

🟢 Metrics 197 complexity · 47 duplication

Metric Results
Complexity 197
Duplication 47

View in Codacy

NEW Get contextual insights on your PRs based on Codacy's metrics, along with PR and Jira context, without leaving GitHub. Enable AI reviewer
TIP This summary will be updated as you push new changes.

Copy link
Copy Markdown

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 3

🧹 Nitpick comments (2)
internal/orchestrator/mount_guard.go (1)

66-72: ⚡ Quick win

Consider renaming or removing this function for clarity.

The function name ensureGuardTargetUnmounted implies it takes action to ensure the target is unmounted, but it only checks the mount status without modifying anything. Since it's now just a pass-through wrapper around isMounted, consider either:

  1. Inlining it by calling isMounted directly from guardMountPoint (line 37), or
  2. Renaming it to something like checkMountStatus or getMountStatus to accurately reflect its behavior.
♻️ Option 1: Inline the function
 func guardMountPoint(ctx context.Context, guardTarget string) error {
 	target, err := normalizeGuardMountRequest(ctx, guardTarget)
 	if err != nil {
 		return err
 	}
-	mounted, err := ensureGuardTargetUnmounted(target)
+	mounted, err := isMounted(target)
 	if err != nil {
 		return fmt.Errorf("check mount status: %w", err)
 	}
 	if mounted {
 		return nil
 	}
 
 	guardDir := guardDirForTarget(target)
 	if err := ensureGuardDirectories(guardDir, target); err != nil {
 		return err
 	}
 	return bindReadOnlyGuard(guardDir, target)
 }
-
-func ensureGuardTargetUnmounted(target string) (bool, error) {
-	mounted, err := isMounted(target)
-	if err != nil {
-		return false, err
-	}
-	return mounted, nil
-}
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@internal/orchestrator/mount_guard.go` around lines 66 - 72, The function
ensureGuardTargetUnmounted only forwards to isMounted but its name is
misleading; remove this wrapper and update guardMountPoint to call
isMounted(target) directly (preserve the existing error handling and return
semantics), then delete the ensureGuardTargetUnmounted function and run tests to
ensure no other references remain; alternatively, if you prefer keeping a
helper, rename ensureGuardTargetUnmounted to checkMountStatus or getMountStatus
and adjust its name where referenced (e.g., in guardMountPoint) to reflect that
it only queries state.
internal/tui/wizard/install.go (1)

456-456: 💤 Low value

Minor: Redundant runtime key removal calls.

config.RemoveRuntimeDerivedEnvKeys(template) is called at both line 456 and line 493 when the wizard is not skipped. While this is harmless (the operation is idempotent), the first call at line 456 appears unnecessary since the template will be sanitized again at line 493 right before writing.

♻️ Proposed simplification
 	if skipConfigWizard {
 		return installConfigResult{SkipConfigWizard: true}, nil
 	}
-	template = config.RemoveRuntimeDerivedEnvKeys(template)

 	logging.DebugStepBootstrap(bootstrap, "install config wizard (cli)", "configuring secondary storage")

Also applies to: 493-493

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@internal/tui/wizard/install.go` at line 456, Duplicate call to
config.RemoveRuntimeDerivedEnvKeys(template) is unnecessary; remove the earlier
invocation (the one executed unconditionally during UI setup) and keep the
single sanitization just before persisting the template inside the non-skipped
path. Specifically, delete the first call to
config.RemoveRuntimeDerivedEnvKeys(template) and ensure the remaining call
(inside the branch that writes the template) stays so the template is sanitized
right before writing.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@cmd/proxsave/base_dir_test.go`:
- Around line 11-12: The test is copying a sync.Once (execInfoOnce) which is
forbidden; change the save/restore logic to use pointers instead of copying the
value: capture references (e.g., &execInfo and &execInfoOnce or store the
original pointer fields) so you don't copy the sync.Once, and update the restore
path to reassign the pointers back to execInfo and execInfoOnce (or their
underlying fields) rather than doing a value copy; ensure all uses of
origInfo/origOnce in this test use the pointer-backed originals to avoid copying
the sync.Once.

In `@docs/CONFIGURATION.md`:
- Line 868: Replace the fragment about aliases with a self-contained sentence
that explicitly states the aliases and normalization behavior; update the text
mentioning `pmf` and its aliases (`proxmox`, `proxmox-notifications`,
`proxmox-mail-forward`) so it reads as a complete sentence such as: "The value
`pmf` may also be written as `proxmox`, `proxmox-notifications`, or
`proxmox-mail-forward`; ProxSave normalizes those aliases to `pmf`." Ensure this
replaces the existing fragment mentioning `pmf`.

In `@internal/backup/archiver.go`:
- Around line 727-730: In drainTarWriterAfterCompressorStartFailure, remove the
unnecessary blank identifier by replacing the receive expression `_ = <-errChan`
with a plain receive `<-errChan>` so the function still waits for the goroutine
to finish while satisfying staticcheck S1005; keep the existing CloseWithError
on pw and use the same parameters (pw, errChan, startErr).

---

Nitpick comments:
In `@internal/orchestrator/mount_guard.go`:
- Around line 66-72: The function ensureGuardTargetUnmounted only forwards to
isMounted but its name is misleading; remove this wrapper and update
guardMountPoint to call isMounted(target) directly (preserve the existing error
handling and return semantics), then delete the ensureGuardTargetUnmounted
function and run tests to ensure no other references remain; alternatively, if
you prefer keeping a helper, rename ensureGuardTargetUnmounted to
checkMountStatus or getMountStatus and adjust its name where referenced (e.g.,
in guardMountPoint) to reflect that it only queries state.

In `@internal/tui/wizard/install.go`:
- Line 456: Duplicate call to config.RemoveRuntimeDerivedEnvKeys(template) is
unnecessary; remove the earlier invocation (the one executed unconditionally
during UI setup) and keep the single sanitization just before persisting the
template inside the non-skipped path. Specifically, delete the first call to
config.RemoveRuntimeDerivedEnvKeys(template) and ensure the remaining call
(inside the branch that writes the template) stays so the template is sanitized
right before writing.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: 151e09ea-8715-43d4-8cfc-70f8bdc27f8d

📥 Commits

Reviewing files that changed from the base of the PR and between b7d9b0a and 3141652.

📒 Files selected for processing (94)
  • cmd/proxsave/base_dir_test.go
  • cmd/proxsave/config_helpers.go
  • cmd/proxsave/decrypt_cmd.go
  • cmd/proxsave/encryption_setup.go
  • cmd/proxsave/encryption_setup_test.go
  • cmd/proxsave/helpers_test.go
  • cmd/proxsave/install.go
  • cmd/proxsave/install_helpers_test.go
  • cmd/proxsave/install_test.go
  • cmd/proxsave/install_tui.go
  • cmd/proxsave/main_config_modes.go
  • cmd/proxsave/main_network.go
  • cmd/proxsave/main_runtime.go
  • cmd/proxsave/main_runtime_log.go
  • cmd/proxsave/new_install.go
  • cmd/proxsave/new_install_test.go
  • cmd/proxsave/newkey.go
  • cmd/proxsave/permissions.go
  • cmd/proxsave/runtime_helpers.go
  • cmd/proxsave/upgrade.go
  • docs/BACKUP_ENV_MAPPING.md
  • docs/CLI_REFERENCE.md
  • docs/CONFIGURATION.md
  • docs/EXAMPLES.md
  • docs/INSTALL.md
  • docs/MIGRATION_GUIDE.md
  • docs/TROUBLESHOOTING.md
  • internal/backup/archiver.go
  • internal/backup/archiver_test.go
  • internal/backup/collector_pbs_datastore.go
  • internal/backup/collector_pxar_datastore_test.go
  • internal/checks/checks.go
  • internal/checks/checks_test.go
  • internal/config/config.go
  • internal/config/config_test.go
  • internal/config/env_mutation.go
  • internal/config/migration.go
  • internal/config/migration_test.go
  • internal/config/templates/backup.env
  • internal/config/upgrade.go
  • internal/identity/identity.go
  • internal/identity/identity_test.go
  • internal/logging/logger.go
  • internal/logging/logger_test.go
  • internal/notify/email.go
  • internal/notify/email_delivery_methods_test.go
  • internal/notify/email_relay.go
  • internal/notify/email_relay_test.go
  • internal/notify/email_test.go
  • internal/notify/notify.go
  • internal/notify/notify_test.go
  • internal/notify/templates.go
  • internal/orchestrator/backup_safety.go
  • internal/orchestrator/backup_safety_test.go
  • internal/orchestrator/directory_recreation.go
  • internal/orchestrator/directory_recreation_config.go
  • internal/orchestrator/directory_recreation_ownership.go
  • internal/orchestrator/directory_recreation_paths.go
  • internal/orchestrator/directory_recreation_pbs.go
  • internal/orchestrator/directory_recreation_pbs_config.go
  • internal/orchestrator/directory_recreation_pve.go
  • internal/orchestrator/directory_recreation_test.go
  • internal/orchestrator/extensions.go
  • internal/orchestrator/extensions_additional_test.go
  • internal/orchestrator/mount_guard.go
  • internal/orchestrator/mount_guard_more_test.go
  • internal/orchestrator/network_apply.go
  • internal/orchestrator/network_apply_workflow_ui_prompt.go
  • internal/orchestrator/network_apply_workflow_ui_rollback.go
  • internal/orchestrator/notification_adapter_test.go
  • internal/orchestrator/pbs_notifications_api_apply.go
  • internal/orchestrator/pbs_notifications_api_apply_test.go
  • internal/orchestrator/restore_workflow_ui.go
  • internal/orchestrator/restore_workflow_ui_backups_services.go
  • internal/orchestrator/restore_workflow_ui_backups_services_test.go
  • internal/orchestrator/restore_workflow_ui_plan.go
  • internal/orchestrator/restore_workflow_ui_run.go
  • internal/orchestrator/restore_workflow_ui_test.go
  • internal/orchestrator/restore_workflow_ui_tfa.go
  • internal/orchestrator/restore_workflow_ui_tfa_test.go
  • internal/orchestrator/telegram_setup_bootstrap.go
  • internal/orchestrator/telegram_setup_bootstrap_test.go
  • internal/safeexec/safeexec.go
  • internal/security/security.go
  • internal/security/security_test.go
  • internal/storage/filesystem.go
  • internal/storage/filesystem_test.go
  • internal/storage/storage.go
  • internal/storage/storage_test.go
  • internal/tui/components/form_test.go
  • internal/tui/wizard/install.go
  • internal/tui/wizard/install_test.go
  • internal/tui/wizard/post_install_audit_tui.go
  • internal/tui/wizard/post_install_audit_tui_test.go
💤 Files with no reviewable changes (1)
  • internal/orchestrator/restore_workflow_ui_plan.go

Comment on lines +11 to +12
origInfo := execInfo
origOnce := execInfoOnce
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🔴 Critical | ⚡ Quick win

Critical: sync.Once must not be copied.

Lines 12 and 26 copy sync.Once by value, which is forbidden because sync.Once contains sync.noCopy to prevent exactly this. Copying synchronization primitives can cause race conditions and undefined behavior.

🔒 Proposed fix: use pointer to avoid copying
 func forceDetectedBaseDirForTest(t *testing.T, baseDir string) {
 	t.Helper()
 	origInfo := execInfo
-	origOnce := execInfoOnce
+	origOnce := &execInfoOnce

 	execPath := filepath.Join(baseDir, "build", "proxsave")
 	execInfo = ExecInfo{
 		ExecPath: execPath,
 		ExecDir:  filepath.Dir(execPath),
 		BaseDir:  baseDir,
 		HasBase:  true,
 	}
-	execInfoOnce = sync.Once{}
+	newOnce := sync.Once{}
+	newOnce.Do(func() {})
+	execInfoOnce = newOnce
-	execInfoOnce.Do(func() {})

 	t.Cleanup(func() {
 		execInfo = origInfo
-		execInfoOnce = origOnce
+		execInfoOnce = *origOnce
 	})
 }

Also applies to: 26-26

🧰 Tools
🪛 golangci-lint (2.12.2)

[error] 12-12: copylocks: assignment copies lock value to origOnce: sync.Once contains sync.noCopy

(govet)

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@cmd/proxsave/base_dir_test.go` around lines 11 - 12, The test is copying a
sync.Once (execInfoOnce) which is forbidden; change the save/restore logic to
use pointers instead of copying the value: capture references (e.g., &execInfo
and &execInfoOnce or store the original pointer fields) so you don't copy the
sync.Once, and update the restore path to reassign the pointers back to execInfo
and execInfoOnce (or their underlying fields) rather than doing a value copy;
ensure all uses of origInfo/origOnce in this test use the pointer-backed
originals to avoid copying the sync.Once.

Comment thread docs/CONFIGURATION.md
| `sendmail` | The node already has a local MTA such as Postfix, Exim, or Sendmail. | In the local MTA. ProxSave calls `/usr/sbin/sendmail`. |
| `pmf` | You explicitly want Proxmox Notifications via `proxmox-mail-forward`. | In Proxmox (`Datacenter -> Notifications` on PVE, or the PBS notification UI/config). ProxSave does not ask for SMTP host/port/user/password. |

`pmf` may also be written as `proxmox`, `proxmox-notifications`, or `proxmox-mail-forward`; ProxSave normalizes those aliases to `pmf`.
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟡 Minor | ⚡ Quick win

Make the alias sentence explicit and self-contained.

Line 868 reads a bit fragment-like in context. Rewording it as a complete sentence improves readability in this dense section.

✍️ Suggested wording
-`pmf` may also be written as `proxmox`, `proxmox-notifications`, or `proxmox-mail-forward`; ProxSave normalizes those aliases to `pmf`.
+You can also write `pmf` as `proxmox`, `proxmox-notifications`, or `proxmox-mail-forward`; ProxSave normalizes all of these aliases to `pmf`.
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
`pmf` may also be written as `proxmox`, `proxmox-notifications`, or `proxmox-mail-forward`; ProxSave normalizes those aliases to `pmf`.
You can also write `pmf` as `proxmox`, `proxmox-notifications`, or `proxmox-mail-forward`; ProxSave normalizes all of these aliases to `pmf`.
🧰 Tools
🪛 LanguageTool

[style] ~868-~868: To form a complete sentence, be sure to include a subject.
Context: ... SMTP host/port/user/password. | pmf may also be written as proxmox, `proxmox-...

(MISSING_IT_THERE)

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@docs/CONFIGURATION.md` at line 868, Replace the fragment about aliases with a
self-contained sentence that explicitly states the aliases and normalization
behavior; update the text mentioning `pmf` and its aliases (`proxmox`,
`proxmox-notifications`, `proxmox-mail-forward`) so it reads as a complete
sentence such as: "The value `pmf` may also be written as `proxmox`,
`proxmox-notifications`, or `proxmox-mail-forward`; ProxSave normalizes those
aliases to `pmf`." Ensure this replaces the existing fragment mentioning `pmf`.

Comment on lines +727 to +730
func drainTarWriterAfterCompressorStartFailure(pw *io.PipeWriter, errChan <-chan error, startErr error) {
_ = pw.CloseWithError(startErr)
_ = <-errChan
}
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major | ⚡ Quick win

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
# Verify the non-idiomatic receive assignment is removed.
rg -nP '\b_ = <-' internal/backup/archiver.go

Repository: tis24dev/proxsave

Length of output: 80


🏁 Script executed:

# Find staticcheck configuration files
fd -i 'golangci|staticcheck' -e yml -e yaml -e toml -e conf

Repository: tis24dev/proxsave

Length of output: 43


🏁 Script executed:

# Check context around lines 727-730 in the file
sed -n '720,735p' internal/backup/archiver.go

Repository: tis24dev/proxsave

Length of output: 596


🏁 Script executed:

# Search for other instances of idiomatic receive assignment issues in the codebase
rg -n '_ = <-' --type go

Repository: tis24dev/proxsave

Length of output: 108


🏁 Script executed:

# Search for CI/CD configuration files
fd -i '\.(github|gitlab)' -type d

Repository: tis24dev/proxsave

Length of output: 232


🏁 Script executed:

# Check for workflow files
find . -path '*/.github/workflows/*' -type f 2>/dev/null

Repository: tis24dev/proxsave

Length of output: 348


🏁 Script executed:

# Check for Makefile or other build configuration
fd -i 'makefile|justfile|\.circleci' -type f

Repository: tis24dev/proxsave

Length of output: 232


🏁 Script executed:

# Search for any reference to staticcheck in the repository
rg -i 'staticcheck' --type go --type sh --type yaml --type toml

Repository: tis24dev/proxsave

Length of output: 816


🏁 Script executed:

# Check the security-ultimate workflow
cat .github/workflows/security-ultimate.yml

Repository: tis24dev/proxsave

Length of output: 3570


🏁 Script executed:

# Check all workflow files for staticcheck references
rg 'staticcheck' .github/workflows/

Repository: tis24dev/proxsave

Length of output: 248


🌐 Web query:

staticcheck S1005 Go rule definition

💡 Result:

Staticcheck S1005 is: “Drop unnecessary use of the blank identifier”. It flags assignments where the blank identifier (_) is used but isn’t needed (the identifier’s use is redundant). [1][2] In practice, it’s about cases like assigning to _ in places where Go’s language semantics make that extra blank assignment unnecessary (Staticcheck’s description is exactly that “assigning to the blank identifier is unnecessary.”) [2] To view the official online definition, use the checks page anchor for S1005: https://staticcheck.dev/docs/checks#S1005. [1][2]

Citations:


Drop unnecessary blank identifier in receive expression.

Line 729 uses _ = <-errChan, which violates staticcheck rule S1005 (unnecessary blank identifier). Use a plain receive expression instead.

🔧 Proposed fix
 func drainTarWriterAfterCompressorStartFailure(pw *io.PipeWriter, errChan <-chan error, startErr error) {
 	_ = pw.CloseWithError(startErr)
-	_ = <-errChan
+	<-errChan
 }
🧰 Tools
🪛 GitHub Actions: Ultimate Security Analysis (Go) / 0_security.txt

[error] 729-729: staticcheck reported: unnecessary assignment to the blank identifier (S1005).

🪛 GitHub Actions: Ultimate Security Analysis (Go) / security

[error] 729-729: staticcheck reported: unnecessary assignment to the blank identifier (S1005)

🪛 GitHub Check: security

[failure] 729-729:
unnecessary assignment to the blank identifier (S1005)

🪛 golangci-lint (2.12.2)

[error] 729-729: S1005: unnecessary assignment to the blank identifier

(staticcheck)

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@internal/backup/archiver.go` around lines 727 - 730, In
drainTarWriterAfterCompressorStartFailure, remove the unnecessary blank
identifier by replacing the receive expression `_ = <-errChan` with a plain
receive `<-errChan>` so the function still waits for the goroutine to finish
while satisfying staticcheck S1005; keep the existing CloseWithError on pw and
use the same parameters (pw, errChan, startErr).

@tis24dev tis24dev merged commit 56b6a95 into main May 13, 2026
10 of 12 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant