Skip to content

Slice 6/6: tests + docs + upgrade tooling + dependabot bumps#263

Merged
tis24dev merged 30 commits into
devfrom
slice-6-tests-docs-upgrade
Jul 15, 2026
Merged

Slice 6/6: tests + docs + upgrade tooling + dependabot bumps#263
tis24dev merged 30 commits into
devfrom
slice-6-tests-docs-upgrade

Conversation

@tis24dev

@tis24dev tis24dev commented Jul 15, 2026

Copy link
Copy Markdown
Owner

Slice 6 of 6 (final) — completes the progressive rebuild of dev (base=dev), cherry-picked checkpoint range, tree matches b1c847f, go build ./... verified. ~59 files.

Content: characterization tests, docs rewrites (DAEMON, CONFIGURATION, RELEASE-PROCESS, CLUSTER_RECOVERY, ENCRYPTION), upgrade-beta.sh + --upgrade --localfile, golangci-lint fixes, dependabot bumps (bubbles v2.1.1, actions group).

After merge, dev == dev-temp (full work reconstructed in 6 reviewed PRs). Trigger review with @coderabbitai review.

Greptile Summary

This PR completes the final rebuild slice. The main changes are:

  • Adds signed beta-upgrade tooling and --upgrade --localfile finalization.
  • Adds characterization tests for upgrade, daemon, restore, health, and UI behavior.
  • Consolidates status rendering and applies lint fixes.
  • Rewrites operational, recovery, security, and configuration documentation.
  • Adds lint CI and updates pinned actions and Go dependencies.

Confidence Score: 5/5

This looks safe to merge.

  • No blocking issues found in the changed code.
  • The local upgrade path invokes the installed executable directly and retains trusted-executable checks during finalization.
  • The shared status renderer preserves sanitization and existing output behavior.

Important Files Changed

Filename Overview
upgrade-beta.sh Adds authenticated beta download, binary replacement, sanity-check rollback, and local finalization.
cmd/proxsave/upgrade.go Splits binary acquisition from shared post-install finalization and adds local-file mode.
internal/cli/args.go Adds parsing support for the local-file upgrade option.
cmd/proxsave/main_modes.go Restricts --localfile to upgrade mode.
internal/orchestrator/workflow_ui_status.go Exports shared sanitized status rendering for dashboard and workflow screens.
cmd/proxsave/dashboard.go Uses the shared status renderer and standardizes dashboard status labels.
.github/workflows/lint.yml Adds a pinned golangci-lint workflow.
go.mod Updates Bubbles and its runewidth dependency.

Flowchart

%%{init: {'theme': 'neutral'}}%%
flowchart TD
    A[Start upgrade] --> B{Local file mode?}
    B -- No --> C[Fetch and install latest release]
    B -- Yes --> D[Use installed binary]
    C --> E[Shared finalization]
    D --> E
    E --> F[Upgrade configuration]
    F --> G[Refresh docs and symlinks]
    G --> H[Migrate or restart daemon]
    H --> I[Normalize permissions]

    J[upgrade-beta.sh] --> K[Download beta assets]
    K --> L[Verify signature and checksum]
    L --> M[Back up and replace binary]
    M --> N{Version check passes?}
    N -- No --> O[Restore previous binary]
    N -- Yes --> P[Run local-file finalization]
    P --> E
Loading
%%{init: {'theme': 'base', 'themeVariables': {"darkMode": true, "background": "#0d1117", "primaryColor": "#21262d", "primaryTextColor": "#e6edf3", "primaryBorderColor": "#8b949e", "lineColor": "#8b949e", "textColor": "#e6edf3", "edgeLabelBackground": "#161b22", "actorBkg": "#21262d", "actorBorder": "#8b949e", "actorTextColor": "#e6edf3", "actorLineColor": "#8b949e", "signalColor": "#8b949e", "signalTextColor": "#e6edf3", "noteBkgColor": "#373320", "noteBorderColor": "#d4a72c", "noteTextColor": "#f0e6c0", "labelBoxBkgColor": "#21262d", "labelBoxBorderColor": "#8b949e", "labelTextColor": "#e6edf3", "loopTextColor": "#e6edf3", "activationBkgColor": "#30363d", "activationBorderColor": "#8b949e"}}}%%
flowchart TD
    A[Start upgrade] --> B{Local file mode?}
    B -- No --> C[Fetch and install latest release]
    B -- Yes --> D[Use installed binary]
    C --> E[Shared finalization]
    D --> E
    E --> F[Upgrade configuration]
    F --> G[Refresh docs and symlinks]
    G --> H[Migrate or restart daemon]
    H --> I[Normalize permissions]

    J[upgrade-beta.sh] --> K[Download beta assets]
    K --> L[Verify signature and checksum]
    L --> M[Back up and replace binary]
    M --> N{Version check passes?}
    N -- No --> O[Restore previous binary]
    N -- Yes --> P[Run local-file finalization]
    P --> E
Loading

Reviews (1): Last reviewed commit: "ci: bump the actions-updates group acros..." | Re-trigger Greptile

Summary by CodeRabbit

  • New Features
    • Added --localfile upgrade support to finalize upgrades without downloading another binary.
    • Added a verified beta upgrade script with signature and checksum checks plus rollback protection.
    • Added automated Go linting and expanded security/testing workflows.
  • Bug Fixes
    • Improved upgrade validation and handling of cancellation, already-current versions, and failures.
    • Standardized dashboard and workflow status messages with clearer uppercase labels and safer text rendering.
  • Documentation
    • Expanded guidance for daemon scheduling, restores, cloud storage, notifications, security, upgrades, and release verification.

tis24dev and others added 30 commits July 15, 2026 10:56
… prompt

The guide walked operators into the destructive cluster-restore behavior
without ever naming the mandatory safety prompt that governs it. Rewritten
against the current restore engine, verified by two adversarial review rounds.

- New section "Cluster restore modes: SAFE vs RECOVERY": the mandatory prompt
  shown whenever a PVE restore includes pve_cluster and the backup carries
  cluster data (FULL and STORAGE always reach it, CUSTOM only if selected,
  SYSTEM BASE never). SAFE applies exported configs to the running cluster via
  pvesh/pveum and never writes config.db, stops services, or unmounts /etc/pve;
  RECOVERY stops the four services, unmounts /etc/pve, restores config.db, and
  is only for an offline/isolated node.
- Corrected the old "STORAGE only -> stop services / unmount /etc/pve /
  restart" story: that sequence is RECOVERY only, not a result of the STORAGE
  scope. Fixed Scenarios 1/2/4/5 to choose the right mode (RECOVERY when the
  node is offline, SAFE when it is up) instead of implying STORAGE restores
  config.db by itself.
- Documented: automatic VM/CT source-node selection on a hostname mismatch,
  the offline-storage mount guards + `proxsave --cleanup-guards`, the armed
  180s network/firewall/HA/access-control auto-rollback, the preserved safety
  backup and its rm hint, the real STORAGE category set, and that extraction
  preserves owner/mode from the archive.
- During RECOVERY, /etc/pve is not written directly: the staged config
  categories skip their apply (config.db owns them) with a shadow-guard as a
  backstop; contents return from config.db on remount.
- Dropped the unnecessary `cd /opt/proxsave` steps, fixed the fabricated
  per-service log transcript, and removed style tells.
…ainst the real pipeline

The release trigger, workflow names, and provenance asset names had all drifted.
Rewritten against the live workflows and .goreleaser.yml, verified by an
adversarial review pass.

RELEASE-PROCESS.md:
- Real flow: push an unprotected pr-vX.Y.Z tag on dev HEAD -> release-intake.yml
  opens a "Release vX.Y.Z" dev->main PR with a release-tag body marker ->
  release-guard.yml validates the PR -> squash-merge -> post-merge-release.yml
  force-moves dev (--force-with-lease) and creates the authoritative vX.Y.Z tag
  once on the squash commit -> release.yml gates on reachable-from-main, validates
  the signing key before publishing, runs GoReleaser, signs SHA256SUMS, and
  attests provenance.
- Removed the wrong "git tag vX.Y.Z on main" trigger and the "do not tag on dev"
  rule (the pr-v* trigger is pushed on dev); removed the nonexistent sync-dev.yml
  and autotag.yml references; kept the legacy-escape-hatch note.

PROVENANCE_VERIFICATION.md:
- State that only linux/amd64 is published and use the real underscore asset names
  (proxsave_X.Y.Z_linux_amd64); fixed every gh attestation example accordingly.
- Deleted the fabricated "Migration from GPG" section and the GPG comparison table
  (no GPG scheme, SHA256SUMS.asc, or that key ever shipped); removed the macOS and
  Windows binary examples; refreshed stale version examples to a vX.Y.Z placeholder.
- Kept the accurate ECDSA P-256 / pinned-key SHA256SUMS.sig section; noted the
  attestation subject-path build/proxsave_*.
…bsystem

The stub framed the daemon as reporting "two independent checks" and omitted
roughly half of what it does. Rewritten from a code map of the daemon +
internal/health subsystem, verified by an adversarial review pass.

- The four monitored checks with exact ping semantics: proxsave-alive
  (heartbeat), proxsave-backup (/start then the exit code, or /fail on a hang),
  proxsave-updates (/0 up to date, /1 available), and one
  proxsave-notify-<channel> per channel the run attempted. Corrected the old
  "/1 warning" claim (the backup finish ping is the exit code, not a fixed /1).
- BACKUP_ENABLED=false skips the scheduled run while the heartbeat continues.
- The SIGUSR1 standalone-backup handoff (the daemon is the sole pinger; a manual
  or dashboard run hands off its exit code, 15-minute stale drop).
- --daemon-status output fields and exit-code semantics (0 only when running and
  aligned); the conditional version/alignment lines.
- Binary alignment after an in-place upgrade (hash-free /proc/<pid>/exe) and the
  restart-and-verify that waits for an in-progress backup.
- The systemd unit (Type=simple, --config pin, Restart=always, RestartSec=10),
  the five on-disk state files under identity/, centralized vs self URL
  resolution, and the full config-key set (adds the updates and eight notify
  keys the stub omitted; notes HEALTHCHECK_ENABLED is forced true by setup).
