Skip to content

Rebuild Hermes Box on Lima and Ubuntu 26.04#10

Open
bmdavis419 wants to merge 20 commits into
mainfrom
codex/hermes-box-v2
Open

Rebuild Hermes Box on Lima and Ubuntu 26.04#10
bmdavis419 wants to merge 20 commits into
mainfrom
codex/hermes-box-v2

Conversation

@bmdavis419

@bmdavis419 bmdavis419 commented Jun 23, 2026

Copy link
Copy Markdown
Contributor

Summary

  • replace the smolvm/v1 stack with a Go host CLI and static ARM64 guest helper on Lima 2.1.3
  • define the Ubuntu 26.04 box, offline component transactions, encrypted backup/restore, durable recovery journals, and blessed tmux SSH UX
  • add reproducible artifact qualification, reviewed-main release gates, full API/spec documentation, and the reusable update skill

Validation

  • make format
  • make check
  • git diff --check
  • independent correctness and operational subagent reviews
  • final claude -p review and follow-up verification
  • verified zero Miles paths are tracked, staged, committed, or pushed

Bootstrap boundary

This implementation PR intentionally does not add the root hermes-box.lock. After this implementation is reviewed and merged, the documented release flow publishes a reviewed-main baseline, runs the isolated six-component qualification, publishes final immutable assets, and promotes the qualified lock in a follow-up PR. The destructive lifecycle was not run locally because limactl is not installed on this host.

Note

Rebuild Hermes Box on Lima and Ubuntu 26.04 with encrypted backups and reproducible release artifacts

  • Replaces the smolvm-based VM with a Lima + Apple VZ guest running Ubuntu 26.04, introducing hermes-box.yaml config and hermes-box.lock for reproducible pinning of all components (Node, uv, Claude, Codex, Hermes, Executor OCI image).
  • Adds an encrypted backup/restore system (internal/backup/) using age encryption and zstd compression, with atomic publish, fsync durability, manifest integrity checks, and macOS Keychain-backed identity storage (internal/keychain/).
  • Introduces a full CLI (internal/app/) with subcommand dispatch, JSON output mode, per-box operation locking, and structured error envelopes; the hermes-box-guest binary serves a stdio JSON protocol for guest update operations.
  • Adds a complete release pipeline under release/ including deterministic Linux ARM64 build scripts for the provisioner, Hermes source/wheels, APT snapshot pinning, lock rendering/verification, and a release/validate-lock.go binary that enforces lifecycle constraints and asserts runtime status.
  • Rewrites guest/bootstrap.sh to provision from a supplied directory, install local debs, bind-mount an ext4 data disk to /data, create an agent user (uid 1000), configure sshd via cloud-init with root-owned authorized keys, and enable a recovery gate service before application services start.
  • Risk: v2 is incompatible with v1 assets; existing VMs, backups, and config files require migration to the new YAML schema and lock format.

Macroscope summarized 6cfaf61.

@coderabbitai

coderabbitai Bot commented Jun 23, 2026

Copy link
Copy Markdown

Review Change Stack

Note

Reviews paused

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

Use the following commands to manage reviews:

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

Use the checkboxes below for quick actions:

  • ▶️ Resume reviews
  • 🔍 Trigger review
📝 Walkthrough

Walkthrough

Hermes Box is fully rewritten from a smolvm v1 runtime to a Lima-backed Ubuntu 26.04 ARM64 VM managed by a new Go CLI. The guest provisioning system, systemd services, backup/restore transaction logic, CLI command handlers, deterministic release qualification CI workflow, and all operator documentation are replaced or substantially rewritten to implement a v2 architecture with no v1 compatibility.

Changes

Hermes Box v2 Complete Migration

