Skip to content

LNVPS/api

Folders and files

NameName
Last commit message
Last commit date

Latest commit

 

History

727 Commits
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 

Repository files navigation

LNVPS

A Bitcoin-powered VPS platform. Customers pay with Bitcoin Lightning (or optionally fiat) to provision and renew virtual machines on Proxmox or LibVirt hypervisors.

Features

  • Payments
  • VM Backends
    • Proxmox (production-ready)
    • LibVirt (WIP)
  • Network / DNS
    • Mikrotik JSON-API — static ARP for anti-spoofing
    • OVH Additional IP virtual MAC management
    • Cloudflare API — forward A/AAAA and reverse PTR records
    • Route-server management — BGP session, tunnel (GRE/VXLAN/WireGuard) and default-route visibility/control across RouterOS and Linux (BIRD/Pathvector over SSH) routers
  • Nostr integration
    • NIP-17 direct-message notifications
    • NIP-05 identity server (lnvps_nostr)
    • NIP-47 Nostr Wallet Connect for auto-renewal
    • NIP-90 Data Vending Machine (DVM) for provisioning via Nostr
  • AI support agent (lnvps_agent)
    • Handles customer support requests with an OpenAI-compatible LLM and API-calling tools
    • Email channel (IMAP IDLE inbox watching + SMTP replies) and Nostr kind-1 mention channel
  • Security
    • XDP eBPF SYN-flood rate limiter with self-upgrading .deb daemon (lnvps_ebpf + lnvps_fw_service)
    • Per-VM firewall rules and default policy (applied on the host)
    • Database field-level AES-GCM encryption for sensitive columns
    • Cloudflare Turnstile captcha (optional)
  • Observability
    • VM time-series metrics (CPU, memory, disk, network) from Proxmox RRD
    • Network health monitoring with Prometheus metrics (lnvps_health)
    • Grafana dashboard included (grafana.json)
  • Other
    • WebSocket serial console proxy
    • EU VAT validation via VIES
    • Per-country tax rates
    • Multi-region host support
    • Kubernetes operator for Nostr domain Ingress management (lnvps_operator)
    • OpenAPI/Swagger generation (--features openapi)
    • Redis for shared state and horizontal scaling (optional)

Workspace Crates

Crate Binary Description
lnvps_api lnvps_api Main user-facing HTTP API + background worker
lnvps_api_admin lnvps_api_admin Admin HTTP API with privileged operations
lnvps_api_common Shared types, pricing engine, exchange rates, Redis queue, NIP-98 auth
lnvps_db Database abstraction (MySQL/SQLx), migrations, field-level encryption
lnvps_nostr lnvps_nostr Standalone NIP-05 identity server (/.well-known/nostr.json)
lnvps_agent lnvps_agent AI support agent — answers support requests via email (IMAP/SMTP) and Nostr kind-1 mentions using an OpenAI-compatible LLM with API-calling tools
lnvps_operator lnvps_operator Kubernetes operator — reconciles Nostr domain Ingress objects
lnvps_health lnvps_health Network health monitor — TCP MSS/PMTU checks, DNS, Prometheus metrics
lnvps_host_util Host-side helper utilities packaged for isolated Docker builds
lnvps_e2e End-to-end integration test suite (spins up API + worker against a real DB/LND)
lnvps_ebpf XDP eBPF SYN-flood rate limiter (kernel program)
lnvps_fw_common Shared types between the eBPF datapath and the userspace loader
lnvps_fw_service lnvps_fw_service Userspace loader + HTTP control API for the eBPF firewall, with self-upgrade from GitHub .deb releases

The lnvps_ebpf, lnvps_fw_common, and lnvps_fw_service crates live in the separate lnvps_fw/ workspace (excluded from the root one) because the eBPF datapath needs its own nightly rust-src + bpf-linker toolchain and target.

Building

cargo build --release -p lnvps_api
cargo build --release -p lnvps_api_admin
cargo build --release -p lnvps_nostr
cargo build --release -p lnvps_agent
cargo build --release -p lnvps_operator
cargo build --release -p lnvps_health

The eBPF crates (lnvps_ebpf, lnvps_fw_common, lnvps_fw_service) live in the separate lnvps_fw/ workspace and require a nightly eBPF toolchain (rust-src + bpf-linker); build them separately. Tagging a vX.Y.Z release builds and attaches a .deb of lnvps_fw_service to the GitHub release, which running daemons install via their self-upgrade endpoint.

