#!/bin/bash # Start/stop/restart sickchill. # Originally created for sickrage in 2016 # Updated to sickchill in 2023 # Copyright 2016-2026 Jeremy Hansen # All rights reserved. # # Redistribution and use of this script, with or without modification, is # permitted provided that the following conditions are met: # # 1. Redistributions of this script must retain the above copyright # notice, this list of conditions and the following disclaimer. # # THIS SOFTWARE IS PROVIDED BY THE AUTHOR "AS IS" AND ANY EXPRESS OR IMPLIED # WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF # MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO # EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, # SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, # PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; # OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, # WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR # OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF # ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. # Set program name in case you want to run sick{beard|rage|gear|etc} PROG=${PROG:-sickchill} # If you want to have multiple instances of sickchill running, set # the suffix here. SUFFIX= # Set this when running multiple sickchill instances. The suffix is used # to distinguish each instance's configuration and data files. if [ -n "$SUFFIX" ]; then FULLPROG="$PROG-$SUFFIX" # Just use $PROG if there isn't a suffix set else FULLPROG="$PROG" fi # Source SickRage configuration if [ -f /etc/"$FULLPROG".conf ]; then . /etc/"$FULLPROG".conf fi # Ensure all required variables are set in conf file # Edit conf file in /etc/$FULLPROG.conf for any changes MISSING=0 for var in USERNAME DATADIR PIDFILE PORT; do if [ -z "${!var}" ]; then ((MISSING++)) VAR="$var $VAR" fi done if [ $MISSING -gt 0 ]; then echo "/etc/$FULLPROG.conf is missing some or all required variables ($VAR)." echo "Please check the file and try again." exit 1 fi # Check if the server is responding with a 200 get_status() { local SERVER_STATUS SERVER_STATUS=$(curl -o /dev/null -s -w "%{http_code}" -L --max-time 5 http://localhost:"$PORT") if [ "$SERVER_STATUS" == "200" ]; then return 0 else return 1 fi } # Check if the program is running and pid file exists check() { # Ensure variables are reset STATUS= TYPE= PID= # First check if any sickchill instance is running. The PID file # and port checks below determine whether this instance is running. if pgrep "$PROG" > /dev/null; then # If it's running, check if the PID file exists if [ -e "$PIDFILE" ]; then PID=$(cat "$PIDFILE" 2> /dev/null) # Check that the PID in the file is a running process if [ -n "$PID" ] && kill -0 "$PID" 2>/dev/null; then # Now check that the PID is actually for the correct sickchill instance if grep -aq "$PROG.*--pidfile=$PIDFILE" "/proc/$PID/cmdline"; then # Finally see if the server responds correctly if get_status; then STATUS=running # Otherwise sickchill is running and the pid exists, but the server # isn't responding properly, so something is broken. else STATUS=broken TYPE=server fi # The PID belongs to a different process. This can happen if sickchill # was stopped and the PID was later reused by another process. else STATUS=broken TYPE=process fi # The PID in PIDFILE is not a running process anymore else STATUS=broken TYPE=pid-stale fi # Any running sickchill instances do not match the expected PID. Let's see # if one matches the expected port, which would mean it is running without # the expected pid file. else # Check if the program is running without the pid file matching if pgrep -f "$PROG.*--port=$PORT" > /dev/null; then STATUS=broken TYPE=pid-missing # If no running instance matches the expected port, then we can safely # assume this instance is not running. else STATUS=stopped fi fi # If a pid file exists without any sickchill running, it's broken else if [ -e "$PIDFILE" ]; then STATUS=broken TYPE=pid-stale else STATUS=stopped fi fi } broken() { if [ "$TYPE" == "pid-stale" ]; then echo "ERROR: $FULLPROG has a stale PID file." echo "The PID file exists, but the process it references is not running:" echo " - $PIDFILE" echo "The stale PID file can be safely removed." echo "Try: $0 force-start" elif [ "$TYPE" == "pid-missing" ]; then echo "ERROR: $FULLPROG is running without its expected PID file." echo "The $FULLPROG process appears to be running, but:" echo " - $PIDFILE" echo "was not found." echo "Did you start $FULLPROG without using $0?" echo "Do not start another instance until the existing process is stopped." elif [ "$TYPE" == "process" ]; then echo "ERROR: $PIDFILE points to a different process." echo "The PID stored in the PID file belongs to a process other than $FULLPROG." echo "PID: $PID" echo "PID file: $PIDFILE" echo "Please investigate the process before removing the PID file." elif [ "$TYPE" == "server" ]; then echo "ERROR: $FULLPROG is running, but its web server is not responding correctly." echo "The expected process is running, but port $PORT did not return HTTP status 200." echo "PID: $PID" echo "Port: $PORT" echo "Check the sickchill logs before attempting to restart the service." else echo "ERROR: $FULLPROG is in an unknown broken state." fi } status() { if [ "$STATUS" == "running" ]; then echo "$FULLPROG currently running." elif [ "$STATUS" == "stopped" ]; then echo "$FULLPROG not running." elif [ "$STATUS" == "broken" ]; then broken else echo "Status unknown." fi } start() { local OUTPUT RETVAL ATTEMPTS STARTCMD COUNT FAILED= if [ "$STATUS" == "stopped" ]; then # Set the start command STARTCMD="/usr/bin/${PROG} --daemon --pidfile=${PIDFILE} --datadir=${DATADIR} --port=${PORT}" echo -n "Starting $PROG: " # Store the output to display later if launch failed. OUTPUT=$(su "$USERNAME" -s /bin/sh -c "$STARTCMD" 2>&1) RETVAL=$? # If the startup command succeeds, check over 5 attempts to see if the # server responds correctly. if [ "$RETVAL" -eq 0 ]; then ATTEMPTS=5 for (( COUNT=0; COUNT < ATTEMPTS; COUNT++ )); do if get_status; then echo "Startup Successful" return 0 fi # If the PID isn't actually there, we can skip the rest of the attempts. if [ -f "$PIDFILE" ]; then PID=$(cat "$PIDFILE" 2>/dev/null) if [ -n "$PID" ] && ! kill -0 "$PID" 2>/dev/null; then break fi fi sleep 1 done FAILED="sickchill did not respond on port $PORT within $ATTEMPTS attempts." else FAILED="The sickchill startup command returned an error." fi # If we aren't in stopped status, don't try and start else status fi # If startup failed, display error messages and exit if [ -n "$FAILED" ]; then echo "Startup Failed. $FAILED" echo "The command used for launching it was:" echo "su $USERNAME -s /bin/sh -c \"$STARTCMD\"" echo "The following was the output from the startup command:" echo "$OUTPUT" return 1 fi } forcestart() { # Allow starting a server with a stale pid file if [ "$STATUS" == "broken" ] && [ "$TYPE" == "pid-stale" ]; then rm -f "$PIDFILE" check start elif [ "$STATUS" == "stopped" ]; then start elif [ "$STATUS" == "broken" ]; then broken else status fi } stop() { local TIMEOUT COUNT SHUTDOWN= if [ "$STATUS" == "stopped" ]; then echo "$PROG doesn't seem to be running. Please try running" echo "$0 start" # If it isn't a broken server, attempt a proper shutdown before # going right to the force kill. elif [ "$STATUS" == "broken" ] && [ "$TYPE" != "server" ]; then broken else if [ "$EUID" -ne 0 ];then echo "Please run as root" exit 1 fi # If it isn't a broken server, attempt a proper shutdown before # going right to the force kill. if [ "$TYPE" != "server" ]; then # sickchill can take some time to properly shut down. # It takes more than 10 seconds on my system to properly close. # If it doesn't close by the timeout, force close it. TIMEOUT=15 echo -n "Giving $PROG $TIMEOUT seconds to shut down: " curl -s --max-time 5 http://localhost:"$PORT"/home/shutdown/?pid="$PID" | grep -q "shutting down" SHUTDOWN=failed for (( COUNT=0; COUNT < TIMEOUT; COUNT++ )); do if ! kill -0 "$PID" 2> /dev/null; then SHUTDOWN=success break fi sleep 1 done fi if [ "$SHUTDOWN" == "success" ]; then echo "Shutdown successful." else if [ "$SHUTDOWN" == "failed" ]; then echo "Normal Shutdown Failed - Attempting to kill the process." else echo "Server is not responding - Attempting to kill the process." fi kill -9 "$PID" sleep 1 if kill -0 "$PID" 2>/dev/null; then echo "ERROR: Failed to terminate PID $PID." return 1 fi fi fi } case "$1" in start) check start ;; force-start) check forcestart ;; stop) check stop ;; restart) check stop sleep 1 check start ;; status) check status ;; *) echo "Usage: $0 {start|force-start|stop|restart|status}" exit 1 esac