railpack

Configure and troubleshoot Railpack builds, with emphasis on RAILPACK_* environment variables, railpack.json overlays, build-plan inspection, local CLI installation and usage, and local BuildKit containers. Use for Railpack provider configuration, custom install/build/start commands, Mise or Apt packages, build or runtime variables and secrets, generated-plan debugging, BUILDKIT_HOST errors, or running Railpack from a release or source checkout.

Install
npx skills add 'https://github.com/railwayapp/railpack/tree/main/.'
Incomplete bundle · no download
main · 21a254fScanned 2026-09-17

Contributors

GitHub-linked commit authors for this SKILL.md at the saved revision. Co-authors and history before file renames are not included.

File history ↗

.mise/tasks/mise-linux-shell

.mise/tasks/mise-linux-shellBrowse 1970 files
View on GitHub
← Back to SKILL.md
#!/usr/bin/env bash# Keep this host-side orchestration in Bash so ShellCheck can analyze it;# ShellCheck does not support zsh scripts.#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)" # mise tool installation and project builds can behave differently on Linux# than on macOS. This task provides a persistent Linux environment for# reproducing and debugging those differences while sharing the Mac project# tree and host Docker daemon. 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:latestPORT=2375# 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/miseMISE_CACHE_DIR_GUEST=/var/cache/miseMISE_CONFIG_DIR_GUEST=/var/lib/mise/config# Host path of this repo (same path inside the machine via the /Users mount).PROJECT_ROOT="$(cd "$(dirname "${BASH_SOURCE[0]}")/../.." && pwd)"RAILPACK_MISE_VERSION="$(<"${PROJECT_ROOT}/core/mise/version.txt")"RAILPACK_MISE_DIR=/tmp/railpack/miseRUNTIME_ROOT="${TMPDIR:-/tmp}"PROXY_STATE_DIR="${RUNTIME_ROOT%/}/railpack-linux-shell-${UID}"PROXY_STATE_FILE="${PROXY_STATE_DIR}/socat.state"PROXY_LOCK_FILE="${PROXY_STATE_DIR}/socat.lock"PROXY_LOG_FILE="${PROXY_STATE_DIR}/socat.log" GATEWAY=""DOCKER_HOST_TCP=""DOCKER_SOCKET=""PROXY_LOCK_HELD=falsePROXY_START_PID=""MANAGED_PROXY_PID=""MANAGED_PROXY_GATEWAY=""MANAGED_PROXY_SOCKET=""WAIT_PID="" log() { echo "$*" >&2; } sleep_ms() {	local milliseconds="$1"	local duration	printf -v duration '%d.%03d' "$((milliseconds / 1000))" "$((milliseconds % 1000))"	sleep "$duration"} # Retry a predicate or explicitly retryable command with capped exponential backoff.retry_command() {	local max_attempts="$1"	local delay_ms="$2"	local max_delay_ms="$3"	local label="$4"	local report_retries="$5"	shift 5 	local attempt=1	local ec=0	while ((attempt <= max_attempts)); do		if "$@"; then			return 0		else			ec=$?		fi 		if ((attempt == max_attempts)); then			return "$ec"		fi		if [[ "$report_retries" == "true" ]]; then			log "==> ${label} failed (exit ${ec}); retry $((attempt + 1))/${max_attempts}"		fi		sleep_ms "$delay_ms"		delay_ms=$((delay_ms * 2))		if ((delay_ms > max_delay_ms)); then			delay_ms="$max_delay_ms"		fi		attempt=$((attempt + 1))	done} # Host gateway on Apple's default container network (not OrbStack, not loopback).discover_gateway() {	local quiet="${1:-false}"	local network_json gateway 	if ! command -v container >/dev/null 2>&1; then		[[ "$quiet" == "true" ]] || log "error: Apple container CLI is not installed"		return 1	fi	if ! command -v jq >/dev/null 2>&1; then		[[ "$quiet" == "true" ]] || log "error: jq is required to inspect the container network"		return 1	fi	if ! network_json="$(container network inspect default 2>&1)"; then		if [[ "$quiet" != "true" ]]; then			log "error: could not inspect the default container network (is 'container system' running?)"			log "$network_json"		fi		return 1	fi	if ! gateway="$(printf '%s\n' "$network_json" | jq -er '.[0].status.ipv4Gateway | select(type == "string" and length > 0)' 2>/dev/null)"; then		[[ "$quiet" == "true" ]] || log "error: default container network has no IPv4 gateway"		return 1	fi 	GATEWAY="$gateway"	DOCKER_HOST_TCP="tcp://${GATEWAY}:${PORT}"} resolve_docker_socket() {	local quiet="${1:-false}"	local socket	if ! socket="$(realpath /var/run/docker.sock 2>/dev/null)" || [[ ! -S "$socket" ]]; then		[[ "$quiet" == "true" ]] || log "error: Docker socket not found at /var/run/docker.sock (is Docker/OrbStack running?)"		return 1	fi	DOCKER_SOCKET="$socket"} docker_api_ok() {	curl -fsS --connect-timeout 1 "http://${GATEWAY}:${PORT}/_ping" 2>/dev/null | grep -qx OK} # Guest commands run once so a real guest failure is not mistaken for a# transient machine transport failure. Readiness checks are retried separately.machine_run() {	container machine run -n "$NAME" "$@"} machine_is_ready() {	container machine run -n "$NAME" -- true >/dev/null 2>&1} wait_machine_ready() {	if retry_command 10 250 2000 "container machine readiness" false machine_is_ready; then		return 0	fi	log "error: container machine $NAME did not become ready after repeated attempts"	return 1} wait_docker_proxy_ready() {	retry_command 6 100 1000 "Docker proxy readiness" false docker_api_ok} lsof_path() {	if [[ -x /usr/sbin/lsof ]]; then		echo /usr/sbin/lsof	else		command -v lsof 2>/dev/null	fi} load_proxy_state() {	MANAGED_PROXY_PID=""	MANAGED_PROXY_GATEWAY=""	MANAGED_PROXY_SOCKET=""	[[ -f "$PROXY_STATE_FILE" ]] || return 1	{		IFS= read -r MANAGED_PROXY_PID		IFS= read -r MANAGED_PROXY_GATEWAY		IFS= read -r MANAGED_PROXY_SOCKET	} <"$PROXY_STATE_FILE"	[[ "$MANAGED_PROXY_PID" =~ ^[0-9]+$ ]] &&		[[ -n "$MANAGED_PROXY_GATEWAY" ]] &&		[[ -n "$MANAGED_PROXY_SOCKET" ]]} write_proxy_state() {	local pid="$1" gateway="$2" socket="$3"	local state_tmp	mkdir -p "$PROXY_STATE_DIR"	chmod 0700 "$PROXY_STATE_DIR"	state_tmp="$(mktemp "${PROXY_STATE_FILE}.XXXXXX")"	printf '%s\n%s\n%s\n' "$pid" "$gateway" "$socket" >"$state_tmp"	mv -f "$state_tmp" "$PROXY_STATE_FILE"} clear_proxy_state() {	rm -f -- "$PROXY_STATE_FILE"} try_acquire_proxy_lock() {	local owner=""	mkdir -p "$PROXY_STATE_DIR"	chmod 0700 "$PROXY_STATE_DIR"	if ln -s "$$" "$PROXY_LOCK_FILE" 2>/dev/null; then		PROXY_LOCK_HELD=true		return 0	fi	owner="$(readlink "$PROXY_LOCK_FILE" 2>/dev/null || true)"	if [[ "$owner" =~ ^[0-9]+$ ]] && ! kill -0 "$owner" 2>/dev/null; then		# Recheck before removing a stale lock in case another process replaced it.		if [[ "$(readlink "$PROXY_LOCK_FILE" 2>/dev/null || true)" == "$owner" ]]; then			rm -f -- "$PROXY_LOCK_FILE"		fi	fi	return 1} acquire_proxy_lock() {	if retry_command 40 50 250 "Docker proxy lock" false try_acquire_proxy_lock; then		return 0	fi	log "error: timed out waiting for the Docker proxy lock: $PROXY_LOCK_FILE"	return 1} release_proxy_lock() {	if [[ "$PROXY_LOCK_HELD" == "true" ]] &&		[[ "$(readlink "$PROXY_LOCK_FILE" 2>/dev/null || true)" == "$$" ]]; then		rm -f -- "$PROXY_LOCK_FILE"	fi	PROXY_LOCK_HELD=false} socat_process_matches() {	local pid="$1" gateway="$2" socket="$3"	local lsof_bin comm args listen_arg connect_arg	[[ "$pid" =~ ^[0-9]+$ ]] || return 1	[[ -n "$gateway" && -n "$socket" ]] || return 1	if ! lsof_bin="$(lsof_path)" || [[ -z "$lsof_bin" ]]; then		return 1	fi	comm="$(ps -p "$pid" -o comm= 2>/dev/null || true)"	comm="${comm#"${comm%%[![:space:]]*}"}"	comm="${comm%"${comm##*[![:space:]]}"}"	[[ "${comm##*/}" == "socat" ]] || return 1	args="$(ps -p "$pid" -o command= 2>/dev/null || true)"	listen_arg="TCP-LISTEN:${PORT},bind=${gateway},reuseaddr,fork"	connect_arg="UNIX-CONNECT:${socket}"	[[ "$args" == *"$listen_arg"* && "$args" == *"$connect_arg"* ]] || return 1	"$lsof_bin" -a -p "$pid" -t -nP "-iTCP@${gateway}:${PORT}" -sTCP:LISTEN 2>/dev/null |		grep -qx "$pid"} listener_pids() {	local gateway="$1" lsof_bin	if ! lsof_bin="$(lsof_path)" || [[ -z "$lsof_bin" ]]; then		return 1	fi	"$lsof_bin" -t -nP "-iTCP@${gateway}:${PORT}" -sTCP:LISTEN 2>/dev/null} find_matching_socat() {	local gateway="$1" socket="$2" pid	while IFS= read -r pid; do		if socat_process_matches "$pid" "$gateway" "$socket"; then			echo "$pid"			return 0		fi	done < <(listener_pids "$gateway" || true)	return 1} process_is_gone() {	! kill -0 "$WAIT_PID" 2>/dev/null} cleanup_proxy_setup() {	if [[ -n "$PROXY_START_PID" ]] && kill -0 "$PROXY_START_PID" 2>/dev/null; then		kill "$PROXY_START_PID" 2>/dev/null || true	fi	PROXY_START_PID=""	release_proxy_lock} # Stop only a listener proven to be socat with the expected bridge and socket arguments.stop_docker_proxy() {	local pid="" gateway="" socket=""	acquire_proxy_lock	trap cleanup_proxy_setup EXIT 	if load_proxy_state; then		gateway="$MANAGED_PROXY_GATEWAY"		socket="$MANAGED_PROXY_SOCKET"		if socat_process_matches "$MANAGED_PROXY_PID" "$gateway" "$socket"; then			pid="$MANAGED_PROXY_PID"		else			pid="$(find_matching_socat "$gateway" "$socket" || true)"		fi	else		# Best-effort adoption supports proxies started before state tracking existed.		if discover_gateway true && resolve_docker_socket true; then			gateway="$GATEWAY"			socket="$DOCKER_SOCKET"			pid="$(find_matching_socat "$gateway" "$socket" || true)"		fi	fi 	if [[ -z "$pid" ]]; then		log "==> No matching socat Docker proxy found"		clear_proxy_state		release_proxy_lock		trap - EXIT		return 0	fi 	if ! socat_process_matches "$pid" "$gateway" "$socket"; then		log "error: refusing to stop PID ${pid}; it no longer matches the expected socat proxy"		clear_proxy_state		release_proxy_lock		trap - EXIT		return 1	fi	log "==> Stopping socat Docker proxy on ${gateway}:${PORT} (PID ${pid})"	kill "$pid"	WAIT_PID="$pid"	if ! retry_command 10 100 500 "socat shutdown" false process_is_gone; then		log "error: socat process ${pid} did not stop"		release_proxy_lock		trap - EXIT		return 1	fi	clear_proxy_state	log "==> Stopped socat Docker proxy (PID ${pid})"	release_proxy_lock	trap - EXIT} # The container machine cannot use the host's Unix Docker socket directly: the# socket lives on macOS, while Docker commands run inside the Linux guest. socat# bridges a TCP endpoint on Apple's container-machine gateway to that host Unix# socket, allowing the guest Docker CLI to use it through DOCKER_HOST.## Reuse an existing Docker API listener (OrbStack/etc.) when possible. Otherwise# start our own proxy bound only to the machine bridge address, never 0.0.0.0,# because access to this unauthenticated endpoint grants control of host Docker.ensure_docker_proxy() {	local pid="" existing_pids="" socat_bin=""	acquire_proxy_lock	trap cleanup_proxy_setup EXIT 	if docker_api_ok; then		if load_proxy_state &&			socat_process_matches "$MANAGED_PROXY_PID" "$MANAGED_PROXY_GATEWAY" "$MANAGED_PROXY_SOCKET"; then			log "==> Using managed socat Docker proxy at ${DOCKER_HOST_TCP} (PID ${MANAGED_PROXY_PID})"		elif resolve_docker_socket true &&			pid="$(find_matching_socat "$GATEWAY" "$DOCKER_SOCKET" || true)" && [[ -n "$pid" ]]; then			write_proxy_state "$pid" "$GATEWAY" "$DOCKER_SOCKET"			log "==> Using recognized socat Docker proxy at ${DOCKER_HOST_TCP} (PID ${pid})"			log "==> mise-linux-shell --stop will stop this exact proxy"		else			clear_proxy_state			log "==> Using existing Docker API listener at ${DOCKER_HOST_TCP}"			log "==> Listener is not a matching socat proxy and will not be stopped by --stop"		fi		release_proxy_lock		trap - EXIT		return 0	fi 	if ! socat_bin="$(command -v socat 2>/dev/null)" || [[ -z "$socat_bin" ]]; then		log "error: host Docker API not reachable at ${DOCKER_HOST_TCP} and socat is not installed"		log ""		log "  brew install socat"		log "  # then re-run: mise run mise-linux-shell"		release_proxy_lock		trap - EXIT		return 1	fi	if ! resolve_docker_socket; then		release_proxy_lock		trap - EXIT		return 1	fi 	pid="$(find_matching_socat "$GATEWAY" "$DOCKER_SOCKET" || true)"	if [[ -n "$pid" ]]; then		write_proxy_state "$pid" "$GATEWAY" "$DOCKER_SOCKET"		log "error: matching socat proxy is listening at ${DOCKER_HOST_TCP} (PID ${pid}), but the Docker API did not respond"		log "  check that Docker is running and the socket is healthy: ${DOCKER_SOCKET}"		release_proxy_lock		trap - EXIT		return 1	fi	existing_pids="$(listener_pids "$GATEWAY" || true)"	if [[ -n "$existing_pids" ]]; then		log "error: ${GATEWAY}:${PORT} is already occupied by a non-matching listener (pids: ${existing_pids//$'\n'/ })"		release_proxy_lock		trap - EXIT		return 1	fi 	# Bind only to the bridge gateway — not 0.0.0.0 — so the API stays off the LAN.	: >"$PROXY_LOG_FILE"	nohup "$socat_bin" \		"TCP-LISTEN:${PORT},bind=${GATEWAY},reuseaddr,fork" \		"UNIX-CONNECT:${DOCKER_SOCKET}" >"$PROXY_LOG_FILE" 2>&1 &	PROXY_START_PID=$!	disown 2>/dev/null || true	log "==> Starting socat Docker proxy on ${GATEWAY}:${PORT} -> ${DOCKER_SOCKET} (PID ${PROXY_START_PID})" 	if ! wait_docker_proxy_ready; then		log "error: started socat but Docker API still not reachable at ${DOCKER_HOST_TCP}"		if [[ -s "$PROXY_LOG_FILE" ]]; then			log "==> socat output ($PROXY_LOG_FILE):"			sed -n '1,20p' "$PROXY_LOG_FILE" >&2		fi		cleanup_proxy_setup		trap - EXIT		return 1	fi 	PROXY_START_PID="$(find_matching_socat "$GATEWAY" "$DOCKER_SOCKET" || true)"	if [[ -z "$PROXY_START_PID" ]]; then		log "error: Docker API became reachable, but the expected socat listener could not be identified"		cleanup_proxy_setup		trap - EXIT		return 1	fi	write_proxy_state "$PROXY_START_PID" "$GATEWAY" "$DOCKER_SOCKET"	log "==> Started managed socat Docker proxy at ${DOCKER_HOST_TCP} (PID ${PROXY_START_PID})"	log "==> Run 'mise run mise-linux-shell --stop' to stop it"	PROXY_START_PID=""	release_proxy_lock	trap - EXIT	return 0} 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 0fi 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() {	machine_run --root -- \		mkdir -p "$MISE_DATA_DIR_GUEST" "$MISE_CACHE_DIR_GUEST" "$MISE_CONFIG_DIR_GUEST"	# The machine user owns mise state; other guest users only need traversal.	machine_run --root -- \		chown "$USER" "$MISE_DATA_DIR_GUEST" "$MISE_CACHE_DIR_GUEST" "$MISE_CONFIG_DIR_GUEST"	machine_run --root -- \		chmod 0755 "$MISE_DATA_DIR_GUEST" "$MISE_CACHE_DIR_GUEST" "$MISE_CONFIG_DIR_GUEST"	machine_run -- \		touch "${MISE_CONFIG_DIR_GUEST}/config.toml"	# Empty global config used for project `mise install` so guest-only tools	# like usage (completions) are not required to be in the project lockfile.	machine_run -- \		rm -f "${MISE_CONFIG_DIR_GUEST}/empty.toml"	machine_run -- \		touch "${MISE_CONFIG_DIR_GUEST}/empty.toml"} # Packages + Linux mise + zsh. container machine run mangles `sh -c '...'`.bootstrap_machine() {	wait_machine_ready 	log "==> Bootstrapping machine packages"	# bash/git: mise's Python backend uses python-build to resolve versions	# 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	machine_run --root -- \		apk add bash git docker-cli zsh curl libstdc++ libgcc gcompat 	log "==> Installing mise"	machine_run --root -- \		curl -fsSL -o /tmp/mise-install.sh https://mise.run	# Alpine is musl — force the musl build (see mise.run / server.sh tip).	machine_run --root \		-e MISE_INSTALL_MUSL=1 \		-e MISE_INSTALL_PATH=/usr/local/bin/mise \		-e "MISE_VERSION=${RAILPACK_MISE_VERSION}" \		-- sh /tmp/mise-install.sh 	ensure_mise_dirs	setup_zsh} # Railpack caches a separate mise binary for host-side version resolution. Its# generic Linux download is glibc-linked, so reuse the pinned musl binary that# this Alpine machine already installed instead.configure_railpack_mise() {	log "==> Configuring Railpack to use musl mise ${RAILPACK_MISE_VERSION}"	machine_run -- mkdir -p "$RAILPACK_MISE_DIR"	machine_run -- \		ln -sfn /usr/local/bin/mise "${RAILPACK_MISE_DIR}/mise-${RAILPACK_MISE_VERSION}"} # mise stops at the ceiling and does not load configs *at* that path. Use the# project's parent so this repo's mise.toml loads, but never parent dirs or ~.guest_ceiling() {	echo "${MISE_CEILING_PATHS:-$(dirname "$PROJECT_ROOT")}"} # Host project tree is mounted into the machine; guest mise state is fresh after# create/restart, so trust the project before any command that loads its config.# Trust PROJECT_ROOT only — not --all (that walks parents too).trust_guest_mise() {	local ceiling	ceiling="$(guest_ceiling)"	machine_run \		-e "MISE_CEILING_PATHS=${ceiling}" \		"${guest_mise_env[@]}" \		-- mise trust "$PROJECT_ROOT"} # Install usage CLI for mise task completions without adding it to any mise# config. Project settings.locked=true would reject a global `usage` tool that# is not in mise.lock.install_usage_cli() {	log "==> Installing usage (completions helper, not a project tool)"	# Isolate from project + guest global config so locked mode cannot apply.	machine_run \		-w /var/tmp \		-e MISE_CEILING_PATHS=/var/tmp \		-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}/empty.toml" \		-e MISE_LOCKED=0 \		-e CI=1 \		-- mise install usage@latest	# Keep global config free of usage so project activate stays clean.	machine_run -- \		cp "${MISE_CONFIG_DIR_GUEST}/empty.toml" "${MISE_CONFIG_DIR_GUEST}/config.toml"	machine_run --root -- \		ln -sfn "${MISE_DATA_DIR_GUEST}/installs/usage/latest/usage" /usr/local/bin/usage} # 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"	local zshrc_host	trust_guest_mise	install_usage_cli 	machine_run --root -- mkdir -p /etc/zsh 	# Write zshrc via the shared project mount (no stdin pipe into machine run).	mkdir -p "${PROJECT_ROOT}/tmp"	zshrc_host="$(mktemp "${PROJECT_ROOT}/tmp/railpack-linux-zshrc.XXXXXX")"	trap 'rm -f -- "$zshrc_host"' EXIT	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' \		>"$zshrc_host"	machine_run --root -- cp "$zshrc_host" /etc/zsh/zshrc	rm -f -- "$zshrc_host"	trap - EXIT 	# Alpine has neither chsh nor usermod; interactive entry always runs zsh -l.} run_mise_install() {	local ceiling	ceiling="$(guest_ceiling)"	log "==> mise install (project tools)"	trust_guest_mise	# Use empty global config: guest global config may list `usage` for shell	# completions, which is not in the project lockfile (settings.locked=true).	machine_run \		-e "MISE_CEILING_PATHS=${ceiling}" \		-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}/empty.toml" \		-e CI=1 \		-- mise install} create_machine() {	log "==> Creating $NAME ($IMAGE)"	container machine create "$IMAGE" --name "$NAME" --cpus 4 --memory 8G	bootstrap_machine	run_mise_install} discover_gatewayensure_docker_proxy if [[ "${usage_restart:-false}" == "true" ]]; then	log "==> --restart: deleting $NAME"	destroy_machinefi if ! container machine inspect "$NAME" &>/dev/null; then	create_machineelse	wait_machine_ready	if ! machine_run -- command -v mise >/dev/null 2>&1; then		bootstrap_machine		run_mise_install	else		ensure_mise_dirs	fifi wait_machine_readyconfigure_railpack_mise CEILING="$(guest_ceiling)" 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