Created
April 10, 2025 01:52
-
-
Save tomdavidson/376ab4a655e1e835c201389c135d500e to your computer and use it in GitHub Desktop.
devenv.nix with systemd-nspawn container backed shell
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
{ pkgs, lib, config, ... }: { | |
# Basic devenv configuration - add your project packages here | |
packages = [ | |
pkgs.systemd | |
pkgs.debootstrap | |
# Add other packages your project needs | |
]; | |
# Define container settings with automatic unique naming | |
env = { | |
CONTAINER_NAME = let | |
# Get the base directory name for unique container naming | |
dirName = builtins.baseNameOf (builtins.toString ./.); | |
in "devenv-${dirName}"; | |
CONTAINER_PATH = "/var/lib/machines/${config.env.CONTAINER_NAME}"; | |
}; | |
# Tasks to setup and manage the container | |
tasks = { | |
# Task to create the container if it doesn't exist | |
"container:setup" = { | |
exec = '' | |
if [ ! -d "${config.env.CONTAINER_PATH}" ]; then | |
echo "Setting up container filesystem at ${config.env.CONTAINER_PATH}..." | |
sudo mkdir -p "${config.env.CONTAINER_PATH}" | |
sudo debootstrap --include=systemd,dbus bullseye "${config.env.CONTAINER_PATH}" | |
# Create a .nspawn file to configure the container | |
cat << EOF | sudo tee /etc/systemd/nspawn/${config.env.CONTAINER_NAME}.nspawn | |
[Exec] | |
Environment=DEVENV_ROOT=${config.env.DEVENV_ROOT} | |
Environment=DEVENV_PROFILE=${config.env.DEVENV_PROFILE} | |
Environment=PATH=${config.env.DEVENV_PROFILE}/bin:/bin:/usr/bin | |
[Network] | |
VirtualEthernet=yes | |
[Files] | |
Bind=/nix/store:/nix/store | |
EOF | |
else | |
echo "Container already exists at ${config.env.CONTAINER_PATH}" | |
fi | |
''; | |
onEnter = true; | |
}; | |
# Task to propagate environment variables file into the container | |
"container:env-update" = { | |
exec = '' | |
# Create a script that will source all environment variables in the container | |
ENV_SCRIPT="${config.env.CONTAINER_PATH}/etc/profile.d/devenv-vars.sh" | |
echo "#!/bin/bash" | sudo tee $ENV_SCRIPT | |
env | grep -v "^_" | grep -v "SHELL=" | grep -v "SHLVL=" | grep -v "OLDPWD=" | while read -r line; do | |
echo "export $line" | sudo tee -a $ENV_SCRIPT | |
done | |
sudo chmod +x $ENV_SCRIPT | |
''; | |
after = ["container:setup"]; | |
onEnter = true; | |
}; | |
}; | |
# Define the enter shell hook that will launch the container | |
enterShell = '' | |
if [ -z "$INSIDE_CONTAINER" ]; then | |
# Run the container setup tasks | |
devenv tasks run container:setup | |
devenv tasks run container:env-update | |
# Launch the container with our project directory mounted | |
export INSIDE_CONTAINER=1 | |
echo "Launching development container..." | |
# Important - save original directory to mount inside container | |
PROJECT_DIR="$(pwd)" | |
# Use exec to replace the current shell with the container shell | |
exec sudo systemd-nspawn \ | |
--directory="${config.env.CONTAINER_PATH}" \ | |
--bind="$PROJECT_DIR:/project" \ | |
--bind="/nix/store:/nix/store" \ | |
--setenv=INSIDE_CONTAINER=1 \ | |
--setenv=DEVENV_ROOT=${config.env.DEVENV_ROOT} \ | |
--setenv=DEVENV_PROFILE=${config.env.DEVENV_PROFILE} \ | |
--setenv=PATH=${config.env.DEVENV_PROFILE}/bin:/bin:/usr/bin \ | |
--chdir=/project \ | |
/bin/bash -l | |
# This won't be reached due to exec | |
exit 0 | |
else | |
echo "Already inside container environment" | |
fi | |
''; | |
# Define a custom prompt to indicate when we're inside the container | |
scripts.container-prompt.exec = '' | |
if [ ! -z "$INSIDE_CONTAINER" ]; then | |
export PS1="\[\e[0;92m\][container]\[\e[0m\] $PS1" | |
fi | |
''; | |
# Source the custom prompt script when entering the shell | |
enterShell = lib.mkBefore '' | |
. ${config.scripts.container-prompt.path} | |
''; | |
# Add any other configuration you need for your project | |
# These will be available in the parent devenv and can be accessed in the container | |
# through the propagated environment variables and mounted directories | |
# Example language configuration | |
languages.rust.enable = true; | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment