Adventskalender 2. Dezember 2023 - Glückskekse als systemd-Service

Smalltalk
Antworten
Benutzeravatar
paedubucher
Beiträge: 856
Registriert: 22.02.2009 16:19:02
Lizenz eigener Beiträge: GNU Free Documentation License
Wohnort: Schweiz
Kontaktdaten:

Adventskalender 2. Dezember 2023 - Glückskekse als systemd-Service

Beitrag von paedubucher » 02.12.2023 08:54:02

Glückskekse als systemd-Service

Wer schon unter grösserer Langweile gelitten hat, dürfte mit dem “Spiel” fortune(6) bekannt sein:

Code: Alles auswählen

# apt install fortunes

$ fortune
Debug is human, de-fix divine.
$ fortune
Abraham Lincoln didn't die in vain.  He died in Washington, D.C.
$ fortune
You work very hard.  Don't try to think as well.
Doch ist diese Art der Bedienung noch angemessen für das dritte Jahrtausend?
Excuse me, wir haben 2023!
Sollte man daraus nicht eine Web-Anwendung machen, die dann mit systemd gestartet wird?

Ein Go-Server

Zunächst benötigen wir einmal Go, damit wir damit eine Anwendung schreiben können:

Code: Alles auswählen

# apt install golang
Womit wir folgende randgruppengerechte Implementierung bereitstellen (fortune1.go):

Code: Alles auswählen

package main

import (
    "math/rand"
    "net/http"
)

var quotes = []string{
    "Kommet mer am beschta glei nochem Mittagessa, no sendr zom Veschpara wieder drhoim.",
    "Sieba Johr hen se mer verseggelt, aber i hans glei gmerkt.",
    "Der putzt da Arsch vor er gschissa hot – no ka-ner s' Babieer zwoamol braucha.",
    "A bissle domm isch jedr, abbr so domm wia manchr isch koinr.",
    "A guads Gwissa kommd bloß vom a schlechda Gedächdnis.",
    "A Schwob wird nedd reich durch viel vrdiena, sondern durch wenig ausgäba!",
    "Drei Bier senn au a Veschbr, ond no hasch erschd no nix gessa.",
    "Wo Geld isch, isch au dr Deifl, wo kois isch, do isch er zwoimol.",
    "Schlof naggich, no vrsoichsch koi Hemmad.",
    "Mr ko au ogschaffd veschbara, abbr ohne Veschbr ko mr nedd schaffa!",
}

func main() {
    http.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) {
        i := rand.Intn(len(quotes))
        w.Write([]byte(quotes[i] + "\n"))
    })
    http.ListenAndServe("0.0.0.0:2023", nil)
}
Das Programm wird folgendermassen kompiliert und gestartet:

Code: Alles auswählen

$ go build fortune1.go
$ ./fortune1
Die Weisheit soll per curl(1) aufgesogen werden:

Code: Alles auswählen

# apt install curl

$ curl localhost:2023
A guads Gwissa kommd bloß vom a schlechda Gedächdnis.
$ curl localhost:2023
A bissle domm isch jedr, abbr so domm wia manchr isch koinr.
$ curl localhost:2023
Mr ko au ogschaffd veschbara, abbr ohne Veschbr ko mr nedd schaffa!
Ein systemd-Service

Damit uns die Weisheit immer gleich beim Systemstart zur Verfügung steht, soll der Server von einer systemd-Service-Unit gestartet werden.

Zunächst soll das ausführbare Programm an einen Ort verschoben werden, wo es alle Benutzer des Systems verwenden können:

Code: Alles auswählen

# mv fortune1 /usr/local/bin/
Dann erstellen wir die Unit-Datei (fortune1.service):

Code: Alles auswählen

[Unit]
Name=fortune1: Weisheiten aus dem Schwarzwald
Documentation=https://wiki.debianforum.de/Adventskalender_2023
After=network.target

[Service]
ExecStart=/usr/local/bin/fortune1
Type=simple
Restart=always

[Install]
WantedBy=multi-user.target
Die einzelnen Abschnitte und Direktiven haben folgende Bedeutung:
  • [Unit]: Allgemeine Informationen über die Unit
    • Name: Ein sprechender Name für die Unit
    • Documentation: Ein Verweis auf eine Manpage oder Online-Dokumentation
    • After: Abhängigkeiten; für unser Beispiel sollte das Netzwerk verfügbar sein
  • [Service]: Spezifische Direktiven für Service-Unit (systemd.service(5))
    • ExecStart: Der Befehl, mit dem der Service gestartet wird
    • Type: Wie der Service gestartet werden soll (Rückmeldung über Starterfolg)
    • Restart: Ob und wie (oft) der Service im Fehlerfall neugestartet werden soll
  • [Install]: Direktiven für die Installation der Unit
    • WantedBy: Welche Target-Unit den Service aufstarten soll
