mirror of
https://github.com/mistergibson/radio.git
synced 2026-09-08 22:09:51 -07:00
141 lines
5.4 KiB
Shell
Executable file
141 lines
5.4 KiB
Shell
Executable file
#!/usr/bin/env bash
|
|
#
|
|
# install_for_python.sh - Installs the Python-based radio automation stack.
|
|
# Derives its service name from the directory it lives in, prompts for a
|
|
# storage path (kept out of the boot drive), creates a virtualenv for
|
|
# dependencies, writes config.json, creates the systemd unit, and sets up
|
|
# cron entries.
|
|
#
|
|
set -euo pipefail
|
|
|
|
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
|
DIR_NAME="$(basename "$SCRIPT_DIR")"
|
|
SERVICE_NAME="${DIR_NAME}"
|
|
SYSTEMD_UNIT="/etc/systemd/system/${SERVICE_NAME}.service"
|
|
CONFIG_FILE="${SCRIPT_DIR}/config.json"
|
|
VENV_DIR="${SCRIPT_DIR}/.venv"
|
|
PYBIN="${VENV_DIR}/bin/python3"
|
|
|
|
echo "=== ${SERVICE_NAME} installer (Python) ==="
|
|
echo "Install dir: ${SCRIPT_DIR}"
|
|
echo ""
|
|
|
|
# --- Verify required source files exist ------------------------------------
|
|
for req in station.liq fetch_podcasts.py update_playlists.py; do
|
|
if [ ! -f "${SCRIPT_DIR}/${req}" ]; then
|
|
echo "ERROR: required file '${req}' not found in ${SCRIPT_DIR}" >&2
|
|
exit 1
|
|
fi
|
|
done
|
|
|
|
# --- Prompt for storage path ---------------------------------------------
|
|
DEFAULT_STORAGE="/srv/radio-storage"
|
|
read -rp "Enter storage path for media/state/logs [${DEFAULT_STORAGE}]: " STORAGE_PATH
|
|
STORAGE_PATH="${STORAGE_PATH:-$DEFAULT_STORAGE}"
|
|
mkdir -p "${STORAGE_PATH}"/{music,podcasts,jingles,announcements,state,playlists,logs}
|
|
# Media dirs are read by the liquidsoap user at stream time
|
|
chown -R liquidsoap:liquidsoap "${STORAGE_PATH}"/{music,podcasts,jingles,announcements} 2>/dev/null || true
|
|
# state/ and playlists/ are written by the cron jobs (run as root) and read by liquidsoap
|
|
chown root:liquidsoap "${STORAGE_PATH}"/{state,playlists} 2>/dev/null || true
|
|
chmod 775 "${STORAGE_PATH}"/{state,playlists} 2>/dev/null || true
|
|
# logs/ is appended to by cron
|
|
chown root:liquidsoap "${STORAGE_PATH}/logs" 2>/dev/null || true
|
|
chmod 775 "${STORAGE_PATH}/logs" 2>/dev/null || true
|
|
|
|
# --- Collect Icecast credentials ------------------------------------------
|
|
read -rp "Icecast host [127.0.0.1]: " IC_HOST
|
|
IC_HOST="${IC_HOST:-127.0.0.1}"
|
|
read -rp "Icecast source port [7777]: " IC_PORT
|
|
IC_PORT="${IC_PORT:-7777}"
|
|
read -rp "Icecast mount [/radio.mp3]: " IC_MOUNT
|
|
IC_MOUNT="${IC_MOUNT:-/radio.mp3}"
|
|
read -rp "Icecast source username [sourceadmin]: " IC_USER
|
|
IC_USER="${IC_USER:-sourceadmin}"
|
|
read -rsp "Icecast source password: " IC_PASS; echo
|
|
IC_PASS="${IC_PASS:?Password required}"
|
|
|
|
# --- gPodder sync (optional) ----------------------------------------------
|
|
read -rp "Enable gPodder.net sync? [y/N]: " GPODDER_ENABLE
|
|
case "${GPODDER_ENABLE,,}" in
|
|
y|yes)
|
|
read -rp "gPodder host [https://gpodder.net]: " GP_HOST
|
|
GP_HOST="${GP_HOST:-https://gpodder.net}"
|
|
# Strip trailing slash if the user typed one
|
|
GP_HOST="${GP_HOST%/}"
|
|
read -rp "gPodder username: " GP_USER
|
|
GP_USER="${GP_USER:?gPodder username required}"
|
|
read -rsp "gPodder password: " GP_PASS; echo
|
|
GP_PASS="${GP_PASS:?gPodder password required}"
|
|
GP_JSON=$(jq -n --arg h "$GP_HOST" --arg u "$GP_USER" --arg p "$GP_PASS" \
|
|
'{enable:true, host:$h, username:$u, password:$p}')
|
|
;;
|
|
*)
|
|
GP_JSON='{"enable":false,"host":"","username":"","password":""}'
|
|
;;
|
|
esac
|
|
|
|
# --- Write config.json -----------------------------------------------------
|
|
cat > "$CONFIG_FILE" <<EOF
|
|
{
|
|
"storage": "${STORAGE_PATH}",
|
|
"icecast": {
|
|
"host": "${IC_HOST}",
|
|
"port": ${IC_PORT},
|
|
"mount": "${IC_MOUNT}",
|
|
"username": "${IC_USER}",
|
|
"password": "${IC_PASS}"
|
|
},
|
|
"gpodder": ${GP_JSON}
|
|
}
|
|
EOF
|
|
chmod 600 "$CONFIG_FILE"
|
|
echo "Wrote ${CONFIG_FILE}"
|
|
|
|
# --- Virtualenv + dependencies ----------------------------------------------
|
|
echo "Creating virtual environment at ${VENV_DIR} ..."
|
|
if [ ! -x "$PYBIN" ]; then
|
|
apt-get update -qq
|
|
apt-get install -y -qq python3-venv python3-pip jq >/dev/null
|
|
fi
|
|
python3 -m venv "$VENV_DIR"
|
|
"$VENV_DIR/bin/pip" install --quiet --upgrade pip
|
|
"$VENV_DIR/bin/pip" install --quiet feedparser requests
|
|
echo "Installed feedparser + requests into ${VENV_DIR}"
|
|
|
|
# --- Systemd unit ------------------------------------------------------------
|
|
cat > "$SYSTEMD_UNIT" <<EOF
|
|
[Unit]
|
|
Description=${SERVICE_NAME} radio automation (liquidsoap)
|
|
After=network.target icecast2.service
|
|
|
|
[Service]
|
|
User=liquidsoap
|
|
WorkingDirectory=${SCRIPT_DIR}
|
|
ExecStart=/usr/bin/liquidsoap ${SCRIPT_DIR}/station.liq
|
|
Restart=on-failure
|
|
RestartSec=5
|
|
|
|
[Install]
|
|
WantedBy=multi-user.target
|
|
EOF
|
|
systemctl daemon-reload
|
|
systemctl enable "${SERVICE_NAME}"
|
|
echo "Enabled systemd unit: ${SERVICE_NAME}"
|
|
|
|
# --- Cron --------------------------------------------------------------------
|
|
CRON_FETCH="0 * * * * cd ${SCRIPT_DIR} && ${PYBIN} fetch_podcasts.py >> ${STORAGE_PATH}/logs/cron.log 2>&1"
|
|
CRON_PLAYLISTS="30 * * * * cd ${SCRIPT_DIR} && ${PYBIN} update_playlists.py >> ${STORAGE_PATH}/logs/cron.log 2>&1"
|
|
|
|
# Guard against pipefail killing us when crontab is empty or has no matching lines
|
|
EXISTING_CRON=$(crontab -l 2>/dev/null | grep -v "fetch_podcasts.py\|update_playlists.py" || true)
|
|
( [ -n "$EXISTING_CRON" ] && echo "$EXISTING_CRON"; echo "$CRON_FETCH"; echo "$CRON_PLAYLISTS" ) | crontab -
|
|
echo "Installed cron jobs:"
|
|
echo " $CRON_FETCH"
|
|
echo " $CRON_PLAYLISTS"
|
|
|
|
echo ""
|
|
echo "=== Installation complete ==="
|
|
echo "Storage root: ${STORAGE_PATH}"
|
|
echo "Virtualenv: ${VENV_DIR}"
|
|
echo "Start with: systemctl start ${SERVICE_NAME}"
|
|
echo "Test fetcher: ${PYBIN} fetch_podcasts.py --list-shows"
|