Files
alro65 de25dcee57 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
2026-05-24 01:36:24 -04:00

104 lines
3.6 KiB
Python
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
#!/usr/bin/env python3
# =============================================================================
# installer/serial_generator.py — AR Electronics serial number generator
# =============================================================================
#
# Developer tool. Generates a batch of unique serial numbers and optionally
# writes them to a CSV log for the AR Electronics CRM.
#
# Format: AR-XXXX-XXXX-XXXX (hex groups, 48 bits of entropy ≈ 281 trillion)
#
# Usage:
# python serial_generator.py 10 # generate 10 serials
# python serial_generator.py 10 --csv serials.csv
# python serial_generator.py 1 --vessel "MY YACHT NAME" --csv serials.csv
# =============================================================================
import argparse
import csv
import os
import secrets
from datetime import datetime, timezone
def generate_serial() -> str:
"""Generate a single AR-XXXX-XXXX-XXXX serial number."""
raw = secrets.token_hex(6).upper() # 6 bytes = 12 hex chars = 3 × 4
return f"AR-{raw[0:4]}-{raw[4:8]}-{raw[8:12]}"
def generate_batch(count: int, vessel: str = "") -> list[dict]:
serials = []
seen: set[str] = set()
while len(serials) < count:
serial = generate_serial()
if serial in seen:
continue # collision (astronomically unlikely)
seen.add(serial)
serials.append(
{
"serial": serial,
"vessel": vessel,
"created_at": datetime.now(timezone.utc).isoformat(),
"status": "unactivated",
}
)
return serials
def write_key_file(serial: str, output_path: str) -> None:
"""Write a single serial number to a serial.key file."""
with open(output_path, "w", encoding="utf-8") as f:
f.write(serial)
print(f" → {output_path}")
def write_csv(records: list[dict], csv_path: str) -> None:
"""Append records to a CSV log (creates file if missing)."""
file_exists = os.path.exists(csv_path)
with open(csv_path, "a", newline="", encoding="utf-8") as f:
writer = csv.DictWriter(f, fieldnames=["serial", "vessel", "created_at", "status"])
if not file_exists:
writer.writeheader()
writer.writerows(records)
print(f"\nAnexado a CSV: {csv_path}")
def main():
parser = argparse.ArgumentParser(
description="AR Electronics — Generador de números de serie"
)
parser.add_argument("count", type=int, nargs="?", default=1,
help="Cantidad de seriales a generar (default: 1)")
parser.add_argument("--vessel", default="",
help="Nombre del buque para asignar al lote")
parser.add_argument("--csv",
help="Ruta al archivo CSV de registro (se crea o se añade)")
parser.add_argument("--key-dir",
help="Directorio donde escribir archivos serial.key individuales")
args = parser.parse_args()
records = generate_batch(args.count, vessel=args.vessel)
print(f"\nSeriales generados ({args.count}):\n")
for rec in records:
vessel_info = f" [{rec['vessel']}]" if rec["vessel"] else ""
print(f" {rec['serial']}{vessel_info}")
if args.csv:
write_csv(records, args.csv)
if args.key_dir:
os.makedirs(args.key_dir, exist_ok=True)
for i, rec in enumerate(records):
filename = f"serial_{i+1:03d}.key" if len(records) > 1 else "serial.key"
write_key_file(rec["serial"], os.path.join(args.key_dir, filename))
print()
if __name__ == "__main__":
main()