Diese verschieben wir ins Verzeichnis mit den übrigen systemd-Unit-Dateien:

Code: Alles auswählen

# mv fortune1.service /etc/systemd/system/
Der systemd-Daemon muss neu geladen werden, damit er die Unit erkennt:

Code: Alles auswählen

# systemctl daemon-reload
Anschliessend starten wir den Service:

Code: Alles auswählen

# systemctl start fortune1.service
Wir überprüfen noch, ob das wirklich geklappt hat:

Code: Alles auswählen

$ systemctl is-active fortune1.service
active
Und ergötzen uns von Neuem der schwäbischen Weisheiten:

Code: Alles auswählen

$ curl localhost:2023
A Schwob wird nedd reich durch viel vrdiena, sondern durch wenig ausgäba!
Der Service soll beim nächsten Systemstart gleich mitgestartet werden:

Code: Alles auswählen

# systemctl enable fortune1.service
Hierdurch wird ein symbolischer Link vom Verzeichnis des multi-user.target auf unsere Unit-Datei erstellt. Sobald das System beim Aufstarten in den Mehrbenutzerbetrieb wechselt, wird auch unser Server gestartet.

Einige Verbesserungen

Die Anwendung und ihr Deployment weisen noch einige Schwächen auf:
  1. Die Anwendung läuft mit root-Rechten. Das sollte zwar bei der aktuellen Implementierung kein Problem darstellen, ist aber trotzdem unnötig.
  2. Die Weisheiten sind hartkodiert und können nicht so einfach erweitert werden.
  3. Der Systemadministrator erfährt nicht, wie oft und von wem der Service verwendet wird.
Diese Mängel sollen behoben werden: Auf Anwendungs- und Konfigurationsebene!

Verbesserter Go-Server

Der Server soll die Weisheiten aus einer Textdatei lesen, welche über ein Kommandozielenargument mitgegeben werden soll. Ausserdem soll er die Aufrufe loggen. Diese Änderungen wurden hier vorgenommen (fortune2.go):

Code: Alles auswählen

package main

import (
    "fmt"
    "math/rand"
    "net/http"
    "os"
    "strings"
)

func main() {
    if len(os.Args) < 2 {
        fmt.Fprintf(os.Stderr, "usage: %s [file]\n", os.Args[0])
        os.Exit(1)
    }
    data, err := os.ReadFile(os.Args[1])
    if err != nil {
        fmt.Fprintf(os.Stderr, "reading file %s: %s\n", os.Args[1], err)
        os.Exit(1)
    }

    var quotes []string
    lines := strings.Split(string(data), "\n")
    for _, line := range lines {
        quote := strings.TrimSpace(line)
        if len(quote) > 0 {
            quotes = append(quotes, quote)
        }
    }

    http.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) {
        fmt.Fprintf(os.Stderr, "wisdom requested from %s\n", r.RemoteAddr)
        i := rand.Intn(len(quotes))
        w.Write([]byte(quotes[i] + "\n"))
    })
    http.ListenAndServe("0.0.0.0:2023", nil)
}
Anstelle hartkodierter Zitate besteht der Code nun v.a. aus Fehlerbehandlung, dem Einlesen der Dateien und dem Herausfiltern leerer Zeilen.

Damit der neue Server getestet werden kann, muss erst einmal der alte beendet (und beim Systemstart nicht wieder aufgestartet) werden:

Code: Alles auswählen

# systemctl disable --now fortune1.service
Die Weisheiten wurden aus dem Code in die Datei schwaebische-weisheiten.txt verschoben:

Code: Alles auswählen

Kommet mer am beschta glei nochem Mittagessa, no sendr zom Veschpara wieder drhoim.
Sieba Johr hen se mer verseggelt, aber i hans glei gmerkt.
Der putzt da Arsch vor er gschissa hot – no ka-ner s' Babieer zwoamol braucha.
A bissle domm isch jedr, abbr so domm wia manchr isch koinr.
A guads Gwissa kommd bloß vom a schlechda Gedächdnis.
A Schwob wird nedd reich durch viel vrdiena, sondern durch wenig ausgäba!
Drei Bier senn au a Veschbr, ond no hasch erschd no nix gessa.
Wo Geld isch, isch au dr Deifl, wo kois isch, do isch er zwoimol.
Schlof naggich, no vrsoichsch koi Hemmad.
Mr ko au ogschaffd veschbara, abbr ohne Veschbr ko mr nedd schaffa!
Anschliessend wird der neue Service kompiliert und getestet:

Code: Alles auswählen

$ go build fortune2.go
$ ./fortune2 schwaebische-weisheiten.txt
Getestet wird erneut mit curl:

