Add reproducible Radxa AirPlay and Bluetooth mixer setup

This commit is contained in:
jsahn
2026-10-12 17:16:51 +09:00
commit ceef64dfed
15 changed files with 1151 additions and 0 deletions
Executable
+157
View File
@@ -0,0 +1,157 @@
#!/bin/bash
# Run on the Radxa itself: sudo bash install.sh --name "Radxa Desk"
set -euo pipefail
PACKAGE_DIR=$(cd -- "$(dirname -- "${BASH_SOURCE[0]}")" && pwd)
AIRPLAY_NAME='Radxa Audio'
DAC_PCM='hw:CARD=Audio,DEV=0'
WIFI5=0
JOBS=1
usage() {
cat <<'HELP'
Usage: sudo bash install.sh [--name NAME] [--dac hw:CARD=Audio,DEV=0] [--wifi5] [--jobs 1|2]
Default output is USB DAC. Pair a headphone after installation to select Bluetooth output.
--wifi5 only applies to aic8800_fdrv with ps_on and he_on module parameters.
HELP
}
while (( $# )); do
case "$1" in
--name) AIRPLAY_NAME=${2:?Missing name}; shift 2 ;;
--dac) DAC_PCM=${2:?Missing DAC PCM}; shift 2 ;;
--wifi5) WIFI5=1; shift ;;
--jobs) JOBS=${2:?Missing jobs}; shift 2 ;;
-h|--help) usage; exit 0 ;;
*) usage >&2; exit 2 ;;
esac
done
[[ $EUID == 0 ]] || { echo 'sudo로 실행해 주세요.' >&2; exit 1; }
[[ "$JOBS" == 1 || "$JOBS" == 2 ]] || { echo '--jobs는 1 또는 2입니다.' >&2; exit 1; }
[[ $(uname -s) == Linux ]] || { echo 'Radxa의 Linux에서 실행해 주세요.' >&2; exit 1; }
# This release is deliberately restricted to the package environment we inspected.
# shellcheck source=/dev/null
. /etc/os-release
[[ ${ID:-} == debian && ${VERSION_ID:-} == 13 ]] || {
echo '이 설치 자료는 Debian 13 기반 Armbian용입니다. 다른 OS에서는 패키지 검증이 필요합니다.' >&2; exit 1;
}
command -v python3 >/dev/null
command -v systemctl >/dev/null
systemctl --version >/dev/null
python3 - "$PACKAGE_DIR" "$AIRPLAY_NAME" "$DAC_PCM" <<'PY'
import hashlib, importlib.util, json, pathlib, sys
base=pathlib.Path(sys.argv[1])
spec=importlib.util.spec_from_file_location('manager',base/'scripts/radxa-audio.py')
m=importlib.util.module_from_spec(spec); spec.loader.exec_module(m)
m.validate(dict(name=sys.argv[2],dac_pcm=sys.argv[3],output='dac',bluetooth_device=''))
manifest=json.loads((base/'sources/manifest.json').read_text())
for source in manifest['sources']:
p=base/'sources'/source['file']
assert hashlib.sha256(p.read_bytes()).hexdigest()==source['sha256'], 'Source checksum mismatch: '+p.name
PY
if (( WIFI5 )); then
modinfo aic8800_fdrv >/dev/null
modinfo -p aic8800_fdrv | grep -q '^ps_on:'
modinfo -p aic8800_fdrv | grep -q '^he_on:'
fi
STAMP=$(date +%Y%m%d-%H%M%S)-$$
BACKUP_DIR=/var/backups/radxa-audio-install/$STAMP
python3 "$PACKAGE_DIR/scripts/backup.py" save "$BACKUP_DIR"
echo "기존 설정 백업: $BACKUP_DIR"
failed() {
local result=$?
trap - EXIT
if (( result != 0 )); then
echo '설치 실패: 서비스와 관리 대상 설정을 복구합니다.' >&2
python3 "$PACKAGE_DIR/scripts/backup.py" restore "$BACKUP_DIR" || {
echo "자동 복구 실패. sudo python3 scripts/backup.py restore $BACKUP_DIR 로 확인해 주세요." >&2;
}
fi
exit "$result"
}
trap failed EXIT
apt-get update
DEBIAN_FRONTEND=noninteractive apt-get install -y \
build-essential git autoconf automake libtool pkg-config xmltoman \
libpopt-dev libconfig-dev libssl-dev libsoxr-dev libasound2-dev \
libavahi-client-dev libavcodec-dev libavformat-dev libavutil-dev libswresample-dev uuid-dev \
libplist-dev libsodium-dev libgcrypt20-dev avahi-daemon \
bluez bluez-alsa-utils libasound2-plugin-bluez alsa-utils python3
# Verify hardware and installed-package capabilities before switching outputs.
[[ -d /sys/class/bluetooth/hci0 ]] || { echo '블루투스 컨트롤러가 없습니다.' >&2; exit 1; }
modinfo snd-aloop >/dev/null
bluealsa --help | grep -q a2dp-sink
bluealsa --help | grep -q a2dp-source
bluealsa-aplay --help | grep -q -- --volume
runuser -u root -- aplay -D "$DAC_PCM" -t raw -f S16_LE -r 44100 -c 2 -d 1 /dev/zero
BUILD_DIR=$(mktemp -d /var/tmp/radxa-audio-build.XXXXXX)
echo "빌드 디렉토리: $BUILD_DIR"
git clone --quiet --branch 1.2.8 "$PACKAGE_DIR/sources/nqptp-1.2.8.bundle" "$BUILD_DIR/nqptp"
git clone --quiet --branch 5.5.2 "$PACKAGE_DIR/sources/shairport-sync-5.5.2.bundle" "$BUILD_DIR/shairport-sync"
[[ $(git -C "$BUILD_DIR/nqptp" rev-parse HEAD) == c925f27c1fd12e4033ac477e5a405969b0b0260b ]]
[[ $(git -C "$BUILD_DIR/shairport-sync" rev-parse HEAD) == 7bad231c18368dbd26f298577f6210e36e4b0797 ]]
(
cd "$BUILD_DIR/nqptp"
autoreconf -fi
./configure --prefix=/usr/local --with-systemd-startup --with-systemdsystemunitdir=/etc/systemd/system
make -j "$JOBS"
)
(
cd "$BUILD_DIR/shairport-sync"
autoreconf -fi
./configure --prefix=/usr/local --sysconfdir=/etc --with-alsa --with-soxr \
--with-avahi --with-ssl=openssl --with-systemd-startup \
--with-systemdsystemunitdir=/etc/systemd/system --with-airplay-2
make -j "$JOBS"
)
# Finish both builds before stopping the existing receiver.
systemctl stop shairport-sync nqptp bluealsa-aplay radxa-bt-output radxa-bose-output 2>/dev/null || true
make -C "$BUILD_DIR/nqptp" install
make -C "$BUILD_DIR/shairport-sync" install
getent group shairport-sync >/dev/null || groupadd --system shairport-sync
id shairport-sync >/dev/null 2>&1 || useradd --system --gid shairport-sync --no-create-home --shell /usr/sbin/nologin shairport-sync
usermod -a -G audio shairport-sync
install -m 755 "$PACKAGE_DIR/scripts/radxa-audio.py" /usr/local/bin/radxa-audio
install -m 755 "$PACKAGE_DIR/scripts/radxa-audio-output" /usr/local/bin/radxa-audio-output
cat > /etc/systemd/system/nqptp.service <<'UNIT'
[Unit]
Description=NQPTP AirPlay 2 clock service
Wants=network-online.target
After=network.target network-online.target
Before=shairport-sync.service
[Service]
ExecStart=/usr/local/bin/nqptp
DynamicUser=yes
LimitRTPRIO=6
AmbientCapabilities=CAP_NET_BIND_SERVICE
Restart=on-failure
RestartSec=3
[Install]
WantedBy=multi-user.target
UNIT
cat > /etc/systemd/system/shairport-sync.service <<'UNIT'
[Unit]
Description=Radxa AirPlay 2 audio receiver
Wants=network-online.target nqptp.service
After=network-online.target avahi-daemon.service nqptp.service sound.target
Requires=avahi-daemon.service
[Service]
ExecStart=/usr/local/bin/shairport-sync
User=shairport-sync
Group=shairport-sync
LimitRTPRIO=5
Restart=on-failure
RestartSec=5
[Install]
WantedBy=multi-user.target
UNIT
if (( WIFI5 )); then
mkdir -p /etc/modprobe.d
printf '%s\n' '# AIC driver compatibility settings; effective after reboot.' \
'options aic8800_fdrv ps_on=0 he_on=0' > /etc/modprobe.d/90-radxa-airplay-wifi.conf
fi
systemctl daemon-reload
/usr/local/bin/shairport-sync -V | grep -q '5.5.2-AirPlay2.*smi10'
/usr/local/bin/nqptp -V | grep -q '1.2.8.*10'
radxa-audio init --name "$AIRPLAY_NAME" --dac "$DAC_PCM"
radxa-audio status
trap - EXIT
echo "설치 완료. 복구: sudo python3 $PACKAGE_DIR/scripts/backup.py restore $BACKUP_DIR"
if (( WIFI5 )); then echo 'Wi-Fi 5 설정 적용을 위해 편할 때 재부팅하세요. USB 네트워크 주소 설정은 변경하지 않았습니다.'; fi