Große Installationen zwingen den Admin zur Automation. Handarbeit wäre hier zu aufwändig, zu fehlerträchtig und zudem wenig attraktiv. Ein sehr leistungsfähiger, noch dazu kostenloser Helfer in dieser Situation ist das Open-Source-Tool Cfengine.
Eine Systemeinstellung ist auf allen Clients neu zu konfigurieren. Meier kann das selber erledigen und er kann auch Schulze helfen, der ihm gegenüber am Schreibtisch sitzt. Bei Müller geht der Admin vorbei. Das gleiche Szenario in einer großen Behörde: Hier gibt es 127 Müllers und würde der Admin jeden persönlich besuchen, wäre er wochenlang auf Achse. In einer solchen Umgebung oder in der Serverfarm eines Rechenzentrums oder beim Grid Computing gibt es kaum eine Alternative zu automatisierter Systemadministration.
Die großen Distributoren von Suse bis Red Hat bieten für solche Arbeiten Unterstützung an und statten ihre kommerziellen Business-Versionen mit den entsprechenden Werkzeugen aus. Auch andere namhafte Hersteller von Unix-Hard- und Software, beispielsweise Sun, haben spezielle Offerten für die Konfigurationsverwaltung in Linux-Umgebungen mit vielen Clients parat [1].
Doch diese Angebote sind nicht in jedem Fall optimal: Einerseits sprengen sie zuweilen den Kostenrahmen, andererseits ist die Systemwelt nicht immer hinreichend homogen und in einem dritten Szenario fehlt vielleicht ein essenzielles Feature. Spätestens dann kommt Cfengine [2] ins Spiel – leistungsstark, kostenlos, plattformübergreifend und erweiterbar ist das Open-Source-Tool eine gute Alternative.
Das Ziel, nicht der Weg
Cfengine (Configuration Engine) wird seit zehn Jahren an der Universität Oslo entwickelt und steht unter der GPL. Es existieren Unix-Versionen und solche für Windows und Mac OS X. Sie beherrschen alle wichtigen Aufgaben der Systemadministration und können zum Beispiel genauso Dateien editieren und im Netzwerk verteilen wie Prozesse starten und überwachen, Dateisysteme mounten oder Verzeichnisse aufräumen und vieles mehr. Viele gängige Distributionen (etwa Suse, Debian oder Fedora) liefern bereits fertige Pakete, entsprechend einfach ist bei ihnen die Installation.
Was bei der Beschäftigung mit Cfengine sicherlich zuerst auffällt, ist ein Denkansatz, der das Tool grundlegend von seinen Verwandten unterscheidet: Vorgegeben wird das Ziel, nicht der Weg. Der Admin übermittelt dem Rechner also kein Skript mit detaillierten Handlungsanweisungen, sondern die Beschreibung eines Soll-Zustands. Über das Wie braucht er sich zunächst keine Gedanken zu machen.
Als Nebeneffekt dieser Herangehensweise lassen sich Cfengine-Skripte bis auf Ausnahmen schadlos mehrfach ausführen (sie sind idempotent). Erreichen sie beim ersten Lauf das Ziel, verändert ein weiterer Start nichts mehr. Um Clients vor einer zu hohen Last durch zu oft ausgeführte Anweisungen zu schützen, lassen sich für jede Aktion Mindestzeitabstände angegeben, die zwischen zwei Ausführungen einzuhalten sind.
Arbeitsteilung
Cfengine besteht aus mehreren Komponenten. Die wichtigsten sind zum einen »cfagent«, der Interpreter, der von Cfengine genutzten Beschreibungssprache, zum anderen »cfservd«, ein Daemon, der Dateien netzwerkweit verteilt. Das Kopieren von Files erfordert allerdings grundsätzlich eine Authentifizierung und erfolgt auf Wunsch verschlüsselt. In diesem Fall verwendet Cfengine die Bibliothek OpenSSL – sie stellt Funktionen für RSA-Schlüssel und den verwendeten Blowfish-Algorithmus zur Verfügung.

