Rebuild Hermes Box on Lima and Ubuntu 26.04#10
Conversation
|
Note Reviews pausedIt 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 Use the following commands to manage reviews:
Use the checkboxes below for quick actions:
📝 WalkthroughWalkthroughHermes 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. ChangesHermes Box v2 Complete Migration
Possibly related PRs
🚥 Pre-merge checks | ✅ 4 | ❌ 1❌ Failed checks (1 warning)
✅ Passed checks (4 passed)
✏️ Tip: You can configure your own custom pre-merge checks in the settings. Comment |
There was a problem hiding this comment.
Actionable comments posted: 10
🧹 Nitpick comments (2)
guest/executor.service (1)
12-12: 🔒 Security & Privacy | 🔵 Trivial | ⚡ Quick winConsider 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:
--memoryand--cpusto prevent resource exhaustion--security-opt no-new-privileges=trueto prevent privilege escalation--read-onlywith appropriate tmpfs mounts if the container doesn't need a writable root filesystemThese 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 winKeep guest-applied lock decoding strict.
yaml.Unmarshalsilently ignores unknown fields, so a guest status lock with an unexpected/typo field can be accepted and rewritten as a different host lock. Use ayaml.DecoderwithKnownFields(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
⛔ Files ignored due to path filters (1)
go.sumis 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.gitignoreAGENTS.mdEXECUTOR_CONNECTIONS.mdMakefilePORTABLE_RESTORE.mdREADME.mdSmolfilebin/hermes-boxcmd/hermes-box-guest/main.gocmd/hermes-box/main.godocs/HERMES_BOX_V2_SPEC.mdgo.modguest/bootstrap.shguest/boxadmin.bash_profileguest/cloud-init.yamlguest/entrypoint.shguest/executor.serviceguest/executor.shguest/extract-executor.pyguest/hermes-box-recoverguest/hermes-box-recover.serviceguest/hermes-box.sudoersguest/hermes.serviceguest/install-node.shguest/restore.shguest/snapshot.shguest/start.shguest/supervisord.confguest/tmguest/tmux.confguest/workspace-seed.shguest/xterm-ghostty.terminfohermes-box.conf.examplehermes-box.yamlinternal/app/app.gointernal/app/app_test.gointernal/app/applied_lock.gointernal/app/backup.gointernal/app/backup_closure_test.gointernal/app/backup_test.gointernal/app/cli.gointernal/app/commands.gointernal/app/default.gointernal/app/default_backup.gointernal/app/default_backup_identity_test.gointernal/app/default_components.gointernal/app/default_components_test.gointernal/app/default_ops.gointernal/app/discovery.gointernal/app/executor.gointernal/app/executor_test.gointernal/app/help.gointernal/app/host.gointernal/app/host_test.gointernal/app/lifecycle.gointernal/app/portable.gointernal/app/portable_publish_darwin.gointernal/app/portable_publish_linux.gointernal/app/portable_publish_unsupported.gointernal/app/portable_test.gointernal/app/types.gointernal/app/ubuntu_test.gointernal/app/version_process_test.gointernal/artifacts/oci.gointernal/artifacts/store.gointernal/artifacts/store_test.gointernal/backup/backup.gointernal/backup/backup_test.gointernal/backup/publish_darwin.gointernal/backup/publish_linux.gointernal/backup/publish_unsupported.gointernal/backup/restore.gointernal/backup/stream.gointernal/backup/verify.gointernal/box/atomic.gointernal/box/lock.gointernal/box/store.gointernal/box/store_test.gointernal/component/component.gointernal/component/component_test.gointernal/config/config.gointernal/config/config_test.gointernal/config/secrets.gointernal/guestupdate/archive.gointernal/guestupdate/engine.gointernal/guestupdate/engine_test.gointernal/guestupdate/errors.gointernal/guestupdate/files.gointernal/guestupdate/installers.gointernal/guestupdate/protocol.gointernal/guestupdate/runner.gointernal/guestupdate/types.gointernal/keychain/keychain_darwin.gointernal/keychain/keychain_darwin_test.gointernal/keychain/keychain_unsupported.gointernal/keychain/keychain_unsupported_test.gointernal/keychain/store.gointernal/keychain/store_test.gointernal/lima/lima.gointernal/lima/lima_test.gointernal/process/process.gonetwork-hosts.txtrelease/README.mdrelease/build-hermes-source.shrelease/build-hermes-wheels.shrelease/build-provisioner.shrelease/configure-apt-snapshot.shrelease/hermes/gated_approval.pyrelease/hermes/patch-hermes-gated-approval.pyrelease/hermes/test_gated_approval.pyrelease/lib.shrelease/pins.envrelease/provisioner-packages.inrelease/python-build-requirements.txtrelease/qualification.lock.templaterelease/render-lock.shrelease/validate-lock.gorelease/validate-lock_test.gorelease/verify-release.shsecret-env.txt.exampletests/config-precedence.conftests/executor-extract.shtests/executor-runtime.shtests/lifecycle.shtests/ssh-provisioning.shtests/static.shtests/tmux.shtests/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
There was a problem hiding this comment.
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
📒 Files selected for processing (25)
.agents/skills/update-hermes-box/references/components.md.github/workflows/release-artifacts.ymlcmd/hermes-box/main.godocs/HERMES_BOX_V2_SPEC.mdinternal/app/app_test.gointernal/app/applied_lock.gointernal/app/commands.gointernal/app/default.gointernal/app/default_backup.gointernal/app/default_backup_identity_test.gointernal/app/default_components.gointernal/app/default_ops.gointernal/app/default_test.gointernal/app/help.gointernal/app/version_process_test.gointernal/guestupdate/engine_test.gointernal/guestupdate/installers.gorelease/README.mdrelease/build-hermes-source.shrelease/build-hermes-wheels.shrelease/build-provisioner.shrelease/configure-apt-snapshot.shrelease/pins.envrelease/verify-release.shtests/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
Summary
Validation
make formatmake checkgit diff --checkclaude -preview and follow-up verificationBootstrap 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 becauselimactlis not installed on this host.Note
Rebuild Hermes Box on Lima and Ubuntu 26.04 with encrypted backups and reproducible release artifacts
hermes-box.yamlconfig andhermes-box.lockfor reproducible pinning of all components (Node, uv, Claude, Codex, Hermes, Executor OCI image).internal/backup/) using age encryption and zstd compression, with atomic publish, fsync durability, manifest integrity checks, and macOS Keychain-backed identity storage (internal/keychain/).internal/app/) with subcommand dispatch, JSON output mode, per-box operation locking, and structured error envelopes; thehermes-box-guestbinary serves a stdio JSON protocol for guest update operations.release/including deterministic Linux ARM64 build scripts for the provisioner, Hermes source/wheels, APT snapshot pinning, lock rendering/verification, and arelease/validate-lock.gobinary that enforces lifecycle constraints and asserts runtime status.guest/bootstrap.shto provision from a supplied directory, install local debs, bind-mount an ext4 data disk to/data, create anagentuser (uid 1000), configure sshd via cloud-init with root-owned authorized keys, and enable a recovery gate service before application services start.Macroscope summarized 6cfaf61.