Add reproducible Radxa AirPlay and Bluetooth mixer setup
This commit is contained in:
Executable
+157
@@ -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
|
||||
Reference in New Issue
Block a user