Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

nix: inherit all environment variables in shell #706

Merged
merged 4 commits into from
Mar 2, 2023
Merged

Conversation

gcurtis
Copy link
Collaborator

@gcurtis gcurtis commented Feb 28, 2023

This is a long description, but I wanted to document the exact changes being made to Devbox environments for reference.

Context

Before release v0.4.0, devbox handled environment variables differently depending on the command:

  • devbox shell would start with a clean environment. Nothing was inherited from the parent shell (but shellrc files would still be sourced)
  • devbox shell -- cmd would inherit the environment.
  • devbox run would inherit the environment.

With v0.4.0, all commands adopted the devbox shell behavior of starting with a clean environment. However, this broke some existing users and the workaround of manually specifying environment variables to inherit would be cumbersome. As discussed, this change flips the behavior such that all commands will now inherit their parent shell's environment. Eventually there will be a flag to enable an "isolated" mode to restore the previous behavior.

Changes to devbox shell and devbox run environments

Devbox environments are now built with environment variables from the following steps:

  1. Variables exported in the user's ~/.bashrc, ~/.zshrc, etc.
  2. Variables from the current environment except for PWD, OLDPWD, SHLVL, and SHELL.
  3. Variables from the nix print-dev-env environment except for BASHOPTS, HOME, NIX_BUILD_TOP, NIX_ENFORCE_PURITY, NIX_LOG_FD, NIX_REMOTE, PPID, SHELL, SHELLOPTS, TEMP, TEMPDIR, TERM, TMP, TMPDIR, TZ, and UID. The SSL_CERT_FILE variable is also ignored if it's set to /no-cert-file.crt.
  4. Variables from Devbox plugins.
  5. PATH is set to the concatenation of the PATHs from step 4, step 3, and step 2 (in that order). The PATH from the user's init files in step 1 is overwritten. The final PATH is cleaned and deduplicated.
  6. HISTFILE is set to a Devbox project-specific file.
  7. PS1 is set to "(devbox) $PS1"

Each step adds to the previous step, overwriting any keys that already exist.

Bug fixes and improvements

  • As suggested by @ipince, shellrc.tmpl simply sources the user's rcfile instead of copying it.
  • The result of PrintEnv now escapes all environment variable values. This means that values such as FOO=$(echo bar) will become FOO="\$(echo bar)" instead of being evaluated by the shell. Allowing variables and command substitution would require implementing a full parser to determine which characters to escape.
  • Some unquoted variables in shellrc.tmpl are now quoted so that devbox directories with spaces work.
  • The cd commands in shellrc.tmpl exit on failure to prevent running further commands in the wrong directory.

Before release v0.4.0, devbox handled environment variables differently
depending on the command:

- `devbox shell` would start with a clean environment. Nothing was
   inherited from the parent shell (but shellrc files would still be
   sourced)
- `devbox shell -- cmd` would inherit the environment.
- `devbox run` would inherit the environment.

With v0.4.0, all commands adopted the `devbox shell` behavior of
starting with a clean environment. However, this broke some existing
users and the workaround of manually specifying environment variables
to inherit would be cumbersome. As discussed, this change flips the
behavior such that all commands will now inherit their parent shell's
environment. Eventually there will be a flag to enable an "isolated"
mode to restore the previous behavior.

## Changes to `devbox shell` and `devbox run` environments

Devbox environments are now built with environment variables from the
following steps:

1. Variables exported in the user's `~/.bashrc`, `~/.zshrc`, etc.
2. Variables from the current environment except for PWD, OLDPWD, SHLVL,
   and SHELL.
3. Variables from the `nix print-dev-env` environment except for
   BASHOPTS, HOME, NIX_BUILD_TOP, NIX_ENFORCE_PURITY, NIX_LOG_FD,
   NIX_REMOTE, PPID, SHELL, SHELLOPTS, TEMP, TEMPDIR, TERM, TMP, TMPDIR,
   TZ, and UID. The SSL_CERT_FILE variable is also ignored if it's set
   to `/no-cert-file.crt`.
