Skip to content

Instantly share code, notes, and snippets.

@tomdavidson
Created April 10, 2025 01:52
Show Gist options
  • Save tomdavidson/376ab4a655e1e835c201389c135d500e to your computer and use it in GitHub Desktop.
Save tomdavidson/376ab4a655e1e835c201389c135d500e to your computer and use it in GitHub Desktop.
devenv.nix with systemd-nspawn container backed shell
{ 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