Layer / File(s) Summary
V2 specification, configuration, and user documentation
docs/HERMES_BOX_V2_SPEC.md, README.md, AGENTS.md, EXECUTOR_CONNECTIONS.md, PORTABLE_RESTORE.md, hermes-box.yaml, go.mod
Adds the complete 1455-line v2 implementation specification defining host/VM/disk architecture, guest-to-host exposure rules, component installation contracts, public CLI contract with idempotency/locking/error codes, backup format, and security boundaries. Rewrites README for v2 model (no v1 adoption/upgrade, Lima + Apple VZ, persistent ext4 data disk, age-encrypted backups, configuration via hermes-box.yaml + immutable hermes-box.lock). Updates AGENTS.md with v2 contributor rules (code ownership, lock edit restrictions, Lima resource naming, runtime safety boundaries, isolated lifecycle testing entrypoint). Rewrites EXECUTOR_CONNECTIONS.md and PORTABLE_RESTORE.md for v2 recovery-bundle semantics and restore workflows. Introduces hermes-box.yaml with VM sizing, executor port, and backup retention policy. Pins Go dependencies (age, container registry, compression, OCI/Docker, sync/crypto).
Agent skill workflow and component playbook contracts
.agents/skills/update-hermes-box/SKILL.md, .agents/skills/update-hermes-box/references/components.md
Rewrites skill document to define v2 invariants (hermes-box.lock input-only, host/guest boundary enforcement, artifact integrity via provenance, durable data/reconstructable roots, host materialization only, no secrets leakage). Renames and restructures components.md into "v2 component playbooks" with "Shared release and transaction contracts" (fail-closed update chain, immutable inputs, repository-owned artifacts, isolated Lima lifecycle proof, reviewed root lock). Reimplements component playbooks with discovery/release-construction/proof checkpoints for Hermes Agent (four-input release model, offline wheel closure, patch-anchor proof, rollback semantics), Lima/Ubuntu/provisioner (rebuild-only platform mutation, durable data disk, verified command/YAML contracts, proof of lifecycle cycles and recovery), uv/Node/Hermes Python (dependent tooling health, offline locked reproduction, pinned archive verification), Claude Code (npm tarball SRI installation, offline with scripts disabled, encrypted persistence, busy rejection), Codex (ARM64 musl bundle, strict config/help/doctor, device-auth persistence, encrypted snapshot), Executor (OCI index → ARM64 child digest, verified tar export, Podman load rules, loopback-only, MCP tool surface, encrypted data snapshot, anti-pattern guidance on mutable tags). Updates Go/CI actions (builder vs host baseline separation, commit-pinned Actions, credential/permission behaviors) and v2 PR boundaries (update COMPONENT|all vs rebuild mode, protected durable scopes, recovery-format changes, exercised lifecycle paths).
Deterministic release qualification and tag/publish workflows
.github/workflows/release-artifacts.yml, .github/workflows/ci.yml
Adds new 404-line release-artifacts.yml workflow with three jobs. build-and-verify runs in Ubuntu 26.04 ARM64 with timestamp-pinned signed Ubuntu snapshot verification (InRelease SHA-256 checks, expected package index hashes), runs apt snapshot configuration, validates pins against release/pins.env, installs pinned uv, proves executor image exact ARM64 digest via Podman/Skopeo load + architecture/digest inspection + smoke start, executes two independent native ARM64 builds and enforces .sha256 determinism equality, renders/verifies lock+artifacts, uploads 7-day-retention qualification candidates (zstd tarballs, SHA-256 files, hermes-box.lock). verify-release-tag gates on v2.0.0-* tags, requires active "Immutable Hermes Box v2 asset tags" ruleset with update/deletion protection and no bypass actors, verifies commit equality between push event and workflow/tag commits, checks tag object is annotated with matching name, ensures tag commit ancestry from origin/main via merge-base. publish job depends on prior jobs, downloads qualification artifacts, attests immutable inputs, refuses existing release replacement, re-validates annotated tag, creates GitHub release with baseline-specific title/notes, re-downloads and re-verifies all published URLs. Extends CI to install tmux alongside shellcheck, adds macOS ARM64 native runner verification, sets CGO_ENABLED=1 for full supported-host suite.
Guest bootstrap, data mount, and SSH hardening
guest/bootstrap.sh, guest/cloud-init.yaml
Completely rewrites guest/bootstrap.sh to consume prebuilt provisioner artifacts and wire runtime instead of performing full software provisioning. Adds root-only guard, validates provisioner artifacts exist, optionally installs .deb files, validates data source as absolute ext4 mount, ensures bind mount at /data via fstab, creates/validates agent user with uid/gid 1000, requires Lima transport key pre-bind, applies reviewed SSH daemon config via cloud-init.yaml (public-key auth, disabled password/kbd-interactive, restricted to agent user, forwarding disabled), validates sshd settings, sets up persistent /home/agent bind mount and /workspace symlink, installs runtime executables/configs/systemd units from provisioner, writes /etc/profile.d/hermes-box.sh with PATH/env vars and interactive shell exec of tm, creates /opt/hermes-box directories, daemon-reloads systemd, disables Hermes/Executor services, enables recovery service. Adds cloud-init.yaml with SSH daemon hardening snippet (public key auth, disabled password/interactive, restrictive defaults). Removes all v1 provisioning logic (apt installation, user creation, Hermes/uv downloader, gated approval patching, Codex/Hermes config generation, snapshot/ssh handling).
Guest systemd services and container management
guest/hermes-box-recover, guest/hermes-box-recover.service, guest/hermes.service, guest/executor.service
Adds hermes-box-recover Bash script with strict error mode that guards against unauthorized transaction state (fails if pending update/restore but no authorization flag). Introduces hermes-box-recover.service one-shot unit that runs the recovery guard, orders after local-fs.target before Hermes/Executor services. Introduces hermes.service Type=simple unit running hermes gateway run as agent user with HOME/HERMES_HOME/PATH, working directory /home/agent/workspace, restart-on-failure, ordering dependencies on network-online/executor/recovery. Introduces executor.service to run Podman-managed executor container with environment-file loading, pre-start cleanup, fixed loopback networking (127.0.0.1:4788:4788), /data/executor bind mount, graceful stop (--time 20), restart-on-failure.
Guest sudoers, tmux, and terminal assets
guest/hermes-box.sudoers, guest/tm, guest/tmux.conf, guest/xterm-ghostty.terminfo
Simplifies hermes-box.sudoers from multi-line Defaults/role-based rules to single passwordless agent ALL rule. Updates guest/tm workdir from /workspace/work to /home/agent/workspace. Rewrites Ghostty terminfo comment to clarify degradation rationale for older tmux. Adds xterm-ghostty terminal type with xterm-256color dependency. Removes v1 scripts: executor.sh (runtime release download/extraction/activation), extract-executor.py (OCI extraction/whitelist/validation), restore.sh (rootfs/workspace restoration, auth setup, entrypoint installation), snapshot.sh (tar compression with warnings and sync), start.sh (full guest startup, overlay repair, user creation, supervisor setup), supervisord.conf (daemon/program/include configuration), workspace-seed.sh (workspace snapshot validation/restoration), entrypoint.sh (restore-ready marker waiting), boxadmin.bash_profile (tmux auto-start and login shell logic), install-node.sh (Node.js download, verify, symlink).
Host CLI entrypoints and dependency construction
cmd/hermes-box/main.go, cmd/hermes-box-guest/main.go, internal/app/default.go, bin/hermes-box
Simplifies cmd/hermes-box/main.go to construct default CLI via app.NewDefault(), create signal-cancellable context, delegate to cli.Run(), and exit with returned status code (eliminates project-root discovery, manual config loading, ExitCode() handling). Adds cmd/hermes-box-guest/main.go entrypoint running guestupdate.Serve with stdin/stdout/stderr and signal cancellation, printing errors to stderr and exiting with code 1 on failure. Implements newDefault factory constructing OS runner, initializing backup keychain (with Darwin-only strict error, non-Darwin tolerance of ErrUnavailable), wiring default loader/operations/backups/locker. Implements defaultLoader.Load with command-based config handling (restore validates environment variables and resolves config path; other commands load directly), builds Definition from config bundle, validates HOME is absolute. Implements defaultLocker.Acquire creating per-box store and per-command lock. Removes HERMES_BOX_PROJECT_ROOT export from bin/hermes-box.
CLI invocation parsing, dispatch, and output formatting
internal/app/cli.go, internal/app/commands.go, internal/app/help.go
Introduces CLI struct with New/Run entrypoint; parses invocation (global flags --config/--json/--quiet/--no-color/-h/--help with environment and home-directory defaults; config path precedence; HOME validation; -- terminator). Implements dispatch router mapping command strings to handlers, returning structured error for unknowns. Implements writeSuccess (JSON envelope when --json, suppress on --quiet, defer to human formatting otherwise) and writeHumanSuccess with per-command formatting (status reports state/setup requirements/component drift/update candidates/failures, other commands return operation-specific results). Implements writeFailure classifying errors, emitting JSON error envelope, printing structured stderr message, returning status code. Introduces runTextCommand dispatching help/completion/version; implements help with optional command name and commandHelp map; implements completion for bash/zsh/fish with embedded command names; adds global usage text and per-command help strings. Implements dispatch router and shared command orchestration helpers (mutate acquires lock/resumes mutations/maps errors, owned enforces ownership/existence, preflight wraps preflight failures).
All command handlers (create through version)
internal/app/commands.go
Implements all command handlers with full argument validation, preflight/ownership checks, lifecycle orchestration: create (provision infrastructure, apply all, health check, initial backup, completion, first-run handoff), start (VM start, recovery, service start, health verify), stop (service/data sync, VM stop), ssh (attach via stdio), exec (run command), status (with optional --check), logs (target/follow/line-count, JSON or plain), open executor (URL return), setup executor (token handling with no-echo TTY, MCP tool listing), update (component selection, health verify), rollback (single component, state return), backup (optional label, result return), restore (absent destination validation, SHA-256 computation, post-restore health), rebuild (recovery state capture/fallback to latest backup, pre-rebuild backup optional, VM recreate, bounded recovery, health verify), doctor (guest/host checks with repair hints), key export (Keychain + recipient fingerprint), destroy (with --force option, final backup/integrity check, warning output), version (schema version reporting).
Applied-lock state model and persistence
internal/app/applied_lock.go
Implements host applied-lock file path resolution and persistence; adds targetAppliedLock to load existing lock and overlay only requested component desired pins (or all components), returning errors for unknown targets. Implements componentStatus to compute per-component maps (desired/applied/running/previous) and derive healthy/failed/drifted state from guest-applied vs released-current pin matching and desired-vs-applied drift. Adds pin helpers (nullablePin, lockPin extracting component-specific pin field from lock). Adds componentLockEqual for deep per-component lock comparison using reflect.DeepEqual. Implements writeLock to validate, YAML-marshal, write to restrictive temp file, fsync, atomic rename, and sync destination directory. Adds encodeLock to marshal lock to YAML string. Implements syncHostAppliedLock to strictly decode guest YAML payload and persist result. Implements copyReplace for atomic file replacement with temp file and directory sync. Implements copyRegularFile refusing overwrite, tolerating missing destination, delegating to copyReplace. Adds sanitizePin normalizing pin strings by replacing / and :.
External release discovery and artifact materialization
internal/app/discovery.go, internal/app/default_components.go
Implements discoverUpdates fetching current release versions for uv/claude/codex/hermes/executor plus Node.js LTS, building per-component result maps (component, current locked version/digest/commit, candidate release, error, qualified flag). Implements materializeOne to fetch artifact from artifacts.Store using name/URL/SHA256. Implements materializeLockClosure iterating fixed lockfile artifact set (Ubuntu image, provisioner, node, uv, codex, hermes, python, hermes-wheels, executor), materializing each and downloading Claude by SRI with on-disk caching (base64-decoded SHA-512 validation, size limits, atomic temp write, fsync, rename). Derives executor OCI index digest from lock.Executor.Image and materializes ARM64 child digest selection. Implements prepareAndUpload to reset guest staging directory, upload selected locked artifacts, construct validated component.Spec entries, and return cleanup function.
Backup closure validation and digest enforcement
internal/app/default_backup.go
Implements validateBackupClosure to YAML-decode archived applied-lock, enforce exact 10-artifact closure cardinality, validate identity fields (non-empty, unique), verify per-artifact SHA-256 matches, require lock-bound artifact exact set, verify Claude as regular file with SHA-512 SRI integrity (base64 decode, size check) and byte-to-SRI matching, verify executor OCI digest matches reviewed lock. Implements materializeBackupClosure resolving lock artifact references, fetching Claude by SRI, deriving executor index/ARM64 child digest, and re-validating closure. Adds helper functions (hashFile, mustEncodeLock, parseIdentity, saveTransactionSnapshot, writeTransactionSnapshot for atomic record coupling).
Backup creation with Age keychain identity and closure materialization
internal/app/default_backup.go
Implements defaultBackups.Create verifying keychain availability, loading/creating Age identity, staging applied.lock, deriving snapshot paths, materializing closure, opening guest tar backup-stream, calling backup.Create, mapping results into BackupResult (deciding identity retention based on published state/warnings/errors). Implements defaultBackups.LatestVerified enumerating envelope JSON files, decoding each, verifying archive against identity and closure validator, selecting newest via CreatedAt with deterministic envelope-path tie-breaker.
Transaction snapshot persistence, restore, and activation
internal/app/default_backup.go
Implements restoreTransactionSnapshot wrapper and transactionSnapshotPath location helper. Implements invalidateOverlappingTransactionSnapshots removing overlapping component snapshots and fsyncing directories. Implements loadTransactionSnapshot strictly decoding JSON. Implements restoreTransactionSnapshotRecord loading snapshot, verifying keychain identity, verifying backup bundle with closure validation, verifying archive checksum consistency, streaming bundle tar into guest restore-paths request, running guest with 4 MiB response bound, requiring confirmation. Implements restoreAndConfirmTransactionSnapshot loading/validating desired lock from retained record. Implements confirmTransactionActivation issuing guest recover, reading status, checking applied pins, conditionally issuing rollback, publishing recovered lock. Implements importRestoredArtifacts and restoreDataStream for artifact import and guest data-stream recovery.
Host infrastructure, VM, and service lifecycle operations
internal/app/default_ops.go
Implements Preflight for platform/port/closure validation; ResumeInterruptedMutation for journal-based crash recovery; Ownership verification (Lima VM/disk presence, metadata SHA/identity checks, guest ownership marker). Implements infrastructure creation (materialize provisioner/Ubuntu, generate Lima definitions, create/bind data disk, install provisioner, bootstrap ownership marker, write journals), CompleteCreate (clear journals), CleanupCreate (safe cleanup gated by recorded intent). Implements VM lifecycle: RecreateVM (rebuild-only, no disk resize), StartVM/StopVM, RemoveVM/RemoveAll with ownership gating and post-removal verification. Implements service control (StartServices/StopServices/SyncData).
Apply and Rollback with encrypted snapshots and journals
internal/app/default_ops.go
Implements Apply with prepared uploads/specs, initial-apply idempotency, per-component transaction snapshot creation, update-recovery journaling, guest apply invocation, post-guest coordination (publish locks, invalidate snapshots, clear journals). Implements Rollback snapshotting pre/current state, persisting rollback journals, restoring/confirming transaction snapshots, invalidating overlapping snapshots, restarting services, writing latest.json, removing rollback snapshots. Implements Health aggregation verifying desired lock, ownership marker, recovery state, mounts, services, component activation, gateway/executor health, executor auth/setup.
Status reporting, execution, and executor portal workflows
internal/app/default_ops.go
Implements Status state machine (absent/stopped/running/degraded) with optional update discovery; includes SSH environment shaping with ghostty preservation. Implements Exec sending bounded guest request, decoding JSON response, validating exit code. Implements Logs (journalctl with optional follow). Implements OpenExecutor with ownership/running gate, executor forwarding health check. Implements SetupExecutor running guest shell script, validating MCP tools. Implements readExecutorToken with no-echo TTY handling and terminal restoration.
Doctor diagnostics and operational support
internal/app/default_ops.go
Implements Doctor orchestrating guest/host checks (platform, ownership, backup/latest, recovery state, component activation, gateway, executor health/setup, loopback forwarding) with per-check repairs. Implements ExportKey with Keychain export and recipient fingerprint. Provides supporting diagnostic scripts/constants and implements keychain/forwarding helpers (backupKeyStatus, checkExecutorForward for loopback-only listener enforcement via lsof parsing), operator quoting utilities.
Rebuild recovery, Restore, and recovery execution
internal/app/default_ops.go
Implements CaptureRecoveryState copying guest applied.lock, materializing closure, capturing artifact paths. Implements PrepareRebuildRecovery verifying rebuild backup identity, persisting stable applied.lock, restoring/materializing closure, SHA-256 validating, writing journal; CompleteRebuild clearing journal/lock; verifyRebuildBackup and verifyRebuildJournal for revalidation on resume. Implements full Restore flow (parse identity, restore/stage closure, select/persist desired lock, materialize closure, create infrastructure, restore data, apply all, start, ensure Keychain identity, complete create). Implements RecoverRebuild restoring pre-rebuild box, removing failed candidate, recreating VM, restoring data, applying, starting, verifying health; removeFailedRebuildCandidate with ownership gating.
Guest protocol plumbing and version reporting
internal/app/default_ops.go
Implements guest protocol orchestration (guestRequest marshaling requests, strictly decoding bounded responses, mapping errors); Version returning configured schema versions with Lima detection; diagnostic plumbing (readGuestStatus, boundedGuestOutput with timeout handling, componentObservations for activation/pin checks, setupRequirements for credential checks); guestShell/runGuest/runLima helpers with limactl exit-status normalization.
Comprehensive test rewrite: CLI, operations, and lifecycles
internal/app/app_test.go, internal/app/backup_closure_test.go, internal/app/default_test.go, internal/app/default_backup_identity_test.go, internal/app/default_components_test.go
Rewrites internal/app/app_test.go with in-file fakes for loader/backups/locker/operations; comprehensive integration tests for CLI parsing, invocation/global flag handling, configuration loading, no-color compatibility, version/help/completion behavior, JSON unsupported detection, success/error output formatting, human status formatting, update candidate listing, JSON envelope behavior, error classification, recovery state validation, transaction journal behavior, executor token safety (no argv leakage, terminal echo handling), guest command protocol validation (argv/exit code), ownership/cleanup absence-proof, mutation locking, doctor repair command quoting. Adds backup_closure_test.go validating exact artifact cardinality enforcement, rejecting extra artifacts and tampering. Adds default_test.go covering keychain initialization error handling and text-command support on unsupported hosts. Adds default_backup_identity_test.go with comprehensive coverage of backup identity lifecycle (no silent creation, deterministic envelope-path tie-break, retention warning mapping, error mapping with result preservation), transaction snapshot durability (single final latest.json, strict 0600 permissions), snapshot restoration (no identity creation, checksum binding, archive tampering rejection), and mutation-induced snapshot invalidation. Adds default_components_test.go testing SRI fetch size bounds enforcement for chunked/streamed responses.
Repository scaffolding, modules, and tooling
.gitignore, go.mod, Makefile, bin/hermes-box
Updates .gitignore replacing v1 patterns (backups/, images/, state/, hermes-box.conf, secret-env.txt) with v2 patterns (dist/, coverage/, /state/, encrypted-key extensions, .env/., .hermes-box/, 10xn-hermes-miles*/). Pins Go module dependencies: adds direct requires for age, go-containerregistry, klauspost/compress, golang.org/x/sys, gopkg.in/yaml.v3; adds indirect transitive dependencies for containerd estargz, Docker/OCI, logging, tar-split, golang.org/x/crypto/sync. Adds Makefile static target running ./tests/static.sh; updates check target to invoke $(MAKE) static instead of direct script execution. Removes HERMES_BOX_PROJECT_ROOT export from bin/hermes-box wrapper.

