#!/usr/bin/env python3 from __future__ import annotations import json import os import subprocess import sys from pathlib import Path import boto3 def load_env_file(path: Path) -> dict[str, str]: data: dict[str, str] = {} if not path.exists(): return data for line in path.read_text(encoding="utf-8").splitlines(): line = line.strip() if not line or line.startswith("#") or "=" not in line: continue key, value = line.split("=", 1) data[key.strip()] = value.strip() return data def env(name: str, default: str = "") -> str: return os.environ.get(name, default) def resolve_target_instance(ec2) -> dict | None: explicit_instance_id = env("VELOCITY_INSTANCE_ID") if explicit_instance_id: reservations = ec2.describe_instances(InstanceIds=[explicit_instance_id])["Reservations"] for reservation in reservations: for instance in reservation["Instances"]: if instance["State"]["Name"] == "running": return instance return None tag_key = env("VELOCITY_INSTANCE_TAG_KEY", "DesineuronRole") tag_value = env("VELOCITY_INSTANCE_TAG_VALUE", "velocity-backend") filters = [ {"Name": "instance-state-name", "Values": ["running"]}, {"Name": f"tag:{tag_key}", "Values": [tag_value]}, ] reservations = ec2.describe_instances(Filters=filters)["Reservations"] instances = [instance for reservation in reservations for instance in reservation["Instances"]] if not instances: return None instances.sort(key=lambda row: row["LaunchTime"], reverse=True) return instances[0] def upsert_route(hostname: str, private_ip: str, port: int) -> subprocess.CompletedProcess[str]: ingress_host = env("INGRESS_SSH_HOST") ingress_user = env("INGRESS_SSH_USER", "ec2-user") ingress_port = env("INGRESS_SSH_PORT", "22") ingress_key = env("INGRESS_SSH_KEY_PATH") helper = env("INGRESS_ROUTE_HELPER", "/usr/local/bin/manage_desineuron_routes.py") payload = json.dumps( { "hostname": hostname, "scheme": "http", "target_host": private_ip, "target_port": port, } ) command = ( f"sudo {helper} upsert '{payload}'" " && sudo caddy validate --config /etc/caddy/Caddyfile" " && sudo systemctl reload caddy" ) return subprocess.run( [ "ssh", "-o", "StrictHostKeyChecking=no", "-o", "UserKnownHostsFile=/dev/null", "-i", ingress_key, "-p", ingress_port, f"{ingress_user}@{ingress_host}", command, ], capture_output=True, text=True, check=False, ) def main() -> int: ops_env = load_env_file(Path(env("OPS_ENV_FILE", "/opt/desineuron-ops-control-plane/.env"))) for key in ["AWS_ACCESS_KEY_ID", "AWS_SECRET_ACCESS_KEY", "AWS_DEFAULT_REGION"]: if key not in os.environ and key in ops_env: os.environ[key] = ops_env[key] os.environ.setdefault("AWS_DEFAULT_REGION", ops_env.get("OPS_DEFAULT_REGION", "us-east-1")) os.environ.setdefault("INGRESS_SSH_HOST", ops_env.get("OPS_INGRESS_SSH_HOST", "")) os.environ.setdefault("INGRESS_SSH_USER", ops_env.get("OPS_INGRESS_SSH_USER", "ec2-user")) os.environ.setdefault("INGRESS_SSH_PORT", ops_env.get("OPS_INGRESS_SSH_PORT", "22")) normalized_key_path = ops_env.get("OPS_SSH_KEY_PATH", "/opt/desineuron-ops-control-plane/state/desineuron-l4-node.pem") if normalized_key_path.startswith("/app/state/"): normalized_key_path = normalized_key_path.replace("/app/state/", "/opt/desineuron-ops-control-plane/state/") os.environ.setdefault("INGRESS_SSH_KEY_PATH", normalized_key_path) os.environ.setdefault("INGRESS_ROUTE_HELPER", ops_env.get("OPS_INGRESS_ROUTE_HELPER", "/usr/local/bin/manage_desineuron_routes.py")) region = os.environ["AWS_DEFAULT_REGION"] hostname = env("VELOCITY_ROUTE_HOSTNAME", "api.desineuron.in") port = int(env("VELOCITY_ROUTE_PORT", "8001")) state_file = Path(env("VELOCITY_ROUTE_STATE_FILE", "/var/lib/desineuron-velocity-route-sync/current_target.txt")) ec2 = boto3.client("ec2", region_name=region) instance = resolve_target_instance(ec2) if not instance: print("No running velocity-backend target instance found", file=sys.stderr) return 1 private_ip = instance.get("PrivateIpAddress") if not private_ip: print("Target instance has no private IP", file=sys.stderr) return 1 current = state_file.read_text(encoding="utf-8").strip() if state_file.exists() else "" if current == private_ip: print(json.dumps({"status": "noop", "hostname": hostname, "target_host": private_ip})) return 0 result = upsert_route(hostname, private_ip, port) if result.returncode != 0: print(result.stdout) print(result.stderr, file=sys.stderr) return result.returncode state_file.parent.mkdir(parents=True, exist_ok=True) state_file.write_text(private_ip, encoding="utf-8") print(json.dumps({"status": "updated", "hostname": hostname, "target_host": private_ip})) return 0 if __name__ == "__main__": raise SystemExit(main())