mirror of
https://github.com/dredx/prole.git
synced 2026-09-24 16:24:32 +00:00
ProleStatus: app-window default, light splash restored, status bar controls; endpoint config via properties; build + docs
- Default to Application Window mode with full app menu; status bar overlay remains available - Restore clickable startup Splash Tip (5s) with animated GIF and live counter; use light theme (Aqua) - Status bar item: monospaced "P" icon; left click shows overlay (status mode) or main window (app mode) - Overlay: add Maximize button (□) to toggle back to main window; keep click‑through elsewhere - Global hotkey Cmd+Opt+Shift+P toggles modes; Prole menu item mirrors the same toggle and updates title dynamically - Application menu (Prole): Show Status Bar / Show Main Window (contextual), Refresh Now, Quit - Application Window layout: non‑scrolling, vertical 1‑line rows (svc, k3s aggregate, local) with top‑right timestamp; bottom‑left controls (⟳ Refresh, _ Minimize) - Parameterize endpoints via `prole.properties` (bundled + user override). Replace legacy raspberry with retropie defaults - ServiceChecker + UI read endpoints from Config loader; tooltips/labels reflect configured hosts/ports - Build script: generate `Info.plist` with `LSUIElement=false`; bundle resources (`prole-type.gif`, `prole.properties`); ad‑hoc codesign. Universal build supported - Documentation: rewrite README with technical build/run/config details and operational posture Files: - proleStatus/Sources/: AppDelegate.swift, OverlayWindow.swift, StatusView.swift, StatusItemController.swift, SplashTipWindowController.swift, MainWindowController.swift, AppStatusView.swift, ServiceChecker.swift, Config.swift - proleStatus/build.sh - proleStatus/prole.properties - proleStatus/README.md Notes: - Build verified via `./build.sh build` (arm64). App starts in App Window mode with light theme; menu + hotkey + overlay toggle operate as intended
This commit is contained in:
parent
053d907666
commit
8a6e8738db
BIN
img/proleIcon.png
Normal file
BIN
img/proleIcon.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 1.4 MiB |
85
install.py
85
install.py
@ -27,6 +27,14 @@ from installer.workstation import (
|
|||||||
)
|
)
|
||||||
from installer import deploy as inst_deploy
|
from installer import deploy as inst_deploy
|
||||||
|
|
||||||
|
# On macOS, set process name as early as possible so the menu bar shows 'Prole Installer'
|
||||||
|
if platform.system() == 'Darwin':
|
||||||
|
try:
|
||||||
|
from Foundation import NSProcessInfo
|
||||||
|
NSProcessInfo.processInfo().setProcessName_("Prole Installer")
|
||||||
|
except Exception:
|
||||||
|
pass
|
||||||
|
|
||||||
# Get the project root directory (kept local for clarity in this legacy entry)
|
# Get the project root directory (kept local for clarity in this legacy entry)
|
||||||
PROJECT_ROOT = Path(__file__).parent.absolute()
|
PROJECT_ROOT = Path(__file__).parent.absolute()
|
||||||
|
|
||||||
@ -90,7 +98,10 @@ class ProleInstaller:
|
|||||||
self._bg_item = None
|
self._bg_item = None
|
||||||
try:
|
try:
|
||||||
from PIL import Image, ImageTk # optional
|
from PIL import Image, ImageTk # optional
|
||||||
# Use the full-frame background image per latest spec
|
# Use the full-frame background image from config
|
||||||
|
try:
|
||||||
|
bg_path = inst_config.get_ui_background_image_path()
|
||||||
|
except Exception:
|
||||||
bg_path = PROJECT_ROOT / 'img' / 'proleLogoSepia.png'
|
bg_path = PROJECT_ROOT / 'img' / 'proleLogoSepia.png'
|
||||||
if bg_path.exists():
|
if bg_path.exists():
|
||||||
self._bg_pil = Image.open(str(bg_path)).convert('RGBA')
|
self._bg_pil = Image.open(str(bg_path)).convert('RGBA')
|
||||||
@ -190,16 +201,13 @@ class ProleInstaller:
|
|||||||
|
|
||||||
# ---------------- App identity (title, Dock icon) ----------------
|
# ---------------- App identity (title, Dock icon) ----------------
|
||||||
def _set_app_identity(self):
|
def _set_app_identity(self):
|
||||||
"""Set the app name shown by Tk and attempt to set the macOS Dock icon.
|
"""Set the installer identity: process/menu name and Dock icon on macOS.
|
||||||
|
|
||||||
Notes:
|
Notes:
|
||||||
- tk appname affects Tk's internal application name and may improve
|
- We set Tk's appname for consistency.
|
||||||
display in some OS integrations. On macOS, fully changing the menu bar
|
- On macOS, attempt to set the process/menu name to 'Prole Installer'
|
||||||
app name from "Python" typically requires running as a bundled app
|
via NSProcessInfo if PyObjC is available.
|
||||||
with CFBundleName set, but we still set the Tk appname here.
|
- Prefer the Prole.app .icns from the built app; fallback to local PNG/GIF.
|
||||||
- For the Dock icon on macOS, we try to use the ProleStatus.app icns
|
|
||||||
(capital P icon). If PyObjC (AppKit) isn't available, we fall back to
|
|
||||||
setting a Tk window icon from a PNG/GIF in img/.
|
|
||||||
"""
|
"""
|
||||||
# Set Tk application name
|
# Set Tk application name
|
||||||
try:
|
try:
|
||||||
@ -207,11 +215,30 @@ class ProleInstaller:
|
|||||||
except Exception:
|
except Exception:
|
||||||
pass
|
pass
|
||||||
|
|
||||||
# macOS Dock icon via AppKit (preferred)
|
# macOS: set process/menu name and Dock icon via AppKit/Foundation
|
||||||
if platform.system() == 'Darwin':
|
if platform.system() == 'Darwin':
|
||||||
|
# Try to set the visible process name for the menu bar
|
||||||
|
try:
|
||||||
|
from Foundation import NSProcessInfo
|
||||||
|
NSProcessInfo.processInfo().setProcessName_("Prole Installer")
|
||||||
|
except Exception:
|
||||||
|
pass
|
||||||
|
|
||||||
|
# Also try to retitle the first main menu item so the menu next to the Apple logo reads 'Prole Installer'
|
||||||
|
try:
|
||||||
|
from AppKit import NSApplication
|
||||||
|
app = NSApplication.sharedApplication()
|
||||||
|
main_menu = app.mainMenu()
|
||||||
|
if main_menu is not None and main_menu.numberOfItems() > 0:
|
||||||
|
first_item = main_menu.itemAtIndex_(0)
|
||||||
|
if first_item is not None:
|
||||||
|
first_item.setTitle_("Prole Installer")
|
||||||
|
except Exception:
|
||||||
|
pass
|
||||||
|
|
||||||
icns_candidates = [
|
icns_candidates = [
|
||||||
PROJECT_ROOT / 'proleStatus' / 'dist' / 'ProleStatus.app' / 'Contents' / 'Resources' / 'AppIcon.icns',
|
PROJECT_ROOT / 'prole-app' / 'dist' / 'Prole.app' / 'Contents' / 'Resources' / 'AppIcon.icns',
|
||||||
PROJECT_ROOT / 'proleStatus' / 'dist' / 'ProleStatus.app' / 'Contents' / 'Resources' / 'ProleStatus.icns',
|
PROJECT_ROOT / 'prole-app' / 'dist' / 'Prole.app' / 'Contents' / 'Resources' / 'Prole.icns',
|
||||||
]
|
]
|
||||||
icns_path = next((p for p in icns_candidates if p.exists()), None)
|
icns_path = next((p for p in icns_candidates if p.exists()), None)
|
||||||
if icns_path is not None:
|
if icns_path is not None:
|
||||||
@ -226,10 +253,16 @@ class ProleInstaller:
|
|||||||
pass
|
pass
|
||||||
|
|
||||||
# Fallback: Tk icon from image assets (PNG/GIF)
|
# Fallback: Tk icon from image assets (PNG/GIF)
|
||||||
|
# Prefer config-defined icon image
|
||||||
|
try:
|
||||||
|
cfg_icon = inst_config.get_ui_icon_image_path()
|
||||||
|
except Exception:
|
||||||
|
cfg_icon = PROJECT_ROOT / 'img' / 'proleIcon.png'
|
||||||
img_candidates = [
|
img_candidates = [
|
||||||
PROJECT_ROOT / 'img' / 'prole-type.gif',
|
cfg_icon,
|
||||||
PROJECT_ROOT / 'img' / 'prole-type.png',
|
PROJECT_ROOT / 'img' / 'prole-type.png',
|
||||||
PROJECT_ROOT / 'img' / 'ProleStatus.png',
|
PROJECT_ROOT / 'img' / 'prole-type.gif',
|
||||||
|
PROJECT_ROOT / 'img' / 'Prole.png',
|
||||||
PROJECT_ROOT / 'img' / 'proleLogoSepia.png',
|
PROJECT_ROOT / 'img' / 'proleLogoSepia.png',
|
||||||
]
|
]
|
||||||
for p in img_candidates:
|
for p in img_candidates:
|
||||||
@ -600,7 +633,7 @@ class ProleInstaller:
|
|||||||
left = 56
|
left = 56
|
||||||
if not hasattr(self, 'deploy_steps'):
|
if not hasattr(self, 'deploy_steps'):
|
||||||
self.deploy_steps = [
|
self.deploy_steps = [
|
||||||
{'name': 'Build ProleStatus macOS app', 'status': 'pending'},
|
{'name': 'Build Prole macOS app', 'status': 'pending'},
|
||||||
{'name': 'Build workstation Vagrant VM', 'status': 'pending'},
|
{'name': 'Build workstation Vagrant VM', 'status': 'pending'},
|
||||||
{'name': 'Check Docker is running', 'status': 'pending'},
|
{'name': 'Check Docker is running', 'status': 'pending'},
|
||||||
{'name': 'Check or configure container registry', 'status': 'pending'},
|
{'name': 'Check or configure container registry', 'status': 'pending'},
|
||||||
@ -664,7 +697,7 @@ class ProleInstaller:
|
|||||||
self.show_page(target)
|
self.show_page(target)
|
||||||
return
|
return
|
||||||
if current_id == 'build':
|
if current_id == 'build':
|
||||||
# Treat Next as Build for ProleStatus
|
# Treat Next as Build for Prole
|
||||||
self.perform_build()
|
self.perform_build()
|
||||||
return
|
return
|
||||||
if current_id == 'build_vm':
|
if current_id == 'build_vm':
|
||||||
@ -949,7 +982,7 @@ class ProleInstaller:
|
|||||||
|
|
||||||
# Reuse existing deploy steps UI but on light theme
|
# Reuse existing deploy steps UI but on light theme
|
||||||
self.deploy_steps = [
|
self.deploy_steps = [
|
||||||
{'name': 'Build ProleStatus macOS app', 'status': 'pending'},
|
{'name': 'Build Prole macOS app', 'status': 'pending'},
|
||||||
{'name': 'Check Docker is running', 'status': 'pending'},
|
{'name': 'Check Docker is running', 'status': 'pending'},
|
||||||
{'name': 'Check or configure container registry', 'status': 'pending'},
|
{'name': 'Check or configure container registry', 'status': 'pending'},
|
||||||
{'name': 'Ensure target cluster', 'status': 'pending'},
|
{'name': 'Ensure target cluster', 'status': 'pending'},
|
||||||
@ -1033,7 +1066,7 @@ class ProleInstaller:
|
|||||||
|
|
||||||
# ---------------- Build integration ----------------
|
# ---------------- Build integration ----------------
|
||||||
def perform_build(self):
|
def perform_build(self):
|
||||||
"""Run ProleStatus build in an embedded console (Scopped bash subprocess)."""
|
"""Run Prole build in an embedded console (Scoped bash subprocess)."""
|
||||||
# Prepare logs dir and file
|
# Prepare logs dir and file
|
||||||
logs_dir = PROJECT_ROOT / 'logs'
|
logs_dir = PROJECT_ROOT / 'logs'
|
||||||
try:
|
try:
|
||||||
@ -1570,7 +1603,7 @@ class ProleInstaller:
|
|||||||
if logp or vlog:
|
if logp or vlog:
|
||||||
self._render_paragraph('Build output was captured from the embedded console.', y=90)
|
self._render_paragraph('Build output was captured from the embedded console.', y=90)
|
||||||
if logp:
|
if logp:
|
||||||
self._render_paragraph('ProleStatus log:', y=120)
|
self._render_paragraph('Prole build log:', y=120)
|
||||||
self._render_paragraph(logp, y=140)
|
self._render_paragraph(logp, y=140)
|
||||||
y_next = 180 if logp else 120
|
y_next = 180 if logp else 120
|
||||||
if vlog:
|
if vlog:
|
||||||
@ -1579,7 +1612,7 @@ class ProleInstaller:
|
|||||||
# clickable link
|
# clickable link
|
||||||
link_y = (y_next + 56) if vlog else 200
|
link_y = (y_next + 56) if vlog else 200
|
||||||
if logp:
|
if logp:
|
||||||
link1 = self.bg_canvas.create_text(56, link_y, anchor='nw', text='Open ProleStatus log', fill='#0a84ff', font=('Helvetica', 12, 'underline'))
|
link1 = self.bg_canvas.create_text(56, link_y, anchor='nw', text='Open Prole build log', fill='#0a84ff', font=('Helvetica', 12, 'underline'))
|
||||||
self._canvas_items.append(link1)
|
self._canvas_items.append(link1)
|
||||||
if vlog:
|
if vlog:
|
||||||
link2 = self.bg_canvas.create_text(56, link_y + 28, anchor='nw', text='Open Vagrant log', fill='#0a84ff', font=('Helvetica', 12, 'underline'))
|
link2 = self.bg_canvas.create_text(56, link_y + 28, anchor='nw', text='Open Vagrant log', fill='#0a84ff', font=('Helvetica', 12, 'underline'))
|
||||||
@ -1993,10 +2026,10 @@ echo "All dependencies installed successfully!"
|
|||||||
self.deploy_widgets['Ensure target cluster']['step']['name'] = f"Ensure target cluster ({env})"
|
self.deploy_widgets['Ensure target cluster']['step']['name'] = f"Ensure target cluster ({env})"
|
||||||
self.deploy_widgets['Ensure target cluster']['label'].master.master.children['!label'].configure(text=f"Ensure target cluster ({env})")
|
self.deploy_widgets['Ensure target cluster']['label'].master.master.children['!label'].configure(text=f"Ensure target cluster ({env})")
|
||||||
|
|
||||||
# Step 0: Build ProleStatus macOS app
|
# Step 0: Build Prole macOS app
|
||||||
self.update_deploy_step_status('Build ProleStatus macOS app', 'running')
|
self.update_deploy_step_status('Build Prole macOS app', 'running')
|
||||||
self.build_prole_status_app()
|
self.build_prole_app()
|
||||||
self.update_deploy_step_status('Build ProleStatus macOS app', 'completed')
|
self.update_deploy_step_status('Build Prole macOS app', 'completed')
|
||||||
|
|
||||||
# Step 1: Check Docker
|
# Step 1: Check Docker
|
||||||
self.update_deploy_step_status('Check Docker is running', 'running')
|
self.update_deploy_step_status('Check Docker is running', 'running')
|
||||||
@ -2049,9 +2082,9 @@ echo "All dependencies installed successfully!"
|
|||||||
"""Check if Xcode Command Line Tools are installed via deploy helper."""
|
"""Check if Xcode Command Line Tools are installed via deploy helper."""
|
||||||
return inst_deploy.check_xcode_tools()
|
return inst_deploy.check_xcode_tools()
|
||||||
|
|
||||||
def build_prole_status_app(self):
|
def build_prole_app(self):
|
||||||
"""Delegate building the native ProleStatus app to deploy helper."""
|
"""Delegate building the native ProleStatus app to deploy helper."""
|
||||||
return inst_deploy.build_prole_status_app(PROJECT_ROOT)
|
return inst_deploy.build_prole_app(PROJECT_ROOT)
|
||||||
|
|
||||||
def check_docker_running(self):
|
def check_docker_running(self):
|
||||||
"""Check if Docker is running"""
|
"""Check if Docker is running"""
|
||||||
|
|||||||
@ -1,7 +1,7 @@
|
|||||||
"""
|
"""
|
||||||
Build helpers for the Prole installer (root-level package).
|
Build helpers for the Prole installer (root-level package).
|
||||||
|
|
||||||
UI-agnostic command composition for the ProleStatus build.
|
UI-agnostic command composition for the Prole macOS app build.
|
||||||
"""
|
"""
|
||||||
from __future__ import annotations
|
from __future__ import annotations
|
||||||
|
|
||||||
@ -9,11 +9,11 @@ from pathlib import Path
|
|||||||
|
|
||||||
|
|
||||||
def get_build_command(project_root: Path, env: str | None) -> str:
|
def get_build_command(project_root: Path, env: str | None) -> str:
|
||||||
"""Return the ProleStatus build command with an env comment suffix.
|
"""Return the Prole app build command with an env comment suffix.
|
||||||
|
|
||||||
Example output:
|
Example output:
|
||||||
cd "/path/to/repo" && ./proleStatus/build.sh build # Dev
|
cd "/path/to/repo" && ./prole-app/build.sh build # Dev
|
||||||
"""
|
"""
|
||||||
env_label = (env or "Dev").strip().title()
|
env_label = (env or "Dev").strip().title()
|
||||||
prole_root = str(project_root)
|
prole_root = str(project_root)
|
||||||
return f"cd \"{prole_root}\" && ./proleStatus/build.sh build # {env_label}"
|
return f"cd \"{prole_root}\" && ./prole-app/build.sh build # {env_label}"
|
||||||
|
|||||||
@ -10,9 +10,82 @@ from pathlib import Path
|
|||||||
import platform
|
import platform
|
||||||
import subprocess
|
import subprocess
|
||||||
from typing import Tuple, Optional
|
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
|
# Repository root: this file lives at <repo>/installer/config.py → parent is repo
|
||||||
PROJECT_ROOT = Path(__file__).resolve().parents[1]
|
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 installer fallback and builds).
|
||||||
|
|
||||||
|
Defaults to img/proleIcon.png under repo root if not set or missing.
|
||||||
|
"""
|
||||||
|
rel = get_config_value("ui.icon", "img/proleIcon.png") 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.
|
||||||
|
|
||||||
|
Defaults to img/proleLogoSepia.png under repo root if not set or missing.
|
||||||
|
"""
|
||||||
|
rel = get_config_value("ui.background", "img/proleLogoSepia.png") 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:
|
def is_apple_silicon() -> bool:
|
||||||
|
|||||||
@ -1,7 +1,7 @@
|
|||||||
"""
|
"""
|
||||||
Deploy/build helpers for the Prole installer (root-level package).
|
Deploy/build helpers for the Prole installer (root-level package).
|
||||||
|
|
||||||
Encapsulates Xcode tools check and building the native ProleStatus app.
|
Encapsulates Xcode tools check and building the native Prole app.
|
||||||
"""
|
"""
|
||||||
from __future__ import annotations
|
from __future__ import annotations
|
||||||
|
|
||||||
@ -21,37 +21,36 @@ def check_xcode_tools() -> bool:
|
|||||||
return False
|
return False
|
||||||
|
|
||||||
|
|
||||||
def build_prole_status_app(project_root: Path) -> None:
|
def build_prole_app(project_root: Path) -> None:
|
||||||
if platform.system() != "Darwin":
|
if platform.system() != "Darwin":
|
||||||
raise Exception("Building the macOS app requires macOS.")
|
raise Exception("Building the macOS app requires macOS.")
|
||||||
if not check_xcode_tools():
|
if not check_xcode_tools():
|
||||||
raise Exception("Xcode Command Line Tools not found. Please run: xcode-select --install")
|
raise Exception("Xcode Command Line Tools not found. Please run: xcode-select --install")
|
||||||
|
|
||||||
# Only use the top-level proleStatus project location; drop legacy
|
# Use the top-level prole-app project directory
|
||||||
# references to nested paths under prole/ to keep the tree clean.
|
prole_app_dir = Path(project_root) / "prole-app"
|
||||||
prole_status_dir = Path(project_root) / "proleStatus"
|
build_script = prole_app_dir / "build.sh"
|
||||||
build_script = prole_status_dir / "build.sh"
|
|
||||||
if not build_script.exists():
|
if not build_script.exists():
|
||||||
raise Exception(
|
raise Exception(
|
||||||
f"Build script not found at {build_script}"
|
f"Build script not found at {build_script}"
|
||||||
)
|
)
|
||||||
|
|
||||||
result = subprocess.run(["bash", str(build_script), "build"], cwd=prole_status_dir, capture_output=True, text=True)
|
result = subprocess.run(["bash", str(build_script), "build"], cwd=prole_app_dir, capture_output=True, text=True)
|
||||||
if result.returncode != 0:
|
if result.returncode != 0:
|
||||||
raise Exception(f"ProleStatus build failed: {result.stderr or result.stdout}")
|
raise Exception(f"Prole build failed: {result.stderr or result.stdout}")
|
||||||
|
|
||||||
app_path = prole_status_dir / "dist" / "ProleStatus.app"
|
app_path = prole_app_dir / "dist" / "Prole.app"
|
||||||
if not app_path.exists():
|
if not app_path.exists():
|
||||||
raise Exception("Build completed but ProleStatus.app was not found in dist/")
|
raise Exception("Build completed but Prole.app was not found in dist/")
|
||||||
|
|
||||||
try:
|
try:
|
||||||
msg = (
|
msg = (
|
||||||
"ProleStatus.app has been built successfully.\n\n"
|
"Prole.app has been built successfully.\n\n"
|
||||||
f"Location: {app_path}\n\n"
|
f"Location: {app_path}\n\n"
|
||||||
"Would you like to copy it to /Applications?"
|
"Would you like to copy it to /Applications?"
|
||||||
)
|
)
|
||||||
if messagebox.askyesno("ProleStatus Built", msg):
|
if messagebox.askyesno("Prole Built", msg):
|
||||||
dest = Path("/Applications") / "ProleStatus.app"
|
dest = Path("/Applications") / "Prole.app"
|
||||||
subprocess.run(["cp", "-R", str(app_path), str(dest)], check=True)
|
subprocess.run(["cp", "-R", str(app_path), str(dest)], check=True)
|
||||||
messagebox.showinfo("Copied", f"Copied to {dest}")
|
messagebox.showinfo("Copied", f"Copied to {dest}")
|
||||||
except Exception as copy_err:
|
except Exception as copy_err:
|
||||||
|
|||||||
@ -10,11 +10,11 @@
|
|||||||
#####################################################
|
#####################################################
|
||||||
```
|
```
|
||||||
|
|
||||||
ProleStatus
|
Prole
|
||||||
— macOS status and control surface for Prole endpoints, with a path to an integrated virtual workstation harness.
|
— macOS status and control surface for Prole endpoints, with a path to an integrated virtual workstation harness.
|
||||||
|
|
||||||
Overview
|
Overview
|
||||||
- ProleStatus provides live reachability and latency signals for Prole service endpoints. It operates in two modes: a regular application window for situational awareness and a minimalist status‑bar overlay for persistent at‑a‑glance status.
|
- Prole provides live reachability and latency signals for Prole service endpoints. It operates in two modes: a regular application window for situational awareness and a minimalist status‑bar overlay for persistent at‑a‑glance status.
|
||||||
- The application will evolve to include a virtual workstation surface to coordinate work across a network of Prole‑linked LLMs. The current scope is operational visibility and control of endpoints.
|
- The application will evolve to include a virtual workstation surface to coordinate work across a network of Prole‑linked LLMs. The current scope is operational visibility and control of endpoints.
|
||||||
|
|
||||||
Supported platform
|
Supported platform
|
||||||
@ -35,31 +35,31 @@ Build environment requirements
|
|||||||
- `plutil` (plist formatting, via xcrun if needed)
|
- `plutil` (plist formatting, via xcrun if needed)
|
||||||
|
|
||||||
Repository layout (subset)
|
Repository layout (subset)
|
||||||
- `proleStatus/` — macOS app sources and build system
|
- `prole-app/` — macOS app sources and build system
|
||||||
- `Sources/` — Swift sources (AppKit)
|
- `Sources/` — Swift sources (AppKit)
|
||||||
- `build.sh` — hermetic CLI build producing a `.app` bundle
|
- `build.sh` — hermetic CLI build producing a `.app` bundle
|
||||||
- `prole.properties` — default endpoint configuration (bundled into Resources)
|
- `prole.properties` — default endpoint configuration (bundled into Resources)
|
||||||
- `dist/ProleStatus.app` — build output
|
- `dist/Prole.app` — build output
|
||||||
|
|
||||||
Build script
|
Build script
|
||||||
- The build is driven by `proleStatus/build.sh`. Typical usage:
|
- The build is driven by `prole-app/build.sh`. Typical usage:
|
||||||
```
|
```
|
||||||
cd proleStatus
|
cd prole-app
|
||||||
./build.sh build # build for host arch
|
./build.sh build # build for host arch
|
||||||
./build.sh run # build (if needed) and open the app
|
./build.sh run # build (if needed) and open the app
|
||||||
./build.sh build-universal # produce a universal (arm64+x86_64) binary
|
./build.sh build-universal # produce a universal (arm64+x86_64) binary
|
||||||
./build.sh debug # run in foreground with verbose logs
|
./build.sh debug # run in foreground with verbose logs
|
||||||
./build.sh clean # remove build artifacts
|
./build.sh clean # remove build artifacts
|
||||||
./build.sh package # zip dist/ProleStatus.app into dist/ProleStatus.zip
|
./build.sh package # zip dist/Prole.app into dist/Prole.zip
|
||||||
```
|
```
|
||||||
|
|
||||||
What the script does
|
What the script does
|
||||||
- Compiles all Swift sources with `swiftc` (AppKit, Carbon, Network frameworks).
|
- Compiles all Swift sources with `swiftc` (AppKit, Carbon, Network frameworks).
|
||||||
- Generates `Contents/Info.plist` with `LSUIElement=false` so the app can present a standard menu when in Application Window mode.
|
- Generates `Contents/Info.plist` with `LSUIElement=false` so the app can present a standard menu when in Application Window mode.
|
||||||
- Generates an application icon (`ProleStatus.icns`) and a template status glyph as needed.
|
- Generates an application icon (`Prole.icns`) and a template status glyph as needed.
|
||||||
- Copies resources:
|
- Copies resources:
|
||||||
- `www/images/prole-type.gif` → `Contents/Resources/prole-type.gif` (for the startup tip splash)
|
- `www/images/prole-type.gif` → `Contents/Resources/prole-type.gif` (for the startup tip splash)
|
||||||
- `proleStatus/prole.properties` → `Contents/Resources/prole.properties`
|
- `prole-app/prole.properties` → `Contents/Resources/prole.properties`
|
||||||
- Performs ad‑hoc code signing of the `.app` bundle.
|
- Performs ad‑hoc code signing of the `.app` bundle.
|
||||||
|
|
||||||
Alternate build path (installer UI)
|
Alternate build path (installer UI)
|
||||||
@ -67,7 +67,7 @@ Alternate build path (installer UI)
|
|||||||
```
|
```
|
||||||
python3 install.py
|
python3 install.py
|
||||||
```
|
```
|
||||||
- Use the “Build ProleStatus macOS app” step. The installer will produce `proleStatus/dist/ProleStatus.app` and can optionally copy it to `/Applications`.
|
- Use the “Build Prole macOS app” step. The installer will produce `prole-app/dist/Prole.app` and can optionally copy it to `/Applications`.
|
||||||
|
|
||||||
Run modes and controls
|
Run modes and controls
|
||||||
- Modes:
|
- Modes:
|
||||||
@ -81,8 +81,8 @@ Run modes and controls
|
|||||||
|
|
||||||
Configuration
|
Configuration
|
||||||
- Endpoint configuration is provided via Java‑style `key=value` properties. Two locations are read at startup; the user override has precedence:
|
- Endpoint configuration is provided via Java‑style `key=value` properties. Two locations are read at startup; the user override has precedence:
|
||||||
1. Bundled defaults: `ProleStatus.app/Contents/Resources/prole.properties`
|
1. Bundled defaults: `Prole.app/Contents/Resources/prole.properties`
|
||||||
2. User override (optional): `~/Library/Application Support/ProleStatus/prole.properties`
|
2. User override (optional): `~/Library/Application Support/Prole/prole.properties`
|
||||||
- Default keys:
|
- Default keys:
|
||||||
- `svc.host`, `svc.port`
|
- `svc.host`, `svc.port`
|
||||||
- `k3s.retropie.host`, `k3s.retropie.port`
|
- `k3s.retropie.host`, `k3s.retropie.port`
|
||||||
@ -116,7 +116,7 @@ Security & signing
|
|||||||
- The `.app` bundle is ad‑hoc signed by default. For distribution, replace with a Developer ID signature and notarize as appropriate for your environment.
|
- The `.app` bundle is ad‑hoc signed by default. For distribution, replace with a Developer ID signature and notarize as appropriate for your environment.
|
||||||
|
|
||||||
Roadmap
|
Roadmap
|
||||||
- Integration of a virtual workstation to orchestrate a network of Prole‑linked LLMs from within the ProleStatus surface.
|
- Integration of a virtual workstation to orchestrate a network of Prole‑linked LLMs from within the Prole surface.
|
||||||
|
|
||||||
License
|
License
|
||||||
- See the repository `LICENSE` file.
|
- See the repository `LICENSE` file.
|
||||||
@ -28,7 +28,7 @@ final class Config {
|
|||||||
// Load user override from Application Support
|
// Load user override from Application Support
|
||||||
let fm = FileManager.default
|
let fm = FileManager.default
|
||||||
if let appSupport = try? fm.url(for: .applicationSupportDirectory, in: .userDomainMask, appropriateFor: nil, create: false) {
|
if let appSupport = try? fm.url(for: .applicationSupportDirectory, in: .userDomainMask, appropriateFor: nil, create: false) {
|
||||||
let dir = appSupport.appendingPathComponent("ProleStatus", isDirectory: true)
|
let dir = appSupport.appendingPathComponent("Prole", isDirectory: true)
|
||||||
let url = dir.appendingPathComponent("prole.properties")
|
let url = dir.appendingPathComponent("prole.properties")
|
||||||
if fm.fileExists(atPath: url.path) {
|
if fm.fileExists(atPath: url.path) {
|
||||||
merge(loadProperties(url: url))
|
merge(loadProperties(url: url))
|
||||||
@ -68,4 +68,14 @@ final class Config {
|
|||||||
|
|
||||||
var localHost: String { string("k3d.local.host", default: "localhost") }
|
var localHost: String { string("k3d.local.host", default: "localhost") }
|
||||||
var localPort: Int { int("k3d.local.port", default: 6443) }
|
var localPort: Int { int("k3d.local.port", default: 6443) }
|
||||||
|
|
||||||
|
// UI asset config
|
||||||
|
// Background image file name relative to the app bundle Resources. If a path is provided, only the last component is used.
|
||||||
|
var uiBackground: String {
|
||||||
|
let raw = string("ui.background", default: "img/proleLogoSepia.png")
|
||||||
|
if let lastSlash = raw.lastIndex(of: "/") {
|
||||||
|
return String(raw[raw.index(after: lastSlash)...])
|
||||||
|
}
|
||||||
|
return raw
|
||||||
|
}
|
||||||
}
|
}
|
||||||
@ -67,13 +67,24 @@ final class SplashTipWindowController: NSWindowController {
|
|||||||
counterLabel.bottomAnchor.constraint(equalTo: content.bottomAnchor, constant: -4)
|
counterLabel.bottomAnchor.constraint(equalTo: content.bottomAnchor, constant: -4)
|
||||||
])
|
])
|
||||||
|
|
||||||
// Load the animated GIF from bundle resources if present
|
// Load background/splash image from config (bundled in Resources) if present
|
||||||
if let url = Bundle.main.url(forResource: "prole-type", withExtension: "gif"),
|
let bgName = Config.shared.uiBackground
|
||||||
|
let candidates: [(String, String?)] = [
|
||||||
|
(bgName, nil),
|
||||||
|
("prole-type", "gif")
|
||||||
|
]
|
||||||
|
var loaded = false
|
||||||
|
for (res, ext) in candidates {
|
||||||
|
if let url = Bundle.main.url(forResource: res, withExtension: ext),
|
||||||
let img = NSImage(contentsOf: url) {
|
let img = NSImage(contentsOf: url) {
|
||||||
imageView.image = img
|
imageView.image = img
|
||||||
} else {
|
loaded = true
|
||||||
// Fallback: show placeholder text if the gif is missing
|
break
|
||||||
let placeholder = NSTextField(labelWithString: "prole-type.gif not found")
|
}
|
||||||
|
}
|
||||||
|
if !loaded {
|
||||||
|
// Fallback: show placeholder text if the resource is missing
|
||||||
|
let placeholder = NSTextField(labelWithString: "Splash background not found")
|
||||||
placeholder.textColor = .secondaryLabelColor
|
placeholder.textColor = .secondaryLabelColor
|
||||||
placeholder.alignment = .center
|
placeholder.alignment = .center
|
||||||
placeholder.translatesAutoresizingMaskIntoConstraints = false
|
placeholder.translatesAutoresizingMaskIntoConstraints = false
|
||||||
@ -1,10 +1,10 @@
|
|||||||
#!/usr/bin/env bash
|
#!/usr/bin/env bash
|
||||||
set -euo pipefail
|
set -euo pipefail
|
||||||
|
|
||||||
# ProleStatus build script — builds a macOS .app bundle without launching Xcode
|
# Prole build script — builds a macOS .app bundle without launching Xcode
|
||||||
# Requirements: Xcode Command Line Tools (swiftc, codesign, plutil, lipo)
|
# Requirements: Xcode Command Line Tools (swiftc, codesign, plutil, lipo)
|
||||||
|
|
||||||
APP_NAME="ProleStatus"
|
APP_NAME="Prole"
|
||||||
BUNDLE_ID="org.prole.${APP_NAME}"
|
BUNDLE_ID="org.prole.${APP_NAME}"
|
||||||
MIN_MACOS="12.0"
|
MIN_MACOS="12.0"
|
||||||
|
|
||||||
@ -50,6 +50,21 @@ ensure_dirs() {
|
|||||||
mkdir -p "$BUILD_DIR" "$DIST_DIR" "$MACOS_DIR" "$RESOURCES_DIR"
|
mkdir -p "$BUILD_DIR" "$DIST_DIR" "$MACOS_DIR" "$RESOURCES_DIR"
|
||||||
}
|
}
|
||||||
|
|
||||||
|
# Read a key from prole.properties (very simple parser)
|
||||||
|
prop_get() {
|
||||||
|
local key="$1"
|
||||||
|
local file="$ROOT_DIR/prole.properties"
|
||||||
|
if [[ -f "$file" ]]; then
|
||||||
|
local line
|
||||||
|
line=$(grep -E "^${key}=" "$file" | tail -n1 || true)
|
||||||
|
if [[ -n "$line" ]]; then
|
||||||
|
echo "${line#*=}"
|
||||||
|
return 0
|
||||||
|
fi
|
||||||
|
fi
|
||||||
|
return 1
|
||||||
|
}
|
||||||
|
|
||||||
gen_statusbar_icon_png() {
|
gen_statusbar_icon_png() {
|
||||||
# Generates a small monochrome template PNG for the status bar (18x18)
|
# Generates a small monochrome template PNG for the status bar (18x18)
|
||||||
local out_png="$RESOURCES_DIR/statusIcon.png"
|
local out_png="$RESOURCES_DIR/statusIcon.png"
|
||||||
@ -89,14 +104,23 @@ SWIFT
|
|||||||
}
|
}
|
||||||
|
|
||||||
gen_app_icns() {
|
gen_app_icns() {
|
||||||
# Create a basic .icns with a bold 'P' on transparent background
|
# Create an .icns for the app. Prefer a configured source image (ui.icon),
|
||||||
|
# otherwise fall back to generating a bold 'P'.
|
||||||
local icon_name="${APP_NAME}"
|
local icon_name="${APP_NAME}"
|
||||||
local iconset_dir="$BUILD_DIR/${icon_name}.iconset"
|
local iconset_dir="$BUILD_DIR/${icon_name}.iconset"
|
||||||
rm -rf "$iconset_dir"
|
rm -rf "$iconset_dir"
|
||||||
mkdir -p "$iconset_dir"
|
mkdir -p "$iconset_dir"
|
||||||
|
|
||||||
# Generate a 1024 base PNG using AppKit so we don't depend on ImageMagick/Pillow
|
|
||||||
local base_png="$BUILD_DIR/${icon_name}_1024.png"
|
local base_png="$BUILD_DIR/${icon_name}_1024.png"
|
||||||
|
|
||||||
|
# Try configured ui.icon relative to repo root
|
||||||
|
local ui_icon
|
||||||
|
ui_icon="$(prop_get ui.icon || true)"
|
||||||
|
if [[ -n "$ui_icon" && -f "$PARENT_DIR/$ui_icon" ]]; then
|
||||||
|
# Use the provided icon image as base; if not already 1024x1024, sips will resize for each size
|
||||||
|
cp "$PARENT_DIR/$ui_icon" "$base_png"
|
||||||
|
else
|
||||||
|
# Generate a 1024 base PNG using AppKit so we don't depend on ImageMagick/Pillow
|
||||||
/usr/bin/env xcrun swift -F /System/Library/PrivateFrameworks - <<'SWIFT' "$base_png"
|
/usr/bin/env xcrun swift -F /System/Library/PrivateFrameworks - <<'SWIFT' "$base_png"
|
||||||
import AppKit
|
import AppKit
|
||||||
import Foundation
|
import Foundation
|
||||||
@ -137,6 +161,7 @@ guard let tiff = img.tiffRepresentation,
|
|||||||
}
|
}
|
||||||
try! png.write(to: URL(fileURLWithPath: outPath))
|
try! png.write(to: URL(fileURLWithPath: outPath))
|
||||||
SWIFT
|
SWIFT
|
||||||
|
fi
|
||||||
|
|
||||||
# Create all iconset sizes from the base image
|
# Create all iconset sizes from the base image
|
||||||
for s in 16 32 128 256 512; do
|
for s in 16 32 128 256 512; do
|
||||||
@ -185,6 +210,19 @@ copy_extra_resources() {
|
|||||||
echo "[resources] prole-type.gif not found at $gif_src (skipping)"
|
echo "[resources] prole-type.gif not found at $gif_src (skipping)"
|
||||||
fi
|
fi
|
||||||
|
|
||||||
|
# Copy configured background image (ui.background) into Resources, keeping basename
|
||||||
|
local ui_bg
|
||||||
|
ui_bg="$(prop_get ui.background || true)"
|
||||||
|
if [[ -n "$ui_bg" && -f "$PARENT_DIR/$ui_bg" ]]; then
|
||||||
|
mkdir -p "$RESOURCES_DIR"
|
||||||
|
local base
|
||||||
|
base="$(basename "$ui_bg")"
|
||||||
|
cp "$PARENT_DIR/$ui_bg" "$RESOURCES_DIR/$base"
|
||||||
|
echo "[resources] Copied background image ($base) into Resources"
|
||||||
|
else
|
||||||
|
echo "[resources] ui.background not set or file missing (skipping)"
|
||||||
|
fi
|
||||||
|
|
||||||
# Copy default properties file if present
|
# Copy default properties file if present
|
||||||
local props_src="$ROOT_DIR/prole.properties"
|
local props_src="$ROOT_DIR/prole.properties"
|
||||||
if [[ -f "$props_src" ]]; then
|
if [[ -f "$props_src" ]]; then
|
||||||
33
prole-app/prole.properties
Normal file
33
prole-app/prole.properties
Normal file
@ -0,0 +1,33 @@
|
|||||||
|
# Prole default endpoints (bundled). Override in:
|
||||||
|
# ~/Library/Application Support/Prole/prole.properties
|
||||||
|
# using the same key=value format.
|
||||||
|
|
||||||
|
# UI assets (relative to repo root when building; file name within app bundle at runtime)
|
||||||
|
# Icon image used for app icon generation and installer UI fallback
|
||||||
|
ui.icon=img/proleIcon.png
|
||||||
|
# Background image used by the app splash/tip and installer background
|
||||||
|
ui.background=img/proleLogoSepia.png
|
||||||
|
|
||||||
|
# Core service
|
||||||
|
svc.host=svc.prole.org
|
||||||
|
svc.port=443
|
||||||
|
|
||||||
|
# k3s aggregate ? replace legacy raspberry with retropie
|
||||||
|
k3s.retropie.host=retropie.prole.org
|
||||||
|
k3s.retropie.port=6443
|
||||||
|
# k3s.pi.host=pi.prole.org
|
||||||
|
# k3s.pi.port=6443
|
||||||
|
|
||||||
|
# local k3d
|
||||||
|
k3d.local.host=localhost
|
||||||
|
k3d.local.port=6443
|
||||||
|
|
||||||
|
# internal postgres
|
||||||
|
postgres.host=k3s.prole.org
|
||||||
|
postgres.port=5432
|
||||||
|
|
||||||
|
# Reserved for future cloud endpoints
|
||||||
|
# aws.eks.host=
|
||||||
|
# aws.eks.port=
|
||||||
|
# gcp.gke.host=
|
||||||
|
# gcp.gke.port=
|
||||||
@ -1,23 +0,0 @@
|
|||||||
# ProleStatus default endpoints (bundled). Override in:
|
|
||||||
# ~/Library/Application Support/ProleStatus/prole.properties
|
|
||||||
# using the same key=value format.
|
|
||||||
|
|
||||||
# Core service
|
|
||||||
svc.host=svc.prole.org
|
|
||||||
svc.port=443
|
|
||||||
|
|
||||||
# k3s aggregate ? replace legacy raspberry with retropie
|
|
||||||
k3s.retropie.host=retropie.prole.org
|
|
||||||
k3s.retropie.port=6443
|
|
||||||
# k3s.pi.host=pi.prole.org
|
|
||||||
# k3s.pi.port=6443
|
|
||||||
|
|
||||||
# local k3d
|
|
||||||
k3d.local.host=localhost
|
|
||||||
k3d.local.port=6443
|
|
||||||
|
|
||||||
# Reserved for future cloud endpoints
|
|
||||||
# aws.eks.host=
|
|
||||||
# aws.eks.port=
|
|
||||||
# gcp.gke.host=
|
|
||||||
# gcp.gke.port=
|
|
||||||
Loading…
Reference in New Issue
Block a user