Full implementation of multi-type server management across GUI and CLI: New clients: SQLClient (MariaDB/MSSQL/PostgreSQL), RedisClient, GrafanaClient, PrometheusClient, TelnetSession, WinRMClient, RemoteDesktopLauncher. New GUI tabs: QueryTab (SQL editor + Treeview), RedisTab (console + history), GrafanaTab (dashboards + alerts), PrometheusTab (PromQL + targets), PowershellTab (PS/CMD), LaunchTab (RDP/VNC external client). Infrastructure: TAB_REGISTRY for conditional tabs per server type, adaptive server_dialog fields, colored type badges in sidebar, status checker for all types (SSH/TCP/SQL/Redis/HTTP), 100+ i18n keys. CLI: ssh.py extended with --sql, --redis, --grafana-*, --prom-*, --ps, --cmd. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
166 lines
4.8 KiB
Python
166 lines
4.8 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 re
|
|
import sys
|
|
import shutil
|
|
import platform
|
|
|
|
# Add project root
|
|
PROJECT_DIR = os.path.dirname(os.path.abspath(__file__))
|
|
sys.path.insert(0, PROJECT_DIR)
|
|
|
|
|
|
def auto_bump_version() -> str:
|
|
"""Auto-increment patch version in version.py on every build."""
|
|
ver_file = os.path.join(PROJECT_DIR, "version.py")
|
|
with open(ver_file, "r", encoding="utf-8") as f:
|
|
content = f.read()
|
|
|
|
match = re.search(r'__version__\s*=\s*"(\d+)\.(\d+)\.(\d+)"', content)
|
|
if not match:
|
|
print("ERROR: Cannot parse version from version.py")
|
|
sys.exit(1)
|
|
|
|
major, minor, patch = int(match.group(1)), int(match.group(2)), int(match.group(3))
|
|
new_patch = patch + 1
|
|
new_version = f"{major}.{minor}.{new_patch}"
|
|
|
|
content = re.sub(
|
|
r'__version__\s*=\s*"[\d.]+"',
|
|
f'__version__ = "{new_version}"',
|
|
content,
|
|
)
|
|
with open(ver_file, "w", encoding="utf-8") as f:
|
|
f.write(content)
|
|
|
|
print(f"Version bumped: {major}.{minor}.{patch} -> {new_version}")
|
|
return new_version
|
|
|
|
|
|
# Auto-bump unless --no-bump flag is passed
|
|
if "--no-bump" not in sys.argv:
|
|
_version = auto_bump_version()
|
|
else:
|
|
sys.argv.remove("--no-bump")
|
|
_version = None
|
|
|
|
from version import __version__, __app_name__
|
|
|
|
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 and connection libraries
|
|
cmd_parts.extend([
|
|
"--hidden-import", "customtkinter",
|
|
"--hidden-import", "PIL",
|
|
"--hidden-import", "pyotp",
|
|
"--hidden-import", "pyte",
|
|
"--hidden-import", "psutil",
|
|
"--hidden-import", "pymysql",
|
|
"--hidden-import", "psycopg2",
|
|
"--hidden-import", "pymssql",
|
|
"--hidden-import", "redis",
|
|
"--hidden-import", "requests",
|
|
"--hidden-import", "winrm",
|
|
"--hidden-import", "telnetlib3",
|
|
"--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()
|