Lazarus Dienst (LazDaemon) unter Linux installieren und steuern

Benutzeravatar
Winni
Beiträge: 1577
Registriert: Mo 2. Mär 2009, 16:45
OS, Lazarus, FPC: Laz2.2.2, fpc 3.2.2
CPU-Target: 64Bit
Wohnort: Fast Dänemark

Re: Lazarus Dienst (LazDaemon) unter Linux installieren und steuern

Beitrag von Winni »

Hi!

Auch wenn Socke es schon gesagt hat:

Wichtig ist es erstmal den Service zu aktivieren mit

systemctl enable TestDaemon.services


Ohne das geht garnix. Danach klapt es auch mit start und stop des Service.

Winni

Socke
Lazarusforum e. V.
Beiträge: 3158
Registriert: Di 22. Jul 2008, 19:27
OS, Lazarus, FPC: Lazarus: SVN; FPC: svn; Win 10/Linux/Raspbian/openSUSE
CPU-Target: 32bit x86 armhf
Wohnort: Köln
Kontaktdaten:

Re: Lazarus Dienst (LazDaemon) unter Linux installieren und steuern

Beitrag von Socke »

Winni hat geschrieben:
Mi 2. Feb 2022, 09:55
Ohne das geht garnix. Danach klapt es auch mit start und stop des Service.
Ganz lustig wird es, wenn man mit Timer-Units arbeitet, weil man bestimmte Dinge zu bestimmten Uhrzeiten ausgeführt haben möchte :roll:
MfG Socke
Ein Gedicht braucht keinen Reim//Ich pack’ hier trotzdem einen rein

Nimral
Beiträge: 390
Registriert: Mi 10. Jun 2015, 11:33

Re: Lazarus Dienst (LazDaemon) unter Linux installieren und steuern

Beitrag von Nimral »

Socke hat geschrieben:
Mi 2. Feb 2022, 09:56
Winni hat geschrieben:
Mi 2. Feb 2022, 09:55
Ohne das geht garnix. Danach klapt es auch mit start und stop des Service.
Ganz lustig wird es, wenn man mit Timer-Units arbeitet, weil man bestimmte Dinge zu bestimmten Uhrzeiten ausgeführt haben möchte :roll:
Gott sei Dank ist das dann für den Dienst selber vermutlich "out of scope".

Armin.

Socke
Lazarusforum e. V.
Beiträge: 3158
Registriert: Di 22. Jul 2008, 19:27
OS, Lazarus, FPC: Lazarus: SVN; FPC: svn; Win 10/Linux/Raspbian/openSUSE
CPU-Target: 32bit x86 armhf
Wohnort: Köln
Kontaktdaten:

Re: Lazarus Dienst (LazDaemon) unter Linux installieren und steuern

Beitrag von Socke »

Nimral hat geschrieben:
Mi 2. Feb 2022, 11:26
Gott sei Dank ist das dann für den Dienst selber vermutlich "out of scope".
Für den Dienst selbst ist das nicht weiter relevant. Um die Systemd-Timer zu verstehen habe ich auch nur zwei Jahre gebraucht ;-).

Es hilft ungemein, sich die Dokumentation dazu nicht allzuweit weg zu legen und von Zeit zu Zeit mal einen Blick hinein zu werfen.
MfG Socke
Ein Gedicht braucht keinen Reim//Ich pack’ hier trotzdem einen rein

Benutzeravatar
willi4willi
Lazarusforum e. V.
Beiträge: 155
Registriert: Sa 1. Nov 2008, 18:06
OS, Lazarus, FPC: Windows, Linux (debian) / Lazarus 3.2 / FPC 3.2.2
CPU-Target: i386, win64, arm

Re: Lazarus Dienst (LazDaemon) unter Linux installieren und steuern

Beitrag von willi4willi »

Ich habe auch schon ewig herumgefummelt, ehe ich einen vernünftigen Dienst unter Linux hinbekommen habe.

Nachdem das alles nichts gebracht hatte, habe ich folgende Lösung. Ich nicht weiß nicht, ob es richtig ist, aber es funktioniert so schon viele Jahre einwandfrei.

Einfachste Variante ist ein Console-Programm, dass dann mit "kill" abgeschossen wird.
Das ist natürlich was ganz Schlimmes, so dass man das niemandem empfehlen kann. Aber da es funktioniert, mal hier mein Beispiel:

Code: Alles auswählen

program TestDaemon1;

{$mode objfpc}{$H+}

uses
  Classes, SysUtils, CustApp;

type

  TMyDaemon = class(TCustomApplication)
  protected
    procedure DoRun; override;
  public
    constructor Create(TheOwner: TComponent); override;
    destructor Destroy; override;
  end;

procedure TMyDaemon.DoRun;
begin
  Writeln(FormatDatetime('dd.mm.yyyyThh:nn:ss,zzz',now()));
  sleep(1000)
end;

constructor TMyDaemon.Create(TheOwner: TComponent);
begin
  inherited Create(TheOwner);
  Writeln('Dienst gestartet');
end;

destructor TMyDaemon.Destroy;
begin
  Writeln('Dienst beendet');
  inherited Destroy;
end;

var
  DaemonApplication: TMyDaemon;
{$R *.res}

begin
  DaemonApplication:=TMyDaemon.Create(nil);
  DaemonApplication.Run;
  DaemonApplication.Free;
end.
Das erzeugte Programm speicher ich unter /usr/local/bin/TestDaemon1 und erzeuge die Datei /etc/systemd/system/TestDaemon1.service:

Code: Alles auswählen

 [Unit]
Description=MyDaemon
After=mysqld.service

[Service]
Type=simple
WorkingDirectory=/usr/local/bin
ExecStart=/usr/local/bin/TestDaemon1
ExecStop=/usr/bin/kill -9 $(pidof -s "/usr/local/bin/TestDaemon1")

[Install]
WantedBy=multi-user.target
Mit dem Kommando

Code: Alles auswählen

systemctl enable TestDaemon1.service
aktiviere und

Code: Alles auswählen

systemctl start TestDaemon1.service
starte ich den Dienst.

Den Status kann ich mir mit

Code: Alles auswählen

systemctl status TestDaemon1.service
ansehen.

Oder, wenn wie in meinem Beispiel, der Dienst irgendwas ausgibt, dann sehe ich das live mit:

Code: Alles auswählen

journalctl -f -u TestDaemon1.service
Natürlich sollte das Programm nicht so viel ausgeben, wie mein Beispiel. Sonst laufen vielleicht noch die Log-Dateien voll. ;-)

Die oben beschriebene Methode ist deshalb so schlimm, weil der Dienst einfach mit kill getötet wird. Besser ist es, wenn man den Dienst von außen steuern kann, um es z.B. vernünftig zu beenden oder zu steuern.
Ich habe darum einen IPC-Server in das Programm integriert.
Hier mein Programmbeispiel:

Code: Alles auswählen

program TestDaemon2;

{$mode objfpc}{$H+}

uses
  {$IFDEF UNIX}{$IFDEF UseCThreads}
  cthreads,
  {$ENDIF}{$ENDIF}
  Classes, SysUtils, CustApp,
  crt, simpleipc;

type

  { TMyDaemon }

  TMyDaemon = class(TCustomApplication)
  protected
    procedure DoRun; override;
  private
    FDaemonActive: boolean;
    IPCClient: TSimpleIPCClient;
    IPCServer: TSimpleIPCServer;
    procedure ReceiveMessage(Sender: TObject);
    procedure SendMessage(Command: String);
    procedure SetDaemonActive(AValue: boolean);
    procedure StartIPCServer;
    procedure StopIPCServer;
  public
    constructor Create(TheOwner: TComponent); override;
    destructor Destroy; override;
    procedure MyService;
    property DaemonActive:boolean read FDaemonActive write SetDaemonActive;
  end;


procedure TMyDaemon.DoRun;

begin
  if FDaemonActive then MyService;                    //< hier wird wirgendwas gemacht
  if Assigned(IPCServer) then if IPCServer.Active then IPCServer.PeekMessage(0,true);
end;

procedure TMyDaemon.ReceiveMessage(Sender: TObject);
var Message :string;
begin
  Message:=IPCServer.StringMessage;
  Writeln('empfangen: ',Message);
  case Message of
   '-q' : Terminate;
   '-r' : DaemonActive:=TRUE;
   '-s' : DaemonActive:=FALSE;

   {   weitere Steuerungsparameter möglich
   ...   }

  else
    Writeln('unbekannt!')
  end;
end;

procedure TMyDaemon.StartIPCServer;
begin
  IPCServer:= TSimpleIPCServer.Create(nil);
  try
    writeln('Starte Daemon');
    IPCServer.ServerID := Title;
    IPCServer.Global := True;
    IPCServer.OnMessage := @ReceiveMessage;
    IPCServer.StartServer;
  except
    Writeln('Fehler!');
  end;
end;

procedure TMyDaemon.StopIPCServer;
begin
  if  Assigned(IPCServer) then
  begin
    writeln('beende Daemon');
    IPCServer.StopServer;
    FreeAndNil(IPCServer);
  end;
end;

procedure TMyDaemon.SendMessage(Command:String);
begin
  IPCClient:= TSimpleIPCClient.Create(nil);
  try
    try
      Writeln('Meldung zum Daemon');
      IPCClient.ServerID := Title;
      if IPCClient.ServerRunning
      then
      begin
        IPCClient.Connect;
        if IPCClient.Active
        then IPCClient.SendStringMessage(Command)
        else Writeln('Verbindung zum Daemon nicht möglich');
        writeln(' gesendet: ',Command);
        Command:='';
        Writeln('Trenne Verbindung');
        IPCClient.Disconnect;
      end
      else Writeln('keinen gestarteten Daemon gefunden!');
    except
      Writeln('ClientFehler!');
    end;
  finally
    FreeAndNil(IPCClient);
    Terminate;
  end;
end;

procedure TMyDaemon.SetDaemonActive(AValue: boolean);
begin
  if FDaemonActive=AValue then Exit;
  if AValue
  then Writeln('Mein Dienst aktiviert')
  else Writeln('Mein Dienst unterbrochen');
  FDaemonActive:=AValue;
end;

constructor TMyDaemon.Create(TheOwner: TComponent);
begin
  inherited Create(TheOwner);
  FDaemonActive:=TRUE;
  if ParamCount=1 then SendMessage(Paramstr(1)) else StartIPCServer ;
end;

destructor TMyDaemon.Destroy;
begin
  StopIPCServer;
  inherited Destroy;
end;

procedure TMyDaemon.MyService;
begin
  {Nur zur Demonstration!! Besser ist ein Thread, der im Hintergrund was macht.}
  Writeln(FormatDatetime('dd.mm.yyyyThh:nn:ss,zzz',now()));
  sleep(1000)
end;

var
  Application: TMyDaemon;
begin
  Application:=TMyDaemon.Create(nil);
  Application.Title:='MyDaemon';
  Application.Run;
  Application.Free;
end.
Das erzeugte Programm speicher ich unter /usr/local/bin/TestDaemon2 und erzeuge die Datei /etc/systemd/system/TestDaemon2.service:

Code: Alles auswählen

 [Unit]
Description=MyDaemon
After=mysqld.service

[Service]
Type=simple
WorkingDirectory=/usr/local/bin
ExecStart=/usr/local/bin/TestDaemon2
ExecStop=/usr/local/bin/TestDaemon2 -q

[Install]
WantedBy=multi-user.target
Dann wieder:

Code: Alles auswählen

systemctl enable TestDaemon2.service
systemctl start TestDaemon2.service
und

Code: Alles auswählen

systemctl status TestDaemon2.service
journalctl -f -u TestDaemon2.service
Möchte man den Dienst anhalten, dann kann man das mit

Code: Alles auswählen

/usr/local/bin/TestDaemon2 -s
Und zum Weiterlaufen lassen:

Code: Alles auswählen

/usr/local/bin/TestDaemon2 -r
Beendet wird der Dienst mit

Code: Alles auswählen

systemctl stop TestDaemon2.service
und deinstalliert mit

Code: Alles auswählen

systemctl disable TestDaemon2.service
Bleibt noch zu erwähnen, dass die eigene Funktion natürlich als Thread läuft, damit die Steuerung auch vernünftig reagiert.

Ich weiß, dass man einen Daemon normalerweise als Service-Applikation erstellt.
Aber das hat bei mir nur unter Windows geklappt. Unter Linux bin ich da nicht weitergekommen, deshalb habe ich diese Lösung gebastelt.
Und bei mir läuft diese auf etlichen Servern problemlos.

Vielleicht hilft es dir weiter?

Oder hat jemand ein Beispiel, wie man es besser macht?
 

Viele Grüße

Willi4Willi

------------

Socke
Lazarusforum e. V.
Beiträge: 3158
Registriert: Di 22. Jul 2008, 19:27
OS, Lazarus, FPC: Lazarus: SVN; FPC: svn; Win 10/Linux/Raspbian/openSUSE
CPU-Target: 32bit x86 armhf
Wohnort: Köln
Kontaktdaten:

Re: Lazarus Dienst (LazDaemon) unter Linux installieren und steuern

Beitrag von Socke »

willi4willi hat geschrieben:
Mi 2. Feb 2022, 16:01
Die oben beschriebene Methode ist deshalb so schlimm, weil der Dienst einfach mit kill getötet wird. Besser ist es, wenn man den Dienst von außen steuern kann, um es z.B. vernünftig zu beenden oder zu steuern.
Was spricht denn dagegen auf SIGTERM oder SIGINT zu reagieren? Man muss ja nicht gleich den Prozess abwürgen, nur weil man ihn beenden will.
MfG Socke
Ein Gedicht braucht keinen Reim//Ich pack’ hier trotzdem einen rein

Benutzeravatar
Winni
Beiträge: 1577
Registriert: Mo 2. Mär 2009, 16:45
OS, Lazarus, FPC: Laz2.2.2, fpc 3.2.2
CPU-Target: 64Bit
Wohnort: Fast Dänemark

Re: Lazarus Dienst (LazDaemon) unter Linux installieren und steuern

Beitrag von Winni »

Hi !

SIGHUP wird bei demons gerne genutzt, um diesen neu zu starten. z.B. mit veränderter Conf-Datei. Oder bei geänderter System-Umgebung.

Ist auch sehr hilfreich während der Entwicklung.

Winni

Nimral
Beiträge: 390
Registriert: Mi 10. Jun 2015, 11:33

Re: Lazarus Dienst (LazDaemon) unter Linux installieren und steuern

Beitrag von Nimral »

Socke hat geschrieben:
Di 1. Feb 2022, 15:51
Ein Package kann -dUseCThreads für alle Projekte setzen, in denen es eingebunden wird. Wenn der Daemon das macht, sollte dessen Paket den Parameter setzen.
Gibt es dafür eigentlich irgendeine Anleitung?

Ich habe gerade folgendes probiert:
- ein Package gebaut, und darin unter "Compiler Options" - "Custom Options" zwei eingetragen:

Code: Alles auswählen

-dGlobalFlag
-dGlobalFlag1
Dann das Package in ein neues Projekt eingebunden, und dort den Status der Flags abgerufen, und zwar einmal in einer Projekt-Unit (Unit1) und dann auch aus einer Unit die Tiel des Package ist (Unit2). Beide enthalten in etwa diesen Code:

Code: Alles auswählen

function GetFlagState: string;

begin
Result := '';
{$IFDEF GlobalFlag}
  Result := 'Unit2: GlobalFlag is set';
{$ELSE}
  Result := 'Unit2: GlobalFlag is not set';
{$ENDIF}
{$IFDEF GlobalFlag1}
  Result := Result + ' GlobalFlag1 is set';
{$ELSE}
  Result := Result + ' GlobalFlag1 is not set';
{$ENDIF}
end;

Code: Alles auswählen

program project1;

uses Unit1, Unit2;

begin
   writeln(Unit1.GetFlagState());
   writeln(Unit2.GetFlagState());
   readln;
end.

Das Resultat ist gleich null:

Code: Alles auswählen

Unit1: GlobalFlag is not set GlobalFlag1 is not set
Unit2: GlobalFlag is not set GlobalFlag1 is not set
Habe ich was falsch verstanden? Wie setzt man diese Defines in einem Package überhaupt?

Armin

wennerer
Beiträge: 507
Registriert: Di 19. Mai 2015, 20:05
OS, Lazarus, FPC: Linux Mint 20 Cinnamon,Lazarus 2.2.6 (rev lazarus_2_2_6) FPC 3.2.2 x86_64-linux-
CPU-Target: x86_64-linux-gtk2

Re: Lazarus Dienst (LazDaemon) unter Linux installieren und steuern

Beitrag von wennerer »

Hallo Armin,
hast du das schon gelesen https://wiki-lazarus-freepascal-org.tra ... _tr_pto=sc?

Obwohl ich da auch nicht durchblicke möchte ich dir kurz mitteilen was/wie's bei mir unter Linux Mint geht.

Ich habe einmal einen Flag hier gesetzt: Hinzufügen, Benutzerdefinierte Option
newflag1.png
newflag1.png (83.3 KiB) 1869 mal betrachtet
Und zwei mal hier:
newflag2.png
newflag2.png (79.96 KiB) 1869 mal betrachtet
Wenn ich bei der Abfrage bei newflag3 windows eingebe muss ich ab und zu zweimal ok drücken bis es erkannt wird.

Mein Code sieht so aus:

Code: Alles auswählen

{$IFDEF NewFlag1}
   showmessage('newflag1 gesetzt');
 {$ELSE}
   showmessage('newflag1 nicht gesetzt');
 {$ENDIF}

 {$IFDEF NewFlag2}
   showmessage('newflag2 gesetzt');
 {$ELSE}
   showmessage('newflag2 nicht gesetzt');
 {$ENDIF}

 {$IFDEF NewFlag3}
   showmessage('newflag3 gesetzt');
 {$ELSE}
   showmessage('newflag3 nicht gesetzt');
 {$ENDIF}          
Viele Grüße
Bernd
Dateianhänge
project1.zip
(105.83 KiB) 88-mal heruntergeladen

Nimral
Beiträge: 390
Registriert: Mi 10. Jun 2015, 11:33

Re: Lazarus Dienst (LazDaemon) unter Linux installieren und steuern

Beitrag von Nimral »

Jep, den kannte ich schon.

Ich glaube, Du musst den Ausgangspunkt der Diskussion sehen. Es ging um den $IFDEF UseCThreads, den Lazarus per Default in jede neue Projektdatei setzt, und die Frage, wer wann und wo UseCThreads denn setzen würde.

Meine Idee war, das (meiner Meinung nach) nutzlose Konstrukt komplett rauszuwerfen. Wir waren uns aber unsicher, ob es nicht vielleicht von einem Package, z.B. lazDaemon gesetzt werden könnte. Nach meinen Tests: nein, wohl eher nicht. Aber eventuell weist mir Deine Idee einen anderen Weg, muss das mit den Custom Options gleich mal mit einem Package probieren.

Antworten