95 lines
4.4 KiB
Bash
Executable File
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"
|