This project enables mill to properly launch systemd daemons that rebuild themselves with each restart.
This renders convenient configuration-as-compiler-checked code, or quick-editing of templates that convert to generated source code.
When you use mill as a launcher, you can simply edit your configuration-as-code or your templates, then hit
systemctl restart myservice
and watch your changes take immediate effect. You enjoy the ergonomics of an
interpreted language with the speed and typesafety of Scala.
-
In
build.sc
, let your module extendDaemonModule
defined in this library. That will give you access to mill commandsrunDaemon
runMainDaemon
-
Override the function
def runDaemonPidFile : Option[os.Path]
to define a place where a PID file should be written by mill prior to shutting down, but after spawning your process. -
Include mill wrapper in your project, and define a launch script that's something like
#!/bin.bash ./millw runMainDaemon mypkg.MyMain "$@"
(This presumes you've also extended
RootModule
. Otherwise, specifymymodulename.runMainDaemon
.) -
When you write your systemd unit file, specify your daemon's
Type=forking
. SetPIDFile=
to the location you specified inrunDaemonPidFile
. -
Start your service (
systemctl start myservice
). Your service will build itself before it starts. Edit stuff, templates, config, core source. Typesystemctl restart myservice
and it will all rebuild.
-
If you asked mill to generate a PID file (by overriding
runDaemonPidFile
), your subprocess will haveMILL_DAEMON_PID_FILE
in its environment. You can use this to, for example, set up a shutdown hook that will delete the PID file when your process terminates.- If you are running daemons under systemd , this is just a nice-to-have backstop! systemd will try to delete the PID file when your process terminates without your intervention. If you do set a shudown hook to delete the PID file please check that the file is a file whose content is your process' PID before deleting. Don't blindly delete a file just because someone was able to get its path stuck in an environment variable.
-
By default, the daemon subprocess inherits the
mill
launcher's standard-in and standard-out. That gives systemd control over where they should be directed, and is usually what you want. However, you can overridedef runDaemonOut : os.ProcessOutput
def runDaemonErr : os.ProcessOutput
to take control of these streams yourself, if you prefer.
- Check out a feedletter installation for a simple example.
Why not just use the runBackground
and runMainBackground
tasks built into JavaModule
?
Applications started via runBackground
and runBackgroundMain
run embedded within a
BackgroundWrapper
process which watches for changes in the files that built the application
and quits when those occur. This is great, exactly what you want, when you are using the mill -w
watch
feature. Whenever you change its source (loosely construed), your application quits and restarts so that
you enjoy prompt updates.
However, this approach is not suitable for daemon processes, which are supposed to run stably and indefinitely, and should not terminate just because someone edits a file or runs a task in the directories from which they emerged.
The runDaemon
tasks here give you clean daemons, mostly decoupled from any continued activity in the build directories
after the parent mill
process terminates.
When you update your mill
build, use systemctl restart <service>
. Until a restart , the "old" service will
continue in its old way.
(In theory, the daemon may not be completely decoupled from activity its launch directory. Infrequently accessed classes compiled into the directory might not be loaded immediately upon daemon launch, and if they are deleted or incompatibly upgraded, your daemon could break when it finally requires them. In practice, this would be unusual. Nevertheless, daemon launch installations shouldn't be active development directories, just sites for occasional modifications, reconfigurations, and relaunches.)