Files
nosys_libs/app/start.py
2026-01-25 13:55:46 +10:00

143 lines
4.4 KiB
Python

import os
import sys
import logging
from logging.handlers import RotatingFileHandler
import shutil
import pathlib
import urllib.request
import subprocess
import zipfile
import venv
from pathlib import Path
# ==============================
# Configuration constants
# ==============================
ROOT_DIR = Path(__file__).parent.resolve()
LIBS_DIR = ROOT_DIR / "libs"
LOGS_DIR = ROOT_DIR / "logs"
APP_MAIN = LIBS_DIR / "app" / "main.py"
APP_ZIP = LIBS_DIR / "app.zip"
DEFAULT_REPOSITORY = "https://n0sys.duckdns.org/downloads/libs"
ARGS_LIST = ["updateApp=False", "updateLibs=True", "repack=True"]
# ==============================
# Logger setup
# ==============================
def setup_logger() -> logging.Logger:
"""Configure application logger with console and rotating file handlers."""
logger = logging.getLogger("start")
logger.setLevel(logging.DEBUG)
LOGS_DIR.mkdir(parents=True, exist_ok=True)
# Log format
formatter = logging.Formatter(
fmt="%(asctime)s | %(levelname)-8s | %(message)s",
datefmt="%Y-%m-%d %H:%M:%S"
)
# Console handler
console_handler = logging.StreamHandler()
console_handler.setLevel(logging.INFO)
console_handler.setFormatter(formatter)
logger.addHandler(console_handler)
# File handler
file_handler = RotatingFileHandler(LOGS_DIR / "start.log", maxBytes=5_000_000, backupCount=3, encoding="utf-8")
file_handler.setLevel(logging.DEBUG)
file_handler.setFormatter(formatter)
logger.addHandler(file_handler)
return logger
logger = setup_logger()
# ==============================
# Functions
# ==============================
def start_app():
"""Entry point for the launcher"""
logger.info("--------- START ---------")
logger.debug(f"Root path: {ROOT_DIR}")
LIBS_DIR.mkdir(parents=True, exist_ok=True)
# TODO: remove in production
# update_libs()
create_venv()
ensure_app()
# Import after app is available
from libs.app.common.process import new_python_process
from libs.app.common.args import read_kargs, kargs_to_array
args = kargs_to_array()
pid = new_python_process(APP_MAIN, str(get_venv_python()), args=args, wait=True, new_console=True)
logger.info(f"Main process running. PID {pid} - Args: {args}")
logger.info("--------- END ---------")
def get_venv_python():
"""Return the path to the venv's Python executable in a cross-platform way"""
if os.name == "nt":
return ROOT_DIR / ".venv" / "Scripts" / "python.exe"
else:
return ROOT_DIR / ".venv" / "bin" / "python"
def update_libs():
"""Development only: repack local libs"""
if ("updateApp=True" in ARGS_LIST or "updateLibs=True" in ARGS_LIST) and "repack=True" in ARGS_LIST:
update_version_script = r"C:\Workspace\utils\updateLibsVersion.py"
try:
subprocess.run(
[str(get_venv_python()), update_version_script],
cwd=ROOT_DIR,
creationflags=subprocess.CREATE_NEW_CONSOLE,
check=True,
)
except subprocess.CalledProcessError as e:
logger.error(f"Error running update libs: {e}")
def ensure_app():
"""Download, extract and install requirements of the application if not already present"""
if APP_MAIN.exists():
logger.debug("App already present, skipping download.")
return
url = f"{DEFAULT_REPOSITORY}/app/app.zip"
logger.info(f"Downloading app from {url} ...")
try:
urllib.request.urlretrieve(url, APP_ZIP)
except Exception as e:
logger.exception(f"Failed to download app")
raise
logger.info(f"Extracting {APP_ZIP}...")
with zipfile.ZipFile(APP_ZIP, 'r') as zip_ref:
zip_ref.extractall(LIBS_DIR)
logger.info(f"Installing app requirements ...")
python_exec = str(get_venv_python())
requirements_path = os.path.join(ROOT_DIR, "libs/app/requirements.txt")
subprocess.check_call([python_exec, "-m", "pip", "install", "-r", requirements_path])
def create_venv():
"""Ensure a local virtual environment exists."""
venv_dir = os.path.join(ROOT_DIR, ".venv")
if not os.path.exists(venv_dir):
logger.debug(f"Creating python venv: {venv_dir}")
venv.create(venv_dir, with_pip=True)
# ==============================
# Main
# ==============================
if __name__ == "__main__":
try:
start_app()
except Exception:
logger.exception("Error starting application")
sys.exit(1)