mirror of
https://github.com/lnbits/lnbits-legend.git
synced 2025-01-19 05:33:47 +01:00
feat: install GitHib releases also
This commit is contained in:
parent
41ce316fc6
commit
9d0cedfcb2
@ -75,25 +75,22 @@ async def add_installed_extension(
|
|||||||
ext_id: str,
|
ext_id: str,
|
||||||
version: str,
|
version: str,
|
||||||
active: bool,
|
active: bool,
|
||||||
hash: str,
|
|
||||||
meta: dict,
|
meta: dict,
|
||||||
conn: Optional[Connection] = None,
|
conn: Optional[Connection] = None,
|
||||||
) -> None:
|
) -> None:
|
||||||
await (conn or db).execute(
|
await (conn or db).execute(
|
||||||
"""
|
"""
|
||||||
INSERT INTO installed_extensions (id, version, active, hash, meta) VALUES (?, ?, ?, ?, ?)
|
INSERT INTO installed_extensions (id, version, active, meta) VALUES (?, ?, ?, ?)
|
||||||
ON CONFLICT (id) DO
|
ON CONFLICT (id) DO
|
||||||
UPDATE SET (version, active, hash, meta) = (?, ?, ?, ?)
|
UPDATE SET (version, active, meta) = (?, ?, ?)
|
||||||
""",
|
""",
|
||||||
(
|
(
|
||||||
ext_id,
|
ext_id,
|
||||||
version,
|
version,
|
||||||
active,
|
active,
|
||||||
hash,
|
|
||||||
json.dumps(meta),
|
json.dumps(meta),
|
||||||
version,
|
version,
|
||||||
active,
|
active,
|
||||||
hash,
|
|
||||||
json.dumps(meta),
|
json.dumps(meta),
|
||||||
),
|
),
|
||||||
)
|
)
|
||||||
|
@ -278,7 +278,6 @@ async def m009_create_installed_extensions_table(db):
|
|||||||
id TEXT PRIMARY KEY,
|
id TEXT PRIMARY KEY,
|
||||||
version TEXT NOT NULL,
|
version TEXT NOT NULL,
|
||||||
active BOOLEAN DEFAULT false,
|
active BOOLEAN DEFAULT false,
|
||||||
hash TEXT NOT NULL,
|
|
||||||
meta TEXT NOT NULL DEFAULT '{}'
|
meta TEXT NOT NULL DEFAULT '{}'
|
||||||
);
|
);
|
||||||
"""
|
"""
|
||||||
|
@ -176,14 +176,16 @@
|
|||||||
>
|
>
|
||||||
<q-card>
|
<q-card>
|
||||||
<q-card-section>
|
<q-card-section>
|
||||||
<q-btn type="a" color="primary unelevated mt-lg pt-lg"
|
<q-btn
|
||||||
|
@click="installExtension(release)"
|
||||||
|
color="primary unelevated mt-lg pt-lg"
|
||||||
>Install</q-btn
|
>Install</q-btn
|
||||||
>
|
>
|
||||||
</q-card-section>
|
</q-card-section>
|
||||||
<q-separator></q-separator>
|
<q-separator></q-separator>
|
||||||
<div
|
<div
|
||||||
v-if="selectedExtension.details"
|
v-if="release.details_html"
|
||||||
v-html="selectedExtension.details"
|
v-html="release.details_html"
|
||||||
></div> </q-card
|
></div> </q-card
|
||||||
></q-expansion-item>
|
></q-expansion-item>
|
||||||
</q-list>
|
</q-list>
|
||||||
@ -258,13 +260,19 @@
|
|||||||
)
|
)
|
||||||
.filter(extensionNameContains(term))
|
.filter(extensionNameContains(term))
|
||||||
},
|
},
|
||||||
installExtension: async function (extension) {
|
installExtension: async function (release) {
|
||||||
|
const extension = this.selectedExtension
|
||||||
try {
|
try {
|
||||||
extension.inProgress = true
|
extension.inProgress = true
|
||||||
await LNbits.api.request(
|
await LNbits.api.request(
|
||||||
'POST',
|
'POST',
|
||||||
`/api/v1/extension/${extension.id}/${extension.hash}?usr=${this.g.user.id}`,
|
`/api/v1/extension?usr=${this.g.user.id}`,
|
||||||
this.g.user.wallets[0].adminkey
|
this.g.user.wallets[0].adminkey,
|
||||||
|
{
|
||||||
|
ext_id: extension.id,
|
||||||
|
archive: release.archive,
|
||||||
|
source_repo: release.source_repo
|
||||||
|
}
|
||||||
)
|
)
|
||||||
window.location.href = [
|
window.location.href = [
|
||||||
"{{ url_for('install.extensions') }}",
|
"{{ url_for('install.extensions') }}",
|
||||||
|
@ -41,6 +41,7 @@ from lnbits.decorators import (
|
|||||||
require_invoice_key,
|
require_invoice_key,
|
||||||
)
|
)
|
||||||
from lnbits.extension_manger import (
|
from lnbits.extension_manger import (
|
||||||
|
CreateExtension,
|
||||||
Extension,
|
Extension,
|
||||||
ExtensionRelease,
|
ExtensionRelease,
|
||||||
InstallableExtension,
|
InstallableExtension,
|
||||||
@ -720,13 +721,30 @@ async def websocket_update_get(item_id: str, data: str):
|
|||||||
return {"sent": False, "data": data}
|
return {"sent": False, "data": data}
|
||||||
|
|
||||||
|
|
||||||
@core_app.post("/api/v1/extension/{ext_id}/{hash}")
|
@core_app.post("/api/v1/extension")
|
||||||
async def api_install_extension(
|
async def api_install_extension(
|
||||||
ext_id: str, hash: str, user: User = Depends(check_admin)
|
data: CreateExtension, user: User = Depends(check_admin)
|
||||||
):
|
):
|
||||||
ext_info: InstallableExtension = await InstallableExtension.get_extension_info(
|
# ext_info: InstallableExtension = await InstallableExtension.get_extension_info(
|
||||||
ext_id, hash
|
# data.ext_id, data.archive
|
||||||
|
# )
|
||||||
|
|
||||||
|
all_releases: List[
|
||||||
|
ExtensionRelease
|
||||||
|
] = await InstallableExtension.get_extension_releases(data.ext_id)
|
||||||
|
selected_release = [
|
||||||
|
r
|
||||||
|
for r in all_releases
|
||||||
|
if r.archive == data.archive and r.source_repo == data.source_repo
|
||||||
|
]
|
||||||
|
if len(selected_release) == 0:
|
||||||
|
raise Exception("uuuuuuu")
|
||||||
|
|
||||||
|
installed_release = selected_release[0]
|
||||||
|
ext_info = InstallableExtension(
|
||||||
|
id=data.ext_id, name=data.ext_id, installed_release=installed_release
|
||||||
)
|
)
|
||||||
|
|
||||||
ext_info.download_archive()
|
ext_info.download_archive()
|
||||||
|
|
||||||
try:
|
try:
|
||||||
@ -734,17 +752,16 @@ async def api_install_extension(
|
|||||||
|
|
||||||
extension = Extension.from_installable_ext(ext_info)
|
extension = Extension.from_installable_ext(ext_info)
|
||||||
|
|
||||||
db_version = (await get_dbversions()).get(ext_id, 0)
|
db_version = (await get_dbversions()).get(data.ext_id, 0)
|
||||||
await migrate_extension_database(extension, db_version)
|
await migrate_extension_database(extension, db_version)
|
||||||
|
|
||||||
await add_installed_extension(
|
await add_installed_extension(
|
||||||
ext_id=ext_id,
|
ext_id=data.ext_id,
|
||||||
version=ext_info.version,
|
version=installed_release.version,
|
||||||
active=False,
|
active=False,
|
||||||
hash=hash,
|
meta={"installed_release": dict(installed_release)},
|
||||||
meta=dict(ext_info),
|
|
||||||
)
|
)
|
||||||
settings.lnbits_disabled_extensions += [ext_id]
|
settings.lnbits_disabled_extensions += [data.ext_id]
|
||||||
|
|
||||||
# mount routes for the new version
|
# mount routes for the new version
|
||||||
core_app_extra.register_new_ext_routes(extension)
|
core_app_extra.register_new_ext_routes(extension)
|
||||||
|
@ -103,13 +103,10 @@ async def extensions_install(
|
|||||||
lambda ext: {
|
lambda ext: {
|
||||||
"id": ext.id,
|
"id": ext.id,
|
||||||
"name": ext.name,
|
"name": ext.name,
|
||||||
"hash": ext.hash,
|
|
||||||
"version": ext.version,
|
|
||||||
"icon": ext.icon,
|
"icon": ext.icon,
|
||||||
"iconUrl": ext.icon_url,
|
"iconUrl": ext.icon_url,
|
||||||
"shortDescription": ext.short_description,
|
"shortDescription": ext.short_description,
|
||||||
"stars": ext.stars,
|
"stars": ext.stars,
|
||||||
"details": ext.details,
|
|
||||||
"dependencies": ext.dependencies,
|
"dependencies": ext.dependencies,
|
||||||
"isInstalled": ext.id in installed_extensions,
|
"isInstalled": ext.id in installed_extensions,
|
||||||
"isActive": not ext.id in inactive_extensions,
|
"isActive": not ext.id in inactive_extensions,
|
||||||
|
@ -111,6 +111,7 @@ class ExtensionRelease(BaseModel):
|
|||||||
published_at: Optional[str]
|
published_at: Optional[str]
|
||||||
html_url: Optional[str]
|
html_url: Optional[str]
|
||||||
description: Optional[str]
|
description: Optional[str]
|
||||||
|
details_html: Optional[str] = None
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
def from_github_release(cls, source_repo: str, r: dict) -> "ExtensionRelease":
|
def from_github_release(cls, source_repo: str, r: dict) -> "ExtensionRelease":
|
||||||
@ -146,19 +147,25 @@ class ExtensionRelease(BaseModel):
|
|||||||
class InstallableExtension(BaseModel):
|
class InstallableExtension(BaseModel):
|
||||||
id: str
|
id: str
|
||||||
name: str
|
name: str
|
||||||
archive: str # todo: move to installed_release
|
|
||||||
hash: str
|
|
||||||
short_description: Optional[str] = None
|
short_description: Optional[str] = None
|
||||||
details: Optional[str] = None
|
|
||||||
icon: Optional[str] = None
|
icon: Optional[str] = None
|
||||||
icon_url: Optional[str] = None
|
icon_url: Optional[str] = None
|
||||||
dependencies: List[str] = []
|
dependencies: List[str] = []
|
||||||
is_admin_only: bool = False
|
is_admin_only: bool = False
|
||||||
version: str = "none" # todo: move to Release
|
|
||||||
stars: int = 0
|
stars: int = 0
|
||||||
latest_release: Optional[ExtensionRelease]
|
latest_release: Optional[ExtensionRelease]
|
||||||
installed_release: Optional[ExtensionRelease]
|
installed_release: Optional[ExtensionRelease]
|
||||||
|
|
||||||
|
@property
|
||||||
|
def hash(self) -> str:
|
||||||
|
if self.installed_release:
|
||||||
|
if self.installed_release.hash:
|
||||||
|
return self.installed_release.hash
|
||||||
|
m = hashlib.sha256()
|
||||||
|
m.update(f"{self.installed_release.archive}".encode())
|
||||||
|
return m.hexdigest()
|
||||||
|
return "not-installed"
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def zip_path(self) -> str:
|
def zip_path(self) -> str:
|
||||||
extensions_data_dir = os.path.join(settings.lnbits_data_folder, "extensions")
|
extensions_data_dir = os.path.join(settings.lnbits_data_folder, "extensions")
|
||||||
@ -186,7 +193,7 @@ class InstallableExtension(BaseModel):
|
|||||||
if os.path.isfile(ext_zip_file):
|
if os.path.isfile(ext_zip_file):
|
||||||
os.remove(ext_zip_file)
|
os.remove(ext_zip_file)
|
||||||
try:
|
try:
|
||||||
download_url(self.archive, ext_zip_file)
|
download_url(self.installed_release.archive, ext_zip_file)
|
||||||
except Exception as ex:
|
except Exception as ex:
|
||||||
logger.warning(ex)
|
logger.warning(ex)
|
||||||
raise HTTPException(
|
raise HTTPException(
|
||||||
@ -195,7 +202,7 @@ class InstallableExtension(BaseModel):
|
|||||||
)
|
)
|
||||||
|
|
||||||
archive_hash = file_hash(ext_zip_file)
|
archive_hash = file_hash(ext_zip_file)
|
||||||
if self.hash != archive_hash:
|
if self.installed_release.hash and self.installed_release.hash != archive_hash:
|
||||||
# remove downloaded archive
|
# remove downloaded archive
|
||||||
if os.path.isfile(ext_zip_file):
|
if os.path.isfile(ext_zip_file):
|
||||||
os.remove(ext_zip_file)
|
os.remove(ext_zip_file)
|
||||||
@ -205,15 +212,22 @@ class InstallableExtension(BaseModel):
|
|||||||
)
|
)
|
||||||
|
|
||||||
def extract_archive(self):
|
def extract_archive(self):
|
||||||
shutil.rmtree(self.ext_dir, True)
|
|
||||||
with zipfile.ZipFile(self.zip_path, "r") as zip_ref:
|
|
||||||
zip_ref.extractall(os.path.join("lnbits", "extensions"))
|
|
||||||
|
|
||||||
os.makedirs(os.path.join("lnbits", "upgrades"), exist_ok=True)
|
os.makedirs(os.path.join("lnbits", "upgrades"), exist_ok=True)
|
||||||
shutil.rmtree(self.ext_upgrade_dir, True)
|
shutil.rmtree(self.ext_upgrade_dir, True)
|
||||||
with zipfile.ZipFile(self.zip_path, "r") as zip_ref:
|
with zipfile.ZipFile(self.zip_path, "r") as zip_ref:
|
||||||
zip_ref.extractall(self.ext_upgrade_dir)
|
zip_ref.extractall(self.ext_upgrade_dir)
|
||||||
|
generated_dir_name = os.listdir(self.ext_upgrade_dir)[0]
|
||||||
|
os.rename(
|
||||||
|
os.path.join(self.ext_upgrade_dir, generated_dir_name),
|
||||||
|
os.path.join(self.ext_upgrade_dir, self.id),
|
||||||
|
)
|
||||||
|
|
||||||
|
shutil.rmtree(self.ext_dir, True)
|
||||||
|
shutil.copytree(
|
||||||
|
os.path.join(self.ext_upgrade_dir, self.id),
|
||||||
|
os.path.join("lnbits", "extensions", self.id),
|
||||||
|
)
|
||||||
|
|
||||||
def nofiy_upgrade(self) -> None:
|
def nofiy_upgrade(self) -> None:
|
||||||
"""Update the the list of upgraded extensions. The middleware will perform redirects based on this"""
|
"""Update the the list of upgraded extensions. The middleware will perform redirects based on this"""
|
||||||
if not self.hash:
|
if not self.hash:
|
||||||
@ -261,15 +275,15 @@ class InstallableExtension(BaseModel):
|
|||||||
logger.warning(e)
|
logger.warning(e)
|
||||||
return None
|
return None
|
||||||
|
|
||||||
@classmethod
|
@classmethod # todo: remove
|
||||||
async def get_extension_info(cls, ext_id: str, hash: str) -> "InstallableExtension":
|
async def get_extension_info(
|
||||||
|
cls, ext_id: str, archive: str
|
||||||
|
) -> "InstallableExtension":
|
||||||
installable_extensions: List[
|
installable_extensions: List[
|
||||||
InstallableExtension
|
InstallableExtension
|
||||||
] = await InstallableExtension.get_installable_extensions()
|
] = await InstallableExtension.get_installable_extensions()
|
||||||
|
|
||||||
valid_extensions = [
|
valid_extensions = [e for e in installable_extensions if e.id == ext_id]
|
||||||
e for e in installable_extensions if e.id == ext_id and e.hash == hash
|
|
||||||
]
|
|
||||||
if len(valid_extensions) == 0:
|
if len(valid_extensions) == 0:
|
||||||
raise HTTPException(
|
raise HTTPException(
|
||||||
status_code=HTTPStatus.BAD_REQUEST,
|
status_code=HTTPStatus.BAD_REQUEST,
|
||||||
@ -322,7 +336,6 @@ class InstallableExtension(BaseModel):
|
|||||||
archive=e["archive"],
|
archive=e["archive"],
|
||||||
hash=e["hash"],
|
hash=e["hash"],
|
||||||
short_description=e["shortDescription"],
|
short_description=e["shortDescription"],
|
||||||
details=e["details"] if "details" in e else "",
|
|
||||||
icon=e["icon"],
|
icon=e["icon"],
|
||||||
dependencies=e["dependencies"]
|
dependencies=e["dependencies"]
|
||||||
if "dependencies" in e
|
if "dependencies" in e
|
||||||
@ -365,6 +378,7 @@ class InstallableExtension(BaseModel):
|
|||||||
hash=e["hash"],
|
hash=e["hash"],
|
||||||
source_repo=url,
|
source_repo=url,
|
||||||
description=e["shortDescription"],
|
description=e["shortDescription"],
|
||||||
|
details_html=e.get("details"),
|
||||||
)
|
)
|
||||||
]
|
]
|
||||||
|
|
||||||
@ -415,6 +429,12 @@ class InstalledExtensionMiddleware:
|
|||||||
await self.app(scope, receive, send)
|
await self.app(scope, receive, send)
|
||||||
|
|
||||||
|
|
||||||
|
class CreateExtension(BaseModel):
|
||||||
|
ext_id: str
|
||||||
|
archive: str
|
||||||
|
source_repo: str
|
||||||
|
|
||||||
|
|
||||||
def get_valid_extensions(include_disabled_exts=False) -> List[Extension]:
|
def get_valid_extensions(include_disabled_exts=False) -> List[Extension]:
|
||||||
return [
|
return [
|
||||||
extension
|
extension
|
||||||
|
Loading…
Reference in New Issue
Block a user