mirror of
https://github.com/mistergibson/radio.git
synced 2026-09-08 22:09:51 -07:00
210 lines
7.1 KiB
Shell
Executable file
210 lines
7.1 KiB
Shell
Executable file
#!/bin/bash
|
|
# install_for_jruby - Installer for the JRuby radio automation stack.
|
|
#
|
|
# Derives its service name from the directory it lives in, e.g.
|
|
# /srv/radio/install_for_jruby -> radio.service
|
|
# Prompts for a storage root and writes a complete config.json.
|
|
# Creates media subdirectories (music, podcasts, jingles, announcements),
|
|
# state/ and logs/ under the storage root, drops and recreates all
|
|
# database files, installs gems one at a time, generates a systemd unit,
|
|
# and adds cron entries to the existing liquidsoap user's crontab.
|
|
set -euo pipefail
|
|
|
|
INSTALL_DIR="$(cd "$(dirname "$0")" && pwd)"
|
|
SERVICE_BASENAME="$(basename "${INSTALL_DIR}")"
|
|
SERVICE_NAME="${SERVICE_BASENAME}.service"
|
|
UNIT_PATH="/etc/systemd/system/${SERVICE_NAME}"
|
|
|
|
CONFIG_JSON="${INSTALL_DIR}/config.json"
|
|
GEMS_DIR="${INSTALL_DIR}/.gems"
|
|
JRUBY_BIN=""
|
|
|
|
echo "=== ${SERVICE_BASENAME} installer ==="
|
|
echo
|
|
|
|
# --- Detect JRuby -----------------------------------------------------------
|
|
if command -v jruby >/dev/null 2>&1; then
|
|
JRUBY_BIN="$(command -v jruby)"
|
|
elif [ -x /opt/jruby/bin/jruby ]; then
|
|
JRUBY_BIN="/opt/jruby/bin/jruby"
|
|
else
|
|
echo "ERROR: jruby not found on PATH or at /opt/jruby/bin/jruby." >&2
|
|
exit 1
|
|
fi
|
|
echo "==> Using JRuby: ${JRUBY_BIN}"
|
|
echo
|
|
|
|
# --- Prompt for storage root ------------------------------------------------
|
|
STORAGE_ROOT_DEFAULT="/mnt/storage/radio"
|
|
printf "Storage root [%s]: " "${STORAGE_ROOT_DEFAULT}"
|
|
read -r STORAGE_ROOT_INPUT
|
|
STORAGE_ROOT="${STORAGE_ROOT_INPUT:-${STORAGE_ROOT_DEFAULT}}"
|
|
echo "==> Storage root: ${STORAGE_ROOT}"
|
|
|
|
# --- Create media and state directories -------------------------------------
|
|
for d in music podcasts jingles announcements state logs; do
|
|
mkdir -p "${STORAGE_ROOT}/${d}"
|
|
done
|
|
chown -R liquidsoap:liquidsoap "${STORAGE_ROOT}" 2>/dev/null || true
|
|
echo "==> Media/state/log directories created under ${STORAGE_ROOT}"
|
|
|
|
# --- Drop and recreate databases --------------------------------------------
|
|
rm -f "${STORAGE_ROOT}/state/subscriptions.db" \
|
|
"${STORAGE_ROOT}/state/played.db" \
|
|
"${STORAGE_ROOT}/state/subscriptions.db-wal" \
|
|
"${STORAGE_ROOT}/state/subscriptions.db-shm" \
|
|
"${STORAGE_ROOT}/state/played.db-wal" \
|
|
"${STORAGE_ROOT}/state/played.db-shm"
|
|
echo "==> Databases dropped (will be recreated now)"
|
|
# Initialize database schemas
|
|
sqlite3 "${STORAGE_ROOT}/state/subscriptions.db" <<'SQL'
|
|
CREATE TABLE IF NOT EXISTS shows (
|
|
guid TEXT PRIMARY KEY,
|
|
slug TEXT UNIQUE NOT NULL,
|
|
title TEXT,
|
|
feed_url TEXT UNIQUE NOT NULL,
|
|
archive INTEGER DEFAULT 0,
|
|
opml_import INTEGER DEFAULT 0,
|
|
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP
|
|
);
|
|
SQL
|
|
|
|
sqlite3 "${STORAGE_ROOT}/state/played.db" <<'SQL'
|
|
CREATE TABLE IF NOT EXISTS episodes (
|
|
guid TEXT PRIMARY KEY,
|
|
show_guid TEXT NOT NULL REFERENCES shows(guid),
|
|
title TEXT,
|
|
url TEXT,
|
|
duration_seconds INTEGER,
|
|
played INTEGER DEFAULT 0,
|
|
local_path TEXT,
|
|
file_size_bytes INTEGER DEFAULT 0,
|
|
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP
|
|
);
|
|
SQL
|
|
|
|
echo "Database schemas initialized."
|
|
|
|
# --- Install gems one at a time ---------------------------------------------
|
|
export GEM_HOME="${GEMS_DIR}"
|
|
export GEM_PATH="${GEMS_DIR}"
|
|
mkdir -p "${GEMS_DIR}"
|
|
|
|
for gem_name in sequel jdbc-sqlite3 json nokogiri; do
|
|
echo "==> Installing gem: ${gem_name}"
|
|
if ! "${JRUBY_BIN}" -S gem install --local "${gem_name}" 2>/dev/null; then
|
|
"${JRUBY_BIN}" -S gem install "${gem_name}"
|
|
fi
|
|
done
|
|
|
|
# --- Write complete config.json ----------------------------------------------
|
|
ICECAST_HOST_DEFAULT="192.168.0.200"
|
|
ICECAST_PORT_DEFAULT="7777"
|
|
ICECAST_MOUNT_DEFAULT="/data"
|
|
ICECAST_SOURCE_USER_DEFAULT="source"
|
|
GPODDER_ENABLE_DEFAULT="false"
|
|
GPODDER_HOST_DEFAULT="gpodder.net"
|
|
|
|
printf "Icecast host [%s]: " "${ICECAST_HOST_DEFAULT}"
|
|
read -r ICECAST_HOST_INPUT; ICECAST_HOST="${ICECAST_HOST_INPUT:-${ICECAST_HOST_DEFAULT}}"
|
|
printf "Icecast source port [%s]: " "${ICECAST_PORT_DEFAULT}"
|
|
read -r ICECAST_PORT_INPUT; ICECAST_PORT="${ICECAST_PORT_INPUT:-${ICECAST_PORT_DEFAULT}}"
|
|
printf "Mount point [%s]: " "${ICECAST_MOUNT_DEFAULT}"
|
|
read -r ICECAST_MOUNT_INPUT; ICECAST_MOUNT="${ICECAST_MOUNT_INPUT:-${ICECAST_MOUNT_DEFAULT}}"
|
|
printf "Source username [%s]: " "${ICECAST_SOURCE_USER_DEFAULT}"
|
|
read -r ICECAST_SRC_USER_INPUT; ICECAST_SRC_USER="${ICECAST_SRC_USER_INPUT:-${ICECAST_SOURCE_USER_DEFAULT}}"
|
|
printf "Source password: "
|
|
read -rs ICECAST_SRC_PASS
|
|
echo
|
|
printf "gPodder sync enabled? (y/n) [n]: "
|
|
read -r GP_SYNC_INPUT
|
|
case "${GP_SYNC_INPUT,,}" in
|
|
y|yes) GP_ENABLED=true ;;
|
|
*) GP_ENABLED=false ;;
|
|
esac
|
|
GP_HOST="${GPODDER_HOST_DEFAULT}"
|
|
GP_USERNAME=""
|
|
GP_PASSWORD=""
|
|
GP_DEVICE_ID=""
|
|
if [ "${GP_ENABLED}" = "true" ]; then
|
|
printf "gPodder username: "
|
|
read -r GP_USERNAME
|
|
printf "gPodder password: "
|
|
read -rs GP_PASSWORD
|
|
echo
|
|
printf "gPodder device ID (leave blank to generate): "
|
|
read -r GP_DEVICE_ID
|
|
if [ -z "${GP_DEVICE_ID}" ]; then
|
|
GP_DEVICE_ID="radio-$(date +%s)-$$"
|
|
fi
|
|
fi
|
|
|
|
cat > "${CONFIG_JSON}" <<EOF
|
|
{
|
|
"storage": "${STORAGE_ROOT}",
|
|
"icecast": {
|
|
"host": "${ICECAST_HOST}",
|
|
"port": ${ICECAST_PORT},
|
|
"mount": "${ICECAST_MOUNT}",
|
|
"source_username": "${ICECAST_SRC_USER}",
|
|
"source_password": "${ICECAST_SRC_PASS}"
|
|
},
|
|
"gpodder": {
|
|
"enable": ${GP_ENABLED},
|
|
"host": "${GP_HOST}",
|
|
"username": "${GP_USERNAME}",
|
|
"password": "${GP_PASSWORD}",
|
|
"device_id": "${GP_DEVICE_ID}"
|
|
}
|
|
}
|
|
EOF
|
|
chmod 600 "${CONFIG_JSON}"
|
|
echo "==> Wrote ${CONFIG_JSON}"
|
|
|
|
# --- Generate systemd unit ----------------------------------------------------
|
|
cat > "${UNIT_PATH}" <<EOF
|
|
[Unit]
|
|
Description=${SERVICE_BASENAME} (JRuby radio automation)
|
|
After=network-online.target sound.target
|
|
Wants=network-online.target
|
|
|
|
[Service]
|
|
Type=simple
|
|
User=liquidsoap
|
|
WorkingDirectory=${INSTALL_DIR}
|
|
Environment=GEM_HOME=${GEMS_DIR}
|
|
Environment=GEM_PATH=${GEMS_DIR}
|
|
ExecStart=${JRUBY_BIN} ${INSTALL_DIR}/station.rb
|
|
Restart=on-failure
|
|
RestartSec=5
|
|
|
|
[Install]
|
|
WantedBy=multi-user.target
|
|
EOF
|
|
systemctl daemon-reload
|
|
echo "==> Installed ${UNIT_PATH}"
|
|
# After the mkdir -p "$STORAGE/state" line, add:
|
|
mkdir -p "$STORAGE/state"
|
|
chown -R liquidsoap:liquidsoap "$STORAGE/state"
|
|
chmod 755 "$STORAGE/state"
|
|
chmod 664 "$STORAGE/state/"*.db 2>/dev/null || true
|
|
|
|
# --- Cron entries (liquidsoap user's crontab already exists from package) -----
|
|
CRON_FETCH="0 * * * * cd ${INSTALL_DIR} && ${INSTALL_DIR}/run_radio.sh fetch_podcasts.rb --fetch-all >> ${STORAGE_ROOT}/logs/fetch_cron.log 2>&1"
|
|
CRON_UPDATE="30 * * * * cd ${INSTALL_DIR} && ${INSTALL_DIR}/run_radio.sh update_playlist.rb >> ${STORAGE_ROOT}/logs/update_cron.log 2>&1"
|
|
|
|
CURRENT_CRONTAB=$(crontab -u liquidsoap -l 2>/dev/null || true)
|
|
NEW_CRONTAB="${CURRENT_CRONTAB}"
|
|
if ! echo "${NEW_CRONTAB}" | grep -qF "fetch_podcasts.rb --fetch-all"; then
|
|
NEW_CRONTAB="${NEW_CRONTAB}${NEW_CRONTAB:+$'\n'}${CRON_FETCH}"
|
|
fi
|
|
if ! echo "${NEW_CRONTAB}" | grep -qF "update_playlist.rb"; then
|
|
NEW_CRONTAB="${NEW_CRONTAB}${NEW_CRONTAB:+$'\n'}${CRON_UPDATE}"
|
|
fi
|
|
echo "${NEW_CRONTAB}" | crontab -u liquidsoap -
|
|
echo "==> Cron entries installed for user liquidsoap"
|
|
|
|
echo
|
|
echo "==> Done. Review ${CONFIG_JSON}, then:"
|
|
echo " systemctl start ${SERVICE_NAME}"
|
|
|