#!/bin/bash # Image build script. Run once by the Dockerfile; removes itself when done. # Everything that is not pinned by an environment variable resolves to its # current release at build time. set -euxo pipefail export DEBIAN_FRONTEND=noninteractive arch="$(dpkg --print-architecture)" latest_tag() { curl -fsSLI -o /dev/null -w '%{url_effective}' \ "https://github.com/$1/releases/latest" | sed 's,.*/tag/,,' } # --- distro packages ------------------------------------------------------- apt-get update apt-get -y full-upgrade apt-get -y install --no-install-recommends \ bzip2 ca-certificates curl git gnupg jq less openssh-client rsync \ unzip wget xz-utils zip \ build-essential make pkg-config \ runit \ golang-go \ nodejs npm \ python3 python3-dev python3-pip python3-venv virtualenv \ libbz2-dev libffi-dev libgdbm-dev liblzma-dev libncurses-dev \ libreadline-dev libsqlite3-dev libssl-dev libxml2-dev libxmlsec1-dev \ tk-dev uuid-dev zlib1g-dev \ shellcheck \ tzdata \ zstd sqlite3 \ libvips-dev libvips-tools libheif-dev libheif-plugin-aomenc \ imagemagick ghostscript \ ruby ruby-dev bundler \ valkey-server valkey-tools postgresql \ nginx # Link the PostgreSQL server binaries onto PATH. They live under a versioned # directory (/usr/lib/postgresql/*/bin); the client psql is already on PATH. for p in postgres initdb pg_ctl; do ln -s /usr/lib/postgresql/*/bin/"$p" /usr/local/bin/"$p" done # --- Go (latest) ------------------------------------------------------------- gover="$(curl -fsSL 'https://go.dev/VERSION?m=text' | head -n1)" curl -fsSL "https://go.dev/dl/${gover}.linux-${arch}.tar.gz" | tar -C /usr/local -xz rm -rf /usr/local/go/test # --- runsvinit (PID 1: reaps zombies, stops services on SIGTERM) ------------- git clone -q https://github.com/peterbourgon/runsvinit /tmp/runsvinit git -C /tmp/runsvinit checkout -q "$RUNSVINIT_COMMIT" ( cd /tmp/runsvinit go mod init runsvinit >/dev/null 2>&1 CGO_ENABLED=0 go build -trimpath -ldflags='-s -w' -o /usr/local/sbin/runsvinit . ) # --- golangci-lint ----------------------------------------------------------- gl="$(latest_tag golangci/golangci-lint)" curl -fsSL "https://github.com/golangci/golangci-lint/releases/download/${gl}/golangci-lint-${gl#v}-linux-${arch}.tar.gz" | tar -C /usr/local/bin -xz --strip-components=1 "golangci-lint-${gl#v}-linux-${arch}/golangci-lint" # --- govulncheck (Go tool) --------------------------------------------------- GOBIN=/usr/local/bin go install golang.org/x/vuln/cmd/govulncheck@latest # --- Hugo (extended) --------------------------------------------------------- hv="$(latest_tag gohugoio/hugo)" curl -fsSL -o /tmp/hugo.deb \ "https://github.com/gohugoio/hugo/releases/download/${hv}/hugo_extended_${hv#v}_linux-${arch}.deb" apt-get -y install /tmp/hugo.deb # --- nvm, Node LTS, yarn, wrangler ------------------------------------------ git clone -q --depth 1 --branch "$NVM_GIT_TAG" https://github.com/nvm-sh/nvm.git "$NVM_DIR" rm -rf "$NVM_DIR/.git" set +ux # nvm.sh is not clean under set -u / too noisy under set -x . "$NVM_DIR/nvm.sh" nvm install --lts nvm alias default "lts/*" nvm use default set -ux npm install -g npm@latest corepack wrangler prettier playwright corepack enable # Pre-populate the corepack cache so projects that pin a package manager # do not download at run time. The last yarn installed becomes the default. corepack install -g pnpm@latest corepack install -g yarn@1 corepack install -g yarn@stable # pnpm 10+ fetches a standalone binary on first run; trigger that now so # it lands in COREPACK_HOME. pnpm --version yarn --version nvm cache clear # --- browsers for tests: Firefox, geckodriver, Playwright -------------------- # Ubuntu's firefox is a snap that does not run in a container, so use # Mozilla's own apt repository for the real binary. install -d -m 0755 /etc/apt/keyrings curl -fsSL https://packages.mozilla.org/apt/repo-signing-key.gpg \ -o /etc/apt/keyrings/packages.mozilla.org.asc echo "deb [signed-by=/etc/apt/keyrings/packages.mozilla.org.asc] https://packages.mozilla.org/apt mozilla main" \ > /etc/apt/sources.list.d/mozilla.list cat > /etc/apt/preferences.d/mozilla <<'EOF' Package: * Pin: origin packages.mozilla.org Pin-Priority: 1000 EOF apt-get update apt-get -y install --no-install-recommends -t mozilla firefox # geckodriver, at its latest release (independent of the Firefox version). gd="$(latest_tag mozilla/geckodriver)" case "$arch" in arm64) gd_arch=linux-aarch64 ;; *) gd_arch=linux64 ;; esac curl -fsSL "https://github.com/mozilla/geckodriver/releases/download/${gd}/geckodriver-${gd}-${gd_arch}.tar.gz" | tar -C /usr/local/bin -xz geckodriver # Playwright's Chromium and Firefox. WebKit is skipped because no repository # uses it today. Playwright's Chromium is also the headless Chrome for # chromedp-style clients, exposed as `chromium` on PATH. playwright install --with-deps chromium firefox chromiums=( "$PLAYWRIGHT_BROWSERS_PATH"/chromium-*/chrome-linux*/chrome ) if [ "${#chromiums[@]}" -ne 1 ] || [ ! -x "${chromiums[0]}" ]; then echo "error: expected exactly one chromium binary, found ${#chromiums[@]}" >&2 exit 1 fi ln -s "${chromiums[0]}" /usr/local/bin/chromium # --- pyenv, Python (latest), pip, virtualenv --------------------------------- git clone -q --depth 1 --branch "$PYENV_GIT_TAG" https://github.com/pyenv/pyenv.git "$PYENV_ROOT" rm -rf "$PYENV_ROOT/.git" pyver="$(pyenv latest --known 3)" pyenv install "$pyver" pyenv global "$pyver" rm -rf "$PYENV_ROOT"/versions/*/lib/python3*/test python3 -m pip install --no-cache-dir --upgrade pip setuptools wheel virtualenv pyenv rehash # --- cleanup ----------------------------------------------------------------- go clean -cache -modcache -testcache apt-get -y autoremove --purge apt-get clean rm -rf /var/lib/apt/lists/* /var/cache/apt/* /var/cache/debconf/*-old /var/log/* rm -rf /usr/share/doc /usr/share/man /usr/share/info /usr/share/lintian find /usr/share/locale -mindepth 1 -maxdepth 1 ! -name 'en*' ! -name 'locale.alias' -exec rm -rf {} + rm -rf /root/.cache /root/.npm /root/.config /root/go /tmp/* /var/tmp/* # nginx and Valkey open their log files at startup; recreate the log # directories the cleanup above removed, with their package owners. install -d -o root -g adm /var/log/nginx install -d -o valkey -g adm -m 2750 /var/log/valkey # --- sanity ------------------------------------------------------------------ go version govulncheck -version golangci-lint version shellcheck --version test -e /usr/share/zoneinfo/America/New_York zstd --version sqlite3 --version vips --version vips -l | grep -i heif > /dev/null magick --version gs --version ruby --version bundle --version valkey-server --version valkey-cli --version valkey-server /etc/valkey/valkey.conf ok=; for i in $(seq 1 50); do if valkey-cli ping 2>/dev/null | grep -q PONG; then ok=1; break; fi; sleep 0.1; done; [ -n "$ok" ] valkey-cli shutdown nosave psql --version postgres --version nginx -t hugo version node --version npm --version COREPACK_ENABLE_NETWORK=0 yarn --version COREPACK_ENABLE_NETWORK=0 pnpm --version wrangler --version prettier --version playwright --version firefox --version geckodriver --version chromium --version python3 --version pip --version virtualenv --version test -x /usr/local/sbin/runsvinit test -d /etc/service # The checks above leave caches and log files behind. rm -rf /tmp/* /var/tmp/* /root/.cache /root/.npm /root/.config rm -f /var/log/valkey/* rm -f /var/log/nginx/* install -d -o valkey -g adm -m 2750 /var/log/valkey rm -f "$0"