How to run a service only while a `devShell` is open?

Hi folks!

Is it possible to only run a service for the duration that a devShell is open?

In other words, is it possible to have a service automatically start upon nix develop .#foo and then have it automatically stop upon exiting the shell?

Motivation

I’ve been been writing a devShell in which I can play with TidalCycles - a pattern language aimed at live music composition/performance. It works by interacting with SuperCollider, a kind of audio server that performs the sound generation.

I’m wondering if there’s some way I can automatically start SuperCollider when I do nix develop and then have it automatically stop when I exit the shell?

2 Likes

I’ve played with this a bit, here is what I came up with. Using shellHook to start the process and Bash trap to make sure it gets killed when bash itself exits. Posting the full flake.

{
  outputs = { self, nixpkgs }: 
  let
    pkgs = nixpkgs.legacyPackages.x86_64-linux;
  in
  {
    devShells.x86_64-linux.default = 
    let
      bg_service = pkgs.writeShellScript "background-service" ''
        while true; do
          sleep 1
          echo "Test 123"
        done
      '';
    in
    pkgs.mkShell {
      shellHook = ''
        ${bg_service} &
        BG_PID=$!
        
        trap "kill -9 $BG_PID" EXIT
      '';
    };

  };
}
2 Likes

The problem of this approach is, that there will be multiple services when you enter the shell multiple times (for whatever reason).

5 Likes

I’m working in a solution using nix-processmgmt, basically during shellHook it does:

  1. Run nixproc-s6-svcscan daemon
  2. Run nixproc-s6-rc-switch nix/processes.nix

In a new shell it fails because daemon is running

1 Like

Yeah in general, it’d be nice to have a solution for each of these cases:

  1. Spawn a unique service per devShell invocation. Each service closes when its respective shell closes. Or,
  2. Ensure only one instance of the service is running if there are one of more instances of the devShell active. It closes only when the last devShell has closed.

For 2 you can take the same approach, but use systemd-run, which should be able to help with deduplicating this service as long as you name it :slight_smile: Only issue is to find out when we should close, probably just a case of finding the right exit condition for the unit.

1 Like

Some alternatives:

A little bit more involved would be to expose a command which then installs and starts a systemd service

example: https://github.com/CommE2E/comm/blob/1bc6faac28eb09424576ef61cacc546e1a30ea96/nix/mysql-up-linux.nix

1 Like

I’ve considered this to run a database for developing my application, but I opted to just create a wrapper script using writeShellScriptBin and run that in a separate terminal. This way, i won’t leave database processes lying around and have an easier time to know what’s going on.
Also, the logs will be in that terminal.

1 Like

I’m having the same issue and I’m wondering if anyone tried solving the problem using screen?

  • You should be able to start a shared session via pkgs.mkShell.shellHook
  • You can pre-configure tabs via the screenrc, with some shell magic they even restart the command in case it crashes
  • You’re able to see & access all your active shells for that devenv
  • Since you’re connecting to a shared instance (or creating it when it doesnt exist) no duplicates
  • Terminating screen should take care of stopping the background process(es)
  • Thanks to screen the number of background processes should only be limited by your hardware