331de4bc9e
- New settings page at /settings (localhost-only) accessible via tray icon - PIN manager with 6-digit codes, 5-min TTL, single-use - API token uses secrets.token_urlsafe(32) for 256-bit entropy - Config auto-creates config.yaml on first run with detected local IP - Default PIN and token generated randomly on first startup - Added pin_ttl setting (default 300s) - Updated build_exe.py for onefile exe with settings assets bundled - Added start.bat and start.sh for easy development startup
135 lines
3.2 KiB
Python
135 lines
3.2 KiB
Python
"""Build script: compile PaperDash server to a standalone .exe with system tray.
|
|
|
|
Usage:
|
|
python build_exe.py # Build in release mode
|
|
python build_exe.py --debug # Build in debug mode (with console)
|
|
|
|
Output: server/dist/PaperDash.exe
|
|
"""
|
|
|
|
from __future__ import annotations
|
|
|
|
import argparse
|
|
import os
|
|
import subprocess
|
|
import sys
|
|
|
|
SCRIPT_DIR = os.path.dirname(os.path.abspath(__file__))
|
|
DIST_DIR = os.path.join(SCRIPT_DIR, "dist")
|
|
|
|
|
|
def build(debug: bool = False) -> None:
|
|
"""Compile the server to an .exe using PyInstaller."""
|
|
os.makedirs(DIST_DIR, exist_ok=True)
|
|
|
|
# PyInstaller spec content — wraps the server with system tray
|
|
spec_content = f'''
|
|
# -*- mode: python ; coding: utf-8 -*-
|
|
"""PyInstaller spec for PaperDash with system tray."""
|
|
|
|
import os
|
|
from pathlib import Path
|
|
|
|
block_cipher = None
|
|
|
|
a = Analysis(
|
|
["tray_wrapper.py"],
|
|
pathex=[],
|
|
binaries=[],
|
|
datas=[
|
|
("settings.html", "."),
|
|
("settings.css", "."),
|
|
],
|
|
hiddenimports=[
|
|
"fastapi",
|
|
"uvicorn",
|
|
"pydantic",
|
|
"pystray",
|
|
"PIL",
|
|
"audio_mixer",
|
|
"media",
|
|
"notifications",
|
|
"telemetry",
|
|
"main",
|
|
"yaml",
|
|
],
|
|
hookspath=[],
|
|
hooksconfig={{}},
|
|
runtime_hooks=[],
|
|
excludes=[],
|
|
win_no_prefer_redirects=False,
|
|
win_private_assemblies=False,
|
|
cipher=block_cipher,
|
|
noarchive=False,
|
|
)
|
|
|
|
pyz = PYZ(a.pure, a.zipped_data, cipher=block_cipher)
|
|
|
|
exe = EXE(
|
|
pyz,
|
|
a.scripts,
|
|
a.binaries,
|
|
a.datas,
|
|
[],
|
|
name="PaperDash",
|
|
debug={"True": True, "False": False}[str(debug)],
|
|
bootloader_ignore_signals=False,
|
|
strip=False,
|
|
upx=True,
|
|
upx_exclude=[],
|
|
runtime_tmpdir=None,
|
|
console=False if not debug else True,
|
|
disable_windowed_traceback=False,
|
|
argv_emulation=False,
|
|
target_arch=None,
|
|
codesign_identity=None,
|
|
entitlements_file=None,
|
|
icon=None,
|
|
onefile=True,
|
|
)
|
|
'''
|
|
|
|
spec_path = os.path.join(SCRIPT_DIR, "PaperDash.spec")
|
|
with open(spec_path, "w") as f:
|
|
f.write(spec_content)
|
|
|
|
try:
|
|
cmd = [
|
|
sys.executable,
|
|
"-m",
|
|
"PyInstaller",
|
|
spec_path,
|
|
"--clean",
|
|
]
|
|
if debug:
|
|
cmd.append("--debug=all")
|
|
|
|
print(f"Building PaperDash.exe (debug={debug})...")
|
|
print(f" Spec: {spec_path}")
|
|
print(f" Output: {DIST_DIR}/PaperDash.exe")
|
|
print()
|
|
|
|
subprocess.run(cmd, check=True)
|
|
|
|
exe_path = os.path.join(DIST_DIR, "PaperDash.exe")
|
|
if os.path.exists(exe_path):
|
|
size_mb = os.path.getsize(exe_path) / (1024 * 1024)
|
|
print(f"\nBuild complete: {exe_path} ({size_mb:.1f} MB)")
|
|
else:
|
|
print("\nBuild completed but .exe not found in dist/")
|
|
|
|
except subprocess.CalledProcessError as e:
|
|
print(f"\nBuild failed: {e}", file=sys.stderr)
|
|
sys.exit(1)
|
|
finally:
|
|
# Clean up spec file
|
|
if os.path.exists(spec_path):
|
|
os.remove(spec_path)
|
|
|
|
|
|
if __name__ == "__main__":
|
|
parser = argparse.ArgumentParser(description="Build PaperDash .exe")
|
|
parser.add_argument("--debug", action="store_true", help="Build with debug console")
|
|
args = parser.parse_args()
|
|
build(debug=args.debug)
|