#!/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()