Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
19 changes: 19 additions & 0 deletions docs/src/getting_started.md
Original file line number Diff line number Diff line change
Expand Up @@ -188,6 +188,25 @@ This will add two commands to the devshell: `database:start` and
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).

You can also run commands before and after starting/stopping a service group.
For example, to run database migrations before starting and cleanup after stopping:
```toml
[serviceGroups.api]
description = "API server and related services"
beforeStart = """
echo "Running database migrations..."
./manage.py migrate
"""
afterStop = """
echo "Cleaning up temp files..."
rm -rf /tmp/api_*
"""
[serviceGroups.api.services.django]
command = "python manage.py runserver 0.0.0.0:8000"
[serviceGroups.api.services.redis]
command = "redis-server"
```

## Wrapping up

**devshell** is extensible in many different ways. In the next chapters we will
Expand Down
80 changes: 80 additions & 0 deletions docs/src/modules_schema.md
Original file line number Diff line number Diff line change
Expand Up @@ -1284,6 +1284,86 @@ attribute set of (submodule)

- [modules/services.nix](https://github.com/numtide/devshell/tree/main/modules/services.nix)

### `serviceGroups.<name>.beforeStart`

Shell command to run before starting the service group.

**Type**:

```console
string
```

**Default value**:

```nix
""
```

**Declared in**:

- [modules/services.nix](https://github.com/numtide/devshell/tree/main/modules/services.nix)

### `serviceGroups.<name>.afterStart`

Shell command to run after starting the service group.

**Type**:

```console
string
```

**Default value**:

```nix
""
```

**Declared in**:

- [modules/services.nix](https://github.com/numtide/devshell/tree/main/modules/services.nix)

### `serviceGroups.<name>.beforeStop`

Shell command to run before stopping the service group.

**Type**:

```console
string
```

**Default value**:

```nix
""
```

**Declared in**:

- [modules/services.nix](https://github.com/numtide/devshell/tree/main/modules/services.nix)

### `serviceGroups.<name>.afterStop`

Shell command to run after stopping the service group.

**Type**:

```console
string
```

**Default value**:

```nix
""
```

**Declared in**:

- [modules/services.nix](https://github.com/numtide/devshell/tree/main/modules/services.nix)

### `serviceGroups.<name>.services.<name>.command`

Command to execute.
Expand Down
166 changes: 105 additions & 61 deletions modules/services.nix
Original file line number Diff line number Diff line change
Expand Up @@ -3,9 +3,9 @@
lib,
pkgs,
...
}:
let
inherit (lib)
}: let
inherit
(lib)
mkOption
types
;
Expand Down Expand Up @@ -41,81 +41,125 @@ let
'';
};
services = mkOption {
type = types.attrsOf (types.submodule { options = serviceOptions; });
default = { };
type = types.attrsOf (types.submodule {options = serviceOptions;});
default = {};
description = ''
Attrset of services that should be run in this group.
'';
};
beforeStart = mkOption {
type = types.lines;
default = "";
description = ''
Shell command to run before starting the service group.
'';
};
afterStart = mkOption {
type = types.lines;
default = "";
description = ''
Shell command to run after starting the service group.
'';
};
beforeStop = mkOption {
type = types.lines;
default = "";
description = ''
Shell command to run before stopping the service group.
'';
};
afterStop = mkOption {
type = types.lines;
default = "";
description = ''
Shell command to run after stopping the service group.
'';
};
};
groupToProcfile =
name: g:
groupToProcfile = name: g:
pkgs.writeText "Procfile.${name}" (
lib.concatLines (
lib.mapAttrsToList (
sName: s: "${if s.name == null then sName else s.name}: ${s.command}"
) g.services
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() {
if ps -p $pid > /dev/null; then
kill -TERM $pid
fi
rm "$PRJ_DATA_DIR/pids/${gName}.pid"
wait $pid
}
trap "on_stop" SIGINT SIGTERM SIGHUP EXIT
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=$(${pkgs.coreutils}/bin/cat "$PRJ_DATA_DIR/pids/${gName}.pid")
kill -TERM $pid
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/"
${g.beforeStart}
${pkgs.honcho}/bin/honcho start -f ${procfile} -d "$PRJ_ROOT" &
pid=$!
echo $pid > "$PRJ_DATA_DIR/pids/${gName}.pid"
${g.afterStart}
on_stop() {
${g.beforeStop}
if ps -p $pid > /dev/null; then
kill -TERM $pid
fi
${g.afterStop}
rm "$PRJ_DATA_DIR/pids/${gName}.pid"
fi
'').outPath;
}
];
in
{
wait $pid
}
trap "on_stop" SIGINT SIGTERM SIGHUP EXIT
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=$(${pkgs.coreutils}/bin/cat "$PRJ_DATA_DIR/pids/${gName}.pid")
${g.beforeStop}
kill -TERM $pid
rm "$PRJ_DATA_DIR/pids/${gName}.pid"
${g.afterStop}
fi
'').outPath;
}
];
in {
options.serviceGroups = mkOption {
type = types.attrsOf (types.submodule { options = groupOptions; });
default = { };
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 = lib.foldl (l: r: l ++ r) [ ] (
config.commands = lib.foldl (l: r: l ++ r) [] (
lib.mapAttrsToList (
gName: g: groupToCommands (if g.name == null then gName else g.name) g
) config.serviceGroups
gName: g:
groupToCommands (
if g.name == null
then gName
else g.name
)
g
)
config.serviceGroups
);
}
Loading
Loading