#!/bin/bash

# Copyright (C) 2008-2015 Petter Reinholdtsen <pere@hungry.com>
#
# This program is free software; you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation; either version 2 of the License, or
# (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License along
# with this program; if not, write to the Free Software Foundation, Inc.,
# 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA

# Using /bin/bash and not /bin/sh to get a shell with support for random number
# generation.
#
# Automatically turn off computers at night, defined as 16:00 - 06:00.
# Should schedule wakeup in the morning.

# How do we decide if it should be enabled for this host?  Netgroup
# membership?  LDAP search?  Flag file?

#set -e

WAKEUPTIME="07:00"
NVRAM_WAKE_ENABLED="no"
ACPI_WAKE_ENABLED="no"

if [ -r /etc/default/shutdown-at-night ]; then
    . /etc/default/shutdown-at-night
fi

wakeuptime="$WAKEUPTIME"

hostname="$(uname -n)"
netgroupname_wakeup="shutdown-at-night-wakeup-hosts"
netgroupname_nowakeup="shutdown-at-night-wakeup-hosts-blacklist"
netgroupname_shutdown="shutdown-at-night-hosts"
netgroupname_noshutdown="shutdown-at-night-hosts-blacklist"

netgroup_wakeup_found=$(netgroup -h $netgroupname_wakeup 1>/dev/null 2>/dev/null && printf "FOUND" || printf "NOT-FOUND");

# Hook directories to allow custom code to detect if a machine is used
# or not.
unuseddlist="/etc/shutdown-at-night/unused.d /usr/lib/shutdown-at-night/unused.d"

in_nowakeup_netgroup() {
    # Try both long and short name
    for h in "$(uname -n)" "$(hostname -s)" ; do
        if innetgr -h "$h" "$netgroupname_nowakeup" ; then
            return 0
        fi
    done
    return 1
}

in_wakeup_netgroup() {
    # Try both long and short name
    for h in "$(uname -n)" "$(hostname -s)" ; do
        if innetgr -h "$h" "$netgroupname_wakeup" ; then
            return 0
        fi
    done
    return 1
}

in_shutdown_netgroup() {
    # Try both long and short name
    for h in "$(uname -n)" "$(hostname -s)" ; do
        if innetgr -h "$h" "$netgroupname_noshutdown"; then
            return 1
        elif innetgr -h "$h" "$netgroupname_shutdown" ; then
            return 0
        fi
    done
    return 1
}

wakeup_enabled_for_host() {
    # Flag for now
    if [ -f /etc/shutdown-at-night/shutdown-at-night-nowakeup-self ]; then
        logger -t shutdown-at-night "Blocking NVRAM/RTC based wake-up of client $hostname (myself); remove /etc/shutdown-at-night/shutdown-at-night-nowakeup-self if wanted otherwise."
        return 1
    elif [ -f /etc/shutdown-at-night/shutdown-at-night ]; then
        return 0
    elif in_nowakeup_netgroup; then
        return 1
    elif in_wakeup_netgroup; then
        return 0
    elif [ "x$netgroup_wakeup_found" != "xFOUND" ] && (! in_nowakeup_netgroup ) && in_shutdown_netgroup; then
        return 0
    fi
    return 1
}

shutdown_enabled_for_host() {
    # Flag for now
    if [ -f /etc/shutdown-at-night/shutdown-at-night ] || in_shutdown_netgroup; then
        return 0
    fi
    return 1
}

# Take wakeup hour:minute in local time zone and return wake up time
# in seconds since EPOC UTC, radomized around the target wakeup time.
whentowakeup() {
    wakeuptime=$1
    # Spread the BIOS wakeup time across a 20 minute period, to avoid
    # all of them waking up at the same time, killing a fuse.

    # Convert local target time in seconds since EPOC UTC
    targettime=$(date +%s -d "tomorrow $wakeuptime $(date +%z)")
    echo $(( $targettime - 600 + $RANDOM % 1200))
}

