An easy-to-host PDS on the ATProtocol, iPhone and MacOS. Maintain control of your keys and data, always.
1
fork

Configure Feed

Select the types of activity you want to include in your feed.

MM-64: Nix Flake + devenv Development Shell#

Summary#

MM-64 establishes a reproducible development environment for the ezpds Rust workspace using Nix and devenv. The goal is a single command — nix develop — that drops any contributor into a shell with the exact same Rust stable toolchain, language tooling (rust-analyzer, clippy, rustfmt), build utilities (just, cargo-audit), and SQLite libraries, regardless of what is already installed on their machine.

The implementation is intentionally narrow: three configuration files (flake.nix, devenv.nix, rust-toolchain.toml) and two companion files (.envrc, flake.lock) at the repo root. rust-toolchain.toml serves as the single source of truth for the Rust version, making it readable both by devenv inside Nix and by rustup outside it — so contributors who do not use Nix still get the correct toolchain automatically. A Cachix binary cache is wired in via nixConfig to avoid 20+ minute cold builds. Build derivations and NixOS system modules are explicitly out of scope; this ticket delivers only the dev shell.

Definition of Done#

A flake.nix at the repo root wires devenv into the Nix flake system. A devenv.nix configures the development shell. Running nix develop drops contributors into a shell with the Rust stable toolchain (rustc, cargo, rust-analyzer, clippy, rustfmt), SQLite dev libraries, just, and cargo-audit. A rust-toolchain.toml pins the toolchain version in a rustup-compatible way. A .envrc with use flake is committed for direnv users. A flake.lock is committed. Build outputs and NixOS modules are explicitly out of scope.

Acceptance Criteria#

MM-64.AC1: Dev shell activates#

  • MM-64.AC1.1 Success: nix develop completes without error on macOS (aarch64-darwin or x86_64-darwin)
  • MM-64.AC1.2 Success: nix develop completes without error on Linux (x86_64-linux)

MM-64.AC2: Required tools are present in the shell#

  • MM-64.AC2.1 Success: rustc --version outputs a stable Rust release (not nightly, not beta)
  • MM-64.AC2.2 Success: cargo --version is available
  • MM-64.AC2.3 Success: rust-analyzer --version is available
  • MM-64.AC2.4 Success: cargo clippy --version is available
  • MM-64.AC2.5 Success: rustfmt --version is available
  • MM-64.AC2.6 Success: just --version is available
  • MM-64.AC2.7 Success: cargo audit --version is available
  • MM-64.AC2.8 Success: sqlite3 --version is available and SQLite dev headers are accessible via pkg-config

MM-64.AC3: Rust version is governed by rust-toolchain.toml#

  • MM-64.AC3.1 Success: The Rust version inside the shell matches the stable channel as specified in rust-toolchain.toml
  • MM-64.AC3.2 Success: rustup on the same machine reads rust-toolchain.toml and resolves to the same Rust version without Nix

MM-64.AC4: direnv integration is committed#

  • MM-64.AC4.1 Success: .envrc contains use flake and is tracked by git (git ls-files .envrc returns it)
  • MM-64.AC4.2 Success: direnv allow in repo root activates the shell automatically on cd

MM-64.AC5: flake.lock is committed#

  • MM-64.AC5.1 Success: flake.lock exists at repo root and is tracked by git

MM-64.AC6: Scope boundaries#

  • MM-64.AC6.1 Negative: flake.nix defines no packages output (no build derivations)
  • MM-64.AC6.2 Negative: flake.nix defines no nixosModules output

Glossary#

  • Nix: A purely functional package manager that builds software in isolated, reproducible environments. Packages are described as expressions; builds are hermetic and content-addressed.
  • Nix flake: A standardized Nix project format. A flake.nix file declares named inputs (dependencies) and outputs (shells, packages, modules). A corresponding flake.lock pins every input to an exact content hash.
  • flake.lock: An auto-generated lockfile that records the exact revision and hash of every flake input. Committing it ensures every contributor resolves the same dependency versions.
  • devenv: A Nix-based tool (from Cachix) for declaring development shell environments. It wraps lower-level Nix plumbing and provides higher-level configuration options like languages.rust.
  • devShell: A Nix flake output type representing a shell environment (nix develop enters it). This ticket produces only a devShells.default output — no packages or nixosModules.
  • nixpkgs: The main package repository for Nix, containing tens of thousands of software packages. devenv uses its own curated fork (devenv-nixpkgs/rolling) for compatibility.
  • rust-overlay: A Nix overlay (provided by oxalica/rust-overlay, used internally by devenv) that surfaces rustup toolchain files as Nix derivations. It is how devenv reads rust-toolchain.toml.
  • rust-toolchain.toml: A rustup-standard file that pins a Rust channel, version, components, and targets. Both rustup and rust-overlay/devenv read this file, making it the single source of truth in this design.
  • rustup: The official Rust toolchain installer and version manager. Reads rust-toolchain.toml natively; relevant here because non-Nix contributors rely on it directly.
  • rust-analyzer: The official Rust language server, used by editors for inline type hints, error checking, and code navigation.
  • clippy: The official Rust linter (cargo clippy). Catches common correctness and style issues beyond what the compiler reports.
  • rustfmt: The official Rust code formatter (rustfmt/cargo fmt).
  • just: A command runner (similar in purpose to make) used in this project to define project-level task shortcuts.
  • cargo-audit: A Cargo subcommand that checks Cargo.lock against the RustSec advisory database for known security vulnerabilities in dependencies.
  • SQLite dev libraries: The C library and header files for SQLite. Required at build time for Rust crates that link against libsqlite3 (e.g., rusqlite). pkg-config is used by the Rust build system to locate them.
  • pkg-config: A system tool that reports compiler and linker flags for installed C libraries. Cargo uses it to find native dependencies like SQLite.
  • direnv: A shell extension that automatically loads (and unloads) environment variables when you cd into a directory. The .envrc file in a project root contains the hook; direnv allow opts the directory in.
  • .envrc: The configuration file read by direnv. In this design it contains a single line, use flake, which tells direnv to activate the Nix dev shell automatically.
  • Cachix: A binary cache hosting service for Nix. Pre-built Nix derivations are fetched from Cachix instead of being compiled locally, dramatically reducing cold-start time.
  • substituter: Nix terminology for a binary cache source. The nixConfig.extra-substituters field in flake.nix registers Cachix as a substituter so Nix fetches pre-built outputs rather than building from source.
  • nix-systems: A small flake (nix-systems/default) that provides a canonical list of supported system architectures (e.g., aarch64-darwin, x86_64-linux). Used here in place of a heavier flake framework like flake-parts.
  • aarch64-darwin / x86_64-darwin / x86_64-linux: Nix system identifiers. aarch64-darwin = Apple Silicon Mac, x86_64-darwin = Intel Mac, x86_64-linux = 64-bit Linux. Acceptance criteria require the shell to work on all three.
  • devenv.local.nix: An optional, git-ignored override file that devenv merges with devenv.nix. Intended for contributor-local machine-specific configuration.

