#!/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 # Exit when run as non-root unless just checking the status if [ "$EUID" -ne 0 ] && [ "$1" != "status" ]; then echo "This script must be run as root." exit 1 fi # Ensure all required variables are set in conf file # Edit conf file in /etc/$FULLPROG.conf for any changes MISSING=0 VAR= 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") # It should return 200 on a normal system, but: # - 301/302: Redirection (e.g. forced HTTPS, a custom web root like /sickchill, or bounced to /login) # - 401: Built-in HTTP basic/digest authentication is turned on # - 403: Forbidden (subnet whitelisting or reverse proxy rules blocking access) # Any of these should mean sickchill is up and running. case "$SERVER_STATUS" in 200|301|302|401|403) return 0 ;; *) return 1 ;; esac } # 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 "Running '$0 start' will automatically remove it and launch the service." 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= # Let's not stop startup just because of a stale pid file if [ "$STATUS" == "broken" ] && [ "$TYPE" == "pid-stale" ]; then echo "Removing stale PID file: $PIDFILE" rm -f "$PIDFILE" STATUS=stopped fi 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 "FAILED" 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 } 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 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 # Try sigterm first if [ "$SHUTDOWN" != "success" ]; then if [ "$SHUTDOWN" == "failed" ]; then echo "Normal Shutdown Failed - Requesting safe system shutdown (SIGTERM)." else echo "Server is not responding - Requesting safe system shutdown (SIGTERM)." fi kill -15 "$PID" 2>/dev/null # Quick 5-second wait to see if the polite request works for (( COUNT=0; COUNT < 5; 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." # Ensure the PID file is removed rm -f "$PIDFILE" else # If even SIGTERM fails, we resort to your original forced kill echo "Safe shutdown failed - Forcing process to stop immediately (SIGKILL)." kill -9 "$PID" sleep 1 if kill -0 "$PID" 2>/dev/null; then echo "ERROR: Failed to terminate PID $PID." return 1 else # Remove the PID file so it can start normally rm -f "$PIDFILE" fi fi fi } case "$1" in start) check start || exit 1 ;; stop) check stop || exit 1 ;; restart) check stop || exit 1 sleep 1 check start || exit 1 ;; status) check status ;; *) echo "Usage: $0 {start|stop|restart|status}" exit 1 esac