Die Überschrift sagt es bereits -- es geht darum, einen Ubuntu-Server für den Einsatz als Web-/Emailserver auf einem bei Hetzner angemieteten Server zu installieren.
Ich schreibe hier einfach mal in Stichworten mit, damit ich später nicht so viel Arbeit habe, wenn ich noch so einen Server installieren muss. Bei der Hardware handelt es sich um einen im August 2009 bei Hetzner angemieteten "Root Server EQ 4".
Vielleicht hilft es ja auch jemandem der hier mitliest -- aber eigentlich ist folgende Auflistung nur für mich selbst gedacht. Sie soll mir helfen, so einen Server noch einmal installiert zu bekommen.
Inhalt
http://doc.ubuntu.com/ubuntu/serverguide/C/
aptitude update
aptitude full-upgrade
aptitude install mc
konfigurieren nicht vergessen
Beispiel: screenrc
cd /etc
mv screenrc screenrc_original
wget http://halvar.at/krimskrams3/linux/screenrc
Meine screenrc
-Datei habe ich mir von Gentoo geliehen und ein wenig angepasst.
aptitude install rcconf
Damit kann man auf einfache Weise einstellen, welche Daemons beim Start des Systems gestartet werden sollen.
passwd
adduser <Benutzername>
sudoers bearbeiten:
root<-->ALL=(ALL) ALL
%sudo ALL=NOPASSWD: ALL
Neuen Benutzer zur Gruppe "sudo" hinzufügen:
usermod --groups sudo --append <neuer Benutzername>
rm
, cp
und mv
etwas entschärfen:
alias rm='rm -i'
alias cp='cp -i'
alias mv='mv -i'
Ich will, dass sich dir
genau so verhält:
alias dir='ls -al --color --group-directories-first'
"bash completion" aktivieren:
if [ -f /etc/bash_completion ]; then
. /etc/bash_completion
fi
mkdir /scripts
cd /etc/profile.d
touch scriptpath.sh
mcedit scriptpath.sh
scriptpath.sh-Datei:
#!/bin/sh
PATH=$PATH:/scripts
export PATH
cd /scripts
touch findstring
mcedit findstring
findstring-Datei:
#!/bin/sh
#
# Sucht nach Dateien in denen der Suchstring vorkommt
#
find -type f -exec grep -iq "${1}" {} \; -print
Ausführen:
chmod +x findstring
..., da später die aktuelleste Version händisch installiert wird:
aptitude install mysql-server mysql-client
Nach der Installation wird nach dem MySQL-"root"-Passwort gefragt. Das ist nicht das Passwort für den Unix-Benutzer, sondern für den MySQL-Benutzer root@localhost.
/etc/mysql/my.cnf:
[mysqld]
:
collation_server = utf8_unicode_ci
character_set_server = utf8
aptitude install phpmyadmin
Während der Installation muss das MySQL-"root"-Passwort und ein neues MySQL Anwendungspasswort für phpMyAdmin angegeben werden.
Früher hatte ich das immer direkt in der Datei /etc/network/interfaces mit eth0:1
usw. erledigt. Aber diesmal nimmt der Computer die zusätzlichen IP-Adressen immer erst nach einem Neustart des Netzwerkes (/etc/init.d/network restart
) an. Das ist natürlich nicht tragbar. Deshalb habe ich mich dafür entschieden, die zustätzlichen IP-Adressen mit ifconfig ... add ...
zu setzen. Und damit dieser Befehl auch immer zum richtigen Zeitpunkt ausgeführt wird, habe ich diesen im Ordner /etc/network/if-up.d in eine neue Datei gelegt.
Neue Datei im Ordner /etc/network/if-up.d erstellen:
cd /etc/network/if-up.d
touch additional-addresses
chmod +x additional-addresses
/etc/network/if-up.d/additional-addresses:
#! /bin/bash
PATH=/sbin:/bin
ifconfig eth0 add <neue IP-Adresse>
ifconfig eth0 add <neue IP-Adresse>
ifconfig eth0 add <neue IP-Adresse>
Installationsdatei (deb-Paket) herunterladen
Abhängigkeiten installieren:
aptitude install libnet-ssleay-perl libauthen-pam-perl \
libio-pty-perl libio-pty-perl libmd5-perl
Webmin selbst kann dann mit dpkg
installiert werden:
dpkg -i webmin_x.xxx_all.deb
Thema einstellen
Sprache einstellen
Unnötige Module löschen:
z.B: ADSL-Client, CD-Brenner, Druckerverwaltung, Exim Mailserver, Heartbeat-Monitor, LILO - Boot-Konfiguration, QMail Mailserver, Sendmail Mailserver, WU-FTP FTP-Server, NIS-Client und -Server, Systemzeit, Volume-Management (LVM)
Das Systemzeit-Modul wird auch entfert, da ich das lieber direkt von Ubuntu erledigen lassen möchte. Webmin soll hier nicht rein pfuschen. Siehe weiter unten: "Zeitsynchronisation"
http://doc.ubuntu.com/ubuntu/serverguide/C/NTP.html
aptitude install ntp
Zeitserver in deiner Nähe sind unter http://www.pool.ntp.org/ zu finden.
Die zu verwendenden Zeitserver werden in der Datei /etc/ntp.conf eingestellt:
server de.pool.ntp.org
server at.pool.ntp.org
server ch.pool.ntp.org
Das Skript /etc/network/if-up.d/ntpdate wird immer dann ausgeführt, wenn das Netzwerk aktiviert wird. Das heißt, dass schon in der Standard- Einstellung des Ubuntu-Servers bei jedem Start des Servers die Uhrzeit eingestellt wird. Das hier genannte Skript ruft den Befehl ntpdate-debian
auf. Die Einstellungen für diesen Befehl befinden sich in der Datei /etc/default/ntpdate.
Der Daemon ntpd ist dazu da, die Systemzeit im Hintergrund ständig in ganz kleinen Schritten an die reale Zeit anzupassen.
aptitude install unattended-upgrades
Nachdem der Emailserver installiert wurde, muss die Datei /etc/apt/apt.conf.d/50unattended-upgrades angepasst werden, dass Emails bei Störung an den Admin geschickt werden.
aptitude install apticron
apticron will configure a cron job to email an administrator information about any packages on the system that need updated as well as a summary of changes in each package.
http://doc.ubuntu.com/ubuntu/serverguide/C/automatic-updates.html
aptitude install nagios3
Die Einstellungen befinden sich unter /etc/nagios3
Passwortdatei für den Zugriff auf die Nagios-Seite erstellen:
htpasswd -c /etc/nagios3/htpasswd.users <Benutzername>
Evt. muss man Nagios und den Apachen neu starten:
/etc/init.d/nagios3 restart
/etc/init.d/apache2 restart
Die Nagios-Seite ist nun unter http://<Domäne>/nagios3
erreichbar
Festplattenüberwachung
aptitude install smartmontools
Die Daten können mit dem Kommandozeilenprogramm smartctl
oder über Webmin ausgelesen werden.
Beides sind Überwachungsprogramme um feststellen zu können, wieviel so an Daten über die Netzwerkkarten übertragen werden.
aptitude install jnettop iftop
Das Emailsystem ist so geplant: Postfix empfängt die Email und legt diese in die eigene Queue. Dann wird die Email an Spamassassin übergeben. Dabei werden noch keine Zuordnungen zu irgendwelchen Emailboxen gemacht. Der Header wird nicht verändert. Spamassassin scannt die Email, schreibt den Spam-Level in den Emailheader und gibt die Email wieder an Postfix zurück. Jetzt legt Postfix fest, wohin die Email ausgeliefert werden soll. Postfix übergibt die Email an Cyrus. Cyrus übernimmt die Email und ordnet sie in das zuständige Postfach ein. Ist eine Sieve-Regel definiert, dann ist diese dafür zuständig, dass die Email evt. in einen speziellen Ordner wie z.B. den Spam-Ordner des Benutzers verschoben wird.
"saslauthd" wird nicht als Daemon benötigt. Es wird direkt auf die SASL-Datenbank zugegriffen.
aptitude install postfix postfix-doc
aptitude install sasl2-bin
aptitude install cyrus-imapd-2.2
aptitude install cyrus-pop3d-2.2
aptitude install cyrus-admin-2.2
Erklärungen zu den SASL-Postfix-Einstellungen sind unter http://www.postfix.org/SASL_README.html zu finden.
imap cmd="imapd -U 30" listen="imap" prefork=0 maxchild=100
pop3 cmd="pop3d -U 30" listen="pop3" prefork=0 maxchild=50
#nntp cmd="nntpd... (wird nicht benötigt)
lmtpunix cmd="lmtpd" listen="/var/run/cyrus/socket/lmtp" prefork=0 maxchild=20
altnamespace: yes
admins: cyrus
sieveusehomedir: false
allowplaintext: yes
sasl_pwcheck_method: auxprop
lmtpsocket: /var/run/cyrus/socket/lmtp
altnamespace: yes
kümmert sich darum, dass die IMAP-Ordner nicht unterhalb des INBOX-Ordners sind. Ich empfinde das im Thunderbird als angenehmer. Aber das ist reine Geschmackssache.
Lmtp so einstellen, dass die Übermittlung der Emails an Cyrus nicht in einer CHROOT-Umgebung läuft:
lmtp unix - - n - - lmtp
Man kann zum Debuggen "-v" als Parameter an "smtpd" anhängen. Das sollte man aber wirklich nur zum Debuggen verwenden. Mit tail -f /var/log/syslog
kann man mitverfolgen, was der Server so tut, wenn man vesucht, darüber ein Email zu verschicken.
debuggen:
smtp inet n - - - - smtpd -v
normal:
smtp inet n - - - - smtpd
smtpd_sasl_auth_enable = yes
smtpd_sasl_local_domain = <SASL-Domäne>
smtpd_recipient_restrictions = permit_mynetworks, permit_sasl_authenticated, reject_unauth_destination
broken_sasl_auth_clients = yes
virtual_alias_domains = <Domänen die angenommen werden sollen>
virtual_alias_maps = hash:/etc/postfix/virtual
mailbox_transport = lmtp:unix:/var/run/cyrus/socket/lmtp
myhostname = <FullQualifiedDomainName>
mydomain = <FullQualifiedDomainName>
Mit smtpd_sasl_local_domain
wird eingestellt, welche Domäne von SASL beim Durchsuchen der SASL-Datenbank verwendet wird. Diese Domäne ist (glaube ich) der Hostname des Servers. Den findet man mit cat /etc/hostname
heraus. Ansonsten, wenn man schon einen Benutzer in die SASL-Datenbank (mit saslpasswd2 -c <Benutzername>
) geschrieben hat, dann kann man mit sasldblistusers2
herausfinden, welches die Standarddomäne für SASL ist. Gibt man diese Einstellung nicht oder falsch an, dann bekommt man so nette Debugmeldungen von Postfix wie z.B. "...no secret in database...". Nicht lachen, das hat mich mindestens vier Stunden gekostet, bis ich das heraus fand. myhostname
und mydomain
sollten auf den voll qualifizierten Domain Name des Servers eingestellt werden. Wichtig ist, dass dieser Domain Name bei einem Reverse Lookup der IP-Adresse des Servers heraus kommt.
Postfix erlauben, auf den LMTP-Socket und die SASL-DB zuzugreifen:
adduser postfix mail
adduser postfix sasl
Hardlink für Postfix (chroot) zur SASL-DB erstellen:
ln /etc/sasldb2 /var/spool/postfix/etc/sasldb2
/etc/init.d/postfix restart
/etc/init.d/cyrus2.2 restart
saslpasswd2 -c cyrus
sicheres Passwort für den Benutzer vergeben und merken
Mit sasldblistusers2
kann man prüfen ob der Benutzer erfolgreich erstellt wurde und welcher Domäne er zugewiesen wurde. Diese Domäne muss in /etc/postfix/main.cf als Einstellung smtpd_sasl_local_domain
gesetzt werden, falls das noch nicht passiert ist.
Zum Testen habe ich mir die Subdomain "test.halvar.at" erstellt und auf die primäre IP-Adresse des Servers zeigen lassen. Auch der MX-Eintrag für "test.halvar.at" zeigt auf diese Adresse.
Die neue Domäne muss in /etc/postfix/main.cf eingetragen werden:
virtual_alias_domains = test.halvar.at
Damit wird der Benutzer in der SASL-DB erstellt:
saslpasswd2 -c testertest
Das Passwort wird interaktiv abgefragt.
Weiter geht's mit dem Erstellen des Postfaches:
cyradm --user cyrus localhost
localhost> cm user.testertest
localhost> exit
setacl
können die Zugriffsrechte des Benutzers auf das Postfach eingestellt werden. Statt "all" kann man die Berechtigungen auch detaillierter angeben:l: Lookup (visible to LIST/LSUB/UNSEEN)
r: Read (SELECT, CHECK, FETCH, PARTIAL, SEARCH, COPY source)
s: Seen (STORE \SEEN)
w: Write flags other than \SEEN and \DELETED
i: Insert (APPEND, COPY destination)
p: Post (send mail to mailbox)
c: Create and Delete mailbox (CREATE new sub-mailboxes, RENAME or DELETE mailbox)
d: Delete (STORE \DELETED, EXPUNGE)
a: Administer (SETACL)
Jetzt muss man Postfix noch mitteilen, dass Emails an tester@test.halvar.at an das Postfach testertest übergeben werden sollen. Das kann man im Webmin mit "Postfix/Virtuelle Domänen" erledigen. Siehe:
Das erzeugt in der Datei /etc/postfix/virtual folgenden Eintrag:
tester@test.halvar.at testertest
Man kann das jetzt weiter spinnen, indem man z.B. auch für "root" einen Eintrag erstellt und diesen über die /etc/aliases zu diesem Postfach leitet.
/etc/postfix/virtual:
root@test.halvar.at testertest
/etc/aliases:
root: root@test.halvar.at
Spamassassin wird als Filter für Postfix aktiviert. Dazu werden die Emails, die über SMTP (inet) hereinkommen, an den Spamassassin-Client (spamc) übergeben. spamc parst die Email (die Arbeit macht spamd) und schreibt den Spam-Status in den Header. Dann sendet spamc die Email (mit sendmail) wieder an Postfix zurück. Da Postfix die lokalen Emails nicht filtert, wird das Email von Postfix direkt an Cyrus übergeben.
aptitude install spamassassin re2c libc6-dev gcc make
Spamassassin läuft zwar auch als einfaches Kommandozeilenprogramm, aber schneller geht's als Server (spamd).
ENABLED=1
CRON=1
report_safe 0
Mit diesem Befehl wird ein Benutzer erstellt, der sich nicht einloggen kann, da ein "!" als Passwort eingestellt wird.
adduser spamassassin
Spamassassin speichert Einstellungen im Home-Ordner dieses Benutzers und das Programm spamc wird in dessen Kontext ausgefürt.
smtp inet n - - - - smtpd
-o content_filter=spamassassin
spamassassin unix - n n - - pipe
user=spamassassin
null_sender=
argv=/usr/bin/spamc -e /usr/sbin/sendmail -oi -f ${sender} ${recipient}
Zuerst muss ein entsprechender IMAP-Ordner für den Benutzer erstellt werden.
cyradm --user cyrus localhost
localhost> cm user.testertest.Spam
localhost> exit
Dann muss man eine Textdatei mit der Sieve-Regel erstellen. Diese wird dann mit dem Programm sieveshell in den Sieve-Ordner des Benutzers kopiert und aktiviert.
mkdir /etc/sieve_templates
touch /etc/sieve_templates/spam
/etc/sieve_templates/spam:
require "fileinto";
if header :contains "X-Spam-Level" "******" {
fileinto "Spam";
stop;
}
Mehr Informationen über Sieve-Regeln bekommst du hier:
Mit dem Programm sieveshell kopiert man nun die Vorlage in den Sieve-Ordner des Benutzers:
sieveshell --user=testertest --authname=cyrus localhost
Man wird nach dem Passwort für den Cyrus-Benutzer gefragt.
> put /etc/sieve_templates/spam
> activate spam
> quit
Das Programm sieveshell kann auch mit einem Skript als Parameter aufgerufen werden. Siehe man sieveshell
.
Ziel ist es, zuerst einmal alle wichtigen Daten eine Woche lang auf Lager zu halten. Falls mal eine Datei gelöscht wird, kann man diese bis zu einer Woche rückwirkend wieder herstellen.
Zweites Ziel ist es, eine Notfallsicherung auf dem Hetzner-Backup-Server zu halten, damit ein totaler Systemausfall (z.B. Festplattencrash oder Controllerfehler) nicht den Totalverlust aller Daten bedeutet.
Die zu sichernden Ordner sind (diesesmal):
Gesichert wird in den /backup-Ordner. Zum Sichern wird tar eingesetzt. Es soll einstellbar sein, wie lange rückwirkend gesichert wird.
Zum Verschlüsseln der Sicherungen, bevor diese zum FTP-Server übertragen werden, dient CCrypt:
aptitude install ccrypt
Dieses kleine Programm kopiert die letzten Sicherungen zum Backup-FTP-Server von Hetzner. Das Programm ist einfach aufgebaut. Man muss nur ein paar Konstanten anpassen und die Passwörter für den FTP-Zugang und zum Verschlüsseln der Sicherungen in zwei Textdateien hinterlegen.
#!/usr/bin/env python
# -*- coding: utf-8 -*-
import os
import ftplib
from cStringIO import StringIO
import subprocess
import tempfile
FTP_HOST = "<IP-Adresse des FTP-Backup-Servers>"
FTP_USER = "<Benutzername fuer den FTP-Backup-Server>"
FTP_PASS_FILE = os.path.join(os.path.dirname(os.path.abspath(__file__)), "ftppasswd")
FTP_PASS = file(FTP_PASS_FILE, "r").readline().strip()
CCRYPT_PASS_FILE = os.path.join(os.path.dirname(os.path.abspath(__file__)), "ccryptpasswd")
PLACEHOLDER = "_placeholder"
CCRYPT = "/usr/bin/ccrypt"
MYSQL_SOURCE_DIR = "/backup/mysql"
MYSQL_SOURCE_CURRENT_FILE = "/backup/mysql/.current"
MYSQL_DEST_DIR = "mysql"
MYSQL_DEST_CURRENT_FILE = "current"
MYSQL_DEST_MAX_BACKUPS = 10
FILES_SOURCE_DIR = "/backup/files"
FILES_SOURCE_CURRENT_FILE = "/backup/files/.current"
FILES_DEST_DIR = "files"
FILES_DEST_CURRENT_FILE = "current"
FILES_DEST_MAX_BACKUPS = 2
def copy_to_ftp(
source_dir, source_current_file, dest_dir, dest_current_file, dest_max_backups
):
# Verbinden
ftp = ftplib.FTP(FTP_HOST, FTP_USER, FTP_PASS)
# Platzhalter-Datei im Root-Ordner erstellen/ueberscheiben
ftp.storbinary("STOR %s" % os.path.join("/", PLACEHOLDER), StringIO())
# Basisordner erstellen
if (
os.path.join("/", dest_dir) not in
(os.path.join("/", item) for item in ftp.nlst("/"))
):
ftp.mkd(dest_dir)
# Platzhalter-Datei im Basis-Ordner erstellen/ueberscheiben
ftp.storbinary(
"STOR %s" % os.path.join("/", dest_dir, PLACEHOLDER),
StringIO()
)
# QUELLE Current-Datei auslesen
source_current = int(file(source_current_file, "r").readline().strip())
# ZIEL Current-Datei auslesen
dest_current_file_path = os.path.join("/", dest_dir, dest_current_file)
if dest_current_file_path in ftp.nlst(os.path.join("/", dest_dir)):
current_file = StringIO()
ftp.retrbinary("RETR %s" % dest_current_file_path, current_file.write)
current_file.seek(0)
try:
dest_current = int(current_file.readline().strip()) + 1
except ValueError:
dest_current = 1
else:
dest_current = 1
if dest_current > dest_max_backups:
dest_current = 1
# Pfade herausfinden
full_source_dir = os.path.join(source_dir, str(source_current))
full_dest_dir = os.path.join("/", dest_dir, str(dest_current))
# Zielordner löschen
if full_dest_dir in ftp.nlst(os.path.join("/", dest_dir)):
for full_filename in ftp.nlst(full_dest_dir):
ftp.delete(full_filename)
ftp.rmd(full_dest_dir)
# Zielordner und Platzhalter-Datei erstellen
ftp.mkd(full_dest_dir)
ftp.storbinary(
"STOR %s" % os.path.join(full_dest_dir, PLACEHOLDER),
StringIO()
)
# Dateien des Quellordner durchlaufen
for filename in os.listdir(full_source_dir):
# Verbindung schliessen
try:
ftp.quit()
ftp.close()
except: pass
# Pfade
full_source_filename = os.path.join(full_source_dir, filename)
full_dest_filename = os.path.join(full_dest_dir, filename + ".cpt")
print "Copy to FTP:", full_source_filename
# Datei verschluesseln
tmp_file = tempfile.TemporaryFile()
args = [CCRYPT, "--encrypt", '--keyfile=%s' % CCRYPT_PASS_FILE]
proc = subprocess.Popen(
args, stdout = tmp_file, stdin = file(full_source_filename, "rb"),
stderr = subprocess.PIPE
)
(stdout_str, stderr_str) = proc.communicate()
if stderr_str:
for line in stderr_str.splitlines():
print " ", line.rstrip()
# Verbindung neu oeffnen
ftp = ftplib.FTP(FTP_HOST, FTP_USER, FTP_PASS)
# Datei in den Zielordner kopieren
tmp_file.seek(0)
ftp.storbinary("STOR %s" % full_dest_filename, tmp_file)
tmp_file.close()
# Ziel Current-Datei erstellen/ueberschreiben
current_file = StringIO()
current_file.write(str(dest_current))
current_file.seek(0)
ftp.storbinary("STOR %s" % dest_current_file_path, current_file)
# Platzhalter-Dateien entfernen
try:
ftp.delete(os.path.join(full_dest_dir, PLACEHOLDER))
except ftplib.error_perm:
pass
try:
ftp.delete(os.path.join(dest_dir, PLACEHOLDER))
except ftplib.error_perm:
pass
try:
ftp.delete(os.path.join("/", PLACEHOLDER))
except ftplib.error_perm:
pass
# Fertig
try:
ftp.quit()
ftp.close()
except: pass
def copy_mysql():
copy_to_ftp(
MYSQL_SOURCE_DIR, MYSQL_SOURCE_CURRENT_FILE, MYSQL_DEST_DIR,
MYSQL_DEST_CURRENT_FILE, MYSQL_DEST_MAX_BACKUPS
)
def copy_files():
copy_to_ftp(
FILES_SOURCE_DIR, FILES_SOURCE_CURRENT_FILE, FILES_DEST_DIR,
FILES_DEST_CURRENT_FILE, FILES_DEST_MAX_BACKUPS
)
def main():
# Letzte MYSQL-Sicherung zum FTP-Server kopieren
copy_mysql()
# Letzte FILES-Sicherung zum FTP-Server kopieren
copy_files()
if __name__ == "__main__":
main()
Hier trägt man die IP-Adresse des Backup-FTP-Servers von Hetzner ein.
Hier trägt man den Benutzernamen fuer den Backup-FTP-Server von Hetzner ein.
In dieser Datei befindet sich das Passwort für den FTP-Zugang zum Backup-Server von Hetzner. Diese Datei sollte sich im selben Ordner befinden wie das copy_to_ftp.py-Programm.
Diese Datei darf nur für "root" lesbar sein:
chown root:root ftppasswd
chmod 600 ftppasswd
In dieser Datei befindet sich das Passwort zum Verschlüsseln der Sicherungsdateien. Diese Datei sollte sich im selben Ordner befinden wie das copy_to_ftp.py-Programm.
Diese Datei darf nur für "root" lesbar sein:
chown root:root ccryptpasswd
chmod 600 ccryptpasswd
Dieses Programm liest aus, welche Datenbanken es gibt und sichert jede Datenbank in eine eigene Dump-Datei (SQL). Die Sicherungen werden mit gzip
gepackt. Nach den mit MAX_BACKUPS eingestellten Sicherungen wird die älteste Sicherung überschrieben.
#!/usr/bin/env python
# coding: utf-8
import os
import sys
import subprocess
import shutil
import stat
MAX_BACKUPS = 50
BACKUP_DIR = "/backup/mysql"
CURRENT_FILE = "/backup/mysql/.current"
MYSQL_ROOTPW_FILE = os.path.join(
os.path.dirname(os.path.abspath(__file__)), "mysqlrootpasswd"
)
MYSQL = "/usr/bin/mysql"
MYSQLDUMP = "/usr/bin/mysqldump"
GZIP = "/bin/gzip"
def main():
# MySQL-Root-Passwort auslesen
password = file(MYSQL_ROOTPW_FILE, "r").readline().strip()
# Current-Datei auslesen
if os.path.isfile(CURRENT_FILE):
try:
current = int(file(CURRENT_FILE, "r").readline().strip()) + 1
except ValueError:
current = 1
else:
current = 1
if current > MAX_BACKUPS:
current = 1
# Pfad für Sicherung
dest_dir = os.path.join(BACKUP_DIR, str(current))
if os.path.isdir(dest_dir):
shutil.rmtree(dest_dir, ignore_errors = True)
os.makedirs(dest_dir)
os.chmod(BACKUP_DIR, stat.S_IRUSR | stat.S_IWUSR | stat.S_IXUSR)
os.chmod(dest_dir, stat.S_IRUSR | stat.S_IWUSR | stat.S_IXUSR)
args = [MYSQL, "--user=root", "--password=%s" % password]
proc = subprocess.Popen(args, stdin = subprocess.PIPE, stdout = subprocess.PIPE)
proc.stdin.write("SHOW DATABASES;")
proc.stdin.close()
proc.stdout.readline()
for line in proc.stdout:
dbname = line.strip()
print "Backup:", dbname
# Dateiname zusammensetzen
sql_file_name = os.path.join(dest_dir, dbname + ".sql")
# Sichern
cmd = MYSQLDUMP + ' --user="root" --password="%s" %s > %s'
cmd = cmd % (password, dbname, sql_file_name)
os.system(cmd)
# Zippen
cmd = GZIP + " %s" % sql_file_name
os.system(cmd)
# Current-Datei neu schreiben
f = file(CURRENT_FILE, "w")
f.write(str(current))
f.close()
if __name__ == "__main__":
main()
In dieser Datei befindet sich das Passwort des MySQL-Root-Benutzers. Diese Datei sollte sich im selben Ordner befinden wie das backup_mysql.py-Programm.
Diese Datei darf nur für "root" lesbar sein::
chown root:root mysqlrootpasswd
chmod 600 mysqlrootpasswd
Dieses Programm sichert alle Ordner, die in der Konstante SOURCE_PATHS angegeben werden, in den Zielordner (BACKUP_DIR). Nach den mit MAX_BACKUPS eingestellten Sicherungen wird die älteste Sicherung überschrieben.
Zum Sichern wird tar verwendet.
#!/usr/bin/env python
# coding: utf-8
import os
import sys
import subprocess
import shutil
import stat
TAR = "/bin/tar"
MAX_BACKUPS = 7
BACKUP_DIR = "/backup/files"
CURRENT_FILE = "/backup/files/.current"
SOURCE_PATHS = [
"/etc",
"/home",
"/root",
"/scripts",
"/var/log",
"/var/spool",
]
def main():
# Current-Datei auslesen
if os.path.isfile(CURRENT_FILE):
try:
current = int(file(CURRENT_FILE, "r").readline().strip()) + 1
except ValueError:
current = 1
else:
current = 1
if current > MAX_BACKUPS:
current = 1
# Pfad für Sicherung
dest_dir = os.path.join(BACKUP_DIR, str(current))
if os.path.isdir(dest_dir):
shutil.rmtree(dest_dir, ignore_errors = True)
os.makedirs(dest_dir)
os.chmod(BACKUP_DIR, stat.S_IRUSR | stat.S_IWUSR | stat.S_IXUSR)
os.chmod(dest_dir, stat.S_IRUSR | stat.S_IWUSR | stat.S_IXUSR)
# Ordner sichern
for path in SOURCE_PATHS:
print "Backup:", path
# Dateiname zusammensetzen
file_name = os.path.join(
dest_dir, path.replace(os.sep, "_")[1:] + ".tar.gz"
)
# Sichern
args = [TAR, "--create", "--file=%s" % file_name, "--gzip", path]
proc = subprocess.Popen(
args, stdout = subprocess.PIPE, stderr = subprocess.STDOUT
)
for line in proc.stdout:
if not (
"Removing leading" in line or
"socket ignored" in line
):
print " ", line.rstrip()
# Current-Datei neu schreiben
f = file(CURRENT_FILE, "w")
f.write(str(current))
f.close()
if __name__ == "__main__":
main()
Diese Sicherung sollte, auf Grund der doch recht massiven Arbeit, die beim Sichern vom Server zu verrichten ist, nur einmal am Tag durchgeführt werden. Am Besten dann, wenn der Server sowiso kaum ausgelastet ist.
Zuerst müssen die Dateien gesichert und dann zum SQL-Server übertragen werden. Danach werden die Datenbanken gesichert und ebenfalls zum SQL-Server übertragen. Ich habe dafür ein kleines Skript geschrieben, welches von Cron täglich in der Nacht ausgeführt wird.
#!/usr/bin/env python
# coding: utf-8
import backup_files
import backup_mysql
import copy_to_ftp
def main():
# Dateien sichern und zum FTP-Server übertragen
backup_files.main()
copy_to_ftp.copy_files()
# MySQL-Datenbanken sichern und zum FTP-Server übertragen
backup_mysql.main()
copy_to_ftp.copy_mysql()
if __name__ == "__main__":
main()
SHELL=/bin/sh
PATH=/usr/local/sbin:/usr/local/bin:/sbin:/bin:/usr/sbin:/usr/bin
MAILTO=root
...
# Taegliches Backup
50 1 * * * root /scripts/backup_full_daily.py
# Vereinzelte MySQL-Backups
43 10 * * * root /scripts/backup_mysql.py
43 16 * * * root /scripts/backup_mysql.py
Info:
man 5 crontab
Dazu muss man zuerst von "root" zum "postgres"-Benutzer wechseln:
su - postgres
Als "postgres"-Benutzer kann man sich jetzt an die DB anmelden:
psql localhost
Im psql-Programm kann man nun mit folgenden Befehlen einen Backup-Benutzer erstellen und das Passwort vergeben:
CREATE USER backupuser WITH SUPERUSER;
\password backupuser
Man wird nun nach dem Passwort des backupuser-Benutzers gefragt.
Man kann an die im Backup-Programm benötigten Kommandozeilenprogramme psql und pg_dump kein Passwort direkt als Parameter übergeben (was auch gut ist). Stattdessen kann man mit Hilfe der Umgebungsvariable PGPASSFILE den Pfad zu einer Textdatei übergeben, die das Passwort enthält.
Die Textdatei /scripts/pgpassfile muss folgende Struktur aufweisen:
hostname:port:database:username:password
Das sieht bei mir z.B. so aus:
localhost:*:*:backupuser:DasPasswort
Dann sollte man sich noch darum kümmern, dass niemand außer root auf die Datei zugreifen kann:
cd /scripts
chown root:root pgpassfile
chmod 600 pgpassfile
#!/usr/bin/env python
# coding: utf-8
import os
import sys
import subprocess
import shutil
import stat
MAX_BACKUPS = 50
BACKUP_DIR = "/backup/postgresql"
CURRENT_FILE = "/backup/postgresql/.current"
BACKUP_USER = "backupuser"
PGPASSFILE = os.path.join(
os.path.dirname(os.path.abspath(__file__)), "pgpassfile"
)
PSQL = "/usr/bin/psql"
PG_DUMP = "/usr/bin/pg_dump"
GZIP = "/bin/gzip"
def main():
# Umgebungsvariable PGPASSFILE
os.environ["PGPASSFILE"] = PGPASSFILE
# Current-Datei auslesen
if os.path.isfile(CURRENT_FILE):
try:
current = int(file(CURRENT_FILE, "r").readline().strip()) + 1
except ValueError:
current = 1
else:
current = 1
if current > MAX_BACKUPS:
current = 1
# Pfad für Sicherung
dest_dir = os.path.join(BACKUP_DIR, str(current))
if os.path.isdir(dest_dir):
shutil.rmtree(dest_dir, ignore_errors = True)
os.makedirs(dest_dir)
os.chmod(BACKUP_DIR, stat.S_IRUSR | stat.S_IWUSR | stat.S_IXUSR)
os.chmod(dest_dir, stat.S_IRUSR | stat.S_IWUSR | stat.S_IXUSR)
args_db = [
PSQL, "--username=%s" % BACKUP_USER, "--dbname=postgres", "--tuples-only",
"--command=SELECT datname FROM pg_database WHERE (datname NOT LIKE 'template_');"
]
proc_db = subprocess.Popen(args_db, stdout = subprocess.PIPE)
for line in proc_db.stdout:
dbname = line.strip()
if not dbname:
continue
print "Backup:", dbname
# Dateiname zusammensetzen
sql_file_name = os.path.join(dest_dir, dbname + ".sql")
# Sichern
args_dump = [
PG_DUMP, "--encoding=utf-8", "--no-owner", "--username=%s" % BACKUP_USER,
"--format=plain", "--file=%s" % sql_file_name, dbname
]
proc_dump = subprocess.Popen(args_dump)
proc_dump.communicate()
# Zippen
cmd = GZIP + " %s" % sql_file_name
os.system(cmd)
# Current-Datei neu schreiben
f = file(CURRENT_FILE, "w")
f.write(str(current))
f.close()
if __name__ == "__main__":
main()
Beim Erstellen eines Unix-Benutzers wird für den zugehörigen Home-Ordner eine Kopie des Ordners /etc/skel gemacht. Alle Ordner und Dateien, die bei fast allen Benutzern gleich sind, kann man dort erstellen. Das erleichtert die Arbeit ein wenig.
Es gibt normale "virtuelle Domänen" und einige deren Datenübertragung mit SSL geschützt werden muss (z.B. Shops). Darum kümmere ich mich später.
Dann gibt es noch den Sonderfall "meine eigene Homepage". Die läuft mit CherryPy (http://cherrypy.org/) und soll per mod_wsgi (http://code.google.com/p/modwsgi/) in den Apachen eingebunden werden. Später kommt vielleicht noch Zope hinzu, welches per mod_proxy hinter dem Apachen werkeln soll.
Mit der Installation von phpMyAdmin wurde bereits PHP installiert. PHP ist also kein Thema mehr. Es gibt vielleicht noch ein paar kleine Anpassungen in der INI-Datei oder in den Einstellungen der virtuellen Domänen. Aber ich rechne hier nicht mit großen Schwierigkeiten.
Die Einstellungen des Apachen befinden sich im Ordner /etc/apache2.
Im Ordner /etc/apache2/conf.d/ sind Apache-Konfigurationen, die von Programmen beim Installieren abgelegt werden. phpMyAdmin oder auch nagios haben sich über diesen Ordner in den Apachen eingeklinkt. Alle Dateien, dieses Ordners werden an die Datei /etc/apache2/apache2.conf angehängt -- sind also eine Ergänzung der Apache-Konfigurationsdatei.
Die Datei /etc/apache2/apache2.conf ist die Haupt-Konfigurationsdatei des Apachen.
Im Ordner mods-available sind die Konfigurationen und Ladeanweisungen für die installierten Apache-Module abgelegt. Möchte man ein Modul aktivieren, dann verlinkt man die zum gewünschten Modul gehörenden Dateien in den mods-enabled-Ordner. Beim Verlinken sollte man absolute Pfade verwenden.
Ich finde es einfacher ln -s
zu verwenden als immer wieder nach dem dafür vorgesehenen Apache-Programm zu suchen. Ja, das gibt es. Es heißt -- warte, ich muss mal nachsehen -- a2enmod
. :-)
Dafür ist die Datei /etc/apache2/ports.conf zuständig. Hier wird eingestellt, an welchen IP-Adressen und Ports der Apache als Server arbeiten soll. Diese Datei sieht bei mir im Moment so aus:
Listen 188.40.88.80:80
NameVirtualHost 188.40.88.80:80
<IfModule mod_ssl.c>
# SSL name based virtual hosts are not yet supported, therefore no
# NameVirtualHost statement here
Listen 188.40.88.80:443
</IfModule>
Eines ist aber klar: sie wird sich im Laufe der weiteren Konfiguration auf jeden Fall noch ändern.
Man kann für jede virtuelle Domäne eine Konfigurationsdatei erstellen. Diese legt man in den Ordner sites-available. Verlinkt man die Konfigurationsdatei mit einem symbolischen Link in den Ordner sites-enabled, dann wird die Konfiguration nach einem Neustart des Apachen aktiv. Zum Verlinken kann man ln -s
, oder das Apache-Programm a2ensite
verwenden. Nimmt man ln -s
, dann kann man zusätzlich den Namen (und somit die Sortierung) der Links bestimmen.
Die Datei /etc/apache2/sites-available/default muss man kaum verändern. Man sollte aber die Emailadresse des Server-Admins korrekt einstellen.
Für die Emails habe ich mir bereits die Testdomäne test.halvar.at eingerichtet. Sie zeigt auf die IP-Adresse des neuen Servers. Diese kann jetzt auch zum Testen des Apachen verwendet werden.
Zuerst erstelle ich den Linux-Benutzer test_halvar_at und folgende zugehörigen Ordner:
Der public-Ordner wird der Root-Ordner der Website. Dann erstelle ich dort noch die Datei index.html und schreibe "Hallo Welt - test.halvar.at" rein. Das genügt um später feststellen zu können, ob der Apache die richtige Datei ausliefert.
Dann erstelle ich die Konfigurationsdatei für die Testdomäne. Ich kopiere mir dazu die default-Datei und passe sie nach meinen Vorstellungen an.
#
# test.halvar.at
#
<VirtualHost 188.40.88.80:80>
ServerAdmin gerold.penz@aon.at
ServerName test.halvar.at
ServerAlias www.test.halvar.at
DocumentRoot /home/test_halvar_at/public
<Directory /home/test_halvar_at/public>
Order allow,deny
Allow from all
Options Indexes MultiViews
IndexOptions FancyIndexing VersionSort HTMLTable NameWidth=* FoldersFirst XHTML
AddDefaultCharset utf-8
AllowOverride All
</Directory>
<Directory /home/test_halvar_at/public/cgi-bin>
AllowOverride None
Options +ExecCGI -Indexes -MultiViews
Order allow,deny
Allow from all
AddHandler cgi-script .cgi .pl .py
</Directory>
# Possible values include: debug, info, notice, warn, error, crit, alert, emerg.
LogLevel info
ErrorLog /home/test_halvar_at/log/apache_error.log
CustomLog /home/test_halvar_at/log/apache_access.log combined
</VirtualHost>
Aktiviert wird diese Konfiguration mit:
a2ensite test_halvar_at
/etc/init.d/apache2 stop
/etc/init.d/apache2 start
Ein Aufruf der Adresse http://test.halvar.at/ im Browser, zeigt dann, ob alles funktioniert hat. :-)
Zum Testen des cgi-bin-Ordners, erstelle ich dort eine Datei mit dem Namen hallowelt.py. Diese muss unbedingt als "ausführbar" gekennzeichnet werden, sonst funktioniert es nicht.
cd /home/test_halvar_at/public/cgi-bin
touch hallowelt.py
chmod +x hallowelt.py
Das Testprogramm sieht so aus::
#!/usr/bin/env python
# coding=utf-8
import time
print "Content-type: text/plain"
print
print "Hallo Welt!"
print
print "Es ist jetzt %s Uhr." % time.strftime("%H:%M:%S")
Ein Aufruf der Adresse http://test.halvar.at/cgi-bin/hallowelt.py im Browser, sollte jetzt in etwa dieses hier zu tage bringen::
Hallo Welt!
Es ist jetzt 20:53:34 Uhr.
Ja! :-) Es funktioniert.
Somit ist der Apache getestet. Das Einrichten von virtuellen Domänen funktioniert. Jetzt kann ich mit der Arbeit beginnen und die echten "virtuellen Domänen" einrichten. :-)
Der Apache gibt ziemlich viele Informationen bei jedem Response und in den eigenen Fehlermeldungen über sich preis. Das zu unterbinden, erschwert es vielleicht den Crackern, herauszufinden, welches OS und welche Apache-Version auf dem Server läuft.
Das kann man mit zwei Einstellungen in folgender Datei ändern.
ServerTokens Prod
ServerSignature Off
aptitude install proftpd
Beim Installieren wird man gefragt, ob der Server "standalone" oder "from_inetd" ausgeführt werden soll. --> "standalone"
Die Einstellungen können über Webmin vorgenommen werden. Hier würde ich einstellen, dass Benutzer nur auf ihre Home-Ordner zugreifen dürfen.
Benutzer für den FTP-Zugang müssen als Unix-Benutzer angelegt werden. Man kann die Benutzer aber einschränken, indem man ihnen /bin/false
als Shell zuweist und "!" oder "*" als Passwort in /etc/shadow
vergibt.
Als Befehl zum Hinzufügen der Benutzer empfehle ich useradd
. Denn damit wird kein Home-Ordner angelegt und als Passwort wird "!" vergeben.
Den Home-Ordner kann man in /etc/passwd
auf /nonexistent
einstellen.
Das Passwort kann dann im Webmin-Fenster "Authentisierung" zugewiesen werden. So ist der Unix-Benutzer nur auf FTP beschränkt, denn für andere Anwendungen hat er kein gültiges Passwort.
Im Webmin kann man über den Punkt "Pro-Verzeichnis-Optionen hinzufügen für..." Optionen angeben, die vom Ordner abhängig sind. Ich habe hier jeden Home-Ordner vermerkt und über das Fenster "Benutzer und Gruppen" (der Pro-Verzeichnis-Option) eingestellt, dass egal wer darauf zugreift, die hochgeladenen Dateien dem Besitzer des Home-Ordners gehören.
Dieses Programm ist dafür zuständig, dass Logdateien nicht bis ins Nirwana anwachsen.
/home/*/log/*.log {
weekly
missingok
rotate 52
compress
delaycompress
notifempty
create 640 root root
sharedscripts
postrotate
if [ -f "`. /etc/apache2/envvars ; echo ${APACHE_PID_FILE:-/var/run/apache2.pid}`" ]; then
/etc/init.d/apache2 reload > /dev/null
fi
endscript
}
http://code.google.com/p/modwsgi/
Quellcode herunterladen und in einen Ordner entpacken. Dann in diesen entpackten Ordner wechseln.
aptitude install apache2-dev
aptitude install python-dev
./configure
make
make install
Das ist die Datei zum Einbinden des Modules in den Apachen.
LoadModule wsgi_module /usr/lib/apache2/modules/mod_wsgi.so
Nach dem Erstellen der Datei kann man das neue Modul mit folgendem Befehl einbinden:
a2enmod wsgi
aptitide install python-setuptools
easy_install cherrypy
easy_install cheetah
Apache Logfile-Analyse
aptitude install webalizer
Die Konfigurationsdatei /etc/webalizer/webalizer.conf musste ich etwas anpassen, damit nicht jeder Spider/Robot mitgezählt wird. Außerdem musste noch die Ausgabe des Content-Type auf "utf-8" umgestellt werden. Mit AllSites
, AllURLs
, usw. kann man einstellen, dass nicht nur die ersten Top-Ergebnisse angezeigt werden. Ganz wichtig ist die Einstellung Incremental yes
. Ist diese Einstellung nicht gesetzt, dann funktioniert das kleine Programm nicht, mit dem ich die Statisiken generieren lasse.
Diese Einstellungen habe ich hinzugefügt:
HTMLHead <META NAME="Content-Type" CONTENT="text/html; Charset=utf-8">
Incremental yes
HideURL *.ico
HideURL *.jpeg
AllSites yes
AllURLs yes
AllReferrers yes
AllAgents yes
AllSearchStr yes
AllUsers yes
IgnoreAgent webcrawler
IgnoreAgent Jyxobot
IgnoreAgent betaBot
IgnoreAgent Yeti
IgnoreAgent search.msn.com
IgnoreAgent Baiduspider
IgnoreAgent msnbot
IgnoreAgent Java
IgnoreAgent ia_archiver
IgnoreAgent Eurobot
IgnoreAgent Gigabot
IgnoreAgent rdfbot
IgnoreAgent libwww-perl
IgnoreAgent Googlebot-Image
IgnoreAgent anonymous
IgnoreAgent eZ Publish Link Validator
IgnoreAgent DoCoMo
IgnoreAgent Python-urllib
IgnoreAgent librabot
IgnoreAgent netEstate
IgnoreAgent nutch/Nutch
IgnoreAgent BaiduImagespider
IgnoreAgent OOZBOT
IgnoreAgent librabot
IgnoreAgent yacybot
IgnoreAgent Googlebot
IgnoreAgent Wget
IgnoreAgent Xenu
IgnoreAgent curl
IgnoreAgent findlinks
IgnoreAgent page_test
IgnoreAgent iCcrawler
IgnoreAgent Yahoo! Slurp
IgnoreAgent Exabot
IgnoreAgent Exabot-Images
IgnoreAgent Twiceler
IgnoreAgent Ask Jeeves
IgnoreAgent YoudaoBot
IgnoreAgent Yandex
IgnoreAgent MJ12bot
IgnoreAgent Twiceler
IgnoreAgent KaloogaBot
IgnoreAgent DotBot
IgnoreAgent nutch-agent
IgnoreAgent VEDENSBOT
IgnoreAgent YoudaoBot
IgnoreAgent SurveyBot
IgnoreAgent atraxbot
IgnoreAgent NaverBot
IgnoreAgent VoilaBot
IgnoreAgent Plukkie
IgnoreAgent Moreoverbot
IgnoreAgent Yahoo! SearchMonkey
IgnoreAgent Websiteshadowbot
IgnoreAgent envolk
IgnoreAgent Speedy Spider
IgnoreAgent Sosospider
IgnoreAgent Sogou web spider
IgnoreAgent Sogou-Test-Spider
IgnoreAgent Spinn3r
Die Konfiguration sollte noch etwas besser durchdacht werden, aber für den Moment genügt es mir so. Die automatische Erstellung der Webalizer-Berichte habe ich früher mit Webmin eingerichtet. Dort ist das ziemlich komfortabel. Aber inzwischen habe ich mir ein kleines Programm geschrieben, welches für mich besser funktioniert. Dieses kleine Programm wird für jeden Benutzer ein oder zwei mal am Tag über die Datei /etc/crontab aufgerufen.
Hier das kleine Programm (/scripts/generate_webalizer_stats.py):
#!/usr/bin/env python
# coding=utf-8
#
# Generiert mit *webalizer* Zugriffsstatistiken
#
# Erwartet als Parameter den Benutzernamen und den Hostname
# Syntax: ``generate_webalizer_stats.py <benutzername> <web-domain>``
# z.B.: ``generate_webalizer_stats.py wallerforum_com www.wallerforum.com``
#
import os
import sys
import glob
import subprocess
WEBALIZER = "/usr/bin/webalizer"
def main():
username = sys.argv[1]
hostname = sys.argv[2]
userdir = os.path.join("/home", username)
logdir = os.path.join(userdir, "log")
destdir = os.path.join(userdir, "webalizer")
logfilenames = sorted(
glob.glob(os.path.join(logdir, "apache_access.log*")),
reverse = True
)
for logfilename in logfilenames:
args = [WEBALIZER, "-n", hostname, "-o", destdir, logfilename]
proc = subprocess.call(args)
if __name__ == "__main__":
main()
Es ist nichts Besonderes an diesem Programm. Das Programm erwartet als Parameter den Benutzernamen und den Hostnamen (Domäne) für die Website, deren Logdateien ausgewertet werden sollen. Das Skript ruft webalizer auf und übergibt die benötigten Parameter.
Die zugehörigen Einträge in der /etc/crontab sehen so aus:
# Webalizer
0 0,12 * * * halvar_at /scripts/generate_webalizer_stats.py halvar_at halvar.at
1 1,13 * * * lugt_halvar_at /scripts/generate_webalizer_stats.py lugt_halvar_at lugt.halvar.at
2 2,14 * * * horde_webmail /scripts/generate_webalizer_stats.py horde_webmail webmail.wallerforum.com
...
Genau so wie Webalizer ist Awstats ein Programm mit dem die Logdateien des Apachen analysiert werden können.
aptitude install awstats
Danach muss man für jeden Benutzer/Domäne eine Datei im /etc/awstats-Ordner anlegen. Die Dateinamen müssen so aussehen: awstats.<benutzername>.conf
.
/etc/awstats/awstats.halvar_at.conf:
Include "/etc/awstats/awstats.conf"
SiteDomain="halvar.at"
HostAliases="localhost 127.0.0.1"
DirData="/home/halvar_at/awstats"
LogFile="cat /home/halvar_at/log/apache_access.log.1 /home/halvar_at/log/apache_access.log |"
Mit Include
wird die Haupt-Konfigurationsdatei eingebunden. Mit LogFile=...
wird festgelegt, welche Konfigurationsdateien in welcher Reihenfolge geparst werden. Mit dem Pipe-Zeichen (|) kann man statt einem Dateinamen auch einen Befehl angeben, der die Daten per Pipe an Awstats schickt.
[Weiter gehts mit dem Updaten der Statistiken und dem Erstellen der HTML-Seiten]
...
Hier gehts weiter...
...
...
Notiz: Es wurde Deutsch als Sprache eingestellt. Dafür musste man im Linux die Sprache aktivieren /usr/...locale
(noch nicht erledigt)
aptitude install squirrelmail
# squirrelmail-spam-buttons ???
Jabber auf dem Server installieren. Da fast alles Fischer auf dem Server sind, könnte das so eine Art Fischer-Chat werden.
Mögliche Clients:
...
Email-Installationsroutine (Usermin)
Floodcontrol: Dateien die älter als 1 Monat sind löschen
Email-Passwort per Usermin ändern lassen:
sasldbpasswd2
anzeigenPHP konfigurieren (Limits erhöhen)
MySQL konfigurieren (Limits erhöhen)
Apache: Memory-Cache und Datei-Cache konfigurieren. Alle Bilder könnten eigentlich bis zu einer gewissen Speichergrenze im Speicher verbleiben und direkt (ohne Festplattenzugriff) ausgeliefert werden.
Alles auf UTF-8 umstellen
Adminseiten mit Links erstellen:
Webmin konfigurieren (/ip/ vom Log ausnehmen)
Awstats installieren und konfigurieren (/ip/ vom Log ausnehmen)
rkhunter und chkrootkit installieren und konfigurieren
Backup testen
Nagios konfigurieren
Spam und Ham lernen lassen
Spamassassin Whitelist lernen lassen
Nagios Server:
so einstellen, dass mit den Daten auch etwas angefangen werden kann.
Emailbenachrichtigung bei Fehler
Ich programmiere Progressive Web Applications, Mobile Apps, Desktop-Programme und noch vieles mehr. Falls es dich interessiert, findest du mehr Informationen darüber auf meiner Business-Website.