Skip to content

Installation

WoompaLoompa edited this page Aug 10, 2026 · 13 revisions

Installation

Requirements

  • LNbits >= 1.5.6 (the background task manager differs between releases: the extension uses lnbits.task_manager when available and falls back to the legacy lnbits.tasks module on LNbits 1.5.x — both work, but nothing below 1.5.x has been tested).
  • Python >= 3.10 (whatever ships with your LNbits image).
  • A working Lightning backend configured in LNbits for the extension's wallet backend. See Lightning backend below.
  • Network access to the Nostr relays you configure (wss://…).

The extension adds no new Python dependencies. It reuses LNbits' bundled coincurve, websockets, and bech32 packages and LNbits' own Nostr utilities (lnbits.utils.nostr).

Installing

Option A — official release (v0.1.3 is published)

v0.1.3 is released (icon fix on top of the v0.1.2 frontend rendering fix + header nav links — see Development). Once the registry pull request (lnbits/lnbits-extensions #832) is merged it will be installable from the LNbits extension manager:

  1. Log in to LNbits.
  2. Open Extensions and enable CLINK for your user.
  3. The extension appears under CLINK in the sidebar.

Until the PR is merged (or if you do not want to wait), use Option B.

The release pipeline automatically publishes GitHub releases and opens a pull request to the official lnbits/lnbits-extensions registry.

Option B — manual install (from the repository)

If you do not want to wait for the registry PR (or the extension manager is not available), install by placing the code in LNbits' extensions directory:

# Inside the LNbits app folder
cd extensions
git clone https://github.com/WoompaLoompa/lnbits-clink.git clink

Important: LNbits scans the directory <LNBITS_EXTENSIONS_PATH>/extensions/ (that is, it appends an extensions sub-folder to the configured path — the default lnbits gives lnbits/extensions). The extension folder must be named clink — LNbits derives the extension id from the folder name and imports it as the clink Python package. (The repository is named lnbits-clink only because GitHub disallows single-word lowercase repo names.)

Then restart LNbits. On a stock install (non-admin-gated extension) the extension is available to every user without per-user enablement.

Option C — repo manifest install (no registry wait)

The repository ships a top-level manifest.json ({"repos":[{"id":"clink","organisation":"WoompaLoompa","repository":"lnbits-clink"}]}). LNbits (>= 1.5.6) merges manifest URLs into the installable-extension listing, so clink becomes installable from the extension manager before the registry PR is merged — no manual copy needed:

# Add the manifest URL alongside the default registry manifest
LNBITS_EXTENSIONS_MANIFESTS="https://raw.githubusercontent.com/lnbits/lnbits-extensions/main/extensions.json,https://raw.githubusercontent.com/WoompaLoompa/lnbits-clink/main/manifest.json"

For each repo entry LNbits resolves the repository's releases/latest GitHub release and the config.json from its default branch to build an installable extension. The extension id comes from config.json["id"] (clink).

GitHub rate limits: manifest resolution calls the GitHub API (releases/latest) and is rate-limited to 60 requests/hour per IP for unauthenticated clients. A busy Fly machine shares its IP, so this budget is easily exhausted. Set LNBITS_EXT_GITHUB_TOKEN to a GitHub token (fine-grained, read-only Contents on the extension repo) to avoid intermittent failures — LNbits documents this setting as "required due to GitHub rate-limit":

fly secrets set -a <app> LNBITS_EXT_GITHUB_TOKEN=<token>

Fly.io install (verified)

The LNbits Docker image ships without extensions, and anything written to the container filesystem is lost on redeploy. Extensions must live on a persistent volume.

  1. Point LNBITS_EXTENSIONS_PATH at a path on your volume in fly.toml and redeploy:

    [env]
      LNBITS_EXTENSIONS_PATH = "/app/data/extensions"

    LNbits creates <path>/extensions/ automatically on startup.

  2. Upload the code. git is usually not installed in the container, so package the repo locally and push it over SFTP:

    tar czf clink.tgz --exclude='.git' --exclude='__pycache__' --exclude='*.pyc' \
      -C /path/to/lnbits-clink .
    printf 'put clink.tgz /tmp/clink.tgz\nbye\n' | fly ssh sftp shell -a clink-lnbits
  3. Extract it into the volume (note the nested extensions folder):

    fly ssh console -a clink-lnbits -C 'sh -s' <<'EOF'
    mkdir -p /app/data/extensions/extensions/clink
    tar xzf /tmp/clink.tgz -C /app/data/extensions/extensions/clink
    rm /tmp/clink.tgz
    ls /app/data/extensions/extensions/clink/config.json
    EOF
  4. Restart the machine so LNbits discovers the extension and runs migrations:

    fly machine restart <machine-id> -a clink-lnbits
  5. Verify (see Verifying the install).

Notes specific to Fly

  • Extensions stored under /app/data/... survive fly deploy and machine restarts because the volume is persistent.
  • The LNbits extension builder in the admin UI (/extensions/builder) can only build from the official registry — it cannot import an existing repo, so it is not usable for this extension.
  • On the demo app clink-lnbits the Lightning backend is now ClinkWallet (see CLINK as the Lightning backend): invoices are requested from a real Lightning.pub node over Nostr. Earlier the backend was FakeWallet (in-memory, always "payable"), which is still useful for walking the UI/protocol flow without moving real funds.
  • The demo app runs both manifests: LNBITS_EXTENSIONS_MANIFESTS="https://raw.githubusercontent.com/lnbits/lnbits-extensions/main/extensions.json,https://raw.githubusercontent.com/WoompaLoompa/lnbits-clink/main/manifest.json", so clink shows up in the extension manager there.

Superadmin setup (first run)

The superadmin flow differs from the old per-user extension toggle:

  1. With LNBITS_ADMIN_UI=true, LNbits auto-generates a superuser on first run and stores it in <data>/data/.super_user. Read it over SSH:

    fly ssh console -a clink-lnbits -C 'cd /app && uv run lnbits-cli superuser'
  2. Open https://<your-lnbits>/admin and log in as the superuser (or use lnbits-cli superuser-url to get a ?usr=<id> deep link).

  3. Create a wallet/user to click around with:

    fly ssh console -a clink-lnbits -C 'sh -s' <<'EOF'
    cd /app
    uv run lnbits-cli users new -u alice -p change-me
    EOF
  4. Log in as that user at the dashboard. On LNbits 1.5.x, a non-admin-gated extension like clink is enabled for every user automatically — the per-user extensions list in the admin API only gates extensions listed in LNBITS_ADMIN_EXTENSIONS. The CLINK pages are then reachable at /clink/pay and /clink/subscriptions.

Configuration

The extension configures its node with a single environment variable:

  • LNBITS_CLINK_FUNDING_NOFFER — a noffer1... string that points at a Lightning.pub node's offer service. When set, the ClinkWallet funding source is ready to receive; when unset it reports a clear error and LNbits falls back to VoidWallet after the startup retries.

  • Backend wallet: the extension is not just a consumer of whatever backend is configured — it can be the backend (see below). The public checkout, debit settlement, and subscriptions call create_invoice/pay_invoice on the configured backend.

  • First relay: you add relays from the UI or the API (/clink/api/v1/relays). Nothing works until at least one relay is enabled.

Lightning backend

For a real end-to-end experience (actually creating and paying Lightning invoices) you need a real Lightning backend such as:

  • LND (LNDWallet)
  • Core Lightning (CoreLightningWallet)
  • Eclair (EclairWallet)
  • A remote service such as Alby (AlbyWallet)

The default VoidWallet backend reports "cannot create invoices" — it is only useful for exercising the protocol layer, not for payments. FakeWallet (in-memory) lets you walk the full checkout/subscription UI flow.

CLINK as the Lightning backend

Since v0.1.4 CLINK itself can be the funding source: invoices are requested from a remote Lightning.pub node's offer service (kind 21001) using only the node's noffer1... string. Receive-first: create_invoice is fully wired; pay_invoice returns a "receive-only" error until a Lightning.pub account is added to the node connection (planned milestone).

How it plugs in

clink/__init__.py registers the ClinkWallet class on the lnbits.wallets module at extension load. LNbits resolves the backend with getattr(lnbits.wallets, settings.lnbits_backend_wallet_class), and because extensions are imported before that lookup at startup, selecting ClinkWallet requires no core changes. The class implements the same Wallet API as the bundled backends (verified against LNbits 1.5.6 and 1.6.x).

Step-by-step switch (what was done on clink-lnbits)

  1. Make ClinkWallet selectable — add it to the allowed funding sources. lnbits_allowed_funding_sources is not stored in the DB on this instance, so it comes from the env secret:

    fly secrets set -a clink-lnbits \
      "LNBITS_ALLOWED_FUNDING_SOURCES=<the-22-core-defaults>,ClinkWallet"

    (Keep the existing defaults in the value or the other backends vanish from the dropdown.) The dropdown in /admin → Funding Sources will then list ClinkWallet.

  2. Point the wallet at the node — set the noffer secret (this is what the ClinkWallet constructor reads):

    fly secrets set -a clink-lnbits "LNBITS_CLINK_FUNDING_NOFFER=noffer1..."

    fly secrets set restarts the machine. Settings precedence note: values in the system_settings table override env vars, so do the DB step below too.

  3. Switch the backend class in the DB — this is the exact row the admin UI writes when you pick a backend and save. lnbits_backend_wallet_class is stored in system_settings (JSON-encoded string), and it takes precedence over LNBITS_BACKEND_WALLET_CLASS:

    fly ssh console -a clink-lnbits -C 'sh -c "python3 - <<\"PY\"
    import sqlite3
    db = sqlite3.connect(\"/app/data/database.sqlite3\")
    db.execute(\"UPDATE system_settings SET value=? WHERE id=?\", ('\"'\"'\"ClinkWallet\"'\"'\"', \"'\"'\"'lnbits_backend_wallet_class'\"'\"'\"))
    db.commit()
    PY"'

    Alternatively — and equivalent — select ClinkWallet in /admin → Funding Sources → LNbits Wallet (Current backend wallet) and save.

  4. Restart and verify:

    fly machine restart <machine-id> -a clink-lnbits
    fly logs -a clink-lnbits --no-tail -n 200 | grep -i "funding source"
    # expect:  clink: registered ClinkWallet as an LNbits funding source
    #          Funding source: ClinkWallet
    #          SUCCESS | ✔️ Backend ClinkWallet connected and with a balance of 0 msat.

    status() only validates the noffer (no network call), so a healthy boot does not guarantee the relay is reachable — create a real invoice to check the receive path:

    fly ssh console -a clink-lnbits -C 'cd /app && PYTHONPATH=/app/data/extensions/extensions \
      /app/.venv/bin/python - <<"PY"
    import asyncio, clink, lnbits.wallets
    async def main():
        w = lnbits.wallets.ClinkWallet()
        print(await w.status())
        r = await w.create_invoice(1000, "check", "check")
        print(r.ok, r.error_message, r.payment_request[:40])
    asyncio.run(main())
    PY'

    ok: True + a BOLT11 string means the node answered over the relay.

Notes

  • Watchdog: the automatic voiding check (lnbits_watchdog_switch_to_voidwallet) is off on this instance. If you enable it, the watchdog compares tracked vs. node balance — with a receive-only wallet that reports 0 msat, the balance delta may trigger a switch, so keep it off (or pair it with a node-account wallet once that lands).
  • Receive-only caveat: while ClinkWallet is the backend, every invoice on the instance comes from the remote node, and the instance cannot pay out. Keep FakeWallet as an alternative in the allowed list to switch back quickly if needed.

Verifying the install

# Public info endpoint: relay list + listener state
curl https://<your-lnbits>/clink/api/v1/info

# Auth-protected pages (401 without a user context is expected)
curl -i https://<your-lnbits>/clink/pay

# In the container
uv run lnbits-cli extensions list
#   Installed extensions:
#     - clink (0.0)

On startup the LNbits log confirms migrations and background tasks:

running migration clink.1
running migration clink.2
running migration clink.3
✔️ All migrations done.
clink: listener task started
clink: subscription poller started

Troubleshooting

  • Blank/white extension pages (/clink/pay, /clink/subscriptions) with a console error like Cannot read properties of undefined (reading 'user') mean the frontend read the old this.g object. On LNbits 1.5.x extension pages must use window.g instead (see Development). Use a release that includes the fix (v0.1.1+).

  • Tabs/dialog inputs stacking on top of each other (Offers/Debits/Relays tabs collapsing into one another, overlapping form fields) means the page still ships self-closing component tags (<q-tab ... />). The in-DOM template is browser-parsed before Vue compiles it, so self-closed non-void tags swallow their siblings. Use v0.1.2+ (see Development).

  • Extension card shows version 0.0 and a broken icon in the LNbits extension manager. That happens when the extension was installed by copying the repo folder into the extensions directory: LNbits records version 0.0 and no GitHub metadata. Reinstall from a release (via the extension manager or a manifest) so the installed_extensions row gets the real version (0.1.3) and an absolute icon URL (https://github.com/WoompaLoompa/lnbits-clink/raw/main/static/clink.png).

  • A button/tab icon renders as two or three icons (e.g. the Plans tab showed event + repeat). The icon name isn't a single ligature in the loaded Material Icons font. Use a single-glyph icon name (see Development); the affected names were fixed in v0.1.3.

Clone this wiki locally