jetify-com / devbox

Instant, easy, and predictable development environments
https://www.jetify.com/devbox/
Apache License 2.0
8.49k stars 196 forks source link

feat: exit_hook #2362

Open pythoninthegrass opened 2 days ago

pythoninthegrass commented 2 days ago

What problem are you trying to solve?

init_hooks are cool. I'd like exit_hooks that do the opposite. For instance, I'm always starting up an ephemeral redis container for ansible facts that only lasts during my project work.

You already implemented process-compose for redis (and other services!), which precludes a docker step. Now turning it off automatically would complete the loop.

What solution would you like?

With exit_hooks, in addition to returning to the original shell, it would have some kind of de-scaffolding.

If we keep it general, exit_hooks could act like bash traps or python's try/except/finally blocks.

Essentially, the workflow would resemble:

{
  "packages": [
    "redis@latest"
  ],
  "env": {
    "REDIS_PORT":      "6379",
    "REDIS_CONF":      "./devbox.d/redis/redis.conf"
  },
  "shell": {
    "init_hook": [
      "devbox services start redis"
    ],
    "exit_hook": [
      "devbox services stop redis"
    ],
    "scripts": {}
  }
}

Alternatives you've considered

Didn't find anything in my cursory search.

However, it wouldn't take a lot of effort to write a shell script or python script to do the same thing. But that defeats the utility of devbox and requires bespoke solutions.

pythoninthegrass commented 2 days ago

Would also solve (or at least mitigate) #2354.

Novel use of a script though.

DerArkeN commented 2 days ago

Besides stopping running services (which imo should happen when the shell dies anyway), is there an actual use case for exit_hooks?

As a workaround you could use your mentioned traps in the init_hooks like the following:

{
    "$schema": "https://raw.githubusercontent.com/jetify-com/devbox/0.13.5/.schema/devbox.schema.json",
    "packages": [],
    "shell": {
      "init_hook": [
        "echo 'Welcome to devbox!'",
        "trap 'echo Goodbye from devbox!' EXIT"
      ],
    }
}

This will result in the following: image

pythoninthegrass commented 3 hours ago

Appreciate the response @DerArkeN !

This works

"shell": {
    "init_hook": [
      ". $VENV_DIR/bin/activate",
      "uv pip install -r requirements.txt",
      "[ $(uname -s ) = 'Darwin' ] && export ANSIBLE_BECOME_PASS=$(askpass --sudo)",
      "devbox services start redis --quiet",
      "trap 'devbox services stop redis --quiet' EXIT"
    ],

but it's kludgey. (Side note: had no idea traps worked outside of shell scripts! Unless ofc this is converted into one haha)

I can work around it with a taskfile

version: "3.0"

set: ['e', 'u', 'pipefail']
shopt: ['globstar']

env:
  REDIS_PORT: "6379"
  REDIS_CONF: "./devbox.d/redis/redis.conf"

tasks:
  start:
    desc: "Start Redis server"
    cmds:
      - devbox services start redis --quiet
    silent: true

  restart:
    desc: "Restart Redis server"
    cmds:
      - devbox services restart redis --quiet
    silent: true

  stop:
    desc: "Stop Redis server"
    cmds:
      - devbox services stop redis --quiet
    silent: true

then

"shell": {
    "init_hook": [
      ". $VENV_DIR/bin/activate",
      "uv pip install -r requirements.txt",
      "[ $(uname -s ) = 'Darwin' ] && export ANSIBLE_BECOME_PASS=$(askpass --sudo)",
      "task redis:start",
      "trap 'task redis:stop' EXIT"
    ],

but a separation of concerns would be a more streamlined approach.

Services do not die with the shell fwiw. I agree that if they did, exit hooks wouldn't be as applicable to process-compose managed services. For everything else, still think exit hooks would be handy.

DerArkeN commented 3 hours ago

@pythoninthegrass glad I could help, looks good! Didn't know about the traps either, saw it once in a nix discussion before😄

@Lagoja what's the expected behaviour for running services when killing a shell? is it planned to implement stopping services when killing a shell?