storage dir setup

This commit is contained in:
G. Gibson 2026-08-29 15:58:25 -07:00
commit 1b191b868d
9 changed files with 418 additions and 402 deletions

View file

@ -1,142 +1,106 @@
#!/usr/bin/env bash
# install_for_python - Provision the Python-based radio automation stack.
# Must be run as root from within the target directory (e.g. /srv/radio/).
# Idempotent: safe to re-run.
#
# 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), writes config.json, creates
# the systemd unit, and sets up cron entries.
#
set -euo pipefail
INSTALL_DIR="$(pwd)"
SERVICE_NAME="$(basename "$INSTALL_DIR")"
VENV="$INSTALL_DIR/.venv"
PYTHON_BIN="$VENV/bin/python"
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"
echo "=== Radio Automation Installer (Python) ==="
echo "Install dir: $INSTALL_DIR"
echo "Service name: $SERVICE_NAME"
echo
echo "=== ${SERVICE_NAME} installer (Python) ==="
echo "Install dir: ${SCRIPT_DIR}"
echo ""
# --- Prompt for config values (pre-fill from existing config.json) ---
EXISTING_CONFIG="$INSTALL_DIR/config.json"
declare -A CFG
CFG[ICE_HOST]="localhost"
CFG[ICE_PORT]="7777"
CFG[ICE_MOUNT]="/audio.mp3"
CFG[ICE_USER]="source"
CFG[ICE_PASS]=""
CFG[GPODDER_ENABLE]="false"
CFG[GPODDER_HOST]="https://gpodder.net"
CFG[GPODDER_USER]=""
CFG[GPODDER_PASS]=""
# --- 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}
chown -R liquidsoap:liquidsoap "${STORAGE_PATH}" 2>/dev/null || true
if [[ -f "$EXISTING_CONFIG" ]]; then
echo "Existing config.json found; using as defaults."
CFG[ICE_HOST]=$(jq -r '.icecast.host // "localhost"' "$EXISTING_CONFIG")
CFG[ICE_PORT]=$(jq -r '.icecast.port // 7777' "$EXISTING_CONFIG")
CFG[ICE_MOUNT]=$(jq -r '.icecast.mount // "/audio.mp3"' "$EXISTING_CONFIG")
CFG[ICE_USER]=$(jq -r '.icecast.username // "source"' "$EXISTING_CONFIG")
CFG[ICE_PASS]=$(jq -r '.icecast.password // ""' "$EXISTING_CONFIG")
CFG[GPODDER_ENABLE]=$(jq -r '.gpodder.enable // false' "$EXISTING_CONFIG")
CFG[GPODDER_HOST]=$(jq -r '.gpodder.host // "https://gpodder.net"' "$EXISTING_CONFIG")
CFG[GPODDER_USER]=$(jq -r '.gpodder.username // ""' "$EXISTING_CONFIG")
CFG[GPODDER_PASS]=$(jq -r '.gpodder.password // ""' "$EXISTING_CONFIG")
fi
# --- Collect Icecast credentials ------------------------------------------
read -rp "Icecast source port [8000]: " IC_PORT
IC_PORT="${IC_PORT:-8000}"
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}"
read -rp "Icecast host [$CFG[ICE_HOST]]: " v; CFG[ICE_HOST]=${v:-$CFG[ICE_HOST]}
read -rp "Icecast port [$CFG[ICE_PORT]]: " v; CFG[ICE_PORT]=${v:-$CFG[ICE_PORT]}
read -rp "Icecast mount [$CFG[ICE_MOUNT]]: " v; CFG[ICE_MOUNT]=${v:-$CFG[ICE_MOUNT]}
read -rp "Icecast source username [$CFG[ICE_USER]]: " v; CFG[ICE_USER]=${v:-$CFG[ICE_USER]}
read -rsp "Icecast source password [$CFG[ICE_PASS]]: "; echo; CFG[ICE_PASS]=${v:-$CFG[ICE_PASS]}
read -rp "Enable gPodder sync? (true/false) [$CFG[GPODDER_ENABLE]]: " v; CFG[GPODDER_ENABLE]=${v:-$CFG[GPODDER_ENABLE]}
read -rp "gPodder host [$CFG[GPODDER_HOST]]: " v; CFG[GPODDER_HOST]=${v:-$CFG[GPODDER_HOST]}
read -rp "gPodder username [$CFG[GPODDER_USER]]: " v; CFG[GPODDER_USER]=${v:-$CFG[GPODDER_USER]}
read -rsp "gPodder password [$CFG[GPODDER_PASS]]: "; echo; CFG[GPODDER_PASS]=${v:-$CFG[GPODDER_PASS]}
# --- 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}"
read -rp "gPodder username: " GP_USER
read -rsp "gPodder password: " GP_PASS; echo
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
echo
echo "Summary:"
echo " Icecast: ${CFG[ICE_HOST]}:${CFG[ICE_PORT]}${CFG[ICE_MOUNT]}"
echo " gPodder sync: ${CFG[GPODDER_ENABLE]}"
read -rp "Proceed? [y/N] " confirm
[[ "$confirm" =~ ^[Yy]$ ]] || { echo "Aborted."; exit 0; }
# --- Install system packages ---
echo "Installing system packages..."
apt-get update -qq
apt-get install -y -qq python3 python3-venv python3-pip jq curl >/dev/null
# --- Create venv and install Python deps ---
if [[ ! -d "$VENV" ]]; then
echo "Creating virtualenv at $VENV..."
python3 -m venv "$VENV"
fi
echo "Installing Python dependencies..."
"$VENV/bin/pip" install --quiet --upgrade pip
"$VENV/bin/pip" install --quiet feedparser requests mutagen
# --- Create directory structure ---
for d in music podcasts jingles announcements playlists state logs; do
mkdir -p "$INSTALL_DIR/$d"
done
# --- Generate config.json ---
cat > "$INSTALL_DIR/config.json" <<EOF
# --- Write config.json -----------------------------------------------------
cat > "$CONFIG_FILE" <<EOF
{
"storage": "${STORAGE_PATH}",
"icecast": {
"host": "${CFG[ICE_HOST]}",
"port": ${CFG[ICE_PORT]},
"mount": "${CFG[ICE_MOUNT]}",
"username": "${CFG[ICE_USER]}",
"password": "${CFG[ICE_PASS]}"
"host": "127.0.0.1",
"port": ${IC_PORT},
"mount": "${IC_MOUNT}",
"username": "${IC_USER}",
"password": "${IC_PASS}"
},
"gpodder": {
"enable": ${CFG[GPODDER_ENABLE]},
"host": "${CFG[GPODDER_HOST]}",
"username": "${CFG[GPODDER_USER]}",
"password": "${CFG[GPODDER_PASS]}"
}
"gpodder": ${GP_JSON}
}
EOF
chmod 600 "$INSTALL_DIR/config.json"
chmod 600 "$CONFIG_FILE"
echo "Wrote ${CONFIG_FILE}"
# --- Ensure liquidsoap user exists ---
if ! id liquidsoap &>/dev/null; then
useradd --system --home-dir "$INSTALL_DIR" --shell /usr/sbin/nologin liquidsoap
fi
chown -R liquidsoap:liquidsoap "$INSTALL_DIR"
# --- Dependencies -----------------------------------------------------------
echo "Installing Python dependencies..."
pip3 install feedparser requests 2>/dev/null || pip install feedparser requests
# --- Write systemd service ---
cat > /etc/systemd/system/"$SERVICE_NAME".service <<EOF
# --- Systemd unit ------------------------------------------------------------
cat > "$SYSTEMD_UNIT" <<EOF
[Unit]
Description=Liquidsoap radio station ($SERVICE_NAME)
Description=${SERVICE_NAME} radio automation (liquidsoap)
After=network.target icecast2.service
[Service]
User=liquidsoap
WorkingDirectory=$INSTALL_DIR
ExecStart=/usr/bin/liquidsoap $INSTALL_DIR/station.liq
WorkingDirectory=${SCRIPT_DIR}
ExecStart=/usr/bin/liquidsoap ${SCRIPT_DIR}/station.liq
Restart=on-failure
RestartSec=5
[Install]
WantedBy=multi-user.target
EOF
# --- Set up cron jobs (deduplicated) ---
CRON_FETCH="0 * * * * sudo -u liquidsoap $PYTHON_BIN $INSTALL_DIR/fetch_podcasts.py >> $INSTALL_DIR/logs/cron_fetch.log 2>&1"
CRON_UPDATE="30 * * * * sudo -u liquidsoap $PYTHON_BIN $INSTALL_DIR/update_playlists.py >> $INSTALL_DIR/logs/cron_update.log 2>&1"
(crontab -l 2>/dev/null | grep -v "fetch_podcasts.py"; echo "$CRON_FETCH") | crontab -
(crontab -l 2>/dev/null | grep -v "update_playlists.py"; echo "$CRON_UPDATE") | crontab -
# --- Reload and enable ---
systemctl daemon-reload
systemctl enable "$SERVICE_NAME"
systemctl enable "${SERVICE_NAME}"
echo "Enabled systemd unit: ${SERVICE_NAME}"
echo
# --- Cron --------------------------------------------------------------------
CRON_FETCH="0 * * * * cd ${SCRIPT_DIR} && /usr/bin/python3 fetch_podcasts.py >> ${STORAGE_PATH}/logs/cron.log 2>&1"
CRON_PLAYLISTS="30 * * * * cd ${SCRIPT_DIR} && /usr/bin/python3 update_playlists.py >> ${STORAGE_PATH}/logs/cron.log 2>&1"
(crontab -l 2>/dev/null | grep -v "fetch_podcasts.py\|update_playlists.py"; echo "$CRON_FETCH"; echo "$CRON_PLAYLISTS") | crontab -
echo "Installed cron jobs:"
echo " $CRON_FETCH"
echo " $CRON_PLAYLISTS"
echo ""
echo "=== Installation complete ==="
echo "Next steps:"
echo " 1. Drop MP3/M4A files into $INSTALL_DIR/music/"
echo " 2. Edit $INSTALL_DIR/schedule.txt with your show schedule"
echo " 3. Review $INSTALL_DIR/station.liq"
echo " 4. Start services:"
echo " sudo systemctl start icecast2"
echo " sudo systemctl start $SERVICE_NAME"
echo "Storage root: ${STORAGE_PATH}"
echo "Start with: systemctl start ${SERVICE_NAME}"