Running

lnvps_api can run as a combined API + worker, or split into separate processes:

./lnvps_api --config config.yaml              # API + worker (default)
./lnvps_api --config config.yaml --mode api   # HTTP server only
./lnvps_api --config config.yaml --mode worker # background worker only

Development Environment

docker compose up -d
# Starts: MariaDB on :3376, Redis on :6398, Krill (RPKI) on :3000

Config Reference

Core (lnvps_api)

# MySQL connection string (required)
db: "mysql://root:root@localhost:3376/lnvps"

# Public base URL used in webhook callbacks (required)
public-url: "https://api.example.com"

# HTTP listen address (default: 0.0.0.0:8000)
listen: "0.0.0.0:8000"

# Days after VM expiry before hard deletion
delete-after: 3

# Prevent VM creation/deletion
read-only: false

Lightning node (exactly one required)

lightning:
  lnd:
    url: "https://127.0.0.1:10003"
    cert: "$HOME/.lnd/tls.cert"
    macaroon: "$HOME/.lnd/data/chain/bitcoin/mainnet/admin.macaroon"

Provisioner (VM backend)

provisioner:
  proxmox:
    qemu:
      machine: "q35"
      os-type: "l26"
      bridge: "vmbr0"
      cpu: "kvm64"
      kvm: false
      arch: "x86_64"
      # Per-NIC Proxmox firewall (optional)
      firewall-config:
        dhcp: true
        enable: true
        ip-filter: true
        mac-filter: true
        ndp: true
        policy-in: "DROP"    # ACCEPT | REJECT | DROP
        policy-out: "ACCEPT"
    # SSH access for host-side CLI commands (optional)
    ssh:
      key: "/root/.ssh/id_ed25519"
      user: "root"
    # MAC prefix for generated NICs (default: bc:24:11)
    mac-prefix: "bc:24:11"

  # LibVirt (WIP)
  libvirt:
    qemu:
      machine: "q35"
      os-type: "l26"
      bridge: "vmbr0"
      cpu: "kvm64"
      kvm: false

Revolut fiat payments (optional)

revolut:
  url: "https://merchant.revolut.com"
  token: "my-revolut-api-token"
  api-version: "2024-09-01"
  public-key: "my-revolut-public-key"

SMTP notifications (optional)

smtp:
  admin: 1                    # user ID to receive system alerts (optional)
  server: "smtp.gmail.com"
  from: "LNVPS <no-reply@example.com>"   # optional
  username: "no-reply@example.com"
  password: "mypassword123"

Nostr notifications — NIP-17 (optional)

nostr:
  relays:
    - "wss://relay.snort.social"
    - "wss://relay.damus.io"
    - "wss://nos.lol"
  nsec: "nsec1234xxx"

DNS — Cloudflare (optional)

dns:
  # Zone ID for forward A/AAAA records (created as vm-<vmid>.<zone>)
  forward-zone-id: "my-cloudflare-zone-id"
  api:
    cloudflare:
      token: "my-api-token"

Redis (optional — enables horizontal scaling)

redis:
  url: "redis://localhost:6379"

When configured, exchange rates, VM state cache, and the work queue all use Redis.

Database field encryption (optional)

encryption:
  key-file: "/etc/lnvps/encryption.key"
  auto-generate: true   # generate key if absent

Encrypted fields: SSH key material, NWC connection strings, email addresses, host API tokens.

Taxes (VAT)

This implements EU VAT only. The seller's country is taken from the company's own VAT number (tax_id) when set — that number is the company's VIES registration and identifies the country it is registered in — otherwise from the company's country_code. EU VAT applies only when that country is in the EU VAT area; if it is outside the list (e.g. a US company), no tax is applied here — other tax systems (such as US sales tax) are not handled.

When the seller is in the EU, standard rates for all member states are fetched from an external source at startup and refreshed daily (cached in-memory by the shared VatClient). The rate applied to a payment is then determined from the seller's country and the customer:

  • B2B with a stored (VIES-validated) VAT number: same country as seller → domestic VAT; another EU country → reverse charge (0%); outside the EU → out of scope (0%).
  • B2C: place of supply is taken from the self-declared country, falling back to the IP-derived country. EU → that country's destination rate (OSS); non-EU → out of scope (0%).
  • Undetermined (no country evidence): the seller's domestic rate is applied conservatively when the seller is in the EU, otherwise out of scope.

