Mittwoch, 28. September 2016

Debian Jessie auf WD MyBook Live (MBL) - Teil 3: Anpassungen

Nach erfolgter, sauberer Debian Jessie Intallation auf dem WD MyBook Live braucht es noch ein paar Handgriffe zum perfekten Heimserver.

Man sollte sich in jedem Fall im Klaren sein, dass mit 256MB Ram heute kein Blumentopf mehr zu gewinnen ist, aber eine bedachte Paketauswahl macht das System zu einem schnellen und leisen Server. (Ich habe z.B. mini-httpd statt Apache installiert, PHP ist bei mir der größte Speicherfresser, daher nutze ich es sehr sparsam. Eine Datenbank kommt nicht zum Einsatz - die Festplatte soll schließlich die meiste Zeit schlafen.)



Im Falle der Neuformatierung der Datenplatte (wegen 16k PageSize) erledigt das ein:

mkfs.ext4 -b 16384 -m 0 /dev/sda4
mount /dev/sda4 /media

Die Swap-Partition wird neu initialisiert mit:

mkswap /dev/sda3
swapon /dev/sda3

Will man nicht mehr ins alte System booten, können die Raid-Informationen auch gelöscht werden, so dass /dev/sda2 normal gemountet werden kann:

apt-get install nullmailer mdadm
parted /dev/sda set 1 raid off
parted /dev/sda set 2 raid off
mdadm --zero-superblock /dev/sda2
mount /dev/sda2 /mnt

Nun können die beiden Zeilen in der fstab auch wieder auskommentiert werden.
Die letzte Zeile der fstab mountet das Logverzeichnis als TMPFS, d.h. die Festplatte muss nicht aus dem Standby geholt werden. Da das Ramlog Paket für Jessie nicht zur Verfügung steht müssen wir uns selbst behelfen:

#!/bin/sh
#
# /usr/local/sbin/ramlog.sh
#

. /lib/lsb/init-functions

start() {
        log_begin_msg "RAMLOG: Read files from disk.."
        tar xfz /var/ram_log.tar.gz -C /
        log_end_msg 0
}

stop() {
        log_begin_msg "RAMLOG: Write files to disk.."
        tar cfz /var/ram_log.tar.gz --directory=/ var/log/
        log_end_msg 0
}

case "$1" in
        start)
                start
                ;;
        stop)
                stop
                ;;
        flush)
                stop
                ;;
        *)
                echo "Usage: $0 {start|stop|flush}"
                exit 1
esac

Und der entsprechende Service Eintrag für Systemd

#/etc/systemd/system/ramlog.service
[Unit]
Description=Ramlog
After=local-fs.target
Before=cron.service syslog.service

[Service]
Type=oneshot
RemainAfterExit=yes
ExecStart=/usr/local/sbin/ramlog start
ExecStop=/usr/local/sbin/ramlog stop

[Install]
WantedBy=multi-user.target

Dem MyBook Live fehlt außerdem eine RTC, daher sollte die per NTP geholte Zeit gelegntlich zwischengespeichert werden, damit sie z.B. beim Starten gleich zur Verfügung steht. Das macht das Script /etc/init.de/saveclock.sh aus dem Original-Image:

#!/bin/sh
#
# /etc/init.d/saveclock.sh
### BEGIN INIT INFO
# Provides:          saveclock
# Required-Start:
# Required-Stop:
# Default-Start:     S
# Default-Stop:  0 1 6
# Short-Description: periodically save clock to disk
# Description: save clock to disk on startup and periodically
### END INIT INFO


. /lib/lsb/init-functions

saveclock=0
buildtime=0
[ -f /var/local/saveclock ] && saveclock=`cat /var/local/saveclock`
[ -f /etc/version.buildtime ] && buildtime=`cat /etc/version.buildtime`

case "$1" in
        start)
                log_daemon_msg "Retrieving saved clock"
                if [ "$saveclock" -lt "$buildtime" ]; then
                        saveclock=$buildtime
                fi
                if [ "$saveclock" != "0" ]; then
                        date -s @$saveclock
                        echo $saveclock > /var/local/saveclock
                        echo "Set clock to `date`"
                fi
                log_end_msg 0
                ;;
        stop|reload)
                date +%s > /var/local/saveclock
                ;;
        *)
                echo "Usage: /etc/init.d/saveclock.sh {start|stop|reload}"
                exit 1
                ;;
esac

exit 0

Und alles Aktivieren (auch gleich noch das /tmp Verzeichnis als TMPFS anlegen):

/usr/local/sbin/ramlog flush
rm -rf /var/log/*
mount /var/log
systemctl enable ramlog.service
systemctl start ramlog.service
systemctl enable tmp.mount
systemctl start tmp.mount
rm /lib/systemd/system/getty.target.wants/getty-static.service
cd /etc/init.d/
/usr/sbin/update-rc.d saveclock.sh defaults
/usr/sbin/update-rc.d saveclock.sh enable

Da irgendwann das /var/log überlaufen würde, müssen die Log-Dateien von Zeit zu Zeit bereinigt werden. Dazu benutze ich ebenfalls ein original WD-Script (/usr/local/sbin/rotateLogs.sh):

# /etc/crontab: system-wide crontab

2 */4   * * *   root    [ ! -f /tmp/standby ] && /etc/init.d/saveclock.sh reload > /dev/null 2>&1
2 */4   * * *   root    [ ! -f /tmp/standby ] && /usr/local/sbin/rotateLogs.sh > /dev/null 2>&1
2 */4   * * *   root    [ ! -f /tmp/standby ] && /usr/local/sbin/ramlog flush > /dev/null 2>&1

14 07   * * *   root    /usr/sbin/ntpdate-debian >> /dev/null

#!/bin/sh
#
# 2010 Western Digital Technologies, Inc. All rights reserved.
#
# /usr/local/sbin/rotateLogs.sh - calls logrotate and purglogs
##

PATH=/sbin:/bin:/usr/bin:/usr/sbin:/usr/local/bin:/usr/local/sbin

# check for log files larger than 1MB & do cleanup
/usr/local/sbin/purgelogs.sh

# check if /var/log/messages file is removed, re-create
if [ ! -e /var/log/messages ]; then
    logrotate -f /etc/logrotate.conf
fi

# check if logs are full, cleanup rotated logs if so
dfout=`df | grep "/var/log"`
percent=`echo "$dfout" | awk '{printf("%.0f\n", $5) }'`
if [ "$percent" -gt "90" ]; then
        logger "Logs getting full, remove all rotated logs"
        rm -f /var/log/*.1*
        rm -f /var/log/*.2*
fi

#!/bin/bash
#
# 2012 Western Digital Technologies, Inc. All rights reserved.
#
# /usr/local/sbin/purgelogs.sh - purges logfiles in order to keep the log TMPFS in check
##

PATH=/sbin:/bin:/usr/bin:/usr/sbin:/usr/local/bin:/usr/local/sbin

do_start() {
    # check for log files larger than 1MB and remove
    MAX_SIZE="1M"
    N_LINES="1000"
    purgeRecord="/var/log/purge.log"

    FILE_LIST=( `find /var/log/* -type f -size +$MAX_SIZE` )
    if [ ! -z "$FILE_LIST" ]; then
        echo `date` >> $purgeRecord

        dfout=`df | grep "/var/log"`
        percent=`echo "$dfout" | awk '{printf("%.0f\n", $5) }'`

        # if ramlog full remove files, else keep last N lines
        if [ "$percent" -gt "90" ]; then
            for i in "${FILE_LIST[@]}"
            do
                echo "ramlog is full, removing files:" >> $purgeRecord
                size=$(stat -c%s "$i")
                echo "$i    $size" >> $purgeRecord
                rm -f $i
            done
         else
            for i in "${FILE_LIST[@]}"
            do
                if [[ "$i" == *.gz ]]; then
                    echo "removed: $i" >> $purgeRecord
                    rm -f $i
                else
                    echo "pruned: $i" >> $purgeRecord
                    tail -n $(($N_LINES)) $i > tmp.log
                    rm -f $i
                    mv tmp.log $i
                fi
            done
         fi
     fi
}

case "$1" in
  start|"")
    do_start
    ;;
  restart|reload|force-reload)
    echo "Error: argument '$1' not supported" >&2
    exit 3
    ;;
  stop)
    # No-op
    ;;
  *)
    echo "Usage: purgelogs.sh [start|stop]" >&2
    exit 3
    ;;
esac


Um die Festplatte schlafen zu legen, benutze ich das monitorio.sh Script von Western Digital und wandle es leicht ab:

#!/bin/bash
#
# (c) 2011 Western Digital Technologies, Inc. All rights reserved.
#
# /usr/local/sbin/monitorio.sh - Monitor disk activity, and put system into standby.  Also, monitor to trigger file tally process
##
PATH=/sbin:/bin:/usr/bin:/usr/sbin:/usr/local/bin:/usr/local/sbin
. /lib/lsb/init-functions

MIN_SINCE_DISK_ACCESS=/tmp/minutes_since_disk_access

dataVolumeDevice=/dev/sda4

declare -i sleepcount
declare -i rootdisk_thresh
declare -i enterStandbyTime=0
rm -f /tmp/standby
source /usr/local/etc/standby.conf

resetSleepCount() {
        sleepcount=0

        source /usr/local/etc/standby.conf
        rootdisk_thresh=$((standby_time-1))
}

currentRootDevice=`cat /proc/cmdline | awk '/root/ { print $1 }' | cut -d= -f2`
rootDisk=`basename ${currentRootDevice}`
dataVolumeDisk=`basename ${dataVolumeDevice}`
drivelist=(${dataVolumeDevice})

echo "0" > ${MIN_SINCE_DISK_ACCESS}

if [ "$1" == "debug" ]; then
        echo "1" > /proc/sys/vm/block_dump
        dmesg -c > /dev/null
fi

while :; do

    for i in ${drivelist[@]}; do
            hdparm -C $i | grep -q "standby"
            standby_test=$?
            [ "$standby_test" -eq "1" ] && break
    done

    if [ "$standby_test" -eq "0" ]; then
        sleep 5
        continue
    else
        if [ -f /tmp/standby ]; then
            standby_since=`stat --format %z /tmp/standby`
            rm -f /tmp/standby
            # Cancel blue color and turn on green if applicable
            echo green > /sys/class/leds/a3g_led/color
            currentTime=`date +%s`
            timeInStandby=`expr $currentTime - $enterStandbyTime`
            echo "exit standby after $timeInStandby (since $standby_since)"
            logger "exit standby after $timeInStandby (since $standby_since)"
            if [ "$1" == "debug" ]; then
                    dmesg -c
            fi
        fi

                resetSleepCount

        echo $sleepcount > ${MIN_SINCE_DISK_ACCESS}
        ior_root=`awk -v disk="${rootDisk}" '{if ($3==disk) print $6}' /proc/diskstats`
        iow_root=`awk -v disk="${rootDisk}" '{if ($3==disk) print $10}' /proc/diskstats`
        ior_datavol=`awk -v disk="${dataVolumeDisk}" '{if ($3==disk) print $6}' /proc/diskstats`
        iow_datavol=`awk -v disk="${dataVolumeDisk}" '{if ($3==disk) print $10}' /proc/diskstats`
        if [ "$1" == "debug" ]; then
                echo "Init          ior_datavol=$ior_datavol ior_datavol2=$ior_datavol2"
                echo "              iow_datavol=$iow_datavol iow_datavol2=$iow_datavol2"
                echo "              ior_root=$ior_root       ior_root2=$ior_root2"
                echo "              iow_root=$iow_root       iow_root2=$iow_root2"
                dmesg -c
        fi

        while :; do
            # Wait for 60 seconds
            sleep 60
            ior_root2=`awk -v disk="${rootDisk}" '{if ($3==disk) print $6}' /proc/diskstats`
            iow_root2=`awk -v disk="${rootDisk}" '{if ($3==disk) print $10}' /proc/diskstats`
            ior_datavol2=`awk -v disk="${dataVolumeDisk}" '{if ($3==disk) print $6}' /proc/diskstats`
            iow_datavol2=`awk -v disk="${dataVolumeDisk}" '{if ($3==disk) print $10}' /proc/diskstats`

            # use data volume writes until near sleep threshold, then check all disk writes
            old_sleepcount=sleepcount
            if [ $((sleepcount)) -eq $((rootdisk_thresh)) ] && [ "$ior_root" -eq "$ior_root2" ] && [ "$iow_root" -eq "$iow_root2" ]; then
                sleepcount=$((sleepcount+1))
            elif  [ $((sleepcount)) -lt $((rootdisk_thresh)) ] && [ "$ior_datavol" -eq "$ior_datavol2" ] && [ "$iow_datavol" -eq "$iow_datavol2" ]; then
                sleepcount=$((sleepcount+1))
            else
                resetSleepCount
            fi
            echo $sleepcount > ${MIN_SINCE_DISK_ACCESS}
            if [ "$1" == "debug" ]; then

                [ "$sleepcount" != "0" ] &&  echo "sleepcount: $sleepcount"
                [ "$sleepcount" == "0" ] && echo "Disk activity:"
                echo "... ior_datavol=$ior_datavol      ior_datavol2=$ior_datavol2"
                echo "... iow_datavol=$iow_datavol      iow_datavol2=$iow_datavol2"
                echo "... ior_root=$ior_root    ior_root2=$ior_root2"
                echo "... iow_root=$iow_root    iow_root2=$iow_root2"
                # dmesg -c
            fi
            ior_datavol=$ior_datavol2
            iow_datavol=$iow_datavol2
            ior_root=$ior_root2
            iow_root=$iow_root2

            if [ "$standby_enable" == "enabled" ] && [ "$sleepcount" -eq "$standby_time" ] ; then
                touch /tmp/standby
                enterStandbyTime=`date +%s`
                echo "Enter standby"
                if [ "$1" == "debug" ]; then
                        echo "`date`: Enter standby "
                        dmesg -c > /dev/null
                fi
                for i in ${drivelist[@]}; do
                        hdparm -y $i >/dev/null
                done

                # turn on solid blue if applicable
                echo blue > /sys/class/leds/a3g_led/color
                sleep 5
                break
            fi
        done
    fi
done

Damit das Script funktioniert, muss noch eine Config Datei angelegt werden:

#/usr/local/etc/standby.conf

standby_enable=enabled
standby_time=14

Und eine entsprechende Systemd Unit:

#/etc/systemd/system/monitorio.service
[Unit]
Description=Disk Sleep Daemon
After=local-fs.target

[Service]
ExecStart=/usr/local/sbin/monitorio.sh
ExecStop=/usr/bin/killall monitorio.sh

[Install]
WantedBy=multi-user.target

Um aus dem RAM auch noch das letzte Quäntchen herauszubekommen, nutze ich ein Kompressions-Modul und damit ca 75% des Speichers als komprimierten SWAP.
Dazu muss das "zram" Modul beim start geladen werden:

# /etc/modules: kernel modules to load at boot time.
#
# This file contains the names of kernel modules that should be loaded
# at boot time, one per line. Lines beginning with "#" are ignored.

zram

Die Einrichtung erfolgt wieder über eine Systemd Unit:

[Unit]
Description=Activate zram swap device
After=systemd-modules-load.service
#zram module must be loaded (e.g. put it into /etc/modules)
ConditionPathExists=/sys/module/zram

[Service]
#the zram device to be used
Environment=ZRAM_DEVICE=0

#the size of the zram swap disk in bytes (K,M,G suffix allowed)
#75% of 256MB
Environment=ZRAM_DISKSIZE=197197824

#the swap priority
Environment=ZRAM_SWAP_PRIO=20

Type=oneshot
RemainAfterExit=yes

ExecStart=/bin/sh -c "echo 1 > /sys/block/zram$ZRAM_DEVICE/reset"
ExecStart=-/bin/sh -c "echo $(nproc --all) > /sys/block/zram$ZRAM_DEVICE/max_comp_streams"
ExecStart=/bin/sh -c "echo $ZRAM_DISKSIZE > /sys/block/zram$ZRAM_DEVICE/disksize"
ExecStart=/sbin/mkswap /dev/zram${ZRAM_DEVICE}
ExecStart=/sbin/swapon -p ${ZRAM_SWAP_PRIO} /dev/zram${ZRAM_DEVICE}
ExecStop=/sbin/swapoff /dev/zram${ZRAM_DEVICE}
ExecStop=/bin/sh -c "echo 1 > /sys/block/zram$ZRAM_DEVICE/reset"

[Install]
WantedBy=multi-user.target