Deactivate all extensions flag (#2206)
* feat: allow all extension deactivation * doc: updated comment * fix: make sure `register_routes` executes after installed extensions are checked * chore: code format * fix: do not run migration on deactivated extensions * fix: make sure the deactivated extension list is loaded in time * feat: register extension routes if extension never loaded before * fix: move `load_disabled_extension_list` * doc: disable by default
This commit is contained in:
parent
0d2447faf3
commit
26ca8c71d7
8 changed files with 57 additions and 24 deletions
|
|
@ -141,6 +141,9 @@ LNBITS_ADMIN_USERS=""
|
||||||
# Extensions only admin can access
|
# Extensions only admin can access
|
||||||
LNBITS_ADMIN_EXTENSIONS="ngrok, admin"
|
LNBITS_ADMIN_EXTENSIONS="ngrok, admin"
|
||||||
|
|
||||||
|
# Start LNbits core only. The extensions are not loaded.
|
||||||
|
# LNBITS_EXTENSIONS_DEACTIVATE_ALL=true
|
||||||
|
|
||||||
# Disable account creation for new users
|
# Disable account creation for new users
|
||||||
# LNBITS_ALLOW_NEW_ACCOUNTS=false
|
# LNBITS_ALLOW_NEW_ACCOUNTS=false
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -10,7 +10,7 @@ import traceback
|
||||||
from hashlib import sha256
|
from hashlib import sha256
|
||||||
from http import HTTPStatus
|
from http import HTTPStatus
|
||||||
from pathlib import Path
|
from pathlib import Path
|
||||||
from typing import Callable, List
|
from typing import Callable, List, Optional
|
||||||
|
|
||||||
from fastapi import FastAPI, HTTPException, Request
|
from fastapi import FastAPI, HTTPException, Request
|
||||||
from fastapi.exceptions import RequestValidationError
|
from fastapi.exceptions import RequestValidationError
|
||||||
|
|
@ -35,7 +35,7 @@ from lnbits.tasks import cancel_all_tasks, create_permanent_task
|
||||||
from lnbits.utils.cache import cache
|
from lnbits.utils.cache import cache
|
||||||
from lnbits.wallets import get_wallet_class, set_wallet_class
|
from lnbits.wallets import get_wallet_class, set_wallet_class
|
||||||
|
|
||||||
from .commands import db_versions, load_disabled_extension_list, migrate_databases
|
from .commands import db_versions, migrate_databases
|
||||||
from .core import init_core_routers
|
from .core import init_core_routers
|
||||||
from .core.db import core_app_extra
|
from .core.db import core_app_extra
|
||||||
from .core.services import check_admin_settings, check_webpush_settings
|
from .core.services import check_admin_settings, check_webpush_settings
|
||||||
|
|
@ -112,7 +112,6 @@ def create_app() -> FastAPI:
|
||||||
add_ratelimit_middleware(app)
|
add_ratelimit_middleware(app)
|
||||||
|
|
||||||
register_startup(app)
|
register_startup(app)
|
||||||
register_routes(app)
|
|
||||||
register_async_tasks(app)
|
register_async_tasks(app)
|
||||||
register_exception_handlers(app)
|
register_exception_handlers(app)
|
||||||
register_shutdown(app)
|
register_shutdown(app)
|
||||||
|
|
@ -189,8 +188,7 @@ async def check_installed_extensions(app: FastAPI):
|
||||||
persist state. Zips that are missing will be re-downloaded.
|
persist state. Zips that are missing will be re-downloaded.
|
||||||
"""
|
"""
|
||||||
shutil.rmtree(os.path.join("lnbits", "upgrades"), True)
|
shutil.rmtree(os.path.join("lnbits", "upgrades"), True)
|
||||||
await load_disabled_extension_list()
|
installed_extensions = await build_all_installed_extensions_list(False)
|
||||||
installed_extensions = await build_all_installed_extensions_list()
|
|
||||||
|
|
||||||
for ext in installed_extensions:
|
for ext in installed_extensions:
|
||||||
try:
|
try:
|
||||||
|
|
@ -212,7 +210,9 @@ async def check_installed_extensions(app: FastAPI):
|
||||||
logger.info(f"{ext.id} ({ext.installed_version})")
|
logger.info(f"{ext.id} ({ext.installed_version})")
|
||||||
|
|
||||||
|
|
||||||
async def build_all_installed_extensions_list() -> List[InstallableExtension]:
|
async def build_all_installed_extensions_list(
|
||||||
|
include_deactivated: Optional[bool] = True,
|
||||||
|
) -> List[InstallableExtension]:
|
||||||
"""
|
"""
|
||||||
Returns a list of all the installed extensions plus the extensions that
|
Returns a list of all the installed extensions plus the extensions that
|
||||||
MUST be installed by default (see LNBITS_EXTENSIONS_DEFAULT_INSTALL).
|
MUST be installed by default (see LNBITS_EXTENSIONS_DEFAULT_INSTALL).
|
||||||
|
|
@ -237,7 +237,17 @@ async def build_all_installed_extensions_list() -> List[InstallableExtension]:
|
||||||
)
|
)
|
||||||
installed_extensions.append(ext_info)
|
installed_extensions.append(ext_info)
|
||||||
|
|
||||||
return installed_extensions
|
if include_deactivated:
|
||||||
|
return installed_extensions
|
||||||
|
|
||||||
|
if settings.lnbits_extensions_deactivate_all:
|
||||||
|
return []
|
||||||
|
|
||||||
|
return [
|
||||||
|
e
|
||||||
|
for e in installed_extensions
|
||||||
|
if e.id not in settings.lnbits_deactivated_extensions
|
||||||
|
]
|
||||||
|
|
||||||
|
|
||||||
def check_installed_extension_files(ext: InstallableExtension) -> bool:
|
def check_installed_extension_files(ext: InstallableExtension) -> bool:
|
||||||
|
|
@ -273,7 +283,7 @@ def register_routes(app: FastAPI) -> None:
|
||||||
"""Register FastAPI routes / LNbits extensions."""
|
"""Register FastAPI routes / LNbits extensions."""
|
||||||
init_core_routers(app)
|
init_core_routers(app)
|
||||||
|
|
||||||
for ext in get_valid_extensions():
|
for ext in get_valid_extensions(False):
|
||||||
try:
|
try:
|
||||||
register_ext_routes(app, ext)
|
register_ext_routes(app, ext)
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
|
|
@ -383,6 +393,9 @@ def register_startup(app: FastAPI):
|
||||||
# check extensions after restart
|
# check extensions after restart
|
||||||
await check_installed_extensions(app)
|
await check_installed_extensions(app)
|
||||||
|
|
||||||
|
# register core and extension routes
|
||||||
|
register_routes(app)
|
||||||
|
|
||||||
if settings.lnbits_admin_ui:
|
if settings.lnbits_admin_ui:
|
||||||
initialize_server_logger()
|
initialize_server_logger()
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -136,7 +136,11 @@ async def migrate_databases():
|
||||||
core_version = current_versions.get("core", 0)
|
core_version = current_versions.get("core", 0)
|
||||||
await run_migration(conn, core_migrations, "core", core_version)
|
await run_migration(conn, core_migrations, "core", core_version)
|
||||||
|
|
||||||
for ext in get_valid_extensions():
|
# here is the first place we can be sure that the
|
||||||
|
# `installed_extensions` table has been created
|
||||||
|
await load_disabled_extension_list()
|
||||||
|
|
||||||
|
for ext in get_valid_extensions(False):
|
||||||
current_version = current_versions.get(ext.code, 0)
|
current_version = current_versions.get(ext.code, 0)
|
||||||
try:
|
try:
|
||||||
await migrate_extension_database(ext, current_version)
|
await migrate_extension_database(ext, current_version)
|
||||||
|
|
|
||||||
|
|
@ -54,8 +54,6 @@ async def stop_extension_background_work(
|
||||||
"""
|
"""
|
||||||
Stop background work for extension (like asyncio.Tasks, WebSockets, etc).
|
Stop background work for extension (like asyncio.Tasks, WebSockets, etc).
|
||||||
Extensions SHOULD expose a DELETE enpoint at the root level of their API.
|
Extensions SHOULD expose a DELETE enpoint at the root level of their API.
|
||||||
This function tries first to call the endpoint using `http`
|
|
||||||
and if it fails it tries using `https`.
|
|
||||||
"""
|
"""
|
||||||
async with httpx.AsyncClient() as client:
|
async with httpx.AsyncClient() as client:
|
||||||
try:
|
try:
|
||||||
|
|
|
||||||
|
|
@ -1,4 +1,5 @@
|
||||||
import asyncio
|
import asyncio
|
||||||
|
import sys
|
||||||
from http import HTTPStatus
|
from http import HTTPStatus
|
||||||
from pathlib import Path
|
from pathlib import Path
|
||||||
from typing import Annotated, List, Optional, Union
|
from typing import Annotated, List, Optional, Union
|
||||||
|
|
@ -11,7 +12,7 @@ from fastapi.routing import APIRouter
|
||||||
from loguru import logger
|
from loguru import logger
|
||||||
from pydantic.types import UUID4
|
from pydantic.types import UUID4
|
||||||
|
|
||||||
from lnbits.core.db import db
|
from lnbits.core.db import core_app_extra, db
|
||||||
from lnbits.core.helpers import to_valid_user_id
|
from lnbits.core.helpers import to_valid_user_id
|
||||||
from lnbits.core.models import User
|
from lnbits.core.models import User
|
||||||
from lnbits.decorators import check_admin, check_user_exists
|
from lnbits.decorators import check_admin, check_user_exists
|
||||||
|
|
@ -74,9 +75,6 @@ async def extensions_install(
|
||||||
):
|
):
|
||||||
await toggle_extension(enable, disable, user.id)
|
await toggle_extension(enable, disable, user.id)
|
||||||
|
|
||||||
# Update user as his extensions have been updated
|
|
||||||
if enable or disable:
|
|
||||||
user = await get_user(user.id) # type: ignore
|
|
||||||
try:
|
try:
|
||||||
installed_exts: List["InstallableExtension"] = await get_installed_extensions()
|
installed_exts: List["InstallableExtension"] = await get_installed_extensions()
|
||||||
installed_exts_ids = [e.id for e in installed_exts]
|
installed_exts_ids = [e.id for e in installed_exts]
|
||||||
|
|
@ -103,20 +101,28 @@ async def extensions_install(
|
||||||
|
|
||||||
try:
|
try:
|
||||||
ext_id = activate or deactivate
|
ext_id = activate or deactivate
|
||||||
|
all_extensions = get_valid_extensions()
|
||||||
|
ext = next((e for e in all_extensions if e.code == ext_id), None)
|
||||||
if ext_id and user.admin:
|
if ext_id and user.admin:
|
||||||
if deactivate and deactivate not in settings.lnbits_deactivated_extensions:
|
if deactivate and deactivate not in settings.lnbits_deactivated_extensions:
|
||||||
settings.lnbits_deactivated_extensions += [deactivate]
|
settings.lnbits_deactivated_extensions += [deactivate]
|
||||||
elif activate:
|
elif activate:
|
||||||
|
# if extension never loaded (was deactivated on server startup)
|
||||||
|
if ext_id not in sys.modules.keys():
|
||||||
|
# run extension start-up routine
|
||||||
|
core_app_extra.register_new_ext_routes(ext)
|
||||||
|
|
||||||
settings.lnbits_deactivated_extensions = list(
|
settings.lnbits_deactivated_extensions = list(
|
||||||
filter(
|
filter(
|
||||||
lambda e: e != activate, settings.lnbits_deactivated_extensions
|
lambda e: e != activate, settings.lnbits_deactivated_extensions
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
|
|
||||||
await update_installed_extension_state(
|
await update_installed_extension_state(
|
||||||
ext_id=ext_id, active=activate is not None
|
ext_id=ext_id, active=activate is not None
|
||||||
)
|
)
|
||||||
|
|
||||||
all_extensions = list(map(lambda e: e.code, get_valid_extensions()))
|
all_ext_ids = list(map(lambda e: e.code, all_extensions))
|
||||||
inactive_extensions = await get_inactive_extensions()
|
inactive_extensions = await get_inactive_extensions()
|
||||||
db_version = await get_dbversions()
|
db_version = await get_dbversions()
|
||||||
extensions = list(
|
extensions = list(
|
||||||
|
|
@ -131,7 +137,7 @@ async def extensions_install(
|
||||||
"dependencies": ext.dependencies,
|
"dependencies": ext.dependencies,
|
||||||
"isInstalled": ext.id in installed_exts_ids,
|
"isInstalled": ext.id in installed_exts_ids,
|
||||||
"hasDatabaseTables": ext.id in db_version,
|
"hasDatabaseTables": ext.id in db_version,
|
||||||
"isAvailable": ext.id in all_extensions,
|
"isAvailable": ext.id in all_ext_ids,
|
||||||
"isAdminOnly": ext.id in settings.lnbits_admin_extensions,
|
"isAdminOnly": ext.id in settings.lnbits_admin_extensions,
|
||||||
"isActive": ext.id not in inactive_extensions,
|
"isActive": ext.id not in inactive_extensions,
|
||||||
"latestRelease": (
|
"latestRelease": (
|
||||||
|
|
|
||||||
|
|
@ -604,11 +604,23 @@ class CreateExtension(BaseModel):
|
||||||
source_repo: str
|
source_repo: str
|
||||||
|
|
||||||
|
|
||||||
def get_valid_extensions() -> List[Extension]:
|
def get_valid_extensions(include_deactivated: Optional[bool] = True) -> List[Extension]:
|
||||||
return [
|
valid_extensions = [
|
||||||
extension for extension in ExtensionManager().extensions if extension.is_valid
|
extension for extension in ExtensionManager().extensions if extension.is_valid
|
||||||
]
|
]
|
||||||
|
|
||||||
|
if include_deactivated:
|
||||||
|
return valid_extensions
|
||||||
|
|
||||||
|
if settings.lnbits_extensions_deactivate_all:
|
||||||
|
return []
|
||||||
|
|
||||||
|
return [
|
||||||
|
e
|
||||||
|
for e in valid_extensions
|
||||||
|
if e.code not in settings.lnbits_deactivated_extensions
|
||||||
|
]
|
||||||
|
|
||||||
|
|
||||||
def version_parse(v: str):
|
def version_parse(v: str):
|
||||||
"""
|
"""
|
||||||
|
|
|
||||||
|
|
@ -71,11 +71,7 @@ def template_renderer(additional_folders: Optional[List] = None) -> Jinja2Templa
|
||||||
settings.lnbits_node_ui and get_node_class() is not None
|
settings.lnbits_node_ui and get_node_class() is not None
|
||||||
)
|
)
|
||||||
t.env.globals["LNBITS_NODE_UI_AVAILABLE"] = get_node_class() is not None
|
t.env.globals["LNBITS_NODE_UI_AVAILABLE"] = get_node_class() is not None
|
||||||
t.env.globals["EXTENSIONS"] = [
|
t.env.globals["EXTENSIONS"] = get_valid_extensions(False)
|
||||||
e
|
|
||||||
for e in get_valid_extensions()
|
|
||||||
if e.code not in settings.lnbits_deactivated_extensions
|
|
||||||
]
|
|
||||||
if settings.lnbits_custom_logo:
|
if settings.lnbits_custom_logo:
|
||||||
t.env.globals["USE_CUSTOM_LOGO"] = settings.lnbits_custom_logo
|
t.env.globals["USE_CUSTOM_LOGO"] = settings.lnbits_custom_logo
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -347,6 +347,7 @@ class EnvSettings(LNbitsSettings):
|
||||||
log_rotation: str = Field(default="100 MB")
|
log_rotation: str = Field(default="100 MB")
|
||||||
log_retention: str = Field(default="3 months")
|
log_retention: str = Field(default="3 months")
|
||||||
server_startup_time: int = Field(default=time())
|
server_startup_time: int = Field(default=time())
|
||||||
|
lnbits_extensions_deactivate_all: bool = Field(default=False)
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def has_default_extension_path(self) -> bool:
|
def has_default_extension_path(self) -> bool:
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue