Skip to content

Sync dev to main#177

Closed
tis24dev wants to merge 87 commits intomainfrom
dev
Closed

Sync dev to main#177
tis24dev wants to merge 87 commits intomainfrom
dev

Conversation

@tis24dev
Copy link
Copy Markdown
Owner

@tis24dev tis24dev commented Mar 18, 2026

  • Align secondary path validation across config load, CLI install, and TUI
  • Align --new-install confirmation flow across CLI and TUI
  • Align existing backup.env handling across CLI and TUI
  • Fix AGE setup validation and install TUI messaging alignment
  • Align Telegram setup flow across CLI and TUI
  • Align decrypt secret prompt semantics across CLI and TUI
  • Add end-to-end coverage for the production decrypt TUI flow
  • Align secondary disable semantics across CLI and TUI
  • Align install cron scheduling across CLI and TUI
  • Add cron install regression coverage for CLI and TUI
  • test(orchestrator): stabilize decrypt TUI end-to-end tests
  • fix(install): guard optional bootstrap logging in TUI install flow
  • fix(newkey): guard success logging when bootstrap is nil
  • fix(decrypt): reject unchanged destination paths in CLI and TUI prompts
  • refactor: simplify ticker wait in rollback countdown
  • fix: respect configured recipient file in --newkey
  • test(tui): complete coverage for new install and telegram setup
  • test(tui): add missing wizard coverage
  • test(tui): harden Telegram setup TUI tests
  • fix(storage): correct UsedSpace and stop deriving it from Bavail
  • fix(orchestrator): avoid duplicate file close in bundle and restore paths
  • fix(orchestrator): make AGE recipient overwrite flow safe and atomic
  • fix(restore): extract regular files atomically
  • fix(config): align notification defaults and legacy env compatibility
  • storage: bound cloud retry backoff and remove overflow-prone shifts
  • Fix binary integrity validation flow
  • Fix GFS daily minimum normalization across retention paths
  • notify: honor context cancellation in email, relay, and webhook paths
  • fixstorage: normalize bundle paths in cloud and secondary store flows

Summary by CodeRabbit

  • New Features

    • Installer: “Keep existing & continue” and Cancel options; unified CLI/TUI prompts
    • Interactive encryption/recipient setup supporting multiple recipients (CLI/TUI)
    • Telegram pairing flow with eligibility checks and verification
    • Installer cron time prompt with normalized schedule (defaults to 02:00)
  • Bug Fixes / Reliability

    • Respect context cancellation across notifications, webhooks, and retries
    • Safer atomic file writes for restore, bundles and recipient files
    • Bounded, context-aware backoff for cloud operations
    • Improved storage reporting using used-space metrics; stricter secondary-path validation
  • Documentation

    • Clarified secondary storage rules, Telegram eligibility, cron guidance, and encryption notes

tis24dev added 29 commits March 13, 2026 11:12
Centralize validation for SECONDARY_PATH and SECONDARY_LOG_PATH so all entrypoints enforce the same absolute-local-path rules. Reject remote/UNC-style secondary paths during config loading, keep SECONDARY_LOG_PATH optional, and update the CLI installer to retry on invalid secondary path input instead of aborting. Add coverage for config parsing, migration, installer, runtime validation, and TUI flows.
Refactor new-install to use a shared reset plan and a single source of truth for preserved entries (build/env/identity). Route --new-install --cli through CLI confirmation only, keep TUI confirmation as a pure adapter, and propagate TUI runner errors instead of swallowing them. Update related help/log messaging and add tests for new-install planning, CLI confirm behavior, TUI confirm rendering/error handling, and reset/preserve consistency.
Introduce a shared decision flow for pre-existing backup.env with four explicit actions: Overwrite, Edit existing, Keep existing & continue, and Cancel. Update CLI prompts to support all modes (including Edit existing and explicit Cancel), update TUI action mapping to the same semantics, and treat “keep existing” as continue (not abort). Ensure TUI post-config steps are skipped consistently when configuration wizard is skipped (AGE setup, post-install audit, Telegram pairing), while finalization steps still run. Propagate CheckExistingConfig runner errors instead of swallowing them. Add/adjust unit tests for decision resolution, CLI prompts, TUI actions, runner error propagation, and prepareBaseTemplate behavior. Update INSTALL and CLI_REFERENCE docs to match the new aligned behavior.
Tighten AGE setup consistency after the shared CLI/TUI refactor. Reuse a shared private-key validator so the TUI rejects malformed AGE identities before they reach the orchestrator, eliminating silent retry loops. Extend the AGE setup workflow to return explicit outcome details (recipient path, wrote file vs reused existing recipients) and update install TUI messaging to report “saved” only on real writes, while showing reuse clearly when existing recipient configuration is kept. Add regression coverage for private-key validation, reuse-vs-write setup results, and the updated TUI wizard behavior.
Introduce a shared Telegram setup bootstrap so CLI and TUI use the same eligibility rules before showing pairing steps. Stop the TUI from falling back to raw backup.env parsing, skip Telegram setup consistently when config loading fails, personal mode is selected, or no Server ID is available, and centralize skip-reason logging in the command layer. Update the TUI install flow to log shared Telegram bootstrap outcomes, add dedicated tests for bootstrap/CLI/TUI behavior, align user-facing docs, and remove now-unreachable TUI branches left over from the old local decision logic.
Restore consistent decrypt prompt behavior between CLI and TUI by treating "0" as an explicit abort in both flows. Update the TUI decrypt secret prompt to advertise the exit semantics clearly, return ErrDecryptAborted on zero input, and keep Cancel as an equivalent exit path. Adjust TUI simulation coverage so the shared decrypt workflow no longer carries a UI-specific semantic drift on secret entry.
Add deterministic end-to-end smoke tests for RunDecryptWorkflowTUI so the real decrypt TUI production path is covered from entrypoint through source selection, candidate selection, secret prompt, destination prompt, and final bundle creation. Introduce test-only helpers for a real AGE-encrypted raw-backup fixture, serialized TUI simulation across multi-screen workflows, bundle-content inspection, and guarded workflow execution. Verify both the success path (including final *.decrypted.bundle.tar contents, metadata, and checksum) and clean abort at the decrypt secret prompt, without changing production behavior.
Introduce a shared env-template helper for secondary storage state and use it from both installer flows so disabling secondary storage always writes the same canonical config: SECONDARY_ENABLED=false, SECONDARY_PATH=, and SECONDARY_LOG_PATH=. This removes the previous TUI-only drift where editing an existing backup.env could leave stale secondary paths after the user disabled the feature. Add focused unit coverage for the shared helper plus CLI and TUI regression tests covering disabled state and clearing of pre-existing secondary values, and clarify the installer docs to note that disabling secondary storage clears the saved secondary paths.
Introduce shared cron parsing/normalization for install workflows and align CLI with the existing TUI cron capability. Add a neutral internal cron helper package, collect cron time during the CLI install wizard, propagate an explicit CronSchedule through the CLI install result, and make install-time cron finalization honor wizard-selected/default cron values instead of falling back to env overrides after a normal wizard run. Keep skip-config-wizard and upgrade flows on their existing env/default behavior, update the TUI wizard to reuse the same cron validation logic, add regression coverage for shared cron parsing, CLI prompt/result propagation, and install schedule precedence, and update install/CLI docs to reflect cron selection in both modes.
Close the remaining cron-install test gaps after aligning CLI and TUI scheduling behavior. Add a TUI wizard regression test that proves blank cron input resolves to the installer default (02:00) even when CRON_SCHEDULE is set in the environment, and add a CLI wizard regression test that aborting exactly at the cron prompt propagates the interactive abort and leaves backup.env unwritten. Introduce minimal test seams for the install wizard runner and cron prompt boundary to exercise the real command/wizard paths without changing production semantics.
Reduces flakiness in the decrypt TUI end-to-end tests when run
with coverage enabled or under package-level load.

- increases simulated input delays
- extends end-to-end test timeouts and contexts
- avoids false negatives without changing production code
Avoid nil-pointer panics in runInstallTUI when the bootstrap logger is not provided. Guard the AGE encryption success-path Info logs and the configuration-saved Debug log with bootstrap nil checks, preserving existing behavior when bootstrap is available.
Prevent nil-pointer panics in the newkey flow by routing final success messages through a shared helper. When a bootstrap logger is available, keep using bootstrap.Info; otherwise fall back to stdout so both CLI and TUI paths remain safe and user-visible. Also add targeted tests for bootstrap and nil-bootstrap cases.
Prevent decrypt path conflict prompts from accepting the same destination path again. Add shared validation that rejects empty or normalized-equivalent paths to the existing target, apply it in both TUI and CLI flows, and update tests to cover valid edits plus normalized-path rejection and retry behavior.
Replace the single-case select in printNetworkRollbackCountdown
with a direct receive from ticker.C.
The loop behavior remains unchanged: the countdown still updates on
each tick and keeps the explicit continue for clarity.
Load the existing config in --newkey so AGE_RECIPIENT_FILE is preserved
when configured, instead of always forcing the default recipient path.

Also update the success message to report the effective recipient file,
add tests for custom/default paths and invalid configs, and align the
CLI docs.
Add targeted tests for the TUI wizards to cover the remaining uncovered branches.

- cover nil and whitespace-only preserved entries in new install tests
- cover Telegram setup bootstrap error propagation
- verify persisted identity rendering in the Server ID panel
- verify truncation of long registration failure messages
- verify ESC behavior after successful verification
- exercise default async wrappers used by the Telegram setup wizard

LiveReview Pre-Commit Check: skipped (iter:1, coverage:0%)
Add missing unit tests for new install and Telegram setup flows, covering edge
cases, UI state rendering, error propagation, ESC handling, and async helpers.

LiveReview Pre-Commit Check: skipped (iter:1, coverage:0%)
Refactor telegram_setup_tui_test to remove hardcoded layout index
assumptions when extracting Telegram setup views.

The helper now finds the Pages container and the relevant widgets by
type and title instead of relying on UI item ordering. The default
wrapper test also replaces sleep-based update timing with direct
QueueUpdateDraw synchronization, keeping only a watchdog timeout to
avoid hangs.

LiveReview Pre-Commit Check: skipped (iter:3, coverage:0%)
Fix the used space calculation in the local and secondary storage
backends by using Blocks-Bfree instead of Total-Bavail, so reserved
blocks are no longer reported as "used".

Centralize statfs space conversion in a shared helper and apply the same
semantics to the PVE collector.

Also propagate UsedSpace through the orchestrator and notifications,
removing the recomputation from free/total so displayed usage values and
percentages match actual filesystem usage.

Update and strengthen tests for Bavail != Bfree cases and for end-to-end
propagation of the corrected values.

LiveReview Pre-Commit Check: skipped (iter:2, coverage:73%)
…aths

Prevent duplicate Close() calls in createBundle() and extractRegularFile() while preserving explicit close error handling. The patch replaces bare deferred closes with guarded defers so resources are only closed if they were not already closed explicitly, and it keeps the explicit Close() calls where finalization order and returned errors still matter. Test coverage was also improved to verify that output files are correctly closed in both success and error paths for bundle creation and regular file extraction.

LiveReview Pre-Commit Check: skipped (iter:2, coverage:100%)
Prevent data loss when replacing the AGE recipient file by changing the backup logic to copy the existing file into a .bak-* backup instead of renaming or deleting the active file. The new recipient file is now written atomically through a temporary file and rename, and the workflow creates the backup only at commit time, after the new recipients have been collected. Backup failures are now propagated instead of being logged and ignored, so aborts and write errors leave the current recipient file intact. Regression tests were added to cover abort, backup failure, write failure, and successful overwrite paths.

LiveReview Pre-Commit Check: skipped (iter:3, coverage:79%)
extractRegularFile now writes regular-file restores to a sibling temp file and renames it into place only after content copy and close succeed. This prevents truncated archive entries from clobbering an existing target or leaving partial files behind on restore failure.

Also improves temp-file handling by reducing name collision risk with an atomic sequence, surfacing deferred close errors, and logging unexpected temp cleanup failures. Tests were added/updated to verify success cleanup, failed-copy cleanup, and preservation of a pre-existing target on copy errors.`

LiveReview Pre-Commit Check: skipped (iter:2, coverage:48%)
Align the notification configuration with the shipped template and documentation by making EMAIL_ENABLED default to false when unset. Restore compatibility with legacy *_ENABLE notification flags during runtime loading and legacy env migration, add the missing Gotify environment overrides, and update tests and docs to reflect the corrected behavior.

LiveReview Pre-Commit Check: skipped (iter:2, coverage:67%)
Replace the shift-based exponential backoff used by cloud remote checks
and upload retries with a bounded retry schedule.
This keeps the current retry timings for normal cases (2s, 4s, 8s, 16s)
while capping larger attempts at 30s and removing the overflow-prone
`1<<attempt` pattern.
Also add regression coverage for large attempts and for the actual sleep
durations used by cloud retry paths.

LiveReview Pre-Commit Check: skipped (iter:2, coverage:83%)
Use os.Lstat in verifyBinaryIntegrity to validate the executable path before opening it, so symlinked executables are rejected correctly. Open the file only when computing the checksum, removing the unused rewind path and making the integrity check flow clearer. Update the related tests to cover the symlink case and the stat failure path.

LiveReview Pre-Commit Check: skipped (iter:2, coverage:6%)
Ensure GFS retention consistently normalizes RETENTION_DAILY<=0 to an effective daily minimum of 1 across classification, storage backends, and runtime summaries. This prevents current-period backups from being misclassified or deleted when daily retention is unset/zero, aligns internal comments and tests with the documented contract, and separates pure GFS config normalization from the logging wrapper used in operational paths.

LiveReview Pre-Commit Check: skipped (iter:2, coverage:67%)
Propagate context-aware cancellation through notification retry and diagnostic flows. Switch email mail-log inspection helpers to use context and CommandContext, replace blocking sleeps with a context-aware wait helper, and stop relay/webhook retry loops immediately when the context is canceled. Add focused tests covering cancellation behavior.

LiveReview Pre-Commit Check: ran (iter:1, coverage:0%)
Normalize bundle path handling when BundleAssociatedFiles is enabled so storage backends accept either the raw archive path or an already bundled path without deriving *.bundle.tar.bundle.tar. Reuse a shared helper for canonical bundle naming, add regression tests for double-suffix decoys, and log unexpected bundle stat errors while still ignoring missing optional bundle files.

LiveReview Pre-Commit Check: skipped (iter:2, coverage:85%)
@coderabbitai
Copy link
Copy Markdown

coderabbitai bot commented Mar 18, 2026

Note

Reviews paused

It looks like this branch is under active development. To avoid overwhelming you with review comments due to an influx of new commits, CodeRabbit has automatically paused this review. You can configure this behavior by changing the reviews.auto_review.auto_pause_after_reviewed_commits setting.

Use the following commands to manage reviews:

  • @coderabbitai resume to resume automatic reviews.
  • @coderabbitai review to trigger a single review.

Use the checkboxes below for quick actions:

  • ▶️ Resume reviews
  • 🔍 Trigger review
📝 Walkthrough

Walkthrough

Adds an internal AGE encryption setup flow with UI/CLI orchestration; refactors install/new-install and cron scheduling; introduces Telegram bootstrap and CLI/TUI flows; centralizes secondary-path validation and env mutation; adds safefs Statfs helpers, GFS retention normalization, cloud backoff, context-aware notify retries, TUI screen helpers, many tests, and defensive guards.

Changes

Cohort / File(s) Summary
Encryption & AGE orchestration
cmd/proxsave/encryption_setup.go, cmd/proxsave/encryption_setup_test.go, internal/orchestrator/age_setup_ui*.go, internal/orchestrator/age_setup_workflow.go, internal/orchestrator/encryption*.go, internal/tui/wizard/age*.go, internal/tui/wizard/age_ui_adapter*.go
New AgeSetupUI/draft types, orchestrator EnsureAgeRecipientsReady* APIs, UI & CLI implementations, interactive recipient-collection workflow, atomic backup/write of recipient files, parsing/validation helpers, and comprehensive tests.
Newkey & CLI integration
cmd/proxsave/newkey.go, cmd/proxsave/newkey_test.go
Centralizes new-key setup, delegates AGE readiness to orchestrator UI path, consolidates logging and tests.
Install flows & existing-config
cmd/proxsave/install*.go, cmd/proxsave/install_existing_config*.go, cmd/proxsave/new_install*.go, cmd/proxsave/install_test.go, cmd/proxsave/install_existing_config_test.go, cmd/proxsave/new_install_test.go
Refactors install to plan-driven flows, adds existing-config state machine (overwrite/edit/keep/ cancel), captures cron schedule from wizard, improves secondary-path handling, and updates many tests.
Cron utilities & scheduling
cmd/proxsave/schedule_helpers.go, internal/cron/cron.go, cmd/proxsave/schedule_helpers_test.go, internal/cron/cron_test.go
Adds NormalizeTime/TimeToSchedule and cron DefaultTime, moves schedule resolution to env-driven path, and adds buildInstallCronSchedule with tests.
Telegram setup (bootstrap + CLI/TUI)
cmd/proxsave/telegram_setup_cli.go, cmd/proxsave/telegram_setup_cli_test.go, internal/orchestrator/telegram_setup_bootstrap.go, internal/orchestrator/telegram_setup_bootstrap_test.go, internal/tui/wizard/telegram_setup_tui*.go, internal/tui/wizard/telegram_setup_tui_test.go
Adds BuildTelegramSetupBootstrap with eligibility modeling, CLI pairing flow, TUI refactor to consume bootstrap state, outcome logging, and unit tests.
Storage, safefs, retention, cloud
internal/safefs/*, internal/storage/*, internal/storage/cloud.go, internal/storage/*_test.go, internal/storage/retention*.go
Adds SpaceUsageFromStatfs, exposes UsedSpace, EffectiveGFSRetentionConfig normalization, bundlePathFor, bounded backoff and context-aware waits for cloud operations, and tests adjusting retention expectations.
Orchestrator storage & notifications
internal/orchestrator/orchestrator.go, internal/orchestrator/notification_adapter.go, internal/orchestrator/storage_adapter.go, tests
Adds LocalUsedSpace/SecondaryUsedSpace, switches usage percent to used-space-based calculations with inconsistency warnings, and makes bundle creation atomic via temp file + rename.
Restore atomicity & cleanup
internal/orchestrator/restore*.go, internal/orchestrator/restore_*_test.go
Writes files atomically to per-target temp files, renames after write, aggregates deferred errors, and adds tests verifying cleanup and file closure.
Notify context-awareness
internal/notify/*, tests
Adds sleepWithContext and threads context cancellation into mail-tail, relay and webhook retry loops; tests ensure cancellation stops retries.
TUI helpers & screen builder
internal/tui/screen.go, internal/tui/wizard/screen.go, internal/orchestrator/tui_screen_env.go, many workflow_ui_tui_* files and tests
Centralizes ScreenSpec/BuildScreen and wizard screen builder, introduces RunWithContext for TUI, unifies prompt flows, adds escaping and many TUI refactors/tests.
Config validation & env mutation
internal/config/*, internal/config/validation_secondary.go, internal/config/env_mutation.go, related tests
Centralized secondary-path validators (required/optional), ApplySecondaryStorageSettings to mutate env templates, legacy notification alias handling, migration and validation tests.
Logging & defensive guards
internal/logging/bootstrap.go, internal/logging/bootstrap_test.go
Adds nil-receiver guards to BootstrapLogger methods and tests confirming no-op semantics on nil receiver.
Misc & tests
numerous internal/*_test.go, cmd/proxsave/*_test.go
Widespread test additions and updates (t.Setenv usage, e2e TUI helpers, new unit tests) and smaller refactors across packages.
Docs
docs/*
Documentation updates: SECONDARY_PATH rules (absolute local paths), installer wording (Keep existing & continue / Cancel), cron defaults, and notification variable renames/aliases.

Sequence Diagram(s)

sequenceDiagram
    participant CLI as CLI/TUI
    participant Orch as Orchestrator
    participant UI as AgeSetupUI
    participant FS as FileSystem
    participant Age as Age Library

    CLI->>Orch: runInitialEncryptionSetupWithUI(ctx, configPath, ui)
    Orch->>Orch: load config, create orchestrator
    Orch->>Orch: prepareAgeRecipientsWithUI(ctx, ui)
    alt cached recipients available
        Orch-->>CLI: return cached result
    else interactive setup required
        Orch->>FS: stat recipient file
        alt file exists
            Orch->>UI: ConfirmOverwriteExistingRecipient(ctx, path)
            UI-->>Orch: confirmed?
            alt yes
                Orch->>FS: backup existing recipient file
            else no
                Orch-->>CLI: abort (ErrAgeRecipientSetupAborted)
            end
        end
        loop collect drafts
            Orch->>UI: CollectRecipientDraft(ctx, path)
            UI-->>Orch: draft
            Orch->>Age: resolveAgeRecipientDraft(draft) -> recipient string
            Orch->>UI: ConfirmAddAnotherRecipient(ctx, count)
            UI-->>Orch: addMore?
        end
        Orch->>FS: write deduplicated recipient file atomically
        FS-->>Orch: write success
        Orch-->>CLI: encryptionSetupResult
    end
Loading
sequenceDiagram
    participant CLI as Install CLI
    participant Config as ExistingConfigDecision
    participant Wizard as Config Wizard
    participant Post as Post-Install

    CLI->>Config: prepareExistingConfigDecisionCLI(ctx, configPath)
    Config->>Config: promptExistingConfigModeCLI(ctx, configPath)
    Config-->>CLI: existingConfigDecision (mode, baseTemplate, skipWizard, abort)
    alt Overwrite or Edit
        CLI->>Wizard: runConfigWizardCLI(ctx) -> installConfigResult
        Wizard-->>CLI: plan (EnableEncryption, SkipConfigWizard, CronSchedule)
        CLI->>Post: runPostInstallSteps(using cronSchedule)
    else Keep & Continue
        CLI->>Post: runPostInstallSteps(skip config-dependent steps)
    else Cancel
        CLI-->>CLI: abort install (errInteractiveAborted)
    end
Loading

Estimated code review effort

🎯 5 (Critical) | ⏱️ ~120 minutes

Possibly related PRs

  • Sync dev to main #171 — Strong overlap around AGE/encryption setup and orchestrator AGE workflow changes; likely related to AgeSetupUI/AgeRecipientDraft and orchestration logic.

Poem

🐇
I hopped through keys and prompts at night,
Backed-up files safe, and cron set right,
I paused on retries when contexts screamed,
Kept paths absolute, and tests all beamed,
A rabbit’s patch: tidy, small, and bright.

✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch dev

tis24dev added 12 commits March 19, 2026 10:02
Propagate the caller-selected TUI page builder through shared decrypt prompts so restore flows preserve their restore-specific wrapper instead of falling back to the default decrypt screen. Fix the remaining SelectExportNode bypass, introduce a shared internal/tui screen shell, and migrate the main orchestrator and wizard TUI layouts to it without changing behavior. Strengthen tests for builder propagation, nested prompt handoff, and exact builder parameters, and clean up BuildScreen footer handling by normalizing optional footer values once before rendering.

LiveReview Pre-Commit Check: skipped (iter:3, coverage:83%)
Fix FakeFS path projection in tests so upward traversal like "../x" cannot escape the sandbox root, while preserving the expected mapping of absolute paths under the fake filesystem. Add targeted coverage for both absolute-path mapping and traversal blocking. Also propagate caller-selected TUI page builders through shared decrypt prompts, fix the remaining restore SelectExportNode bypass, introduce a shared internal/tui screen shell, migrate the main orchestrator and wizard TUI layouts to it without changing behavior, and strengthen tests around builder propagation and optional footer rendering.

LiveReview Pre-Commit Check: skipped (iter:1, coverage:0%)
Fix FakeFS test path mapping so "../x" cannot escape the sandbox root, and stabilize decrypt TUI e2e tests by waiting for the actual RunDecryptWorkflowTUI result with a local watchdog timeout instead of racing ctx.Done(). Also preserve restore-specific TUI page builders in shared decrypt prompts, remove the remaining SelectExportNode builder bypass, unify common TUI screen shells, and strengthen related tests.

LiveReview Pre-Commit Check: skipped (iter:1, coverage:0%)
Update the decrypt TUI abort e2e input sequence to use the same initial startup delays as the stabilized success path, reducing flakiness on slower CI runs without changing the covered abort behavior.

LiveReview Pre-Commit Check: skipped (iter:1, coverage:0%)
Remove the redundant logger nil check inside BootstrapLogger.Flush and rely on the existing early return that already validates both the bootstrap logger and the target logger before flushing entries.

LiveReview Pre-Commit Check: skipped (iter:1, coverage:0%)
Add a small shared test helper for the preserved entries slice in new_install_test.go and use it across all ConfirmNewInstall tests to centralize the repeated fixture values.

LiveReview Pre-Commit Check: skipped (iter:1, coverage:0%)
Harden verifyBinaryIntegrity against executable swap races by validating SameFile before applying ownership or permission fixes, then performing any auto-fix operations on the already-opened executable via file descriptor using fd-based chmod/chown. Add targeted test coverage for the new fd-based permission-fix path.

LiveReview Pre-Commit Check: skipped (iter:1, coverage:0%)
Extract the repeated archive sidecar file-writing setup in bundle_test.go into a shared test helper and reuse it across the createBundle tests to centralize fixture creation without changing test behavior.

LiveReview Pre-Commit Check: skipped (iter:1, coverage:0%)
Add a small test helper to register and restore the global ageWizardRunner via t.Cleanup, and reuse it across age_ui_adapter_test.go to centralize repeated setup and teardown logic.

LiveReview Pre-Commit Check: skipped (iter:1, coverage:0%)
Add shared AGE setup type constants in the wizard package and use them across the AGE setup wizard, UI adapter, and related tests to remove duplicated string literals and keep the setup-type mapping aligned.

LiveReview Pre-Commit Check: skipped (iter:1, coverage:0%)
Add a comment to TestPromptExistingPathDecisionTUI_NewPathContextCanceledWhileRunning explaining that the test waits for two drawCh reads (initial prompt and the secondary "new path" dialog) and cancels the context only after both reads to simulate cancellation while the second dialog is running.

LiveReview Pre-Commit Check: skipped (iter:1, coverage:0%)
Remove the separate lockDecryptTUIE2E helper and acquire the decryptTUIE2EMu mutex inside withTimedSimAppSequence instead. Tests no longer call the lock helper directly; the helper now locks at start and unlocks in the sequence cleanup goroutine. This centralizes mutex handling for decrypt TUI end-to-end tests and reduces duplicated setup/teardown logic (changes in internal/orchestrator/decrypt_tui_e2e_helpers_test.go and decrypt_tui_e2e_test.go).

LiveReview Pre-Commit Check: skipped (iter:1, coverage:0%)
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

Caution

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

⚠️ Outside diff range comments (1)
internal/tui/wizard/telegram_setup_tui.go (1)

103-112: ⚠️ Potential issue | 🟡 Minor

Escape the interpolated values in serverIDView.

This view has SetDynamicColors(true), so any persisted identity path containing [ or ] will be parsed as tview markup instead of literal text. Use tview.Escape() on the interpolated values.

🛠️ Suggested fix
-	serverIDLine := fmt.Sprintf("[yellow]%s[white]", result.ServerID)
+	serverIDLine := fmt.Sprintf("[yellow]%s[white]", tview.Escape(result.ServerID))
 	identityLine := ""
 	if result.IdentityFile != "" {
 		persisted := "not persisted"
 		if result.IdentityPersisted {
 			persisted = "persisted"
 		}
-		identityLine = fmt.Sprintf("\n[gray]Identity file:[white] %s ([yellow]%s[white])", result.IdentityFile, persisted)
+		identityLine = fmt.Sprintf("\n[gray]Identity file:[white] %s ([yellow]%s[white])", tview.Escape(result.IdentityFile), persisted)
 	}
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@internal/tui/wizard/telegram_setup_tui.go` around lines 103 - 112, The
serverIDView text concatenation uses tview dynamic colors and currently inserts
raw values (result.ServerID and result.IdentityFile/persisted) which allows '['
or ']' in those values to be parsed as markup; update the construction of
serverIDLine and identityLine to wrap any interpolated user strings with
tview.Escape() (e.g., escape result.ServerID, result.IdentityFile and the
persisted string) before formatting, then call serverIDView.SetText with the
escaped strings so SetDynamicColors(true) no longer misinterprets literal
brackets.
🧹 Nitpick comments (3)
internal/tui/wizard/age.go (2)

277-283: Inconsistent indentation in form setup.

Lines 277-283 have inconsistent indentation - there appears to be an extra tab before form.Form.AddFormItem(privateKeyField) and the comment/initialization on lines 279-280 compared to the surrounding code.

♻️ Fix indentation
-		form.Form.AddFormItem(privateKeyField)
+	form.Form.AddFormItem(privateKeyField)

-		// Initialize with "existing" type selected
-		setupType = ageSetupTypeExisting
+	// Initialize with "existing" type selected
+	setupType = ageSetupTypeExisting
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@internal/tui/wizard/age.go` around lines 277 - 283, The block initializing
the form has inconsistent indentation: align the lines starting with
form.Form.AddFormItem(privateKeyField), the comment/initialization of setupType
= ageSetupTypeExisting, and the three SetDisabled calls
(passphraseField.SetDisabled, passphraseConfirmField.SetDisabled,
privateKeyField.SetDisabled) with the surrounding code block indentation (remove
the extra tab before form.Form.AddFormItem and ensure the comment and setupType
line use the same indent level), so the entire form setup block is consistently
indented.

286-314: Inconsistent indentation in submit handler.

The submit handler body has inconsistent indentation compared to Go conventions. The switch statement and its cases appear to have extra indentation.

♻️ Fix indentation
 	form.SetOnSubmit(func(values map[string]string) error {
-			data.SetupType = setupType
+		data.SetupType = setupType

-			switch setupType {
-			case ageSetupTypeExisting:
-				publicKey, err := validatePublicKey(publicKeyField.GetText())
-				if err != nil {
-					return err
-				}
-				data.PublicKey = publicKey
-				data.RecipientKey = publicKey
+		switch setupType {
+		case ageSetupTypeExisting:
+			publicKey, err := validatePublicKey(publicKeyField.GetText())
+			if err != nil {
+				return err
+			}
+			data.PublicKey = publicKey
+			data.RecipientKey = publicKey
 
-			case ageSetupTypePassphrase:
-				passphrase, err := validatePassphrase(passphraseField.GetText(), passphraseConfirmField.GetText())
-				if err != nil {
-					return err
-				}
-				data.Passphrase = passphrase
+		case ageSetupTypePassphrase:
+			passphrase, err := validatePassphrase(passphraseField.GetText(), passphraseConfirmField.GetText())
+			if err != nil {
+				return err
+			}
+			data.Passphrase = passphrase
 
-			case ageSetupTypePrivateKey:
-				privateKey, err := validatePrivateKey(privateKeyField.GetText())
-				if err != nil {
-					return err
+		case ageSetupTypePrivateKey:
+			privateKey, err := validatePrivateKey(privateKeyField.GetText())
+			if err != nil {
+				return err
 			}
 			data.PrivateKey = privateKey
 		}
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@internal/tui/wizard/age.go` around lines 286 - 314, The submit handler
registered with form.SetOnSubmit has inconsistent indentation around the switch
and its cases; reformat the block so the switch setupType and each case
(ageSetupTypeExisting, ageSetupTypePassphrase, ageSetupTypePrivateKey) and their
error checks are aligned with standard Go indentation, ensuring the data
assignments (data.PublicKey, data.RecipientKey, data.Passphrase,
data.PrivateKey) and return nil are at the correct indentation level and braces
close in the expected positions; update the form.SetOnSubmit anonymous function
body that calls validatePublicKey, validatePassphrase, and validatePrivateKey to
follow Go formatting conventions.
internal/tui/screen_test.go (1)

11-25: Consider extending the helper to cover additional composite primitives.

The primitiveContainsText helper currently only handles *tview.TextView and *tview.Flex. Other container types like *tview.Grid, *tview.Frame, or *tview.Pages won't be traversed. This is fine for the current BuildScreen test (which returns a Flex with TextViews), but may cause false negatives if the implementation changes or the helper is reused elsewhere.

♻️ Optional: Extend to handle more composite types
 func primitiveContainsText(p tview.Primitive, want string) bool {
 	switch v := p.(type) {
 	case nil:
 		return false
 	case *tview.TextView:
 		return strings.Contains(v.GetText(false), want)
 	case *tview.Flex:
 		for i := 0; i < v.GetItemCount(); i++ {
 			if primitiveContainsText(v.GetItem(i), want) {
 				return true
 			}
 		}
+	case *tview.Grid:
+		for i := 0; i < v.GetItemCount(); i++ {
+			if primitiveContainsText(v.GetItem(i), want) {
+				return true
+			}
+		}
+	case *tview.Pages:
+		for _, name := range v.GetPageNames(true) {
+			if page, _ := v.GetPage(name); page != nil {
+				if primitiveContainsText(page, want) {
+					return true
+				}
+			}
+		}
 	}
 	return false
 }
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@internal/tui/screen_test.go` around lines 11 - 25, Extend
primitiveContainsText to also recurse into other composite tview primitives by
adding type switches for *tview.Grid, *tview.Frame, and *tview.Pages (in
addition to the existing *tview.Flex and *tview.TextView cases); for each new
case, iterate the container’s children using the container’s accessor methods
and call primitiveContainsText recursively on each child so nested TextViews are
discovered even when wrapped in Grid, Frame, or Pages.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@internal/orchestrator/workflow_ui_tui_decrypt_prompts.go`:
- Around line 209-218: The handler passed to form.SetOnSubmit currently trims
the submitted secret and thus mutates valid passphrases and misinterprets inputs
like " 0 " as abort; change the logic in the form.SetOnSubmit function so you
only use strings.TrimSpace on a temporary variable for validation (to check
emptiness and to compare to "0"), but assign the untrimmed original
values[label] to secret when setting it; keep the cancelled = true branch using
the trimmed check and ensure the error for empty input uses the trimmed copy.

In `@internal/tui/screen.go`:
- Around line 59-76: The footer uses dynamic tview color tags but inserts raw
values into the markup for configPath and buildSig, which can break rendering if
they contain '[' or ']'; update the creation of configPathText and buildSigText
to wrap the variable parts with tview.Escape(...) (i.e., use
tview.Escape(configPath) and tview.Escape(buildSig) inside the fmt.Sprintf
before calling SetDynamicColors(true)) so that any brackets are escaped and the
footer renders safely.

In `@internal/tui/wizard/install.go`:
- Around line 65-67: Update the top-level runner to accept and use the provided
context: change the runInstallWizardRunner signature to accept ctx
context.Context (e.g., func(ctx context.Context, app *tui.App, root, focus
tview.Primitive) error) and replace the blocking app.Run() call with a
context-aware run (preferably app.RunWithContext(ctx) if available, otherwise
start app.Run() in a goroutine and cancel/wait on ctx.Done()). Ensure all
callers (including RunInstallWizard and the other runner instance referenced
around the other occurrence) pass the same ctx through to runInstallWizardRunner
so the TUI exits immediately when the workflow context is canceled.

---

Outside diff comments:
In `@internal/tui/wizard/telegram_setup_tui.go`:
- Around line 103-112: The serverIDView text concatenation uses tview dynamic
colors and currently inserts raw values (result.ServerID and
result.IdentityFile/persisted) which allows '[' or ']' in those values to be
parsed as markup; update the construction of serverIDLine and identityLine to
wrap any interpolated user strings with tview.Escape() (e.g., escape
result.ServerID, result.IdentityFile and the persisted string) before
formatting, then call serverIDView.SetText with the escaped strings so
SetDynamicColors(true) no longer misinterprets literal brackets.

---

Nitpick comments:
In `@internal/tui/screen_test.go`:
- Around line 11-25: Extend primitiveContainsText to also recurse into other
composite tview primitives by adding type switches for *tview.Grid,
*tview.Frame, and *tview.Pages (in addition to the existing *tview.Flex and
*tview.TextView cases); for each new case, iterate the container’s children
using the container’s accessor methods and call primitiveContainsText
recursively on each child so nested TextViews are discovered even when wrapped
in Grid, Frame, or Pages.

In `@internal/tui/wizard/age.go`:
- Around line 277-283: The block initializing the form has inconsistent
indentation: align the lines starting with
form.Form.AddFormItem(privateKeyField), the comment/initialization of setupType
= ageSetupTypeExisting, and the three SetDisabled calls
(passphraseField.SetDisabled, passphraseConfirmField.SetDisabled,
privateKeyField.SetDisabled) with the surrounding code block indentation (remove
the extra tab before form.Form.AddFormItem and ensure the comment and setupType
line use the same indent level), so the entire form setup block is consistently
indented.
- Around line 286-314: The submit handler registered with form.SetOnSubmit has
inconsistent indentation around the switch and its cases; reformat the block so
the switch setupType and each case (ageSetupTypeExisting,
ageSetupTypePassphrase, ageSetupTypePrivateKey) and their error checks are
aligned with standard Go indentation, ensuring the data assignments
(data.PublicKey, data.RecipientKey, data.Passphrase, data.PrivateKey) and return
nil are at the correct indentation level and braces close in the expected
positions; update the form.SetOnSubmit anonymous function body that calls
validatePublicKey, validatePassphrase, and validatePrivateKey to follow Go
formatting conventions.

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: 93b0635d-4042-4a06-b575-9485dc09252c

📥 Commits

Reviewing files that changed from the base of the PR and between bfca674 and fc15dea.

📒 Files selected for processing (28)
  • internal/logging/bootstrap.go
  • internal/orchestrator/bundle_test.go
  • internal/orchestrator/decrypt_tui.go
  • internal/orchestrator/decrypt_tui_e2e_helpers_test.go
  • internal/orchestrator/decrypt_tui_e2e_test.go
  • internal/orchestrator/deps_test.go
  • internal/orchestrator/restore_tui.go
  • internal/orchestrator/tui_screen_env.go
  • internal/orchestrator/tui_simulation_test.go
  • internal/orchestrator/workflow_ui_tui_decrypt.go
  • internal/orchestrator/workflow_ui_tui_decrypt_prompts.go
  • internal/orchestrator/workflow_ui_tui_decrypt_test.go
  • internal/orchestrator/workflow_ui_tui_restore.go
  • internal/orchestrator/workflow_ui_tui_restore_test.go
  • internal/security/security.go
  • internal/security/security_test.go
  • internal/tui/screen.go
  • internal/tui/screen_test.go
  • internal/tui/wizard/age.go
  • internal/tui/wizard/age_test.go
  • internal/tui/wizard/age_ui_adapter.go
  • internal/tui/wizard/age_ui_adapter_test.go
  • internal/tui/wizard/install.go
  • internal/tui/wizard/new_install.go
  • internal/tui/wizard/new_install_test.go
  • internal/tui/wizard/post_install_audit_tui.go
  • internal/tui/wizard/screen.go
  • internal/tui/wizard/telegram_setup_tui.go
✅ Files skipped from review due to trivial changes (5)
  • internal/orchestrator/workflow_ui_tui_restore.go
  • internal/logging/bootstrap.go
  • internal/orchestrator/decrypt_tui_e2e_test.go
  • internal/orchestrator/workflow_ui_tui_decrypt_test.go
  • internal/orchestrator/decrypt_tui_e2e_helpers_test.go
🚧 Files skipped from review as they are similar to previous changes (5)
  • internal/orchestrator/decrypt_tui.go
  • internal/security/security_test.go
  • internal/tui/wizard/age_test.go
  • internal/tui/wizard/age_ui_adapter_test.go
  • internal/tui/wizard/age_ui_adapter.go

Normalize indentation and alignment in RunAgeSetupWizard (internal/tui/wizard/age.go) for improved readability. This is a formatting-only change — no logic or behavior was modified.

LiveReview Pre-Commit Check: skipped (iter:1, coverage:0%)
Escape user- or runtime-provided strings in TUI views using tview.Escape to avoid accidental markup/formatting and ensure raw/visual text is correct. Changes apply across orchestrator and tui packages: network health and NIC messages, diagnostics paths, decrypt messages and prompts, screen config/build signature, AGE wizard modals and inputs, install/new-install confirmations, post-install audit details, and Telegram setup display. Also adds a test verifying bracketed server/identity values are escaped, and minor formatting cleanups in the AGE setup dropdown handling.

LiveReview Pre-Commit Check: skipped (iter:1, coverage:0%)
Add a test to ensure passphrases with leading/trailing spaces are preserved and update the TUI decrypt prompt to stop trimming the input before returning it. The prompt now uses a trimmed copy for validation and for detecting the "0" cancel token, but returns the raw value so user-intended surrounding spaces are kept. Files changed: added test in decrypt_tui_simulation_test.go and adjusted logic in workflow_ui_tui_decrypt_prompts.go.

LiveReview Pre-Commit Check: skipped (iter:1, coverage:0%)
Introduce App.RunWithContext to allow running/stopping the TUI based on a context, using an atomic state to differentiate cancellations vs. Run errors. Update internal/tui/app.go imports and logic accordingly. Update install wizard to pass the context into runInstallWizardRunner and call RunWithContext (internal/tui/wizard/install.go), and adjust related tests to validate cancellation behavior and context propagation (internal/tui/abort_context_test.go, internal/tui/wizard/install_test.go). Minor test imports added (errors, tcell, context) and runInstallWizardRunner now sets root/focus before running with context.

