From b953909e0a34d7246cd6cddb5d4914a16583de5a Mon Sep 17 00:00:00 2001 From: Dmitry Date: Wed, 26 Aug 2026 22:09:59 +0300 Subject: [PATCH] Add reproducible Nix dev environment Replace the Python venv with a Nix devshell pinning ansible-core 2.21.3, ansible-lint, yamllint and a Python with proxmoxer/requests. The Python dependencies share the interpreter that runs ansible, so pve-*.yml plays on implicit localhost can import proxmoxer without inventory changes. The shellHook exports absolute ANSIBLE_CONFIG, ANSIBLE_INVENTORY, ANSIBLE_ROLES_PATH and ANSIBLE_COLLECTIONS_PATH, so commands work from the repository root as well as from ansible/. Also un-ignore .envrc, which the global gitignore hides, and ignore the stray .ansible/ runtime directory. Co-Authored-By: Claude Opus 5 Claude-Session: https://claude.ai/code/session_01GTocXkGUUazHdKKd3r9k71 --- .envrc | 3 ++ .gitignore | 6 ++++ flake.lock | 27 +++++++++++++++++ flake.nix | 87 ++++++++++++++++++++++++++++++++++++++++++++++++++++++ 4 files changed, 123 insertions(+) create mode 100644 .envrc create mode 100644 flake.lock create mode 100644 flake.nix diff --git a/.envrc b/.envrc new file mode 100644 index 0000000..58e2de6 --- /dev/null +++ b/.envrc @@ -0,0 +1,3 @@ +# direnv: load the Nix dev shell defined in flake.nix +# Enable once per checkout with: direnv allow +use flake diff --git a/.gitignore b/.gitignore index 9ca00a3..6d5db94 100644 --- a/.gitignore +++ b/.gitignore @@ -20,3 +20,9 @@ __pycache__/ .direnv/ result result-* + +# direnv shell hook lives in the repo (global gitignore hides it) +!.envrc + +# Ansible local runtime dir (facts cache, locks) +.ansible/ diff --git a/flake.lock b/flake.lock new file mode 100644 index 0000000..16c1ef9 --- /dev/null +++ b/flake.lock @@ -0,0 +1,27 @@ +{ + "nodes": { + "nixpkgs": { + "locked": { + "lastModified": 1787498568, + "narHash": "sha256-9i/VTdusq/+NM/tz+J1Re+ojkMB8MBf0QshnYfzHz30=", + "owner": "NixOS", + "repo": "nixpkgs", + "rev": "56c02bc00adcf003215cc4bd996d6efaf4cff188", + "type": "github" + }, + "original": { + "owner": "NixOS", + "ref": "nixos-unstable", + "repo": "nixpkgs", + "type": "github" + } + }, + "root": { + "inputs": { + "nixpkgs": "nixpkgs" + } + } + }, + "root": "root", + "version": 7 +} diff --git a/flake.nix b/flake.nix new file mode 100644 index 0000000..40709e3 --- /dev/null +++ b/flake.nix @@ -0,0 +1,87 @@ +{ + description = "HomeLab infras — reproducible Ansible control-plane dev shell"; + + inputs = { + nixpkgs.url = "github:NixOS/nixpkgs/nixos-unstable"; + }; + + outputs = { self, nixpkgs }: + let + systems = [ "x86_64-linux" "aarch64-linux" ]; + forAllSystems = f: + nixpkgs.lib.genAttrs systems (system: f nixpkgs.legacyPackages.${system}); + in + { + devShells = forAllSystems (pkgs: + let + # One Python environment carrying ansible-core *and* the libraries the + # modules import in-process (community.proxmox needs proxmoxer + + # requests). Ansible sets ansible_python_interpreter for the implicit + # localhost to sys.executable, so `hosts: localhost` and + # `delegate_to: localhost` tasks pick these up automatically. + pythonEnv = pkgs.python3.withPackages (ps: with ps; [ + ansible-core + proxmoxer + requests + ]); + in + { + default = pkgs.mkShell { + name = "homelab-infras"; + + packages = [ + pythonEnv + pkgs.ansible-lint + pkgs.yamllint + pkgs.git + pkgs.jq + pkgs.openssh + pkgs.curl + pkgs.gnumake + ]; + + shellHook = '' + repo_root="$(${pkgs.git}/bin/git rev-parse --show-toplevel 2>/dev/null || pwd)" + ansible_dir="$repo_root/ansible" + + if [ -f "$ansible_dir/ansible.cfg" ]; then + # ansible.cfg spells its paths relative to ansible/, so pin them + # absolutely here — that makes the shell usable from the repo + # root as well as from inside ansible/. + export ANSIBLE_CONFIG="$ansible_dir/ansible.cfg" + export ANSIBLE_INVENTORY="$ansible_dir/inventory/hosts.yml" + export ANSIBLE_ROLES_PATH="$ansible_dir/roles" + export ANSIBLE_COLLECTIONS_PATH="$ansible_dir/collections" + else + echo "warn: $ansible_dir/ansible.cfg not found — run commands from ansible/" >&2 + fi + + # Banner goes to stderr so `nix develop --command ...` keeps a + # clean stdout for scripting. + { + _ver_ansible="$(ansible --version 2>/dev/null | head -n1 | tr -d '[]' | awk '{print $3}')" + _ver_lint="$(NO_COLOR=1 ansible-lint --version 2>/dev/null | tail -n1 | awk '{print $2}')" + _ver_yaml="$(yamllint --version 2>/dev/null | awk '{print $2}')" + _ver_py="$(python3 --version 2>&1 | awk '{print $2}')" + + echo "HomeLab infras dev shell" + echo " ansible-core $_ver_ansible" + echo " ansible-lint $_ver_lint" + echo " yamllint $_ver_yaml" + echo " python $_ver_py (proxmoxer, requests)" + echo "" + echo "ANSIBLE_CONFIG=$ANSIBLE_CONFIG" + echo "" + echo "Galaxy collections are NOT installed automatically. Run once:" + echo " ansible-galaxy collection install -r $ansible_dir/requirements.yml -p $ansible_dir/collections" + echo "" + + unset _ver_ansible _ver_lint _ver_yaml _ver_py + } >&2 + ''; + }; + }); + + formatter = forAllSystems (pkgs: pkgs.nixpkgs-fmt); + }; +}