Possibly related PRs

  • davis7dotsh/hermes-box#3: Directly modifies guest/bootstrap.sh, guest/hermes-box.sudoers, and guest provisioning tooling around Node/tmux, all of which are overwritten in this PR's v2 architecture.
  • davis7dotsh/hermes-box#4: This PR removes the executor implementation pieces introduced in that PR (internal/app/executor.go, guest/executor.sh, guest/extract-executor.py) and replaces them with v2 Podman-based Executor service management.
  • davis7dotsh/hermes-box#7: This PR rewrites the same .agents/skills/update-hermes-box/SKILL.md and references/components.md to establish the canonical v2 component-audit/upgrade workflow with discovery, release construction, and proof requirements.
🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 0.00% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check name Status Explanation
Title check ✅ Passed The title "Rebuild Hermes Box on Lima and Ubuntu 26.04" directly reflects the main change: migrating from smolvm/v1 to a Lima-based system on Ubuntu 26.04, which is the primary architectural shift in the changeset.
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.
Description check ✅ Passed The PR description comprehensively explains the transition from smolvm/v1 to a Go host CLI with Lima and Ubuntu 26.04, covering new features, validation approach, and intentional bootstrap boundary for the lock file.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.


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

Comment thread release/build-provisioner.sh Outdated
Comment thread .github/workflows/release-artifacts.yml
Comment thread .github/workflows/release-artifacts.yml Outdated

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Actionable comments posted: 10

