How to shutdown nethserver automatically

NethServer Version: 7.9.2009 (final)

I want to shut down my nethserver during night if not used anymore.
Before I used nethserver I had a Ubuntu based server and had a script installed which checked the LAN for active devices. The script was started when the system reached a specific runlevel.
I want to realize the same on my nethserver and I’m not sure which would be a practicable way to do this. Would it be better to configure it with systemd, as a action or …?
Does anybody has a hint or realized the same already?

Regards
Pete

Maybe you can share the script to find the best way but usually systemd is recommended to start a script as it’s most reliable and controllable.

I also found these scripts where a cronjob is used to run a network activity check script.

1 Like

Hi

I’ve had clients requiring something similiar in the past.

First thing is agreeing on time frames: Must be running, can run, should be off…

During the can run period, run a check script eg on connected windows hosts (PCs / Notebooks) Most of the clients requiring this happened to be mostly Windows shops…

If no Windows are registered within two check routines (Every 15 Minutse), the server is then shut down by another script, checking if the f"lags" above are set. (Windows yes, two flags…).

To keep things simple, these were two files created / modified / deleted by the check scripts, in a secified folder (/etc/checkscripts). The simple existance of those files was checked using if exist, then triggering shutdown if true…

All of this of course after the last backup of the day!

In the morning, the server is either started from BIOS at a set time, or waken using WOL (Wake on LAN) from another host, like the OPNsense firewall…

My 2 cents
Andy

2 Likes

Hi,
here is the script. It is based on script published by the c’t magazine couple of years ago which I modified a little bit.

#!/bin/bash
#
# Server v0.5.1b
#
# Überwacht das LAN auf aktive Rechner und
# fährt den Server runter, wenn niemand mehr
# im LAN aktiv ist
#
# version 0.1 basic features
# version 0.2 add status request, some minor bugfixes
# version 0.2.1 supports gettext for output, checks UID, remove some
#               bashish style like function keywords ...
# version 0.2.2 added a init info section for debian and opensuse (dependence boot/init)a
# version 0.3.0 feature added: check of ip range (i.e for IPs given by DHCP)
# version 0.4.0 feature added: set different hours to check depending on day of week
# version 0.4.1 feature added: Toggle added to switch on/off ip range check
# version 0.5.0 changes: Logging to a specific logfile instead of the use of 'logger' using /var/log/syslog
# version 0.5.1 changes: Some changes in code
#		
#
# Redaktion c't / heise netze 
# http://ct.de
# http://www.heisenetze.de
# mercurial repository: http://mercurial.intuxication.org/hg/server-sleepd
# example: hg clone http://mercurial.intuxication.org/hg/server-sleepd


### BEGIN INIT INFO
# Provides:       server-sleepd
# Required-Start: $network $syslog $remote_fs
# Required-Stop:  $network $syslog $remote_fs
# Default-Start:  3 5
# Default-Stop:   0 1 2 6
# Short-Description: stopps the server if there is no active host in the lan
# Description:    stopps the server if there is no active host in the lan
### END INIT INFO

##
# multilanguage support via get
TEXTDOMAINDIR=/usr/local/share/locale/
TEXTDOMAIN=server-sleepd

#Switch on the debug function
# DEBUG: 1 == an/on, '' == aus/off
DEBUG=''

# Switch on/off the logging to a specific file
# 1 == an/on, '' == aus/off
LOGGING='1'
LOGDATE=$(date +%d.%m.%Y" "%X)
BASENAME=$(basename $0)
LOGFILE="/var/log/${BASENAME%.*}.log"
[ ${LOGGING} ] && echo $"----- ${LOGDATE} Script '${BASENAME}' started ------" >> ${LOGFILE}


##
# Testen ob das Skript mit root Berechtigungen aufgerufen wird
# Check if the script is started with root permissions
if [ ${UID} -ne 0 ]
then
    [ ${DEBUG} ] && echo "Missing permissions: The script needs to be started with root permissions!"    
    [ ${LOGGING} ] && echo ${LOGDATE} $"Missing permissions: You must run the script with root permissions!" >> ${LOGFILE}
    exit 1
fi

# Lockfile enthält Prozess-ID (PID)
LOCKFILE="/var/run/${BASENAME}.pid"


# Ping-Befehl mit Parametern
# hier: sendet zwei Anfragen
PING="ping -c 2"

# Liste der zu testenden IP-Adressen in das Array 'HOSTS' schreiben
# Write list of IP-adresses to be checked into array 'HOSTS'
HOSTS=(
192.168.234.9
192.168.234.10
192.168.234.13
)

