Files
Kareem Horstink 331de4bc9e
Build Android APK / build (push) Failing after 4m21s
Build Server .exe / build (push) Failing after 2m34s
Add settings page, PIN manager, and auto-config
- 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
2026-07-03 08:21:46 +00:00

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)