🧹 Nitpick comments (2)
guest/executor.service (1)

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

Consider adding resource limits and security constraints.

The podman run command lacks explicit resource limits (CPU, memory) and security constraints. For a containerized service in a VM environment, consider adding:

  • --memory and --cpus to prevent resource exhaustion
  • --security-opt no-new-privileges=true to prevent privilege escalation
  • --read-only with appropriate tmpfs mounts if the container doesn't need a writable root filesystem

These additions would improve isolation and prevent a misbehaving Executor from affecting the VM host or other services.

🤖 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 `@guest/executor.service` at line 12, The ExecStart podman run command for the
hermes-box-executor lacks resource limits and security constraints that could
prevent service isolation issues. Add the --memory flag with an appropriate
memory limit, the --cpus flag to cap CPU usage, the --security-opt
no-new-privileges=true flag to prevent privilege escalation, and evaluate adding
--read-only with associated tmpfs mounts if the container does not require a
writable root filesystem. Insert these flags into the podman run command before
the ${EXECUTOR_IMAGE} variable.
internal/app/applied_lock.go (1)

183-191: 🗄️ Data Integrity & Integration | 🔵 Trivial | ⚡ Quick win

Keep guest-applied lock decoding strict.

yaml.Unmarshal silently ignores unknown fields, so a guest status lock with an unexpected/typo field can be accepted and rewritten as a different host lock. Use a yaml.Decoder with KnownFields(true) here, matching the other lock decode paths.

