From 5ce1dfa39e4a9e6c4a49a6156b8fe1b0d09deb18 Mon Sep 17 00:00:00 2001 From: sneak Date: Wed, 24 Jun 2026 08:58:31 +0200 Subject: [PATCH] Restore --cron warning visibility; show destination on blob upload MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --cron used to behave like full quiet mode for everything that wasn't an error: warnings were swallowed both in the structured log channel (LevelError gate) and at the snapshot terminus (the "Finished (with N warnings)" line went through ui.Complete, which is silenced under SetQuiet). A backup that, say, hit Full Disk Access permission errors on a handful of files and skipped them via --skip-errors would exit 0 and emit nothing — the cron job would never page anyone. --cron now obeys "silent only on total success": * log.Initialize raises the cron/quiet log level from Error to Warn so log.Warn output still reaches stdout (and therefore cron's mail). * The post-backup terminus message switches to ui.Warning when WarningCount > 0. Warning is not silenced by SetQuiet, so cron delivers the summary line whenever the count is non-zero. The no-warnings path keeps ui.Complete, which IS silenced under cron — that's the success path. Separately, blob upload UI now names the actual destination instead of the generic "backup destination store" string. The Begin/Info lines emit the storer's reported Location (s3://bucket, file:///mnt/usb/backup, rclone://remote/path, etc.), so anyone watching a backup can see exactly where each blob is landing. --- internal/log/log.go | 8 ++++++-- internal/snapshot/scanner.go | 9 +++++---- internal/vaultik/snapshot.go | 7 ++++++- 3 files changed, 17 insertions(+), 7 deletions(-) diff --git a/internal/log/log.go b/internal/log/log.go index 4b72036..2843d25 100644 --- a/internal/log/log.go +++ b/internal/log/log.go @@ -46,8 +46,12 @@ func Initialize(cfg Config) { var level slog.Level if cfg.Cron || cfg.Quiet { - // In quiet/cron mode, only show errors - level = slog.LevelError + // In cron/quiet mode keep warnings and errors visible — the + // whole point of --cron is to stay silent only on total + // success, so that anything cron emails to root is genuinely + // "something went wrong, look at it." A backup with stuck + // permission errors or skipped files should NOT be silent. + level = slog.LevelWarn } else if cfg.Debug || strings.Contains(os.Getenv("GODEBUG"), "vaultik") { level = slog.LevelDebug } else if cfg.Verbose { diff --git a/internal/snapshot/scanner.go b/internal/snapshot/scanner.go index fc93531..c6b2442 100644 --- a/internal/snapshot/scanner.go +++ b/internal/snapshot/scanner.go @@ -1177,16 +1177,17 @@ func (s *Scanner) uploadBlobIfNeeded(ctx context.Context, blobPath string, blobW finishedBlob := blobWithReader.FinishedBlob // Check if blob already exists (deduplication after restart) + destination := s.storage.Info().Location if _, err := s.storage.Stat(ctx, blobPath); err == nil { log.Info("Blob already exists in storage, skipping upload", "hash", finishedBlob.Hash, "size", humanize.Bytes(uint64(finishedBlob.Compressed))) - s.ui.Info("Blob %s (%s) already exists in backup destination store. Skipping upload.", - s.ui.Hex(finishedBlob.Hash), s.ui.Size(finishedBlob.Compressed)) + s.ui.Info("Blob %s (%s) already exists at %s. Skipping upload.", + s.ui.Hex(finishedBlob.Hash), s.ui.Size(finishedBlob.Compressed), s.ui.Path(destination)) return true, nil } - s.ui.Begin("Uploading blob %s (%s) to backup destination store.", - s.ui.Hex(finishedBlob.Hash), s.ui.Size(finishedBlob.Compressed)) + s.ui.Begin("Uploading blob %s (%s) to %s.", + s.ui.Hex(finishedBlob.Hash), s.ui.Size(finishedBlob.Compressed), s.ui.Path(destination)) progressCallback := s.makeUploadProgressCallback(ctx, finishedBlob, startTime) diff --git a/internal/vaultik/snapshot.go b/internal/vaultik/snapshot.go index 1607049..ca3fcb7 100644 --- a/internal/vaultik/snapshot.go +++ b/internal/vaultik/snapshot.go @@ -92,8 +92,13 @@ func (v *Vaultik) CreateSnapshot(opts *SnapshotCreateOptions) error { } } + // Terminus must obey the --cron invariant: silent on total + // success only. UI.Complete is dropped in cron/quiet mode (that's + // the success path), but if any warnings fired during the run we + // emit the summary via UI.Warning so cron actually delivers + // something for the user to look at. if v.UI.WarningCount() > 0 { - v.UI.Complete("Finished (with %d warnings).", v.UI.WarningCount()) + v.UI.Warning("Finished with %d warning(s) — review the output above.", v.UI.WarningCount()) } else { v.UI.Complete("Finished successfully.") }