diff --git a/.env.example b/.env.example index cae56b77a..2f8feaf97 100644 --- a/.env.example +++ b/.env.example @@ -70,10 +70,6 @@ FORWARDED_ALLOW_IPS="*" # Inside this directory the `extensions` and `upgrades` sub-directories will be created. # LNBITS_EXTENSIONS_PATH="/path/to/some/dir" -# === WASM Extensions Sandbox Limits === -# LNBITS_WASM_TIMEOUT_SECONDS=3.0 -# LNBITS_WASM_MAX_MODULE_BYTES=1000000 -# LNBITS_WASM_MAX_DB_OPS_PER_MIN=120 # ID of the super user. The user ID must exist. # SUPER_USER="" diff --git a/docs/devs/agents_extensions.md b/docs/devs/agents_extensions.md deleted file mode 100644 index 6652738aa..000000000 --- a/docs/devs/agents_extensions.md +++ /dev/null @@ -1,67 +0,0 @@ ---- -layout: default -parent: For developers -title: Agent Guide - Python Extensions -nav_order: 4 ---- - -# Agent Guide - Python Extensions - -This guide is for AI agents or developers using AI to build **traditional (Python) LNbits extensions**. It defines what to change, what not to change, and the expected structure. - -## Hard Rules (Non-Negotiable) - -- Do **not** change core LNbits files. -- Only edit files inside your extension folder. -- Do **not** add new Python dependencies unless explicitly approved. - -## Extension Folder Layout (Python) - -Your extension lives under: - -``` -lnbits/extensions// -``` - -Typical files to edit: - -- `views.py` (HTML routes) -- `views_api.py` (API routes) -- `crud.py` / `models.py` (storage logic + models) -- `migrations.py` (DB schema) -- `templates//` (HTML) -- `static/` (JS/CSS/images) -- `config.json`, `manifest.json`, `README.md` - -## What You Can Do - -Python extensions can: - -- Define their own database schema via `migrations.py` -- Run long-running background tasks via `*_start()` and `*_stop()` hooks -- Access LNbits internal services directly in Python -- Expose custom API routes under `//api/v1/...` - -## What You Must Not Do - -- Do not modify core services or routes. -- Do not patch LNbits internals for your extension. -- Avoid direct DB access outside your own schema. - -## Background Tasks - -Implement background tasks by exposing: - -``` -def _start(): -def _stop(): -``` - -Use `register_invoice_listener` or `wait_for_paid_invoices` if you need to react to payments. - -## Testing Checklist - -- Extension loads without errors. -- Migrations apply cleanly. -- Routes are registered under `//...`. -- Background tasks start/stop cleanly. diff --git a/docs/devs/agents_wasm_extensions.md b/docs/devs/agents_wasm_extensions.md deleted file mode 100644 index 7e6f8ebda..000000000 --- a/docs/devs/agents_wasm_extensions.md +++ /dev/null @@ -1,174 +0,0 @@ ---- -layout: default -parent: For developers -title: Agent Guide - WASM Extensions -nav_order: 3 ---- - -# Agent Guide - WASM Extensions - -This guide is written for AI agents or developers using AI to build LNbits WASM extensions. It describes what to change, what not to change, and the available capabilities/limits. - -## Hard Rules (Non-Negotiable) - -- Do **not** change core LNbits files. Only edit files inside your extension folder. -- Do **not** add new Python dependencies. -- Do **not** rely on long-running WASM processes. WASM runs per-call with timeouts. - -## Extension Folder Layout (WASM) - -Your extension lives under: - -``` -lnbits/extensions// -``` - -You should only edit files under this folder, typically: - -- `config.json` (metadata, permissions, tags, public handlers) -- `wasm/` (your `module.wasm` or `module.wat`) -- `static/` (frontend assets) -- `templates/` (HTML pages) -- `manifest.json`, `README.md`, `description.md` (docs and metadata) - -## Required Config Fields - -In `config.json`: - -- `id` / `name` -- `extension_type: "wasm"` -- `permissions` (required API permissions) -- `public_wasm_functions` (handlers callable from public routes) -- `public_kv_keys` (publicly readable KV keys) -- `payment_tags` (list of tags the user may grant for watcher access) - -Example: - -```json -{ - "id": "myext", - "name": "MyExt", - "extension_type": "wasm", - "permissions": [ - {"id": "ext.db.read_write", "label": "DB access", "description": "..."}, - { - "id": "api.POST:/api/v1/payments", - "label": "Create invoices", - "description": "..." - }, - { - "id": "ext.payments.watch", - "label": "Watch payments", - "description": "..." - }, - { - "id": "ext.tasks.schedule", - "label": "Schedule tasks", - "description": "..." - }, - {"id": "ext.db.sql", "label": "SQL access", "description": "..."} - ], - "public_wasm_functions": [ - "public_create_invoice", - "on_tag_payment", - "on_schedule" - ], - "public_kv_keys": ["public_lists", "public_tasks"], - "payment_tags": ["coinflip", "myext"] -} -``` - -## What the WASM Host Can Do - -WASM runs in a short-lived subprocess. It can: - -- Read/write extension KV (`/api/v1/kv/*`) -- Read/write secret KV (`/api/v1/secret/*`) -- Call internal LNbits endpoints (only if declared + granted) -- Publish websockets (`ws_publish`) -- Run backend tag watchers and scheduled handlers (server-side triggers) - -## Permissions Model - -Your extension can only call or access what is declared and granted: - -- `api.METHOD:/path` for internal endpoints (core or other extensions) -- `ext.db.read_write` for KV access -- `ext.payments.watch` for payment watchers -- `ext.tasks.schedule` for scheduled jobs -- `ext.db.sql` for SQL interface - -If the endpoint doesn’t exist, permissions won’t save. - -## Tag Watchers (Backend) - -You can register tag watchers: - -``` -POST //api/v1/watch_tag -{ - "tag": "coinflip", - "wallet_id": "", - "handler": "on_tag_payment", - "store_key": "tag:coinflip:last_payment" -} -``` - -Constraints: - -- Tag must be in `payment_tags` and granted by the user. -- Watchers are persisted and restored on restart. - -## Scheduled Tasks (Backend) - -You can schedule periodic handlers: - -``` -POST //api/v1/schedule -{ - "interval_seconds": 30, - "handler": "on_schedule", - "store_key": "schedule:last_run" -} -``` - -Constraints: - -- Requires `ext.tasks.schedule` permission. -- Minimum interval is 5 seconds. -- Stored in extension KV and restored on restart. - -## SQL Interface (Limited) - -You can run SQL within your extension schema: - -- `/api/v1/sql/query` (SELECT only) -- `/api/v1/sql/exec` (limited DDL/DML) - -Rules: - -- Single statement only -- No `PRAGMA`, no `sqlite_master` -- No cross-schema access - -## Public Pages (No Keys) - -Public pages must not depend on `window.g` or wallet keys. -They can call: - -- `/{ext_id}/api/v1/public/kv/{key}` -- `/{ext_id}/api/v1/public/call/{handler}` - -## What Not To Do - -- Do not write to core routes or override existing LNbits paths. -- Do not add background threads; use watchers or scheduler instead. -- Do not assume the WASM process persists. - -## Testing Checklist - -- Permissions show correctly in the extensions UI. -- Public handlers are in `public_wasm_functions`. -- Public KV keys are explicitly listed. -- Tag watchers only use allowed tags. -- Scheduled handlers run and update KV as expected. diff --git a/lnbits/core/wasm/__init__.py b/lnbits/core/wasm/__init__.py index ef0d9da8c..d13a20f89 100644 --- a/lnbits/core/wasm/__init__.py +++ b/lnbits/core/wasm/__init__.py @@ -1 +1 @@ -from lnbits.extensions.wasm.wasm_host import * # noqa: F403 +from lnbits.extensions.wasm.wasm_host import * # noqa: F401,F403 diff --git a/lnbits/core/wasm/extension_host.py b/lnbits/core/wasm/extension_host.py index 9684a922d..80ae38a0d 100644 --- a/lnbits/core/wasm/extension_host.py +++ b/lnbits/core/wasm/extension_host.py @@ -1 +1 @@ -from lnbits.extensions.wasm.wasm_host.extension_host import * # noqa: F403 +from lnbits.extensions.wasm.wasm_host.extension_host import * # noqa: F401,F403 diff --git a/lnbits/core/wasm/runner.py b/lnbits/core/wasm/runner.py index ca97250be..9e443841d 100644 --- a/lnbits/core/wasm/runner.py +++ b/lnbits/core/wasm/runner.py @@ -1 +1 @@ -from lnbits.extensions.wasm.wasm_host.runner import * # noqa: F403 +from lnbits.extensions.wasm.wasm_host.runner import * # noqa: F401,F403 diff --git a/lnbits/core/wasm/service.py b/lnbits/core/wasm/service.py index 2f1bf666a..e1b63a272 100644 --- a/lnbits/core/wasm/service.py +++ b/lnbits/core/wasm/service.py @@ -1 +1 @@ -from lnbits.extensions.wasm.wasm_host.service import * # noqa: F403 +from lnbits.extensions.wasm.wasm_host.service import * # noqa: F401,F403 diff --git a/lnbits/settings.py b/lnbits/settings.py index f4d70ee66..f0ceb78ef 100644 --- a/lnbits/settings.py +++ b/lnbits/settings.py @@ -75,9 +75,6 @@ class ExtensionsSettings(LNbitsSettings): lnbits_extensions_builder_manifest_url: str = Field( default="https://raw.githubusercontent.com/lnbits/extension_builder_stub/refs/heads/main/manifest.json" ) - lnbits_wasm_timeout_seconds: float = Field(default=3.0, ge=0.1) - lnbits_wasm_max_module_bytes: int = Field(default=1_000_000, ge=0) - lnbits_wasm_max_db_ops_per_min: int = Field(default=120, ge=0) @property def extension_builder_working_dir_path(self) -> Path: