FreeBSD-Server inkl. Jails auf neue Version upgraden

Ich bin gerade dabei, einen FreeBSD-Server, der als Host für diverse ezjail-Jails dient, auf eine neue FreeBSD-Version upzugraden. Als Referenz für mich dokumentiere ich das ganze hier einfach mal.

Das System läuft aktuell auf 10.1 und wird auf 10.3 gehoben. Bei späteren Upgrades müssen die Versionsnummern entsprechend angepasst werden.

Als erstes bringen wir das aktuell installierte System auf den letzten Stand und aktualisieren die Ports.

freebsd-update fetch
freebsd-update install
portsnap fetch update

Im nächsten Schritt laden wir die neuen Systemfiles herunter, installieren den neuen Kernel und rebooten das System.

freebsd-update upgrade -r 10.3-RELEASE
freebsd-update install
shutdown -r now

Nach dem Reboot installieren wir die restlichen Systemfiles und rekompilieren alle installierten Ports. Anschließend starten wir den Server noch mal neu. Die Option -m DISABLE_VULNERABILITIES=yes setze ich, damit ich nicht mitten im Upgrade aufhören muss, falls bei einem Port Sicherheitslücken bekannt sind. Temporär damit hinter der Firewall zu leben erscheint mir sinnvoller, als ein gebrickter Server. 😉

freebsd-update install
portmaster -af -m DISABLE_VULNERABILITIES=yes
shutdown -r now

Nach dem Reboot ist der Server an sich auf 10.3. Nun müssen noch die ezjail-Jails upgegradet werden.

Hierfür laden und installieren wir zunächst die aktualisierten Files für die Basejail und starten anschließend ezjail neu.

ezjail-admin install -r 10.3-RELEASE
env UNAME_r=10.3-RELEASE ezjail-admin update -s 10.1-RELEASE -U
ezjail-admin update -P
service ezjail-admin restart

Jetzt müssen wir nur noch in jeder einzelnen Jail die Ports rekompilieren und einmal neustarten.

portmaster -af -m DISABLE_VULNERABILITIES=yes
service -R

Das war’s. Wenn keine Probleme auftreten und kann man damit in ner Stunde durch sein. Wenn mehrere Jails betrieben werden und viele Ports gebaut werden müssen, dauert es entsprechend länger.

Status einer SSH-Session an sudo weitergeben

Wenn man sich per SSH auf einen Rechner connected und anschließend mit “sudo“ root-Rechte erlangt, kann man in dieser Session nicht feststellen, ob man per SSH verbunden ist, oder nicht. Ich brauchte dies z.B., um meinen Shell-Prompt in diesem Fall anders anzuzeigen.

Es ist aber sehr einfach, hier Abhilfe zu schaffen. Wir müssen sudo nur sagen, dass es die Variablen betreffend SSH im Environment halten soll.

Hierfür müssen wir nur folgende Zeile in die sudo-Config (/usr/local/etc/sudoers oder /etc/sudoers) eintragen:

Defaults env_keep += "SSH_TTY SSH_CONNECTION SSH_CLIENT"

Von nun an sind die drei Variablen auch in sudo-Sessions verfügbar.

System-Monitoring mit Glances

Glances ist ein ziemlich schickes System-Monitoring-Tool für die Shell.

Es ist in Python geschrieben, für alle gängigen Betriebssysteme verfügbar und so eine Art (h)top auf Steroiden.

Es zeigt u.A. CPU- und Speicher-Auslastung, Load, Prozesse, Auslastung der Netzwerk-Interfaces, Disk I/O, Temperaturen und die Filesystembelegung an. So hat man alles auf einen Blick, was man sich sonst mit vielen verschiedenen Tools zusammen basteln müsste.

Es bietet eine API und kann die Daten z.B. an einen StatsD weitergeben oder per Web-Interface zugänglich machen. Den Source Code, Instruktionen zum Schreiben von Plugins, sowie Installationsanleitungen gibt’s auf GitHub.

screenshot-glances

Ich habe Glances jetzt seit ein paar Tagen auf allen Servern und dem MacBook im Einsatz und mag es.

Dynamisches DNS bei INWX mit eigener Domain

Ich verwalte alle meine Domains bei InterNetWorX (welcher nebenbei erwähnt, ein ziemlich schnieker Domain-Registrar ist, den ich uneingeschränkt empfehlen kann).

Dieser bietet unter anderem eine API an, mit der ich Domaineinstellungen ändern kann. Da ich meinen Homeserver gerne von außen erreichen möchte, lag die Idee nahe, beides zu kombinieren.

Mit folgendem Script, welches ich auf Github gestellt habe, ist es mir möglich, die Nameservereinstellungen automatisch so anzupassen, so dass zum Beispiel http://homeserver.meinedomain.de immer auf meinen Server im heimischen Arbeitszimmer zeigt. So bin ich unabhängig von anderen Diensten und kann ihn eben auch mit meiner eigenen Domain nutzen. Nebenbei kostet mich das alles nichts extra, da die Funktion eben bei INWX eingebaut ist.

Das Script nsupdate.sh wird auf dem Server zu Hause stündlich per cron aufgerufen. Es liest dann die WAN-IP des Anschlusses aus und vergleicht sie mit der IP, die im Nameserver für die Subdomain hinterlegt ist. Wenn sich beide unterscheiden, trägt sie die aktuelle IP per XML-Voodo in den Nameserver ein.

#!/bin/bash

# Update a nameserver entry at inwx with the current WAN IP (DynDNS)

# Copyright 2013 Christian Busch
# http://github.com/chrisb86/

# Permission is hereby granted, free of charge, to any person obtaining
# a copy of this software and associated documentation files (the
# "Software"), to deal in the Software without restriction, including
# without limitation the rights to use, copy, modify, merge, publish,
# distribute, sublicense, and/or sell copies of the Software, and to
# permit persons to whom the Software is furnished to do so, subject to
# the following conditions:

# The above copyright notice and this permission notice shall be
# included in all copies or substantial portions of the Software.

# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
# EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
# MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
# NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
# LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
# OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
# WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.

# from which site should we get your wan ip?
IP_CHECK_SITE=http://checkip.dyndns.org

source nsupdate.config

LOG=$0.log

NSLOOKUP=$(nslookup -sil $HOSTNAME - ns.inwx.de | tail -2 | head -1 | cut -d' ' -f2)
WAN_IP=`curl -s ${IP_CHECK_SITE}| grep -Eo '<[[:digit:]]{1,3}(.[[:digit:]]{1,3}){3}>'`

API_XML="nameserver.updateRecord

                  user

                     $INWX_USER

                  pass

                     $INWX_PASS

                  id

                     $INWX_DOMAIN_ID

                  content

                     $WAN_IP

"

if [ ! "$NSLOOKUP" == "$WAN_IP" ]; then
    curl -silent -v -XPOST -H"Content-Type: application/xml" -d "$API_XML" https://api.domrobot.com/xmlrpc/
    echo "$(date) - $HOSTNAME updated. Old IP: "$NSLOOKUP "New IP: "$WAN_IP >> $LOG
else
    echo "$(date) - No update needed for $HOSTNAME. Current IP: "$NSLOOKUP >> $LOG
fi

Konfiguriert wird das ganze in der nsupdate.config. Diese liegt im selber Folder, wie das Script selbst. Hier werden die Zugangsdaten für INWX angegeben. Außerdem steht hier die Subdomain, die wir für DynDNS nutzen wollen und die ID, unter welcher die Domain bei INWX geführt wird.

# nsupdate.config

# Login credentials for the inwx admin interface
INWX_USER="USERNAME"
INWX_PASS="PASSWORD"

# The hostname that you want to update and it's ID from the inwx interface
# You get the ID when you edit the given nameserver entry and hover the save button.
HOSTNAME="subdomain.example.com"
INWX_DOMAIN_ID="123456789"

Diese Lösung läuft bei mir jetzt seit einigen Monaten sehr zuverlässig. Die Aktuelle Version findest Du immer auf meiner Github-Seite. Getestet habe ich es nur auf dem Mac und FreeBSD.

Analyse der Apache-Logs einer WordPress-Multisite mit Piwik

Ich habe gestern meine diversen Projekte, die auf WordPress laufen endlich mal in eine Multisite-Installation gepackt. Das heißt, alle Blogs sind weiterhin ganz normal erreichbar, ich muss aber nur noch eine WordPressinstallation mit Plugins etc. pflegen und habe somit weniger Administrationsaufwand.
Bisher habe ich die Nutzungszahlen der Seiten mit Piwik und dem dazugehörigen Javascript-Snippet getrackt. Das hat dort anscheinend auch ganz gut funktioniert. Es läuft aber auch nur dort, wo HTML-Code durch mich beeinflussbar ist.

Seit der Version 1.8 beherrscht Piwik jedoch auch das Parsen und Einlesen von Apache Logfiles und das wollte ich dann gestern auch mal ausprobieren.

Nach ein Bisschen rumprobieren ist mir ein Fehler bzw. unerwünschtes Verhalten aufgefallen. Vorher hatten alle Seiten einen eigenen Apache-vHost und entsprechend eigene Logs. Da jetzt alle Blogs unter einem einzigen vHost laufen, waren die Logs nicht mehr aussagefähig, da ja alle Seitenaufrufe an den selben Host gingen.

Apache beibringen, mehr bzw. anders zu loggen

Die Lösung lag also darin, Apache zu sagen, dass er anders loggen soll.

Hierfür nutze ich in der vHost-Datei unter /etc/apaches2/sites-avaliable/ die Definitionen LogFormat und CustomLog.

LogFormat "%{Host}i %h %l %u %t "%r" %>s %b "%{Referer}i" "%{User-Agent}i"" vhost_common
CustomLog "|/usr/sbin/rotatelogs /var/www/$DOMAIN/logs/access.%Y-%m-%d.log 86400" vhost_common

Mit Logformat sage ich, in welchem Format die Logs geschrieben werden sollen. Hierbei kommt dann sowas raus, wie z.B:

skrupuloes.de XXX.XXX.XXX.XXX - - [08/Jan/2013:11x:03:39 +0100] "GET /feed/podcast-mp3/ HTTP/1.1" 200 9964 "-" "PritTorrent/0.1"  
skrupuloes.de XXX.XXX.XXX.XXX - - [08/Jan/2013:11:10:27 +0100] "GET /wp-content/uploads/sites/7/2012/09/coverart-300x274.jpg HTTP/1.1" 304 - "http://skrupuloes.de/ueber-skrupuloes/" "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_8_2) AppleWebKit/537.4 (KHTML, like Gecko) Chrome/22.0.1229.94 Safari/537.4"  
skrupuloes.de XXX.XXX.XXX.XXX - - [08/Jan/2013:11:10:27 +0100] "GET /favicon.ico HTTP/1.1" 200 - "-" "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_8_2) AppleWebKit/537.4 (KHTML, like Gecko) Chrome/22.0.1229.94 Safari/537.4"  
debilux.org XXX.XXX.XXX.XXX - - [08/Jan/2013:11:10:31 +0100] "GET / HTTP/1.1" 200 14279 "-" "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_8_2) AppleWebKit/537.4 (KHTML, like Gecko) Chrome/22.0.1229.94 Safari/537.4"  
debilux.org XXX.XXX.XXX.XXX - - [08/Jan/2013:11:10:33 +0100] "GET /2012/09/28/fuer-filevault-anderes-passwort-nutzen-als-fuer-systemaccount/ HTTP/1.1" 200 13658 "http://debilux.org/" "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_8_2) AppleWebKit/537.4 (KHTML, like Gecko) Chrome/22.0.1229.94 Safari/537.4"

%{Host}i ist hierbei dafür zuständig, dass auch die Domain mitgeloggt wird, über die der Zugriff erfolgte. Jetzt sind die Logs wieder aussagefähig.

Die CustomLog-Direktive sagt Apache, wie er die Logs in welche Files schreiben soll. Ich nutze hier rotatelogs, um die Files jeden Tag zu rotieren. Meine Logs sind dann zum Beispiel unter /var/www/$DOMAIN/logs/access.2013–01–07.log zu finden.

$DOMAIN steht hier für den Ordner, den ich für jede Domain unter /var/www/ habe. Diese beinhalten einen Ordner _“logs/”, in dem die Logs der jeweiligen Domain abgelegt werden. %Y-%m-%d wird Durch Jahr-Monat-Tag ersetzt. Jede Nacht um 0:00 Uhr wird ein neues Log angefangen und die Logs vom Vortag sind “fertig”.

Apache-Logs in Piwik einlesen

Der Parser für die Piwik-Logfiles liegt im Piwik-Verzeichnis unter /misc/log-analytics/import_logs.py. Man kann ihm viele Argumente mit auf den Weg geben und praktischerweise gleich mehrere Logfiles auf einmal zum Fraß vorwerfen. Da ich keine Lust hatte, großartige Configs zu schreiben, haben ich mir ein kleines Shellscript gebastelt.

#!/bin/bash  

PATH_TO_PIWIK='/var/www/meine_domain/www/piwik' # e.g. /var/www/piwik  
PIWIK_URL='https://meine_domain/piwik' # e.g. http://mysite.tld/piwik  
WWWFOLDER='/var/www/'

# search logs  
LOGDATE=`(date --date='1 days ago' '+%Y-%m-%d')`  
LOGFILES=`(find $WWWFOLDER -iname access.$LOGDATE.log | grep "/logs/" | tr 'n' ' ')`

# import found logs to piwik  
python $PATH_TO_PIWIK/misc/log-analytics/import_logs.py --url=$PIWIK_URL --recorders=4 --add-sites-new-hosts --show-progress $LOGFILES

# run Piwik Auto-Archiving  
php5 $PATH_TO_PIWIK/misc/cron/archive.php --url=$PIWIK_URL

In den ersten drei Variablen sage ich dem Script, wo es Piwik findet, wie Piwik per URL zu erreichen ist und wo die Webseiten, respektive Logs liegen. $LOGDATE stellt fest, welches Datum gestern war (da sind ja die Logs schon “fertig”) und mit Logfiles suche ich dann in /var/www/ nach den Logs von gestern und ersetze in der Ausgabe Zeilenumbrüche durch Leerzeichen, damit ich sie an den Importer als Parameter weitergeben kann.

Anschließend lasse ich den Importer laufen. Er nutzt vier Threads und legt noch unbekannte Hosts automatisch in Piwik an (spart wieder Arbeit 😉 ). Es gibt noch viele Parameter mehr, aber die könnt ihr Auch ja auch selbst anschauen.

Da es hier auch irgendwie reinpasst, lasse ich mit dem Piwik Auto-Archiving auch gleich noch die Datenbank aufräumen.

Das Script läuft bei mir jede Nacht als Cronjob, sodass ich jeden Morgen die Statistiken von gestern einsehen kann. Die sind jetzt zwar nicht mehr tagesaktuell, ich kann aber zum ersten Mal auch die Downloads unseres Podcasts, die nicht über die Seite gingen, tracken.