krops-multi-deploy/docs/lnbits-flake-explanation.md
padreug 30a1ae28f7 Refactor shared configuration and update LNBits service for improved domain handling
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.
2025-10-12 08:52:56 +02:00

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 lnbits binary 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/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:

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:

  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

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

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

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

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:

# 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.