- Add network interface selection per server (VPN/multi-NIC support) - Fix "Install Everything" button hanging on error - Add interactive SSH terminal with PTY (pyte + xterm-256color) - Add release.py for automated versioning and changelog generation - Add CLAUDE.md with project instructions - Add screenshots and release binaries for v1.1–v1.4 Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
122 lines
3.5 KiB
Python
122 lines
3.5 KiB
Python
#!/usr/bin/env python3
|
|
"""
|
|
Build script — creates platform-specific executables via PyInstaller.
|
|
Run on the target OS to build for that platform.
|
|
|
|
Usage:
|
|
python build.py # build for current platform
|
|
python build.py --clean # clean build artifacts first
|
|
"""
|
|
|
|
import os
|
|
import sys
|
|
import shutil
|
|
import platform
|
|
|
|
# Add project root
|
|
sys.path.insert(0, os.path.dirname(os.path.abspath(__file__)))
|
|
from version import __version__, __app_name__
|
|
|
|
PROJECT_DIR = os.path.dirname(os.path.abspath(__file__))
|
|
DIST_DIR = os.path.join(PROJECT_DIR, "dist")
|
|
BUILD_DIR = os.path.join(PROJECT_DIR, "build")
|
|
RELEASES_DIR = os.path.join(PROJECT_DIR, "releases")
|
|
|
|
|
|
def get_platform_tag() -> str:
|
|
system = platform.system().lower()
|
|
machine = platform.machine().lower()
|
|
|
|
arch_map = {
|
|
"x86_64": "x64", "amd64": "x64",
|
|
"x86": "x32", "i686": "x32", "i386": "x32",
|
|
"aarch64": "arm64", "arm64": "arm64",
|
|
"armv7l": "arm",
|
|
}
|
|
arch = arch_map.get(machine, machine)
|
|
|
|
os_map = {"windows": "win", "linux": "linux", "darwin": "mac"}
|
|
os_tag = os_map.get(system, system)
|
|
|
|
return f"{os_tag}-{arch}"
|
|
|
|
|
|
def clean():
|
|
for d in [DIST_DIR, BUILD_DIR]:
|
|
if os.path.exists(d):
|
|
shutil.rmtree(d)
|
|
for f in os.listdir(PROJECT_DIR):
|
|
if f.endswith(".spec"):
|
|
os.remove(os.path.join(PROJECT_DIR, f))
|
|
print("Cleaned build artifacts")
|
|
|
|
|
|
def build():
|
|
tag = get_platform_tag()
|
|
print(f"Building {__app_name__} v{__version__} for {tag}...")
|
|
|
|
system = platform.system().lower()
|
|
|
|
# PyInstaller command
|
|
cmd_parts = [
|
|
sys.executable, "-m", "PyInstaller",
|
|
"--onefile",
|
|
"--windowed",
|
|
f"--name={__app_name__}",
|
|
"--add-data", f"config/servers.example.json{os.pathsep}config",
|
|
"--add-data", f"tools/ssh.py{os.pathsep}tools",
|
|
"--add-data", f"tools/skill-ssh.md{os.pathsep}tools",
|
|
"--add-data", f"core/encryption.py{os.pathsep}core",
|
|
]
|
|
|
|
# Icon
|
|
icon_path = os.path.join(PROJECT_DIR, "assets", "icon.ico")
|
|
if os.path.exists(icon_path):
|
|
cmd_parts.extend(["--icon", icon_path])
|
|
|
|
# Hidden imports for customtkinter
|
|
cmd_parts.extend([
|
|
"--hidden-import", "customtkinter",
|
|
"--hidden-import", "PIL",
|
|
"--hidden-import", "pyotp",
|
|
"--hidden-import", "pyte",
|
|
"--hidden-import", "psutil",
|
|
"--collect-all", "customtkinter",
|
|
])
|
|
|
|
cmd_parts.append("main.py")
|
|
|
|
os.chdir(PROJECT_DIR)
|
|
ret = os.system(" ".join(f'"{p}"' if " " in p else p for p in cmd_parts))
|
|
|
|
if ret != 0:
|
|
print(f"Build failed with code {ret}")
|
|
sys.exit(1)
|
|
|
|
# Move to releases
|
|
os.makedirs(RELEASES_DIR, exist_ok=True)
|
|
|
|
if system == "windows":
|
|
src = os.path.join(DIST_DIR, f"{__app_name__}.exe")
|
|
dst = os.path.join(RELEASES_DIR, f"{__app_name__}-v{__version__}-{tag}.exe")
|
|
elif system == "darwin":
|
|
src = os.path.join(DIST_DIR, __app_name__)
|
|
dst = os.path.join(RELEASES_DIR, f"{__app_name__}-v{__version__}-{tag}")
|
|
else:
|
|
src = os.path.join(DIST_DIR, __app_name__)
|
|
dst = os.path.join(RELEASES_DIR, f"{__app_name__}-v{__version__}-{tag}")
|
|
|
|
if os.path.exists(src):
|
|
shutil.copy2(src, dst)
|
|
size_mb = os.path.getsize(dst) / (1024 * 1024)
|
|
print(f"\nBuild complete: {dst} ({size_mb:.1f} MB)")
|
|
else:
|
|
print(f"Build output not found: {src}")
|
|
sys.exit(1)
|
|
|
|
|
|
if __name__ == "__main__":
|
|
if "--clean" in sys.argv:
|
|
clean()
|
|
build()
|