Skip to content

feat(happier): devbox agents, daemon PAT, opt-in auto-update + 2026-07 audit remediation#6

Open
danljungstrom wants to merge 33 commits into
happier-dev:mainfrom
danljungstrom:feat/devbox-agents-autoupdate
Open

feat(happier): devbox agents, daemon PAT, opt-in auto-update + 2026-07 audit remediation#6
danljungstrom wants to merge 33 commits into
happier-dev:mainfrom
danljungstrom:feat/devbox-agents-autoupdate

Conversation

@danljungstrom

@danljungstrom danljungstrom commented Jul 20, 2026

Copy link
Copy Markdown

What

Brings the happier LXC installer's devbox feature set to the fork, plus a full remediation of the 2026-07-16 audit.

Feature

  • Devbox agent provisioning (claude via the vendor native installer as the happier user, codex via a pinned npm global), with Node installed on demand.
  • Daemon GitHub PAT wiring via a root-owned 600 EnvironmentFile= drop-in (never an Environment= directive).
  • Opt-in auto-update timer (--auto-update), behind a CLI capability probe so older CLIs degrade with a warning instead of failing.
  • Shared misc/happier-common.func extracted so ct/happier.sh and install/happier-install.sh stop copy-pasting channel/UI-bundle/CLI resolvers.
  • Fully non-interactive installs: presetting any HAPPIER_PVE_* knob skips its prompt (PHS_SILENT=1 + mode=default for a fully unattended run).
  • Fork-owned lint workflow + a fork test suite (tests/happier/).

Audit remediation (2026-07-16 backlog) — ~37/40 findings closed, 1 wontfix. Highlights:

  • A3 — the update's bare relay host install reset HAPPIER_SERVER_UI_DIR from the managed bundle dir back to the vendor default, so the refreshed UI stopped being served after every update. Caught on LXC, fixed by re-passing the install-managed env.
  • A1/A2 — guard the relay reinstall (it ran unguarded under the ERR trap between CLI self-update and daemon restart), and preserve the user's autostart/running state across the update.
  • B1/B2 — unset the exported PAT + Tailscale key so third-party root children (npm lifecycle, NodeSource, vendor bootstrap) don't inherit them; pin the codex version.
  • B3/P1/A6-A8 — UI-bundle downgrade guard, skip the no-op same-version refresh, atomic symlink swap, guarded move-aside, per-tag buffered release resolver.
  • E1/E2 — restore proxy-env sourcing in the generated /usr/bin/update; CI now watches and cmp-guards the duplicated installer JSON.
  • G1 — stop labeling the stable channel "recommended for production" (see caveats).

Verification

  • LXC-verified on a live Proxmox host (dev channel, devbox, installers method): install rc=0 (relay active, UI served) and update rc=0, with the A3 env-preserve and the "enabled-but-manually-stopped relay stays stopped" case both driven live. Run records: docs/audits/2026-07-03-lxc-matrix.md and docs/audits/2026-07-17-lxc-matrix.md (incl. a re-smoke on the branch tip).
  • shellcheck 0.11.0 --norc -S info and shfmt -i 2 -ci clean on every fork-owned file.
  • Fork test suite green: 53 tests (bash tests/happier/run.sh).
  • Independent Codex review over the remediation diff: 3 edge-case defects found and fixed (autostart restore for the enabled+stopped state, a missing empty-body guard in the generated update helper, and an un-hardened bootstrap fetch).

Known caveats

  • Stable channel is broken vendor-side. A fresh stable install fails at relay host install ("relay runtime did not become healthy") — re-confirmed 2026-07-17. This is the happier CLI's hosted stable build, not this repo. The channel prompt no longer calls stable "recommended" and the installer warns on preset stable installs; both carry a dated comment to remove once the vendor build works again. dev/preview install cleanly.
  • from_source was exercised end-to-end (Node + hstack setup-from-source ran with no code defect) but the build didn't run to completion on the test host (memory-bound), so it isn't a full clean-deploy confirmation. Tracked as D1.
  • P2 (Tailscale Serve-loop early bail) remains open — determining the no-HTTPS signal safely needs a live tailnet.

Scope

33 commits, 30 files (+4335 / −1077). Fork-owned files plus three small carried patches to the upstream framework (misc/build.func, misc/core.func, misc/install.func).

🤖 Generated with Claude Code


View with Codesmith Autofix with Codesmith
Need help on this PR? Tag /codesmith with what you need. Autofix is disabled.

Note

Add devbox agent provisioning, daemon PAT support, and opt-in auto-update to the Happier installer

  • Extracts shared channel helpers into a new happier-common.func sourced at runtime by both happier-install.sh and happier.sh, replacing duplicated inline implementations.
  • Devbox installs can now optionally install claude and codex agent CLIs and write a secure systemd env drop-in with agent binary paths and an optional GitHub PAT for daemon git operations.
  • Adds an opt-in auto-update prompt with configurable schedule (HAPPIER_PVE_AUTO_UPDATE, HAPPIER_PVE_AUTO_UPDATE_AT); the relay reinstall propagates the --auto-update flag when the timer is already active.
  • Tailscale enrollment is rewritten with file-based auth key (avoids argv leakage), retries, status-flag classification, and automatic Serve URL detection; package install uses verified downloads to temp files.
  • The update flow in happier.sh preserves and restores the relay unit's enabled/active states, validates the CLI after self-update, warns instead of aborting on relay reinstall failure, and restarts the daemon afterward.
  • All installer prompts honor pre-set env vars for fully non-interactive runs and validate preset values.
  • Adds a TAP-style bash test harness under tests/happier/ covering channel helpers, Tailscale enrollment flags, env-file KV manipulation, retry semantics, UI bundle verification, and URL/time validators.
  • Adds a CI workflow (happier-lint.yml) running shellcheck and shfmt on relevant scripts.
  • Risk: misc/install.func now hardcodes the update payload URL to community-scripts/ProxmoxVE@main, ignoring any INSTALLER_REPO/INSTALLER_REF overrides set at install time.

Macroscope summarized cdd3fdf.

Summary by CodeRabbit

  • New Features

    • Added non-interactive installation options for daemon authentication, agent CLI setup, GitHub token configuration, and scheduled updates.
    • Added release-channel configuration and clearer server, configuration-path, and Devbox guidance.
    • Added safer UI bundle installation and refresh with signature and checksum verification.
    • Improved Tailscale enrollment, access URL handling, retries, and service state preservation.
    • Added safer update behavior for the CLI, relay, daemon, and web interface.
  • Bug Fixes

    • Improved validation, error reporting, secret handling, and configuration persistence.
  • Documentation

    • Added audit reports, backlogs, implementation plans, and integration test results.
  • Tests

    • Added comprehensive automated coverage for installation, updates, validation, channels, networking, and UI bundles.

danljungstrom and others added 30 commits July 3, 2026 05:56
…staller

QR-based daemon auth (mobile method, Ctrl+C-skippable, 5-minute timeout)
re-implemented on the refactored community-scripts framework, with hardened
install/update failure paths and the ct/install update drift aligned.

Co-Authored-By: Claude Fable 5 <[email protected]>
Channel mapping, UI-bundle verification, GitHub fetch, CLI resolution, and the
systemd restart idiom move into one shared library fetched at the same
INSTALLER_REPO/INSTALLER_REF as build.func and sourced by both ct/happier.sh
and install/happier-install.sh. Post-install "Next steps" output unified into
one printer; the /usr/bin/update helper honors the same pins.

Co-Authored-By: Claude Fable 5 <[email protected]>
The from_source server binds HAPPIER_STACK_SERVER_PORT, not PORT; the installer
now sets it in SETUP_ENV and persists it to the stack env file when overridden.
json config_path note scoped per install method.

Co-Authored-By: Claude Fable 5 <[email protected]>
bash -n on the happier scripts + patched framework funcs, shellcheck on the
fork-owned scripts, informational shfmt, and json validation — fork-owned so it
actually runs on this repo's PRs (upstream workflows are org-gated and parked).

Co-Authored-By: Claude Fable 5 <[email protected]>
New install knobs (interactive prompts + env): HAPPIER_PVE_INSTALL_AGENTS
(default 1) installs the claude/codex CLIs on devbox; HAPPIER_PVE_GITHUB_PAT
wires a GitHub token into the daemon via a chmod-600 systemd drop-in;
HAPPIER_PVE_AUTO_UPDATE(+_AT) enables the managed relay auto-update timer.
The helper-script Update action now restarts the daemon so a CLI self-update
doesn't leave it running the old binary. Knobs documented in json/happier.json.

Co-Authored-By: Claude Fable 5 <[email protected]>
…cords

Design spec + implementation plan for the devbox-agents/auto-update feature,
plus the 2026-06-24 and 2026-06-25 audit reports and remediation backlogs that
drove the helper extraction, from_source fixes, and CI lint gate.

Co-Authored-By: Claude Fable 5 <[email protected]>
… mismatch

The archive was downloaded as happier-ui-web.tar.gz, but the release checksums
file names the asset (happier-ui-web-v<ver>-web-any.tar.gz) and `sha256sum -c`
opens the file the checksums line names — so verification always failed with
"No such file or directory" and every managed UI-bundle install/refresh aborted
at the checksum step (fail-closed, but always). Save the archive under its
release-asset name. Found by the new UI-bundle verify test suite.

