Direnv .envrc Boilerplate for .env creation and validation

Death to pet .env! Your configuration variables deserve proper local management during refactoring. I contributed my best practices for a .envrc boilerplate to the direnv Wiki, but I will augment the background and add bonus tips.

I created a direnv = https://direnv.net .envrc boilerplate. I wanted to share my opinionated, current “best” practices with the community, because it adds maintainability and quality to direnv use.

Direnv Background

I can’t live without direnv; I’ve used it for years to manage scripts, configuration management, and containers with Git for local development. I refactor hard-coded pet variables out of my prototypes and example code, gists, code copied from the Internet, etc. with this essential tool to achieve the 12 Factor App (and operations). Please see the resulting direnv Wiki .envrc boilerplate page for the code with rationale, justification, and procedure.

I didn’t want to make it too personal or extensive: just enough to be effective. In the boilerplate code, there is a vestige of a draft shell-command post I haven’t completed. I had reviewed the SaaS and enhanced services that can arguably manage .env files with secrets better for collaboration with RBAC, etc. versus Password vaults, etc. It doesn’t keep local development independent, but I’ll save my thoughts for later, it augments Why You Want Google Authenticator, but Don’t Need It.

For local development, I frequently bootstrap direnv and ghq into systems which do not have it available via package management (or CPU architecture) with asdf = Multiple Runtime Version Manager because it is lightweight compared to HomeBrew.sh for Linux and Mac. I will reference that later, when and if I ever make my homelab project public after more refactoring!

Bonus .envrc Boilerplate

I wanted to document optional enhancements to the .envrc boilerplate:

Message of the Day

I often leave notes, reminders of research and reference links, and next steps or goals in the appropriate project directory with Here Documents. The following sets literal output without variable substitution on purpose, I relax that when needed.

cat <<- 'MotD'
  Canonical: `homelab/server/bootstrap/.envrc`
MotD

The above is useful when I document new procedures before refactoring them to scripts, functions, playbooks, etc. and it is a form of literate programming. However, refactoring often leads to the next section I add to .envrc.

Auto Start a Program

Remember Windows AUTORUN.INF? It was a source of convenience to start an app when inserting removable media (such as a CD-ROM, USB thumb drive, etc.), but also a security compromise vector for Trojan Horses. Let’s reproduce, but in a controllable, defeatable manner:

if [[ -n ${AUTOSTART_PAUSE} ]]; then
  _keypress=''

  echo "|AUTOSTART|PREVIEW|${AUTOSTART}"
  echo -n "|AUTOSTART|PAUSE|${AUTOSTART_PAUSE} seconds; Press any key to cancel, [Enter] or [Space] to continue"
  # https://unix.stackexchange.com/questions/293940/how-can-i-make-press-any-key-to-continue
  # https://stackoverflow.com/questions/36056421/press-any-key-with-timeout-progress-displayed-in-shell
  for _ in $(seq "${AUTOSTART_PAUSE}"); do
    if ! read -rs -n1 -t1 _keypress; then
      echo -n "."
    else
      break
    fi
  done

  echo

  if [[ -z ${_keypress} ]]; then
    echo -e "|AUTOSTART| INIT|"
    eval "${AUTOSTART}"
  else
    echo "|AUTOSTART|ABORT|"
  fi

  unset _keypress
fi

where .env.sample might contain:

AUTOSTART='make config || ./build.sh'
AUTOSTART_PAUSE=3

For the above, I adapted https://unix.stackexchange.com/questions/293940/how-can-i-make-press-any-key-to-continue and https://stackoverflow.com/questions/36056421/press-any-key-with-timeout-progress-displayed-in-shell to replace a ^Cancel prompt which had aborted direnv execution and environment variable population.

Init.envrc Bash function

A Bash function (versus a one-line alias) to bootstrap my canonical .envrc boilerplate. Copy it to the current directory and enable; if no .env.sample exists, instantiate a basic example.

$ type init.envrc
init.envrc is a function
init.envrc ()
{
    cp "${GHQ_ROOT-${HOME}/Documents}/gitlab.com/mlavi/homelab/server/bootstrap/.envrc" .

    if [[ ! -f .env.sample ]]; then
      echo -e '# init\nAUTOSTART="echo AUTOSTART"' > .env.sample
    fi
    direnv status && echo && direnv allow
}

Final Thoughts

For a small boilerplate code snippet, there are big implications on the quality of direnv implementation. There is room to improve:

  • Watching for .env.sample changes to trigger direnv reload
  • Documenting .env.sample.environment-or-application modularity, concatenation to .env or perhaps automatic, conditional inclusion of those extended variable files. i.e.: filter on a variable prefix: SAMBA_* for inclusion.
  • Refactoring from .envrc static code blocks to direnv extensions:
    • Git stanza conditionally driven by GIT_REPO_UPSTREAM_URL to add:
      • tracking remote with fetch/merge?
      • conditional: gh {pr,issues} list?
    • .env recreation+validation from .env.sample*
    • AUTOSTART
    • conditional: bat README.md ||cat "${_}" vs. MotD
Tags// , , ,