nvramwakeup() {
# http://www.vdr-portal.de/board/thread.php?threadid=75582&sid=b7aced20e710aef12ffa56b5197fe168
    wakeuptime=$1
    when=$(whentowakeup $wakeuptime)

    # Make sure HW clock is correct, as it is used to decide when to
    # wake up.
    /etc/init.d/hwclock.sh stop > /dev/null

    # Make sure /dev/nvram is available
    modprobe nvram

    # This require a supported motherboard
    if nvram-wakeup -s $when > /dev/null ; then
        logger -t shutdown-at-night "scheduled $hostname to turn itself on at $wakeuptime using nvram-wakeup."
        return 0
    else
        return 1
    fi
}

# This method might not work if the hardware clock is updated during
# shutdown.  Changing /etc/default/hwclock to list HWCLOCKACCESS=yes
# to disable it.
# Based on <URL: http://www.mythtv.org/wiki/ACPI_Wakeup >
acpiwakeup() {
    wakeuptime="$1"
    if [ -e /sys/class/rtc/rtc0/wakealarm ] ; then
        when=$(whentowakeup $wakeuptime)

        # First reset alarm
        echo 0 > /sys/class/rtc/rtc0/wakealarm

        # Next, set it to our selected time
        echo $when > /sys/class/rtc/rtc0/wakealarm
        logger -t shutdown-at-night "scheduled $hostname to turn itself on at $wakeuptime using /sys/class/rtc/rtc0/wakealarm."
        return 0
    else
        return 1
    fi
}

prepare_wakeonlan() {
    interface="$(/sbin/route -n | awk '/^0\.0\.0\.0 / { print $8 }')"
    ethtool -s $interface wol g 2>/dev/null
}

# Return true if local user is logged in, false otherwise
is_local_user() {
    if [ "$(who | grep -v '\(unknown\)')" ] ; then
        return 0
    else
        return 1
    fi
}

# Return true if ldm login is active
is_active_ldm_session() {
    if ps -efwww | egrep -q ' ssh .*LTSP_CLIEN[T]' ; then
        return 0
    else
        return 1
    fi
}

# Return false if X session is confirmed unused (ie login screen is
# shown).  If not sure, claim it is used to be safe.
is_xsession_used() {
    for s in \
        /var/run/gdm3/auth-for-Debian-gdm-*/database \
        /var/lib/lightdm/.Xauthority \
        /var/run/xauth/* \
        /run/xauth/*; do
        if [ -e "$s" ] ; then
            if XAUTHORITY="$s" DISPLAY=:0 xlsclients | egrep -q 'kdmgreet|lightdm-gtk-greeter|razor-lightdm-greeter|lightdm-kde-greeter' \
               || [ -d /var/run/gdm3/greeter ] ; then
               return 1
            fi
        fi
    done
    return 0
}

is_host_unused() {
    # Logged in users, or ldm connection to a remote server
    if is_xsession_used || is_local_user || is_active_ldm_session; then
        return 1
    fi
    # Uptime is less than one hour
    if (( $(cat /proc/uptime  | awk '{print int($1)}') < 3600 )) ; then
        return 1
    fi
    for dir in $unuseddlist ; do
        if [ -d $dir ] ; then
            for f in $dir/* ; do
                if [ -x $f ] && ! $f ; then
                    return 1
                fi
            done
        fi
    done
    return 0
}

fatal() {
    logger -t shutdown-at-night "$1"
    exit 1
}

if shutdown_enabled_for_host ; then
    if is_host_unused; then
        logger -t shutdown-at-night "turning off unused client $hostname."
        if wakeup_enabled_for_host; then
            logger -t shutdown-at-night "Preparing client $hostname for waking up in the morning."
            if [ type nvram-wakeup >/dev/null 2>&1 ] && [ "$NVRAM_WAKE_ENABLED" = yes ] ; then
                nvramwakeup $wakeuptime
            fi
            if [ "$ACPI_WAKE_ENABLED" = yes ] ; then
                acpiwakeup $wakeuptime
            fi
            prepare_wakeonlan || fatal "unable to enable wake-on-lan - aborting shutdown."
        fi
        shutdown -h 5 "Unused host being turned off for the night (cron)." < /dev/null > /dev/null 2>&1 &
    else
      logger -t shutdown-at-night "client $hostname is in use; shutdown sequence will NOT be initiated."
    fi
else
    logger -t shutdown-at-night "shutdown-at-night is not enabled for client $hostname."
fi
