Updated shared.nix to enhance domain parameter propagation and modified configuration.nix to utilize the inherited domain for machine-specific setups. Adjusted example-service.nix to accept the domain as an argument, improving modularity. Additionally, added a new documentation file explaining the LNBits flake deployment process, detailing architecture, key components, and deployment instructions for better onboarding and understanding of the system.
8.2 KiB
How the LNBits Flake Works
Overview
This document explains how the LNBits flake deployment works, particularly how it achieves the equivalent of running uv run lnbits on the deployed NixOS machine.
Architecture
The LNBits flake uses uv2nix to convert uv's lock file into a reproducible Nix build, creating a Python virtual environment that can be deployed as a NixOS service.
Key Components
1. uv2nix: Converting uv.lock to Nix
The flake uses uv2nix to read the uv.lock file and create a reproducible Nix build:
# Read uv.lock and pyproject.toml
workspace = uv2nix.lib.workspace.loadWorkspace { workspaceRoot = ./.; };
# Create overlay preferring wheels (faster than building from source)
uvLockedOverlay = workspace.mkPyprojectOverlay { sourcePreference = "wheel"; };
This converts the uv-managed dependencies into Nix packages.
2. Building a Python Virtual Environment
Instead of uv creating a venv at runtime, Nix creates one during the build:
# Build venv with all dependencies from uv.lock
runtimeVenv = pythonSet.mkVirtualEnv "${projectName}-env" workspace.deps.default;
This creates an immutable virtual environment in /nix/store/...-lnbits-env with all Python packages installed from the locked dependencies.
3. The Wrapper Script (Equivalent to uv run)
The flake creates a wrapper that mimics uv run lnbits:
lnbitsApp = pkgs.writeShellApplication {
name = "lnbits";
text = ''
export SSL_CERT_FILE=${pkgs.cacert}/etc/ssl/certs/ca-bundle.crt
export REQUESTS_CA_BUNDLE=$SSL_CERT_FILE
export PYTHONPATH="$PWD:${PYTHONPATH:-}"
exec ${runtimeVenv}/bin/lnbits "$@"
'';
};
This wrapper:
- Sets up SSL certificates for HTTPS requests
- Adds the current directory to
PYTHONPATH(so it can find local source files) - Executes the
lnbitsbinary from the built venv
4. The NixOS Service Module
The service module (lnbits-service.nix) configures systemd to run LNBits:
# The actual command that runs
ExecStart = "${lib.getExe cfg.package} --port ${toString cfg.port} --host ${cfg.host}";
# Environment variables
environment = {
LNBITS_DATA_FOLDER = "${cfg.stateDir}"; # /var/lib/lnbits
LNBITS_EXTENSIONS_PATH = "${cfg.stateDir}/extensions";
LNBITS_PATH = "${cfg.package.src}"; # Points to source
}
Key points:
cfg.package= the venv from the flake (runtimeVenv)lib.getExe cfg.package= extracts the executable path:/nix/store/xxx-lnbits-env/bin/lnbitscfg.package.src= points back to the LNBits source directory for templates/static files
5. Flake Outputs
The flake exposes the venv as a package:
packages.default = runtimeVenv;
packages.${projectName} = runtimeVenv; # packages.lnbits = runtimeVenv
How Your Deployment Uses It
In your config/lnbits.nix:
package = (builtins.getFlake "path:/var/src/lnbits-src").packages.${pkgs.system}.lnbits;
This breaks down as:
builtins.getFlake "path:/var/src/lnbits-src"- Loads the flake from the deployed source.packages- Accesses the packages output from the flake.${pkgs.system}- Selects the right system architecture (e.g.,x86_64-linux).lnbits- Gets thelnbitspackage (which equalsruntimeVenv)
Understanding Flake References
The flake reference format is crucial to understanding how this works:
Local Path Reference
builtins.getFlake "path:/var/src/lnbits-src"
- Uses files from the local filesystem at
/var/src/lnbits-src - The
.srcattribute points to/var/src/lnbits-src - Files are mutable - you can edit them
- Requires deploying the full source tree via krops
GitHub Reference
builtins.getFlake "github:lnbits/lnbits/main"
- Nix fetches the repository from GitHub
- Stores it in
/nix/store/xxx-source/(read-only) - The
.srcattribute points to/nix/store/xxx-source - Files are immutable
- No need to deploy source separately
Comparison
| Aspect | path:/var/src/lnbits-src |
github:lnbits/lnbits |
|---|---|---|
| Source location | /var/src/lnbits-src |
/nix/store/xxx-source |
| Mutable? | Yes - can edit files | No - read-only |
| Deployment | Deploy via krops | Built-in to Nix |
| Updates | Redeploy source | Change flake ref |
| Local changes | Supported | Not possible |
Why Deploy the Full Source?
The entire lnbits folder must be copied to /var/src/lnbits-src because:
1. Build Time Requirements
The flake needs these files to build the venv:
flake.nix- Defines how to build the venvuv.lock- Contains locked dependency versionspyproject.toml- Defines project metadata
2. Runtime Requirements
LNBits needs the source tree at runtime for:
- Python modules in
lnbits/ - HTML templates
- Static files (CSS, JavaScript, images)
- Extension loading system
Directory Structure
On the Deployed Machine
/var/src/lnbits-src/ ← Full source deployed by krops
├── flake.nix ← Used to build venv
├── uv.lock ← Used to build venv
├── pyproject.toml ← Used to build venv
└── lnbits/ ← Used at runtime
├── templates/
├── static/
└── ...
/nix/store/xxx-lnbits-env/ ← Built venv (Python packages only)
├── bin/lnbits ← Executable
└── lib/python3.12/... ← Dependencies
At Runtime
The systemd service:
- Runs:
/nix/store/xxx-lnbits-env/bin/lnbits - With:
LNBITS_PATH=/var/src/lnbits-src(to find templates/static/etc) - With:
WorkingDirectory=/var/src/lnbits-src
Comparison: uv run vs Nix Flake
Traditional uv run lnbits
cd /path/to/lnbits
uv run lnbits --port 5000 --host 0.0.0.0
This:
- Reads
uv.lock - Creates/updates a venv in
.venv/ - Installs dependencies if needed
- Runs
lnbitsfrom the venv - Uses current directory for source files
Nix Flake Approach
package = (builtins.getFlake "path:/var/src/lnbits-src").packages.${pkgs.system}.lnbits;
This:
- ✅ Reads
uv.lockviauv2nix - ✅ Creates a venv in
/nix/store(immutable) - ✅ All dependencies are locked and reproducible
- ✅ Runs
/nix/store/xxx-lnbits-env/bin/lnbits - ✅ Sets
LNBITS_PATHto source directory for templates/static/extensions - ✅ Runs as a systemd service with proper user/permissions
- ✅ No runtime dependency on
uvitself
Key Differences
| Aspect | uv run |
Nix Flake |
|---|---|---|
| Venv location | .venv/ in source |
/nix/store/xxx-env |
| Mutability | Mutable | Immutable |
| Reproducibility | Lock file only | Full Nix derivation |
| Service management | Manual | systemd integration |
| Dependency on uv | Required at runtime | Only at build time |
The .src Attribute Mystery
A common question: where is cfg.package.src defined?
Answer: It's Automatic
The .src attribute is not explicitly defined - it's automatically set by Nix when loading a flake:
# When you do this:
builtins.getFlake "path:/var/src/lnbits-src"
# Nix automatically:
# 1. Reads the flake at /var/src/lnbits-src
# 2. Evaluates it and builds outputs
# 3. Adds .src = /var/src/lnbits-src to the package
This is a built-in Nix flake feature - packages inherit the source location from where the flake was loaded.
Summary
The LNBits flake deployment:
- Converts uv dependencies to Nix using
uv2nix - Builds an immutable venv in
/nix/store - Deploys full source to
/var/src/lnbits-srcvia krops - Loads the flake from the deployed source using
path:/var/src/lnbits-src - Runs as a systemd service with proper environment variables pointing to the source
This provides:
- ✅ Reproducibility - exact same dependencies every time
- ✅ Declarative configuration - everything in
configuration.nix - ✅ Source mutability - can edit files in
/var/src/lnbits-src - ✅ No uv dependency - service doesn't need
uvat runtime - ✅ Proper service management - systemd integration with user permissions
The key insight is that path: vs github: in the flake reference determines whether you use local deployed files or Nix fetches from a remote repository.