Abbildung 1: Der Pull-Modus benötigt nur drei Komponenten. Cfexecd lässt sich bei Bedarf durch den Systemdienst Cron ersetzen.
Cfengine unterstützt zwei Operationsmodi: Im Pull-Modus startet auf dem Client periodisch »cfagent«. Er bezieht vom »cfservd«-Daemon auf dem Management-Server die benötigten Anweisungen und führt sie dann lokal aus. Im Push-Modus dagegen kontaktiert das Programm »cfrun« auf dem Management-Server nacheinander eine Liste von Hosts und löst dann auf jedem von ihnen die Abarbeitung der anstehenden Aktionen aus.
Und Action!
Die Konfigurationsdateien, die die Zielvorgaben für Cfengine festlegen, sind immer nach dem folgenden Schema aufgebaut:
<Sektionsname>:
<Klassenausdruck>::
<Anweisungen>
|
Tabelle 1: Sektionen |
|
|---|---|
|
Name |
Funktion |
|
alerts 1 |
Warnungen ausgeben |
|
control 1 |
Variablendefinition, Aktionsreihenfolge festlegen |
|
classes 1 |
Definition von Klassen |
|
disks |
Überwachen des verfügbaren Plattenplatzes |
|
directories |
Verzeichnisoperationen |
|
disable |
Umbenennen/Verschieben |
|
editfiles |
Bearbeiten von Dateien |
|
imports 1 |
Import von Anweisungen aus Dateien |
|
links |
Anlegen von Symlinks |
|
processes |
Prozessüberwachung |
|
shellcommands |
Externe Befehle aufrufen |
|
tidy |
Dateien entfernen |
|
1 Optionssektion |
|
Die Sektionen gliedern das File funktional. So gibt etwa die Aktionssektion »files« Dateirechte vor. Optionssektionen auf der anderen Seite steuern das Verhalten von Cfengine selbst. Eine allerdings unvollständige Übersicht über mögliche Sektionen gibt Tabelle 1. Innerhalb einer Sektion folgt eine Aufzählung jener Klassen, deren Angehörige die enthaltenen Anweisungen tatsächlich ausführen sollen.
Cfagent ordnet dazu jeden Client bereits beim Start in eine Reihe von Klassen ein, die sich aus Hardware-Ausstattung und Betriebssystem ergeben. Daneben gibt es vordefinierte Klassen, die zum Beispiel Zeitpunkte bestimmen, aber auch völlig frei definierbare Klassen. Als Klassifikationsmerkmale kommen unter anderem der Hostname, die Existenz von Dateien, Exit-Codes von Programmen oder auch NIS-Netgroups in Frage.Tabelle 2 liefert ein paar Beispiele für einige dieser vordefinierten Klassen.
Die Klassen machen es möglich, dass ein und dasselbe Skript auf unterschiedlichen Systemen unterschiedliche Wirkungen erzeugt. So sorgt die Anweisung
processes:
!(server1|server2)::
"/usr/sbin/inetd" signal=kill
dafür, dass Prozessen, deren Name »/usr/sbin/inetd« enthält, ein Kill-Signal erhalten. Ausnahmen bilden die beiden Computer mit den Hostnamen »server1« oder »server2«, auf denen der angegebene Klassenausdruck den Wert »false« annimmt.
|
Tabelle 2: Beispiele |
|
|---|---|
|
Klassenname |
Beschreibung |
|
ultrix/sun4/linux |
Art des Betriebssystems |
|
hostname |
Name des Hosts |
|
Monday/Tuesday/… |
aktueller Wochentag |
|
Hr00 … Hr23 |
Stunde |
|
Day1 … Day31 |
Tag des Monats |
|
January,February,… |
Monat |
|
ipv4_ ip_Adresse |
IP-Adresse des Netzwerkinterface |
|
net_iface_Interfacename |
Name des Netzwerkinterface |
Eigene Klassen lassen sich in der »classes«-Sektion anlegen. Alle beim Start automatisch definierten Klassen, also beispielsweise der Hostname, stehen in diesem Abschnitt zur Verfügung. Die Zeilen
classes::
inetdAllowed = ( server1 server2 )
processes::
!inetdAllowed::
"/usr/sbin/inetd" signal=kill
haben die gleiche Wirkung wie obiges Beispiel. Der Unterschied ist jedoch, dass hier die selbst definierte Klasse »inetdAllowed« die Bedingung bildet. Die »classes«-Sektion gruppiert auf diese Weise die Systeme.
Volle Kontrolle
Eine »control«-Sektion muss als einzige Bestandteil jedes Cfengine-Skripts sein. Sie enthält die Variable »actionsequence«, die die Reihenfolge der Aktionen bestimmt. In dieser Sektion lassen sich auch Defaultwerte überschreiben oder Variablen und Listen definieren. Mögliche Optionen listet das übersichtliche Reference Manual auf.
Die folgende »actionsequence« bewirkt, dass Cfengine zuerst die Angaben zu Dateien (»files«) und dann die Prozessinformationen (»processes«) auswertet:
control:
any::
actionsequence = ( files processes )
myVariable = ( "Foo" )
tmpDirs = ( /tmp:/var/tmp )
»myVariable« ist eine Variable, auf deren Inhalt man in anderen Sektionen mit »$(myVariable)« zugreifen kann. Listen wie »tmpDirs« spezifizieren Aktionen für verschiedene Ziele gleichzeitig, ähnlich dem »foreach«-Befehl in Programmiersprachen.
Mit »::« abgeschlossene Ausdrücke wie zum Beispiel »any::« dienen der bedingten Ausführung von Anweisungen. Klassen wie »any« sind mit booleschen Variablen vergleichbar, Operationen wie UND, ODER, NICHT sowie Klammern ermöglichen komplexere Ausdrücke. Das Beispiel aus Listing 1 demonstriert alle diese Elemente noch einmal beim Zusammenspiel.
Reinigungskolonne
Die »control«-Sektion (Zeilen 1 bis 4) legt zunächst die Reihenfolge der Aktionen fest. Die hier angegebene »actionsequence« sorgt für die Ausführung der »tidy«- vor der »files«-Sektion. Die Variable »tidyAge« (Zeile 3) enthält den Wert »2«, den später »tidy« als Zeitangabe verwendet. Die Sektion »tidy« (Zeilen 10 bis 18) löscht Dateien, deren Atime-Wert größer ist als die Altersvorgabe in Tagen und die zusätzlich dem angegebenen Namensmuster entsprechen.
Der Klassenausdruck »Monday« (Zeile 11) sorgt dafür, dass das Säubern von »/tmp« nur montags passiert. Analog dazu wird »/var/tmp« an Dienstagen geleert. »recurse=inf« (Zeile 13) in der Aktionsbeschreibung zu »/tmp« hat zur Folge, dass Cfengine unendlich viele Verzeichnisebenen rekursiv durchsucht. Ohne diese Angabe berücksichtigt Cfagent nur die Dateien, die direkt im angegebenen Verzeichnis liegen.
Die in »files« (Zeilen 5 bis 9) enthaltene Beschreibung bewirkt die Überprüfung der Rechte des Heimatverzeichnisses von Root und von »/lost+found«. Entsprechen diese nicht dem vorgegebenen Modus, wird eine Warnung ausgegeben. Enthielte das Skript zusätzlich »action=fixall«, würde Cfengine die Rechte entsprechend korrigieren. In »files« angegebene Aktionen sind durch den Klassenausdruck »any« geschützt. »any« wertet als einzige Klasse immer nach »true« aus und wird impliziert, falls der Klassenausdruck fehlt.
»cfagent -f Dateiname« führt die in der Datei enthaltenen Anweisungen aus. Klassen wie etwa »Monday« und »Tuesday« lassen sich beim Start mit der Option »–define« definieren. Das Kommando »cfagent -f Dateiname -v –define Monday« sorgt dafür, dass das Skript die Anweisungen immer wie an einem Montag interpretiert.
Zusätzliche Ausgaben produziert der Verbose-Modus, aktiviert durch die Option »-v«. Cfagent zeigt dann die beim Start definierten Klassen. Fehlt die Angabe einer Datei durch »-f«, dann verarbeitet Cfagent die Anweisungen aus der Datei »/etc/cfengine/cfagent.conf«. Dieser Pfad ist fest einkompiliert und kann bei anderen Distributionen als Debian Sarge anders lauten.
Die Möglichkeiten, die Cfengine schon lokal bietet, erweitern die eingebauten Netzwerkfunktionen nochmals. Gerade bei der Verwaltung vieler Systeme bereitet es oft Schwierigkeiten, Konfigurationsdateien auf einem einheitlichen Stand zu halten. Cfengine bietet sich auch für diese Aufgabe als bequem handhabbares Werkzeug an.
|
Listing 1: Beispiel eines |
|---|
01 control: 02 actionsequence = ( tidy files ) 03 tidyAge = ( 2 ) 04 rootOnly = ( /root:/lost+found ) 05 files: 06 any:: 07 $(rootOnly) mode=o-rwx 08 owner=root 09 group=root 10 tidy: 11 Monday:: 12 /tmp age=$(tidyAge) 13 recurse=inf 14 pattern=* 15 16 Tuesday:: 17 /var/tmp age=$(tidyAge) 18 pattern=* |
|
Listing 2: |
|---|
01 control: 02 # Unsere Domain 03 domain = ( sample-domain ) 04 05 # Maximale Anzahl der gleichzeitigen Verbindungen. 06 # Jeder Client darf sich per Default nur 1x connecten 07 MaxConnections = ( 20 ) 08 09 # Nur Clients des entsprechenden C-Klasse-Netz 10 # zulassen 11 AllowConnectionsFrom = ( 10.10.10.0/24 ) 12 13 # Welche Benutzer dürfen sich verbinden ? 14 # Der vom Client gesendete Benutzername bestimmt zusammen mit der 15 # Client-IP den Dateinamen des zu überprüfenden Public-Key 16 AllowUsers = ( root ) 17 18 # Verbindungen werden geloggt 19 LogAllConnections = ( true ) 20 21 grant: 22 # Exportieren von Verzeichnissen für den Client 23 /exports/cfengine 10.10.10.0/24 |
Vernetzt
Im folgenden Beispiel stellt »server« mit der IP 10.10.10.100 die Konfigurationsdateien unter »/exports/cfengine« bereit. »client« mit der IP 10.10.10.222 bezieht von dort alle nötigen Daten. Bevor die jedoch Rechnergrenzen überschreiten können, sind die RSA-Public-Schlüssel auszutauschen. Im Gegensatz zu SSH erfolgt bei Cfengin die Autorisierung in zwei Richtungen, sodass immer beide den Public-Key ihres Gegenübers besitzen müssen.
Bekannte Schlüssel speichert Cfengine in dem Verzeichnis »/var/lib/cfengine2/ppkeys/«. Der Dateiname der Public-Keys folgt dabei dem Schema »Benutzer-IP-Adresse.pub«. So wird beispielsweise der Public-Key von »server« auf »client« unter dem Dateinamen »root-10.10.10.100.pub« abgelegt. In »localhost.pub« liegt der lokale Public-Key. Sein geheimes Gegenstück ist in »localhost.priv« gespeichert. Die beiden Schlüsselteile werden meist bei der Installation der Distributionspakete generiert. Ist dies nicht geschehen, erzeugt das Kommando »cfkey« beide Dateien.
Schlüsselfrage
Sind die Schlüssel ausgetauscht kann der Admin den Dienst »cfservd« konfigurieren. Listing 2 zeigt die dafür nötige Konfigurationsdatei »/etc/cfengine/cfservd.conf«. Unter Debian Sarge muss zusätzlich in der Datei »/etc/default/cfengine2« die Variable »RUN_CFSERVD« den Wert »1« erhalten. Ein anschließendes »/etc/init.d/cfengine2 start« als Root startet den Daemon. Zu Testzwecken kann er nach »cfservd -f /etc/cfengine/cfservd.conf –no-fork« auch im Vordergrund laufen.

