#!/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'] + manifest.get('patches', []): 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 \ libglib2.0-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" git apply "$PACKAGE_DIR/sources/shairport-native-volume.patch" 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 --with-dbus-interface 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.*dbus' /usr/local/bin/nqptp -V | grep -q '1.2.8.*10' radxa-audio init --name "$AIRPLAY_NAME" --dac "$DAC_PCM" radxa-audio status bash "$PACKAGE_DIR/scripts/install-web.sh" trap - EXIT echo "설치 완료. 복구: sudo python3 $PACKAGE_DIR/scripts/backup.py restore $BACKUP_DIR" if (( WIFI5 )); then echo 'Wi-Fi 5 설정 적용을 위해 편할 때 재부팅하세요. USB 네트워크 주소 설정은 변경하지 않았습니다.'; fi