[gelöst] Bashskript als Daemon und systemd Unit

Vom einfachen Programm zum fertigen Debian-Paket, Fragen rund um Programmiersprachen, Scripting und Lizenzierung.
Antworten
Benutzeravatar
smutbert
Beiträge: 6385
Registriert: 24.07.2011 13:27:39
Wohnort: Graz

[gelöst] Bashskript als Daemon und systemd Unit

Beitrag von smutbert » 07.05.2018 23:01:54

Hi,

über das kleine Projekt mit dem ich mit einem Relais 12 V und damit den Router von einem PC aus schalte, gibt es ja schon einen Thread: viewtopic.php?f=13&t=167661&start=15

Nun habe ich eine kleine systemd Unit geschrieben, die
  • mit dem Relais den Router einschaltet
  • eine typische Zeit wartet, die der Router zum Starten und Herstellen der Verbindung benötigt
  • in einer Schleife immer wieder überprüft ob eine Internetverbindung besteht
  • beesteht keine Internetverbindung (mehr) oder wird eine maximale Verbindungszeit überschritten (nach 24h verhalten sich Router/Internetverbindung meistens merkwürdig) wird der Router wieder ausgeschaltet und das Skript beendet
Das beste ist dass das ganze sogar einigermaßen funktioniert.
Es sieht so aus, einmal die systemd Unit

Code: Alles auswählen

[Unit]
Description=switch router and monitor internet connection
After=netfilter-persistent.service
Requires=netfilter-persistent.service
BindsTo=netfilter-persistent.service

[Service]
Type=simple
ExecStart=/usr/local/bin/switchrouter
Restart=always
RestartSec=30s

[Install]
WantedBy=network.target
und das Skript »/usr/local/bin/switchrouter«
NoPaste-Eintrag40335


Interessieren würden mich nun erstens allgemeine Verbesserungsvorschläge, die das ganze robuster und übersichtlicher machen (bin ich mir sicher, dass ich aus Unwissen alle möglichen Konventionen gebrochen habe).
Allgemeine Hinweise würden mir außerdem besonders weiterhelfen, weil ich noch eine ganz ähnliche Unit schreiben will, die sich um einen dynDNS-ähnlichen Dienst kümmern soll

Zweitens geht es um einige weitere Funktionen, die ganz nett wären, an denen ich aber gescheitert bin:
Allen voran wollte ich die Unit mittels

Code: Alles auswählen

Type=notify
und dem systemd-notify in Zeile 59 so gestalten, dass die Unit nur als aktiv gilt, wenn eine Internetverbindung besteht. Ich glaube das hätte fast geklappt, aber ... und während ich das schreibe fällt es mir wie Schuppen von den Augen → es hat nur deswegen nicht funktioniert, weil es bei systemd zu einem timeout kommt bis mein Skript das Ok-Signal gibt, was wegen der Abhängigkeiten der Unit(s) in weiterer Folge den Systemstart komplett vereitelt (Restart=always ist vielleicht auch keine so wahnsinnig gute Idee, aber ich weiß nicht wie ich das besser machen soll)...

Die Meldungen, die ich mit meiner Logfunktion mit systemd-cat ausgeben erscheinen zwar im journal, aber nicht, wenn ich gezielt die Meldungen der unit anzeigen lasse

Code: Alles auswählen

# journalctl -p7 -u switchrouter.service
-- Logs begin at Sun 2018-05-06 23:46:59 CEST, end at Mon 2018-05-07 22:38:21 CEST. --
May 06 23:47:00 host systemd[1]: Started connect router to power and reset if necessary.
wohingegen

Code: Alles auswählen

# journalctl -p7 | grep switchrouter
May 06 23:47:00 host switchrouter[481]: Switched on router.
May 06 23:49:24 host switchrouter[580]: Internet connection ok (0 fails).
May 07 00:06:04 host switchrouter[594]: Internet connection ok (0 fails).
May 07 00:22:45 host switchrouter[601]: Internet connection ok (0 fails).
...
(ich glaube ich habe systemd-cat nicht komplett verstanden)

Beim Handling von SIGTERM und SIGKILL bin ich mir auch alles andere als sicher (Zeile 39-46)....

Etwas was ich noch machen will und wobei ich hoffentlich ohne Hilfe auskomme, ist eine udev-Regel, die für etwas weniger Verwechslungsgefahr bei der Gerätedatei für das Relais sorgt und die Unit von einem unprivilegierten Benutzer ausführen zu lassen.

lg smutbert
Zuletzt geändert von smutbert am 20.05.2018 22:50:25, insgesamt 1-mal geändert.

tobo
Beiträge: 563
Registriert: 10.12.2008 10:51:41

Re: Bashskript als Daemon und systemd Unit

Beitrag von tobo » 08.05.2018 00:49:45

smutbert hat geschrieben: ↑ zum Beitrag ↑
07.05.2018 23:01:54
Beim Handling von SIGTERM und SIGKILL bin ich mir auch alles andere als sicher (Zeile 39-46)....
SIGKILL kannst du gar nicht abfangen, da der Kernel das Programm beendet. Das Programm selbst bekommt davon überhaupt nichts mit.

tobo
Beiträge: 563
Registriert: 10.12.2008 10:51:41

Re: Bashskript als Daemon und systemd Unit

Beitrag von tobo » 08.05.2018 15:30:03

Was mir so auffällt am Skript:

Code: Alles auswählen

while [ ${SECONDS} -lt ${online_time_max} ]
do
$SECONDS ist wohl die aktuelle Online-Zeit. Aber wie/wo wird SECONDS befüllt?

Code: Alles auswählen

for i in "${check_online_urls[@]}" ; do
                ${mywget} "${i}" && break
                (( online_try_count++ ))
                sleep 10s
        done || (( online_err_count++ ))
online_err_count wird ja nur erhöht, wenn die Schleife scheitert. Und der Exit-Code der Schleife richtet sich nach dem letzten Befehl (sleep 10s), der in der Schleife ausgeführt wird.

Code: Alles auswählen

function switch ()
{
        case "${1}" in
                on)
                        echo -n -e '\xA0\x01\x01\xA2' > /dev/ttyUSB0
                        ;;

[...]

# Router einschalten
if switch on ; then
        log "Switched on router." 6
else
        log "Switching on router failed, trying to switch off nevertheless." 3
        switch off
        exit 75
fi
Kann da jemals der else-Zweig erreicht werden - sprich, inwiefern kann der echo-Befehl scheitern?

EDIT: Ok, die Anmerkung mit SECONDS ist natürlich hinfällig.

Benutzeravatar
smutbert
Beiträge: 6385
Registriert: 24.07.2011 13:27:39
Wohnort: Graz

Re: Bashskript als Daemon und systemd Unit

Beitrag von smutbert » 08.05.2018 23:01:15

Auf $SECONDS bin ich durch Zufall gestoßen – ist wohl ein Feature der bash, dass die Variable vom Start der bash an hochgezählt wird.

Die beiden Zähler in der Schleife sind ein Ergebnis meiner Planlosigkeit. Zuerst hatte ich nur den Zähler in der inneren (for-)Schleife... damit es nicht so sensibel auf kurze Verbindungsprobleme reagiert, habe ich in der äußeren while-Schleife den zweiten Zähler dazugebastelt.
Dass das noch später hinzugefügte sleep den Exitstatus der Schleife ändert habe ich überhaupt nicht bedacht :facepalm:
Ich werde glaube ich versuchen komplett ohne Zähler auszukommen.
tobo hat geschrieben: ↑ zum Beitrag ↑
08.05.2018 15:30:03
Kann da jemals der else-Zweig erreicht werden - sprich, inwiefern kann der echo-Befehl scheitern?
Darüber habe ich nicht besonders intensiv nachgedacht. Vielleicht unerwartete Probleme mit USB oder ein weiteres serielles Interface, das unbedacht angesteckt ist und die Nummerierung der Gerätedateien verschiebt?
(Es ist mir vor allem wichtig Fehlschläge beim Trennen des Routers von der Stromversorgung zu erkennen, aber da habe ich dank des Threads schon eine Idee: Ohne Strom muss der Linkstatus des Netzwerkinterfaces, an dem der Router hängt inaktiv sein...)