# Definition eines IP-Subnetzes und eines IP-Adressbereichs (z.B. via DHCP vergebene IP-Adressen)
# Definition of a IP-subnet and IP-addressrange to check (i.e. DHCP)
CheckIPRange=yes   # 'yes' = an/on '' == aus/off
subnetIP=192.168.234.0
startIP=151
endIP=179

# Prüfintervall des skripts (Zeit in Sekunden)
# Interval in Seconds
INTERVAL=1800

# Festlegen an welchem Tag zu welcher Stunde geprüft werden soll
# Set the hours when to check depending on day of week
WEEKDAY=$(date +%u)
case $WEEKDAY in
   1) # Monday/Montag
      TIME2CHECK="00 01 10 11 12 13 14 15 16 17 18 19 20 21 22 23"
      ;;
   2) # Tuesday/Dienstag
      TIME2CHECK="00 01 10 11 12 13 14 15 16 17 18 19 20 21 22 23"
      ;;
   3) # Wednesday/Mittwoch
      TIME2CHECK="00 01 10 11 12 13 14 15 16 17 18 19 20 21 22 23"
      ;;
   4) # Thursday/Donnerstag
      TIME2CHECK="00 01 10 11 12 13 14 15 16 17 18 19 20 21 22 23"
      ;;
   5) # Friday/Freitag
      TIME2CHECK="00 01 10 11 12 13 14 15 16 17 18 19 20 21 22 23"
      ;;
   6) # Saturday/Samstag
      TIME2CHECK="00 01 10 11 12 13 14 15 16 17 18 19 20 21 22 23"
      ;;
   7) # Sunday/Sonntag
      TIME2CHECK="00 01 10 11 12 13 14 15 16 17 18 19 20 21 22 23"
      ;;
esac

# Vorgabewert für die Zustandsvariable 'ACTIVE'
# Default value for 'ACTIVE'
ACTIVE=false

# shutdown- oder sleep-Befehl samt Parameter
# *BITTE anpassen* /bin/true ist zum Testen gedacht!
#
# CMD="/sbin/shutdown -h now" # now or +10m
# CMD="/sbin/halt"
# CMD="/usr/sbin/pm-suspend"
# CMD="/usr/sbin/pm-powersave"
# CMD="/bin/true"
delay='+3'   # +time_of_delay (i.e +3) or 'now'
CMD="/sbin/shutdown -h $delay"


