diff --git a/argos/certs/certificate-argos-panel.yaml b/argos/certs/certificate-argos-panel.yaml
deleted file mode 100644
index 002bfee..0000000
--- a/argos/certs/certificate-argos-panel.yaml
+++ /dev/null
@@ -1,12 +0,0 @@
-apiVersion: cert-manager.io/v1
-kind: Certificate
-metadata:
- name: panel-argos-cert
- namespace: argos-core
-spec:
- secretName: panel-argos-tls
- issuerRef:
- name: letsencrypt-prod
- kind: ClusterIssuer
- dnsNames:
- - panel.argos.c2et.net
diff --git a/argos/configmaps/configmap-mediamtx.yaml b/argos/configmaps/configmap-mediamtx.yaml
index 3285b67..778ba0f 100644
--- a/argos/configmaps/configmap-mediamtx.yaml
+++ b/argos/configmaps/configmap-mediamtx.yaml
@@ -11,5 +11,4 @@ data:
hls: no
webrtc: yes
paths:
- all:
- sourceOnDemand: yes
+ all: {}
diff --git a/argos/configmaps/configmap-mosquitto.yaml b/argos/configmaps/configmap-mosquitto.yaml
index 9e55849..5f7ced3 100644
--- a/argos/configmaps/configmap-mosquitto.yaml
+++ b/argos/configmaps/configmap-mosquitto.yaml
@@ -1,9 +1,11 @@
apiVersion: v1
kind: ConfigMap
metadata:
- name: mosquitto-conf
+ name: mosquitto-config
namespace: argos-core
data:
mosquitto.conf: |
+ persistence true
+ persistence_location /mosquitto/data/
listener 1883 0.0.0.0
allow_anonymous true
diff --git a/argos/configmaps/configmap-orchestrator.yaml b/argos/configmaps/configmap-orchestrator.yaml
index 76f0d7f..f9cd57d 100644
--- a/argos/configmaps/configmap-orchestrator.yaml
+++ b/argos/configmaps/configmap-orchestrator.yaml
@@ -6,11 +6,14 @@ metadata:
data:
settings.yaml: |
mqtt:
- host: mqtt.argos.interna
- port: 1883
+ host: mosquitto.argos-core.svc.cluster.local
topic: frigate/events
+ topic_base: argos/alerts
+ port: 1883
client_id: argos-core
minio:
+ endpoint: minio.argos-core.svc.cluster.local:9000
+ secure: false
bucket: argos
region: us-east-1
edges:
diff --git a/argos/configmaps/configmap-panel.yaml b/argos/configmaps/configmap-panel.yaml
index 4404c50..8f849aa 100644
--- a/argos/configmaps/configmap-panel.yaml
+++ b/argos/configmaps/configmap-panel.yaml
@@ -5,83 +5,136 @@ metadata:
namespace: argos-core
data:
app.py: |
- import os, sqlite3, time
- from fastapi import FastAPI, HTTPException
- from fastapi.responses import HTMLResponse
+ import os
+ from fastapi import FastAPI, HTTPException, Query
+ from fastapi.responses import HTMLResponse, StreamingResponse, JSONResponse
from minio import Minio
- from urllib.parse import urlparse
+ from urllib.parse import quote
+ from datetime import timezone
+ import io
- DB="/data/argos.db"
- mc=Minio(os.getenv("MINIO_ENDPOINT","s3.argos.interna"),
- access_key=os.getenv("MINIO_ACCESS_KEY"),
- secret_key=os.getenv("MINIO_SECRET_KEY"),
- secure=os.getenv("MINIO_SECURE","false").lower()=="true")
- app=FastAPI()
+ BUCKET = os.getenv("MINIO_BUCKET", "argos")
+ endpoint = os.getenv("MINIO_ENDPOINT", "minio.argos-core.svc.cluster.local:9000")
+ access_key = os.getenv("MINIO_ACCESS_KEY")
+ secret_key = os.getenv("MINIO_SECRET_KEY")
+ secure = os.getenv("MINIO_SECURE", "false").lower() == "true"
- def rows(limit=100, camera=None, since=None):
- q="SELECT id, ts, edge, camera, label, s3url, thumb_s3 FROM events"
- cond=[]; args=[]
- if camera: cond.append("camera=?"); args.append(camera)
- if since: cond.append("ts>=?"); args.append(int(since))
- if cond: q+=" WHERE "+ " AND ".join(cond)
- q+=" ORDER BY ts DESC LIMIT ?"; args.append(limit)
- con=sqlite3.connect(DB); cur=con.cursor()
- cur.execute(q, tuple(args)); r=cur.fetchall(); con.close()
- return r
+ mc = Minio(endpoint, access_key=access_key, secret_key=secret_key, secure=secure)
+
+ app = FastAPI()
+
+ @app.get("/health")
+ def health():
+ # simple check contra MinIO
+ try:
+ mc.list_buckets()
+ return {"ok": True}
+ except Exception as e:
+ return {"ok": False, "error": str(e)}
+
+ def _iter_events(prefix: str | None, limit: int):
+ # list_objects devuelve en orden “no garantizado”; recolectamos y ordenamos por last_modified desc
+ objs = []
+ for obj in mc.list_objects(BUCKET, prefix=prefix, recursive=True):
+ # omitimos directorios “lógicos” y thumbs por defecto en el listado principal
+ if obj.is_dir:
+ continue
+ if "/thumbs/" in obj.object_name:
+ continue
+ objs.append(obj)
+ objs.sort(key=lambda o: o.last_modified or 0, reverse=True)
+ return objs[:limit]
@app.get("/api/events")
- def api_events(limit:int=100, camera:str=None, since:int=None):
- return [dict(id=i, ts=t, edge=e, camera=c, label=l or "", s3url=s, thumb=th or "")
- for (i,t,e,c,l,s,th) in rows(limit,camera,since)]
+ def api_events(prefix: str | None = Query(default=None), limit: int = 50):
+ data = []
+ for o in _iter_events(prefix, limit):
+ ts = None
+ if o.last_modified:
+ ts = o.last_modified.astimezone(timezone.utc).isoformat()
+ # si existe thumb, la inferimos (cam/... → cam/thumbs/...)
+ thumb = None
+ parts = o.object_name.split("/")
+ if len(parts) >= 2:
+ maybe = f"{parts[0]}/thumbs/" + parts[-1].rsplit(".",1)[0] + ".jpg"
+ try:
+ mc.stat_object(BUCKET, maybe)
+ thumb = maybe
+ except:
+ pass
+ data.append({
+ "key": o.object_name,
+ "size": o.size,
+ "last_modified": ts,
+ "thumb": thumb
+ })
+ return JSONResponse(data)
- @app.get("/api/url/{event_id}")
- def presign(event_id: str, expires: int = 600):
- con=sqlite3.connect(DB); cur=con.cursor()
- cur.execute("SELECT s3url FROM events WHERE id=?", (event_id,))
- row=cur.fetchone(); con.close()
- if not row: raise HTTPException(404, "Not found")
- s3url=row[0]; p=urlparse(s3url); b=p.netloc; k=p.path.lstrip("/")
- return {"url": mc.presigned_get_object(b, k, expires=expires)}
+ @app.get("/file")
+ def file(key: str):
+ try:
+ resp = mc.get_object(BUCKET, key)
+ except Exception as e:
+ raise HTTPException(status_code=404, detail=f"not found: {e}")
+ # stream al cliente sin exponer MinIO
+ def gen():
+ for d in resp.stream(32*1024):
+ yield d
+ # tipo de contenido básico por extensión
+ ctype = "application/octet-stream"
+ k = key.lower()
+ if k.endswith(".mp4"): ctype = "video/mp4"
+ elif k.endswith(".jpg") or k.endswith(".jpeg"): ctype = "image/jpeg"
+ return StreamingResponse(gen(), media_type=ctype)
+
+ @app.get("/view", response_class=HTMLResponse)
+ def view(key: str):
+ keyq = quote(key)
+ return f"""
+
+
Ver {key}
+
+
+ {key}
+
+ Descargar
+ Volver
+
+ """
@app.get("/", response_class=HTMLResponse)
- def index():
- return """
- ARGOS Panel
-
- ARGOS – Alarmas
-
-
-
+ def home(prefix: str | None = None, limit: int = 20):
+ # HTML mínimo que consume /api/events y pinta thumbs/links
+ p = f"&prefix={quote(prefix)}" if prefix else ""
+ items = []
+ for e in _iter_events(prefix, limit):
+ thumb_html = ""
+ # intentar thumb
+ parts = e.object_name.split("/")
+ if len(parts) >= 2:
+ maybe = f"{parts[0]}/thumbs/" + parts[-1].rsplit(".",1)[0] + ".jpg"
+ try:
+ mc.stat_object(BUCKET, maybe)
+ thumb_html = f'
'
+ except:
+ pass
+ items.append(f"""
+
+ {thumb_html}
+ {e.object_name}
+
+ """)
+ items_html = "\n".join(items) or "Sin eventos aún.
"
+ return f"""
+ Argos Panel
+
+
+ Argos Panel
+
+
+
"""
diff --git a/argos/configmaps/configmap-panel.yaml.old b/argos/configmaps/configmap-panel.yaml.old
new file mode 100644
index 0000000..4404c50
--- /dev/null
+++ b/argos/configmaps/configmap-panel.yaml.old
@@ -0,0 +1,87 @@
+apiVersion: v1
+kind: ConfigMap
+metadata:
+ name: argos-panel-config
+ namespace: argos-core
+data:
+ app.py: |
+ import os, sqlite3, time
+ from fastapi import FastAPI, HTTPException
+ from fastapi.responses import HTMLResponse
+ from minio import Minio
+ from urllib.parse import urlparse
+
+ DB="/data/argos.db"
+ mc=Minio(os.getenv("MINIO_ENDPOINT","s3.argos.interna"),
+ access_key=os.getenv("MINIO_ACCESS_KEY"),
+ secret_key=os.getenv("MINIO_SECRET_KEY"),
+ secure=os.getenv("MINIO_SECURE","false").lower()=="true")
+ app=FastAPI()
+
+ def rows(limit=100, camera=None, since=None):
+ q="SELECT id, ts, edge, camera, label, s3url, thumb_s3 FROM events"
+ cond=[]; args=[]
+ if camera: cond.append("camera=?"); args.append(camera)
+ if since: cond.append("ts>=?"); args.append(int(since))
+ if cond: q+=" WHERE "+ " AND ".join(cond)
+ q+=" ORDER BY ts DESC LIMIT ?"; args.append(limit)
+ con=sqlite3.connect(DB); cur=con.cursor()
+ cur.execute(q, tuple(args)); r=cur.fetchall(); con.close()
+ return r
+
+ @app.get("/api/events")
+ def api_events(limit:int=100, camera:str=None, since:int=None):
+ return [dict(id=i, ts=t, edge=e, camera=c, label=l or "", s3url=s, thumb=th or "")
+ for (i,t,e,c,l,s,th) in rows(limit,camera,since)]
+
+ @app.get("/api/url/{event_id}")
+ def presign(event_id: str, expires: int = 600):
+ con=sqlite3.connect(DB); cur=con.cursor()
+ cur.execute("SELECT s3url FROM events WHERE id=?", (event_id,))
+ row=cur.fetchone(); con.close()
+ if not row: raise HTTPException(404, "Not found")
+ s3url=row[0]; p=urlparse(s3url); b=p.netloc; k=p.path.lstrip("/")
+ return {"url": mc.presigned_get_object(b, k, expires=expires)}
+
+ @app.get("/", response_class=HTMLResponse)
+ def index():
+ return """
+ ARGOS Panel
+
+ ARGOS – Alarmas
+
+
+
+ """
diff --git a/argos/deployments/deploy-mediamtx.yaml b/argos/deployments/deploy-mediamtx.yaml
index a92fd19..9cdb62b 100644
--- a/argos/deployments/deploy-mediamtx.yaml
+++ b/argos/deployments/deploy-mediamtx.yaml
@@ -5,29 +5,33 @@ metadata:
namespace: argos-core
spec:
replicas: 1
- selector: { matchLabels: { app: mediamtx } }
+ selector:
+ matchLabels: { app: mediamtx }
template:
- metadata: { labels: { app: mediamtx } }
+ metadata:
+ labels: { app: mediamtx }
spec:
- hostNetwork: true
- dnsPolicy: ClusterFirstWithHostNet
containers:
- name: mediamtx
image: bluenviron/mediamtx:1.14.0
- command: ["/bin/sh","-c"]
- args:
- - |
- set -e
- ulimit -n 1048576
- exec mediamtx /config/mediamtx.yml
+ # Nada de /bin/sh: solo pasa la ruta del YAML como argumento posicional
+ args: ["/config/mediamtx.yml"]
volumeMounts:
- name: cfg
mountPath: /config
ports:
- - containerPort: 8554 # RTSP
- - containerPort: 8189 # SRT
- - containerPort: 8889 # WHIP
- - containerPort: 8880 # HTTP/API
+ - name: rtsp
+ containerPort: 8554
+ protocol: TCP
+ - name: http
+ containerPort: 8880
+ protocol: TCP
+ - name: whip
+ containerPort: 8889
+ protocol: TCP
+ - name: srt
+ containerPort: 8189
+ protocol: UDP
volumes:
- name: cfg
configMap:
diff --git a/argos/deployments/deploy-minio.yaml b/argos/deployments/deploy-minio.yaml
index 09a4f70..3c84773 100644
--- a/argos/deployments/deploy-minio.yaml
+++ b/argos/deployments/deploy-minio.yaml
@@ -4,23 +4,69 @@ metadata:
name: minio
namespace: argos-core
spec:
+ strategy:
+ type: Recreate
replicas: 1
- selector: { matchLabels: { app: minio } }
+ selector:
+ matchLabels:
+ app: minio
template:
- metadata: { labels: { app: minio } }
+ metadata:
+ labels:
+ app: minio
+ app.kubernetes.io/part-of: argos
+ app.kubernetes.io/managed-by: kustomize
spec:
+ # ayuda a que el FS sea accesible por el grupo
+ securityContext:
+ fsGroup: 1000
+ fsGroupChangePolicy: OnRootMismatch
+ # arregla permisos heredados de root en el PVC
+ initContainers:
+ - name: fix-perms
+ image: alpine:3.20
+ command: ["/bin/sh","-c"]
+ args:
+ - |
+ set -ex
+ apk add --no-cache acl
+ chown -R 1000:1000 /data || true
+ chmod -R u+rwX,g+rwX /data || true
+ find /data -type d -exec chmod g+s {} \; || true
+ setfacl -R -m g:1000:rwx /data || true
+ setfacl -R -d -m g:1000:rwx /data || true
+ securityContext:
+ runAsUser: 0
+ volumeMounts:
+ - name: data
+ mountPath: /data
containers:
- name: minio
image: quay.io/minio/minio:latest
+ securityContext:
+ runAsUser: 1000
+ runAsGroup: 1000
args: ["server", "/data", "--console-address", ":9001"]
envFrom:
- secretRef: { name: minio-creds }
ports:
- - containerPort: 9000
- - containerPort: 9001
- volumeMounts:
- - name: data
- mountPath: /data
+ - { containerPort: 9000, name: api }
+ - { containerPort: 9001, name: console }
+ readinessProbe:
+ httpGet: { path: /minio/health/ready, port: 9000 }
+ initialDelaySeconds: 5
+ periodSeconds: 10
+ livenessProbe:
+ httpGet: { path: /minio/health/live, port: 9000 }
+ initialDelaySeconds: 10
+ periodSeconds: 20
+ resources:
+ requests:
+ cpu: 50m
+ memory: 256Mi
+ limits:
+ cpu: "1"
+ memory: 2Gi
volumes:
- name: data
persistentVolumeClaim:
diff --git a/argos/deployments/deploy-mosquitto.yaml b/argos/deployments/deploy-mosquitto.yaml
index a82eb87..75d1a94 100644
--- a/argos/deployments/deploy-mosquitto.yaml
+++ b/argos/deployments/deploy-mosquitto.yaml
@@ -5,22 +5,27 @@ metadata:
namespace: argos-core
spec:
replicas: 1
- selector: { matchLabels: { app: mosquitto } }
+ selector:
+ matchLabels: { app: mosquitto }
template:
- metadata: { labels: { app: mosquitto } }
+ metadata:
+ labels: { app: mosquitto }
spec:
containers:
- name: mosquitto
- image: harbor.c2et.net/library/eclipse-mosquitto:latest
+ image: eclipse-mosquitto:2
ports:
- containerPort: 1883
volumeMounts:
- name: cfg
mountPath: /mosquitto/config
+ - name: data
+ mountPath: /mosquitto/data
volumes:
- name: cfg
configMap:
- name: mosquitto-conf
+ name: mosquitto-config
items:
- - key: mosquitto.conf
- path: mosquitto.conf
+ - { key: mosquitto.conf, path: mosquitto.conf }
+ - name: data
+ emptyDir: {} # o tu PVC si lo tienes
diff --git a/argos/deployments/deploy-panel.yaml b/argos/deployments/deploy-panel.yaml
index 900ccc8..8b0a99b 100644
--- a/argos/deployments/deploy-panel.yaml
+++ b/argos/deployments/deploy-panel.yaml
@@ -5,30 +5,30 @@ metadata:
namespace: argos-core
spec:
replicas: 1
- selector: { matchLabels: { app: argos-panel } }
+ selector:
+ matchLabels: { app: argos-panel }
template:
- metadata: { labels: { app: argos-panel } }
+ metadata:
+ labels: { app: argos-panel }
spec:
containers:
- name: panel
- image: harbor.c2et.net/library/python:3.13.7-slim-bookworm
+ image: python:3.13.7-slim-bookworm
command: ["/bin/sh","-c"]
args:
- |
set -e
pip install fastapi uvicorn minio
- uvicorn app:app --host 0.0.0.0 --port 8000
+ exec uvicorn app:app --host 0.0.0.0 --port 8000 --app-dir /app
envFrom:
- secretRef: { name: argos-panel-secret }
volumeMounts:
- - { name: app, mountPath: /app }
- - { name: data, mountPath: /data }
+ - { name: app, mountPath: /app }
ports:
- - containerPort: 8000
+ - { containerPort: 8000, name: http }
volumes:
- name: app
configMap:
name: argos-panel-config
- items: [ { key: app.py, path: app.py } ]
- - name: data
- emptyDir: {}
+ items:
+ - { key: app.py, path: app.py }
diff --git a/argos/ingress/ingress-panel.yaml b/argos/ingress/ingress-panel.yaml
index d7095e2..192b844 100644
--- a/argos/ingress/ingress-panel.yaml
+++ b/argos/ingress/ingress-panel.yaml
@@ -3,16 +3,21 @@ kind: Ingress
metadata:
name: argos-panel-internal
namespace: argos-core
+ annotations:
+ cert-manager.io/cluster-issuer: letsencrypt-prod
spec:
- ingressClassName: nginx-internal
+ ingressClassName: nginx
rules:
- - host: panel.argos.c2et.net # mismo FQDN
+ - host: argos.panel.c2et.net
http:
paths:
- path: /
pathType: Prefix
backend:
- service: { name: argos-panel, port: { number: 80 } }
+ service:
+ name: argos-panel
+ port:
+ name: http # <-- o usa "number: 8000"
tls:
- - hosts: ["panel.argos.c2et.net"]
+ - hosts: ["argos.panel.c2et.net"]
secretName: panel-argos-tls
diff --git a/argos/kustomization.yaml b/argos/kustomization.yaml
index ce3752f..9c1edfa 100644
--- a/argos/kustomization.yaml
+++ b/argos/kustomization.yaml
@@ -3,14 +3,9 @@ kind: Kustomization
namespace: argos-core
-commonLabels:
- app.kubernetes.io/part-of: argos
- app.kubernetes.io/managed-by: kustomize
-
resources:
- # Namespace y políticas
+ # Namespace
- namespace.yaml
- - policies/network-policy.yaml
# ConfigMaps
- configmaps/configmap-mediamtx.yaml
@@ -18,31 +13,33 @@ resources:
- configmaps/configmap-orchestrator.yaml
- configmaps/configmap-panel.yaml
- # Secrets
- - secrets/secret-minio.yaml
- - secrets/secret-orchestrator.yaml
- - secrets/secret-panel.yaml
-
- # Storage
- - pvc/pvc-minio.yaml
-
# Deployments
- deployments/deploy-mediamtx.yaml
+ - deployments/deploy-minio.yaml
- deployments/deploy-mosquitto.yaml
- deployments/deploy-orchestrator.yaml
- deployments/deploy-panel.yaml
- - deployments/deploy-minio.yaml
# Services
- - services/argos-panel.yaml
- services/svc-mediamtx-tcp.yaml
- services/svc-mediamtx-udp.yaml
- services/svc-minio.yaml
- services/svc-mosquitto.yaml
+ - services/svc-panel.yaml
# Ingress
- ingress/ingress-minio.yaml
- ingress/ingress-panel.yaml
- # Certificados (si usas ACME en el externo y compartes el secret)
- - certs/certificate-argos-panel.yaml
+ # Almacenamiento
+ - pvc/pvc-minio.yaml
+
+ # Políticas de red
+ - policies/network-policy.yaml
+ - policies/allow-same-namespace.yaml
+
+ # Secrets (si ya existen en el cluster, puedes omitirlos aquí)
+ - secrets/secret-minio.yaml
+ - secrets/secret-orchestrator.yaml
+ - secrets/secret-panel.yaml
+# - secrets/secret-harbor-cred.yaml
diff --git a/argos/policies/allow-same-namespace.yaml b/argos/policies/allow-same-namespace.yaml
new file mode 100644
index 0000000..c30457c
--- /dev/null
+++ b/argos/policies/allow-same-namespace.yaml
@@ -0,0 +1,11 @@
+apiVersion: networking.k8s.io/v1
+kind: NetworkPolicy
+metadata:
+ name: allow-same-namespace
+ namespace: argos-core
+spec:
+ podSelector: {} # aplica a todos los pods de argos-core (destino)
+ policyTypes: [Ingress]
+ ingress:
+ - from:
+ - podSelector: {} # origen: cualquier pod del mismo namespace
diff --git a/argos/readme.md b/argos/readme.md
new file mode 100644
index 0000000..03313dc
--- /dev/null
+++ b/argos/readme.md
@@ -0,0 +1,199 @@
+# Argos Core
+
+**Argos Core** es el backend central de una plataforma de videovigilancia distribuida. Está diseñado para recibir eventos desde **edges** (sitios remotos con Frigate), capturar/ingestar vídeo bajo demanda, almacenarlo de forma fiable y ofrecer un **panel web** seguro para consulta y reproducción, todo **encapsulado en Kubernetes** y oculto tras una **VPN WireGuard**.
+
+---
+
+## Visión general
+
+```text
+[Edge / Frigate] --(VPN/WG + MQTT eventos)--> [Argos Core]
+ ├─ Mosquitto (MQTT)
+ ├─ Orchestrator (ingesta clips)
+ │ ├─ Pull clip nativo de Frigate
+ │ └─ Fallback: captura RTSP temporal
+ ├─ MediaMTX (live/retransmisión)
+ ├─ MinIO (S3, almacenamiento)
+ └─ Panel (FastAPI + Uvicorn)
+```
+
+**Objetivo clave:** los edges **no** envían flujo constante; sólo cuando hay evento. El core ingesta y guarda el clip, y ofrece visualización **on‑demand**.
+
+---
+
+## Componentes
+
+* **WireGuard (wg-easy)**: servidor VPN del clúster. Todo el tráfico de edges → core circula por WG.
+* **Mosquitto** (`eclipse-mosquitto:2`): broker MQTT interno (ClusterIP). Topic de eventos (p. ej. `frigate/events`).
+* **Orchestrator** (Python):
+
+ * Se suscribe a MQTT.
+ * Tras un evento, intenta **descargar el clip nativo** desde la API del Frigate del edge.
+ * Si no existe, hace **captura RTSP** temporal con `ffmpeg` (duración configurada) contra la cámara/edge.
+ * Sube el resultado a **MinIO** (`s3://argos/...`) y registra metadatos en SQLite embebido.
+* **MinIO** (S3 compatible): almacenamiento de clips y miniaturas.
+* **MediaMTX**: redistribución de flujos (RTSP/WHIP/SRT) y posible live view on-demand.
+* **Panel** (FastAPI/Uvicorn):
+
+ * Lista eventos desde MinIO (sin exponer MinIO: **proxy** de objetos).
+ * Página de reproducción (`/view?key=…`).
+ * Endpoints simples (`/file`, `/api/events`, `/health`).
+* **CoreDNS**: DNS interno con split-horizon (resolución diferente desde LAN/VPN/cluster cuando aplica).
+* **Ingress NGINX (interno/externo)** + **cert‑manager**: TLS para el panel; HTTP‑01 servido por el controlador externo.
+* **MetalLB**: IPs para servicios tipo LoadBalancer en la red `192.168.200.0/24` (p. ej. MediaMTX).
+
+---
+
+## Flujo de datos (evento → clip)
+
+1. **Edge** detecta (Frigate) y publica en **MQTT** `frigate/events`.
+2. **Orchestrator** recibe el evento, localiza la cámara/edge en su `settings.yaml`.
+3. Intenta **pull** del **clip nativo** vía API Frigate del edge (VPN).
+4. Si no hay clip, ejecuta **`ffmpeg`** para capturar **RTSP** temporal (fallback).
+5. Sube el fichero a **MinIO** con una clave tipo: `camX/YYYY/MM/DD/TS_eventId.mp4` y optional thumb.
+6. **Panel** lo muestra en la lista y permite reproducir vía `/file?key=…` (stream proxy desde MinIO).
+
+> Para **live**, MediaMTX publica `/` con `path=`; puedes incrustar un reproductor WebRTC/RTSP en el panel.
+
+---
+
+## Despliegue
+
+### Requisitos previos
+
+* Kubernetes operativo con:
+
+ * **MetalLB** (rango `192.168.200.0/24`).
+ * **NGINX Ingress** *interno* y *externo* (split-horizon DNS).
+ * **cert‑manager** + `ClusterIssuer` (p. ej. `letsencrypt-prod`).
+ * **wg-easy** funcionando (con NAT/iptables para alcanzar `200.x`, `0.x`, etc.).
+
+### Estructura
+
+```
+argos/
+├─ configmaps/ # mediamtx, mosquitto, orchestrator, panel
+├─ deployments/ # mediamtx, minio, mosquitto, orchestrator, panel
+├─ ingress/ # minio (opcional), panel (TLS)
+├─ policies/ # network-policy, allow-same-namespace
+├─ pvc/ # pvc-minio
+├─ secrets/ # minio, orchestrator, panel
+└─ services/ # mediamtx tcp/udp, minio, mosquitto, panel
+```
+
+### Variables/Secrets esperados
+
+* **MinIO** (`secrets/secret-minio.yaml`): `MINIO_ACCESS_KEY`, `MINIO_SECRET_KEY`.
+* **Orchestrator** (`secrets/secret-orchestrator.yaml`): `MINIO_ENDPOINT`, `MINIO_ACCESS_KEY`, `MINIO_SECRET_KEY`, `MINIO_SECURE`.
+* **Panel** (`secrets/secret-panel.yaml`): `MINIO_*` y `MINIO_BUCKET`.
+
+### Servicios y puertos
+
+* **MinIO**: `ClusterIP` :9000 (API), :9001 (console, opcional). No expuesto fuera.
+* **Mosquitto**: `ClusterIP` :1883.
+* **MediaMTX**: `LoadBalancer` (MetalLB) en `192.168.200.16` (RTSP 8554 TCP, WHIP 8889 TCP/HTTP 8880, SRT 8189 UDP).
+* **Panel**: `ClusterIP` :8000 → Ingress (TLS) `https://argos.panel.c2et.net/` (ajusta FQDN).
+
+### Kustomize
+
+```bash
+kubectl apply -k .
+```
+
+> Nota: evitamos `commonLabels` globales para no romper los **selectors**.
+
+---
+
+## Configuración relevante
+
+### Orchestrator (`configmaps/configmap-orchestrator.yaml`)
+
+* `mqtt.host`: `mosquitto.argos-core.svc.cluster.local`
+* `mqtt.port`: `1883`
+* `mqtt.topic`: `frigate/events`
+* `minio.endpoint`: `minio.argos-core.svc.cluster.local:9000`
+* `edges`: lista de edges y cámaras (URLs de Frigate, RTSP principal, etc.).
+
+### Mosquitto (`configmaps/configmap-mosquitto.yaml`)
+
+* Config mínimo:
+
+ ```
+ persistence true
+ persistence_location /mosquitto/data/
+ listener 1883 0.0.0.0
+ allow_anonymous true # RECOMENDACIÓN: pasar a autenticación/TLS más adelante
+ ```
+
+### MediaMTX
+
+* Ejecutar **sin** `/bin/sh -c …` y pasando **solo** la ruta del YAML como `args`.
+* Corrige warnings como `sourceOnDemand` según el modo (`publisher` o `record`).
+
+### MinIO (seguridad/permisos)
+
+* Ejecuta como **no-root** con `runAsUser: 1000` y un **initContainer** que hace `chown/chmod/ACL` del PVC.
+* Estrategia de despliegue **Recreate** para evitar doble pod en updates cuando la `readinessProbe` tarda.
+
+### Ingress + cert-manager
+
+* El **Certificate** del panel debe forzar el solver HTTP‑01 por el **ingress externo**:
+
+ * Anotación: `acme.cert-manager.io/http01-ingress-class: nginx-external` (o ajusta en `ClusterIssuer`).
+
+---
+
+## Operación
+
+### Comandos útiles
+
+```bash
+# Estado general
+kubectl -n argos-core get pods,svc,ingress,endpoints
+
+# Endpoints vacíos ()
+kubectl -n argos-core get endpoints -o wide
+# Revisa selector del Service y labels del Pod
+
+# Logs
+kubectl -n argos-core logs -f deploy/
+
+# Reinicios controlados
+kubectl -n argos-core rollout restart deploy/
+
+# Diff del Service en vivo vs manifiesto
+kubectl -n argos-core get svc argos-panel -o yaml --show-managed-fields=false
+```
+
+### Problemas típicos y fixes
+
+* **Service con `ENDPOINTS `**: selector ≠ labels, o pod **not Ready** (probe fallando). Ajusta selector a `{app: …}` y corrige readiness.
+* **Dos pods tras update**: rolling update atascado. Usa `strategy: Recreate` mientras estabilizas.
+* **MinIO `file access denied`**: permisos del PVC. Añade `initContainer` que hace `chown -R 1000:1000 /data` + ACL.
+* **Mosquitto `Connection refused`** desde Orchestrator: Service sin endpoints o broker no escucha en 1883. Ver logs `mosquitto` y `ss -lnt`.
+* **Panel `ASGI app not found`**: `uvicorn app:app --app-dir /app` y asegúrate de montar `app.py` con `app = FastAPI()`.
+* **MediaMTX `unknown flag -c`**: no usar `/bin/sh -c …`; pasar `args: ["/config/mediamtx.yml"]`.
+
+---
+
+## Edge (resumen de mañana)
+
+* **WireGuard** cliente (IP del edge en la VPN; DNS apuntando al core).
+* **Frigate** en Docker + `mqtt.host=mosquitto.argos-core.svc.cluster.local`.
+* API de Frigate accesible desde el core por la VPN (p. ej. `http://10.20.0.10:5000`).
+* Opcional: MediaMTX local si se quiere “empujar” live al core durante la alarma.
+
+---
+
+## Seguridad y siguientes pasos
+
+* Endurecer Mosquitto (usuarios/TLS), MinIO (políticas/buckets versionados), y el Panel (auth detrás de Ingress o login propio).
+* Métricas y dashboards (Prometheus/Grafana), y mover metadatos a **PostgreSQL** para búsquedas ricas.
+* Live WebRTC integrado en el panel (MediaMTX WHIP/WHEP).
+
+---
+
+## Licencia / Créditos
+
+* **MediaMTX** (Bluenviron), **MinIO**, **Eclipse Mosquitto**, **Frigate** (en los edges).
+* Argos Core: composición y orquestación Kubernetes + servicios auxiliares.
diff --git a/argos/services/svc-mosquitto.yaml b/argos/services/svc-mosquitto.yaml
index 72424c2..feffbeb 100644
--- a/argos/services/svc-mosquitto.yaml
+++ b/argos/services/svc-mosquitto.yaml
@@ -4,8 +4,7 @@ metadata:
name: mosquitto
namespace: argos-core
spec:
- type: LoadBalancer
- loadBalancerIP: 192.168.200.15
+ type: ClusterIP
selector: { app: mosquitto }
ports:
- name: mqtt
diff --git a/argos/services/argos-panel.yaml b/argos/services/svc-panel.yaml
similarity index 72%
rename from argos/services/argos-panel.yaml
rename to argos/services/svc-panel.yaml
index cc101e9..36da331 100644
--- a/argos/services/argos-panel.yaml
+++ b/argos/services/svc-panel.yaml
@@ -7,4 +7,6 @@ spec:
type: ClusterIP
selector: { app: argos-panel }
ports:
- - { name: http, port: 80, targetPort: 8000 }
+ - name: http
+ port: 8000
+ targetPort: 8000
diff --git a/prueba/nuevoarchivo.md b/prueba/nuevoarchivo.md
deleted file mode 100644
index af39bf8..0000000
--- a/prueba/nuevoarchivo.md
+++ /dev/null
@@ -1 +0,0 @@
-sdgfsdgsdfg
\ No newline at end of file