Proposed strict decode
 func syncHostAppliedLock(def Definition, encoded string) error {
 	if encoded == "" {
 		return errors.New("guest status omitted applied lock")
 	}
 	var lock config.Lock
-	if err := yaml.Unmarshal([]byte(encoded), &lock); err != nil {
+	decoder := yaml.NewDecoder(strings.NewReader(encoded))
+	decoder.KnownFields(true)
+	if err := decoder.Decode(&lock); err != nil {
 		return fmt.Errorf("decode guest applied lock: %w", err)
 	}
 	return writeLock(hostAppliedLockPath(def), lock)
 }
🤖 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 `@internal/app/applied_lock.go` around lines 183 - 191, In the
syncHostAppliedLock function, replace the yaml.Unmarshal call that decodes the
encoded string into the lock variable with a yaml.Decoder approach that enables
KnownFields(true) to strictly validate the YAML structure. This ensures that any
typos or unexpected fields in the guest-provided lock configuration are rejected
rather than silently ignored and potentially rewritten, matching the strict
decoding pattern used in other lock decode paths.
🤖 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 @.agents/skills/update-hermes-box/references/components.md:
- Around line 21-33: The reference to `qualification.lock.template` in the
update chain is missing the `release/` prefix that is consistently used
elsewhere in this documentation and in the repository layout. Update the
reference from `qualification.lock.template` to
`release/qualification.lock.template` to maintain consistency and ensure the
authoritative file path is unambiguous and searchable throughout the document
and codebase.

In @.github/workflows/release-artifacts.yml:
- Around line 184-186: The setup-go action has caching enabled by default which
creates a security vulnerability where a lower-trust pull request run can poison
the build/module cache that gets restored in the higher-trust publish job before
attestation and release publication. Add the cache: false parameter to the with
section of both setup-go action configurations (one in the publish job and one
in the build-and-verify job) to disable automatic dependency caching and prevent
cache poisoning attacks.

In `@docs/HERMES_BOX_V2_SPEC.md`:
- Around line 1008-1010: The lock file path is hardcoded to
`~/.hermes-box/locks/<name>.lock` but this does not respect the HERMES_BOX_HOME
environment variable override documented elsewhere, causing custom-state boxes
to potentially use incorrect lock file locations. Update the documentation and
implementation to derive the lock path from the resolved host-state root instead
of hardcoding it, so that mutating commands acquire locks at a path that
respects the HERMES_BOX_HOME setting and any custom box configurations.

In `@internal/app/commands.go`:
- Around line 284-289: In the return statement that creates the map with the
"lines" key, the current logic splits the output string unconditionally, which
results in a slice containing one empty string when the output is empty. Modify
the logic to check if the trimmed output string is empty after calling
strings.TrimSuffix on output.String(). If the output is empty, return an empty
slice for the "lines" value in the map; otherwise, proceed with the
strings.Split operation as currently implemented.

In `@internal/app/default_backup.go`:
- Around line 496-500: After the backup.Verify() call succeeds and returns the
bundle, add a validation check to ensure the archive checksum matches the
expected value stored in record.Backup.ArchiveSHA256. This check should compare
the actual checksum of the verified archive (likely available from the bundle or
archive object) against record.Backup.ArchiveSHA256 and return an error with an
appropriate message if they do not match. This prevents a replaced
envelope/archive pair from being restored as the retained transaction snapshot
even if it verifies under the same identity.

In `@internal/app/default_components.go`:
- Around line 51-54: The error message in the errors.New call violates the
ST1005 staticcheck rule which requires error strings to begin with a lowercase
letter. Locate the errors.New statement in the code block that checks if the
executor image contains an immutable index digest and change the error message
from starting with a capital letter to starting with a lowercase letter, so it
reads "executor image has no immutable index digest" instead of "Executor image
has no immutable index digest".

In `@internal/app/default_ops.go`:
- Around line 499-502: The CleanupCreate function creates a cleanup context and
overwrites the ctx parameter instead of using a separate variable, creating an
ineffassign issue. Replace the ctx reassignment with directly using the
cleanupCtx variable for all subsequent operations within this function,
eliminating the shadowing of the ctx parameter. Apply this same pattern to other
locations mentioned in the comment where fresh local recovery contexts are
created and should not overwrite existing ctx parameters.
- Around line 43-55: The Write method in boundedProtocolBuffer violates the
io.Writer interface contract by returning 0 instead of the actual number of
bytes written when truncating oversized responses. When remaining bytes are
written to the buffer (the condition where remaining > 0), change the return
statement to return the actual byte count (remaining) instead of 0, while still
returning the error. This ensures that callers receive accurate information
about how many bytes were accepted before the limit was exceeded.

In `@internal/app/default.go`:
- Around line 15-23: The keychain.New call in the NewDefault function is
ignoring the error return value by using underscore assignment, which means
keychain initialization failures are silently dropped and surface later with
poor diagnostics. Capture the error return value from keychain.New and handle it
appropriately by checking if it is not nil and either logging the error,
returning nil from NewDefault, or handling it in a way that prevents degraded
dependencies from being passed downstream.

In `@internal/app/help.go`:
- Around line 71-91: Add a "help" entry to the commandHelp map to enable the
help command to provide its own help text. Insert a new key-value pair in the
commandHelp map with the key "help" and provide appropriate usage text that
describes how to use the help command (for example, something like "Usage:
hermes-box help [COMMAND]\n").

---

Nitpick comments:
In `@guest/executor.service`:
- Line 12: The ExecStart podman run command for the hermes-box-executor lacks
resource limits and security constraints that could prevent service isolation
issues. Add the --memory flag with an appropriate memory limit, the --cpus flag
to cap CPU usage, the --security-opt no-new-privileges=true flag to prevent
privilege escalation, and evaluate adding --read-only with associated tmpfs
mounts if the container does not require a writable root filesystem. Insert
these flags into the podman run command before the ${EXECUTOR_IMAGE} variable.

In `@internal/app/applied_lock.go`:
- Around line 183-191: In the syncHostAppliedLock function, replace the
yaml.Unmarshal call that decodes the encoded string into the lock variable with
a yaml.Decoder approach that enables KnownFields(true) to strictly validate the
YAML structure. This ensures that any typos or unexpected fields in the
guest-provided lock configuration are rejected rather than silently ignored and
potentially rewritten, matching the strict decoding pattern used in other lock
decode paths.
🪄 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: Organization UI

Review profile: CHILL

Plan: Pro Plus

Run ID: cefbee74-69f4-41e3-b325-3fb9c6de36d3

📥 Commits

Reviewing files that changed from the base of the PR and between faab976 and 4204fe8.

⛔ Files ignored due to path filters (1)
  • go.sum is excluded by !**/*.sum
📒 Files selected for processing (132)
  • .agents/skills/update-hermes-box/SKILL.md
  • .agents/skills/update-hermes-box/references/components.md
  • .github/workflows/ci.yml
  • .github/workflows/release-artifacts.yml
  • .gitignore
  • AGENTS.md
  • EXECUTOR_CONNECTIONS.md
  • Makefile
  • PORTABLE_RESTORE.md
  • README.md
  • Smolfile
  • bin/hermes-box
  • cmd/hermes-box-guest/main.go
  • cmd/hermes-box/main.go
  • docs/HERMES_BOX_V2_SPEC.md
  • go.mod
  • guest/bootstrap.sh
  • guest/boxadmin.bash_profile
  • guest/cloud-init.yaml
  • guest/entrypoint.sh
  • guest/executor.service
  • guest/executor.sh
  • guest/extract-executor.py
  • guest/hermes-box-recover
  • guest/hermes-box-recover.service
  • guest/hermes-box.sudoers
  • guest/hermes.service
  • guest/install-node.sh
  • guest/restore.sh
  • guest/snapshot.sh
  • guest/start.sh
  • guest/supervisord.conf
  • guest/tm
  • guest/tmux.conf
  • guest/workspace-seed.sh
  • guest/xterm-ghostty.terminfo
  • hermes-box.conf.example
  • hermes-box.yaml
  • internal/app/app.go
  • internal/app/app_test.go
  • internal/app/applied_lock.go
  • internal/app/backup.go
  • internal/app/backup_closure_test.go
  • internal/app/backup_test.go
  • internal/app/cli.go
  • internal/app/commands.go
  • internal/app/default.go
  • internal/app/default_backup.go
  • internal/app/default_backup_identity_test.go
  • internal/app/default_components.go
  • internal/app/default_components_test.go
  • internal/app/default_ops.go
  • internal/app/discovery.go
  • internal/app/executor.go
  • internal/app/executor_test.go
  • internal/app/help.go
  • internal/app/host.go
  • internal/app/host_test.go
  • internal/app/lifecycle.go
  • internal/app/portable.go
  • internal/app/portable_publish_darwin.go
  • internal/app/portable_publish_linux.go
  • internal/app/portable_publish_unsupported.go
  • internal/app/portable_test.go
  • internal/app/types.go
  • internal/app/ubuntu_test.go
  • internal/app/version_process_test.go
  • internal/artifacts/oci.go
  • internal/artifacts/store.go
  • internal/artifacts/store_test.go
  • internal/backup/backup.go
  • internal/backup/backup_test.go
  • internal/backup/publish_darwin.go
  • internal/backup/publish_linux.go
  • internal/backup/publish_unsupported.go
  • internal/backup/restore.go
  • internal/backup/stream.go
  • internal/backup/verify.go
  • internal/box/atomic.go
  • internal/box/lock.go
  • internal/box/store.go
  • internal/box/store_test.go
  • internal/component/component.go
  • internal/component/component_test.go
  • internal/config/config.go
  • internal/config/config_test.go
  • internal/config/secrets.go
  • internal/guestupdate/archive.go
  • internal/guestupdate/engine.go
  • internal/guestupdate/engine_test.go
  • internal/guestupdate/errors.go
  • internal/guestupdate/files.go
  • internal/guestupdate/installers.go
  • internal/guestupdate/protocol.go
  • internal/guestupdate/runner.go
  • internal/guestupdate/types.go
  • internal/keychain/keychain_darwin.go
  • internal/keychain/keychain_darwin_test.go
  • internal/keychain/keychain_unsupported.go
  • internal/keychain/keychain_unsupported_test.go
  • internal/keychain/store.go
  • internal/keychain/store_test.go
  • internal/lima/lima.go
  • internal/lima/lima_test.go
  • internal/process/process.go
  • network-hosts.txt
  • release/README.md
  • release/build-hermes-source.sh
  • release/build-hermes-wheels.sh
  • release/build-provisioner.sh
  • release/configure-apt-snapshot.sh
  • release/hermes/gated_approval.py
  • release/hermes/patch-hermes-gated-approval.py
  • release/hermes/test_gated_approval.py
  • release/lib.sh
  • release/pins.env
  • release/provisioner-packages.in
  • release/python-build-requirements.txt
  • release/qualification.lock.template
  • release/render-lock.sh
  • release/validate-lock.go
  • release/validate-lock_test.go
  • release/verify-release.sh
  • secret-env.txt.example
  • tests/config-precedence.conf
  • tests/executor-extract.sh
  • tests/executor-runtime.sh
  • tests/lifecycle.sh
  • tests/ssh-provisioning.sh
  • tests/static.sh
  • tests/tmux.sh
  • tests/workspace-seed.sh
💤 Files with no reviewable changes (20)
  • guest/supervisord.conf
  • guest/snapshot.sh
  • guest/install-node.sh
  • guest/entrypoint.sh
  • guest/workspace-seed.sh
  • guest/executor.sh
  • guest/boxadmin.bash_profile
  • guest/extract-executor.py
  • internal/app/host.go
  • hermes-box.conf.example
  • guest/restore.sh
  • Smolfile
  • bin/hermes-box
  • internal/app/backup.go
  • guest/start.sh
  • internal/app/executor.go
  • internal/app/app.go
  • internal/app/executor_test.go
  • internal/app/host_test.go
  • internal/app/backup_test.go

Comment thread .agents/skills/update-hermes-box/references/components.md
Comment thread .github/workflows/release-artifacts.yml Outdated
Comment thread docs/HERMES_BOX_V2_SPEC.md Outdated
Comment thread internal/app/commands.go
Comment thread internal/app/default_backup.go
Comment thread internal/app/default_components.go
Comment thread internal/app/default_ops.go
Comment thread internal/app/default_ops.go Outdated
Comment thread internal/app/default.go Outdated
Comment thread internal/app/help.go
Comment thread docs/HERMES_BOX_V2_SPEC.md

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

🤖 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 `@internal/app/default_test.go`:
- Around line 28-31: The early return at line 28 when err != nil does not verify
the operating system, which means errors on non-Darwin hosts would incorrectly
pass the test. Instead of a simple return, add an explicit OS check (using
runtime.GOOS) to ensure the early return only occurs on Darwin hosts where
Keychain unavailability is expected to be fatal. For non-Darwin hosts, handle
the error case differently to properly assert that errors are not expected on
those platforms, preventing regressions from being hidden.
🪄 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: Organization UI

Review profile: CHILL

Plan: Pro Plus

Run ID: bcd67329-3822-48d9-a284-d24598fd9f83

📥 Commits

Reviewing files that changed from the base of the PR and between 4204fe8 and 89529fd.

📒 Files selected for processing (25)
  • .agents/skills/update-hermes-box/references/components.md
  • .github/workflows/release-artifacts.yml
  • cmd/hermes-box/main.go
  • docs/HERMES_BOX_V2_SPEC.md
  • internal/app/app_test.go
  • internal/app/applied_lock.go
  • internal/app/commands.go
  • internal/app/default.go
  • internal/app/default_backup.go
  • internal/app/default_backup_identity_test.go
  • internal/app/default_components.go
  • internal/app/default_ops.go
  • internal/app/default_test.go
  • internal/app/help.go
  • internal/app/version_process_test.go
  • internal/guestupdate/engine_test.go
  • internal/guestupdate/installers.go
  • release/README.md
  • release/build-hermes-source.sh
  • release/build-hermes-wheels.sh
  • release/build-provisioner.sh
  • release/configure-apt-snapshot.sh
  • release/pins.env
  • release/verify-release.sh
  • tests/static.sh
✅ Files skipped from review due to trivial changes (1)
  • docs/HERMES_BOX_V2_SPEC.md
🚧 Files skipped from review as they are similar to previous changes (11)
  • internal/app/default.go
  • internal/app/default_backup_identity_test.go
  • internal/app/help.go
  • internal/app/default_components.go
  • internal/app/app_test.go
  • internal/app/applied_lock.go
  • .github/workflows/release-artifacts.yml
  • internal/app/commands.go
  • .agents/skills/update-hermes-box/references/components.md
  • internal/app/default_ops.go
  • internal/app/default_backup.go

Comment thread internal/app/default_test.go Outdated
Comment thread release/configure-apt-snapshot.sh Outdated
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