Sign in Join
Wiki copytemporary

Daemons and Services: what changed

Revision 1 to 2. Removed lines have a minus sign; added lines have a plus sign.

OldNewLine
11## What are daemons, services and agents?
22
33Unix *daemons* and Windows *services* are system-wide programs running without user interaction; macOS *agents* are per user programs (cf system-wide daemons) that may or may not run without user interaction. Although the nomenclature differs, their function is similar: for example **www** or **ftp** servers are called daemons under Linux and services under Windows. Because they do not interact with the user directly, they close their stdin, stdout, stderr descriptors at start.
44
55With Free Pascal and Lazarus it is possible to write these daemons/services in a platform-independent manner using the Lazarus lazdaemon package. To avoid name conflicts with the Delphi components these classes are called 'daemons'.
66
77> **Note:** This page focuses on Windows and Linux. To read about Lazarus and macOS "Agents", please refer to [[macOS daemons and agents]].
88
99## Creating a Service/Daemon (using Lazarus and visual Components)
1010
1111> **Note:** The complete sample source code for this wiki page is available via the link at the end of this wiki page.
1212
1313### Prerequisite: Install the LazDaemon package
1414
1515Before you can start creating a daemon using the Lazarus IDE, you must install the LazDaemon package either via "Components" - "Configure installed packages" or by installing the lpk file directly from .../lazarus/components/daemon/lazdaemon.lpk. The package installs some new components and menu items in the IDE.
1616
17-<figure>
18-<img src="2022-02-21_14_34_28-LINDER-LAZW7_-_VMware_Workstation.png" title="2022-02-21_14_34_28-LINDER-LAZW7_-_VMware_Workstation.png" />
19-<figcaption>2022-02-21_14_34_28-LINDER-LAZW7_-_VMware_Workstation.png</figcaption>
20-</figure>
17+![](/wiki-images/3/31/2022-02-21_14_34_28-LINDER-LAZW7_-_VMware_Workstation.png)
2118
2219Due to the way packages work in Lazarus, you need to "Save and rebuild the IDE" to install the LazDaemon package.
2320
2421### Creating and using scaffolded Daemon Project Files
2522
2623After having installed the LazDaemon package, from the "Project" - "New Project" Menu, pick "Daemon (service) application)".
2724
28-<figure>
29-<img src="2022-02-17_16_31_12-Create_a_new_project.png" title="2022-02-17_16_31_12-Create_a_new_project.png" />
30-<figcaption>2022-02-17_16_31_12-Create_a_new_project.png</figcaption>
31-</figure>
25+![](/wiki-images/e/ec/2022-02-17_16_31_12-Create_a_new_project.png)
3226
3327This will automatically create two units, one for a TDaemon descendant ("DaemonUnit"), and one for a TDaemonMapper descendant ("DaemonMapperUnit"), and a main project file ("TestDaemon.lpr"). This file needs a little change to work:
3428
3529``` pascal
3630Program TestDaemon;
3731
3832Uses
3933
4034// This is the scaffolded code, needs modification like shown below,
4135// since UseCThreads is usually not definied, and is not needed here
4236// {$IFDEF UNIX}{$IFDEF UseCThreads}
4337// CThreads,
4438// {$ENDIF}{$ENDIF}
4539
4640{$IFDEF UNIX}
4741 CThreads,
4842{$ENDIF}
4943 DaemonApp, lazdaemonapp, daemonmapperunit, DaemonUnit
5044 { add your units here };
5145
5246{$R *.res}
5347
5448begin
5549 Application.Initialize;
5650 Application.Run;
5751end.
5852```
5953
6054> **Warning:** The scaffolded .lpr file contains an additional {\$IFDEF UseCThreads} ... {\$ENDIF} clause (commented out in the sample above), which has been observed not to work, unless additional measures are taken to define UseCThreads. You can remove the conditional entirely or comment it out, like shown in the above .lpr sample, or add a -dUseCThreads option when you compile your daemon on Linux. Windows services are not affected.
6155
6256Despite that daemons don't have a GUI, both the DaemonApp and the DaemonMapper unit have support for the Lazarus Form Editor. This way you get a familiar GUI to populate the various properties of the daemon and the mapper class. Of course, if you prefer, you can initialize all properties in code as well like shown below.
6357
6458Both the scaffolded DaemonApp and the DaemonMapper unit contain a superfluous "var" definition (var DaemonMapper1: TDaemonMapper1; var Daemon1: TDaemon1;). Those are actually never used by the running daemon, they remain uninitialized, trying to access them results in an access violation fatal error. If you have a closer look at how daemon applications work internally you'll see that the code relies solely on the types (TDaemonMapper1, TDaemon), the vars can be ignored or removed entirely.
6559
6660The code provided by the TDaemon class does mainly respond to the various service control messages sent by the OS. The TDaemonMapper contains data structures describing the service. Both classes need to be registered with the daemon application framework, they will be used internally to configure the daemon when it starts. In this code sample registration is done in the "Initialization" section of each unit.
6761
6862The main "Application" object for a service-style application is introduced by putting a reference to DaemonApp into the uses section of each unit. In a daemon application, "Application" provides the complete service framework including advanced features like install and uninstall support, and logging. For the "worker" part of the daemon you are supposed to create a thread of its own, see sample code below, don't try to use the "Execute" method provided by the TDaemon class.
6963
7064### Populating the DaemonMapper Class
7165
7266For the moment let's just fill some basic properties to get things going. Note that the DaemonClassName must exactly match with what you have defined in the DaemonClass unit.
7367
74-<figure>
75-<img src="2022-01-31_23_32_52-Settings.png" title="2022-01-31_23_32_52-Settings.png" />
76-<figcaption>2022-01-31_23_32_52-Settings.png</figcaption>
77-</figure>
68+![](/wiki-images/1/19/2022-01-31_23_32_52-Settings.png)
7869
7970> **Note:** For cross-platform programmers: In Unix everything is case-sensitive, using lowercase is common use, so you may cause some confusion if you define a mixed-case service name like shown in the above sample.
8071
8172Maybe have a short look into the WinBindings property, which lets you configure various service properties for use by the Windows Service Manager, like service start type and service user account. While not used for this basic demo daemon, setting those options properly is important for a real life daemon application. WinBindings doesn't have any effect on Linux, see the section about install and uninstall support for Linux on how to achieve the same for Linux by setting options in the systemd "unit" (.service) file.
8273
8374### Writing the Daemon Methods
8475
8576TDaemons support the following methods:
8677
8778| Method/Event | Description |
8879|----|----|
8980| `OnStart` | Called when daemon should start. This method must return immediately with *OK:=True*. |
9081| `OnStop` | Called when daemon should stop. This method must return immediately with *OK:=True*. |
9182| `OnShutDown` | Called when daemon should be killed, because the system shuts down. This method must stop the daemon immediately and return with *OK:=True*. This is not triggered under Linux. Linux simply kills the daemon process. |
9283| `OnPause` | Called when daemon should pause. This method must return immediately with *OK:=True*. Under Linux this is not triggered, instead the kernel stops the whole daemon on STOP. |
9384| `OnContinue` | Called when daemon should continue after a pause. This method must return immediately with *OK:=True*. Under Linux this is not triggered. |
9485| `OnExecute` | Do not use this property to implement the working code, use a worker task like shown in the code sample. |
9586| `BeforeInstall` | Called before service installation. |
9687| `AfterInstall` | Called after (successful) service installation. |
9788| `BeforeUninstall` | Called before service de-installation. |
9889| `AfterUninstall` | Called after successful service de-installation. |
9990| | |
10091
10192The following code snippets show all the major event handlers necessary to successfully implement a simple daemon. They have been tested on Windows 7/10/11 and several Linux distros (Debian 10.8 and Ubuntu 20.04.3).
10293
10394Some handy helper functions have been offloaded into separate units, so they do not obfuscate the daemon core code:
10495
10596- FileLoggerUnit: a thread safe log to file helper. It is required because the control signal receiver included in the TDaemon application and the service's working code need to be implemented as separate threads, otherwise the daemon will not respond to control signals while the service worker code runs. So in any daemon there are at least two tasks involved, which could create a clash when accessing the log file simultaneously. See the Lazarus wiki [[Multithreaded Application Tutorial]] for details about how to serialize accesses to a single resource by using a TRTLCriticalSection to make the code thread-safe. The FileLoggerUnit code does always write the log into the program directory, please make sure the daemon has write access permissions. The code writes one file per day, the filename contains the creation date. If you need a more sophisticated logger, consider using the [[LazLogger]] unit.
10697- [DaemonWorkerThread](https://gitlab.com/freepascal.org/lazarus/lazarus/-/tree/main/examples/TDaemon): a TThread descendant class to hold the daemon "worker" code. See the Lazarus wiki [[Multithreaded Application Tutorial]] for details about the TThread.Execute method containing the inevitable "while not terminated" loop. There is nothing special with a worker thread initiated by a TDaemon, it's just a simple thread like any other.
10798- [DaemonSystemdInstallerUnit](https://gitlab.com/freepascal.org/lazarus/lazarus/-/tree/main/examples/TDaemon): a unit which tries to provide support for the -install and -uninstall command line parameters for Linux. Adds systemd/systemctl support by writing an appropriate control file to /lib/systemd/system.
10899
109100### Daemon Start/Stop Signal Handler
110101
111102``` Pascal
112103// ---------------------
113104// Start and Stop signal
114105// ---------------------
115106
116107procedure TDaemon1.DataModuleStart(Sender: TCustomDaemon; var OK: Boolean);
117108begin
118109 LogToFile(Format('Daemon received start signal, PID:%d', [GetProcessID]));
119110 // Create a suspended worker thread - see DaemonWorkerThread unit
120111 FDaemonWorkerThread := TDaemonWorkerThread.Create;
121112 // Parametrize it
122113 FDaemonWorkerThread.FreeOnTerminate := False;
123114 // Start the worker
124115 FDaemonWorkerThread.Start;
125116 OK := True;
126117end;
127118
128119procedure TDaemon1.DataModuleStop(Sender: TCustomDaemon; var OK: Boolean);
129120begin
130121 LogToFile('Daemon received stop signal');
131122 // stop and terminate the worker
132123 if assigned(FDaemonWorkerThread) then
133124 begin
134125 FDaemonWorkerThread.Terminate;
135126 // Wait for the thread to terminate.
136127 FDaemonWorkerThread.WaitFor;
137128 FreeAndNil(FDaemonWorkerThread);
138129 end;
139130 LogToFile('Daemon stopped');
140131 OK := True;
141132end;
142133```
143134
144135These handlers deal with the start and stop signals issued by the operating system. If the daemon starts, DataModuleStart fires and we spawn the "worker" thread found in the DaemonWorkerThread unit, see the TDeamonWorkerThread.Execute method to find the actual working code of our daemon. To understand the thread code, refer to the [[Multithreaded Application Tutorial]] wiki page and to the documentation of TThread.
145136
146137> **Warning:** To implement the daemon worker code, one might consider to implement a handler for the "execute" method found in TDaemon. This does not work well, especially not on WIndows. Execute does not run as a separate thread, while it executes the daemon stops handling control messages. The daemon will start and run fine, but if you try to stop/pause/resume it using the Windows Service Manager or the sc command, the daemon will not respond and appear to hang.
147138
148139### Daemon Shutdown Handler
149140
150141This Windows-only handler is called when the daemon is to be stopped because of an operating system shutdown. If we do not handle the event, Windows will simply forcefully kill the daemon process, potentially corrupting open files. For this simple demo service one can simply call the DataModuleStop event handler, executing the same code like a normal service stop. On Linux this handler is not supported, Linux will instead directly call the stop handler.
151142
152143``` Pascal
153144procedure TDaemon1.DataModuleShutDown(Sender: TCustomDaemon);
154145// Supported on Windows systems only
155146begin
156147 self.Stop; // On shutdown, we trigger the stop handler. This will do nicely for this demo
157148 LogToFile('Daemon received shutdown signal');
158149end;
159150```
160151
161152### Install and Uninstall for Linux (systemd)
162153
163154The built-in install and uninstall support (-install and -uninstall command line parameters) is implemented in LazDaemon applications for Windows only. Here is a simple code sample showing how to make custom handlers for install and uninstall events to add a similiar functionality for Linux.
164155
165156``` Pascal
166157// --------------------------------
167158// Installation and De-Installation
168159// --------------------------------
169160
170161procedure TDaemon1.DataModuleAfterInstall(Sender: TCustomDaemon);
171162
172163 var
173164 isInstalled: boolean = True;
174165 FilePath: string;
175166
176167begin
177168 LogToFile('Daemon installing');
178169 {$IFDEF UNIX}
179170 FilePath := GetSystemdControlFilePath(Self.Definition.Name);
180171 isInstalled := CreateSystemdControlFile(self, FilePath);
181172 if not isInstalled then
182173 LogToFile('Error creating systemd control file: ' + FilePath);
183174 {$ENDIF}
184175 if isInstalled then
185176 LogToFile('Daemon installed');
186177end;
187178
188179procedure TDaemon1.DataModuleBeforeUnInstall(Sender: TCustomDaemon);
189180 var
190181 isUnInstalled: boolean = True;
191182 FilePath: string;
192183
193184 begin
194185 LogToFile('Daemon uninstalling');
195186 {$IFDEF UNIX}
196187 FilePath := GetSystemdControlFilePath(Self.Definition.Name);
197188 isUnInstalled := RemoveSystemdControlFile(FilePath);
198189 if not isUninstalled then
199190 LogToFile('Error removing systemd control file: ' + FilePath);
200191 {$ENDIF}
201192 if isUninstalled then
202193 LogToFile('Daemon uninstalled');
203194 end;
204195```
205196
206197These Unix/Linux-only handlers utilize the routines in DaemonSystemdInstallerUnit to write a systemd control file (.service file) for our service to /lib/systemd/system, so the daemon can be controlled using the systemctl command. On uninstall, the .service file ist deleted.
207198
208199If you don't specify a user account in the .service file, systemd will run the daemon as root. Running a daemon as "root" (Linux) or "LocalSystem" (Windows) is, for security reasons, considered bad practice, and can, amongst many other obstacles, hamper debugging the running daemon. Consider to add a line
209200
210201``` Pascal
211202 [...]
212203 f.WriteString('Service', 'Type', 'simple');
213204 f.WriteString('Service', 'User', '...'); // insert a service account here, otherwise the daemon will run as root
214205 f.WriteString('Service', 'ExecStart', Application.ExeName + ' -r');
215206 [...]
216207```
217208
218209in the DaemonSystemdInstallerUnit.CreateSystemdControlFile code.
219210
220211> **Note:** At runtime you'll find all the TDaemonMapper and especially the TDaemonMapper.WinBindings settings in the "self.Definition" and "self.Definition.WinBindings" structures. If you prefer, you may consider using some of them to populate settings in the .service file.
221212
222213For a comprehensive guide on all settings available in a systemd .service file (sometimes called a systemd "unit file") please see the documentation of your Linux distribution.
223214
224215### Daemon Worker Thread
225216
226217At the core of every daemon there is a piece of code which does the actual work, I named it the "worker" thread. It needs to be a separate thread, because in the background the daemon is supposed to continue to listen for control messages and process them. Creation and destruction of the thread can be seen in the above samples for TDaemon.DataModuleStart and TDaemon.DataModuleStop handlers.
227218
228219``` Pascal
229220procedure TDaemonWorkerThread.Execute;
230221
231222var
232223 i: integer;
233224
234225begin
235226 LogToFile('Daemon worker thread executing');
236227 while not Terminated do
237228 begin
238229 // placeholder, put your actual service code here
239230 // ...
240231 LogToFile('Daemon worker thread running');
241232 // Thread- and CPUload friendly 5s delay loop
242233 for i := 1 to 50 do
243234 begin
244235 if Terminated then break;
245236 Sleep(100);
246237 end;
247238 // ...
248239 // ----------------------------------------
249240 end;
250241 LogToFile('Daemon worker thread terminated');
251242end;
252243```
253244
254245This "worker" doesn't do much, it loops until terminated and writes a message into the log every 5 seconds.
255246
256247For your own daemon functionality, replace the code between "..." and "..." with your own.
257248
258249The inner loop of a TThread.Execute method must be layed out in a way so it checks the "Terminated" flag frequently. The flag is set by the TThread.Terminate method which is called to gracefully end a thread execution, like it can be seen in the above TDaemon.DataModuleStop sample. There is more info about TThreads available in the [[Multithreaded Application Tutorial]] wiki.
259250
260251To keep the sample demo code simple, it does not handle runtime errors in any way. Errors in the logging unit will be dropped silently, all other runtime errors in the daemon will crash the daemon. Please refer to the wiki page about [[Exceptions]] to read more about catching and handling exceptions in general, and see the documentation on TThread.HandleException to read more about how to properly catch and forward an exception within the execute method of a worker thread.
261252
262253### Initializing TDaemon Properties in Code
263254
264255If you prefer not to use the Lazarus IDE and its Forms Editor to populate your TDaemon and TDeamonMapper properties, you can do it all in code as well. If you want to go this route, note that your project must not be based on the LCL/Forms like other applications, but on DaemonApp. Failing this will make the code fail with numerous stream loading errors.
265256
266257Working with TDaemon (The event driven approach to program a daemon) has already been described in the above sections, the only real difference is that you will assign the event handlers in the application code like so:
267258
268259``` Pascal
269260TDaemon1 = class(TDaemon)
270261 {...}
271262 procedure DataModuleStart(Sender: TCustomDaemon; var OK: Boolean); // sample event handler event
272263 {...}
273264public
274265 constructor Create(AOwner: TComponent); override; // overwrite the constructor to initialize the event handlers
275266end;
276267
277268{...}
278269
279270constructor TDaemon1.Create(AOwner: TComponent);
280271begin
281272 inherited Create(AOwner);
282273 {...}
283274 onStart := @DataModuleStart; // assign the sample event handler code
284275 {...}
285276end;
286277```
287278
288279Follow the same strategy with TDaemonMapper to populate the Mapper.
289280
290281> **Note:** If you prefer an OOP based approach using inheritance to customize the daemon- and the mapper class you can do this as well, inherit your own daemon from TCustomDaemon and your own mapper from TCustomDaemonMapper, and overwrite the various virtual functions in the "Protected" section as needed
291282
292283Here is an excerpt from the DaemonApp unit showing the available properties:
293284
294285``` Pascal
295286 TCustomDaemon = Class(TDataModule)
296287 private
297288
298289 [...]
299290
300291 Protected
301292 Function Start : Boolean; virtual;
302293 Function Stop : Boolean; virtual;
303294 Function Pause : Boolean; virtual;
304295 Function Continue : Boolean; virtual;
305296 Function Execute : Boolean; virtual;
306297 Function ShutDown : Boolean; virtual;
307298 Function Install : Boolean; virtual;
308299 Function UnInstall: boolean; virtual;
309300
310301 [...]
311302```
312303
313304## Creating a Service/Daemon (in Code only)
314305
315306> **Note:** The complete sample source code for this wiki page is available via the link at the end of this wiki page.
316307
317308TestDaemonCodeOnly is a demo project utilizing the TCustomDaemon and TCustomDaemonMapper classes, and it works without the Lazarus IDE. It does therefore not need any Lazarus specific helper files like .lpr or .lfm, and can be maintained using any plain text editor. The programming paradigm used is not event-driven like in GUI centric code, but works based on OOP and inheritance. The helper units providing file logging, a daemon worker thread and support for systemd -install and -uninstall for Linux are the same as in the above GUI sample.
318309
319310Support for -install and -uninstall for Linux is provided by the same DaemonSystemdInstallerUnit as used above. Windows service installation and uninstallation is already built-in in the DaemonApp unit. The TDaemonMapper class provides the WinBindings property to configure the service for Windows, on Linux this property is not used and has no effect. The code provided in the DeamonSystemdInstallerUnit provides similar functionality for Linux/systemd/systemctl.
320311
321312The -run parameter is not supported on Windows, Windows has its own facilities (the Windows Service Control Manager or the "sc" command line tool) to control services.
322313
323314The daemon code needs to be implemented in multiple threads, one for handling the daemon control messages and a separate thread for the daemon "worker" code. Just overwriting the TCustomDaemon.Execute method won't work well, since while the daemon loops in the "TDaemon.Execute" method it will not handle control messages. It will therefore appear to hang in the Service Control Manager and refuse to respond to sc commands. The "worker" thread code is identical with what's required in the above GUI sample, and implemented in the DaemonWorkerThread unit.
324315
325316The absence of a GUI and console requires the use of a logging facility. The TCustomDaemon object provides a logging facility (through the EventLog property), but that is not thread-safe. The FileLoggerUnit provides a simple thread-safe log to file facility. For more sophisticated logging, consider to use the [[LazLogger]] unit.
326317
327318> **Warning:** The logfile created by the FileLoggerUnit is written to the program directory, please ensure that the dameon has write access to it.
328319
329320For more detailed informations about multithreading and how to make code thread-safe through the use of CriticalSections see the [[Multithreaded Application Tutorial]].
330321
331322``` Pascal
332323program TestDaemonCodeOnly;
333324
334325{$mode objfpc}{$H+}
335326
336327uses
337328 {$IFDEF UNIX}
338329 cthreads,
339330 {$ENDIF}
340331 Classes,
341332 SysUtils,
342333 { you can add units after this }
343334 DaemonApp,
344335 FileLoggerUnit, // Thread safe file logger
345336 DaemonWorkerThread, // a TThread descendant to do the daemon's work
346337 DaemonSystemdInstallerUnit; // -install and -uninstall support for Linux/systemd
347338
348339// ------------------------------------------------------------------
349340// TDaemonMapper: This class type defines the settings for the daemon
350341// ------------------------------------------------------------------
351342
352343type
353344 TDaemonMapper1 = class(TCustomDaemonMapper)
354345 public
355346 constructor Create(AOwner: TComponent); override;
356347 end;
357348
358349 constructor TDaemonMapper1.Create(AOwner: TComponent);
359350
360351 begin
361352 inherited Create(AOwner);
362353 with TDaemonDef(self.DaemonDefs.Add) do
363354 begin
364355 DaemonClassName := 'TDaemon1'; // This must exactly match the daemon class
365356 Name := 'TestDaemonCodeOnly'; // Service name
366357 DisplayName := 'Test Daemon (CodeOnly)'; // Service display name
367358 Description := 'Lazarus Daemons and Services Wiki Demo Service (Created in Code only)';
368359 Options := [doAllowStop, doAllowPause];
369360 WinBindings.StartType := stManual; // stBoot, stSystem, stAuto, stManual, stDisabled
370361 WinBindings.ServiceType := stWin32;
371362 end;
372363 end;
373364
374365// -------------------------------------------------------------------
375366// TDaemon: This class type definies the daemon task and handles the
376367// events triggered by the Windows/Linux Service Manager
377368// -------------------------------------------------------------------
378369
379370type
380371
381372 { TDaemon1 }
382373
383374 TDaemon1 = class(TCustomDaemon)
384375 private
385376 FDaemonWorkerThread: TDaemonWorkerThread;
386377 public
387378 function Start: boolean; override; // start the daemon worker thread
388379 function Stop: boolean; override; // stop the daemon worker thread
389380 function Pause: boolean; override; // pause the daemon worker thread (Windows only)
390381 function Continue: boolean; override; // resume the daemon worker thread (Windows only)
391382 function ShutDown: boolean; override; // stop the daemon worker thread because of OS shutdown
392383 function Install: boolean; override; // added -install suppport for Linux
393384 function UnInstall: boolean; override; // added -uninstall suppport for Linux
394385 end;
395386
396387 { TDaemon1 }
397388
398389 // ------------------------------------------------
399390 // Daemon start and stop signal
400391 // ------------------------------------------------
401392
402393 function TDaemon1.Start: boolean;
403394 begin
404395 // Create a suspended worker thread - see DaemonWorkerThread unit
405396 FDaemonWorkerThread := TDaemonWorkerThread.Create;
406397 // Parametrize it
407398 FDaemonWorkerThread.FreeOnTerminate := False;
408399 // Start the worker
409400 FDaemonWorkerThread.Start;
410401 LogToFile(Format('TDaemon1: service %s started, PID=%d', [self.Definition.Name, GetProcessID]));
411402 Result := True;
412403 end;
413404
414405 function TDaemon1.Stop: boolean;
415406 begin
416407 // stop and terminate the worker
417408 if assigned(FDaemonWorkerThread) then
418409 begin
419410 FDaemonWorkerThread.Terminate;
420411 // Wait for the thread to terminate.
421412 FDaemonWorkerThread.WaitFor;
422413 FreeAndNil(FDaemonWorkerThread);
423414 end;
424415 Result := True;
425416 LogToFile(Format('TDaemon1: service %s stopped', [self.Definition.Name]));
426417 end;
427418
428419// ------------------------------------------------
429420// Daemon pause and continue signal (Windows only)
430421// ------------------------------------------------
431422
432423 function TDaemon1.Pause: boolean;
433424 begin
434425 FDaemonWorkerThread.Suspend; // deprecated, yet still working
435426 LogToFile(Format('TDaemon1: service %s paused', [self.Definition.Name]));
436427 Result := True;
437428 end;
438429
439430 function TDaemon1.Continue: boolean;
440431
441432 begin
442433 LogToFile(Format('TDaemon1: service %s continuing', [self.Definition.Name]));
443434 FDaemonWorkerThread.Resume; // deprecated, yet still working
444435 Result := True;
445436 end;
446437
447438// --------------------------------------------------------------
448439// Daemon stop on operating system shutdown signal (Windows only)
449440// --------------------------------------------------------------
450441
451442 function TDaemon1.ShutDown: boolean;
452443 begin
453444 Result := self.Stop; // On shutdown, we trigger the stop handler. This will do nicely for this demo
454445 LogToFile(Format('TDaemon1: service %s shutdown', [self.Definition.Name]));
455446 end;
456447
457448// -----------------------------------------------------------------------------------------
458449// Daemon install and uninstall helpers for Linux, Windows is already built in TCustomDaemon
459450// -----------------------------------------------------------------------------------------
460451
461452 function TDaemon1.Install: boolean;
462453
463454 var
464455 FilePath: string;
465456
466457 begin
467458 Result := False;
468459 {$IFDEF WINDOWS}
469460 Result := inherited Install;
470461 {$ELSE}
471462 {$IFDEF UNIX}
472463 FilePath := GetSystemdControlFilePath(Self.Definition.Name);
473464 LogToFile(Format('TDaemon1: installing control file: %s',[FilePath]));
474465 Result := CreateSystemdControlFile(self, FilePath);
475466 if not Result then
476467 LogToFile('TDaemon1: Error creating systemd control file: ' + FilePath);
477468 {$ENDIF}
478469 {$ENDIF}
479470 LogToFile(Format('TDaemon1: service %s installed: %s', [self.Definition.Name, BoolToStr(Result, 'ok', 'failure')]));
480471 end;
481472
482473 function TDaemon1.UnInstall: boolean;
483474
484475 var
485476 FilePath: string;
486477
487478 begin
488479 Result := False;
489480 {$IFDEF WINDOWS}
490481 Result := inherited UnInstall;
491482 {$ELSE}
492483 {$IFDEF UNIX}
493484 FilePath := GetSystemdControlFilePath(Self.Definition.Name);
494485 Result := RemoveSystemdControlFile(FilePath);
495486 if not Result then
496487 LogToFile('TDaemon1: Error removing systemd control file: ' + FilePath);
497488 {$ENDIF}
498489 {$ENDIF}
499490 LogToFile(Format('TDaemon1: service %s uninstalled: %s', [self.Definition.Name, BoolToStr(Result, 'ok', 'failure')]));
500491 end;
501492
502493// ---------------------
503494// Daemon main init code
504495// ---------------------
505496
506497begin
507498 RegisterDaemonClass(TDaemon1);
508499 RegisterDaemonMapper(TDaemonMapper1);
509500 Application.Run;
510501end.
511502```
512503
513504## Daemon/Service Installation
514505
515506### Windows
516507
517508You can install the service from any elevated command prompt by starting the executable with the -install parameter.
518509
519510Open a terminal with elevated privileges (run as administrator), navigate to the directory where you compiled your test application into, and try the following commands:
520511
521-<table>
522512<thead>
523-<tr>
524-<th>Command</th>
525-<th>Description</th>
526-</tr>
513+
527514</thead>
528515<tbody>
529-<tr>
530-<td><code>TestDaemon -install</code></td>
531-<td>install the daemon</td>
532-</tr>
533-<tr>
534-<td><code>sc query TestDaemon</code></td>
535-<td>check the service status</td>
536-</tr>
537-<tr>
538-<td><code>sc config TestDaemon start=auto</code></td>
539-<td>configure the service to be started<br />
516+
540517when the machine boots</td>
541-</tr>
542-<tr>
543-<td><code>sc config TestDaemon start=manual</code></td>
544-<td>configure the service to be started manually</td>
545-</tr>
546-<tr>
547-<td><code>sc config TestDaemon start=disabled</code></td>
548-<td>disable the service (cannot be started manually)</td>
549-</tr>
550-<tr>
551-<td><code>TestDaemon -uninstall</code></td>
552-<td>remove the daemon</td>
553-</tr>
518+
554519</tbody>
555-</table>
556520
557-<figure>
558-<img src="2022-01-31_23_50_28-Lazarus_IDE_v2.2.0RC2_-_Daemon_application.png" title="Screenshot taken 2/2022 on a Windows 11 machine" />
559-<figcaption>Screenshot taken 2/2022 on a Windows 11 machine</figcaption>
560-</figure>
521+![](/wiki-images/a/a4/2022-01-31_23_50_28-Lazarus_IDE_v2.2.0RC2_-_Daemon_application.png)
561522
562523After successful installation you can control the service either via the sc command line, or by GUI using the "Services" management console. Recent Windows Versions do also have a basic service control facility on the "Services" tab of the Task Manager.
563524
564525The most important sc commands to control a service after installation are:
565526
566527| Command | Description |
567528|-----------------------|-------------------------|
568529| `sc start TestDaemon` | start the service |
569530| `sc query TestDaemon` | query the service state |
570531| `sc stop TestDaemon` | stop the service |
571532
572533You may also use the Windows service control manager console.
573534
574-<figure>
575-<img src="ServiceControlManager.png" title="ServiceControlManager.png" />
576-<figcaption>ServiceControlManager.png</figcaption>
577-</figure>
535+![](/wiki-images/0/07/ServiceControlManager.png)
578536
579537> **Note:** Most Windows service properties, like autostart on boot or usage of a specific service account, can be set via the TDaemonMapper1.WinBindings property in the DaemonMapperUnit1 file.
580538
581539### Linux
582540
583541Unlike for Windows, the TDaemonApp application does not have any support for an automated installation, since under Linux/Unix there is a wide variety of service control subsystems. You can implement your own installation scripts in the install and uninstall handlers of the TCustomDaemon object, like shown for systemd support in the above code sample. If you follow that route, installation and de-installation is essentially the same like on Windows.
584542
585543You can install and uninstall the service from a terminal by starting the executable with sudo and the -install or -uninstall parameter:
586544
587-<table>
588545<thead>
589-<tr>
590-<th>Command</th>
591-<th>Description</th>
592-</tr>
546+
593547</thead>
594548<tbody>
595-<tr>
596-<td><code>sudo ./TestDaemon -install</code></td>
597-<td>install the service</td>
598-</tr>
599-<tr>
600-<td><code>sudo ./TestDaemon -uninstall</code></td>
601-<td>uninstall the service</td>
602-</tr>
603-<tr>
604-<td><code>systemctl enable TestDaemon</code></td>
605-<td>configure the service to be started<br />
549+
606550when the machine boots</td>
607-</tr>
608-<tr>
609-<td><code>systemctl disable TestDaemon</code></td>
610-<td>configure the service to not be started<br />
551+
611552when the machine boots</td>
612-</tr>
553+
613554</tbody>
614-</table>
615555
616556After successful installation you can control the service using the systemctl command
617557
618558| Command | Description |
619559|-------------------------------|-------------------------|
620560| `systemctl start TestDaemon` | start the service |
621561| `systemctl status TestDaemon` | query the service state |
622562| `systemctl stop TestDaemon` | stop the service |
623563
624-<figure>
625-<img src="OnLinuxWithSystemctl.png" title="OnLinuxWithSystemctl.png" />
626-<figcaption>OnLinuxWithSystemctl.png</figcaption>
627-</figure>
564+![](/wiki-images/4/4f/OnLinuxWithSystemctl.png)
628565
629566The code in DaemonSystemdInstallerUnit.CreateSystemdControlFile triggered by the -install command line parameter reads the most basic properties of the TDaemonMapper class and writes a simple systemd .service file into the /lib/systemd/system directory to put your daemon under control of systemd. If you prefer to do it manually, you may use any text editor to create your own file following this template:
630567
631568``` ini
632569[Unit]
633570Description=Long description of your application
634571After=network.target
635572
636573[Service]
637574Type=simple
638575User=name_of_a_service_user_account
639576ExecStart=complete_path_and_file_name -r
640577RemainAfterExit=yes
641578TimeoutSec=25
642579
643580[Install]
644581WantedBy=multi-user.target
645582```
646583
647584- Edit the following values
648585 - Description - Long Description of your service application
649586 - ExecStart - complete-path_and_file_name is the name of your compiled service application with its complete path
650587 - User - specify an service user account here, if omitted the daemon will run as root, running a daemon as root is strongly discouraged
651588
652589- Save the file
653590 - Navigate to /lib/systemd/system/
654591 - Name the file the name_of_your_service.service
655592
656593Many more entries in the .service file created by DaemonSystemdInstallerUnit.CreateSystemdControlFile are available to customize how systemd controls your daemon, please see the the systemd "unit" file reference of your Linux distribution.
657594
658595## Reading the Log File
659596
660597This is a sample log file showing the daemon's internals while running, it was created on Windows, but will look exactly the same on Linux.
661598
662-<figure>
663-<img src="2022-02-01_12_17_27-TestDaemon.png" title="2022-02-01_12_17_27-TestDaemon.png" />
664-<figcaption>2022-02-01_12_17_27-TestDaemon.png</figcaption>
665-</figure>
599+![](/wiki-images/d/d1/2022-02-01_12_17_27-TestDaemon.png)
666600
667601> **Warning:** The FileLoggerUnit does always log into the program directory, please make sure the daemon has write access permissions to it.
668602
669603## Debugging a Daemon/Service
670604
671605Debugging a daemon is not as straightforward as debugging a normal application, since a running daemon does usually have neither a GUI nor a console, and its run/stop state is usually managed by the operating system. Windows and Linux require different approaches to debugging, especially to the way you initiate a debugging session. Once you have attached the debugger to the daemon and hit your first breakpoint, things work just with any other application, and you may control the debugger directly from your Lazarus GUI and the daemon source code.
672606
673607As a prerequisite, you need to compile your service/daemon using a "debug" configuration, so debug code gets inserted and a matching debug symbol file (.dbg) file is created.
674608
675609### Windows
676610
677611The debugging strategy on Windows depends on what portion of the code you need to examine:
678612
679613\- If you need to debug -install or -uninstall, set "Run" - "Run Parameters" in the Lazarus GUI to -install or -uninstall, set your breakpoints, and start the code \[F9\].
680614
681615\- If you need to debug the service worker code, start the service via the operating system (sc command or Windows Service Manager), determine the Process ID (PID), and then choose "Run" - "Attach to Program". Once attached, the debugger will automatically halt at a predefined temporary breakpoint in ntdll.dll!DbgUserBreakPoint, and the service will be looping somewhere within the worker code. After setting your breakpoints and watches in the worker code, resume your debugging by running the code using \[F9\].
682616
683-<figure>
684-<img src="2022-02-21_21_25_22-Lazarus_IDE_v2.2.0_-_Daemon_application_(debugging_...).png" title="2022-02-21_21_25_22-Lazarus_IDE_v2.2.0_-_Daemon_application_(debugging_...).png" />
685-<figcaption>2022-02-21_21_25_22-Lazarus_IDE_v2.2.0_-_Daemon_application_(debugging_...).png</figcaption>
686-</figure>
617+![](/wiki-images/d/da/2022-02-21_21_25_22-Lazarus_IDE_v2.2.0_-_Daemon_application_%28debugging_...).png "2022-02-21_21_25_22-Lazarus_IDE_v2.2.0_-_Daemon_application_(debugging_...).png")
687618
688619> **Warning:** The "Pause" button in the Lazarus symbol bar does not seem to work when debugging a running process remotely, it will hang the debugger if pressed. Use "Run" \[F9\] to resume debugging after a breakpoint.
689620
690621\- If you need to debug the create or start handlers, or any other piece in code which might have already been executed before you had a chance to "Attach to program" you can make your code wait until a debugger is attached by inserting the following line in your code:
691622
692623``` Pascal
693624// This code will loop until a debugger is attached
694625While not IsDebuggerPresent do sleep(100); // will loop until a debugger is attached
695626// ... put a breakpoint somewhere after the loop to catch the code as soon as it leaves the loop
696627```
697628
698-<figure>
699-<img src="2022-02-21_21_36_38-Lazarus_IDE_v2.2.0_-_Daemon_application_(debugging_...).png" title="2022-02-21_21_36_38-Lazarus_IDE_v2.2.0_-_Daemon_application_(debugging_...).png" />
700-<figcaption>2022-02-21_21_36_38-Lazarus_IDE_v2.2.0_-_Daemon_application_(debugging_...).png</figcaption>
701-</figure>
629+![](/wiki-images/c/c4/2022-02-21_21_36_38-Lazarus_IDE_v2.2.0_-_Daemon_application_%28debugging_...).png "2022-02-21_21_36_38-Lazarus_IDE_v2.2.0_-_Daemon_application_(debugging_...).png")
702630
703631After attaching the debugger and pressing \[F9\] the daemon will stop right at the breakpoint.
704632
705633> **Note:** The -run parameter supported by Linux is not available on Windows. You must use the Windows Service Control Manager to start and stop the service. To attach the debugger to the then running process you need to run the Lazarus IDE with Administrative Prvilege (elevated), otherwise you will get Error 5 (access denied) when trying to attach to the service. If you are running Lazarus elevated, and nevertheless have difficulties attaching to your service ("Access denied" or "Run" - "Attach to program" is grayed out) make sure you have set the debugger in "Project" - "Project Settings" - "Debugger" properly, most likely you will need "Gdb (Gnu Debugger)".
706634
707635### Linux
708636
709637Linux is propably somewhat easier to debug, since you can execute your daemon like every other program from within the Lazarus IDE, if you enter -run into "Command line parameters" in the "Run" - "Run Parameters" menu. Keep in mind though, that many problems originate from security restrictions enforced by the operating system. Many of those restrictions are not applied when the daemon is run in user context.
710638
711-<figure>
712-<img src="2022-02-22_12_18_20-Debian_10.8_Buster_-_VMware_Workstation.png" title="2022-02-22_12_18_20-Debian_10.8_Buster_-_VMware_Workstation.png" />
713-<figcaption>2022-02-22_12_18_20-Debian_10.8_Buster_-_VMware_Workstation.png</figcaption>
714-</figure>
639+![](/wiki-images/f/fd/2022-02-22_12_18_20-Debian_10.8_Buster_-_VMware_Workstation.png)
715640
716641Now you can start the daemon using \[F9\] like any normal application and debug it.
717642
718-<figure>
719-<img src="2022-02-22_12_19_41-Debian_10.8_Buster_-_VMware_Workstation.png" title="2022-02-22_12_19_41-Debian_10.8_Buster_-_VMware_Workstation.png" />
720-<figcaption>2022-02-22_12_19_41-Debian_10.8_Buster_-_VMware_Workstation.png</figcaption>
721-</figure>
643+![](/wiki-images/e/e9/2022-02-22_12_19_41-Debian_10.8_Buster_-_VMware_Workstation.png)
722644
723645If you do, for instance because you suspect a problem with service specific restrictions, need to attach the debugger to the already running daemon, you can do so using "Run" - "Attach to Program", if you have configured the daemon to run under a specific user account instead of root. See the chapter above about Linux install and uninstall support on how to add an appropriate option to the .service file.
724646
725647Unfortunately there isn't such a handy "IsDebuggerPresent" call available like under Windows, but the following trick works nicely.
726648
727649``` Pascal
728650procedure WaitForDebugger;
729651
730652var
731653 SetMeValue:boolean=false;
732654
733655begin
734656 // Linux trick: attach the debugger, and change SetMeValue to true
735657 while not SetMeValue do
736658 sleep(100); // put a breakpoint here
737659end;
738660```
739661
740662Put a breakpoint into the line with the "sleep" command. Once the daemon is started using systemctl, execution it will be halted at your breakpoint in the while loop. Now you can attach the debugger to the PID you got from systemctl status, put a watch (\[Ctrl-F5\]) on the "SetMeValue" variable and then use the Evaluate/Modify context menu to change the value of the "SetMeValue" variable from "false" to "true", and resume program execution (\[F9\]). The execution will immedately leave the while loop and run to the next breakpoint.
741663
742664## Known problems
743665
744666### Permission related Problems
745667
746668While you can do most of the development of the core daemon code under a user account and from within an IDE (Lazarus), the live daemon will most likely run "in the background" under a different context. Nowadays operating systems do, for security reasons, run daemons in more or less restricted environments. Because a daemon does have neither a terminal nor a GUI to show any messages, debugging such issues can be hard. Unfortunately there is no common recipe which resolves all service related problems in all operating systems supported by FPC/Lazarus, as the strategy behind restricting service code is vastly different between Windows, Linux and macOS, and it is even changing between different versions of the same operating system, with the more recent versions trending to be more restrictive than their predecessors.
747669
748670If you make the transition from running the daemon code under your developer account to "go live" and test the daemon running under a service user account oder under "Localsystem" or "root" (which, btw, is considered bad security practice since many years), take the time to study the security related documentations of your operating system and find out which restrictions and other specialities apply if code runs in the background.
749671
750672> **Note:** See the section above about debugging daemons running in the background by attaching the debugger to the running code. Make your daemon, once the daemon is started by the operating system, wait until the debugger is attached. From there you can then observe the execution and detect restrictions related exceptions and other security/background operation related nastinesses.
751673
752674### Exception EStreamError: "no streaming method available"
753675
754676```text
755677Exception at 0041A30E: EStreamError:
756678Failed to initialize component class "TDaemon1": No streaming method available.
757679```
758680
759681You have included visual components (which require initalization by a built-in resource stream created by the .lfm file in GUI applications) in a code-only daemon project. If you decide to create the daemon in code, you may neither include the LazDaemon package, nor add the "LazDaemonApp" to the uses section. If you do, your project will compile fine, but if you start the exe you get the above error.
760682
761683### Several "Stream Read" and "Unkown Properties" errors ...
762684
763685... when loading a daemon project through the Lazarus IDE.
764686
765687Most likely you try to open a TDaemon (GUI component) based project, but you haven't yet installed the LazDaemon package.
766688
767689Abort all loading errors, and install the LazDaemon package using the Lazarus Package Manager like described above. After Lazarus has re-compiled, the daemon project should re-load and compile without problems.
768690
769-<figure>
770-<img src="Image_9.png" title="Image_9.png" />
771-<figcaption>Image_9.png</figcaption>
772-</figure>
691+![](/wiki-images/f/fb/Image_9.png)
773692
774-<figure>
775-<img src="Image_10.png" title="Image_10.png" />
776-<figcaption>Image_10.png</figcaption>
777-</figure>
693+![](/wiki-images/b/b3/Image_10.png)
778694
779695### Lazarus IDE suggests to clear invalid properties from the .lpm file
780696
781697This is another issue which was observed when you try to open a TDaemon (GUI) based project, but you haven't yet installed the LazDaemon package.
782698
783-<figure>
784-<img src="Image_11.png" title="Image_11.png" />
785-<figcaption>Image_11.png</figcaption>
786-</figure>
699+![](/wiki-images/1/13/Image_11.png)
787700
788701Abort all loading errors, and install the LazDaemon package using the Lazarus Package Manager like described above. After Lazarus has re-compiled itself, the daemon project should re-load and compile without problems.
789702
790703### The Daemon won't start on Unix because of missing threading support
791704
792705The code compiles fine, but if you try to run the daemon using a console and the -run parameter, you get a message saying something like "no thread support compiled in". If you try to start the dameon through systemctl, you won't see any message, it simply won't seem to run. You can use "systemctl status" to see the last error caused by the daemon, this will reveal the same crash infos like starting it using -run.
793706
794707`This binary has no thread support compiled in. Recompile the application with a thread-driver in the program uses clause before other units using thread. Runtime error 232 at ...`\
795708`...`\
796709`No heap dump by heaptrc unit Exitcode = 232`
797710
798711Most likely you compiled your project without support for the CThreads library (not required on Windows). See the above project lpr file sample code and the notes about "UseCThreads". The short of it is to go to the .lpr file and remove the {\$IFDEF UseCThreads} ... {\$ENDIF} around the "uses CThreads" statement, so the CThreads library gets linked into your code on Linux. On Windows this library is not required.
799712
800713### Debugging Issues observed on Windows
801714
802715The "Pause" button in the Lazarus symbol bar activates, if a breakpoint is hit, but it does not seem to work when debugging a running process remotely, debugging will hang if the button is pressed. Use "Run" \[F9\] instead to resume debugging after a breakpoint.
803716
804717To attach the debugger to the running daemon process you need to run the Lazarus IDE with Administrative Prvilege (elevated), otherwise you will get "Error 5 (access denied)" when trying to attach the debugger.
805718
806719If all debugging selections in the "Run" menu are unavailable (grayed), navigate to "Project" - "Project Options" - "Debugger" and set the "Debugger Backend" to "Gdb \[GNU debugger (gdb)\]". It has been observed that this setting may get messed up if you copy the project source files from one computer / Lazarus installation over to a different one.
807720
808721### Debugging Issues observed on Linux
809722
810723Using "Attach to program" to debug a running daemon via process ID (PID) does not seem to work, you get an error "Debugger Error" ... "Attach failed". One reason may be that you haven't specified a user in the service control file, and thus the service runs as "root", which hampers debugging. Besides of running the service using a dedicated service user account, as a workaround, consider to start debugging by loading the daemon from within Lazarus using "Run" - "Run Parameters" and entering the -run command line parameter. This way debugging does work, but note that the code runs in a different context (user instead of system- or service user account), which may change it's behaviour because of permission related issues.
811724
812725### Daemons have no Access to Network- and Cloud Drives (Onedrive, Dropbox)
813726
814727When choosing the directory for storing the daemon executable (which is by default also used for logging), keep in mind that once the daemon runs in the background it does usually run under the context of a system- or service account. User-specific virtual drives created in one user's account are usually available neither to other user accounts nor to the system account. If you run the daemon from a networked/cloud drive, it is likely that the daemon won't start, since the OS will not be able to find the executable, and that write attempts to the log will fail, since by default the log file is written where the executable is. This may be confusing, since -install and -uninstall will likely work and log fine, because they are usually executed by hand and thus run in your user's context.
815728
816729### Specifying a Service User Account does not work on Linux
817730
818731If you start the daemon and check which account it runs under, it appears to run as root, despite you have specified a different account in your systemd .service file.
819732
820733There are (unconfirmed) reports around that (on some versions of Linux?) in the .service file controlling the systemd the "User" entry must appear before the "ExecStart" entry to work properly, like shown in the code samples in the sections about Linux install and uninstall support.
821734
822735### System Codepage / UTF-8
823736
824737\[Old content, needs verfification 3/2022, may be obsolete\] A LazDeamon project is working with default, not UTF-8, codepage. The *-dDisableUTF8RTL* mode has to be activated with *Project Options ...* -\> *Compiler Options* -\> *Additions and Overrides* -\> *Use system encoding*.
825738
826739## \[Some historical contents - needs to be verified and integrated or removed\]
827740
828741\[Old content, needs verfification 3/2022, may be obsolete\]
829742
830743### Linux (only for older Debian)
831744
832745Download, configure, and "Save As" - the sample script located at Web Archive: [1](https://web.archive.org/web/20130209102726/http://aurawin.com/lazarus/debian-service.sh) (The original link is dead for a long time).
833746
834747- SVC_ALIAS is the long description of your application
835748- SVC_FILENAME is the actual file name of your compiled service application
836749- SVC_DIR is the place your you copied the service application
837750- SVC_SERVICE_SCRIPT is the final name of the service.sh when you "Save As" the customized debian-service.sh script.
838751
839752Place your script in the /etc/init.d/ folder
840753
841754start the service by running "sudo service Name_Of_Your_Script start"
842755
843756> **Note:** sudo has some variations, e.g.:
844757>
845758> \
846759> `sudo -s #`\
847760> \
848761> `sudo -H #`\
849762> \
850763> `sudo -i  #`\
851764> \
852765> `sudo su  #`\
853766> \
854767> `sudo sh  #`
855768
856769In order to auto-run the service at startup you can try update-rc.d or else will need a third party tool that will do this.
857770
858771Option 1
859772
860773``` bash
861774sudo update-rc.d Name_Of_Your_Script defaults
862775```
863776
864777Option 2
865778
866779``` bash
867780sudo apt-get install chkconfig
868781sudo chkconfig --add Name_Of_Your_Script
869782sudo chkconfig --level 2345 Name_Of_Your_Script on
870783```
871784
872785## See also
873786
874787- [GitHub Repository](https://github.com/arminlinder/Lazarus-FPC-Wiki-Code-Samples/zipball/master/) Complete demo project sample code (.zip)
875788- [[macOS daemons and agents]] - macOS native agents using launchd
876789- [[ServiceManager]] Example for the Free Pascal unit for managing Windows services
877790- [Taming the daemon: PDF by Michaël Van Canneyt](https://www.freepascal.org/~michael/articles/daemons/daemons.pdf)
878791- [[Docker Containerization]] - Containerization as a means to create daemons / services
879792- [[UniqueInstance]]
880793
881794---
882795*From the Free Pascal wiki, page "Daemons and Services", as it stood on 2022-12-29 after a change by Alextp, out of the wiki's dump of 2024-05-26. Copyright stays with the author who wrote it, according to the old wiki's [[Licensing]] page. If you have concerns about this material, [message the admin](/wiki-contact).*

Back to history