File size: 5,718 Bytes
788eab4 482e621 788eab4 482e621 788eab4 482e621 35e8172 482e621 788eab4 482e621 788eab4 482e621 788eab4 482e621 788eab4 482e621 788eab4 482e621 788eab4 | 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 | """
Single-Command Local & LAN Institutional Placement Engine Launcher.
Detects LAN network interface, runs migrations, verifies frontend assets,
and launches the unified offline server.
"""
from __future__ import annotations
import os
import socket
import subprocess
import sys
PROJECT_ROOT = os.path.abspath(os.path.join(os.path.dirname(__file__), ".."))
if PROJECT_ROOT not in sys.path:
sys.path.insert(0, PROJECT_ROOT)
def get_primary_lan_ip() -> str:
"""Detects active physical LAN IPv4 address without making outbound internet connections."""
s = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
try:
# Routing table lookup to private IP space without sending packets
s.connect(("10.255.255.255", 1))
ip = s.getsockname()[0]
if ip and not ip.startswith("127.") and not ip.startswith("169.254."):
return ip
except Exception:
pass
finally:
s.close()
try:
hostname = socket.gethostname()
candidates = socket.gethostbyname_ex(hostname)[2]
# Prefer physical / non-VMware interfaces
for ip in candidates:
if not ip.startswith("127.") and not ip.startswith("169.254.") and not ip.startswith("192.168.65.") and not ip.startswith("192.168.83."):
return ip
for ip in candidates:
if not ip.startswith("127.") and not ip.startswith("169.254."):
return ip
return "127.0.0.1"
except Exception:
return "127.0.0.1"
def get_all_lan_ips() -> list[str]:
"""Returns all reachable non-loopback IPv4 addresses."""
try:
hostname = socket.gethostname()
candidates = socket.gethostbyname_ex(hostname)[2]
return [ip for ip in candidates if not ip.startswith("127.") and not ip.startswith("169.254.")]
except Exception:
return [get_primary_lan_ip()]
def print_banner(lan_ip: str, port: int = 8000) -> None:
all_ips = get_all_lan_ips()
is_offline = (lan_ip == "127.0.0.1" and len([ip for ip in all_ips if ip != "127.0.0.1"]) == 0)
print("=" * 76)
print(" INSTITUTIONAL PLACEMENT ELIGIBILITY & SCHEDULING ENGINE")
if is_offline:
print(" 100% OFFLINE STANDALONE MODE (NO NETWORK REQUIRED)")
else:
print(" HYBRID PUBLIC CLOUD & INSTITUTIONAL NETWORK DEPLOYMENT")
print("=" * 76)
print(f" [+] Status: ONLINE (READY FOR TRAFFIC)")
if is_offline:
print(f" [+] Network: OFFLINE (NO INTERNET OR LAN REQUIRED)")
else:
print(f" [+] Network: CONNECTED ({lan_ip})")
print(f" [+] Security: Role-Based RBAC & Encrypted Student PII")
print(f" [+] Storage Engine: SQLite WAL Mode (Local Database)")
print(f" [+] Constraint Solver: Google OR-Tools CP-SAT (Local In-Memory)")
print(" " + "-" * 70)
print(f" --> Local Browser URL: http://localhost:{port}")
if not is_offline:
print(f" --> Primary LAN Access: http://{lan_ip}:{port}")
for alt_ip in all_ips:
if alt_ip != lan_ip and not alt_ip.startswith("192.168.65.") and not alt_ip.startswith("192.168.83."):
print(f" --> Alternate LAN/Hotspot: http://{alt_ip}:{port}")
print(" " + "-" * 70)
print(f" SHARE THIS LINK WITH ANYONE ON YOUR WI-FI / LAN NETWORK:")
print(f" >>> http://{lan_ip}:{port} <<<")
else:
print(f" --> Alternate Loopback: http://127.0.0.1:{port}")
print(" " + "-" * 70)
print(" [!] OFFLINE STANDALONE: Running 100% locally on this machine.")
print(f" [!] Open http://localhost:{port} in any browser (Chrome, Edge, Firefox).")
print(" " + "-" * 70)
print(" Default Officer Login: username='officer', password='officer123'")
print(" Default Student Login: roll_no='BT210001' or 'PIPE_001', code='123456'")
print("=" * 76)
sys.stdout.flush()
def main() -> None:
port = int(os.getenv("PORT", "8000"))
host = os.getenv("HOST", "0.0.0.0")
# 1. Run migrations
print("[*] Verifying database schema and migrations...")
try:
subprocess.run([sys.executable, "-m", "alembic", "-c", "backend/alembic.ini", "upgrade", "head"], check=True)
except Exception as exc:
print(f"[!] Migration notice: {exc}. Verifying schema directly...")
from backend.app.storage.database import Base, engine
Base.metadata.create_all(bind=engine)
# 2. Seed baseline accounts
print("[*] Seeding default institutional accounts...")
from backend.app.api.auth.seeder import seed_default_accounts
seed_default_accounts()
# 3. Check frontend dist
dist_index = os.path.abspath(os.path.join(os.path.dirname(__file__), "../frontend/dist/index.html"))
if not os.path.exists(dist_index):
print("[*] Compiled frontend not found. Building production bundle...")
subprocess.run(["npm", "run", "build"], cwd=os.path.abspath(os.path.join(os.path.dirname(__file__), "../frontend")), check=True)
lan_ip = get_primary_lan_ip()
print_banner(lan_ip, port)
# 4. Optional browser auto-open
if os.getenv("AUTO_OPEN_BROWSER", "0") == "1":
try:
import threading
import time
import webbrowser
def _open():
time.sleep(1.2)
webbrowser.open(f"http://localhost:{port}")
threading.Thread(target=_open, daemon=True).start()
except Exception:
pass
# 5. Launch unified server
import uvicorn
uvicorn.run("backend.app.api.main:app", host=host, port=port, log_level="info")
if __name__ == "__main__":
main()
|