#!/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}" < Wrote ${CONFIG_JSON}" # --- Generate systemd unit ---------------------------------------------------- cat > "${UNIT_PATH}" < 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}"