Co-Authored-By: Claude Fable 5 <[email protected]>
Full remediation of the 2026-07-01 audit (docs/audits/2026-07-01-backlog.md
itemizes every finding; A/B/E/F epics land here):
- robustness: setup_nodejs guarded (non-fatal contract honored), systemctl
  --plain for daemon-unit discovery, self-update failures surfaced, GitHub
  fetches get retry/timeouts and UI-bundle refresh degrades instead of
  aborting updates, version swap keeps the live dir until the replacement
  is in place, drop-in write/reload/restart failures surfaced.
- secrets: daemon PAT moves to a root-owned 600 EnvironmentFile (unit
  Environment= is readable via D-Bus), validated before writing; tailscale
  pre-auth key passed via --auth-key=file: instead of argv; claude installed
  via Anthropic's native installer as the happier user instead of root npm.
- unification: shared enroll_tailscale_node/enable_tailscale_serve_url (the
  managed path gains the invalid-key/timeout/AuthURL diagnostics it lacked),
  shared epilogue/daemon-auth/finish helpers, find_happier_daemon_unit,
  resolve_hstack_layout, channel_default_stack_package, retry_until, plus
  exit-code, naming, quoting, and prompt-cancel consistency fixes.

Co-Authored-By: Claude Fable 5 <[email protected]>
shellcheck v0.11.0 and shfmt v3.13.1 installed as sha256-verified pinned
releases (apt shellcheck is years behind; releases/latest drifts).
happier-common.func joins the fully-linted set; the exclude machinery is gone —
fork files are clean under `shellcheck --norc -S info` with per-line directives
at the intentional deviations. shfmt step can now annotate failures; the json
check asserts the installer-contract fields. New tests/happier/ suite (plain
bash, no deps): channel-helper tables, URL/HH:MM validator edge cases, and
sandboxed UI-bundle verify fixtures (stub-minisign tier + real-keypair tier).

Co-Authored-By: Claude Fable 5 <[email protected]>
Adds the 2026-07-01 full-audit report + remediation backlog (47 findings:
43 fixed, 2 wontfix with rationale, D4 pending the LXC matrix run). Truth-up
of existing docs against the code: spec gains a deviations section (Node
installed on demand, from_source scope, PAT EnvironmentFile hardening, claude
native installer, authkey via file:), plan checkboxes reflect completed tasks,
json notes state the INSTALL_AGENTS default and scope AUTO_UPDATE to the
default install method, INSTALLER_REPO/REF pinning documented, the stale
frontend json duplicate synced, and the 2026-06-25 backlog community-scripts#10 re-statused
superseded (lint gate ≠ functional coverage).

Co-Authored-By: Claude Fable 5 <[email protected]>
Every app_questions knob now skips its whiptail prompt when the corresponding
HAPPIER_PVE_* variable is preset (the pattern the devbox knobs already used),
with validation of preset values (install type, remote access mode, channel via
normalize_happier_channel, proxy requires PUBLIC_URL). Fixes a latent preset
bug: REMOTE_ACCESS=tailscale now enables the TUN device on the preset path too.
Combined with build.func's mode=default this allows unattended installs — and
makes the spec's LXC test matrix scriptable.

