feat: install GitHib releases also

This commit is contained in:
Vlad Stan 2023-01-17 11:16:54 +02:00
parent 41ce316fc6
commit 9d0cedfcb2
6 changed files with 79 additions and 41 deletions

View File

@ -75,25 +75,22 @@ async def add_installed_extension(
ext_id: str,
version: str,
active: bool,
hash: str,
meta: dict,
conn: Optional[Connection] = None,
) -> None:
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
UPDATE SET (version, active, hash, meta) = (?, ?, ?, ?)
UPDATE SET (version, active, meta) = (?, ?, ?)
""",
(
ext_id,
version,
active,
hash,
json.dumps(meta),
version,
active,
hash,
json.dumps(meta),
),
)

View File

@ -278,7 +278,6 @@ async def m009_create_installed_extensions_table(db):
id TEXT PRIMARY KEY,
version TEXT NOT NULL,
active BOOLEAN DEFAULT false,
hash TEXT NOT NULL,
meta TEXT NOT NULL DEFAULT '{}'
);
"""

View File

@ -176,14 +176,16 @@
>
<q-card>
<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
>
</q-card-section>
<q-separator></q-separator>
<div
v-if="selectedExtension.details"
v-html="selectedExtension.details"
v-if="release.details_html"
v-html="release.details_html"
></div> </q-card
></q-expansion-item>
</q-list>
@ -258,13 +260,19 @@
)
.filter(extensionNameContains(term))
},
installExtension: async function (extension) {
installExtension: async function (release) {
const extension = this.selectedExtension
try {
extension.inProgress = true
await LNbits.api.request(
'POST',
`/api/v1/extension/${extension.id}/${extension.hash}?usr=${this.g.user.id}`,
this.g.user.wallets[0].adminkey
`/api/v1/extension?usr=${this.g.user.id}`,
this.g.user.wallets[0].adminkey,
{
ext_id: extension.id,
archive: release.archive,
source_repo: release.source_repo
}
)
window.location.href = [
"{{ url_for('install.extensions') }}",

View File

@ -41,6 +41,7 @@ from lnbits.decorators import (
require_invoice_key,
)
from lnbits.extension_manger import (
CreateExtension,
Extension,
ExtensionRelease,
InstallableExtension,
@ -720,13 +721,30 @@ async def websocket_update_get(item_id: str, data: str):
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(
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_id, hash
# ext_info: InstallableExtension = await InstallableExtension.get_extension_info(
# 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()
try:
@ -734,17 +752,16 @@ async def api_install_extension(
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 add_installed_extension(
ext_id=ext_id,
version=ext_info.version,
ext_id=data.ext_id,
version=installed_release.version,
active=False,
hash=hash,
meta=dict(ext_info),
meta={"installed_release": dict(installed_release)},
)
settings.lnbits_disabled_extensions += [ext_id]
settings.lnbits_disabled_extensions += [data.ext_id]
# mount routes for the new version
core_app_extra.register_new_ext_routes(extension)

View File

@ -103,13 +103,10 @@ async def extensions_install(
lambda ext: {
"id": ext.id,
"name": ext.name,
"hash": ext.hash,
"version": ext.version,
"icon": ext.icon,
"iconUrl": ext.icon_url,
"shortDescription": ext.short_description,
"stars": ext.stars,
"details": ext.details,
"dependencies": ext.dependencies,
"isInstalled": ext.id in installed_extensions,
"isActive": not ext.id in inactive_extensions,

View File

@ -111,6 +111,7 @@ class ExtensionRelease(BaseModel):
published_at: Optional[str]
html_url: Optional[str]
description: Optional[str]
details_html: Optional[str] = None
@classmethod
def from_github_release(cls, source_repo: str, r: dict) -> "ExtensionRelease":
@ -146,19 +147,25 @@ class ExtensionRelease(BaseModel):
class InstallableExtension(BaseModel):
id: str
name: str
archive: str # todo: move to installed_release
hash: str
short_description: Optional[str] = None
details: Optional[str] = None
icon: Optional[str] = None
icon_url: Optional[str] = None
dependencies: List[str] = []
is_admin_only: bool = False
version: str = "none" # todo: move to Release
stars: int = 0
latest_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
def zip_path(self) -> str:
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):
os.remove(ext_zip_file)
try:
download_url(self.archive, ext_zip_file)
download_url(self.installed_release.archive, ext_zip_file)
except Exception as ex:
logger.warning(ex)
raise HTTPException(
@ -195,7 +202,7 @@ class InstallableExtension(BaseModel):
)
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
if os.path.isfile(ext_zip_file):
os.remove(ext_zip_file)
@ -205,15 +212,22 @@ class InstallableExtension(BaseModel):
)
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)
shutil.rmtree(self.ext_upgrade_dir, True)
with zipfile.ZipFile(self.zip_path, "r") as zip_ref:
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:
"""Update the the list of upgraded extensions. The middleware will perform redirects based on this"""
if not self.hash:
@ -261,15 +275,15 @@ class InstallableExtension(BaseModel):
logger.warning(e)
return None
@classmethod
async def get_extension_info(cls, ext_id: str, hash: str) -> "InstallableExtension":
@classmethod # todo: remove
async def get_extension_info(
cls, ext_id: str, archive: str
) -> "InstallableExtension":
installable_extensions: List[
InstallableExtension
] = await InstallableExtension.get_installable_extensions()
valid_extensions = [
e for e in installable_extensions if e.id == ext_id and e.hash == hash
]
valid_extensions = [e for e in installable_extensions if e.id == ext_id]
if len(valid_extensions) == 0:
raise HTTPException(
status_code=HTTPStatus.BAD_REQUEST,
@ -322,7 +336,6 @@ class InstallableExtension(BaseModel):
archive=e["archive"],
hash=e["hash"],
short_description=e["shortDescription"],
details=e["details"] if "details" in e else "",
icon=e["icon"],
dependencies=e["dependencies"]
if "dependencies" in e
@ -365,6 +378,7 @@ class InstallableExtension(BaseModel):
hash=e["hash"],
source_repo=url,
description=e["shortDescription"],
details_html=e.get("details"),
)
]
@ -415,6 +429,12 @@ class InstalledExtensionMiddleware:
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]:
return [
extension