Architecture#

Minimal Nix flake with devenv as the sole output. Three configuration files govern the dev shell; a fourth enables automatic activation via direnv.

repo root/
├── flake.nix          # Nix entry point — wires devenv, sets Cachix substituter
├── devenv.nix         # Dev shell config — Rust toolchain + packages
├── rust-toolchain.toml # Toolchain pin — read by both devenv and rustup
├── .envrc             # Direnv hook — `use flake`
└── flake.lock         # Input pins — generated by nix develop, committed

flake.nix has three inputs: devenv-nixpkgs/rolling (devenv's curated nixpkgs fork), cachix/devenv, and nix-systems/default (provides the default system list for multi-arch support without requiring flake-parts). The nixConfig block configures Cachix as a binary cache substituter — without it, the first nix develop run builds devenv's dependencies from source, which can take 20+ minutes.

devenv.nix uses languages.rust.toolchainFile = ./rust-toolchain.toml to wire in the toolchain. devenv does not auto-detect rust-toolchain.toml; the explicit path is required. Extra packages (just, cargo-audit, sqlite) go in the packages list alongside the Rust config.

rust-toolchain.toml is the single source of truth for the Rust version. devenv reads it via rust-overlay's fromRustupToolchainFile. rustup also reads it natively, so contributors not using Nix get the same toolchain.

Existing Patterns#

No prior Nix files exist in the repository. This is a greenfield setup.

The workspace already has a .gitignore with Rust and editor exclusions. This design appends Nix-specific entries (.devenv/, .direnv/, devenv.local.nix) to the existing file rather than replacing it.

Implementation Phases#

Phase 1: Write configuration files#

Goal: Produce all static configuration files for the dev shell.

Components:

  • flake.nix — three-input minimal flake with Cachix nixConfig and devenv.lib.mkShell for devShells.default
  • devenv.nixlanguages.rust.toolchainFile = ./rust-toolchain.toml, packages = [pkgs.just pkgs.cargo-audit pkgs.sqlite pkgs.pkg-config], env.LIBSQLITE3_SYS_USE_PKG_CONFIG = "1" (pkg-config required explicitly; languages.rust does not include it automatically)
  • rust-toolchain.tomlchannel = "stable", components = ["rustfmt" "clippy" "rust-analyzer"], targets = ["aarch64-apple-darwin" "x86_64-unknown-linux-gnu"]
  • .envrc — single line: use flake
  • .gitignore — append .devenv/, .direnv/, devenv.local.nix

Dependencies: None (first phase)

Done when: All five files exist at repo root with correct contents; .gitignore includes the three Nix entries

Phase 2: Generate flake.lock and commit#

Goal: Run nix develop to resolve and pin all flake inputs, then commit everything.

Components:

  • flake.lock — generated by running nix develop for the first time
  • Git commit — all six files (flake.nix, devenv.nix, rust-toolchain.toml, .envrc, .gitignore, flake.lock) committed together

Dependencies: Phase 1 (all configuration files present)

Done when: nix develop exits successfully; flake.lock exists and is committed; all required tools (rustc, cargo, rust-analyzer, clippy, rustfmt, just, cargo-audit, sqlite3) are available inside the shell

Additional Considerations#

Non-Nix contributors: rust-toolchain.toml is read by rustup directly. Contributors who install Rust via rustup outside Nix will automatically get the same stable channel and components.

Cachix key rotation: The extra-trusted-public-keys value in flake.nix is devenv's current public key. If Cachix rotates this key, the first nix develop after rotation will fall back to building from source. Check devenv's docs for the updated key when upgrading devenv.

devenv.local.nix: Excluded from .gitignore by convention. Contributors can use this file for machine-specific overrides without affecting the shared config.