diff --git a/app/dist/index.html b/app/dist/index.html
index 08eed3df..979c2480 100644
--- a/app/dist/index.html
+++ b/app/dist/index.html
@@ -4,7 +4,7 @@
Velocity WebOS
-
+
diff --git a/app/src/lib/api.ts b/app/src/lib/api.ts
index 724431e5..b11340a8 100644
--- a/app/src/lib/api.ts
+++ b/app/src/lib/api.ts
@@ -1,5 +1,5 @@
const rawApiBase = import.meta.env.VITE_API_URL?.trim();
-const DEPLOYED_BACKEND_ORIGIN = 'https://54.152.236.10';
+const DEPLOYED_BACKEND_ORIGIN = 'https://api.desineuron.in';
function getBrowserOrigin() {
if (typeof window !== 'undefined' && window.location?.origin) {
diff --git a/app/vite.config.ts b/app/vite.config.ts
index 41350ce7..c5dafa78 100644
--- a/app/vite.config.ts
+++ b/app/vite.config.ts
@@ -3,10 +3,15 @@ import react from "@vitejs/plugin-react"
import { defineConfig } from "vite"
import { inspectAttr } from 'kimi-plugin-inspect-react'
+const backendProxyTarget = process.env.VITE_BACKEND_PROXY_TARGET?.trim() || "https://api.desineuron.in"
+
// https://vite.dev/config/
export default defineConfig({
base: './',
plugins: [inspectAttr(), react()],
+ optimizeDeps: {
+ exclude: ['@mediapipe/tasks-vision'],
+ },
resolve: {
alias: {
"@": path.resolve(__dirname, "./src"),
@@ -17,18 +22,18 @@ export default defineConfig({
port: 5173,
proxy: {
"/api": {
- target: "https://54.152.236.10",
+ target: backendProxyTarget,
changeOrigin: true,
secure: false,
ws: true,
},
"/assets": {
- target: "https://54.152.236.10",
+ target: backendProxyTarget,
changeOrigin: true,
secure: false,
},
"/vault": {
- target: "https://54.152.236.10",
+ target: backendProxyTarget,
changeOrigin: true,
secure: false,
},
diff --git a/infrastructure/desineuron_ingress/TEAM_HANDOFF_2026-04-08.md b/infrastructure/desineuron_ingress/TEAM_HANDOFF_2026-04-08.md
index 45d4376c..5ce4ea15 100644
--- a/infrastructure/desineuron_ingress/TEAM_HANDOFF_2026-04-08.md
+++ b/infrastructure/desineuron_ingress/TEAM_HANDOFF_2026-04-08.md
@@ -21,6 +21,7 @@ Date: 2026-04-08
15. Team Summary
16. Current Status Snapshot - 2026-04-12
17. Linux Ops Control Plane
+18. Velocity Stable API Runbook
### Outcome
@@ -589,3 +590,76 @@ Reference docs:
- [README.md](/F:/Workin%20In%20Progress/DESINEURON/GITLAB/Project_Velocity/infrastructure/ops_control_plane/README.md)
- [Desineuron Ops Control Plane Bibel.md](/F:/Workin%20In%20Progress/DESINEURON/GITLAB/Project_Velocity/.Agent%20Context/Bibels/Desineuron%20Ops%20Control%20Plane%20Bibel.md)
+
+### Velocity Stable API Runbook
+
+Problem:
+
+- the Velocity backend was still exposed through an ephemeral AWS instance IP
+- frontend code was hardcoded to `https://54.152.236.10`
+- EC2 stop/start changed the backend public IP and broke the app
+- the stable ingress already existed, but Velocity had never been mapped through it
+
+Correct production pattern:
+
+- public API hostname: `api.desineuron.in`
+- public edge: ingress `98.87.120.120`
+- ingress route target: current private IP of the EC2 instance tagged `DesineuronRole=velocity-backend`
+- Linux box runs the route-sync timer, just like the ComfyUI pattern
+- backend stays private and should only accept `8000/8001` from ingress security group `sg-0721b8b48e12c531d`
+
+Repo artifacts added for this pattern:
+
+- [sync_velocity_route.py](/F:/Workin%20In%20Progress/DESINEURON/GITLAB/Project_Velocity/infrastructure/desineuron_ingress/sync_velocity_route.py)
+- [desineuron-velocity-route-sync.service](/F:/Workin%20In%20Progress/DESINEURON/GITLAB/Project_Velocity/infrastructure/desineuron_ingress/desineuron-velocity-route-sync.service)
+- [desineuron-velocity-route-sync.timer](/F:/Workin%20In%20Progress/DESINEURON/GITLAB/Project_Velocity/infrastructure/desineuron_ingress/desineuron-velocity-route-sync.timer)
+- [install_linux_velocity_route_sync.sh](/F:/Workin%20In%20Progress/DESINEURON/GITLAB/Project_Velocity/infrastructure/desineuron_ingress/install_linux_velocity_route_sync.sh)
+
+Frontend changes expected by this pattern:
+
+- `app/src/lib/api.ts` now points production traffic to `https://api.desineuron.in`
+- `app/vite.config.ts` uses `VITE_BACKEND_PROXY_TARGET` for local dev override
+- Vite proxy errors are no longer tied to one stale EC2 IP
+
+Backend bootstrap note:
+
+- `remote_bootstrap_20260401.sh` now includes:
+ - `https://api.desineuron.in`
+ - `https://54.152.236.10`
+ - `https://18.212.122.77`
+ in `CORS_ORIGINS`
+
+Operator steps still required outside the repo:
+
+1. Tag the backend EC2 instance:
+ - key: `DesineuronRole`
+ - value: `velocity-backend`
+
+2. Add Cloudflare DNS:
+ - record: `api.desineuron.in`
+ - type: `A`
+ - value: `98.87.120.120`
+ - proxy: `DNS only`
+
+3. Bootstrap the first ingress route once:
+ - target host: current backend private IP
+ - target port: `8001` unless the backend listener is changed
+
+4. Lock down backend security group:
+ - revoke public `0.0.0.0/0` access to the backend app port
+ - allow backend app port only from ingress security group `sg-0721b8b48e12c531d`
+
+5. Update backend runtime env and restart:
+ - add `https://api.desineuron.in` to `CORS_ORIGINS`
+ - restart `velocity-backend.service`
+
+6. Install the Linux route sync timer:
+ - copy `infrastructure/desineuron_ingress/*velocity*` to Linux temporary staging
+ - run `install_linux_velocity_route_sync.sh`
+
+Expected result after the 6 steps:
+
+- frontend reaches `https://api.desineuron.in`
+- ingress forwards to the current backend private IP
+- backend public IP changes stop mattering
+- Linux auto-heals route drift every 2 minutes and on boot
diff --git a/infrastructure/desineuron_ingress/desineuron-velocity-route-sync.service b/infrastructure/desineuron_ingress/desineuron-velocity-route-sync.service
new file mode 100644
index 00000000..755a54d7
--- /dev/null
+++ b/infrastructure/desineuron_ingress/desineuron-velocity-route-sync.service
@@ -0,0 +1,9 @@
+[Unit]
+Description=Sync api.desineuron.in managed route to current Velocity backend private IP
+After=network-online.target
+Wants=network-online.target
+
+[Service]
+Type=oneshot
+EnvironmentFile=/etc/desineuron-velocity-route-sync.env
+ExecStart=/opt/desineuron-velocity-route-sync/.venv/bin/python /usr/local/bin/sync_velocity_route.py
diff --git a/infrastructure/desineuron_ingress/desineuron-velocity-route-sync.timer b/infrastructure/desineuron_ingress/desineuron-velocity-route-sync.timer
new file mode 100644
index 00000000..50c7c2db
--- /dev/null
+++ b/infrastructure/desineuron_ingress/desineuron-velocity-route-sync.timer
@@ -0,0 +1,10 @@
+[Unit]
+Description=Run velocity route sync on boot and every 2 minutes
+
+[Timer]
+OnBootSec=1min
+OnUnitActiveSec=2min
+Unit=desineuron-velocity-route-sync.service
+
+[Install]
+WantedBy=timers.target
diff --git a/infrastructure/desineuron_ingress/install_linux_velocity_route_sync.sh b/infrastructure/desineuron_ingress/install_linux_velocity_route_sync.sh
new file mode 100644
index 00000000..f52f7048
--- /dev/null
+++ b/infrastructure/desineuron_ingress/install_linux_velocity_route_sync.sh
@@ -0,0 +1,33 @@
+#!/usr/bin/env bash
+set -euo pipefail
+
+APP_ROOT=/opt/desineuron-velocity-route-sync
+VENV_PATH="$APP_ROOT/.venv"
+ENV_FILE=/etc/desineuron-velocity-route-sync.env
+SCRIPT_PATH=/usr/local/bin/sync_velocity_route.py
+SERVICE_FILE=/etc/systemd/system/desineuron-velocity-route-sync.service
+TIMER_FILE=/etc/systemd/system/desineuron-velocity-route-sync.timer
+
+sudo mkdir -p "$APP_ROOT" /var/lib/desineuron-velocity-route-sync
+python3 -m venv "$VENV_PATH"
+"$VENV_PATH/bin/pip" install --upgrade pip boto3
+
+sudo install -m 0755 /tmp/desineuron_ingress/sync_velocity_route.py "$SCRIPT_PATH"
+sudo install -m 0644 /tmp/desineuron_ingress/desineuron-velocity-route-sync.service "$SERVICE_FILE"
+sudo install -m 0644 /tmp/desineuron_ingress/desineuron-velocity-route-sync.timer "$TIMER_FILE"
+
+sudo tee "$ENV_FILE" >/dev/null < 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())
diff --git a/remote_bootstrap_20260401.sh b/remote_bootstrap_20260401.sh
index a2ec4e30..ec98797b 100644
--- a/remote_bootstrap_20260401.sh
+++ b/remote_bootstrap_20260401.sh
@@ -34,7 +34,7 @@ VELOCITY_DB_NAME=velocity
VELOCITY_DB_USER=velocity_app
VELOCITY_DB_PASSWORD=${DB_PASSWORD}
VELOCITY_JWT_SECRET=${JWT_SECRET}
-CORS_ORIGINS=http://localhost:5173,https://18.212.122.77
+CORS_ORIGINS=http://localhost:5173,https://api.desineuron.in,https://54.152.236.10,https://18.212.122.77
VELOCITY_ASSET_DIR=/opt/dlami/nvme/assets
OLLAMA_BASE_URL=http://127.0.0.1:11434
NEMOCLAW_BASE_URL=http://127.0.0.1:8080
@@ -112,4 +112,4 @@ sudo systemctl daemon-reload
sudo systemctl enable velocity-backend.service
sudo systemctl restart velocity-backend.service
sudo nginx -t
-sudo systemctl restart nginx
\ No newline at end of file
+sudo systemctl restart nginx