From f108bab2f940d03da7f22f4444eaf27fcf9f2d1b Mon Sep 17 00:00:00 2001 From: th3r00t Date: Fri, 8 Aug 2025 18:27:28 +0000 Subject: [PATCH] Created install.sh and build.sh Also refactored methods to work as a zipapp --- build.sh | 2 + instal.sh | 8 ++++ src/frontend/lib/FastAPIServer.py | 13 +++--- src/frontend/lib/objects.py | 2 +- src/frontend/lib/runtime_paths.py | 69 +++++++++++++++++++++++++++++++ 5 files changed, 88 insertions(+), 6 deletions(-) create mode 100755 instal.sh create mode 100644 src/frontend/lib/runtime_paths.py diff --git a/build.sh b/build.sh index d278a6a..787db1d 100755 --- a/build.sh +++ b/build.sh @@ -1,3 +1,5 @@ #!/usr/bin/env sh # uv export > requirements.txt +# mkdir if not exists release +mkdir -p release python -m zipapp src --compress --output=release/pyshelf --python="/usr/bin/env python" diff --git a/instal.sh b/instal.sh new file mode 100755 index 0000000..8042dbb --- /dev/null +++ b/instal.sh @@ -0,0 +1,8 @@ +cd /tmp/ +git clone https://github.com/th3r00t/pyShelf.git +cd pyshelf +git checkout 0.8.0--dev-zipapp +./build.sh +sudo cp ./src/frontend/static /var/lib/pyshelf/assets -r +sudo cp ./src/frontend/templates /var/lib/pyshelf/assets -r +sudo cp ./release/pyshelf /usr/local/bin/pyshelf diff --git a/src/frontend/lib/FastAPIServer.py b/src/frontend/lib/FastAPIServer.py index 9d5a565..dc46697 100644 --- a/src/frontend/lib/FastAPIServer.py +++ b/src/frontend/lib/FastAPIServer.py @@ -16,10 +16,13 @@ from fastapi.templating import Jinja2Templates from fastapi.middleware.cors import CORSMiddleware from backend.lib.storage import Storage from .objects import JSInterface +from .runtime_paths import ensure_assets from backend.lib.config import Config app = FastAPI() -templates = Jinja2Templates(directory="src/frontend/templates") +STATIC_DIR, TEMPLATES_DIR = ensure_assets() +templates = Jinja2Templates(directory=str(TEMPLATES_DIR)) +# templates = Jinja2Templates(directory="src/frontend/templates") origins = [ "http://localhost", "http://localhost:8081", @@ -34,7 +37,6 @@ app.add_middleware( allow_headers=["*"], ) - def base64decode(string) -> str: """Decode a base64 string.""" try: @@ -134,9 +136,10 @@ class FastAPIServer(): def __init__(self, config): """Initialize FastAPIServer object parameters.""" self.config = config - app.mount("/static", - StaticFiles(directory="src/frontend/static"), - name="static") + app.mount("/static", StaticFiles(directory=str(STATIC_DIR)), name="static") + # app.mount("/static", + # StaticFiles(directory="src/frontend/static"), + # name="static") self.fe_config = uvicorn.Config(app, host="0.0.0.0", port=8080, log_level="info", reload=True) self.fe_server = uvicorn.Server(self.fe_config) diff --git a/src/frontend/lib/objects.py b/src/frontend/lib/objects.py index 2069c9a..1ca0939 100644 --- a/src/frontend/lib/objects.py +++ b/src/frontend/lib/objects.py @@ -1,7 +1,7 @@ """pyShelf's Frontend Objects.""" from subprocess import run from pathlib import Path -from src.backend.lib.config import Config +from backend.lib.config import Config class JSInterface(): diff --git a/src/frontend/lib/runtime_paths.py b/src/frontend/lib/runtime_paths.py new file mode 100644 index 0000000..4a4ae95 --- /dev/null +++ b/src/frontend/lib/runtime_paths.py @@ -0,0 +1,69 @@ +# src/frontend/lib/runtime_paths.py +from __future__ import annotations +import os, sys, shutil +from pathlib import Path +from importlib import resources + +ASSET_TOPS = ("static", "templates") + +def _inside_zipapp() -> bool: + # zipapps can be a single file (…/pyshelf.pyz) or an executable file without .pyz + # When invoked, sys.argv[0] is the archive path; treat non-directory as zipapp + return not Path(sys.argv[0]).is_dir() + +def assets_root() -> Path: + """ + Directory that *contains* static/ and templates/. + Priority: + 1) PYSHELF_ASSETS + 2) ./pyshelf (sibling dir next to the archive) when running as zipapp + 3) frontend/ (package dir) when running from source/unpacked tree + """ + env = os.environ.get("PYSHELF_ASSETS") + if env: + return Path(env) + + if _inside_zipapp(): + # e.g. /opt/pyshelf/pyshelf -> use /opt/pyshelf/pyshelf{,/static,/templates} + base = Path(sys.argv[0]).resolve() + # strip suffix like ".pyz" if present to get a nice folder name + return base.with_suffix("") + + # Dev/regular run: __file__ = …/frontend/lib/runtime_paths.py => parents[1] == …/frontend + return Path(__file__).resolve().parents[1] + +def _copy_traversable_tree(src_trav, dst_dir: Path) -> None: + """Recursively copy a Traversable (importlib.resources) tree to dst_dir.""" + for child in src_trav.iterdir(): + target = dst_dir / child.name + if child.is_dir(): + target.mkdir(parents=True, exist_ok=True) + _copy_traversable_tree(child, target) + else: + target.parent.mkdir(parents=True, exist_ok=True) + with child.open("rb") as r, open(target, "wb") as w: + shutil.copyfileobj(r, w) + +def ensure_assets() -> tuple[Path, Path]: + """ + Ensure static/ and templates/ exist on disk and return their paths. + If running from zipapp and they don't exist yet, extract packaged copies. + """ + root = assets_root() + static_dir = root / "static" + tmpl_dir = root / "templates" + + # If both already exist, use them (works in repo tree and next to .pyz) + if static_dir.exists() and tmpl_dir.exists(): + return static_dir, tmpl_dir + + # Extract from package data into root/{static,templates} + pkg = "frontend" # package that contains 'static' and 'templates' + for top in ASSET_TOPS: + src = resources.files(pkg) / top # Traversable + dst = root / top + dst.mkdir(parents=True, exist_ok=True) + _copy_traversable_tree(src, dst) + + return static_dir, tmpl_dir +