de25dcee57
installer/:
- build_usb.py: dev tool — builds Flutter + AR-ECDIS, assembles USB pendrive
image with serial.key, autorun.inf, and START_INSTALLER.bat
- serial_generator.py: generates AR-XXXX-XXXX-XXXX serial numbers (48-bit
entropy), logs to CSV for CRM integration
- src/license.py: hardware fingerprint (Windows MachineGuid + primary MAC),
serial validation, online activation POST, local cache with 30-day offline
grace period
- src/sysconfig.py: HKCU autostart registry entries, .lnk shortcuts (desktop
+ Start Menu via WScript.Shell), firewall rules (netsh), COM port detection
- src/install.py: tkinter installer GUI — 9 sequential steps with per-step
progress indicators, threaded execution, error dialogs, and silent mode
license_server/ (FastAPI service — deploy to arelectronics.com VPS):
- POST /api/v1/activate: first activation accepted; same-HW re-activation
refreshes heartbeat; different-HW rejected with 409
- GET /api/v1/validate/{serial}: heartbeat endpoint to refresh offline cache
- Admin endpoints (X-Admin-Key): issue, list, revoke licenses
- SQLAlchemy models: License (serial registry) + Activation (per-machine rows)
- SQLite default, PostgreSQL-ready via DATABASE_URL env var
AR_electronics — AR-Autopilot Project
263 lines
9.4 KiB
Python
263 lines
9.4 KiB
Python
#!/usr/bin/env python3
|
|
# =============================================================================
|
|
# installer/build_usb.py — Build a USB pendrive installer image
|
|
# =============================================================================
|
|
#
|
|
# Developer tool that:
|
|
# 1. Builds the Flutter Windows release (optional — skip with --no-flutter)
|
|
# 2. Copies AR-ECDIS and AR-Autopilot binaries into dist/packages/
|
|
# 3. Generates a fresh serial number and writes serial.key
|
|
# 4. Creates autorun.inf and START_INSTALLER.bat
|
|
#
|
|
# Output: dist/ directory ready to be copied to a USB pendrive.
|
|
#
|
|
# Prerequisites (on the build machine):
|
|
# - Flutter SDK in PATH (for AR-Autopilot Display)
|
|
# - Python 3.11+
|
|
# - AR-ECDIS webecdis cloned next to AR-Autopilot (or set --ecdis-dir)
|
|
# - PyInstaller (pip install pyinstaller) — for AR-ECDIS .exe packaging
|
|
#
|
|
# Usage:
|
|
# cd installer
|
|
# python build_usb.py --vessel "BUQUE NORTE" --csv ../serials_log.csv
|
|
# python build_usb.py --no-flutter --no-ecdis # quick test build
|
|
# =============================================================================
|
|
|
|
from __future__ import annotations
|
|
|
|
import argparse
|
|
import shutil
|
|
import subprocess
|
|
import sys
|
|
from pathlib import Path
|
|
|
|
REPO_ROOT = Path(__file__).resolve().parent.parent
|
|
DISPLAY_DIR = REPO_ROOT / "display"
|
|
INSTALLER_SRC = Path(__file__).resolve().parent / "src"
|
|
DIST_DIR = Path(__file__).resolve().parent / "dist"
|
|
|
|
# Default location for the AR-ECDIS repo (sibling of AR-Autopilot)
|
|
DEFAULT_ECDIS = REPO_ROOT.parent / "AR ECDIS" / "webecdis"
|
|
|
|
|
|
def run(cmd: list[str], cwd: Path | None = None, check: bool = True):
|
|
print(f" $ {' '.join(str(c) for c in cmd)}")
|
|
subprocess.run(cmd, cwd=cwd, check=check)
|
|
|
|
|
|
# ---------------------------------------------------------------------------
|
|
# Step 1 — Flutter Windows build
|
|
# ---------------------------------------------------------------------------
|
|
|
|
def build_flutter(flutter_cmd: str = "flutter") -> Path:
|
|
"""Build AR-Autopilot Display for Windows and return the build output dir."""
|
|
print("\n[1/5] Building Flutter (Windows release)…")
|
|
run([flutter_cmd, "build", "windows", "--release"], cwd=DISPLAY_DIR)
|
|
build_out = DISPLAY_DIR / "build" / "windows" / "x64" / "runner" / "Release"
|
|
if not build_out.exists():
|
|
raise FileNotFoundError(f"Flutter build output not found: {build_out}")
|
|
print(f" Output: {build_out}")
|
|
return build_out
|
|
|
|
|
|
# ---------------------------------------------------------------------------
|
|
# Step 2 — AR-ECDIS PyInstaller build
|
|
# ---------------------------------------------------------------------------
|
|
|
|
def build_ecdis(ecdis_dir: Path) -> Path:
|
|
"""Package AR-ECDIS with PyInstaller and return the dist directory."""
|
|
print("\n[2/5] Building AR-ECDIS (PyInstaller)…")
|
|
if not ecdis_dir.exists():
|
|
print(f" AR-ECDIS dir not found ({ecdis_dir}) — skipping.")
|
|
return Path()
|
|
|
|
main_py = ecdis_dir / "main.py"
|
|
if not main_py.exists():
|
|
print(f" AR-ECDIS main.py not found — skipping.")
|
|
return Path()
|
|
|
|
run(
|
|
[
|
|
sys.executable, "-m", "PyInstaller",
|
|
"--onedir",
|
|
"--name", "AR-ECDIS",
|
|
"--windowed",
|
|
"--clean",
|
|
str(main_py),
|
|
],
|
|
cwd=ecdis_dir,
|
|
check=False, # non-fatal — missing PyInstaller is warned, not fatal
|
|
)
|
|
out = ecdis_dir / "dist" / "AR-ECDIS"
|
|
if out.exists():
|
|
print(f" Output: {out}")
|
|
else:
|
|
print(" PyInstaller output not found — AR-ECDIS skipped.")
|
|
out = Path()
|
|
return out
|
|
|
|
|
|
# ---------------------------------------------------------------------------
|
|
# Step 3 — Assemble dist/ tree
|
|
# ---------------------------------------------------------------------------
|
|
|
|
def assemble_dist(
|
|
flutter_build: Path,
|
|
ecdis_build: Path,
|
|
serial: str,
|
|
) -> None:
|
|
print("\n[3/5] Assembling USB installer tree…")
|
|
|
|
# Clean previous dist
|
|
if DIST_DIR.exists():
|
|
shutil.rmtree(DIST_DIR)
|
|
DIST_DIR.mkdir(parents=True)
|
|
|
|
# Copy installer source files
|
|
pkg_installer = DIST_DIR
|
|
shutil.copytree(INSTALLER_SRC, pkg_installer / "src")
|
|
|
|
# Place the serial key
|
|
(DIST_DIR / "serial.key").write_text(serial, encoding="utf-8")
|
|
|
|
# Copy app packages
|
|
packages = DIST_DIR / "packages"
|
|
packages.mkdir()
|
|
|
|
if flutter_build.exists():
|
|
dest = packages / "AR-Autopilot"
|
|
shutil.copytree(flutter_build, dest)
|
|
print(f" AR-Autopilot → packages/AR-Autopilot/")
|
|
|
|
if ecdis_build.exists():
|
|
dest = packages / "AR-ECDIS"
|
|
shutil.copytree(ecdis_build, dest)
|
|
print(f" AR-ECDIS → packages/AR-ECDIS/")
|
|
|
|
print(f" Serial key → serial.key ({serial})")
|
|
|
|
|
|
# ---------------------------------------------------------------------------
|
|
# Step 4 — Write autorun + launcher batch
|
|
# ---------------------------------------------------------------------------
|
|
|
|
def write_autorun() -> None:
|
|
print("\n[4/5] Writing autorun.inf and START_INSTALLER.bat…")
|
|
|
|
autorun = (
|
|
"[autorun]\n"
|
|
"label=AR Electronics Installer\n"
|
|
"open=START_INSTALLER.bat\n"
|
|
"icon=src\\install.py,0\n"
|
|
)
|
|
(DIST_DIR / "autorun.inf").write_text(autorun, encoding="utf-8")
|
|
|
|
batch = (
|
|
"@echo off\n"
|
|
"title AR Electronics — Instalador J6412\n"
|
|
'echo Iniciando instalador AR Electronics...\n'
|
|
'cd /d "%~dp0"\n'
|
|
"python src\\install.py\n"
|
|
"if errorlevel 1 (\n"
|
|
" echo.\n"
|
|
" echo ERROR: La instalacion fallo.\n"
|
|
" pause\n"
|
|
")\n"
|
|
)
|
|
(DIST_DIR / "START_INSTALLER.bat").write_text(batch, encoding="utf-8")
|
|
|
|
# README for field technicians
|
|
readme = (
|
|
"=== AR Electronics — Instalador J6412 ===\n\n"
|
|
"1. Conecte este pendrive al mini PC J6412.\n"
|
|
"2. Abra el explorador de archivos y ejecute START_INSTALLER.bat.\n"
|
|
" (Si Windows pregunta, elija 'Más información' → 'Ejecutar de todas formas'.)\n"
|
|
"3. El instalador solicitará permisos de administrador — acepte.\n"
|
|
"4. Pulse INSTALAR y espere a que finalice.\n"
|
|
"5. Reinicie el equipo.\n\n"
|
|
"El sistema requiere conexión a internet para la activación de la licencia.\n\n"
|
|
"Soporte: soporte@arelectronics.com\n"
|
|
)
|
|
(DIST_DIR / "LEAME.txt").write_text(readme, encoding="utf-8")
|
|
|
|
|
|
# ---------------------------------------------------------------------------
|
|
# Step 5 — Summary
|
|
# ---------------------------------------------------------------------------
|
|
|
|
def print_summary(serial: str) -> None:
|
|
print("\n[5/5] Build complete.")
|
|
print(f"\n Directorio de salida : {DIST_DIR}")
|
|
print(f" Número de serie : {serial}")
|
|
size_mb = sum(f.stat().st_size for f in DIST_DIR.rglob("*") if f.is_file()) / 1e6
|
|
print(f" Tamaño total : {size_mb:.1f} MB")
|
|
print("\n Copie todo el contenido de dist/ al pendrive USB.")
|
|
print(" Asegúrese de que el pendrive tenga al menos 2 GB de espacio.\n")
|
|
|
|
|
|
# ---------------------------------------------------------------------------
|
|
# Main
|
|
# ---------------------------------------------------------------------------
|
|
|
|
def main():
|
|
parser = argparse.ArgumentParser(
|
|
description="AR Electronics — USB installer builder"
|
|
)
|
|
parser.add_argument("--vessel", default="",
|
|
help="Vessel name to tag the serial number with")
|
|
parser.add_argument("--csv",
|
|
help="Path to serials CSV log (created or appended)")
|
|
parser.add_argument("--ecdis-dir", type=Path, default=DEFAULT_ECDIS,
|
|
help=f"Path to AR-ECDIS webecdis directory (default: {DEFAULT_ECDIS})")
|
|
parser.add_argument("--flutter", default="flutter",
|
|
help="flutter command (default: 'flutter')")
|
|
parser.add_argument("--no-flutter", action="store_true",
|
|
help="Skip Flutter build (use existing build output)")
|
|
parser.add_argument("--no-ecdis", action="store_true",
|
|
help="Skip AR-ECDIS PyInstaller build")
|
|
parser.add_argument("--serial",
|
|
help="Use an existing serial number instead of generating one")
|
|
args = parser.parse_args()
|
|
|
|
print("=" * 60)
|
|
print(" AR Electronics — USB Installer Builder")
|
|
print("=" * 60)
|
|
|
|
# Resolve serial
|
|
if args.serial:
|
|
serial = args.serial
|
|
print(f"\n Using existing serial: {serial}")
|
|
else:
|
|
# Import here to avoid circular issues if running as a module
|
|
sys.path.insert(0, str(Path(__file__).resolve().parent))
|
|
from serial_generator import generate_batch, write_csv # noqa: PLC0415
|
|
|
|
records = generate_batch(1, vessel=args.vessel)
|
|
serial = records[0]["serial"]
|
|
print(f"\n Generated serial: {serial}")
|
|
if args.csv:
|
|
write_csv(records, args.csv)
|
|
|
|
# Flutter build
|
|
if args.no_flutter:
|
|
flutter_build = DISPLAY_DIR / "build" / "windows" / "x64" / "runner" / "Release"
|
|
print(f"\n[1/5] Skipping Flutter build — using {flutter_build}")
|
|
else:
|
|
flutter_build = build_flutter(args.flutter)
|
|
|
|
# AR-ECDIS build
|
|
if args.no_ecdis:
|
|
ecdis_build = Path()
|
|
print("\n[2/5] Skipping AR-ECDIS build.")
|
|
else:
|
|
ecdis_build = build_ecdis(args.ecdis_dir)
|
|
|
|
# Assemble
|
|
assemble_dist(flutter_build, ecdis_build, serial)
|
|
write_autorun()
|
|
print_summary(serial)
|
|
|
|
|
|
if __name__ == "__main__":
|
|
main()
|