From ff8482b841e2bae7d990e6e2e31b63d983021278 Mon Sep 17 00:00:00 2001 From: Thales Maciel Date: Wed, 18 Mar 2026 13:13:11 -0300 Subject: [PATCH] Bake mise into default VM images New VMs should have mise available without a per-VM bootstrap step, and the activation needs to work in the default root bash workflow. Install a pinned mise binary during both the Go-native image build path and the customize.sh rootfs rebuild path, then enable bash activation through /etc/profile.d for login shells and /etc/bash.bashrc for interactive shells. Add a regression around the generated provisioning script and validate with bash -n customize.sh, go test ./..., and make build. Rebuilding the default rootfs is still required before future default-image VMs pick up the change. --- customize.sh | 15 +++++++++++++++ internal/daemon/imagebuild.go | 26 ++++++++++++++++++++++++++ internal/daemon/imagebuild_test.go | 23 +++++++++++++++++++++++ 3 files changed, 64 insertions(+) create mode 100644 internal/daemon/imagebuild_test.go diff --git a/customize.sh b/customize.sh index 4312069..35593a6 100755 --- a/customize.sh +++ b/customize.sh @@ -103,6 +103,9 @@ BASE_ROOTFS="" OUT_ROOTFS="" SIZE_SPEC="" INSTALL_DOCKER=0 +MISE_VERSION="v2025.12.0" +MISE_INSTALL_PATH="/usr/local/bin/mise" +MISE_ACTIVATE_LINE='eval "$(/usr/local/bin/mise activate bash)"' MODULES_DIR="$(bundle_path default_modules_dir "$RUNTIME_DIR/wtf/root/lib/modules/6.8.0-94-generic")" PACKAGES_FILE="$(banger_packages_file)" while [[ $# -gt 0 ]]; do @@ -388,6 +391,18 @@ fi apt-get update DEBIAN_FRONTEND=noninteractive apt-get -y upgrade DEBIAN_FRONTEND=noninteractive apt-get -y install ${APT_PACKAGES_ESCAPED} +curl -fsSL https://mise.run | MISE_INSTALL_PATH=\"$MISE_INSTALL_PATH\" MISE_VERSION=\"$MISE_VERSION\" sh +mkdir -p /etc/profile.d +cat > /etc/profile.d/mise.sh <<'MISEPROFILE' +if [ -n \"\${BASH_VERSION:-}\" ] && [ -x \"$MISE_INSTALL_PATH\" ]; then + eval \"\$($MISE_INSTALL_PATH activate bash)\" +fi +MISEPROFILE +chmod 0644 /etc/profile.d/mise.sh +touch /etc/bash.bashrc +if ! grep -Fqx '$MISE_ACTIVATE_LINE' /etc/bash.bashrc; then + printf '\n%s\n' '$MISE_ACTIVATE_LINE' >> /etc/bash.bashrc +fi if [[ \"$INSTALL_DOCKER\" == \"1\" ]]; then DEBIAN_FRONTEND=noninteractive apt-get -y remove containerd || true if ! DEBIAN_FRONTEND=noninteractive apt-get -y install docker-ce docker-ce-cli containerd.io docker-buildx-plugin docker-compose-plugin; then diff --git a/internal/daemon/imagebuild.go b/internal/daemon/imagebuild.go index ae46e4c..2186cc3 100644 --- a/internal/daemon/imagebuild.go +++ b/internal/daemon/imagebuild.go @@ -19,6 +19,12 @@ import ( "banger/internal/system" ) +const ( + defaultMiseVersion = "v2025.12.0" + defaultMiseInstallPath = "/usr/local/bin/mise" + defaultMiseActivateLine = `eval "$(/usr/local/bin/mise activate bash)"` +) + type imageBuildSpec struct { ID string Name string @@ -234,6 +240,7 @@ func buildProvisionScript(vmName, dnsServer string, packages []string, installDo script.WriteString("DEBIAN_FRONTEND=noninteractive apt-get -y upgrade\n") fmt.Fprintf(&script, "PACKAGES=%s\n", shellArray(packages)) script.WriteString("DEBIAN_FRONTEND=noninteractive apt-get -y install \"${PACKAGES[@]}\"\n") + appendMiseSetup(&script) if installDocker { script.WriteString("DEBIAN_FRONTEND=noninteractive apt-get -y remove containerd || true\n") script.WriteString("if ! DEBIAN_FRONTEND=noninteractive apt-get -y install docker-ce docker-ce-cli containerd.io docker-buildx-plugin docker-compose-plugin; then\n") @@ -249,6 +256,25 @@ func buildModulesCommand(modulesBase string) string { return fmt.Sprintf("bash -se <<'EOF'\nset -euo pipefail\nmkdir -p /lib/modules\ntar -C /lib/modules -xf -\ndepmod -a %s\nmkdir -p /etc/modules-load.d\nprintf 'nf_tables\\nnft_chain_nat\\nveth\\nbr_netfilter\\noverlay\\n' > /etc/modules-load.d/docker-netfilter.conf\nmkdir -p /etc/sysctl.d\ncat > /etc/sysctl.d/99-docker.conf <<'SYSCTL'\nnet.bridge.bridge-nf-call-iptables = 1\nnet.bridge.bridge-nf-call-ip6tables = 1\nnet.ipv4.ip_forward = 1\nSYSCTL\nsysctl --system >/dev/null 2>&1 || true\nEOF", shellQuote(modulesBase)) } +func appendMiseSetup(script *bytes.Buffer) { + fmt.Fprintf(script, "curl -fsSL https://mise.run | MISE_INSTALL_PATH=%s MISE_VERSION=%s sh\n", shellQuote(defaultMiseInstallPath), shellQuote(defaultMiseVersion)) + script.WriteString("mkdir -p /etc/profile.d\n") + script.WriteString("cat > /etc/profile.d/mise.sh <<'EOF'\n") + fmt.Fprintf(script, "if [ -n \"${BASH_VERSION:-}\" ] && [ -x %s ]; then\n", shellQuote(defaultMiseInstallPath)) + fmt.Fprintf(script, " %s\n", defaultMiseActivateLine) + script.WriteString("fi\n") + script.WriteString("EOF\n") + script.WriteString("chmod 0644 /etc/profile.d/mise.sh\n") + appendLineIfMissing(script, "/etc/bash.bashrc", defaultMiseActivateLine) +} + +func appendLineIfMissing(script *bytes.Buffer, path, line string) { + fmt.Fprintf(script, "touch %s\n", shellQuote(path)) + fmt.Fprintf(script, "if ! grep -Fqx %s %s; then\n", shellQuote(line), shellQuote(path)) + fmt.Fprintf(script, " printf '\\n%%s\\n' %s >> %s\n", shellQuote(line), shellQuote(path)) + script.WriteString("fi\n") +} + func shellArray(values []string) string { quoted := make([]string, 0, len(values)) for _, value := range values { diff --git a/internal/daemon/imagebuild_test.go b/internal/daemon/imagebuild_test.go new file mode 100644 index 0000000..86b2733 --- /dev/null +++ b/internal/daemon/imagebuild_test.go @@ -0,0 +1,23 @@ +package daemon + +import ( + "strings" + "testing" +) + +func TestBuildProvisionScriptInstallsAndActivatesMise(t *testing.T) { + t.Parallel() + + script := buildProvisionScript("devbox", "1.1.1.1", []string{"git", "curl"}, false) + for _, snippet := range []string{ + "curl -fsSL https://mise.run | MISE_INSTALL_PATH='/usr/local/bin/mise' MISE_VERSION='v2025.12.0' sh", + "cat > /etc/profile.d/mise.sh <<'EOF'", + "if [ -n \"${BASH_VERSION:-}\" ] && [ -x '/usr/local/bin/mise' ]; then", + `eval "$(/usr/local/bin/mise activate bash)"`, + `if ! grep -Fqx 'eval "$(/usr/local/bin/mise activate bash)"' '/etc/bash.bashrc'; then`, + } { + if !strings.Contains(script, snippet) { + t.Fatalf("buildProvisionScript missing snippet %q\nscript:\n%s", snippet, script) + } + } +}