4. Variables from Devbox plugins.
5. PATH is set to the concatenation of the PATHs from step 4, step 3,
   and step 2 (in that order). The PATH from the user's init files in
   step 1 is overwritten. The final PATH is cleaned and deduplicated.
5. HISTFILE is set to a Devbox project-specific file.
6. PS1 is set to "(devbox) $PS1"

Each step adds to the previous step, overwriting any keys that already
exist.

## Bug fixes and improvements

- As suggested by @ipince, `shellrc.tmpl` simply sources the user's
  rcfile instead of copying it.
- The result of `PrintEnv` now escapes all environment variable values.
  This means that values such as `FOO=$(echo bar)` will become
  `FOO="\$(echo bar)"` instead of being evaluated by the shell. Allowing
  variables and command substitution would require implementing a full
  parser to determine which characters to escape.
- Some unquoted variables in `shellrc.tmpl` are now quoted so that
  devbox directories with spaces work.
- The `cd` commands in `shellrc.tmpl` exit on failure to prevent running
  further commands in the wrong directory.
Copy link
Collaborator

@savil savil left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

awesome! This is clean and easy to understand.

The result of PrintEnv now escapes all environment variable values.

I feel I'm missing something obvious. Could you explain the motivation for this again? perhaps an example may help.

Comment on lines +1042 to +1045
// Devbox may change the working directory of the shell, so using the
// original PWD and OLDPWD would be wrong.
"PWD": true,
"OLDPWD": true,
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

what scenario leads to working directory changing?

I thought doing devbox --config=/path/to/config shell would still start you in the current directory but using the devbox.json of that config path. But maybe there's some other scenario I'm not considering.

Copy link
Collaborator Author

@gcurtis gcurtis Mar 1, 2023

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think it's primarily plugin + init hooks that always run from the devbox.json directory. So if you're in devbox/internal and run devbox shell, the shell changes to the parent directory, runs the init hooks, then switches back.

if !seen[path] {
cleaned = append(cleaned, path)
}
seen[path] = true
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

below: filepath.Join(cleaned...)

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Are you referring to line 638? That's joining on the list separator (:) instead of the path separator.

@gcurtis
Copy link
Collaborator Author

gcurtis commented Mar 1, 2023

@savil the original motivation was to properly handle variables with special characters. We were previously doing fmt.Sprintf("export %s=%q", k, v) to escape quotes, but that can change the value of a variable if you don't escape the other special characters. For example, if you have:

FOO="$(ls "dir 01")" # lists a directory named `dir 01`

and then you only escape quotes, you get:

FOO="$(ls \"dir 01\")" # lists a directory named `"dir 01"`

Because quotes within a command substitution shouldn't be escaped, we'd need to implement a parser. Instead, if we just say all env var values need to be literals then it becomes easy. We just do:

FOO="\$(ls \"dir 01\")" # doesn't list anything, value is `$(ls "dir 01")`

@gcurtis gcurtis merged commit 0d0374e into main Mar 2, 2023
@gcurtis gcurtis deleted the gcurtis/impure-shell branch March 2, 2023 20:25
ipince added a commit that referenced this pull request Mar 6, 2023
@ipince ipince mentioned this pull request Mar 6, 2023
ipince added a commit that referenced this pull request Mar 6, 2023
## Summary
* Applies the same changes from #706 to the fish shellrc.
* Ignores the `_` variable. It's read-only and `fish` complains if we
try to write it.
* Added a note in `shellrc` so we remember do edit `shellrc_fish` too.
If we had tests for fish we could remove this silly note.
* Alphabetized vars so the generated shellrc is a bit cleaner (imo).

Fixes #719 

## How was it tested?
```
SHELL=fish ./devbox shell
```

I'll add some shellrc_fish tests in a future PR
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Development

Successfully merging this pull request may close these issues.

None yet

2 participants