Swap lmstudio -> lmstudio-bin
This commit is contained in:
15 files changed
+594
-93
No files matched your search
@@ -0,0 +1,204 @@
|
||||
#!/usr/bin/env python3
|
||||
"""
|
||||
LM Studio PKGBUILD Maintenance Engine
|
||||
|
||||
Resolves dynamic upstream link, extracts version, calculates SHA256,
|
||||
and generates PKGBUILD from template.
|
||||
|
||||
Updated to handle:
|
||||
- AppImage file with calculated sha256
|
||||
- .desktop file with 'SKIP' (locally managed)
|
||||
- Static icon (lmstudio.png) with calculated sha256
|
||||
- .install file staging and template rendering
|
||||
"""
|
||||
|
||||
import re
|
||||
import hashlib
|
||||
import requests
|
||||
import subprocess
|
||||
import tarfile
|
||||
from pathlib import Path
|
||||
from typing import Tuple, List
|
||||
|
||||
|
||||
def resolve_download_url(redirect_url: str = "https://lmstudio.ai/download/latest/linux/x64") -> str:
|
||||
"""
|
||||
Follow redirects to resolve the final download URL.
|
||||
Uses HEAD request to avoid downloading the full file.
|
||||
"""
|
||||
print(f"Resolving download URL: {redirect_url}")
|
||||
|
||||
session = requests.Session()
|
||||
response = session.head(redirect_url, allow_redirects=True)
|
||||
response.raise_for_status()
|
||||
|
||||
final_url = response.url
|
||||
print(f"Resolved URL: {final_url}")
|
||||
return final_url
|
||||
|
||||
|
||||
def extract_version(filename: str) -> str:
|
||||
"""
|
||||
Extract semantic version from filename using regex.
|
||||
Expected format: LM-Studio-X.Y.Z-x64.AppImage or LM-Studio-X.Y.Z-R-x64.AppImage
|
||||
Extracts only X.Y.Z portion (pkgrel handled separately in PKGBUILD).
|
||||
"""
|
||||
match = re.search(r'LM-Studio-([\d.]+)-(?:\d+-)?x64\.AppImage', filename)
|
||||
if match:
|
||||
return match.group(1)
|
||||
raise ValueError(f"Could not extract version from filename: {filename}")
|
||||
|
||||
|
||||
def calculate_sha256(file_path: str) -> str:
|
||||
"""
|
||||
Calculate SHA256 checksum of a file.
|
||||
Handles both AppImage and icon files with identical hashing logic.
|
||||
"""
|
||||
print(f"Calculating SHA256 for: {file_path}")
|
||||
|
||||
sha256_hash = hashlib.sha256()
|
||||
with open(file_path, "rb") as f:
|
||||
for byte_block in iter(lambda: f.read(4096), b""):
|
||||
sha256_hash.update(byte_block)
|
||||
|
||||
checksum = sha256_hash.hexdigest()
|
||||
print(f"SHA256: {checksum}")
|
||||
return checksum
|
||||
|
||||
|
||||
def download_file(url: str, file_path: str) -> None:
|
||||
"""
|
||||
Download file from URL if it doesn't already exist.
|
||||
"""
|
||||
path = Path(file_path)
|
||||
if path.exists():
|
||||
print(f"File already exists: {file_path}")
|
||||
return
|
||||
|
||||
print(f"Downloading: {url}")
|
||||
response = requests.get(url, stream=True)
|
||||
response.raise_for_status()
|
||||
|
||||
with open(file_path, "wb") as f:
|
||||
for chunk in response.iter_content(chunk_size=8192):
|
||||
if chunk:
|
||||
f.write(chunk)
|
||||
|
||||
print(f"Downloaded: {file_path}")
|
||||
|
||||
|
||||
def stage_install_file() -> None:
|
||||
"""
|
||||
Ensure lmstudio-bin.install file exists and is staged.
|
||||
"""
|
||||
install_path = Path("/home/madgoat/Documents/LMStudio-bin Aur/lmstudio-bin.install")
|
||||
|
||||
if install_path.exists():
|
||||
print(f"✓ Install file present: {install_path}")
|
||||
else:
|
||||
print(f"Warning: Install file not found at {install_path}")
|
||||
|
||||
|
||||
def render_template(template_path: str, output_path: str, version: str, url: str, sha256: str, icon_sha256: str) -> None:
|
||||
"""
|
||||
Render PKGBUILD from template by replacing placeholders.
|
||||
|
||||
Placeholders:
|
||||
- {{version}}: Semantic version extracted from filename
|
||||
- {{url}}: Resolved download URL
|
||||
- {{sha256}}: SHA256 checksum of the AppImage
|
||||
- {{icon_sha256}}: SHA256 checksum of the static icon (lmstudio.png)
|
||||
|
||||
The template includes sha256sums array with appimage hash, icon hash, and SKIP for .desktop file.
|
||||
"""
|
||||
print(f"Rendering template: {template_path} -> {output_path}")
|
||||
|
||||
with open(template_path, "r") as f:
|
||||
content = f.read()
|
||||
|
||||
content = content.replace("{{version}}", version)
|
||||
content = content.replace("{{url}}", url)
|
||||
content = content.replace("{{sha256}}", sha256)
|
||||
content = content.replace("{{icon_sha256}}", icon_sha256)
|
||||
|
||||
with open(output_path, "w") as f:
|
||||
f.write(content)
|
||||
|
||||
print(f"Generated: {output_path}")
|
||||
|
||||
|
||||
def generate_srcinfo() -> None:
|
||||
"""
|
||||
Generate .SRCINFO using makepkg --printsrcinfo.
|
||||
"""
|
||||
print("Generating .SRCINFO...")
|
||||
|
||||
result = subprocess.run(
|
||||
["makepkg", "--printsrcinfo"],
|
||||
capture_output=True,
|
||||
text=True,
|
||||
cwd="/home/madgoat/Documents/LMStudio-bin Aur"
|
||||
)
|
||||
|
||||
if result.returncode != 0:
|
||||
raise RuntimeError(f"makepkg failed: {result.stderr}")
|
||||
|
||||
with open("/home/madgoat/Documents/LMStudio-bin Aur/.SRCINFO", "w") as f:
|
||||
f.write(result.stdout)
|
||||
|
||||
print("Generated: .SRCINFO")
|
||||
|
||||
|
||||
def main():
|
||||
"""
|
||||
Main maintenance engine workflow.
|
||||
"""
|
||||
print("=== LM Studio PKGBUILD Maintenance Engine ===\n")
|
||||
|
||||
# Change to workspace directory
|
||||
import os
|
||||
os.chdir("/home/madgoat/Documents/LMStudio-bin Aur")
|
||||
|
||||
# Step 1: Resolve download URL
|
||||
final_url = resolve_download_url()
|
||||
|
||||
# Step 2: Extract filename and version
|
||||
filename = final_url.split("/")[-1]
|
||||
version = extract_version(filename)
|
||||
print(f"Detected version: {version}\n")
|
||||
|
||||
# Step 3: Download file if missing
|
||||
download_file(final_url, filename)
|
||||
|
||||
# Step 4: Calculate SHA256 for AppImage
|
||||
sha256 = calculate_sha256(filename)
|
||||
print()
|
||||
|
||||
# Step 5: Calculate SHA256 for static icon (lmstudio.png)
|
||||
# Uses identical hashing logic as AppImage
|
||||
icon_hash = calculate_sha256("lmstudio.png")
|
||||
print()
|
||||
|
||||
# Step 6: Stage install file
|
||||
stage_install_file()
|
||||
print()
|
||||
|
||||
# Step 7: Render PKGBUILD from template
|
||||
# The template uses {{sha256}} for the AppImage hash and {{icon_sha256}} for icon hash
|
||||
# and the sha256sums array in the template includes both hashes plus 'SKIP' for .desktop file
|
||||
render_template("PKGBUILD.template", "PKGBUILD", version, final_url, sha256, icon_hash)
|
||||
print()
|
||||
|
||||
# Step 8: Generate .SRCINFO
|
||||
generate_srcinfo()
|
||||
|
||||
print("\n=== Maintenance Engine Complete ===")
|
||||
print(f"Version: {version}")
|
||||
print(f"URL: {final_url}")
|
||||
print(f"AppImage SHA256: {sha256}")
|
||||
print(f"Icon SHA256: {icon_hash}")
|
||||
print(f"SHA256sums: ('{sha256}' '{icon_hash}' 'SKIP')")
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
||||
Reference in new issue
Block a user