#!/usr/bin/env bash # Packer provisioner for the Open SWE base AMI (ARM64 Ubuntu 24.04). # # Bakes the runtime + boot-time templates ONLY. No secrets, no env-specific # values. Everything env-specific is materialized at first boot by user-data.sh # + deploy/seahaven/fetch-config.sh. set -euo pipefail PYTHON_VERSION="${PYTHON_VERSION:-3.12}" NODE_MAJOR="${NODE_MAJOR:-24}" UV_VERSION="${UV_VERSION:-0.11.24}" CLOUDWATCH_AGENT_DEB_URL="${CLOUDWATCH_AGENT_DEB_URL:?}" AWSCLI_ZIP_URL="${AWSCLI_ZIP_URL:?}" # Layout (must match user-data.sh and the templates). SERVICE_USER="openswe" APP_DIR="/opt/open-swe/app" SERVICE_HOME="/opt/open-swe" WWW_ROOT="/var/www/open-swe" TEMPLATE_DIR="/opt/open-swe/templates" LOG_DIR="/var/log/open-swe" UV_BIN="/usr/local/bin/uv" export DEBIAN_FRONTEND=noninteractive echo "==> apt base packages" apt-get update -y apt-get upgrade -y apt-get install -y --no-install-recommends \ nginx jq curl unzip ca-certificates gnupg lsb-release \ build-essential pkg-config git acl echo "==> awscli v2 (aarch64)" tmp="$(mktemp -d)" curl -fsSL "$AWSCLI_ZIP_URL" -o "$tmp/awscliv2.zip" unzip -q "$tmp/awscliv2.zip" -d "$tmp" "$tmp/aws/install" --update rm -rf "$tmp" aws --version echo "==> CloudWatch agent (arm64)" tmp="$(mktemp -d)" curl -fsSL "$CLOUDWATCH_AGENT_DEB_URL" -o "$tmp/amazon-cloudwatch-agent.deb" dpkg -i -E "$tmp/amazon-cloudwatch-agent.deb" rm -rf "$tmp" # Do NOT enable/start the agent during the build; user-data fetches its config # (with env-specific log-group names + 30-day retention) and starts it at boot. systemctl disable amazon-cloudwatch-agent.service || true echo "==> uv ${UV_VERSION} + Python ${PYTHON_VERSION} (system-wide)" export UV_INSTALL_DIR=/usr/local/bin curl -fsSL "https://astral.sh/uv/${UV_VERSION}/install.sh" | env UV_NO_MODIFY_PATH=1 sh "$UV_BIN" --version # Pre-install the interpreter so the box never reaches out at boot to build a venv. UV_PYTHON_INSTALL_DIR=/opt/uv/python "$UV_BIN" python install "$PYTHON_VERSION" echo "==> node ${NODE_MAJOR} + bun (build-time UI tooling only; the SPA is built in CI)" curl -fsSL "https://deb.nodesource.com/setup_${NODE_MAJOR}.x" | bash - apt-get install -y --no-install-recommends nodejs node --version # bun installed system-wide; used only if any UI tooling must run on-box. The # production SPA build runs in GitHub Actions -> S3 (no on-box build, no swapfile). export BUN_INSTALL=/usr/local curl -fsSL https://bun.sh/install | bash /usr/local/bin/bun --version || true echo "==> non-login service user '${SERVICE_USER}'" if ! id "$SERVICE_USER" >/dev/null 2>&1; then useradd --system --create-home --home-dir "$SERVICE_HOME" \ --shell /usr/sbin/nologin "$SERVICE_USER" fi echo "==> directories" install -d -o "$SERVICE_USER" -g "$SERVICE_USER" -m 0755 "$SERVICE_HOME" "$APP_DIR" install -d -o "$SERVICE_USER" -g "$SERVICE_USER" -m 0755 "$WWW_ROOT" install -d -o "$SERVICE_USER" -g "$SERVICE_USER" -m 0750 "$LOG_DIR" install -d -o root -g root -m 0755 "$TEMPLATE_DIR" echo "==> stage boot-time templates into the image" cp /tmp/open-swe-templates/* "$TEMPLATE_DIR/" chown root:root "$TEMPLATE_DIR"/* chmod 0644 "$TEMPLATE_DIR"/* rm -rf /tmp/open-swe-templates echo "==> tmpfs for the runtime .env (root/owner-only, noexec/nosuid/nodev)" # /run is already tmpfs on Ubuntu; this is an explicit, deliberately-small mount # scoped to the service user so the materialized .env never touches disk. if ! grep -q '/run/open-swe' /etc/fstab; then cat >>/etc/fstab < disable nginx default site (open-swe site is installed at boot)" rm -f /etc/nginx/sites-enabled/default systemctl enable nginx echo "==> harden: no password auth, IMDSv2 already enforced by launch template" # (sshd is not exposed publicly — instance is in a private subnet, SG inbound = ALB only.) echo "==> clean apt caches" apt-get clean rm -rf /var/lib/apt/lists/* echo "==> provision complete"