…d-vs-template defaults

CONFIGURATION.md documented correct key names but omitted two config areas and
presented several template values as if they were the compiled fallbacks.
Verified against config.go by an adversarial review pass.

- Add a Scheduler engine section (SCHEDULER_MODE/TIME, MAX_RUN_DURATION,
  DAEMON_OPT_OUT) and a Healthchecks connector section (the full HEALTHCHECK_*
  set including the updates and eight per-channel notify keys), both pointing to
  DAEMON.md. Note HEALTHCHECK_ENABLED is forced true when enabling the daemon,
  and a fresh install writes SCHEDULER_MODE=daemon.
- Fix the config-file default: configs/backup.env resolves under the install
  directory (BASE_DIR), not the current directory; absolute --config is used
  as-is.
- Distinguish compiled fallbacks from the shipped template values: compression
  6/standard (template 9/ultra), RCLONE_TRANSFERS 4 (template 16),
  RCLONE_BANDWIDTH_LIMIT empty (template 10M), MIN_DISK_SPACE 10 GB with <=0
  coerced and SECONDARY/CLOUD falling back to PRIMARY, MAX_*_BACKUPS 7/14/30
  (template 15), GFS tiers all 0 (off until enabled, DAILY forced >=1).
- Add the Telegram delivery-confirmation keys; use the canonical compression
  values (gz/bz2/zst) with the long names as aliases; note advanced and extreme
  both resolve to the debug log level. Remove curly quotes, em dashes, and a
  stray Unicode ellipsis.
…steps

The install wizard now chooses a scheduler engine and a healthchecks mode, and
--upgrade migrates cron installs to the daemon, but INSTALL.md still framed
scheduling as a cron-only step. Verified against the install code.

- Wizard: add the Scheduler engine step (ProxSave local daemon vs system cron;
  fresh installs and Overwrite default to the daemon, editing keeps the stored
  engine) and the Healthchecks step (daemon only: Off / ProxSave HC Server
  (centralized, default) / Your own server (self, with a ping-URL screen and a
  verification screen); dimmed and forced off with cron). Reframe the schedule as
  "Run at (HH:MM)", the daily time used by either engine.
- Upgrade: drop the wrong "does NOT touch your cron schedule"; --upgrade migrates
  a cron install to the daemon unless opted out with --daemon-remove, and a daemon
  install stays on the daemon. Check the scheduler with proxsave --daemon-status.
- Note install.sh accepts --cli for the text-mode installer; fix the config-path
  framing (from --config, resolved under BASE_DIR, not asked in the wizard); add
  a "Scheduling and the daemon" cross-link to DAEMON.md and the --daemon-* flags.
- Remove decorative TOC emoji, a stray em dash, and the check/cross emoji list.
…lized bot relay

