use new settings and remove unused amdin extension stuff

This commit is contained in:
dni ⚡ 2022-10-03 16:46:46 +02:00
parent 9e2f1f6545
commit 1ffc8c3498
11 changed files with 340 additions and 230 deletions

View File

@ -8,6 +8,8 @@ import click
from genericpath import exists from genericpath import exists
from loguru import logger from loguru import logger
from lnbits.settings import Settings, settings
from .core import db as core_db from .core import db as core_db
from .core import migrations as core_migrations from .core import migrations as core_migrations
from .db import COCKROACH, POSTGRES, SQLITE from .db import COCKROACH, POSTGRES, SQLITE
@ -17,7 +19,8 @@ from .helpers import (
get_valid_extensions, get_valid_extensions,
url_for_vendored, url_for_vendored,
) )
from .settings import LNBITS_PATH
path = settings.lnbits_path
@click.command("migrate") @click.command("migrate")
@ -36,15 +39,15 @@ def transpile_scss():
warnings.simplefilter("ignore") warnings.simplefilter("ignore")
from scss.compiler import compile_string # type: ignore from scss.compiler import compile_string # type: ignore
with open(os.path.join(LNBITS_PATH, "static/scss/base.scss")) as scss: with open(os.path.join(path, "static/scss/base.scss")) as scss:
with open(os.path.join(LNBITS_PATH, "static/css/base.css"), "w") as css: with open(os.path.join(path, "static/css/base.css"), "w") as css:
css.write(compile_string(scss.read())) css.write(compile_string(scss.read()))
def bundle_vendored(): def bundle_vendored():
for getfiles, outputpath in [ for getfiles, outputpath in [
(get_js_vendored, os.path.join(LNBITS_PATH, "static/bundle.js")), (get_js_vendored, os.path.join(path, "static/bundle.js")),
(get_css_vendored, os.path.join(LNBITS_PATH, "static/bundle.css")), (get_css_vendored, os.path.join(path, "static/bundle.css")),
]: ]:
output = "" output = ""
for path in getfiles(): for path in getfiles():
@ -54,33 +57,6 @@ def bundle_vendored():
f.write(output) f.write(output)
async def get_admin_settings():
from lnbits.extensions.admin.models import Admin
try:
ext_db = importlib.import_module(f"lnbits.extensions.admin").db
except:
return False
async with ext_db.connect() as conn:
if conn.type == SQLITE:
exists = await conn.fetchone(
"SELECT * FROM sqlite_master WHERE type='table' AND name='admin'"
)
elif conn.type in {POSTGRES, COCKROACH}:
exists = await conn.fetchone(
"SELECT * FROM information_schema.tables WHERE table_name = 'admin'"
)
if not exists:
return False
row = await conn.fetchone("SELECT * from admin.admin")
return Admin(**row) if row else None
async def migrate_databases(): async def migrate_databases():
"""Creates the necessary databases if they don't exist already; or migrates them.""" """Creates the necessary databases if they don't exist already; or migrates them."""

View File