Co-Authored-By: Claude Fable 5 <[email protected]>
The HAPPIER_INSTALL_DIR/BIN_DIR/... overrides prefixed `curl`, not the
consuming `bash -s` — an env prefix applies only to the first command of a
pipeline — so the vendor bootstrap fell back to its $HOME defaults and put the
CLI under /root/.happier, which the happier service user cannot execute. Move
the env to the bash side and widen resolve_installed_cli_for_channel with the
known bootstrap layouts (BIN_DIR shim + managed current/ dirs) as fallbacks.
Found by LXC matrix case A/B (install aborted: "Unable to resolve the
installed happier CLI").

Co-Authored-By: Claude Fable 5 <[email protected]>
Released Happier CLIs reject unknown `relay host install` arguments, so an
opted-in auto-update aborted the whole install (LXC matrix case A). Probe the
CLI's help output for --auto-update and degrade with a warning when the flag
is not supported yet.

Co-Authored-By: Claude Fable 5 <[email protected]>
…vior

Three LXC-matrix findings against current vendor CLIs:
- the bootstrap creates INSTALL_DIR mode 700 (correct for its $HOME default,
  wrong for a system-wide /opt path) — the happier service user could not
  traverse to the CLI; chmod 755 the install dir.
- `service install --mode system` now requires root (was run via sudo -u
  happier); run it as root with --system-user happier.
- the CLI's post-install activation check cannot pass before daemon auth
  (WAIT_FOR_AUTH), so tolerate a non-zero exit when the daemon unit was
  actually created (current name shape: happier-daemon.default.service, still
  matched by the happier-daemon* glob).

Co-Authored-By: Claude Fable 5 <[email protected]>
…r pins

Two more LXC-matrix findings:
- the CLI's daemon `service install` removed the relay unit during its
  reconciliation on a fresh devbox install (observed on 0.2.10-dev; not
  reproducible once a daemon already runs). Guard: after the background
  service install, reinstall the relay host with the original argument set if
  its unit vanished (ensure_relay_host_installed, idempotent).
- /usr/bin/update claimed to honor install-time pins but its quoted heredoc
  resolved INSTALLER_REPO/REF at update time, silently falling back to the
  moving default branch. Bake install-time values as defaults (env still
  overrides).

Co-Authored-By: Claude Fable 5 <[email protected]>
Full integration matrix executed unattended on pve-hp (fresh Debian 13 CTs,
dev channel): agents/PAT/drop-in wiring, PAT invisible to systemctl show and
the happier user, auto-update graceful degrade, Update action restarting relay
+ daemon with the UI bundle intact, server_only regression clean. Backlog D4
and all remediated rows closed. Upstream vendor findings (broken stable-channel
CLI build, 700-mode install dir, missing --auto-update flags) documented for
reporting to happier-dev/happier.

Co-Authored-By: Claude Fable 5 <[email protected]>
Update path (ct/happier.sh update_script) plus the installer's relay
reinstall, from the 2026-07-16 audit (A1/A2/A4/A5):

- A1: guard the relay `host install` reinstall. It runs under the ERR
  trap between the CLI self-update and the daemon restart; an unguarded
  failure aborted the whole update with the daemon left on the old CLI.
  Warn and continue instead.
- A2: capture the relay unit's enabled/active state before the reinstall
  and restore it after, so an autostart-disabled install is not silently
  re-enabled/started by the update.
- A4: post-check the relay unit in ensure_relay_host_installed and
  hard-fail if it is still missing after the recovery reinstall, instead
  of reporting success with no relay unit.
- A5: capture `relay host install --help` then grep a here-string rather
  than piping the CLI straight into `grep -q`; under `set -o pipefail`
  grep's early SIGPIPE (or a non-zero CLI exit) flipped the probe and
  silently disabled the user's --auto-update opt-in.

A3 (bare-arg reinstall dropping persisted --env) is held pending an LXC
update run: it depends on whether the vendor CLI preserves server.env.

Verified: shellcheck 0.11.0 --norc -S info, shfmt -i 2 -ci, and
tests/happier/run.sh all clean. Update-path behavior still pending an
LXC matrix smoke before push.

Co-Authored-By: Claude Opus 4.8 <[email protected]>
…ades

misc/happier-common.func install_managed_ui_bundle + tag resolver, from the
2026-07-16 audit (A6/A7/A8/B3/P1):

- A6: atomic `current` symlink swap — build the new link under a temp name and
  `mv -T` it over `current` in one rename(2), instead of `ln -sfn` (unlink then
  symlink, a window where `current` does not exist).
- A7: guard the refresh move-aside `mv` (refresh runs with errexit suspended, so
  an unguarded failure would nest the staging dir inside the live version dir and
  report success with stale content), and sweep stale `.tmp.*`/`.old.*` debris
  from interrupted prior runs.
- A8: buffer each tag attempt in its own file in resolve_happier_release_json_for_tags;
  writing straight to the caller's single stdout redirect let a partial failed
  attempt corrupt the JSON the next tag appended to, defeating the fallback.
- P1: read the installed version from the `current` symlink and short-circuit a
  same-version refresh before any download/verify/extract (was redone on every
  timer run, ~5-30s waste in the common no-op case).
- B3: refuse a downgrade to an older (still validly-signed) bundle via `sort -V`
  version binding — closes the signed-pair replay path (pairs with P1's read).

Tests: parametrize the fixture builder by version and add upgrade-swaps (A6/A7),
same-version-skips (P1), and downgrade-refused (B3) cases; UI suite now 10 tests.
Verified: shellcheck 0.11.0 --norc -S info, shfmt -i 2 -ci, tests/happier/run.sh
all clean. No LXC gate (fully sandboxed). A8's resolver stays stubbed in tests →
real exercise tracked as D7.

Co-Authored-By: Claude Opus 4.8 <[email protected]>
install/happier-install.sh write_update_helper, from the 2026-07-16 audit
(A9 + E1 part 1):

- A9: the generated /usr/bin/update piped `curl | bash` with no timeout, which
  can execute a truncated script if the transfer drops mid-stream. Download the
  fork's ct/happier.sh to a temp file with --retry/--connect-timeout/--max-time
  and run the file instead.
- E1 part 1: source /etc/profile.d/90-http-proxy.sh before updating so `update`
  keeps HTTP(S) proxy env on proxied networks. write_update_helper overwrites
  customize()'s /usr/bin/update, and had dropped the proxy sourcing customize()
  ships — proxied networks lost proxy env on update. Restores parity.

E1 part 2 (revert the now-dead misc/install.func update-line override to upstream
and drop install.func from CI PATCHED_FUNCS) is folded into the ci-guard epic
with F6 — same fork-patch-surface/CI concern. finish_install always overwrites
customize()'s helper in both install methods, so the override is confirmed dead.

Verified: shellcheck 0.11.0 --norc -S info + shfmt -i 2 -ci on the installer are
clean; the rendered helper parses and carries the proxy + retry/timeout hardening.

Co-Authored-By: Claude Opus 4.8 <[email protected]>
install/happier-install.sh, from the 2026-07-16 audit (B1/B2):

- B1: unset HAPPIER_PVE_GITHUB_PAT and HAPPIER_PVE_TAILSCALE_AUTHKEY after they
  are copied into locals. They arrive exported (from ct/happier.sh via build.func)
  and were never cleared, so every third-party root child spawned later — the
  claude.ai bootstrap, NodeSource, `npm install -g` lifecycle scripts — inherited
  the secrets in its environment. Confirmed no later reads of the exported names;
  the daemon PAT / tailscale key are consumed via the locals. sudo children were
  already protected by env_reset.
- B2: pin codex to `@openai/[email protected]` (verified current latest on npm today)
  instead of an unpinned `npm install -g @openai/codex`, which pulled whatever was
  latest and ran its lifecycle scripts as root. The prior audit's "pin at minimum"
  was never applied; this applies it.

Verified: shellcheck 0.11.0 --norc -S info + shfmt -i 2 -ci clean. No LXC gate.

Co-Authored-By: Claude Opus 4.8 <[email protected]>
…ride

.github/workflows/happier-lint.yml + misc/install.func, from the 2026-07-16
audit (E2 + M1 + E1 part 2):

- E2: add frontend/public/json/happier.json to the lint path triggers and add a
  `cmp` step that fails if the two installer JSONs drift. They are duplicated
  (byte-identical today) with no sync guard — the exact desync a prior audit had
  to fix by hand. CI now watches and enforces the frontend copy.
- M1: actions/checkout@v6 -> @v7.
- E1 part 2: revert misc/install.func's customize() /usr/bin/update line to the
  exact upstream literal. write_update_helper overwrites that file in both install
  methods, so the fork's INSTALLER_REPO override on this line was dead; reverting
  it lets the line merge cleanly on upstream syncs. install.func keeps its live
  core.func/error_handler.func/tools.func URL overrides, so it stays a patched
  file.

F6 (drop core.func from PATCHED_FUNCS) is NOT applied — it is a false positive:
`git diff` against current community-scripts shows core.func does carry a fork
patch (the cleanup_lxc timeout / DPkg::Lock::Timeout apt hardening) with no
comment marker, which the audit's marker-grep missed. It stays in PATCHED_FUNCS.
P4 (CI tool caching) is deferred — it cannot be runtime-verified without a GitHub
runner and is the lowest-value item.

Verified: YAML parses, the cmp guard passes on the current identical twins, and
`bash -n misc/install.func` is clean.

Co-Authored-By: Claude Opus 4.8 <[email protected]>
…retry sleep

ct/happier.sh + install/happier-install.sh, from the 2026-07-16 audit
(F1/E4/P3):

- F1: the Tailscale pre-auth-key passwordbox Cancel now skips
  (`|| HAPPIER_PVE_TAILSCALE_AUTHKEY=""`) instead of `|| exit_script`, so
  pressing Cancel no longer aborts the whole install. This matches the PAT
  prompt; the F7 fix had missed this one. Wording updated to say Cancel skips.
- E4: the installers-method epilogue now prints the captured TAILSCALE_AUTH_URL
  (lazy-resolved from `tailscale status --json` if enrollment never populated
  it), the same guidance the from_source epilogue already gives.
- P3: retry_until and resolve_tailscale_https_url_with_retries no longer sleep
  after the final failed attempt (up to ~45s saved, amplified inside the Serve
  loop). Uses an errexit-safe `if ((i < attempts))` guard.

P2 (Serve-loop early-bail + progress) is deferred: the no-HTTPS-capability
signal needs a live tailnet to detect without risking false bails on valid
setups, and the progress display is framework-spinner-sensitive.

Verified: shellcheck 0.11.0 --norc -S info + shfmt -i 2 -ci clean; a retry_until
timing check confirms 3 attempts sleep 2s not 3; tests/happier/run.sh green.

Co-Authored-By: Claude Opus 4.8 <[email protected]>
tests/happier/* + a small extract in install/happier-install.sh, from the
2026-07-16 audit (D3/D4/D10/D12/D13):

- D3: extract_function now matches the `function name()` keyword form, not just
  `name()`. This is what made ct/happier.sh's update_script un-extractable (0
  bytes) and therefore untestable; it now extracts (152 lines) and parses,
  unblocking D2.
- D4: run.sh fails a test file that emits no `# N tests` plan line, so a file
  that never calls run_tests can no longer pass silently (negative-tested).
- D10: run_tests no longer prints a second `ok` line for a skipped test (it
  tracked only the failed delta, not the skipped one).
- D12: added port-preserve and query/hash-drop assertions for
  normalize_https_public_url_or_empty.
- D13: extracted the daemon PAT charset gate into is_valid_github_pat (wired at
  the call site in write_daemon_env_dropin) and added unit tests for it plus
  retry_until timing (no sleep after the final attempt / on success).

Remaining test items are open: D1 (from_source matrix case) and D2's timer-name
E2E are LXC-gated; D5-D9 and D11 need more install-fn scaffolding.

Verified: shellcheck 0.11.0 --norc -S info + shfmt -i 2 -ci clean on the installer
and all test files; tests/happier/run.sh green (6+5+10+6, 1 skipped).

Co-Authored-By: Claude Opus 4.8 <[email protected]>
…ed note

From the 2026-07-16 audit (G2 + G3):

- G2: add a "Deviations (matrix hardening, 2026-07-02/03)" section to the design
  spec covering the three behaviors the 2026-07-01 implementation did not: the
  `--auto-update` capability probe, the baked update-helper INSTALLER_REPO/REF
  pins, and the relay-unit reconciliation reinstall.
- G3: the json unattended-install note now mentions PHS_SILENT=1 (needed for a
  fully unattended run alongside preset HAPPIER_PVE_* vars + mode=default).
  Applied to both json/happier.json and its frontend twin so they stay
  byte-identical (the new lint cmp guard enforces this).

G1 (stable-channel "recommended" label) stays open: it is needs-decision and
requires re-verifying the stable channel on the current vendor CLI in a live
container (LXC).

Verified: both json parse, meet the installer contract, contain the PHS_SILENT
note, and are byte-identical.

Co-Authored-By: Claude Opus 4.8 <[email protected]>
ct/happier.sh + install/happier-install.sh + misc/happier-common.func, from the
2026-07-16 audit (E3 + A10):

- E3: hoist the from_source stack layout default literals into common.func
  (HAPPIER_STACK_DEFAULT_BIN/HOME/LABEL/ENV). The ct/happier.sh legacy update
  branch and the installer's from_source path now reference the shared defaults,
  so `/home/happier/.happier-stack` and `dev.happier.stack` live in one place.
  Dropped the now-dead STACK_NAME chain — after centralizing the env default it
  was assign-only (its only read was the removed `${STACK_NAME}/env` derivation).
- A10: the login-shell HSTACK_BIN fallback now pipes through `grep -m1 '^/'` so
  MOTD/profile stdout noise from `bash -lc` cannot pollute the capture and cause
  a false "hstack not found" abort. The hardcoded-path fast path is unchanged.

Verified: shellcheck 0.11.0 --norc -S info + shfmt -i 2 -ci clean on all three
files; tests/happier/run.sh green.

Co-Authored-By: Claude Opus 4.8 <[email protected]>
Records the 2026-07-16 multi-dimension audit (40 kept findings) and the backlog
ledger with this session's remediation progress tracked per epic.

Closed (verified local: shellcheck 0.11.0/shfmt/tests): update-path A1/A2/A4/A5,
ui-bundle A6/A7/A8/B3/P1, update-helper A9 + E1, secrets B1/B2, ci-guard E2/M1,
tailscale-ux F1/E4/P3, test-harness D3/D4/D10/D12/D13, docs G2/G3, hstack E3/A10.

Not applied, with reasons in the backlog: F6 wontfix (false positive — core.func
carries an unmarked fork patch, must stay in PATCHED_FUNCS); P2/P4 deferred
(can't be verified without a live tailnet / GitHub runner). LXC-gated batch
(needs an unattended matrix run on pve-hp before push): A3, D1, D2 E2E, G1, plus
the update-path/tailscale behavior changes. Remaining test coverage open:
D5-D9, D11.

Co-Authored-By: Claude Opus 4.8 <[email protected]>
…C-verified)

ct/happier.sh update_script, from the 2026-07-16 audit (A3), confirmed live on
pve-hp: the bare `relay host install` reinstall the update runs resets the relay
env to the vendor defaults — it rewrites HAPPIER_SERVER_UI_DIR from the
installer's managed /var/lib/happier-dev/ui-web/current back to /opt/..., so the
freshly-refreshed UI bundle stops being served after every update.

Fix: re-pass the install-managed env on the reinstall — HAPPIER_SERVER_UI_DIR
from channel_ui_current_dir (when the UI is managed), plus HAPPIER_SERVER_HOST,
PORT, HAPPIER_PUBLIC_SERVER_URL and HAPPIER_WEBAPP_URL read back from the
persisted server.env.

The live update re-test caught two errexit hazards the first attempt introduced
(both abort under the update's `set -Eeuo pipefail`, and shellcheck flagged
neither): `[[ -n "$v" ]] && arr+=(...)` fails when the value is empty (proxy URLs
on a non-proxy install), and `v=$(grep|tail|cut)` fails when grep matches
nothing. Fixed with an `if` guard and a trailing `|| true`.

Verified end-to-end on a dev-channel devbox CT: broke server.env to /opt/...,
ran the fixed update → rc=0, UI_DIR restored to /var/lib/..., relay active. Also
locally: shellcheck 0.11.0 --norc -S info + shfmt -i 2 -ci clean, 27-test suite
green.

Co-Authored-By: Claude Opus 4.8 <[email protected]>
…verified)

From the 2026-07-16 audit (G1, needs-decision), resolved by re-verifying live:
a fresh stable-channel install on the current vendor CLI STILL fails — CLI and
UI install, then the relay host install aborts because the relay runtime never
becomes healthy (install exit 1), matching the 2026-07-03 finding. The vendor
stable build still cannot serve.

Per the default plan (warn if still broken):
- The channel prompt no longer labels stable "Stable (recommended)" /
  "recommended for production"; it now says "Stable (see note)" and the note
  explains the hosted stable build has been failing fresh installs.
- Preset/unattended installs skip that prompt, so install/happier-install.sh
  now emits a msg_warn when the resolved channel is stable, pointing at
  preview/dev.

Both carry a dated comment to remove once the vendor stable channel serves a
working build again. The underlying breakage is vendor-side (happier CLI), not
this repo.

Verified: shellcheck 0.11.0 --norc -S info + shfmt -i 2 -ci clean; test suite green.

Co-Authored-By: Claude Opus 4.8 <[email protected]>
Records the unattended LXC matrix on pve-hp that gated the update-path/tailscale
behavior changes and the LXC-gated findings.

Outcomes: dev-channel devbox install + update PASS (A1/A2/A4/A5/A6/E1/A9/B1
live-verified); A3 confirmed a real bug and fixed (bare reinstall reset
HAPPIER_SERVER_UI_DIR; re-pass fix verified end-to-end); G1 re-verified still
broken and fixed; D1 partial (from_source path exercised with no code defect,
full build memory-blocked on the loaded host); D2 partial (update path
validated, unit tests + vendor-blocked timer E2E remain); P2 untested (needs a
tailnet). Runner notes captured: the un-timeouted bootstrap fetch stalls under
rate-limit, and default-mode build.func needs storage/PAT/HOME presets headless.

No production impact — memory-guarded, one CT at a time, all matrix CTs
destroyed and host staging removed.

Co-Authored-By: Claude Opus 4.8 <[email protected]>
…rap fetch

Closes the remaining test-coverage backlog items (D5–D9, D11) and hardens the
host-side bootstrap fetch found during the LXC matrix.

Tests (suite 27 -> 49):
- D5 test_env_kv.sh: set_env_kv append-vs-update and the sed-escape round-trip
  for values with `| \ & /`, plus remove_env_kv multi-line + missing-file no-op.
- D8/D9 test_hstack_layout.sh: resolve_hstack_layout jq parse + workspace 3-way
  fallback via a fake `hstack where --json`; resolve_ui_extract_root flat/nested/
  missing index.html.
- D7 test_github_curl.sh: happier_github_curl token->Bearer `--config` vs anon
  headers + net opts (curl stubbed), and the resolve_happier_release_json_for_tags
  tag-fallback buffering (exercises the A8 fix for real).
- D6 test_enroll_tailscale.sh: enroll_tailscale_node outcome classification
  (no-key / invalid-key / timeout / online / offline-needs-login) via a fake
  `tailscale` binary and fast waiter stubs.
- D11: the missing-minisign test is now hermetic (a `command` override hides
  minisign) so it runs in CI instead of skipping when minisign is installed.

Fix: fetch_remote_script (ct/happier.sh) fetched build.func/happier-common.func
with a bare `curl -fsSL` and no timeout — a stalled/rate-limited connection hung
the install with no output (hit during the 2026-07-17 matrix). Added
`--retry 3 --retry-delay 2 --connect-timeout 15 --max-time 120`, matching
happier_github_curl and the generated update helper.

Verified: shellcheck 0.11.0 --norc -S info + shfmt -i 2 -ci clean;
tests/happier/run.sh green (49 tests, 1 skipped).

Co-Authored-By: Claude Opus 4.8 <[email protected]>
danljungstrom and others added 3 commits July 17, 2026 21:00
… (P4)

- D2: extract the update_script channel-detection loop into detect_installed_channel
  (misc/happier-common.func) — priority stable > preview > dev by which
  self-host-state.json exists. update_script now calls it (and still extracts
  cleanly via the D3 fix). New test_channel_detect.sh covers the priority order
  and the none-present case. The `-updater.timer`-name E2E leg stays vendor-blocked
  (the released CLI rejects --auto-update, so no timer is created to name-check),
  and the legacy from_source branch of update_script is warning-only.
- P4: .github/workflows/happier-lint.yml caches the pinned shellcheck/shfmt
  binaries with actions/cache keyed on the versions, into ~/.happier-lint-tools on
  $GITHUB_PATH. Sha256 is still verified on the download path (cache miss); bumping
  a *_VERSION rotates the key and forces a fresh verified download. Saves the
  per-run re-download; validates on the next CI run.

Verified: shellcheck 0.11.0 --norc -S info + shfmt -i 2 -ci clean, workflow YAML
parses, tests/happier/run.sh green (53 tests, 1 skipped).

Co-Authored-By: Claude Opus 4.8 <[email protected]>
A Codex pass over the session diff (a79bd52..HEAD) found three real defects;
all fixed here. It confirmed the A3 re-pass and the B1 unset are sound.

- A2: the autostart restore only handled disabled and running relays, so an
  enabled-but-manually-stopped relay was silently started by the update (the
  reinstall can start the unit). Restore enabled-vs-disabled and active-vs-inactive
  independently: re-disable if it was disabled, and restart if it was running else
  stop it.
- The generated /usr/bin/update helper `bash`ed the downloaded ct/happier.sh
  without checking it is non-empty, so an HTTP 200 with an empty body became a
  silent no-op "successful" update. Reject a zero-byte download with `[ -s ]`.
- The installer's inline happier-common.func bootstrap fetch was still a bare
  `curl -fsSL` with no timeout/retries (its own comment says it must track
  ct/happier.sh's fetch_remote_script). Added the same
  `--retry 3 --retry-delay 2 --connect-timeout 15 --max-time 120`.

Accepted (documented, not changed): test_refresh_newer_version_swaps asserts the
swap result, not `mv -T` atomicity — that rename(2) property can't be unit-tested
without a concurrent reader.

Verified: shellcheck 0.11.0 --norc -S info + shfmt -i 2 -ci clean; the rendered
/usr/bin/update carries the empty-body guard and parses; tests/happier/run.sh
green (53 tests).

Co-Authored-By: Claude Opus 4.8 <[email protected]>
…fied)

A dev-channel devbox install + update re-smoke on branch HEAD (04aa8b0), which
includes the commits the 2026-07-17 matrix predated (test coverage, the D2
detect_installed_channel refactor, P4, and the Codex-review fixes). Install rc=0;
update rc=0 with A3 (UI_DIR restored to /var/lib) and the Codex A2 fix (an
enabled-but-manually-stopped relay stayed enabled+inactive, not restarted) both
verified live. Closes the gap between the matrix run and the current branch tip.

Co-Authored-By: Claude Opus 4.8 <[email protected]>
@coderabbitai

coderabbitai Bot commented Jul 20, 2026

Copy link
Copy Markdown

Review Change Stack

📝 Walkthrough

Walkthrough

Happier installer and update flows now share channel, layout, fetch, Tailscale, and UI verification helpers. Devbox configuration gains agent, authentication, PAT, and auto-update options. CI validates scripts, JSON assets, and tests, with extensive Bash coverage and updated audit documentation.

Changes

Happier installer and update flow

Layer / File(s) Summary
Shared channel and UI helpers
misc/happier-common.func
Adds channel/path resolution, installed-channel detection, bounded GitHub fetching, CLI discovery, and minisign/SHA-256 verified UI bundle installation with install versus refresh failure behavior.
Installer provisioning and source setup
install/happier-install.sh
Adds validated configuration handling, bounded retries, Tailscale enrollment and Serve setup, devbox agent and daemon wiring, optional relay auto-update, pinned source setup, and persisted server settings.
Host update and configuration flow
ct/happier.sh
Adds pinned remote script fetching, channel-aware updates, UI and CLI refreshes, relay state preservation, daemon restarts, source-layout resolution, and non-interactive configuration exports.
Update helper behavior
misc/install.func
Changes generated /usr/bin/update to fetch the application script from a fixed upstream URL.
Configuration documentation
json/happier.json, frontend/public/json/happier.json
Documents unattended configuration, server ports, devbox extras, auto-update settings, and installer pinning.
CI checks and executable test coverage
.github/workflows/happier-lint.yml, tests/happier/*
Adds syntax, shellcheck, tool-integrity, JSON consistency/schema, and Happier test execution checks, plus focused Bash tests for shared helpers and installer behavior.
Audit and design records
docs/audits/*, docs/superpowers/*
Adds dated audit reports, remediation backlogs, LXC matrix results, implementation plans, and the devbox update design specification.

Estimated code review effort: 4 (Complex) | ~60 minutes

Sequence Diagram(s)

sequenceDiagram
  participant Operator
  participant HappierInstaller
  participant Tailscale
  participant RelayHost
  participant HappierDaemon

  Operator->>HappierInstaller: provide install configuration
  HappierInstaller->>Tailscale: enroll and enable Serve
  HappierInstaller->>RelayHost: install relay and optional auto-update
  HappierInstaller->>HappierDaemon: install agents and write environment drop-in
  HappierDaemon-->>Operator: expose configured daemon access
Loading

Possibly related PRs

🚥 Pre-merge checks | ✅ 3 | ❌ 2

❌ Failed checks (2 warnings)

Check name Status Explanation Resolution
Description check ⚠️ Warning The description covers the work and verification, but it misses required template sections like Related Issue, prerequisites, and type of change. Add the template sections: Description, Related Issue/Fixes #, prerequisites checkboxes, and type-of-change checkboxes.
Docstring Coverage ⚠️ Warning Docstring coverage is 21.77% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (3 passed)
Check name Status Explanation
Title check ✅ Passed The title clearly summarizes the main changes: devbox agents, daemon PAT, auto-update, and audit remediation.
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 unit tests (beta)
  • Create PR with unit tests

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.

@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: 7

🧹 Nitpick comments (3)
docs/audits/2026-06-25-audit.md (2)

72-73: 📐 Maintainability & Code Quality | 🔵 Trivial | 💤 Low value

Add a blank line before the table.

Markdown tables should be surrounded by blank lines for proper rendering.

💡 Proposed refactor
 **Conflict surface**
+
 | File | Upstream churn | Overlap with fork patch | Risk |
🤖 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/audits/2026-06-25-audit.md` around lines 72 - 73, Insert a blank line
between the “Conflict surface” heading and the Markdown table beginning with “|
File |”.

Source: Linters/SAST tools


92-94: 📐 Maintainability & Code Quality | 🔵 Trivial | 💤 Low value

Escape asterisks or use backticks for identifiers.

The unescaped asterisks in channel_* and resolve_* are being parsed as Markdown emphasis markers, which may break the formatting of the text. Wrapping them in backticks prevents this.

💡 Proposed refactor
-  - **Extract `misc/happier-common.func`** (dedup) → folds findings: github_curl (High), UI-bundle verify (High), channel_* (Med), resolve_* trio (Med), CLI resolver (Med), release-repo default (Low), restart_happier_unit (Low), + the minisign-warn consistency Medium and the two channel form-drift Lows. **Do during the sync re-apply** — write the happier files against the shared lib from the start.
+  - **Extract `misc/happier-common.func`** (dedup) → folds findings: github_curl (High), UI-bundle verify (High), `channel_*` (Med), `resolve_*` trio (Med), CLI resolver (Med), release-repo default (Low), restart_happier_unit (Low), + the minisign-warn consistency Medium and the two channel form-drift Lows. **Do during the sync re-apply** — write the happier files against the shared lib from the start.
🤖 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/audits/2026-06-25-audit.md` around lines 92 - 94, In the “Extract
misc/happier-common.func” bullet, wrap the wildcard identifiers channel_* and
resolve_* in backticks so their asterisks render literally instead of being
interpreted as Markdown emphasis.

Source: Linters/SAST tools

.github/workflows/happier-lint.yml (1)

58-58: 🔒 Security & Privacy | 🔵 Trivial | ⚡ Quick win

Disable credential persistence on checkout.

This job never pushes or needs authenticated git after checkout; persist-credentials: false avoids leaving the token in the local git config for the rest of the job.

🔒️ Proposed fix
       - uses: actions/checkout@v7
+        with:
+          persist-credentials: false
🤖 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/happier-lint.yml at line 58, Update the actions/checkout
step to disable credential persistence by setting persist-credentials to false,
while leaving the existing checkout behavior unchanged.

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 `@ct/happier.sh`:
- Around line 328-335: Update the interactive proxy branch in the remote-access
prompt to reject an empty `HAPPIER_PVE_PUBLIC_URL` and re-prompt the user
instead of executing `break`. Preserve the existing `whiptail` cancellation
behavior through `exit_script`, and only leave the loop once a non-empty URL has
been entered.

In `@docs/audits/2026-07-03-lxc-matrix.md`:
- Around line 14-18: The auto-update timer verification is vendor-blocked
because released CLIs reject the required flags. In
docs/audits/2026-07-03-lxc-matrix.md lines 14-18, revise Case 3 to explicitly
indicate partial verification/vendor blockage, retaining the verified
graceful-degradation and disabled-path results. In
docs/superpowers/plans/2026-06-28-happier-devbox-agents-autoupdate.md lines
356-361, mark the timer end-to-end test as unverified/vendor-blocked rather than
confirmed.

In `@docs/audits/2026-07-17-lxc-matrix.md`:
- Around line 15-20: Update the GitHub-raw rate-limiting note near
fetch_remote_script to identify it as an observation from the pre-hardening
initial run, before retry and timeout flags were added to both bootstrap
fetches. Preserve the documented workaround and distinguish this historical
behavior from the later 2026-07-20 HEAD re-smoke.

In `@docs/superpowers/plans/2026-06-28-happier-devbox-agents-autoupdate.md`:
- Around line 5-20: Update the completed plan sections covering Tasks 1–7,
including the referenced range, to accurately document the shipped
implementation: the 53-test suite, native Claude installation, on-demand Node
setup, pinned Codex version, EnvironmentFile-based PAT delivery, validation, and
explicit error reporting. Replace obsolete claims about no test harness,
npm-global Claude installation, preinstalled Node, inline Environment=
configuration, and blanket || true handling; preserve the remaining task
structure and mark only the actual outstanding integration matrix and PR work as
incomplete.

In `@install/happier-install.sh`:
- Around line 356-365: Update both Tailscale repository curl calls in the key
and apt source list download blocks to include the same retry, connect-timeout,
and max-time bounds used by happier_github_curl and the happier-common.func
fetch block, while preserving their existing failure checks and cleanup
behavior.

In `@misc/install.func`:
- Line 517: Update the generated /usr/bin/update helper in the installer flow so
its curl URL uses the install-time INSTALLER_REPO and INSTALLER_REF values as
baked-in defaults instead of hardcoding community-scripts/ProxmoxVE/main.
Preserve runtime environment overrides by allowing existing INSTALLER_REPO and
INSTALLER_REF values in the update shell to take precedence.

In `@tests/happier/test_ui_bundle_verify.sh`:
- Around line 227-258: Update test_real_minisign_good_and_bad_signature so the
tampered checksums file is associated with a newly advertised version before
calling refresh. Bump the fixture VERSION and recompute
ARCHIVE_NAME/CHECKSUMS_NAME (using the existing fixture/version setup helpers)
after the successful install and before tampering, ensuring refresh cannot take
the same-version skip path and must perform signature verification.

---

Nitpick comments:
In @.github/workflows/happier-lint.yml:
- Line 58: Update the actions/checkout step to disable credential persistence by
setting persist-credentials to false, while leaving the existing checkout
behavior unchanged.

In `@docs/audits/2026-06-25-audit.md`:
- Around line 72-73: Insert a blank line between the “Conflict surface” heading
and the Markdown table beginning with “| File |”.
- Around line 92-94: In the “Extract misc/happier-common.func” bullet, wrap the
wildcard identifiers channel_* and resolve_* in backticks so their asterisks
render literally instead of being interpreted as Markdown emphasis.
🪄 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: eaaf3829-b270-4035-9cf8-7ace3f7986ae

📥 Commits

Reviewing files that changed from the base of the PR and between ab4bd35 and cdd3fdf.

📒 Files selected for processing (30)
  • .github/workflows/happier-lint.yml
  • ct/happier.sh
  • docs/audits/2026-06-24-audit.md
  • docs/audits/2026-06-24-backlog.md
  • docs/audits/2026-06-25-audit.md
  • docs/audits/2026-06-25-backlog.md
  • docs/audits/2026-07-01-audit.md
  • docs/audits/2026-07-01-backlog.md
  • docs/audits/2026-07-03-lxc-matrix.md
  • docs/audits/2026-07-16-audit.md
  • docs/audits/2026-07-16-backlog.md
  • docs/audits/2026-07-17-lxc-matrix.md
  • docs/superpowers/plans/2026-06-28-happier-devbox-agents-autoupdate.md
  • docs/superpowers/specs/2026-06-27-happier-installer-devbox-updates-design.md
  • frontend/public/json/happier.json
  • install/happier-install.sh
  • json/happier.json
  • misc/happier-common.func
  • misc/install.func
  • tests/happier/lib.sh
  • tests/happier/run.sh
  • tests/happier/test_channel_detect.sh
  • tests/happier/test_channel_helpers.sh
  • tests/happier/test_enroll_tailscale.sh
  • tests/happier/test_env_kv.sh
  • tests/happier/test_github_curl.sh
  • tests/happier/test_hstack_layout.sh
  • tests/happier/test_retry_helpers.sh
  • tests/happier/test_ui_bundle_verify.sh
  • tests/happier/test_url_time_validation.sh

Comment thread ct/happier.sh
Comment on lines +328 to 335
if [[ "${HAPPIER_PVE_REMOTE_ACCESS}" == "proxy" ]]; then
HAPPIER_PVE_PUBLIC_URL=$(
whiptail --backtitle "$BACKTITLE" --title "CUSTOM HTTPS URL" --inputbox \
whiptail --backtitle "${BACKTITLE}" --title "CUSTOM HTTPS URL" --inputbox \
"\nEnter the HTTPS URL of your reverse proxy.\n\nExample:\n https://happier.example.com\n\nThis URL will be embedded in QR codes/deep links and must be reachable from your phone.\n" 18 72 \
3>&1 1>&2 2>&3
) || exit_script
break
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.

🎯 Functional Correctness | 🟡 Minor | ⚡ Quick win

Interactive proxy URL isn't validated for emptiness.

The preset path enforces a non-empty HAPPIER_PVE_PUBLIC_URL for proxy mode (Lines 312-315), but the interactive branch accepts whatever whiptail returns and breaks. An empty value (blank inputbox + OK) leaves REMOTE_ACCESS=proxy with no URL, producing a broken relay config (HAPPIER_PUBLIC_SERVER_URL=) that's hard to diagnose. Consider re-prompting until non-empty.

🛡️ Proposed fix
     if [[ "${HAPPIER_PVE_REMOTE_ACCESS}" == "proxy" ]]; then
-      HAPPIER_PVE_PUBLIC_URL=$(
-        whiptail --backtitle "${BACKTITLE}" --title "CUSTOM HTTPS URL" --inputbox \
-          "\nEnter the HTTPS URL of your reverse proxy.\n\nExample:\n  https://happier.example.com\n\nThis URL will be embedded in QR codes/deep links and must be reachable from your phone.\n" 18 72 \
-          3>&1 1>&2 2>&3
-      ) || exit_script
-      break
+      HAPPIER_PVE_PUBLIC_URL=$(
+        whiptail --backtitle "${BACKTITLE}" --title "CUSTOM HTTPS URL" --inputbox \
+          "\nEnter the HTTPS URL of your reverse proxy.\n\nExample:\n  https://happier.example.com\n\nThis URL will be embedded in QR codes/deep links and must be reachable from your phone.\n" 18 72 \
+          3>&1 1>&2 2>&3
+      ) || exit_script
+      if [[ -z "${HAPPIER_PVE_PUBLIC_URL// /}" ]]; then
+        whiptail --backtitle "${BACKTITLE}" --title "CUSTOM HTTPS URL" --msgbox "\nA proxy URL is required for proxy mode.\n" 10 60
+        continue
+      fi
+      break
     fi
📝 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 [[ "${HAPPIER_PVE_REMOTE_ACCESS}" == "proxy" ]]; then
HAPPIER_PVE_PUBLIC_URL=$(
whiptail --backtitle "$BACKTITLE" --title "CUSTOM HTTPS URL" --inputbox \
whiptail --backtitle "${BACKTITLE}" --title "CUSTOM HTTPS URL" --inputbox \
"\nEnter the HTTPS URL of your reverse proxy.\n\nExample:\n https://happier.example.com\n\nThis URL will be embedded in QR codes/deep links and must be reachable from your phone.\n" 18 72 \
3>&1 1>&2 2>&3
) || exit_script
break
fi
if [[ "${HAPPIER_PVE_REMOTE_ACCESS}" == "proxy" ]]; then
HAPPIER_PVE_PUBLIC_URL=$(
whiptail --backtitle "${BACKTITLE}" --title "CUSTOM HTTPS URL" --inputbox \
"\nEnter the HTTPS URL of your reverse proxy.\n\nExample:\n https://happier.example.com\n\nThis URL will be embedded in QR codes/deep links and must be reachable from your phone.\n" 18 72 \
3>&1 1>&2 2>&3
) || exit_script
if [[ -z "${HAPPIER_PVE_PUBLIC_URL// /}" ]]; then
whiptail --backtitle "${BACKTITLE}" --title "CUSTOM HTTPS URL" --msgbox "\nA proxy URL is required for proxy mode.\n" 10 60
continue
fi
break
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 `@ct/happier.sh` around lines 328 - 335, Update the interactive proxy branch in
the remote-access prompt to reject an empty `HAPPIER_PVE_PUBLIC_URL` and
re-prompt the user instead of executing `break`. Preserve the existing
`whiptail` cancellation behavior through `exit_script`, and only leave the loop
once a non-empty URL has been entered.

Comment on lines +14 to +18
| 1 | devbox, agents ON, no PAT | 103 happier-mxa | PASS 16/16 — claude (native installer, happier user) + codex resolve; drop-in 600 with both `*_PATH` keys, no PAT wiring; relay active + `/v1/version` answers; UI bundle installed and served |
| 2 | devbox, agents OFF, PAT via env | 104 happier-mxb | PASS — no agents; drop-in 600 with `EnvironmentFile=` only; PAT in 600 env file; **PAT absent from `systemctl show -p Environment` and unreadable by the happier user** (B1 verified live); relay active |
| 3 | auto-update ON / OFF | 103 / 104 | PASS with vendor caveat — current CLIs reject `--auto-update`; installer degrades gracefully (warn, no timer). OFF: no timer. Timer activation itself is pending a vendor CLI release with the flags |
| 4 | Update action | 103 | PASS — rc=0; relay restarted (PID 5930→7992) and healthy; daemon active after update; UI bundle same-version refresh left `current` intact (A6 fix verified); requires the json-declared 4 CPU/8192MB (2/4096 aborts by design) |
| 5 | server_only regression | 108 happier-mxc | PASS 9/9 — no agents, no node, no drop-in, no daemon unit, no timer; relay active; update helper present |

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

Align all verification records with the vendor-blocked timer test.

The auto-update-on timer was not validated because released CLIs rejected the flags; only graceful degradation and the disabled path were verified.

  • docs/audits/2026-07-03-lxc-matrix.md#L14-L18: change Case 3 from PASS to partial/vendor-blocked.
  • docs/superpowers/plans/2026-06-28-happier-devbox-agents-autoupdate.md#L356-L361: mark the timer E2E as unverified/vendor-blocked rather than confirmed.
📍 Affects 2 files
  • docs/audits/2026-07-03-lxc-matrix.md#L14-L18 (this comment)
  • docs/superpowers/plans/2026-06-28-happier-devbox-agents-autoupdate.md#L356-L361
🤖 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/audits/2026-07-03-lxc-matrix.md` around lines 14 - 18, The auto-update
timer verification is vendor-blocked because released CLIs reject the required
flags. In docs/audits/2026-07-03-lxc-matrix.md lines 14-18, revise Case 3 to
explicitly indicate partial verification/vendor blockage, retaining the verified
graceful-degradation and disabled-path results. In
docs/superpowers/plans/2026-06-28-happier-devbox-agents-autoupdate.md lines
356-361, mark the timer end-to-end test as unverified/vendor-blocked rather than
confirmed.

Comment on lines +15 to +20
- **GitHub-raw rate-limiting** from repeated attempts made ct/happier.sh's bootstrap fetch hang.
`fetch_remote_script` (ct/happier.sh:18) uses a bare `curl -fsSL` with **no `--max-time`/
`--connect-timeout`**, so a throttled/stalled connection hangs the install indefinitely with no
output. Latent robustness gap (same class as the A9 hardening) — noted, not fixed this session.
Worked around by staging the fork files on the host and sourcing build.func/happier-common.func
from local copies.

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

Qualify this as a pre-hardening harness observation.

The later remediation records that both bootstrap fetches gained retry and timeout flags, while this file also documents a 2026-07-20 HEAD re-smoke. State that lines 15–20 describe the initial run before that fix, or update the note to the current behavior.

🤖 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/audits/2026-07-17-lxc-matrix.md` around lines 15 - 20, Update the
GitHub-raw rate-limiting note near fetch_remote_script to identify it as an
observation from the pre-hardening initial run, before retry and timeout flags
were added to both bootstrap fetches. Preserve the documented workaround and
distinguish this historical behavior from the later 2026-07-20 HEAD re-smoke.

Comment on lines +5 to +20
**Goal:** Make a Happier `devbox` install produce a working agent daemon (claude/codex wired, optionally installed) and make updates hands-off (opt-in auto-update timer) and complete (daemon restarted on update).

**Architecture:** Host-side `ct/happier.sh:app_questions` collects new toggles/secret and exports them as `HAPPIER_PVE_*` env (inherited into the container via the existing lxc-attach passthrough). In-container `install/happier-install.sh` reads them: appends `--auto-update` flags to the managed relay install, installs the agent CLIs, and writes a `chmod 600` systemd drop-in with the daemon env. `ct/happier.sh:update_script` gains a daemon restart.

**Tech Stack:** Bash (Proxmox community-scripts helper model), whiptail, systemd drop-ins, npm-global agent installs. No unit-test harness in this domain — verification per task is `bash -n` + `shellcheck` (the repo's `happier-lint` workflow) plus the LXC integration matrix from the spec (`docs/superpowers/specs/2026-06-27-happier-installer-devbox-updates-design.md`).

> **Status (2026-07-01):** Tasks 1-7 implemented and committed on `feat/devbox-agents-autoupdate`
> (landed as the "devbox agents, daemon PAT, and opt-in auto-update" feature commit after the
> branch history was regrouped). Deviation from Task 3:
> Node 24 is NOT pre-present on the installers path — `install_devbox_agents` installs it on
> demand (guarded, non-fatal) instead of only warning when npm is missing. Remaining: the LXC
> integration matrix and the eventual PR (unticked below).

**Branch:** `feat/devbox-agents-autoupdate` (already created off `daemon-auth`; the spec is committed there).

**Conventions to follow (existing in these files):** `msg_info`/`msg_ok`/`msg_warn` for output; `$STD` to silence verbose cmds; `sudo -u happier -H` for happier-user actions; `printf` (never heredoc) for writing secret-bearing files; `chmod 600` for secrets; best-effort steps guarded with `|| true` so the ERR trap doesn't abort.

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

Refresh the completed plan to match the shipped implementation.

The checked plan still describes no test harness, npm-global Claude installation, preinstalled Node, inline Environment= PAT delivery, and blanket || true handling. The implementation now uses a 53-test suite, native Claude installation, on-demand Node, pinned Codex, EnvironmentFile=, validation, and explicit error reporting. Leaving the old snippets marked complete makes this plan an unsafe maintenance reference.

Also applies to: 104-185

🤖 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/superpowers/plans/2026-06-28-happier-devbox-agents-autoupdate.md` around
lines 5 - 20, Update the completed plan sections covering Tasks 1–7, including
the referenced range, to accurately document the shipped implementation: the
53-test suite, native Claude installation, on-demand Node setup, pinned Codex
version, EnvironmentFile-based PAT delivery, validation, and explicit error
reporting. Replace obsolete claims about no test harness, npm-global Claude
installation, preinstalled Node, inline Environment= configuration, and blanket
|| true handling; preserve the remaining task structure and mark only the actual
outstanding integration matrix and PR work as incomplete.

Comment on lines +356 to +365
if ! curl -fsSL "${base}.noarmor.gpg" -o "${keyring_tmp}" || [[ ! -s "${keyring_tmp}" ]]; then
rm -f "${keyring_tmp}" "${list_tmp}"
msg_error "Failed to download the Tailscale signing key for ${os_id}/${os_codename}."
return 1
fi
return 1
if ! curl -fsSL "${base}.tailscale-keyring.list" -o "${list_tmp}" || [[ ! -s "${list_tmp}" ]]; then
rm -f "${keyring_tmp}" "${list_tmp}"
msg_error "Failed to download the Tailscale apt source list for ${os_id}/${os_codename}."
return 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

Bound the Tailscale repo curl calls like the rest of the PR.

These two curl calls omit the --retry/--connect-timeout/--max-time flags applied everywhere else in this change (e.g. happier_github_curl and the happier-common.func fetch block). Since curl has no default timeout, a stalled connection to pkgs.tailscale.com hangs the install indefinitely — the exact failure mode the PR set out to eliminate.

🛡️ Proposed fix
-  if ! curl -fsSL "${base}.noarmor.gpg" -o "${keyring_tmp}" || [[ ! -s "${keyring_tmp}" ]]; then
+  if ! curl -fsSL --retry 3 --retry-delay 2 --connect-timeout 15 --max-time 120 "${base}.noarmor.gpg" -o "${keyring_tmp}" || [[ ! -s "${keyring_tmp}" ]]; then
     rm -f "${keyring_tmp}" "${list_tmp}"
     msg_error "Failed to download the Tailscale signing key for ${os_id}/${os_codename}."
     return 1
   fi
-  if ! curl -fsSL "${base}.tailscale-keyring.list" -o "${list_tmp}" || [[ ! -s "${list_tmp}" ]]; then
+  if ! curl -fsSL --retry 3 --retry-delay 2 --connect-timeout 15 --max-time 120 "${base}.tailscale-keyring.list" -o "${list_tmp}" || [[ ! -s "${list_tmp}" ]]; then
     rm -f "${keyring_tmp}" "${list_tmp}"
     msg_error "Failed to download the Tailscale apt source list for ${os_id}/${os_codename}."
     return 1
   fi
📝 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 ! curl -fsSL "${base}.noarmor.gpg" -o "${keyring_tmp}" || [[ ! -s "${keyring_tmp}" ]]; then
rm -f "${keyring_tmp}" "${list_tmp}"
msg_error "Failed to download the Tailscale signing key for ${os_id}/${os_codename}."
return 1
fi
return 1
if ! curl -fsSL "${base}.tailscale-keyring.list" -o "${list_tmp}" || [[ ! -s "${list_tmp}" ]]; then
rm -f "${keyring_tmp}" "${list_tmp}"
msg_error "Failed to download the Tailscale apt source list for ${os_id}/${os_codename}."
return 1
fi
if ! curl -fsSL --retry 3 --retry-delay 2 --connect-timeout 15 --max-time 120 "${base}.noarmor.gpg" -o "${keyring_tmp}" || [[ ! -s "${keyring_tmp}" ]]; then
rm -f "${keyring_tmp}" "${list_tmp}"
msg_error "Failed to download the Tailscale signing key for ${os_id}/${os_codename}."
return 1
fi
if ! curl -fsSL --retry 3 --retry-delay 2 --connect-timeout 15 --max-time 120 "${base}.tailscale-keyring.list" -o "${list_tmp}" || [[ ! -s "${list_tmp}" ]]; then
rm -f "${keyring_tmp}" "${list_tmp}"
msg_error "Failed to download the Tailscale apt source list for ${os_id}/${os_codename}."
return 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 `@install/happier-install.sh` around lines 356 - 365, Update both Tailscale
repository curl calls in the key and apt source list download blocks to include
the same retry, connect-timeout, and max-time bounds used by happier_github_curl
and the happier-common.func fetch block, while preserving their existing failure
checks and cleanup behavior.

Comment thread misc/install.func
msg_ok "Customized Container"
fi
echo 'set -a; [ -f /etc/profile.d/90-http-proxy.sh ] && . /etc/profile.d/90-http-proxy.sh; set +a; bash -c "$(curl -fsSL https://raw.githubusercontent.com/'"${INSTALLER_REPO:-community-scripts/ProxmoxVE}/${INSTALLER_REF:-main}"'/ct/'"${app}"'.sh)"' >/usr/bin/update
echo 'set -a; [ -f /etc/profile.d/90-http-proxy.sh ] && . /etc/profile.d/90-http-proxy.sh; set +a; bash -c "$(curl -fsSL https://raw.githubusercontent.com/community-scripts/ProxmoxVE/main/ct/'"${app}"'.sh)"' >/usr/bin/update

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

🗄️ Data Integrity & Integration | 🔴 Critical | ⚡ Quick win

Bake INSTALLER_REPO and INSTALLER_REF into the generated update helper.

The generated /usr/bin/update helper hardcodes community-scripts/ProxmoxVE/main. This contradicts the design specification and the JSON documentation, which explicitly state that the update helper must honor the install-time pins to prevent silently pulling from the unpinned upstream main branch.

To align with the design spec (which requires the values to be baked in as defaults but still allow environment overrides at update time), inject the variables into the generated script.

🐛 Proposed fix
-  echo 'set -a; [ -f /etc/profile.d/90-http-proxy.sh ] && . /etc/profile.d/90-http-proxy.sh; set +a; bash -c "$(curl -fsSL https://raw.githubusercontent.com/community-scripts/ProxmoxVE/main/ct/'"${app}"'.sh)"' >/usr/bin/update
+  echo 'set -a; [ -f /etc/profile.d/90-http-proxy.sh ] && . /etc/profile.d/90-http-proxy.sh; set +a; INSTALLER_REPO="${INSTALLER_REPO:-'"${INSTALLER_REPO}"'}"; INSTALLER_REF="${INSTALLER_REF:-'"${INSTALLER_REF}"'}"; bash -c "$(curl -fsSL https://raw.githubusercontent.com/${INSTALLER_REPO}/${INSTALLER_REF}/ct/'"${app}"'.sh)"' >/usr/bin/update
📝 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
echo 'set -a; [ -f /etc/profile.d/90-http-proxy.sh ] && . /etc/profile.d/90-http-proxy.sh; set +a; bash -c "$(curl -fsSL https://raw.githubusercontent.com/community-scripts/ProxmoxVE/main/ct/'"${app}"'.sh)"' >/usr/bin/update
echo 'set -a; [ -f /etc/profile.d/90-http-proxy.sh ] && . /etc/profile.d/90-http-proxy.sh; set +a; INSTALLER_REPO="${INSTALLER_REPO:-'"${INSTALLER_REPO}"'}"; INSTALLER_REF="${INSTALLER_REF:-'"${INSTALLER_REF}"'}"; bash -c "$(curl -fsSL https://raw.githubusercontent.com/${INSTALLER_REPO}/${INSTALLER_REF}/ct/'"${app}"'.sh)"' >/usr/bin/update
🤖 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 `@misc/install.func` at line 517, Update the generated /usr/bin/update helper
in the installer flow so its curl URL uses the install-time INSTALLER_REPO and
INSTALLER_REF values as baked-in defaults instead of hardcoding
community-scripts/ProxmoxVE/main. Preserve runtime environment overrides by
allowing existing INSTALLER_REPO and INSTALLER_REF values in the update shell to
take precedence.

Comment on lines +227 to +258
test_real_minisign_good_and_bad_signature() {
drop_fake_minisign
if ! command -v minisign >/dev/null 2>&1; then
skip "minisign not installed (CI installs it)"
return
fi
make_fixtures
local keydir="${SANDBOX}/keys"
mkdir -p "${keydir}"
minisign -f -G -W -p "${keydir}/test.pub" -s "${keydir}/test.key" >/dev/null 2>&1 || {
fail "could not generate a test minisign keypair"
return
}
minisign -S -W -s "${keydir}/test.key" -m "${FIXTURE_DIR}/${CHECKSUMS_NAME}" \
-x "${FIXTURE_DIR}/${CHECKSUMS_NAME}.minisig" >/dev/null 2>&1 || {
fail "could not sign the fixture checksums"
return
}
# shellcheck disable=SC2034 # consumed by the sourced install_managed_ui_bundle
MINISIGN_PUBKEY="$(cat "${keydir}/test.pub")"
local out=""
out="$(install_managed_ui_bundle stable install)" || {
fail "install failed with a genuine valid signature"
return
}
assert_ok "current symlink resolves (real sig)" test -f "$(channel_ui_current_dir stable)/index.html"
# Now corrupt the signed file: signature must no longer verify.
echo "# drift" >>"${FIXTURE_DIR}/${CHECKSUMS_NAME}"
if install_managed_ui_bundle stable refresh >/dev/null 2>&1; then
fail "refresh succeeded although the checksums file no longer matches its signature"
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.

🎯 Functional Correctness | 🔴 Critical | ⚡ Quick win

Test is invalidated by the P1 same-version-skip short-circuit — this is the CI failure.

test_real_minisign_good_and_bad_signature installs version 9.9.9 (247-251), then tampers ${CHECKSUMS_NAME} for that same version (254) and calls refresh (255) expecting rejection. But this file's own P1 tests document that install_managed_ui_bundle ... refresh short-circuits — skipping download/verification entirely — whenever the already-installed version matches the remote-advertised version (see test_refresh_same_version_skips_download, Lines 148-163, and the comment on test_install_happy_path_and_same_version_refresh, Lines 114-116). Since VERSION/ARCHIVE_NAME/CHECKSUMS_NAME are never bumped before the tamper, the tampered checksums file is for the exact version already installed, so refresh never re-verifies it — it isn't a signature-verification bug, the verification path is simply never reached.

Fix by advertising a new version before tampering, so the refresh is forced through the real verify path.

🐛 Proposed fix
   out="$(install_managed_ui_bundle stable install)" || {
     fail "install failed with a genuine valid signature"
     return
   }
   assert_ok "current symlink resolves (real sig)" test -f "$(channel_ui_current_dir stable)/index.html"
-  # Now corrupt the signed file: signature must no longer verify.
-  echo "# drift" >>"${FIXTURE_DIR}/${CHECKSUMS_NAME}"
+  # Advertise a new version so refresh doesn't hit the P1 same-version
+  # short-circuit (see test_refresh_same_version_skips_download); re-sign it
+  # with the real test key, then corrupt it so verification must fail.
+  build_release_fixture "9.9.11"
+  minisign -S -W -s "${keydir}/test.key" -m "${FIXTURE_DIR}/${CHECKSUMS_NAME}" \
+    -x "${FIXTURE_DIR}/${CHECKSUMS_NAME}.minisig" >/dev/null 2>&1 || {
+    fail "could not re-sign the bumped-version fixture checksums"
+    return
+  }
+  echo "# drift" >>"${FIXTURE_DIR}/${CHECKSUMS_NAME}"
   if install_managed_ui_bundle stable refresh >/dev/null 2>&1; then
     fail "refresh succeeded although the checksums file no longer matches its signature"
   fi
📝 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
test_real_minisign_good_and_bad_signature() {
drop_fake_minisign
if ! command -v minisign >/dev/null 2>&1; then
skip "minisign not installed (CI installs it)"
return
fi
make_fixtures
local keydir="${SANDBOX}/keys"
mkdir -p "${keydir}"
minisign -f -G -W -p "${keydir}/test.pub" -s "${keydir}/test.key" >/dev/null 2>&1 || {
fail "could not generate a test minisign keypair"
return
}
minisign -S -W -s "${keydir}/test.key" -m "${FIXTURE_DIR}/${CHECKSUMS_NAME}" \
-x "${FIXTURE_DIR}/${CHECKSUMS_NAME}.minisig" >/dev/null 2>&1 || {
fail "could not sign the fixture checksums"
return
}
# shellcheck disable=SC2034 # consumed by the sourced install_managed_ui_bundle
MINISIGN_PUBKEY="$(cat "${keydir}/test.pub")"
local out=""
out="$(install_managed_ui_bundle stable install)" || {
fail "install failed with a genuine valid signature"
return
}
assert_ok "current symlink resolves (real sig)" test -f "$(channel_ui_current_dir stable)/index.html"
# Now corrupt the signed file: signature must no longer verify.
echo "# drift" >>"${FIXTURE_DIR}/${CHECKSUMS_NAME}"
if install_managed_ui_bundle stable refresh >/dev/null 2>&1; then
fail "refresh succeeded although the checksums file no longer matches its signature"
fi
}
test_real_minisign_good_and_bad_signature() {
drop_fake_minisign
if ! command -v minisign >/dev/null 2>&1; then
skip "minisign not installed (CI installs it)"
return
fi
make_fixtures
local keydir="${SANDBOX}/keys"
mkdir -p "${keydir}"
minisign -f -G -W -p "${keydir}/test.pub" -s "${keydir}/test.key" >/dev/null 2>&1 || {
fail "could not generate a test minisign keypair"
return
}
minisign -S -W -s "${keydir}/test.key" -m "${FIXTURE_DIR}/${CHECKSUMS_NAME}" \
-x "${FIXTURE_DIR}/${CHECKSUMS_NAME}.minisig" >/dev/null 2>&1 || {
fail "could not sign the fixture checksums"
return
}
# shellcheck disable=SC2034 # consumed by the sourced install_managed_ui_bundle
MINISIGN_PUBKEY="$(cat "${keydir}/test.pub")"
local out=""
out="$(install_managed_ui_bundle stable install)" || {
fail "install failed with a genuine valid signature"
return
}
assert_ok "current symlink resolves (real sig)" test -f "$(channel_ui_current_dir stable)/index.html"
# Advertise a new version so refresh doesn't hit the P1 same-version
# short-circuit (see test_refresh_same_version_skips_download); re-sign it
# with the real test key, then corrupt it so verification must fail.
build_release_fixture "9.9.11"
minisign -S -W -s "${keydir}/test.key" -m "${FIXTURE_DIR}/${CHECKSUMS_NAME}" \
-x "${FIXTURE_DIR}/${CHECKSUMS_NAME}.minisig" >/dev/null 2>&1 || {
fail "could not re-sign the bumped-version fixture checksums"
return
}
echo "# drift" >>"${FIXTURE_DIR}/${CHECKSUMS_NAME}"
if install_managed_ui_bundle stable refresh >/dev/null 2>&1; then
fail "refresh succeeded although the checksums file no longer matches its signature"
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 `@tests/happier/test_ui_bundle_verify.sh` around lines 227 - 258, Update
test_real_minisign_good_and_bad_signature so the tampered checksums file is
associated with a newly advertised version before calling refresh. Bump the
fixture VERSION and recompute ARCHIVE_NAME/CHECKSUMS_NAME (using the existing
fixture/version setup helpers) after the successful install and before
tampering, ensuring refresh cannot take the same-version skip path and must
perform signature verification.

Source: Pipeline failures

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