Development Environment: Winter 2024 Edition

Development Environment: Winter 2024 Edition

blackglasses
blackglasses

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.

/assets/2775902727.webp

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:

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.

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.