LiveReview Pre-Commit Check: skipped (iter:1, coverage:0%)
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: 2

Caution

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

⚠️ Outside diff range comments (1)
internal/tui/wizard/age.go (1)

167-169: ⚠️ Potential issue | 🟡 Minor

Pass ctx to RunWithContext for context-aware cancellation.

The ctx context.Context parameter is accepted but never used in RunAgeSetupWizard. The ageWizardRunner should be updated to accept and pass the context to app.RunWithContext(ctx) instead of app.Run(), matching the pattern used in RunInstallWizard at internal/tui/wizard/install.go:68. This ensures context cancellation actually stops the TUI wizard.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@internal/tui/wizard/age.go` around lines 167 - 169, RunAgeSetupWizard
currently ignores the ctx parameter; update ageWizardRunner and its invocation
so the TUI uses context-aware execution. Modify the ageWizardRunner (and any
function literal passed to tui.NewApp or app.Run) to accept a context parameter
and call app.RunWithContext(ctx) instead of app.Run(), and then change
RunAgeSetupWizard to pass the received ctx into ageWizardRunner so cancellation
of the provided context stops the wizard.
🧹 Nitpick comments (2)
internal/tui/screen.go (1)

31-35: Note: HeaderText is not escaped.

The spec.HeaderText is rendered with SetDynamicColors(true) but isn't escaped. This is safe as long as HeaderText is always set to static/trusted strings (as seen in current usage). If user-controlled data is ever passed here, escaping would be needed.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@internal/tui/screen.go` around lines 31 - 35, The header text is currently
passed raw into welcomeText with SetDynamicColors(true) which allows
color/control sequences; update the code that sets welcomeText to escape
spec.HeaderText before setting it (e.g., use tview.Escape on spec.HeaderText)
or, if dynamic colors are required for trusted static headers only, explicitly
document and assert the trust boundary; modify the welcomeText construction (the
call that uses spec.HeaderText together with SetDynamicColors(true) in the
welcomeText variable) to apply escaping or disable dynamic colors to eliminate
unescaped user-controlled input.
internal/orchestrator/workflow_ui_tui_decrypt_prompts.go (1)

23-62: Consider using app.Stop() instead of QueueEvent(Ctrl+C).

The runTUIAppWithContext helper sends a Ctrl+C event to cancel the TUI, but App.RunWithContext in app.go uses app.Stop() directly. For consistency and reliability (Ctrl+C handling depends on input handlers), consider aligning with the pattern in App.RunWithContext.

That said, if Ctrl+C is intentionally chosen to trigger specific cleanup behavior, this approach is valid.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@internal/orchestrator/workflow_ui_tui_decrypt_prompts.go` around lines 23 -
62, runTUIAppWithContext currently cancels the TUI by queueing a Ctrl+C key
event; replace that with a direct call to app.Stop() for consistency with
App.RunWithContext and more reliable shutdown. In the goroutine watching
ctx.Done(), call app.Stop() (while keeping the atomic state transitions using
the existing atomic.Int32 `state` and CompareAndSwap checks) instead of
app.QueueEvent(tcell.NewEventKey(...)); ensure the same state values (0->1 for
cancellation, 0->2 for normal stop) and the existing error-return logic in
runTUIAppWithContext remain unchanged so behavior and return values still depend
on `state` and ctx.Err().
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@internal/tui/wizard/telegram_setup_tui.go`:
- Around line 22-25: The wizard currently only uses ctx for checkCtx so
cancellation doesn't stop Run(); update the telegram setup flow (where
telegramSetupBuildBootstrap, telegramSetupCheckRegistration,
telegramSetupQueueUpdateDraw and telegramSetupGo are used and where Run() is
invoked) to honor the parent ctx by either (a) wiring the wizard into the
context-aware runner so the screen's Run() is cancelled when ctx.Done() is
closed, or (b) spawn a goroutine that listens for ctx.Done() and calls the TUI
shutdown (stop the app / call app.Stop or equivalent) to unblock Run(); ensure
checkCtx use remains but add the ctx cancellation hook around the app.Run/Run()
path so the install context cancellation propagates upstream.
- Around line 103-115: The status text built from untrusted HTTP response bodies
is not escaped and can be interpreted as tview color markup (since statusView
uses SetDynamicColors), so update the two callers in
internal/notify/telegram_registration.go that build Message (including
string(body)) to escape untrusted parts before passing to setStatus (or before
calling statusView.SetText): wrap the raw response body (and any other untrusted
strings) with tview.Escape(...) and then compose the Message so only trusted
color tags remain; ensure you update the setStatus call sites and any direct
statusView.SetText usage to use the escaped string.

---

Outside diff comments:
In `@internal/tui/wizard/age.go`:
- Around line 167-169: RunAgeSetupWizard currently ignores the ctx parameter;
update ageWizardRunner and its invocation so the TUI uses context-aware
execution. Modify the ageWizardRunner (and any function literal passed to
tui.NewApp or app.Run) to accept a context parameter and call
app.RunWithContext(ctx) instead of app.Run(), and then change RunAgeSetupWizard
to pass the received ctx into ageWizardRunner so cancellation of the provided
context stops the wizard.

---

Nitpick comments:
In `@internal/orchestrator/workflow_ui_tui_decrypt_prompts.go`:
- Around line 23-62: runTUIAppWithContext currently cancels the TUI by queueing
a Ctrl+C key event; replace that with a direct call to app.Stop() for
consistency with App.RunWithContext and more reliable shutdown. In the goroutine
watching ctx.Done(), call app.Stop() (while keeping the atomic state transitions
using the existing atomic.Int32 `state` and CompareAndSwap checks) instead of
app.QueueEvent(tcell.NewEventKey(...)); ensure the same state values (0->1 for
cancellation, 0->2 for normal stop) and the existing error-return logic in
runTUIAppWithContext remain unchanged so behavior and return values still depend
on `state` and ctx.Err().

In `@internal/tui/screen.go`:
- Around line 31-35: The header text is currently passed raw into welcomeText
with SetDynamicColors(true) which allows color/control sequences; update the
code that sets welcomeText to escape spec.HeaderText before setting it (e.g.,
use tview.Escape on spec.HeaderText) or, if dynamic colors are required for
trusted static headers only, explicitly document and assert the trust boundary;
modify the welcomeText construction (the call that uses spec.HeaderText together
with SetDynamicColors(true) in the welcomeText variable) to apply escaping or
disable dynamic colors to eliminate unescaped user-controlled input.

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: 4d8d0d6a-f36a-41f7-8016-df08c4a2303b

📥 Commits

Reviewing files that changed from the base of the PR and between fc15dea and f855e80.

📒 Files selected for processing (14)
  • internal/orchestrator/decrypt_tui_simulation_test.go
  • internal/orchestrator/restore_tui.go
  • internal/orchestrator/workflow_ui_tui_decrypt.go
  • internal/orchestrator/workflow_ui_tui_decrypt_prompts.go
  • internal/tui/abort_context_test.go
  • internal/tui/app.go
  • internal/tui/screen.go
  • internal/tui/wizard/age.go
  • internal/tui/wizard/install.go
  • internal/tui/wizard/install_test.go
  • internal/tui/wizard/new_install.go
  • internal/tui/wizard/post_install_audit_tui.go
  • internal/tui/wizard/telegram_setup_tui.go
  • internal/tui/wizard/telegram_setup_tui_test.go
🚧 Files skipped from review as they are similar to previous changes (2)
  • internal/tui/wizard/post_install_audit_tui.go
  • internal/orchestrator/decrypt_tui_simulation_test.go

Comment on lines +103 to 115
escapedServerID := tview.Escape(result.ServerID)
serverIDLine := fmt.Sprintf("[yellow]%s[white]", escapedServerID)
identityLine := ""
if result.IdentityFile != "" {
persisted := "not persisted"
if result.IdentityPersisted {
persisted = "persisted"
}
identityLine = fmt.Sprintf("\n[gray]Identity file:[white] %s ([yellow]%s[white])", result.IdentityFile, persisted)
escapedIdentityFile := tview.Escape(result.IdentityFile)
escapedPersisted := tview.Escape(persisted)
identityLine = fmt.Sprintf("\n[gray]Identity file:[white] %s ([yellow]%s[white])", escapedIdentityFile, escapedPersisted)
}
serverIDView.SetText(serverIDLine + identityLine)
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

🧩 Analysis chain

🏁 Script executed:

# First, locate the file and check its structure
find . -name "telegram_setup_tui.go" -type f

