forked from sagnik/Project_Velocity
merge upstream
This commit is contained in:
2
app/dist/index.html
vendored
2
app/dist/index.html
vendored
@@ -4,7 +4,7 @@
|
|||||||
<meta charset="UTF-8" />
|
<meta charset="UTF-8" />
|
||||||
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
||||||
<title>Velocity WebOS</title>
|
<title>Velocity WebOS</title>
|
||||||
<script type="module" crossorigin src="./assets/index-CJRJmEe7.js"></script>
|
<script type="module" crossorigin src="./assets/index-BYTPd1oW.js"></script>
|
||||||
<link rel="stylesheet" crossorigin href="./assets/index-UA0RXBVG.css">
|
<link rel="stylesheet" crossorigin href="./assets/index-UA0RXBVG.css">
|
||||||
</head>
|
</head>
|
||||||
<body>
|
<body>
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
const rawApiBase = import.meta.env.VITE_API_URL?.trim();
|
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() {
|
function getBrowserOrigin() {
|
||||||
if (typeof window !== 'undefined' && window.location?.origin) {
|
if (typeof window !== 'undefined' && window.location?.origin) {
|
||||||
|
|||||||
@@ -3,10 +3,15 @@ import react from "@vitejs/plugin-react"
|
|||||||
import { defineConfig } from "vite"
|
import { defineConfig } from "vite"
|
||||||
import { inspectAttr } from 'kimi-plugin-inspect-react'
|
import { inspectAttr } from 'kimi-plugin-inspect-react'
|
||||||
|
|
||||||
|
const backendProxyTarget = process.env.VITE_BACKEND_PROXY_TARGET?.trim() || "https://api.desineuron.in"
|
||||||
|
|
||||||
// https://vite.dev/config/
|
// https://vite.dev/config/
|
||||||
export default defineConfig({
|
export default defineConfig({
|
||||||
base: './',
|
base: './',
|
||||||
plugins: [inspectAttr(), react()],
|
plugins: [inspectAttr(), react()],
|
||||||
|
optimizeDeps: {
|
||||||
|
exclude: ['@mediapipe/tasks-vision'],
|
||||||
|
},
|
||||||
resolve: {
|
resolve: {
|
||||||
alias: {
|
alias: {
|
||||||
"@": path.resolve(__dirname, "./src"),
|
"@": path.resolve(__dirname, "./src"),
|
||||||
@@ -17,18 +22,18 @@ export default defineConfig({
|
|||||||
port: 5173,
|
port: 5173,
|
||||||
proxy: {
|
proxy: {
|
||||||
"/api": {
|
"/api": {
|
||||||
target: "https://54.152.236.10",
|
target: backendProxyTarget,
|
||||||
changeOrigin: true,
|
changeOrigin: true,
|
||||||
secure: false,
|
secure: false,
|
||||||
ws: true,
|
ws: true,
|
||||||
},
|
},
|
||||||
"/assets": {
|
"/assets": {
|
||||||
target: "https://54.152.236.10",
|
target: backendProxyTarget,
|
||||||
changeOrigin: true,
|
changeOrigin: true,
|
||||||
secure: false,
|
secure: false,
|
||||||
},
|
},
|
||||||
"/vault": {
|
"/vault": {
|
||||||
target: "https://54.152.236.10",
|
target: backendProxyTarget,
|
||||||
changeOrigin: true,
|
changeOrigin: true,
|
||||||
secure: false,
|
secure: false,
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -21,6 +21,7 @@ Date: 2026-04-08
|
|||||||
15. Team Summary
|
15. Team Summary
|
||||||
16. Current Status Snapshot - 2026-04-12
|
16. Current Status Snapshot - 2026-04-12
|
||||||
17. Linux Ops Control Plane
|
17. Linux Ops Control Plane
|
||||||
|
18. Velocity Stable API Runbook
|
||||||
|
|
||||||
### Outcome
|
### Outcome
|
||||||
|
|
||||||
@@ -589,3 +590,76 @@ Reference docs:
|
|||||||
|
|
||||||
- [README.md](/F:/Workin%20In%20Progress/DESINEURON/GITLAB/Project_Velocity/infrastructure/ops_control_plane/README.md)
|
- [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)
|
- [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
|
||||||
|
|||||||
@@ -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
|
||||||
@@ -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
|
||||||
@@ -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 <<EOF
|
||||||
|
OPS_ENV_FILE=/opt/desineuron-ops-control-plane/.env
|
||||||
|
VELOCITY_ROUTE_HOSTNAME=api.desineuron.in
|
||||||
|
VELOCITY_ROUTE_PORT=8001
|
||||||
|
VELOCITY_INSTANCE_TAG_KEY=DesineuronRole
|
||||||
|
VELOCITY_INSTANCE_TAG_VALUE=velocity-backend
|
||||||
|
VELOCITY_ROUTE_STATE_FILE=/var/lib/desineuron-velocity-route-sync/current_target.txt
|
||||||
|
INGRESS_SSH_KEY_PATH=/opt/desineuron-ops-control-plane/state/desineuron-l4-node.pem
|
||||||
|
EOF
|
||||||
|
|
||||||
|
sudo chmod 600 "$ENV_FILE"
|
||||||
|
sudo systemctl daemon-reload
|
||||||
|
sudo systemctl enable --now desineuron-velocity-route-sync.timer
|
||||||
|
sudo systemctl start desineuron-velocity-route-sync.service
|
||||||
|
sudo systemctl --no-pager --full status desineuron-velocity-route-sync.service desineuron-velocity-route-sync.timer
|
||||||
142
infrastructure/desineuron_ingress/sync_velocity_route.py
Normal file
142
infrastructure/desineuron_ingress/sync_velocity_route.py
Normal file
@@ -0,0 +1,142 @@
|
|||||||
|
#!/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())
|
||||||
@@ -34,7 +34,7 @@ VELOCITY_DB_NAME=velocity
|
|||||||
VELOCITY_DB_USER=velocity_app
|
VELOCITY_DB_USER=velocity_app
|
||||||
VELOCITY_DB_PASSWORD=${DB_PASSWORD}
|
VELOCITY_DB_PASSWORD=${DB_PASSWORD}
|
||||||
VELOCITY_JWT_SECRET=${JWT_SECRET}
|
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
|
VELOCITY_ASSET_DIR=/opt/dlami/nvme/assets
|
||||||
OLLAMA_BASE_URL=http://127.0.0.1:11434
|
OLLAMA_BASE_URL=http://127.0.0.1:11434
|
||||||
NEMOCLAW_BASE_URL=http://127.0.0.1:8080
|
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 enable velocity-backend.service
|
||||||
sudo systemctl restart velocity-backend.service
|
sudo systemctl restart velocity-backend.service
|
||||||
sudo nginx -t
|
sudo nginx -t
|
||||||
sudo systemctl restart nginx
|
sudo systemctl restart nginx
|
||||||
|
|||||||
Reference in New Issue
Block a user