#!/usr/bin/env bash # Runs inside the Ubuntu 22.04 build container (see build.sh). set -euo pipefail trap 'echo "inside.sh:$LINENO: \`$BASH_COMMAND\` failed with exit $?" >&2' ERR : "${ALBEDO_VERSION:?}" OTP_VERSION=29.1.1 GLEAM_URL=https://github.com/gleam-lang/gleam/releases/download/v1.19.0/gleam-v1.19.0-x86_64-unknown-linux-musl.tar.gz REBAR3_URL=https://github.com/erlang/rebar3/releases/download/3.27.1/rebar3 GO_URL=https://go.dev/dl/go1.26.8.linux-amd64.tar.gz NODE_URL=https://nodejs.org/dist/v24.21.0/node-v24.21.0-linux-x64.tar.xz PYTHON_URL='https://github.com/astral-sh/python-build-standalone/releases/download/20261026/cpython-3.11.18%2B20261026-x86_64-unknown-linux-gnu-install_only_stripped.tar.gz' RG_URL=https://github.com/BurntSushi/ripgrep/releases/download/15.2.0/ripgrep-15.2.0-x86_64-unknown-linux-musl.tar.gz NFPM_URL=https://github.com/goreleaser/nfpm/releases/download/v2.48.0/nfpm_2.48.0_Linux_x86_64.tar.gz # The AppImage runtime: a static ELF the squashfs is appended to. Assembling the # image by hand never executes an AppImage, which hosts that hand AppImages to a # binfmt_misc interpreter (NixOS) cannot do inside the container. RUNTIME_URL=https://github.com/AppImage/type2-runtime/releases/download/20251108/runtime-x86_64 OTP_URL=https://github.com/erlang/otp/releases/download/OTP-$OTP_VERSION/otp_src_$OTP_VERSION.tar.gz # The OTP applications the daemon calls into (static imports of every shipped # module), plus asn1, which public_key needs, and sasl for its boot scripts. OTP_APPS="erts kernel stdlib compiler crypto public_key asn1 ssl inets runtime_tools syntax_tools sasl" export DEBIAN_FRONTEND=noninteractive apt-get update -qq apt-get install -y -qq --no-install-recommends \ build-essential autoconf libncurses-dev libssl-dev perl curl ca-certificates \ xz-utils zstd file git squashfs-tools >/dev/null mkdir -p /cache/dl fetch() { local name="${2:-$(basename "${1%%\?*}")}" name="${name//%2B/+}" if [ ! -s "/cache/dl/$name" ]; then curl -fsSL --retry 3 -o "/cache/dl/$name.part" "$1" mv "/cache/dl/$name.part" "/cache/dl/$name" fi echo "/cache/dl/$name" } # --- Erlang/OTP, compiled once and cached. Nothing it builds may need a host # library beyond glibc: OpenSSL is linked into crypto, libstdc++ and libgcc # (the JIT is C++) into the emulator, and termcap, which only the interactive # shell uses, is left out (NixOS, for one, has no libtinfo where ld.so looks). # Bump OTP_BUILD when the configure flags change, so the cache recompiles. OTP_BUILD=2 otp="/cache/otp-$OTP_VERSION-b$OTP_BUILD" if [ ! -x "$otp/bin/erl" ]; then rm -rf /tmp/otp && mkdir /tmp/otp tar -xzf "$(fetch "$OTP_URL")" -C /tmp/otp --strip-components=1 (cd /tmp/otp && ./configure --prefix="$otp" --with-ssl --disable-dynamic-ssl-lib \ --without-wx --without-javac --without-odbc --without-jinterface \ --without-debugger --without-observer --without-et --without-megaco \ --without-diameter --without-snmp --without-termcap \ LDFLAGS="-static-libstdc++ -static-libgcc" >/tmp/otp-configure.log && make -j"$(nproc)" >/tmp/otp-make.log 2>&1 && make install >/tmp/otp-install.log 2>&1) || { tail -n 60 /tmp/otp-configure.log /tmp/otp-make.log /tmp/otp-install.log 2>/dev/null exit 1 } fi export PATH="$otp/bin:$PATH" # --- build tools mkdir -p /tools/bin tar -xzf "$(fetch "$GLEAM_URL")" -C /tools/bin install -m 755 "$(fetch "$REBAR3_URL")" /tools/bin/rebar3 tar -xzf "$(fetch "$GO_URL")" -C /tools tar -xJf "$(fetch "$NODE_URL")" -C /tools && mv /tools/node-v* /tools/node tar -xzf "$(fetch "$NFPM_URL")" -C /tools/bin nfpm export PATH="/tools/bin:/tools/go/bin:/tools/node/bin:$PATH" export HOME=/tmp/home GOPATH=/cache/gopath GOCACHE=/cache/gocache npm_config_cache=/cache/npm mkdir -p "$HOME" mkdir -p /work/src && tar -xf /work/src.tar -C /work/src cd /work/src # --- the install tree, identical for the AppImage and the packages: # bin/albedo (CLI), bin/rg, bin/albedo-daemon (sets PATH, runs the shipment) # lib/albedo/ (the sealed daemon shipment), erlang/, python/, web/ # bin/albedo-daemon beside lib/albedo/albedo is the layout the CLI looks for a # build seal in, so packaged installs boot from a standby. tree=/work/tree rm -rf "$tree" && mkdir -p "$tree/bin" "$tree/lib" gleam export erlang-shipment >/tmp/gleam.log 2>&1 || { tail -n 60 /tmp/gleam.log exit 1 } cp -R build/erlang-shipment "$tree/lib/albedo" install -m 755 /work/release/albedo-daemon "$tree/bin/albedo-daemon" mkdir -p "$tree/erlang/lib/erlang/lib" cp -R "$otp/lib/erlang/bin" "$otp/lib/erlang/releases" "$tree/erlang/lib/erlang/" cp -R "$otp"/lib/erlang/erts-* "$tree/erlang/lib/erlang/" for a in $OTP_APPS; do for d in "$otp"/lib/erlang/lib/"$a"-*; do mkdir -p "$tree/erlang/lib/erlang/lib/$(basename "$d")" for sub in ebin priv; do [ -d "$d/$sub" ] && cp -R "$d/$sub" "$tree/erlang/lib/erlang/lib/$(basename "$d")/" done done done rm -rf "$tree"/erlang/lib/erlang/erts-*/{include,lib,src,doc,man} # OTP's erl finds its root from its own location (find_rootdir), so the moved # tree runs from wherever it is installed or mounted. tar -xzf "$(fetch "$PYTHON_URL")" -C "$tree" py="$tree/python/lib/python3.11" rm -rf "$py"/{test,idlelib,tkinter,turtledemo,ensurepip,lib2to3} "$py"/config-3.11-* \ "$tree"/python/lib/{tcl,tk,itcl,thread}* "$py"/lib-dynload/_tkinter* \ "$py"/lib-dynload/_crypt.* # crypt (removed in 3.13) needs the host's libcrypt.so.1 find "$py" -name tests -type d -prune -exec rm -rf {} + CGO_ENABLED=0 go -C cli build -trimpath -ldflags "-s -w" -o "$tree/bin/albedo" ./cmd/albedo (cd web && npm ci --no-audit --no-fund >/tmp/npm.log 2>&1 && npm run build >>/tmp/npm.log 2>&1) || { tail -n 40 /tmp/npm.log exit 1 } cp -R web/dist "$tree/web" tar -xzf "$(fetch "$RG_URL")" -C /tmp && cp /tmp/ripgrep-*/rg "$tree/bin/rg" # Installed and mounted trees are read-only: compile Python once, hash-checked # because packaging may reset mtimes. Then seal the daemon app, last, since # sealing hashes the finished tree. "$tree/python/bin/python3" -m compileall -q -j0 --invalidation-mode checked-hash \ "$py" "$tree/lib/albedo/albedo/priv/python" >/dev/null || true ALBEDO_BUILD_APP="$tree/lib/albedo/albedo" erl +S 2:2 -pa "$tree"/lib/albedo/*/ebin -noshell \ -eval 'application:ensure_all_started(crypto), ok = albedo_daemon:seal_build(unicode:characters_to_binary(os:getenv("ALBEDO_BUILD_APP"))), halt().' test -s "$tree/lib/albedo/albedo/.build-digest" # --- every bundled ELF may need only glibc, or a library the tree itself ships bundled_libs="$(find "$tree" -name '*.so*' -printf '%f\n' | sort -u)" bad="$(find "$tree" -type f -exec sh -c 'file -b "$1" | grep -q "^ELF" && readelf -d "$1" 2>/dev/null | sed -n "s/.*Shared library: \[\(.*\)\]/\1/p" | sed "s|.*/||" | grep -vE "^(libc|libm|libdl|libpthread|librt|libutil|ld-linux-x86-64)\.so" | grep -vxF "$2" | sed "s|^|$1: |"' _ {} "$bundled_libs" \;)" if [ -n "$bad" ]; then echo "bundled binaries need host libraries beyond glibc:" echo "$bad" exit 1 fi # --- AppImage: the tree plus AppRun, desktop entry and icon app=/work/AppDir rm -rf "$app" && cp -a "$tree" "$app" sed "s|@VERSION@|$ALBEDO_VERSION|" /work/release/appimage/AppRun >"$app/AppRun" chmod 755 "$app/AppRun" cp /work/release/appimage/albedo.desktop /work/release/appimage/albedo.svg "$app/" ln -s albedo.svg "$app/.DirIcon" out="/out/$ALBEDO_VERSION" rm -rf "$out" && mkdir -p "$out" target="$out/albedo-$ALBEDO_VERSION-x86_64.AppImage" mksquashfs "$app" /tmp/payload.squashfs -root-owned -noappend -comp zstd \ -Xcompression-level 19 -b 1M -quiet -no-progress cat "$(fetch "$RUNTIME_URL")" /tmp/payload.squashfs >"$target.part" chmod 755 "$target.part" && mv "$target.part" "$target" # --- deb, rpm and Arch packages: the tree under /usr/lib/albedo maintainer="${PACKAGE_MAINTAINER:-nekomimi.pet }" sed -e "s|@VERSION@|$ALBEDO_VERSION|" -e "s|@MAINTAINER@|$maintainer|" \ -e "s|@TREE@|$tree|" -e "s|@LAUNCHER@|/work/release/packages/albedo|" \ /work/release/packages/nfpm.yaml >/tmp/nfpm.yaml for packager in deb rpm archlinux; do nfpm package --config /tmp/nfpm.yaml --packager "$packager" --target "$out/" >/dev/null done du -h "$out"/*