Repository: tis24dev/proxsave

Length of output: 104


🏁 Script executed:

# Search for setStatus calls in the file
rg "setStatus\(" internal/tui/wizard/telegram_setup_tui.go -A 2 -B 1

Repository: tis24dev/proxsave

Length of output: 597


🏁 Script executed:

# Check for SetDynamicColors usage in the file
rg "SetDynamicColors" internal/tui/wizard/telegram_setup_tui.go -B 3 -A 1

Repository: tis24dev/proxsave

Length of output: 424


🏁 Script executed:

# Look at telegram_registration.go to see how Message is constructed
find . -name "telegram_registration.go" -type f

Repository: tis24dev/proxsave

Length of output: 103


🏁 Script executed:

# Check the statusView setup and how it's used
rg "statusView" internal/tui/wizard/telegram_setup_tui.go -B 2 -A 2

Repository: tis24dev/proxsave

Length of output: 477


🏁 Script executed:

# Now examine telegram_registration.go to see how status.Message is built
cat ./internal/notify/telegram_registration.go | head -100

Repository: tis24dev/proxsave

Length of output: 4088


🏁 Script executed:

# Get more of the file to understand the Message construction
wc -l ./internal/notify/telegram_registration.go

Repository: tis24dev/proxsave

Length of output: 106


🏁 Script executed:

# Search for where Message is assigned in telegram_registration.go
rg "Message\s*:" ./internal/notify/telegram_registration.go -B 5 -A 2

Repository: tis24dev/proxsave

Length of output: 2565


🏁 Script executed:

# Look at the exact context around the two setStatus calls with untrusted data
rg "Linked successfully" ./internal/tui/wizard/telegram_setup_tui.go -B 10 -A 5

Repository: tis24dev/proxsave

Length of output: 528


🏁 Script executed:

# Get more context around the truncate call
rg "truncate\(msg" ./internal/tui/wizard/telegram_setup_tui.go -B 15 -A 5

Repository: tis24dev/proxsave

Length of output: 666


Escape untrusted status text to prevent markup injection.

The statusView is created with SetDynamicColors(true), so markup characters like [ and ] are interpreted as color tags. In internal/notify/telegram_registration.go, the unexpected-status path constructs Message by including the raw HTTP response body via string(body). If that response contains markup characters, they will corrupt the status display.

Both setStatus() calls that use untrusted data need escaping:

Proposed fix
-					setStatus(fmt.Sprintf("[green]✓ Linked successfully.[white]\n\n%s", status.Message))
+					setStatus(fmt.Sprintf("[green]✓ Linked successfully.[white]\n\n%s", tview.Escape(status.Message)))-				setStatus(fmt.Sprintf("[yellow]%s[white]%s", truncate(msg, 300), hint))
+				setStatus(fmt.Sprintf("[yellow]%s[white]%s", tview.Escape(truncate(msg, 300)), hint))
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@internal/tui/wizard/telegram_setup_tui.go` around lines 103 - 115, The status
text built from untrusted HTTP response bodies is not escaped and can be
interpreted as tview color markup (since statusView uses SetDynamicColors), so
update the two callers in internal/notify/telegram_registration.go that build
Message (including string(body)) to escape untrusted parts before passing to
setStatus (or before calling statusView.SetText): wrap the raw response body
(and any other untrusted strings) with tview.Escape(...) and then compose the
Message so only trusted color tags remain; ensure you update the setStatus call
sites and any direct statusView.SetText usage to use the escaped string.

Change ageWizardRunner to accept a context.Context and use App.RunWithContext. Update callers to pass context.Background() where no ctx is available and propagate the provided ctx in RunAgeSetupWizard. Adjust tests to the new runner signature and add TestRunAgeSetupWizard_PassesContextToRunner to verify context propagation. Also update registerAgeWizardRunner and related UI adapter tests to match the new signature.

LiveReview Pre-Commit Check: skipped (iter:1, coverage:0%)
Use tview.Escape on ScreenSpec.HeaderText before setting the TextView so literal characters like '['/']' aren't interpreted as tview markup. Added TestBuildScreenEscapesHeaderText to assert the header is rendered escaped.

LiveReview Pre-Commit Check: skipped (iter:1, coverage:0%)
Make TUI wizard runners context-aware: change runner signatures to accept context.Context and use app.RunWithContext(ctx) instead of Run. Update callers to pass the ctx through. Add tests verifying the context is propagated for post-install audit and Telegram setup wizards. Also stop the TUI app via app.Stop() when the orchestration context is done to ensure proper shutdown.

LiveReview Pre-Commit Check: skipped (iter:1, coverage:0%)
Pass context.Context through TUI prompt/runner APIs and use app.RunWithContext to honor cancellations/timeouts. Updated many orchestrator and wizard functions (prompts, restore flow, decrypt UI, network commit, installers) to accept a ctx parameter and forward it to the underlying TUI runner. Adjusted tests and stub helpers to include context. Removed the custom runTUIAppWithContext helper and an unused import, and replaced direct app.Run() calls with RunWithContext (or taskCtx where appropriate). This enables proper context cancellation handling for interactive UI flows.

LiveReview Pre-Commit Check: skipped (iter:1, coverage:0%)
Introduce context-aware variants for identity and filesystem operations and wire them through the install/upgrade/TUI flows. Added DetectWithContext, writeIdentityFileWithContext, setImmutableAttributeWithContext, clearImmutableAttributesWithContext and resetInstallBaseDirWithContext (with backward-compatible wrappers). Commands now use exec.CommandContext and operations check ctx.Err() so cancellations/timeouts are respected and propagated as errors. Updated callers in cmd/proxsave to pass context and added tests that validate cancellation behavior (TestResetInstallBaseDirWithContext_CanceledBeforeRemoval, TestSetImmutableAttributeWithContext_CanceledBeforeCommand).

LiveReview Pre-Commit Check: skipped (iter:1, coverage:0%)
Prevent tview from interpreting bracketed text in status messages by escaping messages before rendering. Replace raw status.Message and truncated msg usage with tview.Escape in the telegram setup wizard UI. Add TestRunTelegramSetupWizard_EscapesBracketedStatusMessage to verify the view shows the escaped text in the raw view while preserving the literal message in plain text and LastStatusMessage.

LiveReview Pre-Commit Check: skipped (iter:1, coverage:0%)
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: 2

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

364-375: Consider escaping error text to prevent unintended tview markup.

The compatErr is interpolated directly into the message at line 365. If the error contains tview markup characters (e.g., [red] or brackets), it could cause rendering issues. The same applies to cause at line 378.

Given the PR's pattern of escaping user-derived strings in promptNetworkCommitTUI, consider applying tview.Escape() here for consistency.

💡 Suggested fix
 func promptCompatibilityTUI(ctx context.Context, configPath, buildSig string, compatErr error) (bool, error) {
-	message := fmt.Sprintf("Compatibility check reported:\n\n[red]%v[white]\n\nContinuing may cause system instability.\n\nDo you want to continue anyway?", compatErr)
+	message := fmt.Sprintf("Compatibility check reported:\n\n[red]%s[white]\n\nContinuing may cause system instability.\n\nDo you want to continue anyway?", tview.Escape(compatErr.Error()))
 	return promptYesNoTUIFunc(
 func promptContinueWithoutSafetyBackupTUI(ctx context.Context, configPath, buildSig string, cause error) (bool, error) {
-	message := fmt.Sprintf("Failed to create safety backup:\n\n[red]%v[white]\n\nWithout a safety backup, it will be harder to rollback changes.\n\nContinue without safety backup?", cause)
+	message := fmt.Sprintf("Failed to create safety backup:\n\n[red]%s[white]\n\nWithout a safety backup, it will be harder to rollback changes.\n\nContinue without safety backup?", tview.Escape(cause.Error()))
 	return promptYesNoTUIFunc(
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@internal/orchestrator/restore_tui.go` around lines 364 - 375, The
compatibility error string is interpolated directly into
promptCompatibilityTUI's message (and similarly for the `cause` in the nearby
code), which can trigger tview markup rendering if it contains brackets; update
promptCompatibilityTUI to escape user-derived text before formatting the message
by applying tview.Escape to the error (e.g., convert compatErr to a string and
pass through tview.Escape) and do the same for any `cause` variable, following
the pattern used in promptNetworkCommitTUI; ensure you still use
promptYesNoTUIFunc with the escaped message so the UI displays raw error text
safely.
cmd/proxsave/install.go (1)

492-493: Redundant assignment.

