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,103 @@
|
||||
#!/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()
|
||||
Reference in New Issue
Block a user