remove uppgrade_hash

This commit is contained in:
dni
2026-07-09 08:05:59 +02:00
parent 5865a3f96a
commit bb53fe41c8
7 changed files with 22 additions and 60 deletions
+6 -8
View File
@@ -2,7 +2,6 @@ import asyncio
import glob import glob
import importlib import importlib
import os import os
import shutil
import sys import sys
import time import time
from collections.abc import Callable from collections.abc import Callable
@@ -383,11 +382,6 @@ async def restore_installed_extension(app: FastAPI, ext: InstallableExtension):
def register_custom_extensions_path(): def register_custom_extensions_path():
upgrades_dir = settings.lnbits_extensions_upgrade_path
shutil.rmtree(upgrades_dir, True)
Path(upgrades_dir).mkdir(parents=True, exist_ok=True)
sys.path.append(str(upgrades_dir))
if settings.has_default_extension_path: if settings.has_default_extension_path:
return return
default_ext_path = os.path.join("lnbits", "extensions") default_ext_path = os.path.join("lnbits", "extensions")
@@ -441,7 +435,11 @@ def register_ext_tasks(ext: Extension) -> None:
def register_ext_routes(app: FastAPI, ext: Extension) -> None: def register_ext_routes(app: FastAPI, ext: Extension) -> None:
"""Register FastAPI routes for extension.""" """Register FastAPI routes for extension."""
ext_module = importlib.import_module(ext.module_name) module_name = ext.module_name
if module_name in sys.modules:
ext_module = importlib.reload(sys.modules[module_name])
else:
ext_module = importlib.import_module(module_name)
ext_route = getattr(ext_module, f"{ext.code}_ext") ext_route = getattr(ext_module, f"{ext.code}_ext")
@@ -451,7 +449,7 @@ def register_ext_routes(app: FastAPI, ext: Extension) -> None:
else [] else []
) )
settings.activate_extension_paths(ext.code, ext.upgrade_hash, ext_redirects) settings.activate_extension_paths(ext.code, ext_redirects)
# Remove existing routes for this extension before re-registering so that # Remove existing routes for this extension before re-registering so that
# an upgraded extension replaces the old one at the same paths (no prefix). # an upgraded extension replaces the old one at the same paths (no prefix).
-5
View File
@@ -1,6 +1,5 @@
import asyncio import asyncio
import importlib import importlib
import sys
import time import time
from functools import wraps from functools import wraps
from getpass import getpass from getpass import getpass
@@ -377,10 +376,6 @@ async def extensions_update( # noqa: C901
if not await _can_run_operation(url): if not await _can_run_operation(url):
return return
upgrades_dir = settings.lnbits_extensions_upgrade_path
Path(upgrades_dir).mkdir(parents=True, exist_ok=True)
sys.path.append(str(upgrades_dir))
if extension: if extension:
await update_extension(extension, repo_index, source_repo, url, admin_user) await update_extension(extension, repo_index, source_repo, url, admin_user)
return return
-12
View File
@@ -147,21 +147,13 @@ class Extension(BaseModel):
name: str | None = None name: str | None = None
short_description: str | None = None short_description: str | None = None
tile: str | None = None tile: str | None = None
upgrade_hash: str | None = ""
@property @property
def module_name(self) -> str: def module_name(self) -> str:
if self.is_upgrade_extension:
return f"{self.code}-{self.upgrade_hash}"
if settings.has_default_extension_path: if settings.has_default_extension_path:
return f"lnbits.extensions.{self.code}" return f"lnbits.extensions.{self.code}"
return self.code return self.code
@property
def is_upgrade_extension(self) -> bool:
return self.upgrade_hash != ""
@classmethod @classmethod
def from_installable_ext(cls, ext_info: InstallableExtension) -> Extension: def from_installable_ext(cls, ext_info: InstallableExtension) -> Extension:
return Extension( return Extension(
@@ -170,7 +162,6 @@ class Extension(BaseModel):
name=ext_info.name, name=ext_info.name,
short_description=ext_info.short_description, short_description=ext_info.short_description,
tile=ext_info.icon, tile=ext_info.icon,
upgrade_hash=ext_info.hash if ext_info.ext_upgrade_dir.is_dir() else "",
) )
@@ -375,9 +366,6 @@ class InstallableExtension(BaseModel):
@property @property
def module_name(self) -> str: def module_name(self) -> str:
if self.ext_upgrade_dir.is_dir():
return f"{self.id}-{self.hash}"
if settings.has_default_extension_path: if settings.has_default_extension_path:
return f"lnbits.extensions.{self.id}" return f"lnbits.extensions.{self.id}"
return self.id return self.id
+15 -16
View File
@@ -56,11 +56,10 @@ async def install_extension(
else: else:
await update_installed_extension(ext_info) await update_installed_extension(ext_info)
extension = Extension.from_installable_ext(ext_info) if installed_ext:
if extension.is_upgrade_extension:
# call stop while the old routes are still active
await stop_extension_background_work(ext_info.id) await stop_extension_background_work(ext_info.id)
extension = Extension.from_installable_ext(ext_info)
await start_extension_background_work(ext_info.id) await start_extension_background_work(ext_info.id)
return extension return extension
@@ -103,16 +102,16 @@ async def stop_extension_background_work(ext_id: str) -> bool:
Stop background work for extension (like asyncio.Tasks, WebSockets, etc). Stop background work for extension (like asyncio.Tasks, WebSockets, etc).
Extension must expose a `myextension_stop()` function if it is starting tasks. Extension must expose a `myextension_stop()` function if it is starting tasks.
""" """
upgrade_hash = settings.extension_upgrade_hash(ext_id) ext = Extension(code=ext_id, is_valid=True)
ext = Extension(code=ext_id, is_valid=True, upgrade_hash=upgrade_hash) module_name = ext.module_name
try: try:
logger.info(f"Stopping background work for extension '{ext.module_name}'.") logger.info(f"Stopping background work for extension '{module_name}'.")
old_module = importlib.import_module(ext.module_name) old_module = importlib.import_module(module_name)
stop_fn_name = f"{ext_id}_stop" stop_fn_name = f"{ext_id}_stop"
if not hasattr(old_module, stop_fn_name): if not hasattr(old_module, stop_fn_name):
raise ValueError(f"No stop function found for '{ext.module_name}'.") raise ValueError(f"No stop function found for '{module_name}'.")
stop_fn = getattr(old_module, stop_fn_name) stop_fn = getattr(old_module, stop_fn_name)
if stop_fn: if stop_fn:
@@ -120,9 +119,9 @@ async def stop_extension_background_work(ext_id: str) -> bool:
await stop_fn() await stop_fn()
else: else:
stop_fn() stop_fn()
logger.info(f"Stopped background work for extension '{ext.module_name}'.") logger.info(f"Stopped background work for extension '{module_name}'.")
except Exception as ex: except Exception as ex:
logger.warning(f"Failed to stop background work for '{ext.module_name}'.") logger.warning(f"Failed to stop background work for '{module_name}'.")
logger.warning(ex) logger.warning(ex)
return False return False
@@ -135,12 +134,12 @@ async def start_extension_background_work(ext_id: str) -> bool:
Extension CAN expose a `myextension_start()` function if it is starting tasks. Extension CAN expose a `myextension_start()` function if it is starting tasks.
Extension MUST expose a `myextension_stop()` in that case. Extension MUST expose a `myextension_stop()` in that case.
""" """
upgrade_hash = settings.extension_upgrade_hash(ext_id) ext = Extension(code=ext_id, is_valid=True)
ext = Extension(code=ext_id, is_valid=True, upgrade_hash=upgrade_hash) module_name = ext.module_name
try: try:
logger.info(f"Starting background work for extension '{ext.module_name}'.") logger.info(f"Starting background work for extension '{module_name}'.")
new_module = importlib.import_module(ext.module_name) new_module = importlib.import_module(module_name)
start_fn_name = f"{ext_id}_start" start_fn_name = f"{ext_id}_start"
# start function is optional, return False if not found # start function is optional, return False if not found
@@ -153,10 +152,10 @@ async def start_extension_background_work(ext_id: str) -> bool:
await start_fn() await start_fn()
else: else:
start_fn() start_fn()
logger.info(f"Started background work for extension '{ext.module_name}'.") logger.info(f"Started background work for extension '{module_name}'.")
return True return True
except Exception as ex: except Exception as ex:
logger.warning(f"Failed to start background work for '{ext.module_name}'.") logger.warning(f"Failed to start background work for '{module_name}'.")
logger.warning(ex) logger.warning(ex)
return False return False
-11
View File
@@ -166,8 +166,6 @@ class ExchangeRateProvider(BaseModel):
class InstalledExtensionsSettings(LNbitsSettings): class InstalledExtensionsSettings(LNbitsSettings):
# installed extensions that have been deactivated # installed extensions that have been deactivated
lnbits_deactivated_extensions: set[str] = Field(default=set()) lnbits_deactivated_extensions: set[str] = Field(default=set())
# upgraded extensions that require API redirects
lnbits_upgraded_extensions: dict[str, str] = Field(default={})
# list of redirects that extensions want to perform # list of redirects that extensions want to perform
lnbits_extensions_redirects: list[RedirectPath] = Field(default=[]) lnbits_extensions_redirects: list[RedirectPath] = Field(default=[])
@@ -190,16 +188,10 @@ class InstalledExtensionsSettings(LNbitsSettings):
def activate_extension_paths( def activate_extension_paths(
self, self,
ext_id: str, ext_id: str,
upgrade_hash: str | None = None,
ext_redirects: list[dict] | None = None, ext_redirects: list[dict] | None = None,
): ):
self.lnbits_deactivated_extensions.discard(ext_id) self.lnbits_deactivated_extensions.discard(ext_id)
# Track upgrade hashes so that module names can be resolved for
# background-task start/stop (the module lives in the upgrades dir).
if upgrade_hash:
self.lnbits_upgraded_extensions[ext_id] = upgrade_hash
if ext_redirects: if ext_redirects:
self._activate_extension_redirects(ext_id, ext_redirects) self._activate_extension_redirects(ext_id, ext_redirects)
@@ -209,9 +201,6 @@ class InstalledExtensionsSettings(LNbitsSettings):
self.lnbits_deactivated_extensions.add(ext_id) self.lnbits_deactivated_extensions.add(ext_id)
self._remove_extension_redirects(ext_id) self._remove_extension_redirects(ext_id)
def extension_upgrade_hash(self, ext_id: str) -> str:
return settings.lnbits_upgraded_extensions.get(ext_id, "")
def _activate_extension_redirects(self, ext_id: str, ext_redirects: list[dict]): def _activate_extension_redirects(self, ext_id: str, ext_redirects: list[dict]):
ext_redirect_paths = [ ext_redirect_paths = [
RedirectPath(**{"ext_id": ext_id, **er}) for er in ext_redirects RedirectPath(**{"ext_id": ext_id, **er}) for er in ext_redirects
-2
View File
@@ -124,7 +124,6 @@ async def test_install_extension_updates_existing_upgrade_and_preserves_payments
settings.lnbits_data_folder = str(tmp_path / "data") settings.lnbits_data_folder = str(tmp_path / "data")
settings.lnbits_extensions_path = str(tmp_path / "code") settings.lnbits_extensions_path = str(tmp_path / "code")
await create_installed_extension(existing_ext) await create_installed_extension(existing_ext)
updated_ext.ext_upgrade_dir.mkdir(parents=True, exist_ok=True)
extension = await install_extension(updated_ext, skip_download=True) extension = await install_extension(updated_ext, skip_download=True)
stored = await get_installed_extension(ext_id) stored = await get_installed_extension(ext_id)
@@ -134,7 +133,6 @@ async def test_install_extension_updates_existing_upgrade_and_preserves_payments
settings.lnbits_extensions_path = original_extensions_path settings.lnbits_extensions_path = original_extensions_path
assert extension.code == ext_id assert extension.code == ext_id
assert extension.is_upgrade_extension is True
assert stored is not None assert stored is not None
assert stored.meta is not None assert stored.meta is not None
assert stored.meta.payments == [existing_payment] assert stored.meta.payments == [existing_payment]
+1 -6
View File
@@ -216,16 +216,11 @@ def test_installed_extensions_settings_activate_and_deactivate_paths():
} }
] ]
installed.activate_extension_paths( installed.activate_extension_paths("lnurlp", ext_redirects=redirects)
"lnurlp",
upgrade_hash="hash123",
ext_redirects=redirects,
)
redirect = installed.find_extension_redirect("/.well-known/lnurlp", []) redirect = installed.find_extension_redirect("/.well-known/lnurlp", [])
assert redirect is not None assert redirect is not None
assert redirect.ext_id == "lnurlp" assert redirect.ext_id == "lnurlp"
assert installed.lnbits_upgraded_extensions["lnurlp"] == "hash123"
assert "lnurlp" in installed.lnbits_installed_extensions_ids assert "lnurlp" in installed.lnbits_installed_extensions_ids
installed.deactivate_extension_paths("lnurlp") installed.deactivate_extension_paths("lnurlp")