mirror of
https://github.com/mistergibson/radio.git
synced 2026-09-08 22:09:51 -07:00
storage dir setup
This commit is contained in:
parent
952c9ae5a9
commit
1b191b868d
9 changed files with 418 additions and 402 deletions
|
|
@ -1,200 +1,113 @@
|
|||
#!/usr/bin/env bash
|
||||
# install_for_jruby - Provision the JRuby-based radio automation stack.
|
||||
# Detects/reuses existing Java and JRuby; installs gems one at a time.
|
||||
# Must be run as root from within the target directory (e.g. /srv/radio/).
|
||||
# Idempotent: safe to re-run.
|
||||
|
||||
#
|
||||
# install_for_jruby.sh - Installs the JRuby-based radio automation stack.
|
||||
# Same layout as the Python installer but invokes jruby for the scripts.
|
||||
# Derives its service name from the containing directory, prompts for a
|
||||
# storage path, writes config.json, creates the systemd unit and cron jobs.
|
||||
#
|
||||
set -euo pipefail
|
||||
|
||||
INSTALL_DIR="$(pwd)"
|
||||
SERVICE_NAME="$(basename "$INSTALL_DIR")"
|
||||
GEM_HOME_LOCAL="$INSTALL_DIR/.gems"
|
||||
JRBURY_VERSION="10.1.1.0"
|
||||
MAVEN_BASE="https://repo1.maven.org/maven2/org/jruby/jruby-dist"
|
||||
JRUBY_HOME_PINNED="/opt/jruby-${JRBURY_VERSION}"
|
||||
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 (JRuby) ==="
|
||||
echo "Install dir: $INSTALL_DIR"
|
||||
echo "Service name: $SERVICE_NAME"
|
||||
echo
|
||||
JRuby_BIN="$(command -v jruby || echo /usr/local/bin/jruby)"
|
||||
|
||||
# --- 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]=""
|
||||
echo "=== ${SERVICE_NAME} installer (JRuby) ==="
|
||||
echo "Install dir: ${SCRIPT_DIR}"
|
||||
echo "Using jruby: ${JRuby_BIN}"
|
||||
echo ""
|
||||
|
||||
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
|
||||
# --- 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
|
||||
|
||||
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]}
|
||||
# --- 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}"
|
||||
|
||||
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; }
|
||||
# --- 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
|
||||
|
||||
# --- Base packages ---
|
||||
echo "Installing base packages..."
|
||||
apt-get update -qq
|
||||
apt-get install -y -qq liquidsoap icecast2 jq curl unzip ca-certificates >/dev/null
|
||||
|
||||
# --- Detect or install Java (>= 21) ---
|
||||
JAVA_OK=false
|
||||
if command -v java &>/dev/null; then
|
||||
JAVA_VER=$(java -version 2>&1 | head -1 | sed 's/.*"\([0-9]*\)\..*/\1/')
|
||||
if (( JAVA_VER >= 21 )); then
|
||||
echo "Found Java $JAVA_VER; reusing."
|
||||
JAVA_OK=true
|
||||
fi
|
||||
fi
|
||||
if [[ "$JAVA_OK" != "true" ]]; then
|
||||
echo "Installing OpenJDK 21 headless..."
|
||||
apt-get install -y -qq openjdk-21-jdk-headless >/dev/null
|
||||
fi
|
||||
|
||||
# --- Detect or install JRuby ---
|
||||
JRUBY_BIN=""
|
||||
if [[ -x "$JRUBY_HOME_PINNED/bin/jruby" ]]; then
|
||||
JRUBY_BIN="$JRUBY_HOME_PINNED/bin/jruby"
|
||||
echo "Found pinned JRuby at $JRUBY_HOME_PINNED"
|
||||
elif command -v jruby &>/dev/null; then
|
||||
JRUBY_BIN="$(command -v jruby)"
|
||||
echo "Found JRuby on PATH: $JRUBY_BIN"
|
||||
else
|
||||
echo "Downloading JRuby ${JRBURY_VERSION} from Maven Central..."
|
||||
TARBALL="jruby-dist-${JRBURY_VERSION}-bin.tar.gz"
|
||||
URL="${MAVEN_BASE}/${JRBURY_VERSION}/${TARBALL}"
|
||||
cd /tmp
|
||||
curl -fsSL -o "$TARBALL" "$URL"
|
||||
tar xzf "$TARBALL"
|
||||
mv "jruby-${JRBURY_VERSION}" "$JRUBY_HOME_PINNED"
|
||||
rm -f "$TARBALL"
|
||||
JRUBY_BIN="$JRUBY_HOME_PINNED/bin/jruby"
|
||||
echo "Installed JRuby to $JRUBY_HOME_PINNED"
|
||||
fi
|
||||
|
||||
# --- Register via update-alternatives ---
|
||||
for cmd in jruby bundle gem rake irb; do
|
||||
ALT_SRC="$($JRUBY_BIN -e "puts Gem.bindir" 2>/dev/null || echo "$(${JRUBY_BIN%/jruby}${cmd}" --version &>/dev/null && which ${cmd} 2>/dev/null || echo "$JRUBY_HOME_PINNED/bin/${cmd}")")"
|
||||
# Simpler: just link the known bin paths
|
||||
LINK_TARGET="$JRUBY_HOME_PINNED/bin/${cmd}"
|
||||
if [[ -x "$LINK_TARGET" ]]; then
|
||||
ln -sf "$LINK_TARGET" "/usr/local/bin/${cmd}"
|
||||
update-alternatives --install "/usr/local/bin/${cmd}" "${cmd}" "$LINK_TARGET" 100 || true
|
||||
fi
|
||||
done
|
||||
echo "JRuby integrated via /usr/local/bin symlinks."
|
||||
|
||||
# --- Install gems one at a time (avoid OOM on low-RAM hosts) ---
|
||||
export GEM_HOME="$GEM_HOME_LOCAL"
|
||||
export GEM_PATH="$GEM_HOME_LOCAL"
|
||||
export JRUBY_OPTS="-J-Xmx1g -J-Xss512k"
|
||||
|
||||
echo "Installing bundler..."
|
||||
"$JRUBY_BIN" -S gem install bundler --no-document 2>&1 | tail -1
|
||||
echo "Installing nokogiri..."
|
||||
"$JRUBY_BIN" -S gem install nokogiri --no-document 2>&1 | tail -1
|
||||
echo "Installing sqlite3..."
|
||||
"$JRUBY_BIN" -S gem install sqlite3 --no-document 2>&1 | tail -1
|
||||
echo "Installing json..."
|
||||
"$JRUBY_BIN" -S gem install json --no-document 2>&1 | tail -1
|
||||
|
||||
# --- 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"
|
||||
# --- Gem check (install one at a time to avoid memory limits) --------------
|
||||
for gem in sqlite3 json; do
|
||||
if ! "${JRuby_BIN}" -e "require '${gem}'" >/dev/null 2>&1; then
|
||||
echo "Installing gem: ${gem}"
|
||||
gem install "${gem}"
|
||||
fi
|
||||
done
|
||||
|
||||
# --- 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
|
||||
Environment=GEM_HOME=${GEM_HOME_LOCAL}
|
||||
Environment=GEM_PATH=${GEM_HOME_LOCAL}
|
||||
Environment=JRUBY_OPTS=-J-Xmx1g -J-Xss512k
|
||||
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) ---
|
||||
FETCH_CMD="sudo -u liquidsoap env GEM_HOME=${GEM_HOME_LOCAL} GEM_PATH=${GEM_HOME_LOCAL} JRUBY_OPTS=-J-Xmx1g -J-Xss512k /usr/local/bin/jruby -S bundle exec $INSTALL_DIR/fetch_podcasts.rb >> $INSTALL_DIR/logs/cron_fetch.log 2>&1"
|
||||
UPDATE_CMD="sudo -u liquidsoap env GEM_HOME=${GEM_HOME_LOCAL} GEM_PATH=${GEM_HOME_LOCAL} JRUBY_OPTS=-J-Xmx1g -J-Xss512k /usr/local/bin/jruby -S bundle exec $INSTALL_DIR/update_playlists.rb >> $INSTALL_DIR/logs/cron_update.log 2>&1"
|
||||
|
||||
(crontab -l 2>/dev/null | grep -v "fetch_podcasts.rb"; echo "0 * * * * $FETCH_CMD") | crontab -
|
||||
(crontab -l 2>/dev/null | grep -v "update_playlists.rb"; echo "30 * * * * $UPDATE_CMD") | 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} && ${JRuby_BIN} fetch_podcasts.rb >> ${STORAGE_PATH}/logs/cron.log 2>&1"
|
||||
CRON_PLAYLISTS="30 * * * * cd ${SCRIPT_DIR} && ${JRuby_BIN} update_playlists.rb >> ${STORAGE_PATH}/logs/cron.log 2>&1"
|
||||
|
||||
(crontab -l 2>/dev/null | grep -v "fetch_podcasts.rb\|update_playlists.rb"; 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}"
|
||||
|
|
|
|||
Loading…
Reference in a new issue