Skip to content

Instantly share code, notes, and snippets.

@heri16
Last active May 11, 2026 09:29
Show Gist options
  • Select an option

  • Save heri16/a4094ff89469edaf358fbe0958cd7545 to your computer and use it in GitHub Desktop.

Select an option

Save heri16/a4094ff89469edaf358fbe0958cd7545 to your computer and use it in GitHub Desktop.
Enable Nested virtualization for apple/container
#!/usr/bin/env sh
# Download from Apple
curl -fsSL -O "https://github.com/apple/container/releases/download/0.11.0/container-0.11.0-installer-signed.pkg"
open container-0.11.0-installer-signed.pkg
# Setup container system
container system start --enable-kernel-install
container system property set build.rosetta false
# Setup localdomain for containers
sudo container system dns create pod
container system property set dns.domain pod
container system property list
# Run terminal apps in Alpine Bash shell
(container image pull bash:alpine3.23 && container stop bash)
container run -it --name bash --ssh --memory 4G --rm bash:alpine3.23
# Launch desktop apps in Ubuntu Webtop
HALF_RAM_GB=$(( $(sysctl -n hw.memsize) / 1024**3 / 2 ))
(container image pull lscr.io/linuxserver/webtop:ubuntu-kde && container stop webtop)
container run -d \
--name webtop \
--ssh --cpus 8 --memory ${HALF_RAM_GB}G \
-e PUID=$(id -u) \
-e PGID=$(id -g) \
-e TZ=Etc/UTC \
-e PIXELFLUX_WAYLAND=true \
--rm lscr.io/linuxserver/webtop:ubuntu-kde
container exec webtop mount -o remount,size=1g /dev/shm
sleep 1s && until nc -z webtop.pod 3001; do sleep 0.5; done && open https://webtop.pod:3001
# Launch Brave Browser
HALF_RAM_GB=$(( $(sysctl -n hw.memsize) / 1024**3 / 2 ))
mkdir -p "$HOME/Library/Application Support/BraveSoftware/Brave-Browser"
(container image pull lscr.io/linuxserver/brave:latest && container stop brave)
container run -it \
--name brave \
--ssh --cpus 8 --memory ${HALF_RAM_GB}G \
--mount type=tmpfs,target=/dev/shm,size=1g \
-e PUID=$(id -u) \
-e PGID=$(id -g) \
-e TZ=Etc/UTC \
-e PIXELFLUX_WAYLAND=true \
-v "${HOME}/Downloads:/config/Downloads" \
-v "${HOME}/Library/Application Support/BraveSoftware/Brave-Browser:/config/.config/BraveSoftware/Brave-Browser" \
--rm lscr.io/linuxserver/brave:latest ||
container run -d \
--name brave \
--ssh --cpus 8 --memory ${HALF_RAM_GB}G \
--entrypoint /bin/sh \
-e PUID=$(id -u) \
-e PGID=$(id -g) \
-e TZ=Etc/UTC \
-e PIXELFLUX_WAYLAND=true \
-v "${HOME}/Downloads:/config/Downloads" \
-v "${HOME}/Library/Application Support/BraveSoftware/Brave-Browser:/config/.config/BraveSoftware/Brave-Browser" \
--rm lscr.io/linuxserver/brave:latest \
-c 'mount -o remount,size=1g /dev/shm && exec /init'
sleep 1s && until nc -z brave.pod 3001; do sleep 0.5; done && open https://brave.pod:3001
# Launch Librewolf Browser
mkdir -p "$HOME/Library/Application Support/LibreWolf"
(container image pull lscr.io/linuxserver/librewolf:latest && container stop librewolf)
container run -d \
--name librewolf \
--ssh --cpus 8 --memory 4G \
-e PUID=$(id -u) \
-e PGID=$(id -g) \
-e TZ=Etc/UTC \
-e PIXELFLUX_WAYLAND=true \
-v "${HOME}/Downloads:/config/Downloads" \
-v "${HOME}/Library/Application Support/LibreWolf:/config/.config/librewolf/librewolf" \
--rm lscr.io/linuxserver/librewolf:latest
container exec librewolf mount -o remount,size=1g /dev/shm
sleep 1s && until nc -z librewolf.pod 3001; do sleep 0.5; done && open https://librewolf.pod:3001
# Stop and uninstall container system
#container system stop
#echo 'sudo() { su - admin -c "$*" }' >> ~/.zshrc && source ~/.zshrc
#sudo /usr/local/bin/uninstall-container.sh -d
#!/usr/bin/env sh
# See: https://github.com/apple/container/blob/main/docs/how-to.md#expose-virtualization-capabilities-to-a-container
container system property set build.rosetta false
git clone --depth=1 https://github.com/apple/containerization.git
cd ./containerization
git fetch --tags
git checkout 0.30.1
cd kernel
make
container system kernel set --binary ./vmlinux
ls -la ~/Library/Application\ Support/com.apple.container/kernels/
container run --name nested-virtualization --virtualization --rm bash:alpine3.23 sh -c "dmesg | grep kvm"
# If you see the below, nested virtualization has been enabled:
# [ 0.013544] kvm [1]: nv: 568 coarse grained trap handlers
# [ 0.013566] kvm [1]: IPA Size Limit: 40 bits
# [ 0.013745] kvm [1]: GICv3: no GICV resource entry
# [ 0.013747] kvm [1]: disabling GICv2 emulation
# [ 0.013769] kvm [1]: GIC system register CPU interface enabled
# [ 0.013775] kvm [1]: vgic interrupt IRQ9
# [ 0.013814] kvm [1]: Hyp nVHE mode initialized successfully
# Shell into a new container terminal
# See options: https://images.chainguard.dev/directory?category=free
#container run -it --name agent --virtualization --ssh --cpus 8 --memory 4G --rm cgr.dev/chainguard/wolfi-base
# See options: https://hub.docker.com/hardened-images/catalog/dhi/debian-base
(container image pull ubuntu:latest && container stop agent)
container run -it --name agent --virtualization --ssh --cpus 8 --memory 4G --user ubuntu --rm ubuntu:latest
container exec -it --user root agent bash -c 'apt-get install -y sudo && echo "%sudo ALL=NOPASSWD:ALL" >/etc/sudoers.d/sudo'
# Warning: Some high severity-vulnerabilites in Alpine at time of posting
(container image pull bash:alpine3.23 && container stop agent)
container run -it --name agent --virtualization --ssh --cpus 8 --memory 4G --rm bash:alpine3.23
# Run a container in the background
HALF_RAM_GB=$(( $(sysctl -n hw.memsize) / 1024**3 / 2 ))
(container image pull bash:alpine3.23 && container stop agent)
container run -d --name agent --virtualization --ssh --cpus 8 --memory ${HALF_RAM_GB}G -v $HOME/Projects:$HOME/Projects bash:alpine3.23
# Run desktop apps in Ubuntu Webtop (with nested virtualization support)
HALF_RAM_GB=$(( $(sysctl -n hw.memsize) / 1024**3 / 2 ))
(container image pull lscr.io/linuxserver/webtop:ubuntu-kde && container stop webtop)
container run -d \
--name webtop \
--virtualization --ssh --cpus 8 --memory ${HALF_RAM_GB}G \
-e PUID=$(id -u) \
-e PGID=$(id -g) \
-e TZ=Etc/UTC \
-e PIXELFLUX_WAYLAND=true \
--rm lscr.io/linuxserver/webtop:ubuntu-kde
container exec webtop mount -o remount,size=1g /dev/shm
sleep 1s && until nc -z webtop.pod 3001; do sleep 0.5; done && open https://webtop.pod:3001
#!/usr/bin/env sh
# Install Claude Code
# See: https://code.claude.com/docs/en/quickstart#step-1-install-claude-code
cd $HOME
apk update || sudo apt-get update
command -v bash || (apk add --no-cache bash && sed -i '/^root:/ s|/bin/sh$|/bin/bash|' /etc/passwd)
command -v curl || (apk add --no-cache curl || sudo apt-get install -y --no-install-recommends curl ca-certificates)
curl -fsSL --tlsv1.3 https://claude.ai/install.sh | bash
echo 'export PATH="$HOME/.local/bin:$PATH"' >> ~/.bashrc && source ~/.bashrc
claude --version
claude auth status
# Install claude-agent-acp (to support ACP clients and editors)
# See: https://zed.dev/acp#editors-on-acp
# See: https://agentclientprotocol.com/get-started/clients)
# See: https://rait-09.github.io/obsidian-agent-client/agent-setup/claude-code.html
# See: https://github.com/xenodium/agent-shell?tab=readme-ov-file#claude-agent-sdk
command -v mise || (curl --tlsv1.3 https://mise.run/bash | sh && echo 'eval "$($HOME/.local/bin/mise activate bash --shims)"' >> ~/.bash_profile)
mise settings paranoid=1
command -v npx || mise use -g node@lts || (mise settings set node.mirror_url https://unofficial-builds.nodejs.org/download/release/ && mise settings set node.flavor musl && mise settings set node.compile 0 && apk add --no-cache libgcc && mise use -g node@lts)
source ~/.bashrc; npx -v
#npm install -g @agentclientprotocol/claude-agent-acp
#which claude-agent-acp
# Example output: /root/.local/share/mise/installs/node/24.15.0/bin/claude-agent-acp
#/usr/bin/env container exec -i -e IS_SANDBOX=1 -e BASH_ENV=/root/.bash_profile agent bash -c 'exec claude-agent-acp'
#/usr/bin/env container exec -i -e IS_SANDBOX=1 agent sh -c 'eval "$($HOME/.local/bin/mise activate bash --shims)"; exec claude-agent-acp'
#/usr/bin/env container exec -i -e IS_SANDBOX=1 agent /root/.local/share/mise/shims/claude-agent-acp
#/usr/bin/env container exec -i -e IS_SANDBOX=1 agent /root/.local/share/mise/shims/npx @agentclientprotocol/claude-agent-acp@0.29.1
# Recommended client for testing:
# Aizen.app: https://github.com/Uzaaft/awesome-libghostty?tab=readme-ov-file#ai-tools--agent-orchestration
# SHA256-hash of Aizen-1.0.81.dmg: https://www.virustotal.com/gui/file/3a4b654840fc1984ff8c4934ee376a09b0da6f76565e80f08a34fab8eb38cfdd/behavior
#!/usr/bin/env sh
# See: https://gist.github.com/heri16/60acf5b57518cd8518e62f1ce74f14a8
command -v curl || (apk add --no-cache curl)
# Install latest release of gVisor
# See: https://gvisor.dev/docs/user_guide/install/
(
set -e
ARCH=$(uname -m)
URL=https://storage.googleapis.com/gvisor/releases/release/latest/${ARCH}
curl --proto '=https' --tlsv1.3 -O "${URL}/runsc" \
-O "${URL}/runsc.sha512" \
-O "${URL}/containerd-shim-runsc-v1" \
-O "${URL}/containerd-shim-runsc-v1.sha512"
sha512sum -c runsc.sha512 \
-c containerd-shim-runsc-v1.sha512
rm -f *.sha512
chmod a+rx runsc containerd-shim-runsc-v1
mkdir -p ${HOME}/.local/bin
mv runsc containerd-shim-runsc-v1 ${HOME}/.local/bin
)
# Run a test container with gVisor
echo 'export PATH="$HOME/.local/bin:$PATH"' >> ~/.bashrc && source ~/.bashrc
command -v podman || (apk add --no-cache podman iptables)
#podman run --runtime=runsc --runtime-flag platform=kvm --runtime-flag ignore-cgroups --rm alpine sh -c 'dmesg && echo KVM gVisor ready!'
podman run --runtime=runsc --runtime-flag platform=systrap --runtime-flag ignore-cgroups --rm bash:alpine3.23 bash -c 'dmesg && echo Systrap gVisor ready!'
# Add lightpanda (headless browser) as MCP tool to Claude Code
claude mcp add --scope user lightpanda -- podman run -i --runtime=${HOME}/.local/bin/runsc --runtime-flag ignore-cgroups --rm lightpanda/browser:nightly /bin/lightpanda mcp
git clone --depth=1 https://github.com/lightpanda-io/agent-skill.git ~/.claude/skills/lightpanda
# Remote Browser Isolation with gVisor
# Starts Isolated Firefox browser at https://agent.pod:3001
mkdir -p ~/.mozilla/firefox
podman image pull lscr.io/linuxserver/firefox:latest
podman run -d \
--name=firefox --replace \
--runtime=$(command -v runsc) \
--runtime-flag ignore-cgroups \
--security-opt label=disable \
-e PUID=$(id -u) \
-e PGID=$(id -g) \
-e TZ=Etc/UTC \
-e PIXELFLUX_WAYLAND=true \
-e FIREFOX_CLI=https://youtube.com/music \
-e MOZ_FAKE_NO_SANDBOX=1 \
-p 3000:3000 \
-p 3001:3001 \
-v "${HOME}/.mozilla/firefox:/config" \
--shm-size="1gb" \
--restart unless-stopped \
lscr.io/linuxserver/firefox:latest
#podman logs firefox
#!/usr/bin/env sh
# See: https://tutorialsdojo.com/firecracker-for-students-launch-your-first-microvm-on-any-os/
# See: https://github.com/yashdiq/firecracker-lima-vm/blob/main/instructions.md
FC_VERSION="v1.13.0"
ARCH="aarch64"
# Download Firecracker
command -v curl || (apk add --no-cache curl)
curl -fsSL --tlsv1.3 --proto='=https' -O "https://github.com/firecracker-microvm/firecracker/releases/download/${FC_VERSION}/firecracker-${FC_VERSION}-${ARCH}.tgz"
# Extract and install
tar -xzf firecracker-${FC_VERSION}-${ARCH}.tgz
mkdir -p /usr/local/bin
mv release-${FC_VERSION}-${ARCH}/firecracker-${FC_VERSION}-${ARCH} /usr/local/bin/firecracker
chmod +x /usr/local/bin/firecracker
# Cleanup
rm -rf firecracker-${FC_VERSION}-${ARCH}.tgz release-${FC_VERSION}-${ARCH}
# Verify installation
firecracker --version
# Get the latest Ubuntu filesystem & kernel
latest_kernel_key=$(curl -fsSL --tlsv1.3 --proto='=https' "https://s3.amazonaws.com/spec.ccfc.min/?prefix=firecracker-ci/v1.13/${ARCH}/vmlinux-5.10&list-type=2" | awk -v arch="${ARCH}" -v RS='</Key>' 'match($0, "firecracker-ci/v1.13/" arch "/vmlinux-5\\.10\\.[0-9]{3}$") {print substr($0, RSTART, RLENGTH)}')
latest_ubuntu_key=$(curl -fsSL --tlsv1.3 --proto='=https' "https://s3.amazonaws.com/spec.ccfc.min/?prefix=firecracker-ci/v1.13/${ARCH}/ubuntu-&list-type=2" | awk -v arch="${ARCH}" -v RS='</Key>' 'match($0, "firecracker-ci/v1.13/" arch "/ubuntu-[0-9]+\\.[0-9]+\\.squashfs$") {print substr($0, RSTART, RLENGTH)}' | sort -V | tail -1)
ubuntu_version=$(printf '%s\n' "$latest_ubuntu_key" | awk -F'ubuntu-|.squashfs' '{print $2}')
echo ${latest_kernel_key} ${latest_ubuntu_key} ${ubuntu_version}
# Download the latest Ubuntu filesystem & kernel
mkdir -p ~/microvm && cd ~/microvm && mkdir -p upstream
curl -fsSL --tlsv1.3 --proto='=https' -O "https://s3.amazonaws.com/spec.ccfc.min/${latest_kernel_key}"
curl -fsSL --tlsv1.3 --proto='=https' -o upstream/ubuntu-$ubuntu_version.squashfs "https://s3.amazonaws.com/spec.ccfc.min/$latest_ubuntu_key"
# Extract squashfs
command -v unsquashfs || (apk add --no-cache squashfs-tools)
unsquashfs upstream/ubuntu-$ubuntu_version.squashfs
# Setup SSH access key
command -v ssh-keygen || (apk add --no-cache openssh-client)
ssh-keygen -f id_rsa -N ""
cp -v id_rsa.pub squashfs-root/root/.ssh/authorized_keys
chown -R root:root squashfs-root/root
mv -v id_rsa.pub ./ubuntu-$ubuntu_version.id_rsa.pub
mv -v id_rsa ./ubuntu-$ubuntu_version.id_rsa
# Create writable ext4 filesystem
command -v mkfs.ext4 || (apk add --no-cache e2fsprogs)
truncate -s 1G ubuntu-$ubuntu_version.ext4
mkfs.ext4 -d squashfs-root -F ubuntu-$ubuntu_version.ext4
rm -rf squashfs-root
# Install starter script
curl -fsSL --tlsv1.3 --proto='=https' -O "https://raw.githubusercontent.com/yashdiq/firecracker-lima-vm/refs/heads/main/start.sh"
sed -i '1s|^#!/bin/bash|#!/usr/bin/env bash|' *.sh
sed -i 's#^HOST_IFACE=.*#HOST_IFACE=$(ip route list default | awk '\''{print $5}'\'')#' start.sh
sed -i 's#^API_SOCKET=.*#[ -z "$API_SOCKET" ] \&\& API_SOCKET="./firecracker.socket"#' start.sh
sed -i "s#'nameserver 8\.8\.8\.8'#'nameserver 9.9.9.9'#" start.sh
apk add iproute2 iptables sudo
chmod +x start.sh
# Display setup summary
echo "πŸ“‹ Setup Summary:"
KERNEL=$(ls vmlinux-* | tail -1)
[ -f $KERNEL ] && echo "βœ… Kernel: $KERNEL" || echo "❌ ERROR: Kernel $KERNEL does not exist"
ROOTFS=$(ls *.ext4 | tail -1)
e2fsck -fn $ROOTFS &>/dev/null && echo "βœ… Rootfs: $ROOTFS" || echo "❌ ERROR: $ROOTFS is not a valid ext4 fs"
KEY_NAME=$(ls *.id_rsa | tail -1)
[ -f $KEY_NAME ] && echo "βœ… SSH Key: $KEY_NAME" || echo "❌ ERROR: Key $KEY_NAME does not exist"
#!/usr/bin/env sh
# Start Firecracker microVM with PCI support
export API_SOCKET="./firecracker.socket"
cd ~/microvm
rm -f "${API_SOCKET}"
./start.sh & firecracker --api-sock "${API_SOCKET}" --enable-pci
#!/usr/bin/env sh
# claude: host-side shim that forwards to the agent container
set -eu
: "${CONTAINER_CMD:=container}"
: "${CONTAINER_NAME:=agent}"
: "${CLAUDE_AGENT_ACP_VERSION:=latest}"
CMD_NAME="claude"
#CMD_ARGS="@agentclientprotocol/claude-agent-acp@${CLAUDE_AGENT_ACP_VERSION}"
flags="-i"
if [ -t 0 ] && [ -t 1 ]; then
flags="-it"
fi
"$CONTAINER_CMD" start "$CONTAINER_NAME" >/dev/null
MKDIR_OUT="$("$CONTAINER_CMD" exec "$CONTAINER_NAME" sh -c "mkdir -p '${PWD}'" 2>&1 | tee -a /tmp/claude.err)"
CMD="$("$CONTAINER_CMD" exec "$CONTAINER_NAME" bash -lc "command -v ${CMD_NAME}" 2>>/tmp/claude.err || true)"
[ -n "$CMD" ] || CMD="$("$CONTAINER_CMD" exec "$CONTAINER_NAME" sh -c 'echo $HOME' 2>>/tmp/claude.err)/.local/share/mise/shims/${CMD_NAME}"
exec "$CONTAINER_CMD" exec $flags --workdir "$PWD" -e "IS_SANDBOX=${IS_SANDBOX:-1}" "$CONTAINER_NAME" "$CMD" ${CMD_ARGS:-} "$@" 2>>/tmp/claude.err
#!/usr/bin/env sh
# claude: host-side shim that forwards to the agent container
set -eu
: "${CONTAINER_CMD:=container}"
: "${CONTAINER_NAME:=agent}"
: "${CLAUDE_AGENT_ACP_VERSION:=latest}"
CMD_NAME="npx"
CMD_ARGS="@agentclientprotocol/claude-agent-acp@${CLAUDE_AGENT_ACP_VERSION}"
flags="-i"
if [ -t 0 ] && [ -t 1 ]; then
flags="-it"
fi
"$CONTAINER_CMD" start "$CONTAINER_NAME" >/dev/null
MKDIR_OUT="$("$CONTAINER_CMD" exec "$CONTAINER_NAME" sh -c "mkdir -p '${PWD}'" 2>&1 | tee -a /tmp/claude.err)"
CMD="$("$CONTAINER_CMD" exec "$CONTAINER_NAME" bash -lc "command -v ${CMD_NAME}" 2>>/tmp/claude.err || true)"
[ -n "$CMD" ] || CMD="$("$CONTAINER_CMD" exec "$CONTAINER_NAME" sh -c 'echo $HOME' 2>>/tmp/claude.err)/.local/share/mise/shims/${CMD_NAME}"
exec "$CONTAINER_CMD" exec $flags --workdir "$PWD" -e "IS_SANDBOX=${IS_SANDBOX:-1}" "$CONTAINER_NAME" "$CMD" ${CMD_ARGS:-} "$@" 2>>/tmp/claude.err
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment