diff --git a/docs/getting_started.md b/docs/getting_started.md index 659047f4..1820f24a 100644 --- a/docs/getting_started.md +++ b/docs/getting_started.md @@ -161,9 +161,32 @@ packages = [ ] ``` -### Wrapping up + +## Adding background services + +Many projects need background services to be running during development or to +run tests (e.g. a database, caching server, webserver, ...). This is supported +in devshell through the concept of service groups. + +A service group defines a collection of long-running processes that can be +started and stopped. + +An example service group could be configured like this: +```toml +[serviceGroups.database] +description = "Runs a database in the backgroup" +[serviceGroups.database.services.postgres] +command = "postgres" +[serviceGroups.database.services.memcached] +command = "memcached" +``` + +This will add two commands to the devshell: `database:start` and +`database:stop`. `database:start` starts the defined services in the `database` +group in the foreground and shows their output. `database:stop` can be executed +in a different shell to stop the processes (or press Ctrl-C in the main shell). + +## Wrapping up **devshell** is extensible in many different ways. In the next chapters we will discuss the various ways in which it can be adapted to your project's needs. -to find -of the configuration options available. diff --git a/modules/modules.nix b/modules/modules.nix index a2361fc2..f4a8e507 100644 --- a/modules/modules.nix +++ b/modules/modules.nix @@ -16,6 +16,7 @@ let branch = "main"; }]; } + ./services.nix ]; pkgsModule = { config, ... }: { diff --git a/modules/services.nix b/modules/services.nix new file mode 100644 index 00000000..3f072217 --- /dev/null +++ b/modules/services.nix @@ -0,0 +1,97 @@ +{ config, lib, pkgs, ... }: +with lib; +let + serviceOptions = { + name = mkOption { + type = types.nullOr types.str; + default = null; + description = '' + Name of this service. Defaults to attribute name in group services. + ''; + }; + command = mkOption { + type = types.str; + description = '' + Command to execute. + ''; + }; + }; + groupOptions = { + name = mkOption { + type = types.nullOr types.str; + default = null; + description = '' + Name of the service group. Defaults to attribute name in groups. + ''; + }; + description = mkOption { + type = types.nullOr types.str; + default = null; + description = '' + Short description of the service group, shown in generated commands + ''; + }; + services = mkOption { + type = types.attrsOf (types.submodule { options = serviceOptions; }); + default = { }; + description = '' + Attrset of services that should be run in this group. + ''; + }; + }; + groupToProcfile = name: g: pkgs.writeText "Procfile.${name}" (concatLines + (mapAttrsToList (sName: s: "${if s.name == null then sName else s.name}: ${s.command}") g.services) + ); + groupToCommands = gName: g: + let + procfile = groupToProcfile gName g; + description = if g.description == null then gName else g.description; + in + [ + { + name = "${gName}:start"; + category = "service groups"; + help = "Start ${description} services"; + command = (pkgs.writeShellScript "${gName}-services-start" '' + if [ -e "$PRJ_DATA_DIR/pids/${gName}.pid" ]; then + echo "Already running, refusing to start" + exit 1 + fi + mkdir -p "$PRJ_DATA_DIR/pids/" + ${pkgs.honcho}/bin/honcho start -f ${procfile} -d $PRJ_ROOT & + pid=$! + echo $pid > "$PRJ_DATA_DIR/pids/${gName}.pid" + on_stop() { + kill -TERM $pid + rm "$PRJ_DATA_DIR/pids/${gName}.pid" + wait $pid + } + trap "on_stop" SIGINT SIGTERM SIGHUP + wait $pid + '').outPath; + } + { + name = "${gName}:stop"; + category = "service groups"; + help = "Stop ${description} services"; + command = (pkgs.writeShellScript "${gName}-services-stop" '' + if [ -e "$PRJ_DATA_DIR/pids/${gName}.pid" ]; then + pid=$(cat "$PRJ_DATA_DIR/pids/${gName}.pid") + kill -TERM $pid + rm "$PRJ_DATA_DIR/pids/${gName}.pid" + fi + '').outPath; + } + ]; +in +{ + options.serviceGroups = mkOption { + type = types.attrsOf (types.submodule { options = groupOptions; }); + default = { }; + description = '' + Add services to the environment. Services can be used to group long-running processes. + ''; + }; + + config.commands = foldl (l: r: l ++ r) [ ] (mapAttrsToList (gName: g: groupToCommands (if g.name == null then gName else g.name) g) config.serviceGroups); +}