Isolated Linux VM (Apple Silicon)
Run hstack flows in a clean Linux VM on macOS (Apple Silicon) for repeatable validation.
Isolated Linux VM (Apple Silicon)
If you want to validate hstack or a Happier PR using hstack review-pr on a fresh isolated system (no existing ~/.happier-stack, no host tooling), the simplest repeatable approach on Apple Silicon is a Linux VM managed by Lima (it uses Apple’s Virtualization.framework).
This avoids Docker/container UX issues (browser opening, Expo networking, file watching) while still being truly “clean”.
Option A (recommended): Lima + Ubuntu ARM64
1) Install Lima (macOS host)
brew install lima1b) Automated smoke test (optional)
On your macOS host (this repo, from apps/stack/):
./scripts/provision/macos-lima-hstack-smoke.sh happy-e2eWhat it validates (best-effort):
hstack setup --profile=selfhostin a fully isolated sandbox (no auth / tailscale / autostart / menubar)- server health endpoint (
/health) - UI is served by server-light (
/returns HTML) - monorepo worktree creation (
hstack wt new ...)
Notes:
- This runs inside the VM (Linux) and uses
npxby default. - You can pin the version under test:
HSTACK_VERSION=0.6.14 ./scripts/provision/macos-lima-hstack-smoke.sh happy-e2e. - If you’re testing a fork, you can point the runner at your fork’s raw scripts:
HSTACK_RAW_BASE=https://raw.githubusercontent.com/<owner>/<repo>/<ref>/apps/stack ./scripts/provision/macos-lima-hstack-smoke.sh happy-e2e. - If you’re testing unpublished local changes, copy a packed tarball into the VM and run:
HSTACK_TGZ=./happier-dev-stack-*.tgz /tmp/linux-ubuntu-hstack-smoke.sh.
2) Create + configure a VM (recommended script)
On your macOS host (this repo, from apps/stack/):
./scripts/provision/macos-lima-vm.sh happy-testThis creates the VM if needed and configures localhost port forwarding for the port ranges used by our VM defaults.
(This is important because the Expo web app uses WebCrypto and needs a secure context like http://localhost.)
It also sets a higher default VM memory size (to avoid Expo/Metro getting OOM-killed and exiting with code=137).
Override if needed:
LIMA_MEMORY=12GiB ./scripts/provision/macos-lima-vm.sh happy-testPort ranges note:
review-prruns in a fully isolated sandbox (separate hstack home dir), so VM defaults written to~/.happier-stack/env.localinside the VM won’t be read automatically.- Prefer passing
--vm-ports(or explicit--stack-port-start=...) toreview-prso the sandbox uses the forwarded ranges.
2b) Manual setup (if you prefer)
limactl create --name happy-pr --tty=false template://ubuntu-24.04
limactl start happy-prIf you run review-pr (Expo web / Metro) inside the VM, allocate enough memory (recommend 8GiB+).
Edit ~/.lima/happy-pr/lima.yaml on the macOS host and set:
memory: "8GiB"2c) Host access (ports + browser URLs)
When you want to open Happier/Expo URLs in your macOS browser, use localhost port forwarding.
Why this matters: the Expo web app uses WebCrypto (crypto.subtle) via expo-crypto for things like key derivation.
Browsers only expose WebCrypto in secure contexts:
https://...http://localhost,http://127.0.0.1, andhttp://*.localhost
If you open the UI via a VM LAN IP like http://192.168.x.y:PORT, the browser treats it as insecure and you can hit errors like:
TypeError: Cannot read properties of undefined (reading 'digest').
Configure port forwarding (recommended)
Edit the instance config on the macOS host:
limactl stop happy-pr || true
open -a TextEdit ~/.lima/happy-pr/lima.yamlAdd a portForwards section to forward the hstack VM port ranges to your host localhost:
portForwards:
# Stack/server ports (default VM range from our provision script)
- guestPortRange: [13000, 13999]
hostPortRange: [13000, 13999]
# Expo dev-server (web) ports (default VM range from our provision script)
- guestPortRange: [18000, 19099]
hostPortRange: [18000, 19099]Then restart the VM:
limactl start happy-prOptional: IP-based access (only when you need LAN)
If you explicitly need to access guest services by VM IP (e.g. for testing from another device), you can enable vzNAT:
limactl stop happy-pr || true
limactl start happy-pr --network vzNATNote: IP-based URLs (like http://192.168...) may break web-only crypto flows unless you use HTTPS or a browser dev override.
3) Provision the VM (Node + build deps)
limactl shell happy-prInside the VM:
curl -fsSL https://raw.githubusercontent.com/happier-dev/happier/main/apps/stack/scripts/provision/linux-ubuntu-provision.sh -o /tmp/linux-ubuntu-provision.sh \
&& chmod +x /tmp/linux-ubuntu-provision.sh \
&& /tmp/linux-ubuntu-provision.sh --profile=happier3b) (Optional) Run the hstack dev setup wizard
If your goal is to work on changes (not just review a PR), you can run the dev profile:
npx --yes -p @happier-dev/stack@latest hstack setup --profile=dev --bind=loopbackNotes:
- This bootstraps a workspace (clones repos, installs deps, sets up worktrees/stacks tooling).
- On Linux VMs you typically want
--no-mobileworkflows (iOS dev-client requires Xcode/macOS).
4) Run review-pr via npx (published package)
Inside the VM:
npx --yes -p @happier-dev/stack@latest hstack review-pr \
--repo=https://github.com/happier-dev/happier/pull/<PR_NUMBER> \
--vm-ports \
--no-mobile \
--keep-sandbox \
--verbose \
-- --bind=loopbackNotes:
--no-mobilekeeps the validation focused (Expo mobile dev-client adds more host requirements).- You can also add
--keep-sandboxif you want to inspect the sandbox contents after a failure. - For full reproducibility, pin the version:
npx --yes -p @happier-dev/[email protected] hstack review-pr ... --vm-portsforces the stack/server and Expo dev-server (web) ports into the forwarded VM ranges (pairs with theportForwardsconfig in this doc).
Optional: test unreleased local changes
If you need to test changes that aren’t published to npm yet:
- On your Mac (repo checkout):
npm pack- Copy the generated
happier-dev-stack-*.tgzinto the VM (any method you like), then inside the VM:
npx --yes ./happier-dev-stack-*.tgz hstack review-pr ...Option B: GUI VM (UTM) – simplest when you want a “real desktop”
If you want the most realistic “reviewer” experience (open browser, etc.), a GUI VM is great:
- Install UTM (macOS host):
brew install --cask utm - Create an Ubuntu 24.04 ARM64 VM (UTM wizard).
- Run the same provisioning +
node bin/hstack.mjs review-pr ...inside the VM.
Option C: Apple “container” / Docker
Containers are excellent for server-only validation, but are usually not the best fit for end-to-end review-pr UX because:
- opening the host browser from inside the container is awkward
- Expo/dev-server workflows and networking tend to require extra port mapping and host interaction
Use containers only if you explicitly want “CLI-only” checks and are okay opening URLs manually.
Resetting / starting fresh
Full reset (recommended): recreate the VM
On the macOS host:
limactl stop happy-pr || true
limactl delete happy-pr
limactl create --name happy-pr --tty=false template:ubuntu-24.04
limactl start happy-prThen re-run the provisioning step (Node + build deps) from this doc.
Soft reset: keep the VM, delete hstack state
If you want a “clean-ish” rerun without recreating the VM, delete the hstack home + any workspace you chose:
Inside the VM:
rm -rf ~/.happier-stack ~/.happierIf you used hstack setup --profile=dev and picked a custom workspace directory (outside ~/.happier-stack/workspace), delete that directory too.
Notes / scope
- This doc targets web-only validation (
--no-mobile) on Ubuntu ARM64 VMs. - On-device iOS testing via
--mobilerequires a macOS host with Xcode (not possible inside the Linux VM).