IP geolocation (for the fallback location signal) requires the optional geoip-database setting pointing at a MaxMind GeoLite2/GeoIP2 Country .mmdb. EU VAT numbers are validated against the VIES service before being stored.

Until the first successful rate refresh (or if the rate source is unreachable), no rates are known and VAT falls back to 0%.

Disclaimer: This VAT handling is an automated, best-effort determination from the available evidence and configuration — it is not tax or legal advice and makes no guarantee of compliance in any jurisdiction. Rates and validation come from third-party sources that may lag official changes. The operator is solely responsible for confirming the correct VAT/OSS treatment for their business and should have it reviewed by a qualified tax professional.

Nostr address host (optional)

# Enables NIP-05 routing under this hostname
nostr-address-host: "nostr.example.com"

Captcha (optional)

captcha:
  turnstile:
    secret-key: "my-cloudflare-turnstile-secret"

lnvps_nostr config

Standalone NIP-05 identity server. Reads domain/handle records from the shared database.

db: "mysql://root:root@localhost:3376/lnvps"
listen: "0.0.0.0:8001"

lnvps_operator config (Kubernetes)

Reconciles Kubernetes Ingress and cert-manager Certificate objects for Nostr domains.

db: "mysql://root:root@localhost:3376/lnvps"
namespace: "default"
reconcile-interval: 60        # seconds
error-retry-interval: 30
service-name: "lnvps-nostr"
port-name: "http"
cluster-issuer: "letsencrypt-prod"
ingress-class: "nginx"
annotations:
  nginx.ingress.kubernetes.io/ssl-redirect: "true"

lnvps_health config

Network health monitoring daemon. Runs TCP MSS/PMTU probes, DNS checks, exposes Prometheus metrics, and sends email alerts.

interval-secs: 600           # check interval
alert-cooldown-secs: 3600    # minimum time between repeated alerts

metrics:
  enabled: true
  bind: "127.0.0.1:9090"    # Prometheus scrape endpoint (/metrics)

smtp:
  host: "smtp.gmail.com"
  port: 587
  username: "alerts@example.com"
  password: "password"
  from: "alerts@example.com"
  to: "admin@example.com"

mss-checks:
  - name: "My Server"
    host: "server1.example.com"
    port: 443
    expected-mss: 1460
    expected-mss-v6: 1440    # optional, defaults to expected-mss - 20
./lnvps_health --config config.yaml         # run continuously
./lnvps_health --config config.yaml --once  # run once and exit

lnvps_agent config

AI support agent. Watches an email inbox (IMAP IDLE) and/or Nostr kind-1 mentions, and answers support requests using an OpenAI-compatible LLM with tools that call the LNVPS APIs. Config is loaded from a settings.yaml file in the working directory; all keys can also be overridden with LNVPS_AGENT__* environment variables.

listen: "0.0.0.0:8080"                       # agent HTTP server (default)
admin-api-url: "https://api.example.com"     # LNVPS admin API base URL (required)
user-api-url: "https://api.example.com"      # LNVPS user API base URL (required)
nsec: "nsec1234xxx"                           # signs NIP-98 auth events / Nostr ops (required)
system-prompt: "You are LNVPS support..."    # optional system prompt override
conversation-history-path: "/var/lib/lnvps-agent"  # optional history store dir

openai:                                       # OpenAI-compatible LLM (required)
  base-url: "http://localhost:11434/v1"      # e.g. Ollama, or https://api.openai.com/v1
  api-key: "sk-..."                          # optional (not needed for Ollama)
  model: "gpt-4o"
  max-tokens: 2048

email:                                        # email channel (optional)
  imap-server: "imap.gmail.com:993"
  imap-username: "support@example.com"
  imap-password: "app-password"
  imap-mailbox: "INBOX"                       # optional
  smtp-server: "smtp.gmail.com:587"
  smtp-username: "support@example.com"
  smtp-password: "app-password"
  smtp-from: "support@example.com"
  smtp-from-name: "LNVPS Support"            # optional

kind1:                                        # Nostr kind-1 mention channel (optional)
  relays:
    - "wss://relay.damus.io"
  mention-pubkeys: []                          # hex pubkeys to watch; defaults to the bot's own
  poll-interval-secs: 30

About

API backend for LNVPS

Topics

Resources

Stars

8 stars

Watchers

1 watching

Forks

Contributors