feat(happier): devbox agents, daemon PAT, opt-in auto-update + 2026-07 audit remediation#6
Conversation
…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]>
… (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]>
📝 WalkthroughWalkthroughHappier 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. ChangesHappier installer and update flow
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
Possibly related PRs
🚥 Pre-merge checks | ✅ 3 | ❌ 2❌ Failed checks (2 warnings)
✅ Passed checks (3 passed)
✨ Finishing Touches🧪 Generate unit tests (beta)
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. Comment |
There was a problem hiding this comment.
Actionable comments posted: 7
🧹 Nitpick comments (3)
docs/audits/2026-06-25-audit.md (2)
72-73: 📐 Maintainability & Code Quality | 🔵 Trivial | 💤 Low valueAdd 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 valueEscape asterisks or use backticks for identifiers.
The unescaped asterisks in
channel_*andresolve_*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 winDisable credential persistence on checkout.
This job never pushes or needs authenticated git after checkout;
persist-credentials: falseavoids 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
📒 Files selected for processing (30)
.github/workflows/happier-lint.ymlct/happier.shdocs/audits/2026-06-24-audit.mddocs/audits/2026-06-24-backlog.mddocs/audits/2026-06-25-audit.mddocs/audits/2026-06-25-backlog.mddocs/audits/2026-07-01-audit.mddocs/audits/2026-07-01-backlog.mddocs/audits/2026-07-03-lxc-matrix.mddocs/audits/2026-07-16-audit.mddocs/audits/2026-07-16-backlog.mddocs/audits/2026-07-17-lxc-matrix.mddocs/superpowers/plans/2026-06-28-happier-devbox-agents-autoupdate.mddocs/superpowers/specs/2026-06-27-happier-installer-devbox-updates-design.mdfrontend/public/json/happier.jsoninstall/happier-install.shjson/happier.jsonmisc/happier-common.funcmisc/install.functests/happier/lib.shtests/happier/run.shtests/happier/test_channel_detect.shtests/happier/test_channel_helpers.shtests/happier/test_enroll_tailscale.shtests/happier/test_env_kv.shtests/happier/test_github_curl.shtests/happier/test_hstack_layout.shtests/happier/test_retry_helpers.shtests/happier/test_ui_bundle_verify.shtests/happier/test_url_time_validation.sh
| 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 |
There was a problem hiding this comment.
🎯 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.
| 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.
| | 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 | |
There was a problem hiding this comment.
🎯 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.
| - **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. |
There was a problem hiding this comment.
📐 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.
| **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. |
There was a problem hiding this comment.
📐 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.
| 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 |
There was a problem hiding this comment.
🩺 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.
| 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.
| 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 |
There was a problem hiding this comment.
🗄️ 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.
| 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.
| 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 | ||
| } |
There was a problem hiding this comment.
🎯 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.
| 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
What
Brings the happier LXC installer's devbox feature set to the fork, plus a full remediation of the 2026-07-16 audit.
Feature
claudevia the vendor native installer as thehappieruser,codexvia a pinned npm global), with Node installed on demand.EnvironmentFile=drop-in (never anEnvironment=directive).--auto-update), behind a CLI capability probe so older CLIs degrade with a warning instead of failing.misc/happier-common.funcextracted soct/happier.shandinstall/happier-install.shstop copy-pasting channel/UI-bundle/CLI resolvers.HAPPIER_PVE_*knob skips its prompt (PHS_SILENT=1+mode=defaultfor a fully unattended run).tests/happier/).Audit remediation (2026-07-16 backlog) — ~37/40 findings closed, 1
wontfix. Highlights:relay host installresetHAPPIER_SERVER_UI_DIRfrom 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./usr/bin/update; CI now watches andcmp-guards the duplicated installer JSON.Verification
rc=0(relay active, UI served) and updaterc=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.mdanddocs/audits/2026-07-17-lxc-matrix.md(incl. a re-smoke on the branch tip).shellcheck 0.11.0 --norc -S infoandshfmt -i 2 -ciclean on every fork-owned file.bash tests/happier/run.sh).Known caveats
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/previewinstall cleanly.hstack setup-from-sourceran 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.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
Need help on this PR? Tag
/codesmithwith what you need. Autofix is disabled.Note
Add devbox agent provisioning, daemon PAT support, and opt-in auto-update to the Happier installer
claudeandcodexagent CLIs and write a secure systemd env drop-in with agent binary paths and an optional GitHub PAT for daemon git operations.HAPPIER_PVE_AUTO_UPDATE,HAPPIER_PVE_AUTO_UPDATE_AT); the relay reinstall propagates the--auto-updateflag when the timer is already active.happier.shpreserves 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.shellcheckandshfmton relevant scripts.misc/install.funcnow hardcodes the update payload URL tocommunity-scripts/ProxmoxVE@main, ignoring anyINSTALLER_REPO/INSTALLER_REFoverrides set at install time.Macroscope summarized cdd3fdf.
Summary by CodeRabbit
New Features
Bug Fixes
Documentation
Tests