feat(installer): J6412 USB installer + AR Electronics license activation server
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
This commit is contained in:
@@ -0,0 +1,262 @@
|
||||
#!/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()
|
||||
Reference in New Issue
Block a user