result.SkipConfigWizard = false is unnecessary since the zero value of bool is already false.

♻️ Proposed fix
 	result.CronSchedule = cronutil.TimeToSchedule(cronTime)
-	result.SkipConfigWizard = false
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@cmd/proxsave/install.go` around lines 492 - 493, The assignment
result.SkipConfigWizard = false is redundant because the bool zero value is
false; remove that line from the install.go diff where result.CronSchedule is
set (target the result variable and its SkipConfigWizard field) so the field
remains at its zero value. Ensure no other code relies on that explicit
assignment (run tests or search for places that expect an explicit write) and
keep only the cron assignment cronutil.TimeToSchedule(cronTime).
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@internal/tui/wizard/new_install.go`:
- Line 39: ConfirmNewInstall currently drops caller cancellation by using
context.Background(); change its signature to accept a context.Context (e.g.,
func ConfirmNewInstall(ctx context.Context, baseDir string, buildSig string,
preservedEntries []string) (bool, error)) and pass that ctx into
confirmNewInstallRunner instead of context.Background(); update all callers of
ConfirmNewInstall to forward their context so timeouts/cancellations propagate,
and adjust any tests/mocks to supply a context as well.
- Around line 27-29: The current code unconditionally appends a "/" to each
preservedEntries item (variable trimmed), which incorrectly turns regular files
like "backup.env" into "backup.env/"; update the logic so you only append a
trailing slash when the entry is actually a directory: for each preservedEntries
entry, check the filesystem with os.Stat(trimmed) and call fi.IsDir() (handling
the error by treating unknown entries as non-directories), and only then do
trimmed += "/". Keep the existing strings.HasSuffix(trimmed, "/") guard so you
don't double-append.

---

Nitpick comments:
In `@cmd/proxsave/install.go`:
- Around line 492-493: The assignment result.SkipConfigWizard = false is
redundant because the bool zero value is false; remove that line from the
install.go diff where result.CronSchedule is set (target the result variable and
its SkipConfigWizard field) so the field remains at its zero value. Ensure no
other code relies on that explicit assignment (run tests or search for places
that expect an explicit write) and keep only the cron assignment
cronutil.TimeToSchedule(cronTime).

In `@internal/orchestrator/restore_tui.go`:
- Around line 364-375: The compatibility error string is interpolated directly
into promptCompatibilityTUI's message (and similarly for the `cause` in the
nearby code), which can trigger tview markup rendering if it contains brackets;
update promptCompatibilityTUI to escape user-derived text before formatting the
message by applying tview.Escape to the error (e.g., convert compatErr to a
string and pass through tview.Escape) and do the same for any `cause` variable,
following the pattern used in promptNetworkCommitTUI; ensure you still use
promptYesNoTUIFunc with the escaped message so the UI displays raw error text
safely.

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: 2efd2a25-56ac-4b3c-b4c6-1ea9d50d4a90

📥 Commits

Reviewing files that changed from the base of the PR and between f855e80 and b76830c.

📒 Files selected for processing (28)
  • cmd/proxsave/install.go
  • cmd/proxsave/install_test.go
  • cmd/proxsave/install_tui.go
  • cmd/proxsave/main.go
  • cmd/proxsave/upgrade.go
  • internal/identity/identity.go
  • internal/identity/identity_test.go
  • internal/orchestrator/network_apply_workflow_ui.go
  • internal/orchestrator/restore_tui.go
  • internal/orchestrator/restore_tui_simulation_test.go
  • internal/orchestrator/restore_tui_test.go
  • internal/orchestrator/tui_simulation_test.go
  • internal/orchestrator/workflow_ui_tui_decrypt.go
  • internal/orchestrator/workflow_ui_tui_decrypt_prompts.go
  • internal/orchestrator/workflow_ui_tui_restore.go
  • internal/tui/screen.go
  • internal/tui/screen_test.go
  • internal/tui/wizard/age.go
  • internal/tui/wizard/age_test.go
  • internal/tui/wizard/age_ui_adapter_test.go
  • internal/tui/wizard/install.go
  • internal/tui/wizard/install_test.go
  • internal/tui/wizard/new_install.go
  • internal/tui/wizard/new_install_test.go
  • internal/tui/wizard/post_install_audit_tui.go
  • internal/tui/wizard/post_install_audit_tui_test.go
  • internal/tui/wizard/telegram_setup_tui.go
  • internal/tui/wizard/telegram_setup_tui_test.go
✅ Files skipped from review due to trivial changes (3)
  • internal/tui/wizard/age_ui_adapter_test.go
  • internal/orchestrator/workflow_ui_tui_restore.go
  • internal/orchestrator/workflow_ui_tui_decrypt.go
🚧 Files skipped from review as they are similar to previous changes (7)
  • cmd/proxsave/upgrade.go
  • internal/tui/wizard/install_test.go
  • cmd/proxsave/main.go
  • internal/tui/wizard/age_test.go
  • internal/tui/wizard/new_install_test.go
  • internal/tui/wizard/install.go
  • internal/tui/screen.go

Comment on lines +27 to +29
if !strings.HasSuffix(trimmed, "/") {
trimmed += "/"
}
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

Don't force every preserved entry to look like a directory.

If preservedEntries includes regular files — e.g. the backup.env preservation mentioned in this PR — Lines 27-29 will render them as backup.env/, which is misleading in a destructive confirmation prompt.

Suggested fix
-		if !strings.HasSuffix(trimmed, "/") {
-			trimmed += "/"
-		}
 		formatted = append(formatted, trimmed)
📝 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
if !strings.HasSuffix(trimmed, "/") {
trimmed += "/"
}
formatted = append(formatted, trimmed)
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@internal/tui/wizard/new_install.go` around lines 27 - 29, The current code
unconditionally appends a "/" to each preservedEntries item (variable trimmed),
which incorrectly turns regular files like "backup.env" into "backup.env/";
update the logic so you only append a trailing slash when the entry is actually
a directory: for each preservedEntries entry, check the filesystem with
os.Stat(trimmed) and call fi.IsDir() (handling the error by treating unknown
entries as non-directories), and only then do trimmed += "/". Keep the existing
strings.HasSuffix(trimmed, "/") guard so you don't double-append.

Escape errors inserted into TUI prompt messages to prevent tview markup interpretation. Use tview.Escape(fmt.Sprint(...)) for compatibility and safety-backup prompts (promptCompatibilityTUI, promptContinueWithoutSafetyBackupTUI). Add tests to verify bracketed text is escaped in both prompts.

LiveReview Pre-Commit Check: skipped (iter:1, coverage:0%)
Drop the explicit `result.SkipConfigWizard = false` in runConfigWizardCLI. The field's zero value is already false and the assignment was redundant and could overwrite a previously set value, so it's removed to clean up the code.

LiveReview Pre-Commit Check: skipped (iter:1, coverage:0%)
Add context propagation for the interactive new-install TUI: change ConfirmNewInstall and newInstallConfirmTUI signatures to accept a context.Context and use that context when invoking the TUI runner (instead of context.Background()). Update runNewInstall to pass the incoming ctx to the TUI confirmation. Update tests to match the new signatures and add tests that assert the context is passed through (TestRunNewInstallTUIPassesContextToConfirm, TestConfirmNewInstallPassesContextToRunner).

LiveReview Pre-Commit Check: skipped (iter:1, coverage:0%)
Introduce indirection and test hooks to make HA restore and new-install code mockable, and expand unit test coverage.

- Expose a newInstallBuildSignature variable so build signature can be overridden in tests and add multiple new_install tests (stdin handling, formatting, plan validation, TUI/CLI flows).
- Add ha* function variables (haApplyGeteuid, haIsMounted, haIsRealRestoreFS, haArmRollback, haDisarmRollback, haApplyFromStage) and replace direct calls so HA restore behavior can be injected/mocked in tests. Use nowRestore() for time-sensitive behavior.
- Add comprehensive restore_ha_additional_test.go with many tests covering rollback arming/disarming, apply-from-stage edge cases, error paths and commit/rollback outcomes.
- Add TUI test helpers and extra tests (newSimulationApp, TestAppRunWithContext variants) and simplify RunWithContext error handling to prefer context errors.
- Add small test helpers in wizard/new_install_test.go for overriding confirm runner.

These changes primarily enable more robust testing and increase coverage around HA restore and new-install flows without changing core runtime behavior.

LiveReview Pre-Commit Check: skipped (iter:1, coverage:0%)
@tis24dev tis24dev closed this Mar 19, 2026
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