mirror of
https://github.com/dredx/prole.git
synced 2026-09-23 10:13:58 +00:00
- Refactored installer UI with updated canvas rendering, sidebar navigation, and footer buttons. - Enhanced styling for macOS compatibility and consistent design across controls. - Added Pytest-based unit tests for `screen.py` and `config.py`. - Expanded dependency catalog with new tools like `tshark` and `pyshark`. - Improved error tolerance for background rendering and added placeholders for Kerberos configuration.
219 lines
6.7 KiB
Python
219 lines
6.7 KiB
Python
"""
|
|
Shared configuration and utility functions for the Prole installer (root-level).
|
|
|
|
This mirrors `prole.installer.config` but is located under the root `installer/`
|
|
package per the refactor request. UI code should import from `installer.config`.
|
|
"""
|
|
from __future__ import annotations
|
|
|
|
from pathlib import Path
|
|
import platform
|
|
import subprocess
|
|
import sys
|
|
from typing import Tuple, Optional
|
|
import os
|
|
|
|
# .properties loader (simple key=value, # comments)
|
|
def _load_properties(path: Path) -> dict:
|
|
props: dict[str, str] = {}
|
|
try:
|
|
text = path.read_text(encoding="utf-8")
|
|
except Exception:
|
|
return props
|
|
for line in text.splitlines():
|
|
s = line.strip()
|
|
if not s or s.startswith("#"):
|
|
continue
|
|
if "=" in s:
|
|
k, v = s.split("=", 1)
|
|
k = k.strip()
|
|
v = v.strip()
|
|
if k:
|
|
props[k] = v
|
|
return props
|
|
|
|
# Repository root: this file lives at <repo>/installer/config.py → parent is repo
|
|
PROJECT_ROOT = Path(__file__).resolve().parents[1]
|
|
PROLE_APP_DIR = PROJECT_ROOT / "prole-app"
|
|
PROLE_PROPS_PATH = PROLE_APP_DIR / "prole.properties"
|
|
|
|
_PROLE_PROPS_CACHE: Optional[dict] = None
|
|
|
|
def get_properties() -> dict:
|
|
"""Load and cache prole.properties from the repo (installer context).
|
|
|
|
Order:
|
|
- Repo default at prole-app/prole.properties
|
|
- Optional env override: PROLE_PROPERTIES points to a file
|
|
"""
|
|
global _PROLE_PROPS_CACHE
|
|
if _PROLE_PROPS_CACHE is None:
|
|
props: dict[str, str] = {}
|
|
# repo default
|
|
if PROLE_PROPS_PATH.exists():
|
|
props.update(_load_properties(PROLE_PROPS_PATH))
|
|
# env override (absolute path)
|
|
env_path = os.environ.get("PROLE_PROPERTIES")
|
|
if env_path:
|
|
p = Path(env_path)
|
|
if p.exists():
|
|
props.update(_load_properties(p))
|
|
_PROLE_PROPS_CACHE = props
|
|
return dict(_PROLE_PROPS_CACHE)
|
|
|
|
def get_config_value(key: str, default: Optional[str] = None) -> Optional[str]:
|
|
return get_properties().get(key, default)
|
|
|
|
def get_ui_icon_image_path() -> Path:
|
|
"""Return absolute path to the UI icon image for the installer.
|
|
|
|
Priority (new → legacy):
|
|
- `icon` key in prole.properties (requested)
|
|
- legacy `ui.icon`
|
|
|
|
Defaults to img/proleIcon.png under repo root if not set or missing.
|
|
"""
|
|
# Prefer new key `icon`, fall back to old `ui.icon`
|
|
rel = (
|
|
get_config_value("icon")
|
|
or get_config_value("ui.icon")
|
|
or "img/proleIcon.png"
|
|
)
|
|
p = (PROJECT_ROOT / rel).resolve()
|
|
if p.exists():
|
|
return p
|
|
# fallback
|
|
return (PROJECT_ROOT / "img/proleIcon.png").resolve()
|
|
|
|
def get_ui_background_image_path() -> Path:
|
|
"""Return absolute path to the UI background image.
|
|
|
|
Priority (new → legacy):
|
|
- `background` key in prole.properties (requested)
|
|
- legacy `ui.background`
|
|
|
|
Defaults to img/proleLogoSepia.png under repo root if not set or missing.
|
|
"""
|
|
rel = (
|
|
get_config_value("background")
|
|
or get_config_value("ui.background")
|
|
or "img/proleLogoSepia.png"
|
|
)
|
|
p = (PROJECT_ROOT / rel).resolve()
|
|
if p.exists():
|
|
return p
|
|
# fallback
|
|
return (PROJECT_ROOT / "img/proleLogoSepia.png").resolve()
|
|
|
|
|
|
def is_apple_silicon() -> bool:
|
|
return platform.machine() == "arm64" and platform.system() == "Darwin"
|
|
|
|
|
|
def get_docker_build_platform_args() -> list[str]:
|
|
if is_apple_silicon():
|
|
return ["--platform", "linux/amd64"]
|
|
return []
|
|
|
|
|
|
def normalize_version(text: str) -> str:
|
|
if not text:
|
|
return ""
|
|
import re
|
|
m = re.search(r"(\d+(?:[._-]\d+){0,5})", text)
|
|
if not m:
|
|
nums = re.findall(r"\d+", text)
|
|
else:
|
|
nums = re.findall(r"\d+", m.group(1))
|
|
if not nums:
|
|
return text.strip()
|
|
parts = (nums + ["0", "0"])[:3]
|
|
try:
|
|
parts = [f"{int(p):02d}" for p in parts]
|
|
except Exception:
|
|
parts = [p.zfill(2) for p in parts]
|
|
return ".".join(parts)
|
|
|
|
|
|
DEPENDENCIES = [
|
|
{
|
|
"id": "brew",
|
|
"name": "Homebrew",
|
|
"description": "Package manager for macOS",
|
|
"url": "https://brew.sh",
|
|
"install_cmd": '/bin/bash -c "$(curl -fsSL https://raw.githubusercontent.com/Homebrew/install/HEAD/install.sh)"',
|
|
"check_cmd": "brew --version",
|
|
"bin": "brew",
|
|
},
|
|
{
|
|
"id": "docker",
|
|
"name": "Docker",
|
|
"description": "Container platform for running Prole services",
|
|
"url": "https://www.docker.com/products/docker-desktop",
|
|
"install_cmd": None,
|
|
"check_cmd": "docker --version",
|
|
"bin": "docker",
|
|
},
|
|
{
|
|
"id": "k3d",
|
|
"name": "k3d",
|
|
"description": "Lightweight wrapper to run k3s in Docker",
|
|
"url": "https://k3d.io",
|
|
"install_cmd": "brew install k3d",
|
|
"check_cmd": "k3d --version",
|
|
"bin": "k3d",
|
|
},
|
|
{
|
|
"id": "ollama",
|
|
"name": "Ollama",
|
|
"description": "Local LLM runtime used by the deployment agent",
|
|
"url": "https://ollama.com",
|
|
"install_cmd": "brew install ollama",
|
|
"check_cmd": "ollama --version",
|
|
"bin": "ollama",
|
|
},
|
|
{
|
|
"id": "tshark",
|
|
"name": "tshark (Wireshark)",
|
|
"description": "Network protocol analyzer for network scanning",
|
|
"url": "https://www.wireshark.org",
|
|
"install_cmd": "brew install wireshark" if platform.system() == 'Darwin' else "sudo apt-get update && sudo apt-get install -y tshark",
|
|
"check_cmd": "tshark --version",
|
|
"bin": "tshark",
|
|
},
|
|
{
|
|
"id": "pyshark",
|
|
"name": "pyshark",
|
|
"description": "Python wrapper for tshark",
|
|
"url": "https://github.com/KimiNewt/pyshark",
|
|
"install_cmd": f"'{sys.executable}' -m pip install pyshark",
|
|
"check_cmd": f"'{sys.executable}' -c 'import pyshark'",
|
|
"bin": None,
|
|
},
|
|
]
|
|
|
|
|
|
def get_dep_info(dep: dict) -> Tuple[bool, Optional[str], Optional[str]]:
|
|
bin_name = dep.get("bin") or dep["name"]
|
|
location = None
|
|
version = None
|
|
installed = False
|
|
try:
|
|
if bin_name:
|
|
res = subprocess.run(["bash", "-lc", f"command -v {bin_name}"], capture_output=True, text=True)
|
|
if res.returncode == 0:
|
|
location = res.stdout.strip()
|
|
installed = True
|
|
check_cmd = dep.get("check_cmd")
|
|
if check_cmd:
|
|
res2 = subprocess.run(["bash", "-lc", check_cmd], capture_output=True, text=True)
|
|
if res2.returncode == 0:
|
|
version = " ".join(res2.stdout.strip().splitlines()[:1])
|
|
# If check_cmd succeeded, consider it installed
|
|
installed = True
|
|
if not location:
|
|
location = "Installed via Python"
|
|
except Exception:
|
|
pass
|
|
return installed, location, version
|