Code: Alles auswählen

$ curl localhost:2023
Wo Geld isch, isch au dr Deifl, wo kois isch, do isch er zwoimol.
Was serverseitig folgende Ausgabe erzeugt:

Code: Alles auswählen

wisdom requested from 127.0.0.1:39948
Der verbesserte Service wird sogleich allen Benutzern des Systems zugänglich gemacht:

Code: Alles auswählen

# mv fortune2 /usr/local/bin/
Verbesserte Service-Konfiguration

Zunächst erstellen wir einen neuen Benutzer mit $HOME-Verzeichnis, eigener Benutzergruppe und der Bash als Shell:

Code: Alles auswählen

# useradd -m -d /home/fortune -U -s /usr/bin/bash fortune
Die Weisheiten werden ins $HOME-Verzeichnis dieses neuen Benutzers verschoben, der sogleich zum neuen Besitzer davon gemacht wird:

Code: Alles auswählen

# mv schwaebische-weisheiten.txt /home/fortune/
# chown fortune:fortune /home/fortune/schwaebische-weisheiten.txt
Die neue Service-Unit soll auf der alten basieren:

Code: Alles auswählen

# cp /etc/systemd/system/fortune1.service /etc/systemd/system/fortune2.service
Doch sollen einige Änderungen daran vorgenommen werden:

Code: Alles auswählen

[Unit]
Name=fortune2: Weisheiten aus aller Welt
Documentation=https://wiki.debianforum.de/Adventskalender_2023
After=network.target

[Service]
ExecStart=/usr/local/bin/fortune2 schwaebische-weisheiten.txt
Type=simple
Restart=always
User=fortune
Group=fortune
WorkingDirectory=/home/fortune

[Install]
WantedBy=multi-user.target
Es wurden folgende Änderungen vorgenommen:
  • Name: Die Unit heisst nun fortune2 und nicht mehr fortune1.
  • ExecStart: Das neue Programm fortune2 wird mit einem Argument gestartet.
  • User und Group: Der Service wird mit dem neuen Benutzer gestartet.
  • WorkingDirectory: Das Arbeitsverzeichnis ist das $HOME-Verzeichnis des fortune-Benutzers, welches auch die Datei mit den Weisheiten enthält.
Der systemd-Daemon muss neu geladen werden, damit er die neue Unit erkennt:

Code: Alles auswählen

# systemctl daemon-reload
So soll der Service testhalber gestartet werden:

Code: Alles auswählen

# systemctl start fortune2.service
Läuft der Service?

Code: Alles auswählen

$ systemctl is-active fortune2.service
active
Er läuft und wird sogleich getestet. Der Admin möchte aber hierzu die Log-Ausgaben mitverfolgen:

Code: Alles auswählen

# journalctl -fu fortune2.service
Der Test kann starten:

Code: Alles auswählen

$ curl localhost:2023
Kommet mer am beschta glei nochem Mittagessa, no sendr zom Veschpara wieder drhoim.
Und der Admin sieht folgendes:

Code: Alles auswählen

Nov 30 10:23:48 bookworm fortune2[3216]: wisdom requested from 127.0.0.1:40670
So soll der verbesserte Service automatisch aufgestartet werden:

Code: Alles auswählen

# systemctl enable fortune2.service
Fazit

Es wurde zunächst eine einfache Go-Serveranwendung entwickelt, welche zufällige Zitate aus einer hartkodierten Liste via HTTP zurückliefert. Die Serveranwendung wurde als systemd-Service-Unit konfiguriert und ausgeführt.

Anschliessend wurde die Serveranwendung verbessert, sodass sie Weisheiten aus einer Datei anbieten kann und die Aufrufe als Logmeldungen ausgibt. Die Service-Konfiguration wurde dahingehend erweitert, dass der Server von einem Benutzer mit eingeschränkten Rechten ausgeführt wird. Die Logmeldungen der Anwendung können im Journal eingesehen werden.

Übungen

Wer etwas tiefer in Go, systemd oder andere Quellen der Weisheit eintauchen möchte, der kann sich an den folgenden Übungen versuchen:
  1. Der Service horcht auf die Adresse 0.0.0.0:2023, d.h. akzeptiert Aufrufe von überall her auf Port 2023. Diese beiden Angaben könnte man per Kommandozeilen-Flag (Go-Package flag) oder mithilfe von Umgebungsvariablen (FORTUNE_ADDR, FORTUNE_PORT) konfigurierbar machen. Hierzu muss sicher das Go-Programm, aber je nach Lösungsweg auch die Unit-Konfiguration angepasst werden.
  2. Da die Datei mit den Weisheiten nun einfach ersetzbar ist, könnten ein paar weitere Beispiele aus anderen Regionen für Abwechslung sorgen.
