Development Environment: Winter 2024 Edition
Introduction
In this article, I provide a breakdown of my development environment based on Nix and Home Manager. I discuss its efficient software management, cross-platform support, and version control capabilities. I walk you through the use of Nix and Home Manager for software configuration, and the setup and management of elements like environment variables, user applications, and dotfiles.
I also show how to enhance basic commands, source code environments, and other features with specific tools.
If you’re interested in my dotfiles
or neovim
configurations:
- dotfiles.nix (Nix + Home Manager)
- thealtf4stream.nvim (Neovim)
Nix + Home Manager
Nix and Home Manager form the backbone of my development environment. Nix is a package manager that provides a reproducible and portable package management system. I use a flake.nix
file which serves as a declarative build configuration, managing all dependencies, irrespective of whether they originate from nixpkgs
or other sources.
Home Manager is utilized to manage software configurations in a user’s home directory. By offering a declarative format for home environment configurations, it eases both management and version control. This significantly simplifies the process of updating my development environment by handling all packages, Neovim plugins, and other dependencies.
{
description = "Flake supporting NixOS and Darwin using Home Manager.";
inputs = {
nixpkgs.url = "github:NixOS/nixpkgs/nixos-23.11";
darwin.url = "github:lnl7/nix-darwin";
home-manager.url = "github:nix-community/home-manager/release-23.11";
};
outputs = { self, nixpkgs, home-manager, darwin, ... }:
let
system = { pkgs, ... }: {
imports = [ ./home.nix ];
};
in
{
nixosConfigurations = {
myNixos = nixpkgs.lib.nixosSystem {
system = "x86_64-linux";
modules = [ system ];
};
};
darwinConfigurations = {
myMac = darwin.lib.darwinSystem {
system = "x86_64-darwin";
modules = [ system ];
};
};
};
}
Cross-Platform Support
Nix is compatible with macOS and Linux environments. This is made possible by using nixpkgs.lib.nixosSystem
or darwin.lib.darwinSystem
to configure Nix environments, including Home Manager.
For configuring a Nix environment on a Linux system, you would use:
nixosConfigurations = {
myNixos = nixpkgs.lib.nixosSystem {
system = "x86_64-linux";
modules = [ system ];
};
};
For configuring a Nix environment on a macOS system, you would use:
darwinConfigurations = {
myMac = darwin.lib.darwinSystem {
system = "x86_64-darwin";
modules = [ system ];
};
};
Shared Configuration
Home Manager configurations are shared across both environments, streamlining the setup process and ensuring consistency.
{
# Define shared Home Manager configuration
homeManagerConfig = ./shared-home-manager-configuration.nix;
# System-specific configurations
nixosConfigurations = {
myNixos = nixpkgs.lib.nixosSystem {
system = "x86_64-linux";
modules = [ homeManagerConfig ];
};
};
darwinConfigurations = {
myMac = darwin.lib.darwinSystem {
system = "x86_64-darwin";
modules = [ homeManagerConfig ];
};
};
}
In this example, shared-home-manager-configuration.nix
is a file containing the shared Home Manager configuration. It is imported and used in both the NixOS and Darwin configurations.
Version Controlled
My development environment is cloned with all configurations supporting both macOS and NixOS. This includes a basic system configuration for native macOS functionality, and an advanced setup for NixOS that features i3-gaps and an optional headless mode for server use.
# Cloning a flake repository
git clone https://github.com/ALT-F4-LLC/dotfiles.nix.git
# Navigating into the cloned directory
cd dotfiles.nix
# On NixOS to switch to the latest version
nixos-rebuild switch --flake .#x86_64
# On macOS to switch to the latest version
darwin-rebuild switch --flake .#aarch64
User Applications
My development environment flake includes universal programs that are standalone and used across multiple projects or outside of projects. It’s designed to be as lightweight as possible, allowing myself to share programs or use different versions in project flakes.
{
home.packages = with pkgs; [
awscli2
cachix
doppler
fd
gh
jq
k9s
kubectl
lazydocker
ripgrep
z-lua
];
}
Environment Variables
My setup also involves the management of environment variables for shell:
{
home.sessionVariables = {
EDITOR = "nvim";
PATH = "$PATH:$GOPATH/bin";
};
}
Additional Settings
Home Manager allows you to manage a variety of settings in addition to user applications and environment variables.
Dotfiles: Home Manager can manage your dotfiles, ensuring they’re kept up-to-date and in sync across multiple machines:
{
home.file = {
".zshrc".source = ~/dotfiles/.zshrc;
".tmux.conf".source = ~/dotfiles/.tmux.conf;
".vimrc".source = ~/dotfiles/.vimrc;
};
}
Shell Configuration: Home Manager can handle the configuration of various shells, such as bash, zsh, or fish. This includes managing shell aliases, shell scripts, and more:
{
programs.zsh = {
enable = true;
plugins = [ "autosuggestions" "syntax-highlighting" ];
shellAliases = {
ls = "ls --color=auto";
ll = "ls -l";
l = "ls -CF";
};
};
}
System Services: Home Manager can manage user-level system services, using systemd or other init systems:
{
services.user = {
enable = true;
services = {
myService = {
Description = "My user-level service";
Wants = [ "network-online.target" ];
After = [ "network-online.target" ];
ExecStart = "${pkgs.myPackage}/bin/my-command";
Restart = "on-failure";
};
};
};
}
SSH Configurations: Home Manager can manage your SSH configurations, including keys, known hosts, and ssh_config files:
{
programs.ssh = {
enable = true;
forwardX11 = true;
matchBlocks = {
"Host *" = {
hostname = "example.com";
user = "username";
port = 22;
identityFile = "~/.ssh/id_rsa";
};
};
knownHosts = {
"example.com" = {
hashed = true;
fingerprint = "ssh-ed25519 <fingerprint>";
};
};
};
}
Fonts: Home Manager can manage the installation and configuration of fonts.
{
home.packages = with pkgs; [
noto-fonts
];
fonts = {
enableFontDir = true;
enableGhostscriptFonts = true;
fonts = with pkgs; [
noto-fonts
];
};
}
Desktop Environment Settings: If you use a desktop environment, Home Manager can manage its settings. This includes settings for GNOME, KDE, Xfce, and others.
{
home = {
sessionVariables = {
XDG_CONFIG_HOME = "$HOME/.config";
};
programs.gnome = {
enable = true;
terminal = {
profileSettings = {
useThemeColors = false;
foregroundColor = "rgb(255,255,255)";
backgroundColor = "rgb(0,0,0)";
};
};
};
programs.kde = {
enable = true;
globalShortcuts.kwin = {
"Switch to Desktop 1" = "Meta+1";
"Switch to Desktop 2" = "Meta+2";
};
};
programs.xfce = {
enable = true;
terminal = {
colorScheme = "Tango";
font = {
name = "DejaVu Sans Mono";
size = 12;
};
};
};
};
}
User Cron Jobs: Home Manager can manage your cron jobs, ensuring they run on the schedule you specify.
{
services.user.cron = {
jobs = [
{
command = "echo 'This is a cron job' >> ~/cron.log";
period = "* * * * *";
description = "Example cron job that writes to a log file every minute";
}
];
};
}
Development Tools
An efficient development environment depends on the tools used. Here are some programs I use and the reasons for their importance.
Enhancing Basic Commands
Bat is a fantastic replacement for the ‘cat’ command. It’s customizable with themes, providing a more aesthetic and readable output.
# Using Bat to display a file with line numbers
bat -n file.txt
Bottom is a modern replacement for ‘top’ that provides an enhanced TUI based experience for system metrics.
# Using bottom to display system resource usage
btm
Source Code Environments
Nix-direnv, designed for nix flake environments, provides support for automatically loading and unloading devShells
in different flakes (similar to how direnv works). It also includes Zsh Integration and Nix Direnv integration for a seamless experience.
# Load the Direnv configuration for the current directory
echo "use flake" >> .envrc
# Auto load the flake development environment
direnv allow
For source code management I use Git. Among various customizations, GPG signing for all commits stands out, enabling a ‘verified’ status for all commits in GitHub:
Terminal
Kitty is my primary terminal customized with Geist Mono font and Oxocarbon ported themes which I enjoy using daily.
Note
I have been beta testing Ghostty on macOS however still use Kitty for all other platforms.
Git Workflows and Editing
Lazygit simplifies all primary git workflows with a simple navigable terminal user interface with panels and extensive functionality.
Neovim is my primary editor and includes a personal environment that drives daily editing across multiple languages. All language servers and plugins are maintained and updated via the Nix package manager (see configuration).
Note
It’s worth noting that Copilot is the only autocompletion I use in my editor, and all autocompletion is AI-driven.
Navigation and Prompt
NNN is used for navigation, which provides a full-featured terminal file manager which is tiny, nearly 0-config and incredibly fast.
Starship improves my prompt, with zsh integration enabled to make it more interactive.
Sessions and Shell Customization
Tmux offers multi-session management with overrides for TERM set depending on the platform.
Zsh, my default shell, is enhanced with autosuggestions, auto-completion, and plugins. It also has ‘shellAliases’ that significantly improve productivity:
shellAliases = {
cat = "bat";
dr = "docker container run --interactive --rm --tty";
lg = "lazygit";
ll =
if isDarwin
then "nnn"
else "nnn -P K";
nb = "nix build --json --no-link --print-build-logs";
s = ''doppler run --config "nixos" --project "$(whoami)"'';
wt = "git worktree"; # only use Git worktree
};
Conclusion
The right development environment is crucial to enhance productivity and efficiency. Each tool I use plays a distinct role in creating an environment conducive to robust, seamless, and efficient development.