@ -8,7 +8,7 @@ from loguru import logger
from lnbits import bolt11 from lnbits import bolt11
from lnbits.db import COCKROACH, POSTGRES, Connection from lnbits.db import COCKROACH, POSTGRES, Connection
from lnbits.settings import DEFAULT_WALLET_NAME, LNBITS_ADMIN_USERS from lnbits.settings import settings
from . import db from . import db
from .models import BalanceCheck, Payment, User, Wallet from .models import BalanceCheck, Payment, User, Wallet
@ -63,8 +63,8 @@ async def get_user(user_id: str, conn: Optional[Connection] = None) -> Optional[
email=user["email"], email=user["email"],
extensions=[e[0] for e in extensions], extensions=[e[0] for e in extensions],
wallets=[Wallet(**w) for w in wallets], wallets=[Wallet(**w) for w in wallets],
admin=user["id"] in [x.strip() for x in LNBITS_ADMIN_USERS] admin=user["id"] in settings.lnbits_admin_users
if LNBITS_ADMIN_USERS if settings.lnbits_admin_users
else False, else False,
) )
@ -99,7 +99,7 @@ async def create_wallet(
""", """,
( (
wallet_id, wallet_id,
wallet_name or DEFAULT_WALLET_NAME, wallet_name or settings.lnbits_default_wallet_name,
user_id, user_id,
uuid4().hex, uuid4().hex,
uuid4().hex, uuid4().hex,

View File

@ -21,7 +21,7 @@ from lnbits.decorators import (
) )
from lnbits.helpers import url_for, urlsafe_short_hash from lnbits.helpers import url_for, urlsafe_short_hash
from lnbits.requestvars import g from lnbits.requestvars import g
from lnbits.settings import FAKE_WALLET, RESERVE_FEE_MIN, RESERVE_FEE_PERCENT, WALLET from lnbits.settings import FAKE_WALLET, WALLET, settings
from lnbits.wallets.base import PaymentResponse, PaymentStatus from lnbits.wallets.base import PaymentResponse, PaymentStatus
from . import db from . import db
@ -381,4 +381,6 @@ async def check_transaction_status(
# WARN: this same value must be used for balance check and passed to WALLET.pay_invoice(), it may cause a vulnerability if the values differ # WARN: this same value must be used for balance check and passed to WALLET.pay_invoice(), it may cause a vulnerability if the values differ
def fee_reserve(amount_msat: int) -> int: def fee_reserve(amount_msat: int) -> int:
return max(int(RESERVE_FEE_MIN), int(amount_msat * RESERVE_FEE_PERCENT / 100.0)) reserve_min = settings.lnbits_reserve_fee_min
reserve_percent = settings.lnbits_reserve_fee_percent
return max(int(reserve_min), int(amount_msat * reserve_percent / 100.0))

View File

@ -30,7 +30,7 @@ from lnbits.decorators import (
require_invoice_key, require_invoice_key,
) )
from lnbits.helpers import url_for, urlsafe_short_hash from lnbits.helpers import url_for, urlsafe_short_hash
from lnbits.settings import LNBITS_ADMIN_USERS, LNBITS_SITE_TITLE, WALLET from lnbits.settings import WALLET, settings
from lnbits.utils.exchange_rates import ( from lnbits.utils.exchange_rates import (
currencies, currencies,
fiat_amount_as_satoshis, fiat_amount_as_satoshis,
@ -76,7 +76,7 @@ async def api_wallet(wallet: WalletTypeInfo = Depends(get_key_type)):
async def api_update_balance( async def api_update_balance(
amount: int, wallet: WalletTypeInfo = Depends(get_key_type) amount: int, wallet: WalletTypeInfo = Depends(get_key_type)
): ):
if wallet.wallet.user not in LNBITS_ADMIN_USERS: if wallet.wallet.user not in settings.lnbits_admin_users:
raise HTTPException( raise HTTPException(
status_code=HTTPStatus.FORBIDDEN, detail="Not an admin user" status_code=HTTPStatus.FORBIDDEN, detail="Not an admin user"
) )
@ -178,7 +178,7 @@ async def api_payments_create_invoice(data: CreateInvoiceData, wallet: Wallet):
else: else:
description_hash = b"" description_hash = b""
unhashed_description = b"" unhashed_description = b""
memo = data.memo or LNBITS_SITE_TITLE memo = data.memo or settings.lnbits_site_title
if data.unit == "sat": if data.unit == "sat":
amount = int(data.amount) amount = int(data.amount)
else: else:
@ -678,7 +678,7 @@ async def img(request: Request, data):
@core_app.get("/api/v1/audit/") @core_app.get("/api/v1/audit/")
async def api_auditor(wallet: WalletTypeInfo = Depends(get_key_type)): async def api_auditor(wallet: WalletTypeInfo = Depends(get_key_type)):
if wallet.wallet.user not in LNBITS_ADMIN_USERS: if wallet.wallet.user not in settings.lnbits_admin_users:
raise HTTPException( raise HTTPException(
status_code=HTTPStatus.FORBIDDEN, detail="Not an admin user" status_code=HTTPStatus.FORBIDDEN, detail="Not an admin user"
) )

View File

@ -16,14 +16,7 @@ from lnbits.core.models import User
from lnbits.decorators import check_user_exists from lnbits.decorators import check_user_exists
from lnbits.helpers import template_renderer, url_for from lnbits.helpers import template_renderer, url_for
from lnbits.requestvars import g from lnbits.requestvars import g
from lnbits.settings import ( from lnbits.settings import settings
LNBITS_ADMIN_UI,
LNBITS_ADMIN_USERS,
LNBITS_ALLOWED_USERS,
LNBITS_CUSTOM_LOGO,
LNBITS_SITE_TITLE,
SERVICE_FEE,
)
from ...helpers import get_valid_extensions from ...helpers import get_valid_extensions
from ..crud import ( from ..crud import (
@ -119,14 +112,6 @@ async def wallet(
user_id = usr.hex if usr else None user_id = usr.hex if usr else None
wallet_id = wal.hex if wal else None wallet_id = wal.hex if wal else None
wallet_name = nme wallet_name = nme
service_fee = int(SERVICE_FEE) if int(SERVICE_FEE) == SERVICE_FEE else SERVICE_FEE
if LNBITS_ADMIN_UI:
LNBITS_ADMIN_USERS = g().admin_conf.admin_users
LNBITS_ALLOWED_USERS = g().admin_conf.allowed_users
else:
LNBITS_ADMIN_USERS = []
LNBITS_ALLOWED_USERS = []
if not user_id: if not user_id:
user = await get_user((await create_account()).id) user = await get_user((await create_account()).id)
@ -137,11 +122,14 @@ async def wallet(
return template_renderer().TemplateResponse( return template_renderer().TemplateResponse(
"error.html", {"request": request, "err": "User does not exist."} "error.html", {"request": request, "err": "User does not exist."}
) )
if LNBITS_ALLOWED_USERS and user_id not in LNBITS_ALLOWED_USERS: if (
len(settings.lnbits_allowed_users) > 0
and user_id not in settings.lnbits_allowed_users
):
return template_renderer().TemplateResponse( return template_renderer().TemplateResponse(
"error.html", {"request": request, "err": "User not authorized."} "error.html", {"request": request, "err": "User not authorized."}
) )
if LNBITS_ADMIN_USERS and user_id in LNBITS_ADMIN_USERS: if user_id in settings.lnbits_admin_users:
user.admin = True user.admin = True
if not wallet_id: if not wallet_id:
if user.wallets and not wallet_name: # type: ignore if user.wallets and not wallet_name: # type: ignore
@ -172,7 +160,7 @@ async def wallet(
"request": request, "request": request,
"user": user.dict(), # type: ignore "user": user.dict(), # type: ignore
"wallet": userwallet.dict(), "wallet": userwallet.dict(),
"service_fee": service_fee, "service_fee": settings.lnbits_service_fee,
"web_manifest": f"/manifest/{user.id}.webmanifest", # type: ignore "web_manifest": f"/manifest/{user.id}.webmanifest", # type: ignore
}, },
) )
@ -194,7 +182,7 @@ async def lnurl_full_withdraw(request: Request):
"k1": "0", "k1": "0",
"minWithdrawable": 1000 if wallet.withdrawable_balance else 0, "minWithdrawable": 1000 if wallet.withdrawable_balance else 0,
"maxWithdrawable": wallet.withdrawable_balance, "maxWithdrawable": wallet.withdrawable_balance,
"defaultDescription": f"{LNBITS_SITE_TITLE} balance withdraw from {wallet.id[0:5]}", "defaultDescription": f"{settings.lnbits_site_title} balance withdraw from {wallet.id[0:5]}",
"balanceCheck": url_for("/withdraw", external=True, usr=user.id, wal=wallet.id), "balanceCheck": url_for("/withdraw", external=True, usr=user.id, wal=wallet.id),
} }
@ -293,12 +281,12 @@ async def manifest(usr: str):
raise HTTPException(status_code=HTTPStatus.NOT_FOUND) raise HTTPException(status_code=HTTPStatus.NOT_FOUND)
return { return {
"short_name": LNBITS_SITE_TITLE, "short_name": settings.lnbits_site_title,
"name": LNBITS_SITE_TITLE + " Wallet", "name": settings.lnbits_site_title + " Wallet",
"icons": [ "icons": [
{ {
"src": LNBITS_CUSTOM_LOGO "src": settings.lnbits_custom_logo
if LNBITS_CUSTOM_LOGO if settings.lnbits_custom_logo
else "https://cdn.jsdelivr.net/gh/lnbits/lnbits@0.3.0/docs/logos/lnbits.png", else "https://cdn.jsdelivr.net/gh/lnbits/lnbits@0.3.0/docs/logos/lnbits.png",
"type": "image/png", "type": "image/png",
"sizes": "900x900", "sizes": "900x900",

View File

@ -10,7 +10,7 @@ from sqlalchemy import create_engine
from sqlalchemy_aio.base import AsyncConnection from sqlalchemy_aio.base import AsyncConnection
from sqlalchemy_aio.strategy import ASYNCIO_STRATEGY # type: ignore from sqlalchemy_aio.strategy import ASYNCIO_STRATEGY # type: ignore
from .settings import LNBITS_DATA_FOLDER, LNBITS_DATABASE_URL from lnbits.settings import settings
POSTGRES = "POSTGRES" POSTGRES = "POSTGRES"
COCKROACH = "COCKROACH" COCKROACH = "COCKROACH"
@ -85,8 +85,8 @@ class Database(Compat):
def __init__(self, db_name: str): def __init__(self, db_name: str):
self.name = db_name self.name = db_name
if LNBITS_DATABASE_URL: if settings.lnbits_database_url:
database_uri = LNBITS_DATABASE_URL database_uri = settings.lnbits_database_url
if database_uri.startswith("cockroachdb://"): if database_uri.startswith("cockroachdb://"):
self.type = COCKROACH self.type = COCKROACH
@ -131,14 +131,16 @@ class Database(Compat):
) )
) )
else: else:
if os.path.isdir(LNBITS_DATA_FOLDER): if os.path.isdir(settings.lnbits_data_folder):
self.path = os.path.join(LNBITS_DATA_FOLDER, f"{self.name}.sqlite3") self.path = os.path.join(
settings.lnbits_data_folder, f"{self.name}.sqlite3"
)
database_uri = f"sqlite:///{self.path}" database_uri = f"sqlite:///{self.path}"
self.type = SQLITE self.type = SQLITE
else: else:
raise NotADirectoryError( raise NotADirectoryError(
f"LNBITS_DATA_FOLDER named {LNBITS_DATA_FOLDER} was not created" f"LNBITS_DATA_FOLDER named {settings.lnbits_data_folder} was not created"
f" - please 'mkdir {LNBITS_DATA_FOLDER}' and try again" f" - please 'mkdir {settings.lnbits_data_folder}' and try again"
) )
logger.trace(f"database {self.type} added for {self.name}") logger.trace(f"database {self.type} added for {self.name}")
self.schema = self.name self.schema = self.name

View File

@ -14,12 +14,7 @@ from starlette.requests import Request
from lnbits.core.crud import get_user, get_wallet_for_key from lnbits.core.crud import get_user, get_wallet_for_key
from lnbits.core.models import User, Wallet from lnbits.core.models import User, Wallet
from lnbits.requestvars import g from lnbits.requestvars import g
from lnbits.settings import ( from lnbits.settings import settings
LNBITS_ADMIN_EXTENSIONS,
LNBITS_ADMIN_UI,
LNBITS_ADMIN_USERS,
LNBITS_ALLOWED_USERS,
)
class KeyChecker(SecurityBase): class KeyChecker(SecurityBase):
@ -139,11 +134,6 @@ async def get_key_type(
detail="Invoice (or Admin) key required.", detail="Invoice (or Admin) key required.",
) )
if LNBITS_ADMIN_UI:
LNBITS_ADMIN_USERS = g().admin_conf.admin_users
else:
LNBITS_ADMIN_USERS = []
for typenr, WalletChecker in zip( for typenr, WalletChecker in zip(
[0, 1], [WalletAdminKeyChecker, WalletInvoiceKeyChecker] [0, 1], [WalletAdminKeyChecker, WalletInvoiceKeyChecker]
): ):
@ -156,8 +146,12 @@ async def get_key_type(
status_code=HTTPStatus.NOT_FOUND, detail="Wallet does not exist." status_code=HTTPStatus.NOT_FOUND, detail="Wallet does not exist."
) )
if ( if (
LNBITS_ADMIN_USERS and wallet.wallet.user not in LNBITS_ADMIN_USERS settings.lnbits_admin_users
) and (LNBITS_ADMIN_EXTENSIONS and pathname in LNBITS_ADMIN_EXTENSIONS): and wallet.wallet.user not in settings.lnbits_admin_users
) and (
settings.lnbits_admin_extensions
and pathname in settings.lnbits_admin_extensions
):
raise HTTPException( raise HTTPException(
status_code=HTTPStatus.FORBIDDEN, status_code=HTTPStatus.FORBIDDEN,
detail="User not authorized for this extension.", detail="User not authorized for this extension.",
@ -233,22 +227,33 @@ async def require_invoice_key(
async def check_user_exists(usr: UUID4) -> User: async def check_user_exists(usr: UUID4) -> User:
g().user = await get_user(usr.hex) g().user = await get_user(usr.hex)
if not g().user: if not g().user:
raise HTTPException( raise HTTPException(
status_code=HTTPStatus.NOT_FOUND, detail="User does not exist." status_code=HTTPStatus.NOT_FOUND, detail="User does not exist."
) )
if LNBITS_ADMIN_UI: if (
LNBITS_ADMIN_USERS = g().admin_conf.admin_users len(settings.lnbits_allowed_users) > 0
LNBITS_ALLOWED_USERS = g().admin_conf.allowed_users and g().user.id not in settings.lnbits_allowed_users
else: ):
LNBITS_ADMIN_USERS = []
LNBITS_ALLOWED_USERS = []
if LNBITS_ALLOWED_USERS and g().user.id not in LNBITS_ALLOWED_USERS:
raise HTTPException( raise HTTPException(
status_code=HTTPStatus.UNAUTHORIZED, detail="User not authorized." status_code=HTTPStatus.UNAUTHORIZED, detail="User not authorized."
) )
if LNBITS_ADMIN_USERS and g().user.id in LNBITS_ADMIN_USERS:
if g().user.id in settings.lnbits_admin_users:
g().user.admin = True g().user.admin = True
return g().user return g().user
async def check_admin(usr: UUID4) -> User:
user = await check_user_exists(usr)
if not user.id in settings.lnbits_admin_users:
raise HTTPException(
status_code=HTTPStatus.UNAUTHORIZED,
detail="User not authorized. No admin privileges.",
)
return user

View File

@ -6,9 +6,9 @@ from typing import Any, List, NamedTuple, Optional
import jinja2 import jinja2
import shortuuid # type: ignore import shortuuid # type: ignore
import lnbits.settings as settings
from lnbits.jinja2_templating import Jinja2Templates from lnbits.jinja2_templating import Jinja2Templates
from lnbits.requestvars import g from lnbits.requestvars import g
from lnbits.settings import settings
class Extension(NamedTuple): class Extension(NamedTuple):
@ -24,15 +24,10 @@ class Extension(NamedTuple):
class ExtensionManager: class ExtensionManager:
def __init__(self): def __init__(self):
if settings.LNBITS_ADMIN_UI: self._disabled: List[str] = settings.lnbits_disabled_extensions
settings.LNBITS_DISABLED_EXTENSIONS = g().admin_conf.disabled_ext self._admin_only: List[str] = settings.lnbits_admin_extensions
settings.LNBITS_ADMIN_EXTENSIONS = g().admin_conf.admin_ext
self._disabled: List[str] = settings.LNBITS_DISABLED_EXTENSIONS
self._admin_only: List[str] = [
x.strip(" ") for x in settings.LNBITS_ADMIN_EXTENSIONS
]
self._extension_folders: List[str] = [ self._extension_folders: List[str] = [
x[1] for x in os.walk(os.path.join(settings.LNBITS_PATH, "extensions")) x[1] for x in os.walk(os.path.join(settings.lnbits_path, "extensions"))
][0] ][0]
@property @property
@ -48,7 +43,7 @@ class ExtensionManager:
try: try:
with open( with open(
os.path.join( os.path.join(
settings.LNBITS_PATH, "extensions", extension, "config.json" settings.lnbits_path, "extensions", extension, "config.json"
) )
) as json_file: ) as json_file:
config = json.load(json_file) config = json.load(json_file)
@ -120,7 +115,7 @@ def get_css_vendored(prefer_minified: bool = False) -> List[str]:
def get_vendored(ext: str, prefer_minified: bool = False) -> List[str]: def get_vendored(ext: str, prefer_minified: bool = False) -> List[str]:
paths: List[str] = [] paths: List[str] = []
for path in glob.glob( for path in glob.glob(
os.path.join(settings.LNBITS_PATH, "static/vendor/**"), recursive=True os.path.join(settings.lnbits_path, "static/vendor/**"), recursive=True
): ):
if path.endswith(".min" + ext): if path.endswith(".min" + ext):
# path is minified # path is minified
@ -146,7 +141,7 @@ def get_vendored(ext: str, prefer_minified: bool = False) -> List[str]:
def url_for_vendored(abspath: str) -> str: def url_for_vendored(abspath: str) -> str:
return "/" + os.path.relpath(abspath, settings.LNBITS_PATH) return "/" + os.path.relpath(abspath, settings.lnbits_path)
def url_for(endpoint: str, external: Optional[bool] = False, **params: Any) -> str: def url_for(endpoint: str, external: Optional[bool] = False, **params: Any) -> str:
@ -163,16 +158,6 @@ def removeEmptyString(arr):
def template_renderer(additional_folders: List = []) -> Jinja2Templates: def template_renderer(additional_folders: List = []) -> Jinja2Templates:
if settings.LNBITS_ADMIN_UI:
_ = g().admin_conf
settings.LNBITS_AD_SPACE = _.ad_space
settings.LNBITS_HIDE_API = _.hide_api
settings.LNBITS_SITE_TITLE = _.site_title
settings.LNBITS_DENOMINATION = _.denomination
settings.LNBITS_SITE_TAGLINE = _.site_tagline
settings.LNBITS_SITE_DESCRIPTION = _.site_description
settings.LNBITS_THEME_OPTIONS = _.theme
settings.LNBITS_CUSTOM_LOGO = _.custom_logo
t = Jinja2Templates( t = Jinja2Templates(
loader=jinja2.FileSystemLoader( loader=jinja2.FileSystemLoader(
@ -180,20 +165,21 @@ def template_renderer(additional_folders: List = []) -> Jinja2Templates:
) )
) )
if settings.LNBITS_AD_SPACE: if settings.lnbits_ad_space:
t.env.globals["AD_SPACE"] = settings.LNBITS_AD_SPACE t.env.globals["AD_SPACE"] = settings.lnbits_ad_space
t.env.globals["HIDE_API"] = settings.LNBITS_HIDE_API
t.env.globals["SITE_TITLE"] = settings.LNBITS_SITE_TITLE
t.env.globals["LNBITS_DENOMINATION"] = settings.LNBITS_DENOMINATION
t.env.globals["SITE_TAGLINE"] = settings.LNBITS_SITE_TAGLINE
t.env.globals["SITE_DESCRIPTION"] = settings.LNBITS_SITE_DESCRIPTION
t.env.globals["LNBITS_THEME_OPTIONS"] = settings.LNBITS_THEME_OPTIONS
t.env.globals["LNBITS_VERSION"] = settings.LNBITS_COMMIT
t.env.globals["EXTENSIONS"] = get_valid_extensions()
if settings.LNBITS_CUSTOM_LOGO:
t.env.globals["USE_CUSTOM_LOGO"] = settings.LNBITS_CUSTOM_LOGO
if settings.DEBUG: t.env.globals["HIDE_API"] = settings.lnbits_hide_api
t.env.globals["SITE_TITLE"] = settings.lnbits_site_title
t.env.globals["LNBITS_DENOMINATION"] = settings.lnbits_denomination
t.env.globals["SITE_TAGLINE"] = settings.lnbits_site_tagline
t.env.globals["SITE_DESCRIPTION"] = settings.lnbits_site_description
t.env.globals["LNBITS_THEME_OPTIONS"] = settings.lnbits_theme_options
t.env.globals["LNBITS_VERSION"] = settings.lnbits_commit
t.env.globals["EXTENSIONS"] = get_valid_extensions()
if settings.lnbits_custom_logo:
t.env.globals["USE_CUSTOM_LOGO"] = settings.lnbits_custom_logo
if settings.debug:
t.env.globals["VENDORED_JS"] = map(url_for_vendored, get_js_vendored()) t.env.globals["VENDORED_JS"] = map(url_for_vendored, get_js_vendored())
t.env.globals["VENDORED_CSS"] = map(url_for_vendored, get_css_vendored()) t.env.globals["VENDORED_CSS"] = map(url_for_vendored, get_css_vendored())
else: else:

View File

@ -1,9 +1,16 @@
import asyncio
import uvloop
uvloop.install()
import contextlib
import multiprocessing as mp
import sys
import time import time
import click import click
import uvicorn import uvicorn
from lnbits.settings import HOST, PORT from lnbits.settings import settings
@click.command( @click.command(
@ -12,12 +19,13 @@ from lnbits.settings import HOST, PORT
allow_extra_args=True, allow_extra_args=True,
) )
) )
@click.option("--port", default=PORT, help="Port to listen on") @click.option("--port", default=settings.port, help="Port to listen on")
@click.option("--host", default=HOST, help="Host to run LNBits on") @click.option("--host", default=settings.host, help="Host to run LNBits on")
@click.option("--reload", is_flag=True, help="Reload LNBits on changes in code")
@click.option("--ssl-keyfile", default=None, help="Path to SSL keyfile") @click.option("--ssl-keyfile", default=None, help="Path to SSL keyfile")
@click.option("--ssl-certfile", default=None, help="Path to SSL certificate") @click.option("--ssl-certfile", default=None, help="Path to SSL certificate")
@click.pass_context @click.pass_context
def main(ctx, port: int, host: str, ssl_keyfile: str, ssl_certfile: str): def main(ctx, port: int, host: str, ssl_keyfile: str, ssl_certfile: str, reload: bool):
"""Launched with `poetry run lnbits` at root level""" """Launched with `poetry run lnbits` at root level"""
# this beautiful beast parses all command line arguments and passes them to the uvicorn server # this beautiful beast parses all command line arguments and passes them to the uvicorn server
d = dict() d = dict()
@ -33,17 +41,31 @@ def main(ctx, port: int, host: str, ssl_keyfile: str, ssl_certfile: str):
else: else:
d[a.strip("--")] = True # argument like --key d[a.strip("--")] = True # argument like --key
config = uvicorn.Config( while True:
"lnbits.__main__:app", # loop = asyncio.new_event_loop()
port=port, config = uvicorn.Config(
host=host, "lnbits.__main__:app",
ssl_keyfile=ssl_keyfile, port=port,
ssl_certfile=ssl_certfile, host=host,
**d reload=reload,
) # loop=loop,
server = uvicorn.Server(config) ssl_keyfile=ssl_keyfile,
server.run() ssl_certfile=ssl_certfile,
**d
)
server = uvicorn.Server(config=config)
process = mp.Process(target=server.run)
process.start()
server_restart.wait()
server_restart.clear()
server.should_exit = True
server.force_exit = True
process.terminate()
process.join()
time.sleep(3)
server_restart = mp.Event()
if __name__ == "__main__": if __name__ == "__main__":
main() main()

View File

@ -1,88 +1,220 @@
import importlib import importlib
import json
import subprocess import subprocess
from os import path from os import path
from typing import List from sqlite3 import Row
from typing import List, Optional
from environs import Env from loguru import logger
from pydantic import BaseSettings, Field, validator
def list_parse_fallback(v):
try:
return json.loads(v)
except Exception as e:
replaced = v.replace(" ", "")
if replaced:
return replaced.split(",")
else:
return []
read_only_variables = ["host", "port", "lnbits_commit"]
class Settings(BaseSettings):
lnbits_admin_ui: bool = Field(default=False)
# .env
debug: Optional[bool]
host: Optional[str]
port: Optional[int]
lnbits_path: Optional[str] = path.dirname(path.realpath(__file__))
lnbits_commit: str = Field(default="unknown")
# users
lnbits_admin_users: List[str] = Field(default=[])
lnbits_allowed_users: List[str] = Field(default=[])
lnbits_admin_extensions: List[str] = Field(default=[])
lnbits_disabled_extensions: List[str] = Field(default=[])
# Change theme
lnbits_site_title: str = Field(default="LNbits")
lnbits_site_tagline: str = Field(default="free and open-source lightning wallet")
lnbits_site_description: str = Field(default=None)
lnbits_default_wallet_name: str = Field(default="LNbits wallet")
lnbits_theme_options: List[str] = Field(
default=["classic", "flamingo", "mint", "salvador", "monochrome", "autumn"]
)
lnbits_custom_logo: str = Field(default=None)
lnbits_ad_space: List[str]
# ops
lnbits_data_folder: str = Field(default="./data")
lnbits_database_url: str = Field(default=None)
lnbits_force_https: bool = Field(default=True)
lnbits_reserve_fee_min: int = Field(default=4000)
lnbits_reserve_fee_percent: float = Field(default=1.0)
lnbits_service_fee: float = Field(default=0)
lnbits_hide_api: bool = Field(default=False)
lnbits_denomination: str = Field(default="sats")
# funding sources
lnbits_backend_wallet_class: str = Field(default="VoidWallet")
lnbits_allowed_funding_sources: List[str] = Field(
default=[
"CLightningWallet",
"LndRestWallet",
"LndWallet",
"LntxbotWallet",
"LNPayWallet",
"LnbitsWallet",
"OpenNodeWallet",
]
)
fake_wallet_secret: str = Field(default="ToTheMoon1")
lnbits_endpoint: str = Field(default="https://legend.lnbits.com")
lnbits_key: Optional[str] = Field(default=None)
cliche_endpoint: Optional[str] = Field(default=None)
corelightning_rpc: Optional[str] = Field(default=None)
eclair_url: Optional[str] = Field(default=None)
eclair_pass: Optional[str] = Field(default=None)
lnd_rest_endpoint: Optional[str] = Field(default=None)
lnd_rest_cert: Optional[str] = Field(default=None)
lnd_rest_macaroon: Optional[str] = Field(default=None)
lnpay_api_endpoint: Optional[str] = Field(default=None)
lnpay_api_key: Optional[str] = Field(default=None)
lnpay_wallet_key: Optional[str] = Field(default=None)
lntxbot_api_endpoint: Optional[str] = Field(default=None)
lntxbot_key: Optional[str] = Field(default=None)
opennode_api_endpoint: Optional[str] = Field(default=None)
opennode_key: Optional[str] = Field(default=None)
spark_url: Optional[str] = Field(default=None)
spark_token: Optional[str] = Field(default=None)
# boltz
boltz_network: str = Field(default="main")
boltz_url: str = Field(default="https://boltz.exchange/api")
boltz_mempool_space_url: str = Field(default="https://mempool.space")
boltz_mempool_space_url_ws: str = Field(default="wss://mempool.space")
@validator(
"lnbits_admin_users",
"lnbits_allowed_users",
"lnbits_theme_options",
"lnbits_ad_space",
"lnbits_admin_extensions",
"lnbits_disabled_extensions",
"lnbits_allowed_funding_sources",
pre=True,
)
def validate(cls, val):
if type(val) == str:
val = val.split(",") if val else []
return val
@classmethod
def from_row(cls, row: Row) -> "Settings":
data = dict(row)
return cls(**data)
class Config:
env_file = ".env"
env_file_encoding = "utf-8"
case_sensitive = False
json_loads = list_parse_fallback
settings = Settings()
settings.lnbits_commit = (
subprocess.check_output(
["git", "-C", settings.lnbits_path, "rev-parse", "HEAD"],
stderr=subprocess.DEVNULL,
)
.strip()
.decode("ascii")
)
if not settings.lnbits_admin_ui:
logger.debug(f"Enviroment Settings:")
for key, value in settings.dict(exclude_none=True).items():
logger.debug(f"{key}: {value}")
async def check_admin_settings():
if settings.lnbits_admin_ui:
try:
ext_db = importlib.import_module(f"lnbits.extensions.admin").db
except:
logger.error("could not import module lnbits.extensions.admin database")
raise
async with ext_db.connect() as db:
try:
row = await db.fetchone("SELECT * FROM admin.settings")
if not row or len(row) == 0:
logger.warning(
"admin.settings empty. inserting new settings and creating admin account"
)
from lnbits.core.crud import create_account
account = await create_account()
settings.lnbits_admin_users.insert(0, account.id)
keys = []
values = ""
for key, value in settings.dict(exclude_none=True).items():
keys.append(key)
if type(value) == list:
joined = ",".join(value)
values += f"'{joined}'"
if type(value) == int or type(value) == float:
values += str(value)
if type(value) == bool:
values += "true" if value else "false"
if type(value) == str:
value = value.replace("'", "")
values += f"'{value}'"
values += ","
q = ", ".join(keys)
v = values.rstrip(",")
sql = f"INSERT INTO admin.settings ({q}) VALUES ({v})"
await db.execute(sql)
logger.warning(
"initialized admin.settings from enviroment variables."
)
row = await db.fetchone("SELECT * FROM admin.settings")
assert row, "Newly updated settings couldn't be retrieved"
admin = Settings(**row)
logger.debug(f"Admin settings:")
for key, value in admin.dict(exclude_none=True).items():
if not key in read_only_variables:
try:
setattr(settings, key, value)
logger.debug(f"{key}: {value}")
except:
logger.error(
f"error overriding setting: {key}, value: {value}"
)
http = "https" if settings.lnbits_force_https else "http"
user = settings.lnbits_admin_users[0]
logger.warning(
f" ✔️ Access admin user account at: {http}://{settings.host}:{settings.port}/wallet?usr={user}"
)
except:
logger.warning("admin.settings tables does not exist.")
raise
env = Env()
env.read_env()
wallets_module = importlib.import_module("lnbits.wallets") wallets_module = importlib.import_module("lnbits.wallets")
wallet_class = getattr( wallet_class = getattr(wallets_module, settings.lnbits_backend_wallet_class)
wallets_module, env.str("LNBITS_BACKEND_WALLET_CLASS", default="VoidWallet")
)
DEBUG = env.bool("DEBUG", default=False)
HOST = env.str("HOST", default="127.0.0.1")
PORT = env.int("PORT", default=5000)
LNBITS_PATH = path.dirname(path.realpath(__file__))
LNBITS_DATA_FOLDER = env.str(
"LNBITS_DATA_FOLDER", default=path.join(LNBITS_PATH, "data")
)
LNBITS_DATABASE_URL = env.str("LNBITS_DATABASE_URL", default=None)
LNBITS_ALLOWED_USERS: List[str] = [
x.strip(" ") for x in env.list("LNBITS_ALLOWED_USERS", default=[], subcast=str)
]
LNBITS_ADMIN_UI = env.bool("LNBITS_ADMIN_UI", default=False)
LNBITS_ADMIN_USERS: List[str] = [
x.strip(" ") for x in env.list("LNBITS_ADMIN_USERS", default=[], subcast=str)
]
LNBITS_ADMIN_EXTENSIONS: List[str] = [
x.strip(" ") for x in env.list("LNBITS_ADMIN_EXTENSIONS", default=[], subcast=str)
]
LNBITS_DISABLED_EXTENSIONS: List[str] = [
x.strip(" ")
for x in env.list("LNBITS_DISABLED_EXTENSIONS", default=[], subcast=str)
]
LNBITS_AD_SPACE = [x.strip(" ") for x in env.list("LNBITS_AD_SPACE", default=[])]
LNBITS_HIDE_API = env.bool("LNBITS_HIDE_API", default=False)
LNBITS_SITE_TITLE = env.str("LNBITS_SITE_TITLE", default="LNbits")
LNBITS_DENOMINATION = env.str("LNBITS_DENOMINATION", default="sats")
LNBITS_SITE_TAGLINE = env.str(
"LNBITS_SITE_TAGLINE", default="free and open-source lightning wallet"
)
LNBITS_SITE_DESCRIPTION = env.str("LNBITS_SITE_DESCRIPTION", default="")
LNBITS_THEME_OPTIONS: List[str] = [
x.strip(" ")
for x in env.list(
"LNBITS_THEME_OPTIONS",
default="classic, flamingo, mint, salvador, monochrome, autumn",
subcast=str,
)
]
LNBITS_CUSTOM_LOGO = env.str("LNBITS_CUSTOM_LOGO", default="")
WALLET = wallet_class() WALLET = wallet_class()
FAKE_WALLET = getattr(wallets_module, "FakeWallet")() FAKE_WALLET = getattr(wallets_module, "FakeWallet")()
DEFAULT_WALLET_NAME = env.str("LNBITS_DEFAULT_WALLET_NAME", default="LNbits wallet")
PREFER_SECURE_URLS = env.bool("LNBITS_FORCE_HTTPS", default=True)
RESERVE_FEE_MIN = env.int("LNBITS_RESERVE_FEE_MIN", default=2000)
RESERVE_FEE_PERCENT = env.float("LNBITS_RESERVE_FEE_PERCENT", default=1.0)
SERVICE_FEE = env.float("LNBITS_SERVICE_FEE", default=0.0)
try:
LNBITS_COMMIT = (
subprocess.check_output(
["git", "-C", LNBITS_PATH, "rev-parse", "HEAD"], stderr=subprocess.DEVNULL
)
.strip()
.decode("ascii")
)
except:
LNBITS_COMMIT = "unknown"
BOLTZ_NETWORK = env.str("BOLTZ_NETWORK", default="main")
BOLTZ_URL = env.str("BOLTZ_URL", default="https://boltz.exchange/api")
BOLTZ_MEMPOOL_SPACE_URL = env.str(
"BOLTZ_MEMPOOL_SPACE_URL", default="https://mempool.space"
)
BOLTZ_MEMPOOL_SPACE_URL_WS = env.str(
"BOLTZ_MEMPOOL_SPACE_URL_WS", default="wss://mempool.space"
)

View File

@ -2,12 +2,12 @@ import asyncio
import hashlib import hashlib
import random import random
from datetime import datetime from datetime import datetime
from os import getenv
from typing import AsyncGenerator, Dict, Optional from typing import AsyncGenerator, Dict, Optional
from environs import Env # type: ignore
from loguru import logger from loguru import logger
from lnbits.settings import settings
from ..bolt11 import Invoice, decode, encode from ..bolt11 import Invoice, decode, encode
from .base import ( from .base import (
InvoiceResponse, InvoiceResponse,
@ -17,9 +17,6 @@ from .base import (
Wallet, Wallet,
) )
env = Env()
env.read_env()
class FakeWallet(Wallet): class FakeWallet(Wallet):
queue: asyncio.Queue = asyncio.Queue(0) queue: asyncio.Queue = asyncio.Queue(0)
@ -47,7 +44,7 @@ class FakeWallet(Wallet):
) -> InvoiceResponse: ) -> InvoiceResponse:
# we set a default secret since FakeWallet is used for internal=True invoices # we set a default secret since FakeWallet is used for internal=True invoices
# and the user might not have configured a secret yet # and the user might not have configured a secret yet
secret = settings.fake_wallet_secret
data: Dict = { data: Dict = {
"out": False, "out": False,
"amount": amount, "amount": amount,