Danke, dass du dir die Zeit genommen hast mein Skript anzusehen!

Benutzeravatar
smutbert
Beiträge: 6385
Registriert: 24.07.2011 13:27:39
Wohnort: Graz

Re: Bashskript als Daemon und systemd Unit

Beitrag von smutbert » 18.05.2018 10:36:24

Soweit ich das beurteilen kann, ist es nach einigen Änderungen zuverlässig gelaufen.

Mein nächster Schritt war die ganze Unit von einem eigenen unprivilegierten User statt root ausführen zu lassen, der lediglich in der Gruppe dialout sein muss, um auf das serielle Interface zugreifen zu können. Das hat auch noch funktioniert.

Allerdings scheitere ich nun an unerwarteter Stelle, nachdem ich eine Abfrage nach dem Verbindungsstatus einbauen wollte. Der entscheidende Abschnitt sieht so aus:

Code: Alles auswählen

RELAIS_NETIF="enp0s25"
RELAIS_SETTLE_SLEEP=30

   sleep ${RELAIS_SETTLE_SLEEP}
   if ethtool ${RELAIS_NETIF} | grep "Link detected: yes" > /dev/null ; then
      log "Switched on router, network link ok." 6
   else
      log "Error" 5
   fi
Ich verstehe nicht wieso das scheitert und ich immer beim else lande, obwohl der Link zweifelsohne aktiv ist. Ich hab mir auch einmal die komplette Ausgabe von ethtool ins journal ausgeben lassen (grep und Ausgabeumleitung weggelassen) und dann läuft es (allerdings natürlich ohne den gewünschten Zweck zu erfüllen)

Code: Alles auswählen

May 18 10:15:41 meinrouter switchrouter[943]: Cannot get wake-on-lan settings: Operation not permitted
May 18 10:15:41 meinrouter switchrouter[943]: Settings for enp0s25:
May 18 10:15:41 meinrouter switchrouter[943]:         Supported ports: [ TP ]
May 18 10:15:41 meinrouter switchrouter[943]:         Supported link modes:   10baseT/Half 10baseT/Full
May 18 10:15:41 meinrouter switchrouter[943]:                                 100baseT/Half 100baseT/Full
May 18 10:15:41 meinrouter switchrouter[943]:                                 1000baseT/Full
May 18 10:15:41 meinrouter switchrouter[943]:         Supported pause frame use: No
May 18 10:15:41 meinrouter switchrouter[943]:         Supports auto-negotiation: Yes
May 18 10:15:41 meinrouter switchrouter[943]:         Advertised link modes:  10baseT/Half 10baseT/Full
May 18 10:15:41 meinrouter switchrouter[943]:                                 100baseT/Half 100baseT/Full
May 18 10:15:41 meinrouter switchrouter[943]:                                 1000baseT/Full
May 18 10:15:41 meinrouter switchrouter[943]:         Advertised pause frame use: No
May 18 10:15:41 meinrouter switchrouter[943]:         Advertised auto-negotiation: Yes
May 18 10:15:41 meinrouter switchrouter[943]:         Speed: 100Mb/s
May 18 10:15:41 meinrouter switchrouter[943]:         Duplex: Full
May 18 10:15:41 meinrouter switchrouter[943]:         Port: Twisted Pair
May 18 10:15:41 meinrouter switchrouter[943]:         PHYAD: 1
May 18 10:15:41 meinrouter switchrouter[943]:         Transceiver: internal
May 18 10:15:41 meinrouter switchrouter[943]:         Auto-negotiation: on
May 18 10:15:41 meinrouter switchrouter[943]:         MDI-X: off (auto)
May 18 10:15:41 meinrouter switchrouter[943]:         Current message level: 0x00000007 (7)
May 18 10:15:41 meinrouter switchrouter[943]:                                drv probe link
May 18 10:15:41 meinrouter switchrouter[943]:         Link detected: yes
May 18 10:15:41 meinrouter switchrouter[950]: Switched on router, network link ok.
Ich habe auch die Abfrage einzeln unter einem anderen unprivilegierten User getestet und finde keinen Fehler. Unter genau dem dafür gedachten User zu testen ist lästig, weil ich den als Systemuser ohne Loginshell angelegt habe, aber daran kann es doch nicht liegen?

tobo
Beiträge: 563
Registriert: 10.12.2008 10:51:41

Re: Bashskript als Daemon und systemd Unit

Beitrag von tobo » 18.05.2018 19:51:18

Gute Frage!? ethtool gibt aus, wird also auch in /sbin mit deinem non-login-shell-PATH gefunden. Das grep sollte so auch funktionieren. Genauso wie, dass ethtool auf 1> ausgibt (anders als z.B. ffmpeg). Aliase hast du wohl keine angelegt, bleibt eigentlich nur noch die pipe!? Hast du stdout generell umgelegt? Hast du da noch andere "| grep"-Konstrukte, die funktionieren? Vielleicht einfach mal irgendwas ausgeben und per gepipten grep wieder suchen!? Ansonsten, die teste einfach_mal_durch-Methode und die Ausgabe auffangen und OUTPUT.txt schauen:

Code: Alles auswählen

set -x
set -v
shopt -s
which ifconfig
which grep

RELAIS_NETIF="enp0s25"
RELAIS_SETTLE_SLEEP=30

   sleep ${RELAIS_SETTLE_SLEEP:?}
   if ethtool ${RELAIS_NETIF:?} |& tee OUTPUT.txt |& grep -qi "Link.*detected:.*yes"; then
      log "Switched on router, network link ok." 6
   else
      log "Error" 5
   fi

Benutzeravatar
smutbert
Beiträge: 6385
Registriert: 24.07.2011 13:27:39
Wohnort: Graz

Re: Bashskript als Daemon und systemd Unit

Beitrag von smutbert » 19.05.2018 13:32:47

In anderen Aufrufen funktioniert grep, ich habe keine aliase definiert und keine generellen Ausgabeumleitungen.

Es hat nun allerdings nach ewig vielen Versuchen genau einmal geklappt ohne dass ich etwas geändert habe. Es klingt unwahrscheinlich, aber offensichtlich hat sich bei meinen zahllosen Versuchen, die ich zu Testzwecken mit

Code: Alles auswählen

…
   if ethtool ${RELAIS_NETIF} | grep "Link detected: yes" > /dev/null ; then
      log "$(ethtool ${RELAIS_NETIF} | grep Link)" 1
      log "Switched on router, network link ok." 6
...
gemacht habe, der Link Staus zwischen der if-Abfrage und der Abfrage, die gleich darauf ins Log geschrieben wurde, geändert. Möglicherweise habe ich es mit meinem Skript und dem RELAIS_SETTLE_SLEEP beeindruckend zuverlässig geschafft die Zehntelsekunde zu treffen in der Link, während des Bootens des Routers gerade inaktiv ist.

(Davor habe ich nicht einmal mitbekommen, dass er beim Booten überhaupt noch einmal kurz „verschwindet“.)

Nun frage ich stattdessen einen anderen Wert ab, der (hoffentlich) keinen derartigen Zuckungen unterliegt:

Code: Alles auswählen

RELAIS_NETIF="/sys/class/net/enp0s25/carrier"

if [ $(cat ${RELAIS_NETIF}) -eq 1 ] ; then
                                        log "Switched on router, network link ok." 6
...
ich werde das jetzt eine Zeit lang ganz genau beobachten

edit:
Das dürfte es tatsächlich gewesen sein - jetzt läuft es zuverlässig.

Danke!

Antworten