New HIGH-create doc mapped against the notification code: the non-critical
invariant (IsCritical false, adapter swallows Send errors, exit code frozen
before dispatch), the two-tier delivery/monitoring split, dispatch order with
Healthchecks last, the centralized Telegram relay (bot token stays on the host),
TOFU relay-secret provisioning, the two-response delivery model (acceptance vs
polled delivery), the portal magic-link sanitize boundary, Email/Gotify/Webhook
channels, the serverbot transport contract, and the log-redaction rules.
Indexed in docs/README.md.
RESTORE_TECHNICAL.md described superseded, less-safe mechanisms. Rewritten
against internal/orchestrator: the atomic sibling-temp + rename extraction model
(fchown/fchmod on the FD, .proxsave-tmp-* pattern) replacing the old
os.Create+io.Copy-on-target snippets; the three-tier category split
(normal/export/staged) replacing the two-pass framing; the real staged-apply
order (PBS, PVE, PVE SDN, access-control, accounts, notifications) with the
anti-lockout account merge and PBS raw-config skips; dedup symlink
materialization (issue #70); the secure temp-root guard (#54) and registry
orphan cleanup (#55) added to Safety Mechanisms; isRealRestoreFS on the root
check; ZFS check corrected (category-gated, not PBS-only); restore_<ts>_<seq>.log
log name. Style pass: removed em dashes, curly quotes, and table emoji.
DEVELOPER_GUIDE.md navigated by a stale map. Regenerated the internal/ package
tree from the real layout (internal/tui -> internal/ui; added health, serverbot,
safefs, safeexec, cron, installer, support, uitest, and others; removed the
non-existent configs/ dir); noted the embedded config template and the Charm UI
stack. Replaced the cobra dependency examples with real deps (filippo.io/age,
stdlib flag); fixed the benchmark path (internal/backup, not internal/compression);
completed the compression list; added make coverage / coverage-check and a
section on the two structural lint guards (check-no-tview, check-serverbot-leaf);
pointed lint at make lint. Trimmed decorative emoji. Indexed via existing links.
SECURITY.md was accurate but far too thin (58 lines, only the threat model and
gosec bookkeeping). Expanded from the runtime security code with new sections:
execution model (safeexec argv-only allowlist, no shell, self-exec validation,
rclone sanitizers); the security preflight (executable integrity with symlink
refusal + SameFile TOCTOU + <exe>.md5, permission/ownership enforcement that
refuses to touch symlinks, private-key scan, network/process checks, abort
semantics, and the real env-var defaults); secret redaction; terminal-escape
scrubbing of untrusted display data; immutable identity secrets; bounded
filesystem I/O (safefs); read-only bind guards. Softened the G304 note (a few
legacy #nosec sites remain). Cross-linked NOTIFICATIONS/DAEMON/RESTORE_TECHNICAL.
… drift

RESTORE_GUIDE.md and RESTORE_DIAGRAMS.md were substantively accurate on the
engine but drifted on presentation. --restore/--decrypt default to a Charm TUI
now (classic text prompts only under --cli), so added a framing note and relabeled
the transcripts as the --cli experience. Fixed the category counts (11 PVE + 9 PBS
+ 12 Common = 32; PVE host 23, PBS host 21) and documented the missing 'accounts'
Common category (passwd/group/shadow/gshadow/sudoers, safe merge preserving the
current host root/system accounts). Replaced the nonexistent decryption sub-menu
with the single key-or-passphrase field, documented the two-stage restore
confirmation, and noted that STORAGE/BASE strip export-only categories. In
DIAGRAMS: renamed Two-Pass to Three-Tier Extraction (added the staged-apply lane),
dropped the passphrase/identity branch, and added accounts to CommonFull. Style
pass: removed em dashes, curly quotes, and ellipses.
… bundle layout

CLOUD_STORAGE.md was largely accurate but omitted several behaviors. Added an
RCLONE_FLAGS row (whitespace-split, injected verbatim into every rclone command)
and a BUNDLE_ASSOCIATED_FILES row (default true). Corrected CLOUD_REMOTE from
'required' to empty-by-default (cloud is silently disabled when the remote is
blank, even with CLOUD_ENABLED=true). Documented RCLONE_TIMEOUT_OPERATION=0 as
unbounded uploads while management/query ops use that value when > 0 else a 300s
floor. Added a 'Cloud layout (bundle vs raw)' section (single .bundle.tar by
default vs raw archive + sidecars, with .manifest.json as the authoritative raw
metadata) and a 'How ProxSave invokes rclone' section (fixed subcommand allowlist,
copyto not copy). Noted the legacy env-var aliases and that bundling is the default
in the DR steps. Style pass: removed curly quotes.
… ownership

ENCRYPTION.md was largely accurate but omitted a few enforced behaviors.
Documented the passphrase-strength policy (min 12 chars, at least 3 of 4 character
classes, a small weak-passphrase blocklist), the AGE_RECIPIENTS plural fallback
alias (used only when AGE_RECIPIENT is empty), and that AGE_RECIPIENT_FILE is empty
by default and resolved to <BASE_DIR>/identity/age/recipient.txt at runtime. Added
the root:root ownership requirement (auto-fixed with AUTO_FIX_PERMISSIONS, else
warned) to the permission notes, and expanded the salt section to the decrypt
fallback chain (per-install v2 manifest salt first, then the proxsave and
pre-rebrand proxmox-backup-go v1 namespaces). Style pass: removed an em dash and an
ellipsis.
…th, support email; add exit codes

Two HIGH plus reference gaps. Replaced the nonexistent lz4 compression value with
the real set (gzip/bzip2/xz/lzma/zstd, also pigz; none). Removed the fabricated
[DRY-RUN] output block and described what dry-run actually does. Corrected the lock
file path (.backup.lock under LOCK_PATH, default <BASE_DIR>/lock/.backup.lock) and
the support-mode email (the maintainer address is baked into the build, injected at
build time, not the hardcoded github-support@tis24.it). Added an Exit Codes table
(0-15 from types/exit_codes.go), including that a warnings-only run is promoted from
0 to exit 1 and errors to 4. Style pass: removed curly quotes and ellipses.
…itest

TEST_STRATEGY.md was the only Italian doc while docs/README links it as English.
Translated it to English. Updated the stale legacy-file count (347 legacy + 30
audited, was 238) and added a section on internal/uitest (test-only Charm driver
helpers; uitest.Deadline scales render-poll timeouts by raceScale 8 under -race, 1
otherwise) plus the serial/no-CPU-saturation constraint for UI tests. Corrected the
context reference: the pre-test audit file is diagnostics/coverage-audit-2026-06-09.md
(dev @ 3222a30, the tests-audit-baseline commit); dropped the fabricated
precoverage-bughunt filename and the unverifiable '26 bugs' count.
…fest vs metadata

The three audit LOW imprecisions, verified against internal/backup/collector*.go.
Only newDualRecipe() lives in collector_bricks.go; the per-role builders are in
collector_bricks_pve.go/_pbs.go/_system.go. Documented that the PBS branch runs two
recipes (newPBSRecipe then newPBSUserConfigRecipe, the per-user API-token pass seeded
by the user_list.json snapshot). Split the manifest/metadata fields: the collector
manifest.json carries only ProxmoxType/ProxmoxTargets and is an ExportOnly diagnostic
not read by restore, while PVE_VERSION/PBS_VERSION are written by the orchestrator into
the metadata sidecar that restore actually reads. Expanded Related Files with the split
builder files.
…dd a daemon example

Example 4 depicted the --newkey and --decrypt flows as numbered text menus; they are a
Charm TUI by default (--cli forces text prompts). Reworded both: --newkey derives a
recipient from a passphrase (min 12 chars, at least 3 character classes), and --decrypt
selects a backup, takes the key/passphrase in a single field, and prompts for a
destination directory (defaults to decrypt/ under BASE_DIR), writing
<name>.decrypted.bundle.tar there. Added Example 11 for the resident daemon and
healthchecks monitoring (--daemon-setup/--daemon-status/--daemon-remove, the systemd
service, and the four monitored checks). Style pass: removed an ellipsis.
…rouping comment

Resolves the two deferred dashboard-consistency items.

Issue 3 (Status keyword casing): the shared Status: colored keyword was fed in three
casings across sibling screens (ALL-CAPS, lowercase, Title). Standardized on ALL-CAPS
at the caller sites: restartVerifyStatus, the daemon admin results, the cleanup-guards
and update-config check screens, and the post-install audit. daemonStatusStyle is
shared with the plain-text --daemon-status CLI line, so its dashboard screen is
uppercased at the graphical consumer only, leaving the CLI/log readout in its natural
case; installVerifyVerdict and summarizeRestartVerify (log/install-outcome lines) are
likewise left natural. RenderStatusLevel is untouched (it must not blindly upcase
embedded version tokens). Tests updated to the new casing.

Issue 8 (menu enum comment): clarified that the Action enum's second group is a
DISPATCH grouping (routed through runDashboardDiagnostic), not the menu layout, so
ActionCheckUpgrade rendering under Maintenance is expected, not a mismatch.

Build, vet, and the cmd/proxsave + install + menu tests are green.
Both are upgrade finalize-block bricks that had zero direct test coverage.
Pin their observable behavior before extracting an upgradeFinalizePhase seam:

- maybeAutoMigrateDaemon: the three gates that short-circuit before the
  systemd migration (already-daemon, opted-out, unreadable config). The real
  migration branch shells to systemctl and is left to integration coverage;
  absence of the pre-migration stdout marker proves the gate held.
- installSupportDocs: every embedded doc written under the base dir at its
  Name (nested dirs included), byte-for-byte at mode 0600, plus the
  directory-creation error path.
Split runUpgrade into two phases so the local finalize can be driven on a
binary that is already on disk:

- upgradeAcquireBinary: download+install the latest release (default), or with
  --localfile return the on-disk binary and skip the release check + download
  entirely (a fetch would resolve the latest STABLE release and could pull a
  beta tester off their build).
- upgradeFinalizePhase: the shared post-binary work (upgrade backup.env,
  refresh docs/symlinks, migrate/restart the daemon, fix permissions, footer).

--localfile is a modifier of --upgrade (rejected on its own) and is the seam
upgrade-beta.sh will use after it swaps in a verified beta binary. Behavior of
plain --upgrade is unchanged. Covered by the maybeAutoMigrateDaemon and
installSupportDocs characterization tests plus new tests for the localfile
branch and the validation guard.
Twin of install.sh for beta/rc testers. The built-in --upgrade and install.sh
only see /releases/latest, which excludes prereleases, so betas are invisible
to them.

Resolution looks at the newest release OVERALL (/releases is newest-first, so
the first non-draft entry is it) and branches on its prerelease flag:
  - newest is a beta   -> show installed vs available, confirm, install it,
  - newest is stable   -> point the user at 'proxsave --upgrade' and stop, so a
                          month-old beta is never installed over a newer stable.
An explicit tag argument forces that tag (escape hatch) and skips the guards.

It downloads the archive + SHA256SUMS + .sig, verifies the signature (same
pinned ECDSA key) and checksum identically to install.sh, backs the current
binary up to .prev, swaps in the beta, sanity-checks it runs (rolling back on
failure), and finalizes with 'proxsave --upgrade --localfile' so the beta binary
does the local upgrade (backup.env keys, docs, daemon, permissions) without a
re-download. Confirmation is interactive (/dev/tty) unless -y is passed.

Hardening (adversarial review):
  - on finalize failure, print the rollback path explicitly (set -e would
    otherwise abort before the footer that documents .prev),
  - guard the installed-version probe against set -e when a corrupt binary's
    --version exits non-zero (the 'unknown' fallback was unreachable),
  - refuse a publish-order downgrade in the auto path (sort -V): a back-ported
    beta published after a newer stable is no longer offered.
The auto-detect downgrade guard referenced ${BETA_TAG#v} before BETA_TAG was
assigned (it was only set after the guard), so under 'set -euo pipefail' the
auto path aborted with 'BETA_TAG: unbound variable' before any download,
breaking 'sh upgrade-beta.sh' whenever a beta was available. Move the
BETA_TAG=${LATEST_TAG} assignment ahead of the guard.
…urity-patches group across 1 directory (#249)

deps(deps): bump charm.land/bubbles/v2

Bumps the security-patches group with 1 update in the / directory: [charm.land/bubbles/v2](https://github.com/charmbracelet/bubbles).


Updates `charm.land/bubbles/v2` from 2.1.0 to 2.1.1
- [Release notes](https://github.com/charmbracelet/bubbles/releases)
- [Commits](charmbracelet/bubbles@v2.1.0...v2.1.1)

---
updated-dependencies:
- dependency-name: charm.land/bubbles/v2
  dependency-version: 2.1.1
  dependency-type: direct:production
  update-type: version-update:semver-patch
  dependency-group: security-patches
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
…250)

Bumps the actions-updates group with 6 updates in the / directory:

| Package | From | To |
| --- | --- | --- |
| [actions/setup-go](https://github.com/actions/setup-go) | `6.4.0` | `6.5.0` |
| [github/codeql-action/init](https://github.com/github/codeql-action) | `4.36.2` | `4.37.0` |
| [github/codeql-action/analyze](https://github.com/github/codeql-action) | `4.36.2` | `4.37.0` |
| [goreleaser/goreleaser-action](https://github.com/goreleaser/goreleaser-action) | `7.2.2` | `7.2.3` |
| [actions/attest-build-provenance](https://github.com/actions/attest-build-provenance) | `4.1.0` | `4.1.1` |
| [github/codeql-action/upload-sarif](https://github.com/github/codeql-action) | `4.36.2` | `4.37.0` |



Updates `actions/setup-go` from 6.4.0 to 6.5.0
- [Release notes](https://github.com/actions/setup-go/releases)
- [Commits](actions/setup-go@4a36011...924ae3a)

Updates `github/codeql-action/init` from 4.36.2 to 4.37.0
- [Release notes](https://github.com/github/codeql-action/releases)
- [Changelog](https://github.com/github/codeql-action/blob/main/CHANGELOG.md)
- [Commits](github/codeql-action@8aad20d...99df26d)

Updates `github/codeql-action/analyze` from 4.36.2 to 4.37.0
- [Release notes](https://github.com/github/codeql-action/releases)
- [Changelog](https://github.com/github/codeql-action/blob/main/CHANGELOG.md)
- [Commits](github/codeql-action@8aad20d...99df26d)

Updates `goreleaser/goreleaser-action` from 7.2.2 to 7.2.3
- [Release notes](https://github.com/goreleaser/goreleaser-action/releases)
- [Commits](goreleaser/goreleaser-action@5daf1e9...f06c13b)

Updates `actions/attest-build-provenance` from 4.1.0 to 4.1.1
- [Release notes](https://github.com/actions/attest-build-provenance/releases)
- [Changelog](https://github.com/actions/attest-build-provenance/blob/main/RELEASE.md)
- [Commits](actions/attest-build-provenance@a2bbfa2...0f67c3f)

Updates `github/codeql-action/upload-sarif` from 4.36.2 to 4.37.0
- [Release notes](https://github.com/github/codeql-action/releases)
- [Changelog](https://github.com/github/codeql-action/blob/main/CHANGELOG.md)
- [Commits](github/codeql-action@8aad20d...99df26d)

---
updated-dependencies:
- dependency-name: actions/attest-build-provenance
  dependency-version: 4.1.1
  dependency-type: direct:production
  update-type: version-update:semver-patch
  dependency-group: actions-updates
- dependency-name: actions/setup-go
  dependency-version: 6.5.0
  dependency-type: direct:production
  update-type: version-update:semver-minor
  dependency-group: actions-updates
- dependency-name: github/codeql-action/analyze
  dependency-version: 4.36.3
  dependency-type: direct:production
  update-type: version-update:semver-patch
  dependency-group: actions-updates
- dependency-name: github/codeql-action/init
  dependency-version: 4.36.3
  dependency-type: direct:production
  update-type: version-update:semver-patch
  dependency-group: actions-updates
- dependency-name: github/codeql-action/upload-sarif
  dependency-version: 4.36.3
  dependency-type: direct:production
  update-type: version-update:semver-patch
  dependency-group: actions-updates
- dependency-name: goreleaser/goreleaser-action
  dependency-version: 7.2.3
  dependency-type: direct:production
  update-type: version-update:semver-patch
  dependency-group: actions-updates
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
@qodo-code-review

Copy link
Copy Markdown

Qodo reviews are paused for this user.

Troubleshooting steps vary by plan Learn more →

On a Teams plan?
Reviews resume once this user has a paid seat and their Git account is linked in Qodo.
Link Git account →

Using GitHub Enterprise Server, GitLab Self-Managed, or Bitbucket Data Center?
These require an Enterprise plan - Contact us
Contact us →

@sourcery-ai sourcery-ai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Sorry @tis24dev, your pull request is larger than the review limit of 150000 diff characters

@coderabbitai

coderabbitai Bot commented Jul 15, 2026

Copy link
Copy Markdown

Review Change Stack

📝 Walkthrough

Walkthrough

This pull request adds a local-file upgrade path and prerelease upgrade script, standardizes dashboard status rendering, introduces Go linting, refreshes pinned CI actions and dependencies, and expands documentation for daemon operation, restore workflows, security, notifications, configuration, and release verification.

Changes

Upgrade flow

Layer / File(s) Summary
Local-file upgrade contract
internal/cli/args.go, cmd/proxsave/main_modes.go, cmd/proxsave/main_modes_test.go
Adds --localfile, validates that it is used with --upgrade, and tests both accepted and rejected combinations.
Two-phase upgrade orchestration
cmd/proxsave/upgrade.go, cmd/proxsave/upgrade_localfile_test.go
Separates binary acquisition from finalization, supports local binaries, handles cancellation and terminal gates, and tests the no-download path.
Prerelease download and rollback
upgrade-beta.sh
Adds prerelease selection, signature/checksum verification, binary replacement with rollback, sanity checking, and local upgrade finalization.

Status UI

Layer / File(s) Summary
Shared status prompt contract
internal/orchestrator/*, cmd/proxsave/dashboard_check_apply.go, internal/ui/flows/install/*
Exports BuildStatusPrompt, routes dashboard and workflow rendering through it, sanitizes content, and standardizes uppercase status keywords.
Daemon status outcomes
cmd/proxsave/dashboard.go, cmd/proxsave/daemon_setup.go, cmd/proxsave/daemon_restart_verify_test.go
Updates daemon alignment and restart status rendering while preserving existing gate behavior.
Dashboard upgrade interaction tests
cmd/proxsave/dashboard_upgrade.go, cmd/proxsave/dashboard_upgrade_test.go
Covers repeated upgrade results, argument forwarding, warning display, and asynchronous completion.
Daemon migration and support tests
cmd/proxsave/daemon_auto_migrate_characterization_test.go, cmd/proxsave/install_docs_characterization_test.go
Adds characterization coverage for migration gates and embedded support-document installation.

CI and repository tooling

Layer / File(s) Summary
Go lint workflow
.github/workflows/lint.yml, .golangci.yml, README.md
Adds a concurrency-controlled Go lint workflow, lint configuration, and a README status badge.
Pinned action refresh
.github/workflows/*
Updates pinned setup, CodeQL, GoReleaser, provenance, and SARIF action revisions.
Toolchain and local artifact configuration
go.mod, .gitignore, internal/logging/bootstrap.go
Updates Go and UI dependencies, ignores local/build artifacts, and explicitly discards console-write results.
Test assertion maintenance
internal/health/*, internal/orchestrator/*_test.go, internal/ui/**/*_test.go
Rewrites equivalent boolean assertions, escapes control characters explicitly, and checks symlink materialization errors.

Documentation

Layer / File(s) Summary
Operations and integration guidance
docs/CLOUD_STORAGE.md, docs/CONFIGURATION.md, docs/DAEMON.md, docs/NOTIFICATIONS.md, docs/COLLECTOR_ARCHITECTURE.md
Documents daemon monitoring, cloud/rclone behavior, configuration defaults, notification flows, and collector metadata.
Restore and cluster recovery guidance
docs/CLUSTER_RECOVERY.md, docs/RESTORE_DIAGRAMS.md, docs/RESTORE_GUIDE.md, docs/RESTORE_TECHNICAL.md
Updates restore UI, category handling, SAFE/RECOVERY behavior, three-tier extraction, mount guards, and cleanup semantics.
Security and provenance guidance
docs/ENCRYPTION.md, docs/SECURITY.md, docs/PROVENANCE_VERIFICATION.md
Expands encryption configuration, runtime security controls, secret handling, and signed release verification procedures.
Developer, installation, and troubleshooting guidance
docs/DEVELOPER_GUIDE.md, docs/EXAMPLES.md, docs/INSTALL.md, docs/RELEASE-PROCESS.md, docs/TEST_STRATEGY.md, docs/TROUBLESHOOTING.md, docs/README.md
Updates development commands, installation and release workflows, examples, test-audit guidance, troubleshooting, and documentation indexing.

Estimated code review effort: 5 (Critical) | ~120 minutes

Possibly related PRs

🚥 Pre-merge checks | ✅ 5
✅ Passed checks (5 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title is broad but accurately reflects the main areas changed: tests, docs, upgrade tooling, and dependency bumps.
Docstring Coverage ✅ Passed Docstring coverage is 88.37% which is sufficient. The required threshold is 80.00%.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.
✨ 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 slice-6-tests-docs-upgrade

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

@github-actions

Copy link
Copy Markdown

Dependency Review

The following issues were found:
  • ✅ 0 vulnerable package(s)
  • ✅ 0 package(s) with incompatible licenses
  • ✅ 0 package(s) with invalid SPDX license definitions
  • ⚠️ 1 package(s) with unknown licenses.
See the Details below.

License Issues

go.mod

PackageVersionLicenseIssue Type
github.com/mattn/go-runewidth0.0.24NullUnknown License
Allowed Licenses: MIT, Apache-2.0, BSD-2-Clause, BSD-3-Clause, ISC, LicenseRef-scancode-google-patent-license-golang

OpenSSF Scorecard

PackageVersionScoreDetails
actions/actions/attest-build-provenance 0f67c3f4856b2e3261c31976d6725780e5e4c373 UnknownUnknown
actions/actions/setup-go 924ae3a1cded613372ab5595356fb5720e22ba16 🟢 5.2
Details
CheckScoreReason
Maintained⚠️ 23 commit(s) and 0 issue activity found in the last 90 days -- score normalized to 2
Code-Review🟢 10all changesets reviewed
Binary-Artifacts🟢 10no binaries found in the repo
Packaging⚠️ -1packaging workflow not detected
CII-Best-Practices⚠️ 0no effort to earn an OpenSSF best practices badge detected
Dangerous-Workflow🟢 10no dangerous workflow patterns detected
Token-Permissions⚠️ 0detected GitHub workflow tokens with excessive permissions
Pinned-Dependencies⚠️ 0dependency not pinned by hash detected -- score normalized to 0
Fuzzing⚠️ 0project is not fuzzed
License🟢 10license file detected
Signed-Releases⚠️ -1no releases found
Security-Policy🟢 9security policy file detected
Branch-Protection⚠️ 0branch protection not enabled on development/release branches
SAST🟢 9SAST tool is not run on all commits -- score normalized to 9
actions/goreleaser/goreleaser-action f06c13b6b1a9625abc9e6e439d9c05a8f2190e94 🟢 4.9
Details
CheckScoreReason
Code-Review⚠️ 0Found 1/23 approved changesets -- score normalized to 0
Dangerous-Workflow🟢 10no dangerous workflow patterns detected
Packaging⚠️ -1packaging workflow not detected
Binary-Artifacts🟢 10no binaries found in the repo
Maintained🟢 1025 commit(s) and 2 issue activity found in the last 90 days -- score normalized to 10
CII-Best-Practices⚠️ 0no effort to earn an OpenSSF best practices badge detected
Token-Permissions⚠️ 0detected GitHub workflow tokens with excessive permissions
Pinned-Dependencies🟢 9dependency not pinned by hash detected -- score normalized to 9
Fuzzing⚠️ 0project is not fuzzed
License🟢 10license file detected
Signed-Releases⚠️ -1no releases found
Security-Policy⚠️ 0security policy file not detected
Branch-Protection⚠️ -1internal error: error during branchesHandler.setup: internal error: some github tokens can't read classic branch protection rules: https://github.com/ossf/scorecard-action/blob/main/docs/authentication/fine-grained-auth-token.md
SAST⚠️ 0SAST tool is not run on all commits -- score normalized to 0
actions/actions/setup-go 924ae3a1cded613372ab5595356fb5720e22ba16 🟢 5.2
Details
CheckScoreReason
Maintained⚠️ 23 commit(s) and 0 issue activity found in the last 90 days -- score normalized to 2
Code-Review🟢 10all changesets reviewed
Binary-Artifacts🟢 10no binaries found in the repo
Packaging⚠️ -1packaging workflow not detected
CII-Best-Practices⚠️ 0no effort to earn an OpenSSF best practices badge detected
Dangerous-Workflow🟢 10no dangerous workflow patterns detected
Token-Permissions⚠️ 0detected GitHub workflow tokens with excessive permissions
Pinned-Dependencies⚠️ 0dependency not pinned by hash detected -- score normalized to 0
Fuzzing⚠️ 0project is not fuzzed
License🟢 10license file detected
Signed-Releases⚠️ -1no releases found
Security-Policy🟢 9security policy file detected
Branch-Protection⚠️ 0branch protection not enabled on development/release branches
SAST🟢 9SAST tool is not run on all commits -- score normalized to 9
actions/github/codeql-action/upload-sarif 99df26d4f13ea111d4ec1a7dddef6063f76b97e9 UnknownUnknown
gomod/charm.land/bubbles/v2 2.1.1 🟢 5.4
Details
CheckScoreReason
Code-Review🟢 6Found 7/11 approved changesets -- score normalized to 6
Maintained🟢 68 commit(s) and 0 issue activity found in the last 90 days -- score normalized to 6
Packaging⚠️ -1packaging workflow not detected
Dangerous-Workflow🟢 10no dangerous workflow patterns detected
Binary-Artifacts🟢 10no binaries found in the repo
Pinned-Dependencies⚠️ 0dependency not pinned by hash detected -- score normalized to 0
Token-Permissions⚠️ 0detected GitHub workflow tokens with excessive permissions
CII-Best-Practices⚠️ 0no effort to earn an OpenSSF best practices badge detected
Fuzzing🟢 10project is fuzzed
License🟢 10license file detected
Branch-Protection⚠️ 0branch protection not enabled on development/release branches
Signed-Releases⚠️ -1no releases found
Security-Policy🟢 10security policy file detected
SAST⚠️ 0SAST tool is not run on all commits -- score normalized to 0
gomod/github.com/mattn/go-runewidth 0.0.24 UnknownUnknown

Scanned Files

  • .github/workflows/release.yml
  • .github/workflows/security-ultimate.yml
  • go.mod

@codecov

codecov Bot commented Jul 15, 2026

Copy link
Copy Markdown

Codecov Report

❌ Patch coverage is 70.00000% with 3 lines in your changes missing coverage. Please review.

Files with missing lines Patch % Lines
internal/ui/flows/install/audit.go 40.00% 3 Missing ⚠️

📢 Thoughts on this report? Let us know!

@tis24dev

Copy link
Copy Markdown
Owner Author

@coderabbitai review

@coderabbitai

coderabbitai Bot commented Jul 15, 2026

Copy link
Copy Markdown
✅ Action performed

Review finished.

Note: CodeRabbit is an incremental review system and does not re-review already reviewed commits. This command is applicable only when automatic reviews are paused.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Actionable comments posted: 10

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
docs/CLOUD_STORAGE.md (1)

794-799: 🗄️ Data Integrity & Integration | 🟠 Major | ⚡ Quick win

Download all raw-layout sidecars during recovery.

When BUNDLE_ASSOCIATED_FILES=false, the preceding step still selects and copies only one LATEST object, but the archive, manifest, metadata, and checksum files are separate. Skipping extraction therefore leaves required files unavailable for restore discovery or verification. Update Steps 4–6 to derive the archive base name and download all associated objects.

🤖 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/CLOUD_STORAGE.md` around lines 794 - 799, Update Steps 4–6 in the
recovery instructions for BUNDLE_ASSOCIATED_FILES=false to derive the selected
LATEST archive’s base name and download its separate archive, manifest,
metadata, and checksum sidecars. Ensure the raw-layout path provides every
required object for restore discovery and verification, while preserving the
existing bundled-archive extraction flow.
🧹 Nitpick comments (1)
docs/CLUSTER_RECOVERY.md (1)

137-137: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

Add language identifiers to the flagged fenced blocks.

This removes the MD040 warnings and keeps the documentation lint-clean:

  • docs/CLUSTER_RECOVERY.md#L137-L137: mark the prompt block as text.
  • docs/CLUSTER_RECOVERY.md#L197-L197: mark the output block as text.
  • docs/CLUSTER_RECOVERY.md#L393-L393: mark the prompt block as text.
  • docs/CLUSTER_RECOVERY.md#L403-L403: mark the restore-plan block as text.
  • docs/CLUSTER_RECOVERY.md#L419-L419: mark the log block as text.
  • docs/RESTORE_GUIDE.md#L520-L520: mark the prompt block as text.
  • docs/RESTORE_GUIDE.md#L644-L644: mark the confirmation block as text.
  • docs/RESTORE_TECHNICAL.md#L1911-L1911: mark the tier summary block as text.
🤖 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/CLUSTER_RECOVERY.md` at line 137, Add the text language identifier to
each flagged fenced block: docs/CLUSTER_RECOVERY.md lines 137, 197, 393, 403,
and 419; docs/RESTORE_GUIDE.md lines 520 and 644; and docs/RESTORE_TECHNICAL.md
line 1911. Mark each prompt, output, restore-plan, log, confirmation, or
tier-summary fence as text without changing its contents.

Source: Linters/SAST tools

🤖 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 @.github/workflows/race.yml:
- Around line 40-43: Disable Go caching in the setup-go steps for both
.github/workflows/race.yml lines 40-43 and .github/workflows/release.yml lines
86-88 by removing or disabling the cache setting, ensuring pull request
workflows cannot populate caches reused by release workflows.

In `@docs/CLUSTER_RECOVERY.md`:
- Around line 1477-1480: Update the permissions statement in the “Permissions
wrong” section to describe restored ownership as a best-effort chown attempt,
acknowledging that owners may differ if it fails; retain the assertion that file
modes are preserved as long as chmod failures remain fatal.

In `@docs/CONFIGURATION.md`:
- Around line 828-829: The configuration documentation’s GFS fallback statement
conflates raw fallback values with the post-activation daily minimum. Update the
paragraph near the “Compiled fallbacks” text to state that all raw fallbacks are
0, then clarify that enabling RETENTION_POLICY=gfs promotes RETENTION_DAILY=0 to
1 while other tiers remain unset until configured.

In `@docs/DAEMON.md`:
- Around line 66-74: Add language identifiers to every affected fenced block:
use text for docs/DAEMON.md lines 66-74 and docs/NOTIFICATIONS.md lines 55-57
and 197-200, and bash for docs/DAEMON.md lines 129-163, preserving all block
contents.

In `@docs/EXAMPLES.md`:
- Around line 971-991: Update the daemon documentation near the description of
the four checks to state that healthchecks reports three fixed checks—alive,
backup, and updates—plus one check for each enabled notification channel; remove
the hard-coded total.

In `@docs/PROVENANCE_VERIFICATION.md`:
- Around line 121-127: Update the verification commands in the provenance
instructions to invoke gh attestation verify separately for the ${ASSET}
artifact and the ${ASSET}.tar.gz artifact, avoiding the multi-file glob while
preserving the existing repository and download context.
- Around line 222-233: Update the attestation verification examples in
PROVENANCE_VERIFICATION.md to enforce the claims listed in the verification
table by adding --signer-workflow for .github/workflows/release.yml and
--source-ref for the release tag or ref alongside --repo. Apply this
consistently to each relevant gh attestation verify example.
- Around line 175-185: Update the offline verification example in the provenance
documentation to use the digest-based filename produced by gh attestation
download instead of attestation.jsonl, and add --custom-trusted-root
trusted_root.jsonl to the gh attestation verify command while preserving the
existing repository and artifact arguments.

In `@docs/RELEASE-PROCESS.md`:
- Around line 9-16: Update the workflow diagram code fence in the release
process documentation to specify a language immediately after the opening fence,
using text unless converting the diagram to valid mermaid is necessary; preserve
the existing diagram content.

In `@upgrade-beta.sh`:
- Around line 318-338: Handle failure of the binary swap in the installation
block around `mv proxsave "${TARGET_BIN}"` instead of relying on `set -e`. If
the move fails, restore `${TARGET_BIN}.prev` to `${TARGET_BIN}` when possible,
ensure the restored binary has mode 755, print clear recovery guidance
consistent with the finalize-failure path, and exit nonzero; preserve the
existing post-swap sanity-check rollback for binaries that install but fail to
run.

---

Outside diff comments:
In `@docs/CLOUD_STORAGE.md`:
- Around line 794-799: Update Steps 4–6 in the recovery instructions for
BUNDLE_ASSOCIATED_FILES=false to derive the selected LATEST archive’s base name
and download its separate archive, manifest, metadata, and checksum sidecars.
Ensure the raw-layout path provides every required object for restore discovery
and verification, while preserving the existing bundled-archive extraction flow.

---

Nitpick comments:
In `@docs/CLUSTER_RECOVERY.md`:
- Line 137: Add the text language identifier to each flagged fenced block:
docs/CLUSTER_RECOVERY.md lines 137, 197, 393, 403, and 419;
docs/RESTORE_GUIDE.md lines 520 and 644; and docs/RESTORE_TECHNICAL.md line
1911. Mark each prompt, output, restore-plan, log, confirmation, or tier-summary
fence as text without changing its contents.
🪄 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: fc37e11b-f4e5-4b5f-8cd6-a29d520f1d53

📥 Commits

Reviewing files that changed from the base of the PR and between 88b599d and 2103893.

⛔ Files ignored due to path filters (1)
  • go.sum is excluded by !**/*.sum
📒 Files selected for processing (58)
  • .github/workflows/codecov.yml
  • .github/workflows/codeql.yml
  • .github/workflows/lint.yml
  • .github/workflows/race.yml
  • .github/workflows/release.yml
  • .github/workflows/security-ultimate.yml
  • .gitignore
  • .golangci.yml
  • README.md
  • cmd/proxsave/daemon_auto_migrate_characterization_test.go
  • cmd/proxsave/daemon_restart_verify_test.go
  • cmd/proxsave/daemon_setup.go
  • cmd/proxsave/dashboard.go
  • cmd/proxsave/dashboard_check_apply.go
  • cmd/proxsave/dashboard_cleanup_guards.go
  • cmd/proxsave/dashboard_update_config.go
  • cmd/proxsave/dashboard_upgrade.go
  • cmd/proxsave/dashboard_upgrade_test.go
  • cmd/proxsave/install_docs_characterization_test.go
  • cmd/proxsave/main_modes.go
  • cmd/proxsave/main_modes_test.go
  • cmd/proxsave/upgrade.go
  • cmd/proxsave/upgrade_localfile_test.go
  • docs/CLOUD_STORAGE.md
  • docs/CLUSTER_RECOVERY.md
  • docs/COLLECTOR_ARCHITECTURE.md
  • docs/CONFIGURATION.md
  • docs/DAEMON.md
  • docs/DEVELOPER_GUIDE.md
  • docs/ENCRYPTION.md
  • docs/EXAMPLES.md
  • docs/INSTALL.md
  • docs/NOTIFICATIONS.md
  • docs/PROVENANCE_VERIFICATION.md
  • docs/README.md
  • docs/RELEASE-PROCESS.md
  • docs/RESTORE_DIAGRAMS.md
  • docs/RESTORE_GUIDE.md
  • docs/RESTORE_TECHNICAL.md
  • docs/SECURITY.md
  • docs/TEST_STRATEGY.md
  • docs/TROUBLESHOOTING.md
  • go.mod
  • internal/cli/args.go
  • internal/health/daemon_state_test.go
  • internal/logging/bootstrap.go
  • internal/orchestrator/healthcheck_setup_test.go
  • internal/orchestrator/pve_staged_apply_additional_test.go
  • internal/orchestrator/restore_dedup_materialize_test.go
  • internal/orchestrator/workflow_ui_charm.go
  • internal/orchestrator/workflow_ui_status.go
  • internal/orchestrator/workflow_ui_status_test.go
  • internal/ui/components/formgrid_test.go
  • internal/ui/components/sanitize_test.go
  • internal/ui/flows/install/audit.go
  • internal/ui/flows/install/install_test.go
  • internal/ui/flows/menu/menu.go
  • upgrade-beta.sh

Comment on lines +40 to 43
uses: actions/setup-go@924ae3a1cded613372ab5595356fb5720e22ba16
with:
go-version-file: go.mod
cache: true

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

🔒 Security & Privacy | 🟠 Major | ⚡ Quick win

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
rg -n 'pull_request|pull_request_target|workflow_call|permissions:|cache:' \
  .github/workflows/race.yml .github/workflows/release.yml

Repository: tis24dev/proxsave

Length of output: 515


🏁 Script executed:

#!/bin/bash
sed -n '1,120p' .github/workflows/race.yml
printf '\n----\n'
sed -n '1,140p' .github/workflows/release.yml

Repository: tis24dev/proxsave

Length of output: 7340


🌐 Web query:

actions/setup-go cache input default false README cache-dependency-path go-version-file

💡 Result:

In the actions/setup-go GitHub Action, the default value for the cache input is true, meaning caching is enabled by default [1][2][3]. Regarding the other inputs: cache-dependency-path: This input is used to specify the path to dependency files (e.g., go.sum) [1][2][3]. By default, the action searches for go.sum in the repository root and uses its hash as part of the cache key [2][4][5]. You should use this input if you have multiple dependency files or if they are located in different subdirectories; it supports glob patterns [2][3][5]. go-version-file: This input specifies the path to a go.mod, go.work,.go-version, or.tool-versions file to determine which Go version to use [2][3][5]. If both the go-version and go-version-file inputs are provided, the go-version input takes precedence [2][3][5].

Citations:


Disable shared Go caching across the PR and release workflows. .github/workflows/race.yml#L40-L43 runs on pull_request with cache: true, and .github/workflows/release.yml#L86-L88 also enables the default cache. That lets an untrusted PR populate the cache later restored by release runs; disable caching or split the cache scope by trust boundary.

📍 Affects 2 files
  • .github/workflows/race.yml#L40-L43 (this comment)
  • .github/workflows/release.yml#L86-L88
🤖 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 @.github/workflows/race.yml around lines 40 - 43, Disable Go caching in the
setup-go steps for both .github/workflows/race.yml lines 40-43 and
.github/workflows/release.yml lines 86-88 by removing or disabling the cache
setting, ensuring pull request workflows cannot populate caches reused by
release workflows.

Source: Linters/SAST tools

Comment thread docs/CLUSTER_RECOVERY.md
Comment on lines 1477 to +1480
**3. Permissions wrong**:

ProxSave preserves each restored file's owner and mode from the backup archive, so it does not hardcode these. The following is manual Proxmox advice for the case where the ownership on `/var/lib/pve-cluster` is wrong for some other reason:

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

🎯 Functional Correctness | 🟡 Minor | ⚡ Quick win

Qualify the ownership-preservation claim.

Extraction applies ownership with best-effort chown; therefore restored owners may differ when the operation fails. Document this as an attempt/best-effort behavior rather than guaranteeing preservation, while keeping the mode-preservation claim if chmod remains fatal.

🤖 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/CLUSTER_RECOVERY.md` around lines 1477 - 1480, Update the permissions
statement in the “Permissions wrong” section to describe restored ownership as a
best-effort chown attempt, acknowledging that owners may differ if it fails;
retain the assertion that file modes are preserved as long as chmod failures
remain fatal.

Comment thread docs/CONFIGURATION.md
Comment on lines +828 to +829
**Compiled fallbacks** are all `0`: GFS keeps nothing until you set `RETENTION_POLICY=gfs` and at least one tier. The `7/4/12/3` above are example/template values, not the defaults. `RETENTION_DAILY` is forced to at least `1` (0 is treated as 1).

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

🎯 Functional Correctness | 🟡 Minor | ⚡ Quick win

🧩 Analysis chain

🏁 Script executed:

sed -n '810,845p' docs/CONFIGURATION.md
printf '\n--- SEARCH ---\n'
rg -n "RETENTION_DAILY|Compiled fallbacks|RETENTION_POLICY|gfs" -S .

Repository: tis24dev/proxsave

Length of output: 16927


🏁 Script executed:

sed -n '730,760p' internal/config/config.go
printf '\n--- NORMALIZE ---\n'
sed -n '1,120p' internal/storage/retention_normalize.go
printf '\n--- TEST ---\n'
sed -n '880,910p' internal/config/config_test.go

Repository: tis24dev/proxsave

Length of output: 3332


🏁 Script executed:

sed -n '1,220p' internal/storage/retention.go
printf '\n--- GFS TESTS ---\n'
sed -n '1,220p' internal/storage/retention_test.go
printf '\n--- LOCAL GFS TEST ---\n'
sed -n '1,140p' internal/storage/local_gfs_retention_test.go

Repository: tis24dev/proxsave

Length of output: 14047


🏁 Script executed:

rg -n "NormalizeGFSRetentionConfig|EffectiveGFSRetentionConfig|applyGFSRetention|ClassifyBackupsGFS" internal/storage internal/orchestrator cmd/proxsave -S
printf '\n--- APPLY PATH ---\n'
sed -n '440,560p' internal/storage/local.go

Repository: tis24dev/proxsave

Length of output: 13890


Clarify the GFS fallback semantics at docs/CONFIGURATION.md:828-829. RETENTION_DAILY=0 is promoted to 1 once RETENTION_POLICY=gfs is active, so “GFS keeps nothing until you set RETENTION_POLICY=gfs and at least one tier” is misleading. Spell out that 0 is the raw fallback and the daily minimum is applied after GFS is enabled.

🤖 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` around lines 828 - 829, The configuration
documentation’s GFS fallback statement conflates raw fallback values with the
post-activation daily minimum. Update the paragraph near the “Compiled
fallbacks” text to state that all raw fallbacks are 0, then clarify that
enabling RETENTION_POLICY=gfs promotes RETENTION_DAILY=0 to 1 while other tiers
remain unset until configured.

Comment thread docs/DAEMON.md
Comment on lines +66 to +74
```
Daemon status: <keyword>
Scheduler mode: <cron|daemon>
Daemon service (proxsave-daemon.service): installed | not installed
Service state (systemctl is-active): <active|inactive|...>
Opted out of auto-migration (--daemon-remove): yes | no
Running version: <version> (<commit>)
Binary alignment: aligned | BEHIND (restart needed) | unknown
```

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

📐 Maintainability & Code Quality | 🟡 Minor | ⚡ Quick win

Make all fenced blocks pass markdownlint.

  • docs/DAEMON.md#L66-L74: add a language such as text.
  • docs/DAEMON.md#L129-L163: add bash.
  • docs/NOTIFICATIONS.md#L55-L57: add text.
  • docs/NOTIFICATIONS.md#L197-L200: add text.
🧰 Tools
🪛 markdownlint-cli2 (0.23.0)

[warning] 66-66: Fenced code blocks should have a language specified

(MD040, fenced-code-language)

📍 Affects 2 files
  • docs/DAEMON.md#L66-L74 (this comment)
  • docs/DAEMON.md#L129-L163
  • docs/NOTIFICATIONS.md#L55-L57
  • docs/NOTIFICATIONS.md#L197-L200
🤖 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/DAEMON.md` around lines 66 - 74, Add language identifiers to every
affected fenced block: use text for docs/DAEMON.md lines 66-74 and
docs/NOTIFICATIONS.md lines 55-57 and 197-200, and bash for docs/DAEMON.md lines
129-163, preserving all block contents.

Source: Linters/SAST tools

Comment thread docs/EXAMPLES.md
Comment on lines +971 to +991
## Example 11: Resident daemon and healthchecks monitoring

**Scenario**: You want the backup scheduled and supervised by a resident service with a
hang watchdog and an external dead-man switch, instead of a bare cron entry. Fresh
installs default to this engine; use these commands to switch an existing install or to
inspect it.

### Switch to the daemon

```bash
# Install the systemd service, remove the cron entry, turn on centralized healthchecks
proxsave --daemon-setup

# Check status (exit 0 only when running, beating, and binary-aligned)
proxsave --daemon-status
```

`--daemon-setup` writes `SCHEDULER_MODE=daemon` and `HEALTHCHECK_ENABLED=true` and starts
`proxsave-daemon.service`. The daemon runs the backup daily at `SCHEDULER_TIME`, under a
`MAX_RUN_DURATION` watchdog, and reports four checks (alive, backup, updates, and one per
notification channel) to an external healthchecks monitor.

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

🎯 Functional Correctness | 🟡 Minor | ⚡ Quick win

Do not hard-code the daemon check count.

“Four checks” conflicts with “one per notification channel,” because the total varies. Document this as three fixed checks plus one check per enabled notification channel.

🤖 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/EXAMPLES.md` around lines 971 - 991, Update the daemon documentation
near the description of the four checks to state that healthchecks reports three
fixed checks—alive, backup, and updates—plus one check for each enabled
notification channel; remove the hard-coded total.

Comment on lines 121 to 127
```bash
# Download all the binaries you need
cd ~/downloads
wget https://github.com/tis24dev/proxsave/releases/download/v0.9.0/proxsave-linux-amd64
wget https://github.com/tis24dev/proxsave/releases/download/v0.9.0/proxsave-darwin-amd64
wget "https://github.com/tis24dev/proxsave/releases/download/${TAG}/${ASSET}"
wget "https://github.com/tis24dev/proxsave/releases/download/${TAG}/${ASSET}.tar.gz"

# Verify all together
gh attestation verify proxsave-* --repo tis24dev/proxsave
gh attestation verify proxsave_${VER}_linux_amd64* --repo tis24dev/proxsave
```

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

🎯 Functional Correctness | 🟡 Minor | ⚡ Quick win

🧩 Analysis chain

🏁 Script executed:

sed -n '100,150p' docs/PROVENANCE_VERIFICATION.md

Repository: tis24dev/proxsave

Length of output: 1648


🏁 Script executed:

git diff -- docs/PROVENANCE_VERIFICATION.md

Repository: tis24dev/proxsave

Length of output: 155


🌐 Web query:

gh attestation verify accepts multiple artifacts glob positional arguments manual

💡 Result:

The gh attestation verify command does not support multiple artifacts, glob patterns, or positional argument lists for simultaneous verification [1][2][3]. The command is designed to accept a single file path or a single OCI image URI as a positional argument [3][4]. Users cannot pass multiple files or glob patterns directly to the command line to verify them in a single execution [1]. If you need to verify multiple artifacts, you must iterate over them using a shell script or similar mechanism, running the gh attestation verify command once for each artifact. Note that while the actions/attest GitHub Action (used to create attestations) supports glob patterns and lists for generating attestations [5], this functionality is not mirrored in the verification command provided by the GitHub CLI [1][2].

Citations:


🌐 Web query:

gh attestation verify manual single artifact path glob

💡 Result:

The GitHub CLI command gh attestation verify does not support glob patterns for specifying artifact paths [1]. The command explicitly requires either a single, specific file path to an artifact on disk or a container image URI (e.g., oci://) [2][3][4]. Because the command does not natively support globbing, you must provide the path to each artifact individually. If you need to verify multiple artifacts, you must iterate over the files in your shell environment and invoke the command for each one [5]. For example, in a bash-like environment, you could use a loop: for artifact in path/to/artifacts/*; do gh attestation verify "$artifact" -R owner/repo done Note that while the GitHub Actions action actions/attest supports glob patterns when generating attestations [6], this functionality does not extend to the verification command provided by the GitHub CLI [1].

Citations:


🌐 Web query:

gh attestation verify accepts multiple artifact paths glob positional arguments

💡 Result:

The gh attestation verify command in the GitHub CLI does not natively support glob patterns or multiple positional arguments for artifact paths [1]. The command is designed to accept a single artifact path or OCI image URI as a positional argument [2][3][4]. Because the command does not support batch processing via globs or multiple arguments, users must iterate over their artifacts using a shell loop or a similar mechanism to perform verification on multiple files [5]. Example of how to verify multiple artifacts: for artifact in path/to/artifacts/*.zip; do gh attestation verify "$artifact" --repo OWNER/REPO done Official documentation and existing issues confirm that the command expects a single target per execution [6][1][7].

Citations:


Verify each artifact separately. gh attestation verify only accepts one artifact path per run, so the glob here expands to multiple paths and will fail; run it once for ${ASSET} and once for ${ASSET}.tar.gz.

🤖 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/PROVENANCE_VERIFICATION.md` around lines 121 - 127, Update the
verification commands in the provenance instructions to invoke gh attestation
verify separately for the ${ASSET} artifact and the ${ASSET}.tar.gz artifact,
avoiding the multi-file glob while preserving the existing repository and
download context.

Source: MCP tools

Comment on lines 175 to 185
```bash
# Download the attestation as a bundle
gh attestation download proxsave-linux-amd64 \
gh attestation download "${ASSET}" \
--repo tis24dev/proxsave \
--output attestation.jsonl

# Verify offline using the bundle
gh attestation verify proxsave-linux-amd64 \
gh attestation verify "${ASSET}" \
--bundle attestation.jsonl \
--repo tis24dev/proxsave
```

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

🎯 Functional Correctness | 🟡 Minor | ⚡ Quick win

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
set -euo pipefail

# Inspect the referenced section in the docs file.
sed -n '160,200p' docs/PROVENANCE_VERIFICATION.md

# Search for other attestation examples in the repo for context.
rg -n "gh attestation (download|verify)|custom-trusted-root|--output" docs -S

Repository: tis24dev/proxsave

Length of output: 2106


🌐 Web query:

gh attestation download --output custom-trusted-root offline verification docs

💡 Result:

To perform offline verification of artifact attestations using the GitHub CLI (gh), you must follow a three-step process involving downloading the necessary files on an internet-connected machine and performing the verification in your offline environment [1]. 1. Download the attestation bundle Use the gh attestation download command to retrieve the attestation bundle for your artifact [1]. gh attestation download PATH/TO/YOUR/ARTIFACT -R OWNER/REPO 2. Download the trusted roots On the online machine, generate a trusted_root.jsonl file, which is required for offline verification [1][2]. gh attestation trusted-root > trusted_root.jsonl 3. Perform offline verification Transfer the GitHub CLI, your artifact, the bundle file (from Step 1), and the trusted root file (from Step 2) to your offline environment [1]. Then, use the gh attestation verify command with the --bundle and --custom-trusted-root flags [1][3]: gh attestation verify PATH/TO/YOUR/ARTIFACT -R OWNER/REPO --bundle PATH/TO/BUNDLE.jsonl --custom-trusted-root trusted_root.jsonl Key considerations for offline verification: - It is best practice to generate a fresh trusted_root.jsonl file whenever importing new signed material [1]. - The trusted_root.jsonl file does not contain an expiration date, but offline verification will not account for any key revocations that occur after the file is generated [1]. - For more detailed guidance, refer to the official GitHub documentation on verifying attestations offline [1][4].

Citations:


🏁 Script executed:

#!/bin/bash
set -euo pipefail

python3 - <<'PY'
import urllib.request, ssl
urls = [
    "https://cli.github.com/manual/gh_attestation_download",
    "https://cli.github.com/manual/gh_attestation_verify",
    "https://cli.github.com/manual/gh_attestation_trusted-root",
]
for url in urls:
    print(f"\n### {url}")
    try:
        with urllib.request.urlopen(url, context=ssl.create_default_context(), timeout=20) as r:
            text = r.read().decode("utf-8", "replace")
        # print a compact excerpt around flag names
        for needle in ["--output", "--bundle", "--custom-trusted-root", "usage", "USAGE"]:
            idx = text.find(needle)
            if idx != -1:
                start = max(0, idx - 400)
                end = min(len(text), idx + 1000)
                print(text[start:end].replace("\n", " "))
                break
        else:
            print(text[:1200].replace("\n", " "))
    except Exception as e:
        print("ERROR:", e)
PY

Repository: tis24dev/proxsave

Length of output: 757


🌐 Web query:

site:cli.github.com/manual/gh_attestation_download --output gh attestation download

💡 Result:

The gh attestation download command in the GitHub CLI is used to download attestations associated with a specific artifact for offline use [1]. To use this command, you must provide either a file path to the local artifact or a container image URI [1]. The command is structured as follows: gh attestation download [file-path | oci-image-uri] [flags] This command is part of the broader gh attestation toolset provided by the GitHub CLI [1].

Citations:


🏁 Script executed:

#!/bin/bash
set -euo pipefail

python3 - <<'PY'
import ssl, urllib.request, re

urls = {
    "download": "https://cli.github.com/manual/gh_attestation_download",
    "verify": "https://cli.github.com/manual/gh_attestation_verify",
    "trusted-root": "https://cli.github.com/manual/gh_attestation_trusted-root",
}

ctx = ssl._create_unverified_context()
for name, url in urls.items():
    print(f"\n### {name} ###")
    with urllib.request.urlopen(url, context=ctx, timeout=30) as r:
        text = r.read().decode("utf-8", "replace")
    # Extract relevant flag/usage-ish lines from the HTML page text.
    for needle in ["gh attestation download", "gh attestation verify", "--output", "--bundle", "--custom-trusted-root", "trusted-root"]:
        idx = text.find(needle)
        if idx != -1:
            start = max(0, idx - 700)
            end = min(len(text), idx + 1800)
            snippet = text[start:end]
            # compact whitespace a bit for readability
            snippet = re.sub(r"\s+", " ", snippet)
            print(snippet)
            print("---")
            break
PY

Repository: tis24dev/proxsave

Length of output: 6959


🏁 Script executed:

#!/bin/bash
set -euo pipefail

python3 - <<'PY'
import ssl, urllib.request, re

urls = {
    "download": "https://cli.github.com/manual/gh_attestation_download",
    "verify": "https://cli.github.com/manual/gh_attestation_verify",
    "trusted-root": "https://cli.github.com/manual/gh_attestation_trusted-root",
}

ctx = ssl._create_unverified_context()
for name, url in urls.items():
    print(f"\n### {name} ###")
    with urllib.request.urlopen(url, context=ctx, timeout=30) as r:
        text = r.read().decode("utf-8", "replace")
    for needle in ["--output", "--bundle", "--custom-trusted-root", "gh attestation download", "gh attestation verify", "gh attestation trusted-root"]:
        idx = text.find(needle)
        if idx != -1:
            start = max(0, idx - 900)
            end = min(len(text), idx + 2200)
            snippet = text[start:end]
            snippet = re.sub(r"\s+", " ", snippet)
            print(snippet)
            print("---")
    print()
PY

Repository: tis24dev/proxsave

Length of output: 20074


Fix the offline verification example.

gh attestation download writes the bundle to a file named after the artifact digest, and gh attestation verify needs --custom-trusted-root trusted_root.jsonl for offline use.

🤖 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/PROVENANCE_VERIFICATION.md` around lines 175 - 185, Update the offline
verification example in the provenance documentation to use the digest-based
filename produced by gh attestation download instead of attestation.jsonl, and
add --custom-trusted-root trusted_root.jsonl to the gh attestation verify
command while preserving the existing repository and artifact arguments.

Source: MCP tools

Comment on lines 222 to +233
When you run `gh attestation verify`, the following checks are performed:

| Verification | Description |
|--------------|-------------|
| ✓ **Source repository** | Confirms that the build comes from `github.com/tis24dev/proxsave` |
| ✓ **Commit SHA** | Verifies the exact commit used for the build |
| ✓ **Workflow** | Checks that `.github/workflows/release.yml` was used |
| ✓ **Build environment** | Confirms that the build occurred on GitHub-hosted runner |
| ✓ **SHA256 integrity** | Calculates the file hash and compares it with the attested one |
| ✓ **Cryptographic signature** | Verifies the OIDC/Sigstore signature on the attestation |
| ✓ **Rekor timestamp** | Checks the immutable record in the transparency log |
| ✓ **SLSA compliance** | Verifies that the attestation complies with the SLSA v1 standard |
| Source repository | Confirms the build comes from `github.com/tis24dev/proxsave` |
| Commit SHA | Verifies the exact commit used for the build |
| Workflow | Checks that `.github/workflows/release.yml` was used |
| Build environment | Confirms the build occurred on a GitHub-hosted runner |
| SHA256 integrity | Recomputes the file hash and compares it with the attested one |
| Cryptographic signature | Verifies the OIDC/Sigstore signature on the attestation |
| Rekor timestamp | Checks the immutable record in the transparency log |
| SLSA compliance | Verifies the attestation complies with the SLSA v1 standard |

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

🔒 Security & Privacy | 🟠 Major | ⚡ Quick win

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
set -euo pipefail

# Map the document structure, then inspect the referenced range.
wc -l docs/PROVENANCE_VERIFICATION.md
ast-grep outline docs/PROVENANCE_VERIFICATION.md || true
sed -n '200,260p' docs/PROVENANCE_VERIFICATION.md | cat -n

# Search for gh attestation verify usage and any signer-workflow/source-ref flags.
rg -n "gh attestation verify|signer-workflow|source-ref|release\.yml|source-ref" docs/PROVENANCE_VERIFICATION.md

Repository: tis24dev/proxsave

Length of output: 3293


🏁 Script executed:

#!/bin/bash
set -euo pipefail

# Inspect the document for any other verification examples that constrain signer workflow or source ref.
rg -n -C 3 "gh attestation verify|signer-workflow|source-ref|release\.yml" docs/PROVENANCE_VERIFICATION.md

Repository: tis24dev/proxsave

Length of output: 2603


🌐 Web query:

gh attestation verify signer-workflow source-ref source-tag source-branch cli.github.com manual

💡 Result:

The gh attestation verify command is used in the GitHub CLI to verify the integrity and provenance of an artifact using cryptographically signed attestations [1][2]. When verifying attestations, you can use several optional flags to enforce specific requirements regarding the identity of the signer and the source of the build [3][2]. Key flags related to your query include: --signer-workflow: Enforces that the Actions workflow that signed the attestation matches a specified value. This is particularly useful when using reusable workflows, as it ensures the attestation was generated by the expected workflow file (format: [host/]owner/repo/.github/workflows/workflow.yml) [3][2]. --source-ref: Enforces that the Git reference (e.g., a branch name or tag) associated with the source repository matches the provided value [4][2]. --source-tag: Note that there is no specific --source-tag flag in the current gh attestation verify command. Users should typically use --source-ref to enforce a specific branch or tag [4][2]. Other related flags include: --signer-repo: Enforces that the repository containing the workflow that signed the attestation matches the provided value [3][2]. --signer-digest: Enforces that the digest associated with the signer workflow matches the provided value [4][2]. --source-digest: Enforces that the digest associated with the source repository matches the provided value [4][2]. These flags help ensure that the verified artifact originated from the expected source and was produced by the authorized workflow [3][2].

Citations:


🏁 Script executed:

#!/bin/bash
set -euo pipefail

# Show the exact section around the table and any verifier examples adjacent to it.
sed -n '210,255p' docs/PROVENANCE_VERIFICATION.md | cat -n

# Show all occurrences of verifier commands in the doc.
rg -n -C 2 "gh attestation verify" docs/PROVENANCE_VERIFICATION.md

Repository: tis24dev/proxsave

Length of output: 3238


Bind attestation verification to the published workflow and tag/ref. The examples only use --repo, so they don’t enforce .github/workflows/release.yml or the release ref the table claims. Add --signer-workflow and --source-ref, or soften the table.

🧰 Tools
🪛 LanguageTool

[uncategorized] ~228-~228: The official name of this software platform is spelled with a capital “H”.
Context: ...or the build | | Workflow | Checks that .github/workflows/release.yml was used | | Bui...

(GITHUB)

🤖 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/PROVENANCE_VERIFICATION.md` around lines 222 - 233, Update the
attestation verification examples in PROVENANCE_VERIFICATION.md to enforce the
claims listed in the verification table by adding --signer-workflow for
.github/workflows/release.yml and --source-ref for the release tag or ref
alongside --repo. Apply this consistently to each relevant gh attestation verify
example.

Source: MCP tools

Comment thread docs/RELEASE-PROCESS.md
Comment on lines 9 to 16
```
dev → Pull Request → main → tag vX.Y.Z → GitHub Actions (GoReleaser) → GitHub Release
push pr-vX.Y.Z on dev
-> release-intake.yml opens a dev -> main PR (marker: <!-- release-tag: vX.Y.Z -->)
-> release-guard.yml validates the PR
-> squash-merge the PR into main
-> post-merge-release.yml force-moves dev and creates the vX.Y.Z tag on the squash commit
-> release.yml (GoReleaser) builds, signs, and publishes the GitHub Release
```

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

📐 Maintainability & Code Quality | 🟡 Minor | ⚡ Quick win

Specify the language for the workflow diagram fence.

Add text (or convert the block to valid mermaid) after the opening fence so Markdown linting passes.

🧰 Tools
🪛 markdownlint-cli2 (0.23.0)

[warning] 9-9: Fenced code blocks should have a language specified

(MD040, fenced-code-language)

🤖 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/RELEASE-PROCESS.md` around lines 9 - 16, Update the workflow diagram
code fence in the release process documentation to specify a language
immediately after the opening fence, using text unless converting the diagram to
valid mermaid is necessary; preserve the existing diagram content.

Source: Linters/SAST tools

Comment thread upgrade-beta.sh
Comment on lines +318 to +338
###############################################
# 15) Swap in the beta binary (keep a rollback copy)
###############################################
echo "[+] Backing up current binary -> ${TARGET_BIN}.prev"
cp -f "${TARGET_BIN}" "${TARGET_BIN}.prev"

echo "[+] Installing beta binary -> ${TARGET_BIN}"
# Conventional executable mode 0755 (owner rwx, group/other r-x). The binary runs
# as root and only root can replace it; the security check verifies it is
# root-owned and not group/other-writable, not an exact mode.
mv proxsave "${TARGET_BIN}"
chmod 755 "${TARGET_BIN}"

# Sanity-check the swapped binary actually runs before finalizing. A corrupt or
# incompatible binary is rolled back to the previous one immediately.
if ! "${TARGET_BIN}" --version >/dev/null 2>&1; then
echo "❌ The installed beta binary failed to run; rolling back to the previous binary."
mv -f "${TARGET_BIN}.prev" "${TARGET_BIN}"
chmod 755 "${TARGET_BIN}"
exit 1
fi

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

🩺 Stability & Availability | 🟡 Minor | ⚡ Quick win

No auto-rollback if mv itself fails during the binary swap.

The post-swap sanity check (Line 333) rolls back to .prev if the new binary fails to run, but a failure of the mv on Line 328 itself (likely a copy+unlink if TMP_DIR and ${TARGET_DIR} are on different filesystems, e.g. tmpfs vs /opt) aborts the script via set -e with no rollback and no printed guidance -- unlike the finalize-failure path (Lines 350-359), which does tell the user how to recover. A partial/failed cross-device copy can leave ${TARGET_BIN} corrupted with the process's only cue being a bare error.

🔒️ Suggested fix: rollback + guidance on swap failure
 echo "[+] Installing beta binary -> ${TARGET_BIN}"
-mv proxsave "${TARGET_BIN}"
-chmod 755 "${TARGET_BIN}"
+if ! mv proxsave "${TARGET_BIN}"; then
+  echo "❌ Failed to install the new binary; restoring the previous binary."
+  mv -f "${TARGET_BIN}.prev" "${TARGET_BIN}" 2>/dev/null || true
+  chmod 755 "${TARGET_BIN}" 2>/dev/null || true
+  exit 1
+fi
+chmod 755 "${TARGET_BIN}"
📝 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
###############################################
# 15) Swap in the beta binary (keep a rollback copy)
###############################################
echo "[+] Backing up current binary -> ${TARGET_BIN}.prev"
cp -f "${TARGET_BIN}" "${TARGET_BIN}.prev"
echo "[+] Installing beta binary -> ${TARGET_BIN}"
# Conventional executable mode 0755 (owner rwx, group/other r-x). The binary runs
# as root and only root can replace it; the security check verifies it is
# root-owned and not group/other-writable, not an exact mode.
mv proxsave "${TARGET_BIN}"
chmod 755 "${TARGET_BIN}"
# Sanity-check the swapped binary actually runs before finalizing. A corrupt or
# incompatible binary is rolled back to the previous one immediately.
if ! "${TARGET_BIN}" --version >/dev/null 2>&1; then
echo "❌ The installed beta binary failed to run; rolling back to the previous binary."
mv -f "${TARGET_BIN}.prev" "${TARGET_BIN}"
chmod 755 "${TARGET_BIN}"
exit 1
fi
###############################################
# 15) Swap in the beta binary (keep a rollback copy)
###############################################
echo "[+] Backing up current binary -> ${TARGET_BIN}.prev"
cp -f "${TARGET_BIN}" "${TARGET_BIN}.prev"
echo "[+] Installing beta binary -> ${TARGET_BIN}"
# Conventional executable mode 0755 (owner rwx, group/other r-x). The binary runs
# as root and only root can replace it; the security check verifies it is
# root-owned and not group/other-writable, not an exact mode.
if ! mv proxsave "${TARGET_BIN}"; then
echo "❌ Failed to install the new binary; restoring the previous binary."
mv -f "${TARGET_BIN}.prev" "${TARGET_BIN}" 2>/dev/null || true
chmod 755 "${TARGET_BIN}" 2>/dev/null || true
exit 1
fi
chmod 755 "${TARGET_BIN}"
# Sanity-check the swapped binary actually runs before finalizing. A corrupt or
# incompatible binary is rolled back to the previous one immediately.
if ! "${TARGET_BIN}" --version >/dev/null 2>&1; then
echo "❌ The installed beta binary failed to run; rolling back to the previous binary."
mv -f "${TARGET_BIN}.prev" "${TARGET_BIN}"
chmod 755 "${TARGET_BIN}"
exit 1
fi
🤖 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 `@upgrade-beta.sh` around lines 318 - 338, Handle failure of the binary swap in
the installation block around `mv proxsave "${TARGET_BIN}"` instead of relying
on `set -e`. If the move fails, restore `${TARGET_BIN}.prev` to `${TARGET_BIN}`
when possible, ensure the restored binary has mode 755, print clear recovery
guidance consistent with the finalize-failure path, and exit nonzero; preserve
the existing post-swap sanity-check rollback for binaries that install but fail
to run.

@tis24dev
tis24dev merged commit bee67a2 into dev Jul 15, 2026
12 of 13 checks passed
@tis24dev
tis24dev deleted the slice-6-tests-docs-upgrade branch July 15, 2026 09:23
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