Wer Lust hat, kann gerne sein Ergebnis als fortune3.go, fortune3.service und *-weisheiten.txt hier veröffentlichen!
Habe nun, ach! Java
Python und C-Sharp,
Und leider auch Visual Basic!
Durchaus programmiert mit heissem Bemühn.
Da steh' ich nun, ich armer Tor!
Und bin so klug als wie zuvor.

Benutzeravatar
Meillo
Moderator
Beiträge: 8818
Registriert: 21.06.2005 14:55:06
Wohnort: Balmora
Kontaktdaten:

Re: Adventskalender 2. Dezember 2023 - Glückskekse als systemd-Service

Beitrag von Meillo » 02.12.2023 09:03:09

Ja, Sack Zement, dess isch ja mol a Dierle! Do schwaetzt oinr grad so wia ih. ;-)

Wo ih des mit systemd g'seah hau, do hau'e erscht gar ed nei gugga wella, aber 's hat sich dann doch g'lohnat. I moi, eigentlich hau'e bloss dia schwaebische Schprichla aguggad -- do d'rfier jedafalls mei greascht's Lob. :THX:
Use ed once in a while!

Benutzeravatar
paedubucher
Beiträge: 856
Registriert: 22.02.2009 16:19:02
Lizenz eigener Beiträge: GNU Free Documentation License
Wohnort: Schweiz
Kontaktdaten:

Re: Adventskalender 2. Dezember 2023 - Glückskekse als systemd-Service

Beitrag von paedubucher » 02.12.2023 10:47:53

Meillo hat geschrieben: ↑ zum Beitrag ↑
02.12.2023 09:03:09
Ja, Sack Zement, dess isch ja mol a Dierle! Do schwaetzt oinr grad so wia ih. ;-)

Wo ih des mit systemd g'seah hau, do hau'e erscht gar ed nei gugga wella, aber 's hat sich dann doch g'lohnat. I moi, eigentlich hau'e bloss dia schwaebische Schprichla aguggad -- do d'rfier jedafalls mei greascht's Lob. :THX:
Vielleicht hast du ja noch einige Ergänzungen zum Wisdom-File :)
Habe nun, ach! Java
Python und C-Sharp,
Und leider auch Visual Basic!
Durchaus programmiert mit heissem Bemühn.
Da steh' ich nun, ich armer Tor!
Und bin so klug als wie zuvor.

TuxPeter
Beiträge: 1966
Registriert: 19.11.2008 20:39:02
Lizenz eigener Beiträge: MIT Lizenz

Re: Adventskalender 2. Dezember 2023 - Glückskekse als systemd-Service

Beitrag von TuxPeter » 02.12.2023 12:38:54

Ein feiner Beitrag mit Mehrfach-Nutzen! Falls ich mal so etwas systemweit verankern möchte, dann weiß ich jetzt, wie ich das hinbekommen kann. Bzw. wo ich das Rezept dafür finde.
Und über die schwäbischen Sprüche habe ich wirklich gelacht - auch, weil wir einen Schwaben in der Familie haben, der sich sicherlich auch darüber amüsieren wird!

Außerdem gibt es ja noch einen Spruch, der lautet: "Wer einen Hammer hat, dem ist alles Nagel". Und da mein Hämmerchen Pascal heißt, habe ich gleich mal folgendes gemacht:

Code: Alles auswählen

Program Hello_Login;

const Sprueche : array [0 .. 3] of String = ('Es ist alles nur halb so doppelt!',
                            'Morgenstund hat Gold im Mund und Blei im Hintern.',
                            'Der frühe Vogel kann mich mal!',
                            'EDV = "Ende der Vernunft"');

begin
  randomize;
  write (Sprueche [random (4)]); // Random-Werte von 0 bis 3!
  readln;
end.
Das Ganze je nach Wunsch in mein .bash_profile oder in einen Starter gepackt, und mich erfreut jedesmal ein frisch ausgewählter Spruch. (Wobei mein Beispiel natürlich, im Gegensatz zu diesem Adventstürchen, gar nicht besonders lehrreich ist.)

MfG
TuxPeter

Benutzeravatar
spiralnebelverdreher
Beiträge: 1296
Registriert: 23.12.2005 22:29:03
Lizenz eigener Beiträge: GNU Free Documentation License
Wohnort: Frankfurt am Main

Re: Adventskalender 2. Dezember 2023 - Glückskekse als systemd-Service

Beitrag von spiralnebelverdreher » 02.12.2023 12:59:07

Super Beitrag!

Zwei Weisheiten hätte ich noch beizutragen:
* Er moint, er wär dr Käs - d'boi schtingt er blooß.
* Du glaubsch gar ned wie vial i essa ko wänn i eiglada ben.

Schönen ersten Advent!

Antworten