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 loguru import logger
from lnbits.settings import Settings, settings
from .core import db as core_db
from .core import migrations as core_migrations
from .db import COCKROACH, POSTGRES, SQLITE
@ -17,7 +19,8 @@ from .helpers import (
get_valid_extensions,
url_for_vendored,
)
from .settings import LNBITS_PATH
path = settings.lnbits_path
@click.command("migrate")
@ -36,15 +39,15 @@ def transpile_scss():
warnings.simplefilter("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(LNBITS_PATH, "static/css/base.css"), "w") as css:
with open(os.path.join(path, "static/scss/base.scss")) as scss:
with open(os.path.join(path, "static/css/base.css"), "w") as css:
css.write(compile_string(scss.read()))
def bundle_vendored():
for getfiles, outputpath in [
(get_js_vendored, os.path.join(LNBITS_PATH, "static/bundle.js")),
(get_css_vendored, os.path.join(LNBITS_PATH, "static/bundle.css")),
(get_js_vendored, os.path.join(path, "static/bundle.js")),
(get_css_vendored, os.path.join(path, "static/bundle.css")),
]:
output = ""
for path in getfiles():
@ -54,33 +57,6 @@ def bundle_vendored():
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():
"""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.db import COCKROACH, POSTGRES, Connection
from lnbits.settings import DEFAULT_WALLET_NAME, LNBITS_ADMIN_USERS
from lnbits.settings import settings
from . import db
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"],
extensions=[e[0] for e in extensions],
wallets=[Wallet(**w) for w in wallets],
admin=user["id"] in [x.strip() for x in LNBITS_ADMIN_USERS]
if LNBITS_ADMIN_USERS
admin=user["id"] in settings.lnbits_admin_users
if settings.lnbits_admin_users
else False,
)
@ -99,7 +99,7 @@ async def create_wallet(
""",
(
wallet_id,
wallet_name or DEFAULT_WALLET_NAME,
wallet_name or settings.lnbits_default_wallet_name,
user_id,
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.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 . 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
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,
)
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 (
currencies,
fiat_amount_as_satoshis,
@ -76,7 +76,7 @@ async def api_wallet(wallet: WalletTypeInfo = Depends(get_key_type)):
async def api_update_balance(
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(
status_code=HTTPStatus.FORBIDDEN, detail="Not an admin user"
)
@ -178,7 +178,7 @@ async def api_payments_create_invoice(data: CreateInvoiceData, wallet: Wallet):
else:
description_hash = b""
unhashed_description = b""
memo = data.memo or LNBITS_SITE_TITLE
memo = data.memo or settings.lnbits_site_title
if data.unit == "sat":
amount = int(data.amount)
else:
@ -678,7 +678,7 @@ async def img(request: Request, data):
@core_app.get("/api/v1/audit/")
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(
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.helpers import template_renderer, url_for
from lnbits.requestvars import g
from lnbits.settings import (
LNBITS_ADMIN_UI,
LNBITS_ADMIN_USERS,
LNBITS_ALLOWED_USERS,
LNBITS_CUSTOM_LOGO,
LNBITS_SITE_TITLE,
SERVICE_FEE,
)
from lnbits.settings import settings
from ...helpers import get_valid_extensions
from ..crud import (
@ -119,14 +112,6 @@ async def wallet(
user_id = usr.hex if usr else None
wallet_id = wal.hex if wal else None
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:
user = await get_user((await create_account()).id)
@ -137,11 +122,14 @@ async def wallet(
return template_renderer().TemplateResponse(
"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(
"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
if not wallet_id:
if user.wallets and not wallet_name: # type: ignore
@ -172,7 +160,7 @@ async def wallet(
"request": request,
"user": user.dict(), # type: ignore
"wallet": userwallet.dict(),
"service_fee": service_fee,
"service_fee": settings.lnbits_service_fee,
"web_manifest": f"/manifest/{user.id}.webmanifest", # type: ignore
},
)
@ -194,7 +182,7 @@ async def lnurl_full_withdraw(request: Request):
"k1": "0",
"minWithdrawable": 1000 if wallet.withdrawable_balance else 0,
"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),
}
@ -293,12 +281,12 @@ async def manifest(usr: str):
raise HTTPException(status_code=HTTPStatus.NOT_FOUND)
return {
"short_name": LNBITS_SITE_TITLE,
"name": LNBITS_SITE_TITLE + " Wallet",
"short_name": settings.lnbits_site_title,
"name": settings.lnbits_site_title + " Wallet",
"icons": [
{
"src": LNBITS_CUSTOM_LOGO
if LNBITS_CUSTOM_LOGO
"src": settings.lnbits_custom_logo
if settings.lnbits_custom_logo
else "https://cdn.jsdelivr.net/gh/lnbits/lnbits@0.3.0/docs/logos/lnbits.png",
"type": "image/png",
"sizes": "900x900",

View File

@ -10,7 +10,7 @@ from sqlalchemy import create_engine
from sqlalchemy_aio.base import AsyncConnection
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"
COCKROACH = "COCKROACH"
@ -85,8 +85,8 @@ class Database(Compat):
def __init__(self, db_name: str):
self.name = db_name
if LNBITS_DATABASE_URL:
database_uri = LNBITS_DATABASE_URL
if settings.lnbits_database_url:
database_uri = settings.lnbits_database_url
if database_uri.startswith("cockroachdb://"):
self.type = COCKROACH
@ -131,14 +131,16 @@ class Database(Compat):
)
)
else:
if os.path.isdir(LNBITS_DATA_FOLDER):
self.path = os.path.join(LNBITS_DATA_FOLDER, f"{self.name}.sqlite3")
if os.path.isdir(settings.lnbits_data_folder):
self.path = os.path.join(
settings.lnbits_data_folder, f"{self.name}.sqlite3"
)
database_uri = f"sqlite:///{self.path}"
self.type = SQLITE
else:
raise NotADirectoryError(
f"LNBITS_DATA_FOLDER named {LNBITS_DATA_FOLDER} was not created"
f" - please 'mkdir {LNBITS_DATA_FOLDER}' and try again"
f"LNBITS_DATA_FOLDER named {settings.lnbits_data_folder} was not created"
f" - please 'mkdir {settings.lnbits_data_folder}' and try again"
)
logger.trace(f"database {self.type} added for {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.models import User, Wallet
from lnbits.requestvars import g
from lnbits.settings import (
LNBITS_ADMIN_EXTENSIONS,
LNBITS_ADMIN_UI,
LNBITS_ADMIN_USERS,
LNBITS_ALLOWED_USERS,
)
from lnbits.settings import settings
class KeyChecker(SecurityBase):
@ -139,11 +134,6 @@ async def get_key_type(
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(
[0, 1], [WalletAdminKeyChecker, WalletInvoiceKeyChecker]
):
@ -156,8 +146,12 @@ async def get_key_type(
status_code=HTTPStatus.NOT_FOUND, detail="Wallet does not exist."
)
if (
LNBITS_ADMIN_USERS and wallet.wallet.user not in LNBITS_ADMIN_USERS
) and (LNBITS_ADMIN_EXTENSIONS and pathname in LNBITS_ADMIN_EXTENSIONS):
settings.lnbits_admin_users
and wallet.wallet.user not in settings.lnbits_admin_users
) and (
settings.lnbits_admin_extensions
and pathname in settings.lnbits_admin_extensions
):
raise HTTPException(
status_code=HTTPStatus.FORBIDDEN,
detail="User not authorized for this extension.",
@ -233,22 +227,33 @@ async def require_invoice_key(
async def check_user_exists(usr: UUID4) -> User:
g().user = await get_user(usr.hex)
if not g().user:
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:
LNBITS_ADMIN_USERS = g().admin_conf.admin_users
LNBITS_ALLOWED_USERS = g().admin_conf.allowed_users
else:
LNBITS_ADMIN_USERS = []
LNBITS_ALLOWED_USERS = []
if LNBITS_ALLOWED_USERS and g().user.id not in LNBITS_ALLOWED_USERS:
if (
len(settings.lnbits_allowed_users) > 0
and g().user.id not in settings.lnbits_allowed_users
):
raise HTTPException(
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
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 shortuuid # type: ignore
import lnbits.settings as settings
from lnbits.jinja2_templating import Jinja2Templates
from lnbits.requestvars import g
from lnbits.settings import settings
class Extension(NamedTuple):
@ -24,15 +24,10 @@ class Extension(NamedTuple):
class ExtensionManager:
def __init__(self):
if settings.LNBITS_ADMIN_UI:
settings.LNBITS_DISABLED_EXTENSIONS = g().admin_conf.disabled_ext
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._disabled: List[str] = settings.lnbits_disabled_extensions
self._admin_only: List[str] = settings.lnbits_admin_extensions
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]
@property
@ -48,7 +43,7 @@ class ExtensionManager:
try:
with open(
os.path.join(
settings.LNBITS_PATH, "extensions", extension, "config.json"
settings.lnbits_path, "extensions", extension, "config.json"
)
) as 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]:
paths: List[str] = []
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):
# 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:
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:
@ -163,16 +158,6 @@ def removeEmptyString(arr):
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(
loader=jinja2.FileSystemLoader(
@ -180,20 +165,21 @@ def template_renderer(additional_folders: List = []) -> Jinja2Templates:
)
)
if 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.lnbits_ad_space:
t.env.globals["AD_SPACE"] = settings.lnbits_ad_space
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_CSS"] = map(url_for_vendored, get_css_vendored())
else:

View File

@ -1,9 +1,16 @@
import asyncio
import uvloop
uvloop.install()
import contextlib
import multiprocessing as mp
import sys
import time
import click
import uvicorn
from lnbits.settings import HOST, PORT
from lnbits.settings import settings
@click.command(
@ -12,12 +19,13 @@ from lnbits.settings import HOST, PORT
allow_extra_args=True,
)
)
@click.option("--port", default=PORT, help="Port to listen on")
@click.option("--host", default=HOST, help="Host to run LNBits on")
@click.option("--port", default=settings.port, help="Port to listen 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-certfile", default=None, help="Path to SSL certificate")
@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"""
# this beautiful beast parses all command line arguments and passes them to the uvicorn server
d = dict()
@ -33,17 +41,31 @@ def main(ctx, port: int, host: str, ssl_keyfile: str, ssl_certfile: str):
else:
d[a.strip("--")] = True # argument like --key
config = uvicorn.Config(
"lnbits.__main__:app",
port=port,
host=host,
ssl_keyfile=ssl_keyfile,
ssl_certfile=ssl_certfile,
**d
)
server = uvicorn.Server(config)
server.run()
while True:
# loop = asyncio.new_event_loop()
config = uvicorn.Config(
"lnbits.__main__:app",
port=port,
host=host,
reload=reload,
# loop=loop,
ssl_keyfile=ssl_keyfile,
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__":
main()

View File

@ -1,88 +1,220 @@
import importlib
import json
import subprocess
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")
wallet_class = getattr(
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_class = getattr(wallets_module, settings.lnbits_backend_wallet_class)
WALLET = wallet_class()
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 random
from datetime import datetime
from os import getenv
from typing import AsyncGenerator, Dict, Optional
from environs import Env # type: ignore
from loguru import logger
from lnbits.settings import settings
from ..bolt11 import Invoice, decode, encode
from .base import (
InvoiceResponse,
@ -17,9 +17,6 @@ from .base import (
Wallet,
)
env = Env()
env.read_env()
class FakeWallet(Wallet):
queue: asyncio.Queue = asyncio.Queue(0)
@ -47,7 +44,7 @@ class FakeWallet(Wallet):
) -> InvoiceResponse:
# we set a default secret since FakeWallet is used for internal=True invoices
# and the user might not have configured a secret yet
secret = settings.fake_wallet_secret
data: Dict = {
"out": False,
"amount": amount,