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.(Consider using
defaultRunDaemonPidFile(...)
) -
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.
DaemonModule
will not generate any PID file at all unless you set a file location by
overriding def runDaemonPidFile : Option[os.Path]
.
You can override this to yield any path you choose (as long as your daemon will have permission to write it).
mill-daemon offers a default. Just write
override def runDaemonPidFile : Option[os.Path] = defaultRunDaemonPidFile("mydaemon") // obviously, use your own daemon's name!
This will place the PID file at your build's workspace root directory under mydaemon.pid
.
However, users will be able to define an alternative location by creating a file in the workspace root directory called .pid-file-path
,
and providing in that file an absolute path.
This creates a good, sensible default, but also allows users to override this default without having to modify build.mill
.
-
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.
-
There is now a supporting utility to help you do this properly. Just include the dependency
com.mchange::mill-daemon-util:<mill-daemon-version>
in your application, thenimport com.mchange.milldaemon.util.PidFileManager PidFileManager.installShutdownHookCarefulDelete()
-
-
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.)