Files
alice-backend/scripts/build-spotify-runtime.sh
T

95 lines
4.4 KiB
Bash
Executable File

#!/usr/bin/env bash
# Build on an x86-64 Linux Docker host; writes the archive to the current directory.
set -euo pipefail
out="${1:-$PWD/alice-spotify-runtime.tar.gz}"
work="$(mktemp -d)"
trap 'rm -rf "$work"' EXIT
cat > "$work/build.sh" <<'INNER'
#!/bin/bash
set -euo pipefail
export DEBIAN_FRONTEND=noninteractive
dpkg --add-architecture arm64
apt-get update -qq
apt-get install -y -qq --no-install-recommends curl ca-certificates binutils python3 > /dev/null
mkdir -p /work/debs/partial /work/root /work/out/lib /work/out/bin /work/out/etc
# An empty dpkg status prevents installed amd64 packages from satisfying arm64 dependencies.
apt-get -y --download-only --no-install-recommends -o APT::Architecture=arm64 -o Dir::State::status=/dev/null -o Dir::Cache::archives=/work/debs install pulseaudio:arm64 pulseaudio-utils:arm64 > /work/apt.log
for f in /work/debs/*.deb; do dpkg-deb -x "$f" /work/root; done
curl -fL --retry 3 https://soloist-builds.spotifycdn.com/soloist_release_arm64.tar.gz -o /work/soloist.tar.gz
mkdir /work/soloist
tar -xzf /work/soloist.tar.gz -C /work/soloist
find /work/soloist -type f -maxdepth 4 -print
cp /work/root/usr/bin/pulseaudio /work/root/usr/bin/pacat /work/root/usr/bin/pactl /work/out/bin/
cp -a /work/soloist/. /work/out/soloist/
# Follow DT_NEEDED edges, including module dependencies and PulseAudio's private libs.
python3 - <<'PY'
import os, subprocess, shutil, glob
root='/work/root'; out='/work/out'; paths={}
for base, dirs, files in os.walk(root):
for f in files:
p=os.path.join(base,f)
if '.so' in f and os.path.isfile(p): paths.setdefault(f,p)
seeds=[out+'/bin/pulseaudio',out+'/bin/pacat',out+'/bin/pactl']
for base,dirs,files in os.walk(out+'/soloist'):
for f in files:
p=os.path.join(base,f)
if open(p,'rb').read(4)==b'\x7fELF': seeds.append(p)
mods=glob.glob(root+'/usr/lib/aarch64-linux-gnu/pulseaudio/modules/module-{alsa-sink,native-protocol-unix}.so')
# glob does not expand braces.
mods=[glob.glob(root+'/usr/lib/**/module-'+n+'.so',recursive=True)[0] for n in ('alsa-sink','native-protocol-unix','remap-sink')]
for p in mods: shutil.copy2(p,out+'/lib/'); seeds.append(out+'/lib/'+os.path.basename(p))
seen=set(); missing=set()
while seeds:
p=seeds.pop()
try: s=subprocess.check_output(['readelf','-d',p],stderr=subprocess.DEVNULL,text=True)
except subprocess.CalledProcessError: continue
for line in s.splitlines():
if '(NEEDED)' not in line: continue
name=line.split('[')[1].split(']')[0]
if name in seen: continue
seen.add(name)
if name not in paths: missing.add(name); continue
src=paths[name]; dest=out+'/lib/'+name
shutil.copy2(src,dest,follow_symlinks=True); seeds.append(dest)
if missing: raise SystemExit('Missing ELF dependencies: '+', '.join(sorted(missing)))
loader=glob.glob(root+'/lib/ld-linux-aarch64.so.1')+glob.glob(root+'/usr/lib/aarch64-linux-gnu/ld-linux-aarch64.so.1')
shutil.copy2(loader[0],out+'/lib/ld-linux-aarch64.so.1',follow_symlinks=True)
PY
# ALSA configuration is data loaded at runtime, not an ELF dependency.
cp -a /work/root/usr/share/alsa /work/out/alsa
cat > /work/out/etc/default.pa <<'PA'
load-module module-native-protocol-unix auth-anonymous=1
load-module module-alsa-sink sink_name=station device=hw:0,0
load-module module-remap-sink sink_name=music master=station
set-default-sink music
PA
cat > /work/out/etc/daemon.conf <<'PA'
daemonize = no
exit-idle-time = -1
flat-volumes = no
PA
cat > /work/out/run <<'RUN'
#!/bin/sh
set -eu
base=$(CDPATH= cd -- "$(dirname -- "$0")" && pwd)
export ALSA_CONFIG_PATH="$base/alsa/alsa.conf"
export ALSA_CONFIG_DIR="$base/alsa"
export PULSE_RUNTIME_PATH="${PULSE_RUNTIME_PATH:-/data/local/tmp/alice-pulse}"
export PULSE_CONFIG_PATH="$base/etc"
mkdir -p "$PULSE_RUNTIME_PATH"
exec "$base/lib/ld-linux-aarch64.so.1" --library-path "$base/lib" "$@"
RUN
chmod +x /work/out/run
(cd /work/out && find . -type f ! -name SHA256SUMS -print0 | sort -z | xargs -0 sha256sum > SHA256SUMS)
for f in /work/debs/*.deb; do dpkg-deb -f "$f" Package Version Architecture; printf "\n"; done > /work/out/DEBIAN-PACKAGES.txt
(cd /work/out && sha256sum DEBIAN-PACKAGES.txt >> SHA256SUMS)
sha256sum /work/soloist.tar.gz > /work/out/SOLOIST-ARCHIVE-SHA256.txt
(cd /work/out && sha256sum SOLOIST-ARCHIVE-SHA256.txt >> SHA256SUMS)
tar -czf /output/runtime.tar.gz -C /work/out .
du -sh /work/out /output/runtime.tar.gz
INNER
chmod +x "$work/build.sh"
docker run --rm --platform linux/amd64 -v "$work:/output" debian:trixie-slim bash /output/build.sh
cp "$work/runtime.tar.gz" "$out"
printf 'Built %s\n' "$out"