##
# Liste der Hosts überprüfen
# check the list 'HOSTS'
checkhosts()
{

ACTIVE=false
HOSTcount=${#HOSTS[*]}
indexHOSTS='0'

while [ $indexHOSTS -lt $HOSTcount ]
do
  ${PING} ${HOSTS[$indexHOSTS]} > /dev/null
  if [ $? -eq 0 ]
   then
       [ ${DEBUG} ] && echo $"$0: ${HOSTS[$indexHOSTS]} is active!"
       [ ${LOGGING} ] && echo ${LOGDATE} "host ${HOSTS[$indexHOSTS]} is active" >> ${LOGFILE}
       ACTIVE=true
     break   # Schleife beenden wenn ein aktiver Host gefunden wurde / terminate the loop when an active machine is found
  else
      [ ${DEBUG} ] && echo $"$0: ${HOSTS[$indexHOSTS]} is not active!"
      [ ${LOGGING} ] && echo ${LOGDATE} "host ${HOSTS[$indexHOSTS]} is not active" >> ${LOGFILE}
  fi
   ((indexHOSTS++))
done

# Wenn kein Host aus der Liste 'HOSTS' aktiv ist, nach Hosts im angegebenen IP-Adressbereich suchen
# If no host of the list 'HOSTS' is active, then look for hosts in the given IP-adress range
if [ "${ACTIVE}" = "false" ] && [ "${CheckIPRange}" = "yes" ]
 then
     checkIP=$startIP
    while [ ${checkIP} -le ${endIP} ]
     do
      ADDR=${subnetIP%.*}"."${checkIP}
      ${PING} ${ADDR} > /dev/null
      if [ $? -eq 0 ]   # Wenn Ping erfolgreich / If Ping unsuccessful
       then
           [ ${DEBUG} ] && echo $"$0: IP-adress range: "${subnetIP%.*}"."${startIP}" - "${subnetIP%.*}"."${endIP}": host "${ADDR}" is active!"
           [ ${LOGGING} ] && echo ${LOGDATE} "IP-adress range: "${subnetIP%.*}"."${startIP}" - "${subnetIP%.*}"."${endIP}": host "${ADDR}" is active" >> ${LOGFILE}
           ACTIVE=true
        break   # Schleife beenden wenn ein aktiver Host gefunden wurde / terminate the loop when an active machine is found
      elif [ $? -eq 1 ]   # Wenn Ping nicht erfolgreich / If Ping unsuccessful
       then
	   [ ${DEBUG} ] && echo $"$0: IP-adress range: "${subnetIP%.*}"."${startIP}" - "${subnetIP%.*}"."${endIP}": host "${ADDR}" is not active!"
           [ ${LOGGING} ] && echo ${LOGDATE} "IP-adress range: "${subnetIP%.*}"."${startIP}" - "${subnetIP%.*}"."${endIP}": host "${ADDR}" is not active" >> ${LOGFILE}
      elif [ $? -eq 1 ] && [ ${checkIP} -eq ${endIP} ]   # Wenn Ping nicht erfolgreich und die letzte IP-Adresse überprüft wurde/ If Ping unsuccessful and last IP-adress ist checked
       then
          [ ${DEBUG} ] && echo $"$0: IP-adress range: "${subnetIP%.*}"."${startIP}" - "${subnetIP%.*}"."${endIP}": No host active!"
	  [ ${LOGGING} ] && echo ${LOGDATE} "IP-adress range: "${subnetIP%.*}"."${startIP}" - "${subnetIP%.*}"."${endIP}": No host active!"  >> ${LOGFILE}
	break
      fi
	     (( checkIP++ ))
     done
fi

# Wenn 'ACTIVE' immernoch 'false, dann den Server ausschalten
# If 'ACTIVE' is still 'false', switch the server off
if [ "${ACTIVE}" = "false" ]
 then
    [ ${DEBUG} ] && echo $"$0: No active hosts, no users logged in, running 'CMD' ..."
    [ ${LOGGING} ] && echo ${LOGDATE} "No active hosts, no users logged in, running 'CMD' ..." >> ${LOGFILE}
    shutoff
else
    [ ${DEBUG} ] && echo $"$0: One host is active, continue ..."
    [ ${LOGGING} ] && echo ${LOGDATE} "One host is active, continue ...." >> ${LOGFILE}
fi

} 


##
# Server ausschalten
# Turn the server off
shutoff()
{
    if [ $delay = now ]
     then
         [ ${DEBUG} ] && echo $"$0: Executing command $CMD ...System is going down $delay!"
         [ ${LOGGING} ] && echo ${LOGDATE} $"Executing command $CMD ...System is going down $delay!" >> ${LOGFILE}
    else
        [ ${DEBUG} ] && echo $"$0: Executing command $CMD ...System is going down in ${delay#+} minutes!"
        [ ${LOGGING} ] && echo ${LOGDATE} $"Executing command $CMD ...System is going down in ${delay#+} minutes!" >> ${LOGFILE}
    fi
    ${CMD}
    
}

##
# Überprüfe auf lokal oder per SSH angemeldete Nutzer, Screen-Sitzungen werden berücksichtigt
# Check for local users or users logged in via SSH, screen sessions are considered
localUsers()
{
    countUSers=$(who | sed '/^\s*$/d' | wc -l)   # Anzahl der Zeilen der Ausgabe von 'who' / Number of lines of the command 'who'
    [ $DEBUG ] && echo $"$0: local users: $countUSers "
	if [ $countUSers -gt 0 ]
	then
		ACTIVEUSERS=$(who -q)
		ACTIVEUSERS=${ACTIVEUSERS%#*}
	fi
    return ${countUSers}
}


##
# Hauptprogramm
# Dauerschleife bis Strg-C, kill, killall ...
# Aufräumarbeiten erledigt das Trap-Kommando oben

case "$1" in 
    start)
        if [ -e ${LOCKFILE} ]   # wenn Lockfile vorhanden / if Lockfile exists
         then
             echo "Script '$BASENAME' is already running!"
             exit
        else
            [ ${LOGGING} ] && echo $"----- ${LOGDATE} Script '${BASENAME}' started ------" >> ${LOGFILE}
            echo -ne $"Starting $0 "
	    $0 run & 
	    while [ ! -e ${LOCKFILE} ]   # wenn Lockfile nicht vorhanden / if Lockfile doesn't exist
	     do
	       echo -ne "."
	       sleep 2
	     done
	    echo $"(PID: "`cat ${LOCKFILE}`")."
	    exit
        fi 
	;;
    stop)
        if [ -e ${LOCKFILE} ]
	then
	  CPID=`cat ${LOCKFILE}`
	  echo -ne $"Stopping $0 with PID $CPID "
	  # beenden mit SIGTERM,
	  # warte bis PID-File entfernt ist
	  while [ -e ${LOCKFILE} ]
	  do
	     echo -ne "."
	     kill ${CPID}
	     sleep 3
	  done
	  echo $"[done]"
          [ ${LOGGING} ] && echo $"----- ${LOGDATE} Script '${BASENAME}' stopped ------" >> ${LOGFILE}    
	else
	  echo $"Script '${BASENAME}' is not running."
	  exit
	fi	  
	;;
    restart)
        [ ${LOGGING} ] && echo $"----- ${LOGDATE} Restarting script '${BASENAME}' ------" >> ${LOGFILE}
        $0 stop ; $0 start
	;;
    run)
	if [ -e ${LOCKFILE} ]   # wenn Lockfile vorhanden / if Lockfile exists
	then 
	    CPID=$(cat ${LOCKFILE})
	    echo $"Script is already running. (PID: ${CPID})"
	    exit -1
	else
            # PID File samt PID speichern
            # wird via trap am Programm-Ende wieder
            # entsorgt
	    echo $$ > ${LOCKFILE}
	    trap "rm -f ${LOCKFILE}; exit" INT TERM EXIT
	fi
	
        # foreground
	while true
	do
	  sleep $INTERVAL
	    for hour in ${TIME2CHECK}
	    do
		if [ `date +%H` -eq ${hour} ]
		 then
		    [ $DEBUG ] && echo $"$0: (TIME2CHECK) hour '"$hour"' matches current time"
		    [ ${LOGGING} ] && echo ${LOGDATE} "(TIME2CHECK): hour '"$hour"' matches current time" >> ${LOGFILE}
	            # Wenn kein Nutzer angemeldet ist die Hosts überprüfen, ansonsten Skript weiter ausführen
		    # If no user is logged in check the hosts, otherwise continue the script
		    localUsers
		    case $countUSers in
   			0)   [ ${DEBUG} ] && echo $"No user logged in"
			     [ ${LOGGING} ] && echo ${LOGDATE} "No user logged in" >> ${LOGFILE}
			     checkhosts   #hosts überprüfen / check hosts
				;;
   			1)   [ ${DEBUG} ] && echo $"$countUSers user logged in. Name of user logged in: ${ACTIVEUSERS}"
			     [ ${LOGGING} ] && echo ${LOGDATE} "$countUSers user logged in. Name of user logged in: ${ACTIVEUSERS}" >> ${LOGFILE}
				;;
   			*)   [ ${DEBUG} ] && echo $"$countUSers users logged in. Names of users logged in: ${ACTIVEUSERS}"
			     [ ${LOGGING} ] && echo ${LOGDATE} "$countUSers users logged in. Names of users logged in: ${ACTIVEUSERS}" >> ${LOGFILE}
				;;
		    esac
		elif [ `date +%H` -lt ${hour} ]
		 then
		     break    
		else
		    [ ${DEBUG} ] && echo $"$0: (TIME2CHECK) hour '"$hour"' is checked"
		    [ ${LOGGING} ] && echo ${LOGDATE} "(TIME2CHECK): hour '"$hour"' is checked" >> ${LOGFILE}
		    continue
		fi
	    done
	done	
	;;
    status)
	# Statusabfrage
	if [ -e ${LOCKFILE} ]   # wenn Lockfile vorhanden / if Lockfile exists
	then
	    CPID=$(cat ${LOCKFILE})
	    echo "Script '$BASENAME' is running with PID $CPID "
	else
	    echo "Script '$BASENAME' is not running."
	fi
	exit 
	;;
    *)
	echo $"usage: $0 start|stop|restart|run|status"
	echo $"options:"
	echo $"  start: runs program in background"
	echo $"   stop: stops background program"
	echo $"restart: restarts a running instance"
	echo $"    run: runs program in foreground, stopp with Strg-C"
	echo $" status: checks whether program is running"
	exit
	;;
esac


1 Like

Hi Andy,
your idea is interesting as well, I’ll think about this.

I want to use my opnsense firewall to wake up my server, do you know if it’s possible to create a timeschedule to send the magic paket?

Regards Pete

Hi @Pete

Yes that’s no problem.

I’m an avid user of OPNsense for my 30 clients.
OPNsense comes with a WOL module and GUI.
Once that’s installed, it’s simply a matter of setting a cron job at the right time.
Create a script file, eg /etc/Jobs/wol.script. Set the command including the MAC Address in there.
Don’t forget to set the right permissions on the script / folder (700, user root/group root).

My 2 cents
Andy

1 Like

Hi Andy,
thanks for your hints, as soon as I have some spare time I’ll give it a try.

Thanks Pete