haiku вайб
This commit is contained in:
parent
8d41095ac3
commit
cdee38d433
2 changed files with 157 additions and 1 deletions
|
|
@ -26,7 +26,7 @@ DEVICE_ROLES = ("plate", "face", "overview", "other")
|
||||||
DEVICE_LOCATIONS = ("server_room", "street")
|
DEVICE_LOCATIONS = ("server_room", "street")
|
||||||
|
|
||||||
DEVICE_STATUSES = ("online", "offline", "unknown")
|
DEVICE_STATUSES = ("online", "offline", "unknown")
|
||||||
DEVICE_SOURCES = ("auto_sync", "manual", "csv_import", "sheets_sync")
|
DEVICE_SOURCES = ("auto_sync", "manual", "csv_import", "sheets_sync", "auto_ssh")
|
||||||
|
|
||||||
|
|
||||||
class Device(Base, TimestampMixin):
|
class Device(Base, TimestampMixin):
|
||||||
|
|
|
||||||
|
|
@ -155,6 +155,73 @@ async def _read_remote_config(
|
||||||
return result.stdout
|
return result.stdout
|
||||||
|
|
||||||
|
|
||||||
|
async def _discover_mikrotik_via_ssh_client(
|
||||||
|
host: str,
|
||||||
|
port: int,
|
||||||
|
username: str,
|
||||||
|
password: str,
|
||||||
|
) -> str | None:
|
||||||
|
"""
|
||||||
|
Discover MikroTik router IP via SSH_CLIENT environment variable.
|
||||||
|
When connected via L2TP → MikroTik → main_server, the SSH_CLIENT env var
|
||||||
|
shows the client IP connecting to the server (MikroTik's L2TP interface IP).
|
||||||
|
"""
|
||||||
|
try:
|
||||||
|
async with asyncssh.connect(
|
||||||
|
host,
|
||||||
|
port=port,
|
||||||
|
username=username,
|
||||||
|
password=password,
|
||||||
|
known_hosts=None,
|
||||||
|
connect_timeout=10,
|
||||||
|
) as conn:
|
||||||
|
result = await conn.run("echo $SSH_CLIENT", check=False)
|
||||||
|
ssh_client_output = result.stdout.strip()
|
||||||
|
if ssh_client_output:
|
||||||
|
# SSH_CLIENT format: "client_ip client_port server_port"
|
||||||
|
parts = ssh_client_output.split()
|
||||||
|
if parts:
|
||||||
|
return parts[0]
|
||||||
|
except Exception as e:
|
||||||
|
print(f"[ssh_discovery] Failed to discover MikroTik via SSH_CLIENT: {e}")
|
||||||
|
return None
|
||||||
|
|
||||||
|
|
||||||
|
async def _discover_proxmox_via_port_scan(
|
||||||
|
host: str,
|
||||||
|
port: int,
|
||||||
|
username: str,
|
||||||
|
password: str,
|
||||||
|
) -> list[str]:
|
||||||
|
"""
|
||||||
|
Discover Proxmox servers on the local subnet by scanning port 8006.
|
||||||
|
Runs a subnet scanning script that checks port 8006 on all IPs in the subnet.
|
||||||
|
"""
|
||||||
|
try:
|
||||||
|
async with asyncssh.connect(
|
||||||
|
host,
|
||||||
|
port=port,
|
||||||
|
username=username,
|
||||||
|
password=password,
|
||||||
|
known_hosts=None,
|
||||||
|
connect_timeout=10,
|
||||||
|
) as conn:
|
||||||
|
# Scan script: determine subnet and scan all IPs for port 8006
|
||||||
|
scan_script = (
|
||||||
|
"subnet=$(ip -o -f inet addr show | awk '!/ lo |docker|br-|wg/ {print $4; exit}' | "
|
||||||
|
"cut -d/ -f1 | awk -F. '{print $1\".\"$2\".\"$3}') && "
|
||||||
|
"(set +m; for ip in $subnet.{1..254}; do "
|
||||||
|
"timeout 0.3 bash -c \"echo > /dev/tcp/$ip/8006\" 2>/dev/null && echo \"$ip\" & "
|
||||||
|
"done; wait)"
|
||||||
|
)
|
||||||
|
result = await conn.run(scan_script, check=False)
|
||||||
|
ips = [line.strip() for line in result.stdout.strip().split('\n') if line.strip()]
|
||||||
|
return ips
|
||||||
|
except Exception as e:
|
||||||
|
print(f"[ssh_discovery] Failed to discover Proxmox servers via port scan: {e}")
|
||||||
|
return []
|
||||||
|
|
||||||
|
|
||||||
def _parse_db_uri(uri: str) -> dict | None:
|
def _parse_db_uri(uri: str) -> dict | None:
|
||||||
"""Parse postgresql+psycopg2://user:pass@host:port/dbname → dict."""
|
"""Parse postgresql+psycopg2://user:pass@host:port/dbname → dict."""
|
||||||
try:
|
try:
|
||||||
|
|
@ -400,7 +467,9 @@ async def discover_via_ssh(
|
||||||
result.errors.append(f"SSH to {rack_host} failed: {exc}")
|
result.errors.append(f"SSH to {rack_host} failed: {exc}")
|
||||||
continue
|
continue
|
||||||
|
|
||||||
|
print(f"[ssh_discovery] Processing rack {rack_host} ({rack_name})")
|
||||||
devices_found = _parse_config(config_text, rack_host)
|
devices_found = _parse_config(config_text, rack_host)
|
||||||
|
print(f"[ssh_discovery] Found {len(devices_found)} devices from plugins in {rack_host}")
|
||||||
|
|
||||||
for found in devices_found:
|
for found in devices_found:
|
||||||
# Hostname: "rack{rack_name}-{plugin_name}", e.g. "rack01-camera_grz"
|
# Hostname: "rack{rack_name}-{plugin_name}", e.g. "rack01-camera_grz"
|
||||||
|
|
@ -427,6 +496,9 @@ async def discover_via_ssh(
|
||||||
if existing.hostname != hostname:
|
if existing.hostname != hostname:
|
||||||
existing.hostname = hostname
|
existing.hostname = hostname
|
||||||
changed = True
|
changed = True
|
||||||
|
|
||||||
|
action = "UPDATED" if changed else "SKIPPED"
|
||||||
|
print(f"[ssh_discovery] Device {found.ip} ({hostname}) - {action}")
|
||||||
result.updated += 1 if changed else 0
|
result.updated += 1 if changed else 0
|
||||||
result.skipped += 0 if changed else 1
|
result.skipped += 0 if changed else 1
|
||||||
else:
|
else:
|
||||||
|
|
@ -439,8 +511,10 @@ async def discover_via_ssh(
|
||||||
source="auto_ssh",
|
source="auto_ssh",
|
||||||
device_meta={"plugin": found.plugin_name, "cls": found.cls},
|
device_meta={"plugin": found.plugin_name, "cls": found.cls},
|
||||||
))
|
))
|
||||||
|
print(f"[ssh_discovery] Device {found.ip} ({hostname}) - ADDED")
|
||||||
result.created += 1
|
result.created += 1
|
||||||
except Exception as exc:
|
except Exception as exc:
|
||||||
|
print(f"[ssh_discovery] Error upserting {found.ip}: {exc}")
|
||||||
result.errors.append(f"Error upserting {found.ip}: {exc}")
|
result.errors.append(f"Error upserting {found.ip}: {exc}")
|
||||||
|
|
||||||
# Flush after each rack so that subsequent SELECTs (for other racks
|
# Flush after each rack so that subsequent SELECTs (for other racks
|
||||||
|
|
@ -452,4 +526,86 @@ async def discover_via_ssh(
|
||||||
except Exception as exc:
|
except Exception as exc:
|
||||||
result.errors.append(f"Flush error after rack {rack_host}: {exc}")
|
result.errors.append(f"Flush error after rack {rack_host}: {exc}")
|
||||||
|
|
||||||
|
# Step 3: Discover MikroTik via SSH_CLIENT
|
||||||
|
print(f"[ssh_discovery] Attempting to discover MikroTik via SSH_CLIENT on {obj.server_ip or 'main_server'}")
|
||||||
|
mikrotik_ip = await _discover_mikrotik_via_ssh_client(
|
||||||
|
host=obj.server_ip or rack_hosts[0][1],
|
||||||
|
port=obj.ssh_port or 22,
|
||||||
|
username=obj.ssh_user,
|
||||||
|
password=ssh_password,
|
||||||
|
)
|
||||||
|
if mikrotik_ip:
|
||||||
|
print(f"[ssh_discovery] Discovered MikroTik at {mikrotik_ip}")
|
||||||
|
try:
|
||||||
|
existing = (await db.execute(
|
||||||
|
select(Device).where(
|
||||||
|
Device.object_id == obj.id,
|
||||||
|
Device.ip == mikrotik_ip,
|
||||||
|
)
|
||||||
|
)).scalar_one_or_none()
|
||||||
|
|
||||||
|
if existing is not None:
|
||||||
|
print(f"[ssh_discovery] Device {mikrotik_ip} (mikrotik) - SKIPPED")
|
||||||
|
result.skipped += 1
|
||||||
|
else:
|
||||||
|
db.add(Device(
|
||||||
|
object_id=obj.id,
|
||||||
|
ip=mikrotik_ip,
|
||||||
|
hostname="mikrotik",
|
||||||
|
category="router",
|
||||||
|
role="other",
|
||||||
|
source="auto_ssh",
|
||||||
|
))
|
||||||
|
print(f"[ssh_discovery] Device {mikrotik_ip} (mikrotik) - ADDED")
|
||||||
|
result.created += 1
|
||||||
|
except Exception as exc:
|
||||||
|
print(f"[ssh_discovery] Error upserting {mikrotik_ip}: {exc}")
|
||||||
|
result.errors.append(f"Error upserting {mikrotik_ip}: {exc}")
|
||||||
|
|
||||||
|
await db.flush()
|
||||||
|
else:
|
||||||
|
print(f"[ssh_discovery] No MikroTik found via SSH_CLIENT")
|
||||||
|
|
||||||
|
# Step 4: Discover Proxmox via port 8006 scan
|
||||||
|
print(f"[ssh_discovery] Attempting to discover Proxmox servers via port 8006 scan on {obj.server_ip or 'main_server'}")
|
||||||
|
proxmox_ips = await _discover_proxmox_via_port_scan(
|
||||||
|
host=obj.server_ip or rack_hosts[0][1],
|
||||||
|
port=obj.ssh_port or 22,
|
||||||
|
username=obj.ssh_user,
|
||||||
|
password=ssh_password,
|
||||||
|
)
|
||||||
|
if proxmox_ips:
|
||||||
|
print(f"[ssh_discovery] Discovered {len(proxmox_ips)} Proxmox server(s): {proxmox_ips}")
|
||||||
|
for idx, proxmox_ip in enumerate(proxmox_ips, 1):
|
||||||
|
hostname = f"proxmox_{idx}"
|
||||||
|
try:
|
||||||
|
existing = (await db.execute(
|
||||||
|
select(Device).where(
|
||||||
|
Device.object_id == obj.id,
|
||||||
|
Device.ip == proxmox_ip,
|
||||||
|
)
|
||||||
|
)).scalar_one_or_none()
|
||||||
|
|
||||||
|
if existing is not None:
|
||||||
|
print(f"[ssh_discovery] Device {proxmox_ip} ({hostname}) - SKIPPED")
|
||||||
|
result.skipped += 1
|
||||||
|
else:
|
||||||
|
db.add(Device(
|
||||||
|
object_id=obj.id,
|
||||||
|
ip=proxmox_ip,
|
||||||
|
hostname=hostname,
|
||||||
|
category="vm",
|
||||||
|
role="other",
|
||||||
|
source="auto_ssh",
|
||||||
|
))
|
||||||
|
print(f"[ssh_discovery] Device {proxmox_ip} ({hostname}) - ADDED")
|
||||||
|
result.created += 1
|
||||||
|
except Exception as exc:
|
||||||
|
print(f"[ssh_discovery] Error upserting {proxmox_ip}: {exc}")
|
||||||
|
result.errors.append(f"Error upserting {proxmox_ip}: {exc}")
|
||||||
|
|
||||||
|
await db.flush()
|
||||||
|
else:
|
||||||
|
print(f"[ssh_discovery] No Proxmox servers found via port 8006 scan")
|
||||||
|
|
||||||
return result
|
return result
|
||||||
|
|
|
||||||
Loading…
Add table
Reference in a new issue