Something went wrong. Try again.
An orchestrator running microVMs on demand. Built for AI agent sandboxes, CI engines, and more.
Something went wrong. Try again.
7.2 kB · 174 lines
Shell
at main
123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175#!/usr/bin/env bashset -euo pipefail
# Interactive host setup for hyperfleet using charmbracelet/gum.# Grants the current user permission to talk to containerd via a shared group,# so the dev server can run unprivileged.## Steps:# 1. Verify gum + containerd are installed.# 2. Create a `containerd` group.# 3. Drop-in /etc/containerd/config.d/grpc-gid.toml setting [grpc].gid.# 4. Add the current user to the group.# 5. Restart containerd; verify socket ownership.
GROUP="${HYPERFLEET_GROUP:-containerd}"USER_NAME="${SUDO_USER:-${USER:-$(id -un)}}"CONFIG_MAIN="/etc/containerd/config.toml"CONFIG_DROPIN_DIR="/etc/containerd/config.d"CONFIG_DROPIN="${CONFIG_DROPIN_DIR}/grpc-gid.toml"SOCKET="/run/containerd/containerd.sock"
# --- gum bootstrap ------------------------------------------------------------install_gum() { local release_url="https://github.com/charmbracelet/gum/releases" local arch_raw arch latest version tarball tmp arch_raw="$(uname -m)" case "${arch_raw}" in x86_64) arch="x86_64" ;; aarch64|arm64) arch="arm64" ;; *) echo "unsupported arch: ${arch_raw}" >&2; return 1 ;; esac latest="$(basename "$(curl -fsSLI -o /dev/null -w '%{url_effective}' "${release_url}/latest")")" version="${latest#v}" tarball="gum_${version}_Linux_${arch}.tar.gz" tmp="$(mktemp -d)" trap 'rm -rf "${tmp}"' RETURN echo "installing gum ${version} for Linux/${arch}..." curl -fsSL -o "${tmp}/gum.tgz" "${release_url}/download/${latest}/${tarball}" tar -xzf "${tmp}/gum.tgz" -C "${tmp}" sudo install -m 0755 "${tmp}/gum_${version}_Linux_${arch}/gum" /usr/local/bin/gum echo "installed: $(/usr/local/bin/gum --version)"}
if ! command -v gum >/dev/null 2>&1; then install_gum || { echo "gum install failed; see https://github.com/charmbracelet/gum" >&2; exit 1; }fi
header() { gum style --border normal --margin "1 0" --padding "0 2" --foreground 212 "$1"; }info() { gum log --time kitchen --level info "$@"; }warn() { gum log --time kitchen --level warn "$@"; }errlog() { gum log --time kitchen --level error "$@"; }ok() { gum style --foreground 42 "$1"; }
header "hyperfleet setup"
# --- containerd present? ------------------------------------------------------if ! command -v containerd >/dev/null 2>&1; then errlog "containerd is not installed. Run: make install-containerd" exit 1fiinfo "found $(containerd --version)"
# --- group --------------------------------------------------------------------if getent group "${GROUP}" >/dev/null; then info "group ${GROUP} already exists"else gum confirm "Create system group '${GROUP}'?" || { warn "aborted"; exit 1; } sudo groupadd --system "${GROUP}" info "created group ${GROUP}"fiGID="$(getent group "${GROUP}" | cut -d: -f3)"info "group ${GROUP} → gid ${GID}"
# --- legacy drop-in cleanup ---------------------------------------------------# Older versions of this script wrote a drop-in that doesn't reliably override# the main config's explicit gid = 0. Remove it if present.if [[ -f "${CONFIG_DROPIN}" ]]; then info "removing legacy drop-in ${CONFIG_DROPIN}" sudo rm -f "${CONFIG_DROPIN}"fi
# --- main config: set [grpc].gid in place -------------------------------------# Drop-in /etc/containerd/config.d/grpc-gid.toml does NOT reliably win against# an explicit `gid = 0` written by `containerd config default`, so we patch the# main config in place. Awk handles three cases: (a) gid line exists in [grpc] →# replace the value; (b) [grpc] section exists without a gid line → insert one;# (c) no [grpc] section → append the whole section.if [[ ! -f "${CONFIG_MAIN}" ]]; then errlog "${CONFIG_MAIN} does not exist; install containerd first" exit 1fi
CURRENT_GID="$(sudo awk ' /^[[:space:]]*\[grpc\]/ { in_grpc=1; next } /^[[:space:]]*\[/ && !/^[[:space:]]*\[grpc\]/ { in_grpc=0 } in_grpc && /^[[:space:]]*gid[[:space:]]*=/ { gsub(/[^0-9-]/, "", $0); print; exit }' "${CONFIG_MAIN}")"
if [[ "${CURRENT_GID}" == "${GID}" ]]; then info "[grpc].gid is already ${GID} in ${CONFIG_MAIN}"else info "patching ${CONFIG_MAIN} → [grpc].gid = ${GID} (was ${CURRENT_GID:-unset})" sudo cp "${CONFIG_MAIN}" "${CONFIG_MAIN}.bak.$(date +%s)" TMP="$(mktemp)" sudo awk -v gid="${GID}" ' BEGIN { in_grpc=0; saw_grpc=0; replaced=0 } /^[[:space:]]*\[grpc\]/ { in_grpc=1; saw_grpc=1; print; next } /^[[:space:]]*\[/ && !/^[[:space:]]*\[grpc\]/ { if (in_grpc && !replaced) { print " gid = " gid; replaced=1 } in_grpc=0; print; next } in_grpc && /^[[:space:]]*gid[[:space:]]*=[[:space:]]*-?[0-9]+/ { sub(/=[[:space:]]*-?[0-9]+/, "= " gid); replaced=1; print; next } { print } END { if (in_grpc && !replaced) { print " gid = " gid } if (!saw_grpc) { print ""; print "[grpc]"; print " gid = " gid } } ' "${CONFIG_MAIN}" > "${TMP}" sudo install -m 0644 "${TMP}" "${CONFIG_MAIN}" rm -f "${TMP}"fi
# --- user membership ----------------------------------------------------------if id -nG "${USER_NAME}" | tr ' ' '\n' | grep -qx "${GROUP}"; then info "${USER_NAME} already in ${GROUP}"else gum confirm "Add user '${USER_NAME}' to '${GROUP}' group?" || { warn "aborted"; exit 1; } sudo usermod -a -G "${GROUP}" "${USER_NAME}" info "added ${USER_NAME} to ${GROUP}"fi
# --- restart containerd -------------------------------------------------------gum confirm "Restart containerd to apply the new socket gid?" || { warn "aborted before restart"; exit 1; }gum spin --spinner dot --title "restarting containerd..." -- sudo systemctl restart containerdsleep 1
# --- verify -------------------------------------------------------------------if [[ ! -S "${SOCKET}" ]]; then errlog "socket ${SOCKET} not present after restart" exit 1fi
SOCK_OWNER="$(stat -c '%U:%G %a' "${SOCKET}")"info "socket: ${SOCKET} (${SOCK_OWNER})"
EXPECTED_GROUP=$(stat -c '%G' "${SOCKET}")if [[ "${EXPECTED_GROUP}" != "${GROUP}" ]]; then warn "socket group is ${EXPECTED_GROUP}, expected ${GROUP}" warn "containerd may have ignored the drop-in; check /etc/containerd/config.toml" exit 1fi
ok "✓ containerd socket is now group-readable as ${GROUP}"
CURRENT_GROUPS="$(id -nG)"if echo "${CURRENT_GROUPS}" | tr ' ' '\n' | grep -qx "${GROUP}"; then ok "✓ your shell session already has the ${GROUP} group" info "ready: try \`make run\`"else warn "your current shell does NOT yet have the ${GROUP} group" info "(supplementary groups are read at login; a child process cannot mutate the parent's)" if gum confirm "Launch \`make run\` now under group ${GROUP}?"; then info "exec'ing into: sg ${GROUP} -c 'make run'" exec sg "${GROUP}" -c "make run" fi info "to upgrade your current shell in place, run: exec newgrp ${GROUP}" info "(plain \`newgrp\` nests a child shell; \`exec\` replaces the current shell so it doesn't)"fi