mirror of
https://github.com/hanzoai/pack.git
synced 2026-09-30 23:43:13 +00:00
build: add Linux shell environment for Apple container machines (#628)
* feat: add linux shell environment task for Apple container machines - Add `mise-linux-shell` task to manage a dedicated Alpine Linux container machine for tool isolation. - Configure automatic bootstrapping for `mise` and `zsh` within the container. - Implement Docker TCP proxying to allow the guest machine to interface with host Docker services. - Update `mise.toml` cleanup task to include teardown for the machine and its network proxy. Generated-by: aiautocommit * docs: add documentation for running Linux container shell on macOS Generated-by: aiautocommit
This commit is contained in:
Executable
+200
@@ -0,0 +1,200 @@
|
||||
#!/usr/bin/env bash
|
||||
#MISE description="Linux shell (Apple container machine) using host Docker"
|
||||
#MISE interactive=true
|
||||
#USAGE flag "--restart" help="Delete and recreate the machine from scratch"
|
||||
#USAGE flag "--stop" help="Delete the machine and stop the host Docker TCP proxy (socat)"
|
||||
|
||||
set -euo pipefail
|
||||
|
||||
NAME=railpack-linux
|
||||
# Alpine is used because Apple container machines require /sbin/init in the
|
||||
# image. Stock ubuntu:24.04 does not have it (needs a custom systemd image /
|
||||
# Dockerfile). Alpine ships busybox init and needs no image build.
|
||||
IMAGE=alpine:3.22
|
||||
# Host gateway on Apple's container-machine bridge (not OrbStack, not loopback).
|
||||
GATEWAY=192.168.64.1
|
||||
PORT=2375
|
||||
DOCKER_HOST_TCP="tcp://${GATEWAY}:${PORT}"
|
||||
# Keep all mise state on the machine disk. The Mac home is mounted at
|
||||
# /Users/...; if mise used ~/.local/share or ~/.config there it would mix
|
||||
# host (darwin) installs with guest (linux) ones.
|
||||
MISE_DATA_DIR_GUEST=/var/lib/mise
|
||||
MISE_CACHE_DIR_GUEST=/var/cache/mise
|
||||
MISE_CONFIG_DIR_GUEST=/var/lib/mise/config
|
||||
|
||||
log() { echo "$*" >&2; }
|
||||
|
||||
# Kill host-side socat proxying Docker on the machine bridge (if any).
|
||||
# Prefer lsof by listen address so we don't need pkill -f (matches wrapper argv).
|
||||
stop_docker_proxy() {
|
||||
local lsof_bin pids=""
|
||||
lsof_bin="$(command -v lsof || true)"
|
||||
[[ -x /usr/sbin/lsof ]] && lsof_bin=/usr/sbin/lsof
|
||||
if [[ -n "$lsof_bin" ]]; then
|
||||
pids="$("$lsof_bin" -t -nP "-iTCP@${GATEWAY}:${PORT}" -sTCP:LISTEN 2>/dev/null || true)"
|
||||
fi
|
||||
if [[ -z "$pids" ]]; then
|
||||
log "==> No Docker TCP proxy on ${GATEWAY}:${PORT}"
|
||||
return 0
|
||||
fi
|
||||
log "==> Stopping Docker TCP proxy on ${GATEWAY}:${PORT} (pids: ${pids//$'\n'/ })"
|
||||
# shellcheck disable=SC2086
|
||||
kill $pids 2>/dev/null || true
|
||||
}
|
||||
|
||||
destroy_machine() {
|
||||
log "==> Stopping machine: $NAME"
|
||||
container machine stop "$NAME" 2>/dev/null || true
|
||||
container machine delete "$NAME" 2>/dev/null || true
|
||||
}
|
||||
|
||||
if [[ "${usage_stop:-false}" == "true" ]]; then
|
||||
if [[ "${usage_restart:-false}" == "true" ]]; then
|
||||
log "error: --stop and --restart are mutually exclusive"
|
||||
exit 1
|
||||
fi
|
||||
destroy_machine
|
||||
stop_docker_proxy
|
||||
log "==> Stopped"
|
||||
exit 0
|
||||
fi
|
||||
|
||||
guest_mise_env=(
|
||||
-e "MISE_DATA_DIR=${MISE_DATA_DIR_GUEST}"
|
||||
-e "MISE_CACHE_DIR=${MISE_CACHE_DIR_GUEST}"
|
||||
-e "MISE_CONFIG_DIR=${MISE_CONFIG_DIR_GUEST}"
|
||||
-e "MISE_GLOBAL_CONFIG_FILE=${MISE_CONFIG_DIR_GUEST}/config.toml"
|
||||
)
|
||||
|
||||
ensure_mise_dirs() {
|
||||
container machine run -n "$NAME" --root -- \
|
||||
mkdir -p "$MISE_DATA_DIR_GUEST" "$MISE_CACHE_DIR_GUEST" "$MISE_CONFIG_DIR_GUEST"
|
||||
container machine run -n "$NAME" --root -- \
|
||||
chmod 1777 "$MISE_DATA_DIR_GUEST" "$MISE_CACHE_DIR_GUEST" "$MISE_CONFIG_DIR_GUEST"
|
||||
container machine run -n "$NAME" --root -- \
|
||||
touch "${MISE_CONFIG_DIR_GUEST}/config.toml"
|
||||
}
|
||||
|
||||
# Packages + Linux mise + zsh. container machine run mangles `sh -c '...'`.
|
||||
bootstrap_machine() {
|
||||
log "==> Bootstrapping machine packages"
|
||||
# docker-cli: talk to host Docker via DOCKER_HOST
|
||||
# zsh: interactive shell
|
||||
# libstdc++/libgcc/gcompat: many mise tools ship glibc/musl hybrids
|
||||
# curl: mise installer
|
||||
container machine run -n "$NAME" --root -- \
|
||||
apk add docker-cli zsh curl libstdc++ libgcc gcompat
|
||||
|
||||
log "==> Installing mise"
|
||||
container machine run -n "$NAME" --root -- \
|
||||
curl -fsSL -o /tmp/mise-install.sh https://mise.run
|
||||
# Alpine is musl — force the musl build (see mise.run / server.sh tip).
|
||||
container machine run -n "$NAME" --root \
|
||||
-e MISE_INSTALL_MUSL=1 \
|
||||
-e MISE_INSTALL_PATH=/usr/local/bin/mise \
|
||||
-- sh /tmp/mise-install.sh
|
||||
|
||||
ensure_mise_dirs
|
||||
setup_zsh
|
||||
}
|
||||
|
||||
# zsh + mise (modeled on https://github.com/wintermi/zsh-mise/blob/main/zsh-mise.plugin.zsh).
|
||||
# usage is a prerequisite for mise completions (same as that plugin's README).
|
||||
setup_zsh() {
|
||||
log "==> Configuring zsh + mise"
|
||||
container machine run -n "$NAME" \
|
||||
"${guest_mise_env[@]}" \
|
||||
-- mise use -g usage
|
||||
|
||||
container machine run -n "$NAME" --root -- mkdir -p /etc/zsh
|
||||
|
||||
# Mirrors wintermi/zsh-mise: activate, hook-env, fpath + background completion gen.
|
||||
printf '%s\n' \
|
||||
'# railpack-linux-shell — https://github.com/wintermi/zsh-mise' \
|
||||
'if (( $+commands[mise] )); then' \
|
||||
' source <(mise activate zsh)' \
|
||||
' source <(mise hook-env -s zsh)' \
|
||||
' _mise_comp_dir="${MISE_CACHE_DIR:-${XDG_CACHE_HOME:-$HOME/.cache}/mise}/zsh-completions"' \
|
||||
' mkdir -p "$_mise_comp_dir"' \
|
||||
' fpath=("$_mise_comp_dir" $fpath)' \
|
||||
' if [[ ! -f "$_mise_comp_dir/_mise" ]]; then' \
|
||||
' typeset -g -A _comps' \
|
||||
' autoload -Uz _mise' \
|
||||
' _comps[mise]=_mise' \
|
||||
' fi' \
|
||||
' mise completion zsh >| "$_mise_comp_dir/_mise" &|' \
|
||||
'fi' \
|
||||
'autoload -Uz compinit && compinit' |
|
||||
container machine run -n "$NAME" --root -- tee /etc/zsh/zshrc >/dev/null
|
||||
|
||||
container machine run -n "$NAME" --root -- \
|
||||
chsh -s /bin/zsh "$USER" 2>/dev/null ||
|
||||
container machine run -n "$NAME" --root -- \
|
||||
usermod -s /bin/zsh "$USER" 2>/dev/null || true
|
||||
}
|
||||
|
||||
run_mise_install() {
|
||||
local ceiling="${MISE_CEILING_PATHS:-$(cd .. && pwd)}"
|
||||
log "==> mise install (project tools)"
|
||||
container machine run -n "$NAME" \
|
||||
-e "MISE_CEILING_PATHS=${ceiling}" \
|
||||
"${guest_mise_env[@]}" \
|
||||
-- mise trust --all
|
||||
container machine run -n "$NAME" \
|
||||
-e "MISE_CEILING_PATHS=${ceiling}" \
|
||||
"${guest_mise_env[@]}" \
|
||||
-- mise install
|
||||
}
|
||||
|
||||
create_machine() {
|
||||
log "==> Creating $NAME ($IMAGE)"
|
||||
container machine create "$IMAGE" --name "$NAME" --cpus 4 --memory 8G
|
||||
bootstrap_machine
|
||||
run_mise_install
|
||||
}
|
||||
|
||||
if ! curl -fsS --connect-timeout 1 "http://${GATEWAY}:${PORT}/_ping" 2>/dev/null | grep -qx OK; then
|
||||
log "error: host Docker API not reachable at ${DOCKER_HOST_TCP}"
|
||||
log ""
|
||||
log "Expose Docker on the Apple container-machine bridge (${GATEWAY}), then re-run:"
|
||||
log ""
|
||||
log " # temporary proxy (brew install socat)"
|
||||
log " socat TCP-LISTEN:${PORT},bind=${GATEWAY},reuseaddr,fork UNIX-CONNECT:\$(realpath /var/run/docker.sock)"
|
||||
log ""
|
||||
log " # or OrbStack hosts config (prefer ${GATEWAY}, not 0.0.0.0):"
|
||||
log " # https://docs.orbstack.dev/docker/"
|
||||
log ""
|
||||
log " mise run mise-linux-shell"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
if [[ "${usage_restart:-false}" == "true" ]]; then
|
||||
log "==> --restart: deleting $NAME"
|
||||
destroy_machine
|
||||
fi
|
||||
|
||||
if ! container machine inspect "$NAME" &>/dev/null; then
|
||||
create_machine
|
||||
elif ! container machine run -n "$NAME" -- command -v mise >/dev/null 2>&1; then
|
||||
bootstrap_machine
|
||||
run_mise_install
|
||||
else
|
||||
ensure_mise_dirs
|
||||
fi
|
||||
|
||||
container machine run -n "$NAME" -- true 2>/dev/null || {
|
||||
container machine stop "$NAME" 2>/dev/null || true
|
||||
container machine run -n "$NAME" -- true
|
||||
}
|
||||
|
||||
CEILING="${MISE_CEILING_PATHS:-$(cd .. && pwd)}"
|
||||
|
||||
log "==> DOCKER_HOST=$DOCKER_HOST_TCP"
|
||||
log "==> MISE_DATA_DIR=$MISE_DATA_DIR_GUEST (machine-local, not Mac home)"
|
||||
log "==> MISE_CEILING_PATHS=$CEILING"
|
||||
exec container machine run -it \
|
||||
-n "$NAME" \
|
||||
-e "DOCKER_HOST=${DOCKER_HOST_TCP}" \
|
||||
-e "MISE_CEILING_PATHS=${CEILING}" \
|
||||
"${guest_mise_env[@]}" \
|
||||
-- zsh -l
|
||||
@@ -304,6 +304,15 @@ Mise is critical to this project. For any serious change, you'll need to underst
|
||||
is different.
|
||||
* If `mise tool erlang` reports a `core:` plugin it means this plugin is compiled into the mise binary and its source is available with the mise monorepo. This can be confusing since there are often open source shell-based repos available for a tool as well, but they are unused by default.
|
||||
|
||||
### Linux shell (Apple container machine)
|
||||
|
||||
On Apple Silicon Macs you can open a Linux environment that uses the
|
||||
host Docker daemon and a Linux-native mise install via
|
||||
`mise run mise-linux-shell`.
|
||||
|
||||
This creates (or reuses) an Alpine-based [container machine](https://github.com/apple/container)
|
||||
that connects to the host docker. This is helpful for debugging linux-only issues with the test suite.
|
||||
|
||||
### Mise Commands
|
||||
|
||||
Some helpful commands for debugging issues with mise:
|
||||
|
||||
@@ -60,6 +60,8 @@ run = [
|
||||
"docker images 'railpack-test-*' -q | xargs -r docker rmi -f",
|
||||
# remove images created by manually running `railpack build` against a project example
|
||||
"find examples -name test.json -exec dirname {} \\; | xargs -n 1 basename | xargs -r -I {} docker rmi -f {}",
|
||||
# tear down Apple container machine + host Docker TCP proxy used by mise-linux-shell
|
||||
"mise run mise-linux-shell --stop",
|
||||
"go clean -testcache",
|
||||
"mise cache clear",
|
||||
"rm -rf /tmp/railpack",
|
||||
|
||||
Reference in New Issue
Block a user