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.
264 lines
8.2 KiB
Markdown
264 lines
8.2 KiB
Markdown
# 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:
|
|
|
|
```nix
|
|
# 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:
|
|
|
|
```nix
|
|
# 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`:
|
|
|
|
```nix
|
|
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 `lnbits` binary from the built venv
|
|
|
|
### 4. The NixOS Service Module
|
|
|
|
The service module (`lnbits-service.nix`) configures systemd to run LNBits:
|
|
|
|
```nix
|
|
# 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/lnbits`
|
|
- `cfg.package.src` = points back to the LNBits source directory for templates/static files
|
|
|
|
### 5. Flake Outputs
|
|
|
|
The flake exposes the venv as a package:
|
|
|
|
```nix
|
|
packages.default = runtimeVenv;
|
|
packages.${projectName} = runtimeVenv; # packages.lnbits = runtimeVenv
|
|
```
|
|
|
|
## How Your Deployment Uses It
|
|
|
|
In your `config/lnbits.nix`:
|
|
|
|
```nix
|
|
package = (builtins.getFlake "path:/var/src/lnbits-src").packages.${pkgs.system}.lnbits;
|
|
```
|
|
|
|
This breaks down as:
|
|
|
|
1. `builtins.getFlake "path:/var/src/lnbits-src"` - Loads the flake from the deployed source
|
|
2. `.packages` - Accesses the packages output from the flake
|
|
3. `.${pkgs.system}` - Selects the right system architecture (e.g., `x86_64-linux`)
|
|
4. `.lnbits` - Gets the `lnbits` package (which equals `runtimeVenv`)
|
|
|
|
## Understanding Flake References
|
|
|
|
The **flake reference format** is crucial to understanding how this works:
|
|
|
|
### Local Path Reference
|
|
|
|
```nix
|
|
builtins.getFlake "path:/var/src/lnbits-src"
|
|
```
|
|
|
|
- Uses files from the local filesystem at `/var/src/lnbits-src`
|
|
- The `.src` attribute points to `/var/src/lnbits-src`
|
|
- Files are mutable - you can edit them
|
|
- Requires deploying the full source tree via krops
|
|
|
|
### GitHub Reference
|
|
|
|
```nix
|
|
builtins.getFlake "github:lnbits/lnbits/main"
|
|
```
|
|
|
|
- Nix fetches the repository from GitHub
|
|
- Stores it in `/nix/store/xxx-source/` (read-only)
|
|
- The `.src` attribute 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 venv
|
|
- `uv.lock` - Contains locked dependency versions
|
|
- `pyproject.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`
|
|
|
|
```bash
|
|
cd /path/to/lnbits
|
|
uv run lnbits --port 5000 --host 0.0.0.0
|
|
```
|
|
|
|
This:
|
|
1. Reads `uv.lock`
|
|
2. Creates/updates a venv in `.venv/`
|
|
3. Installs dependencies if needed
|
|
4. Runs `lnbits` from the venv
|
|
5. Uses current directory for source files
|
|
|
|
### Nix Flake Approach
|
|
|
|
```nix
|
|
package = (builtins.getFlake "path:/var/src/lnbits-src").packages.${pkgs.system}.lnbits;
|
|
```
|
|
|
|
This:
|
|
1. ✅ Reads `uv.lock` via `uv2nix`
|
|
2. ✅ Creates a venv in `/nix/store` (immutable)
|
|
3. ✅ All dependencies are locked and reproducible
|
|
4. ✅ Runs `/nix/store/xxx-lnbits-env/bin/lnbits`
|
|
5. ✅ Sets `LNBITS_PATH` to source directory for templates/static/extensions
|
|
6. ✅ Runs as a systemd service with proper user/permissions
|
|
7. ✅ No runtime dependency on `uv` itself
|
|
|
|
### 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:
|
|
|
|
```nix
|
|
# 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:
|
|
|
|
1. **Converts uv dependencies to Nix** using `uv2nix`
|
|
2. **Builds an immutable venv** in `/nix/store`
|
|
3. **Deploys full source** to `/var/src/lnbits-src` via krops
|
|
4. **Loads the flake** from the deployed source using `path:/var/src/lnbits-src`
|
|
5. **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 `uv` at 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.
|