mirror of
https://github.com/wu736139669/hapi.git
synced 2026-11-14 02:51:43 +00:00
- extract the pinned-identity signing into scripts/sign-build.sh so local and remote deploys ship the same stable signature (macOS TCC grants key on the signing identity, not the path) - add scripts/deploy-remote.sh: signs locally, copies the binary to a new versioned file on the remote host, swaps the ~/.hapi/bin/hapi symlink, kicks the launchd job (default com.hapi.runner, override via HAPI_REMOTE_LAUNCHD_LABEL), and verifies the runner executes the new file; running sessions survive - document the remote flow in AGENTS.md and docs/local-deployment.md
173 lines
6.3 KiB
Markdown
173 lines
6.3 KiB
Markdown
# Local deployment branch
|
|
|
|
`local/deploy-main` is the source of the locally deployed HAPI binary.
|
|
|
|
## Update rule
|
|
|
|
Never reset this branch to `main` or recreate it from `main`. Update it with:
|
|
|
|
```bash
|
|
git fetch upstream main
|
|
git merge upstream/main
|
|
```
|
|
|
|
Resolve conflicts by preserving the local features below. Build and deploy only
|
|
after typecheck and focused regression tests pass.
|
|
|
|
## Local features carried by this branch
|
|
|
|
- Claude local history import (`tiann/hapi#1429`)
|
|
- Codex and Cursor mid-turn steering (`tiann/hapi#1443`)
|
|
- Separate setting to pin all active sessions (`tiann/hapi#1447`)
|
|
- Claude custom models from `settings.json` (`customClaudeModels`); upstream PR
|
|
`tiann/hapi#1318` was closed, so this must remain local
|
|
- macOS case-safe Storage Usage module names, required for local typecheck/build
|
|
|
|
When one of the open PRs is merged upstream, drop only the equivalent local
|
|
commits after verifying the merged implementation is present. Do not drop the
|
|
other local features.
|
|
|
|
## Local work preserved on separate branches
|
|
|
|
- Notification preferences and customizable push copy:
|
|
`feat/notification-preferences` (`tiann/hapi#1360`)
|
|
- HTML preview and completed-unseen marker: `feat/html-preview`
|
|
- Earlier recovery work, including Codex quick import:
|
|
`feat/recover-local-features`
|
|
|
|
These branches must not be deleted during cleanup or upstream updates.
|
|
|
|
## macOS executable deployment (important)
|
|
|
|
Deploy with:
|
|
|
|
```bash
|
|
bun run build:single-exe
|
|
scripts/deploy-local.sh [tag]
|
|
```
|
|
|
|
The script implements the sequence below, including the runner refresh; read
|
|
on to understand the macOS constraints it works around.
|
|
|
|
### Code-signature cache (never overwrite the stable path)
|
|
|
|
macOS caches the Mach-O signature against the executable pathname and
|
|
modification time. Replacing `~/.hapi/bin/hapi` in place (including a
|
|
temp-file + rename) or using a plain `cp` can leave a stale code-signature
|
|
cache. The next launch then fails with:
|
|
|
|
```text
|
|
OS_REASON_CODESIGNING
|
|
embedded signature doesn't match attached signature
|
|
```
|
|
|
|
This is a deployment/install issue, not a HAPI application error. Use a fresh
|
|
versioned path for every build and keep the stable command path as a symlink.
|
|
Do not deploy by copying over the stable path.
|
|
|
|
### Sign with a stable identity (TCC)
|
|
|
|
macOS TCC records permission grants against the code-signing identity. An
|
|
ad-hoc signature (`codesign --sign -`) has no stable identity, so each rebuild
|
|
looks like a brand-new app and every protected permission (Documents,
|
|
Downloads, Apple Music/media library, ...) is asked again. Sign every build
|
|
with one pinned identity:
|
|
|
|
- `scripts/deploy-local.sh` stores the chosen Apple Development SHA-1 in
|
|
`~/.hapi/signing-identity` and reuses it. Override with
|
|
`HAPI_SIGN_IDENTITY` when rotating certificates.
|
|
- All builds use the signed identifier `run.hapi.cli`.
|
|
- With no Apple Development identity the script falls back to ad-hoc; that
|
|
still works, but expect TCC prompts to reappear after every deploy.
|
|
|
|
Agent sessions should also avoid recursive `$HOME` sweeps (`find ~`,
|
|
`du -sh ~`, ...) unless they prune TCC-protected folders (`~/Music`,
|
|
`~/Pictures`, `~/Movies`, `~/Library`): a walk into
|
|
`~/Music/Music/Media.localized` raises a spurious Apple Music prompt
|
|
attributed to the hapi binary.
|
|
|
|
### Refresh the runner
|
|
|
|
The runner is a long-lived detached process. New sessions it spawns resolve
|
|
`~/.hapi/bin/hapi` and therefore pick up the new binary automatically, but the
|
|
runner's own code stays old until the process restarts:
|
|
|
|
- compiled binaries never self-update: the heartbeat compares the mtime of the
|
|
runner's own resolved exec path, which is fixed for the life of the process
|
|
- a stale runner keeps old machine RPC handlers and capability flags, so hub
|
|
features fail with "restart the runner" errors
|
|
|
|
`scripts/deploy-local.sh` runs `hapi runner start` when a runner is already
|
|
running; the CLI stops the stale runner and starts a fresh one with
|
|
`HAPI_CLI_EXECUTABLE` pinned to the stable symlink. Running sessions are
|
|
detached and survive the restart.
|
|
|
|
### Manual sequence
|
|
|
|
```bash
|
|
set -euo pipefail
|
|
build=cli/dist-exe/bun-darwin-arm64/hapi
|
|
stamp=$(date +%Y%m%d-%H%M%S)
|
|
release="$HOME/.hapi/bin/hapi.$stamp"
|
|
|
|
identity=$(cat "$HOME/.hapi/signing-identity") # SHA-1, or a unique cert name
|
|
|
|
# Bun's linker signature is not suitable after installation; re-sign once.
|
|
codesign --remove-signature "$build" 2>/dev/null || true
|
|
codesign --force --sign "$identity" --identifier run.hapi.cli "$build"
|
|
codesign --verify --deep --strict "$build"
|
|
|
|
# -p preserves the mtime covered by the code-signature cache.
|
|
cp -p "$build" "$release"
|
|
codesign --verify --deep --strict "$release"
|
|
"$release" --version
|
|
|
|
# Keep the old target as a rollback point. If hapi is already a symlink,
|
|
# replace only the link; otherwise move the legacy regular file aside first.
|
|
stable="$HOME/.hapi/bin/hapi"
|
|
if [ -L "$stable" ]; then
|
|
old_target=$(readlink "$stable")
|
|
else
|
|
old_target="hapi.bak.$stamp"
|
|
mv "$stable" "$HOME/.hapi/bin/$old_target"
|
|
fi
|
|
ln -sfn "$(basename "$release")" "$stable"
|
|
|
|
launchctl kickstart -k "gui/$(id -u)/com.hapi.hub"
|
|
sleep 2
|
|
curl -fsS http://127.0.0.1:3006/health >/dev/null
|
|
"$stable" --version
|
|
|
|
# Refresh a running runner; new sessions already use the new binary via the
|
|
# symlink, but the runner's own machine RPCs/capabilities stay stale.
|
|
HAPI_CLI_EXECUTABLE="$stable" "$stable" runner start
|
|
```
|
|
|
|
If the health check fails, immediately restore the prior link and restart the
|
|
agent:
|
|
|
|
```bash
|
|
ln -sfn "$old_target" "$HOME/.hapi/bin/hapi"
|
|
launchctl kickstart -k "gui/$(id -u)/com.hapi.hub"
|
|
```
|
|
|
|
Never use `cp`, `mv`, or `codesign` on the stable symlink target after the
|
|
launch agent has been started. Keep versioned binaries until the replacement
|
|
has been running and verified.
|
|
|
|
### Remote machines
|
|
|
|
Macs that run the same layout (versioned binary + `~/.hapi/bin/hapi` symlink +
|
|
a supervised runner) are updated from here over SSH:
|
|
|
|
```bash
|
|
scripts/deploy-remote.sh <ssh-target> [tag] # e.g. scripts/deploy-remote.sh k2lab stable-signing
|
|
```
|
|
|
|
The script signs the build with the same pinned identity, copies it to a new
|
|
versioned file, swaps the symlink, restarts the launchd job (`com.hapi.runner`
|
|
by default; override with `HAPI_REMOTE_LAUNCHD_LABEL`), and verifies the runner
|
|
executes the new file. Running sessions survive; roll back by restoring the
|
|
previous symlink and kicking the job again. The remote host needs no build
|
|
toolchain — it only receives the signed binary.
|