Abbildung 2: Das Beispielskript im Einsatz: Der ausgeführte Putzjob in »/tmp« und die Rechte-Kontrolle werden penibel dokumentiert (Ausgabe gekürzt).
Ist alles konfiguriert, dann lassen sich wie zum Beispiel in Listing 3 Konfigurationsdateien im Netz verteilen. Dazu genügt die Angabe der Option »server« in »copy«-Aktionen (Zeile 12). Die Pfadangabe »/exports/etc/nscd.conf« bezeichnet nun den Pfad der Datei auf »server«. Die in Zeile 13 aktivierte Blowfish-Verschlüsselung während des Transfers macht das Mitlesen des Netzwerkverkehrs sinnlos. So kann man auch sensible Daten sicher kopieren. Die Angabe von »verify=true« (Zeile 14) veranlasst Cfagent dazu, nach dem Kopieren die Integrität erneut zu prüfen.
|
Listing 3: Zentrale |
|---|
01 control: 02 actionsequence = ( copy shellcommands processes ) 03 04 classes: 05 # Aktionen machen nur Sinn, wenn nscd installiert ist 06 nscdInstalled = ( FileExists(/etc/init.d/nscd) ) 07 08 copy: 09 nscdInstalled:: 10 /exports/etc/nscd.conf dest=/etc/nscd.conf 11 define=nscdConfChanged 12 server=10.10.10.100 13 encrypt=true 14 verify=true 15 shellcommands: 16 nscdConfChanged:: 17 "/bin/bash /etc/init.d/nscd restart" 18 19 processes: 20 nscdInstalled:: 21 "/usr/sbin/nscd" matches=>0 22 restart "/etc/init.d/nscd start" 23 24 alerts: 25 !nscdInstalled:: 26 "NameServiceCacheDaemon not installed on host $(fqhost)" 27 ifelapsed=240 |
Zusatzaufgaben
Neben dem zentralen Verteilen von Konfigurationsdateien illustriert Listing 3 weiter gehende Funktionen wie Prozessüberwachung, das Überprüfen installierter Anwendungen und das bedingte Neustarten von Diensten nach Konfigurationsänderungen. Zunächst prüft die »classes«-Sektion anhand der Existenz der Datei »/etc/init.d/nscd«, ob das Paket überhaupt installiert (Zeile 5) und die Klasse »nscdInstalled« definiert ist. Sie dient als Bedingung in der »copy-Sektion« (Zeilen 8 bis 14). Durch die Angabe von »define=nscdConfChanged« in Zeile 11 wird die Datei kopiert. So kann man in der Sektion »shellcommands« (Zeilen 15 bis 18) einen Neustart bei geänderter Konfiguration auslösen.
Zeitschalter
In der zum Schluss abgearbeiteten Sektion »processes« (Zeilen 19 bis 23) wird die Prozesstabelle nach dem String »/usr/sbin/nscd« durchsucht. Sollte sich nichts Passendes finden, wird das angegebene Restart-Kommando ausgeführt. Zu guter Letzt wertet Cfagent die Optionssektion »alerts« aus (Zeilen 24 bis 27). Ist die Klasse »nscdInstalled« nicht definiert, erfolgt eine Warnung. Die Angabe der Option »ifelapsed=600« sorgt für maximal eine Warnung innerhalb von 600 Minuten, egal wie oft das Skript zwischenzeitlich startet.
Mit »ifelapsed« lassen sich (fast) alle Aktionen vor zu häufiger Ausführung schützen, der Wert von einer Minute gilt als Default, wenn eine andere Angabe fehlt. Im praktischen Einsatz ist dieser Mindestzeitabstand von einer Minute sinnvoll. Während der Admin Skripte testet, deaktiviert die Cfagent-Option »–no-lock« die Lock-Datenbank.
Wer die Konfigurationsdateien von Cfengine in »/etc/cfengine« synchronisieren muss, kann dies ebenfalls mit Bordmitteln erledigen. Cfagent verarbeitet vor den eigentlichen Anweisungen die Informationen aus »/etc/cfengine/update.conf« (sofern vorhanden). Enthält »update.conf« die Anweisung, den Inhalt von »/etc/cfengine« von einem anderen Rechner zu kopieren, ist die zentrale Verteilung schon organisiert.
Die Integration neuer Clients benötigt jetzt nur noch den oben beschrieben Schlüsselaustausch sowie eine Kopie der »update.conf« auf dem neuen Client. Der erste Lauf von Cfagent kopiert damit den aktuellen Inhalt von »/etc/cfengine« auf die lokale Maschine. Anschließend wertet das Tool die frisch kopierte Datei »/etc/cfengine/cfagent.conf« aus, was automatisch alle vom Admin geplanten Arbeiten auslöst.
Das vereinfacht sehr das Einrichten eines neuen Dienstes, beispielsweise des SNMP-Daemon »net-snmpd«. Neben der passenden Konfigurationsdatei für den Daemon bedarf es lediglich eines Cfagent-Skripts, das über die »shellcommands« das Paket installiert und konfiguriert. Beide Dateien hinterlegt der Admin auf einem zentralen Rechner. Nun erledigt ein Lauf von Cfagent, gestartet durch Cron oder Cfexecd, auf jeder Client-Maschine die Arbeit.
Ein Abstand von 20 Minuten zwischen den Läufen ist selbst auf betagten 350-MHz-Computern vertretbar. Bei Bedarf lassen sich die Zugriffe innerhalb eines Zwei-Minuten-Intervalls durch Verwendung von »sleep $(($RANDOM % 120))« streuen. Weil es auf diese Weise keine unnötigen Aktionen gibt, bleibt in späteren Zyklen oft nichts mehr zu tun, da schon der erste Lauf alle gewünschten Einstellungen – etwa die von Zugriffsrechten – vorgenommen hat. Im Gegensatz hierzu braucht ein »chmod 755 /usr/bin/*« per Shellskript bei jedem Lauf die gleiche Zeit.
|
Praktische |
|---|
|
Das im Folgenden vorgeschlagene Vorgehen hat der Autor in den letzten 14 Monaten zur Verwaltung des Linux-Pools der Zentraleinrichtung Rechenzentrum (ZRZ) der TU-Berlin angewandt. Es hat sich als sehr leistungsfähig erwiesen. Dabei hat er zehn Serversysteme, rund 60 Workstations sowie drei X-Terminal-Server neu installiert und verwaltet. Nach der rund 300 MByte umfassenden Grundinstallation mit FAI (Fully Automatic Installation) hat Cfengine die Systeme komplett konfiguriert. Dies umfasste die Installation von Diensten und deren Konfiguration sowie die Überwachung von Dateirechten, freiem Plattenplatz und laufenden Services. Auf Workstations und X-Terminal-Servern waren zusätzlich KDE und Benutzerprogramme zu installieren und den Bedingungen anzupassen. Bessere ÜbersichtDie Arbeiten haben auch gezeigt: Die Datei »cfagent.conf« wird in umfangreicheren Projekten schnell unübersichtlich. Gleichzeitiges Bearbeiten durch mehrere Administratoren ist nicht möglich und ihre Verwaltung in einem Versionskontrollsystem könnte komfortabler sein. Ein Ausweg bestünde darin, die Anweisungen in einzelne Dateien auszulagern, die »cfagent.conf« importiert. Diese Verwendung von »import« hatten die Entwickler aber offenbar nicht im Sinn, denn wenn importierte Skripte Aktionssequenzen enthalten, werden diese einfach aneinander gehängt. Aus »actionsequence = ( copy shellcommands )« in einer Datei und der umgekehrten Anweisung »actionsequence = ( shellcommands copy )« in einer zweiten entsteht durch den Import beider Dateien die Sequenz: actionsequence = ( copy shellcommands shellcommands copy ) Das Resultat widerspricht der Intention des zweiten Skripts und führt, wenn die Reihenfolge wichtig ist, zu Fehlern. Als Workaround empfiehlt es sich, an Stelle von »import« die »shellcommands«-Sektion zu nutzen. Die Anweisung »shellcommands: “/usr/sbin/cfagent -f Skriptname“« bewirkt das Gewünschte. Aktionssequenzen sind nun pro Datei lokal. Leider geht auf diese Weise die Möglichkeit verloren, globale Variablen zu verwenden. Diese kann der Admin jedoch mit ein wenig Shell-Scripting leicht nachrüsten, sodass er auf nichts verzichten muss. Das Verfahren hat sich beim Autor in der Praxis bewährt. Ein Beispielskript »import-hack.sh« liegt auf [3] zum Download bereit. |
Fazit
Im praktischen Einsatz hat sich Cfengine hervorragend bewährt. Es ist sehr viel flexibler und bietet weitaus mehr Möglichkeiten als Konzepte, die komplette Systemimages oder vorbereitete Konfigurationsdateien netzwerkweit verteilen. Auch gegenüber Shellskripten kann es durch größere Flexibilität, bessere Performance und umfangreiche Fehlerbehandlung punkten.
Im Vergleich mit kommerziellen Werkzeugen für das Systemmanagement führt es die nicht zu unterbietenden Anschaffungskosten und die gute Erweiterbarkeit ins Feld. Seine Praxistauglichkeit hat Cfengine in einigen sehr großen Installationen schon bewiesen. (jcb)
|
Infos |
|---|
|
[1] Jens-Christoph Brendel,Martin Kuppinger, “Alles unter Kontrolle”: Linux-Magazin 06/05, S. 32 [2] Cfengine: [http://www.cfengine.org] [3] Weitere Beispielskripte zu diesem Beitrag: [ftp://www.linux-magazin.de/pub/listings/magazin/2005/12/cfengine] |







