Complete snapshot of /opt/microdao-daarion/ from NODE1 (144.76.224.179).
This represents the actual running production code that has diverged
significantly from the previous main branch.
Key changes from old main:
- Gateway (http_api.py): expanded from ~40KB to 164KB with full agent support
- Router: new /v1/agents/{id}/infer endpoint with vision + DeepSeek routing
- Behavior Policy: SOWA v2.2 (3-level: FULL/ACK/SILENT)
- Agent Registry: config/agent_registry.yml as single source of truth
- 13 agents configured (was 3)
- Memory service integration
- CrewAI teams and roles
Excluded from snapshot: venv/, .env, data/, backups, .tgz archives
Co-authored-by: Cursor <cursoragent@cursor.com>
19 KiB
NODA1 — Точні патчі для трьох техборгів
Source of truth: repo (node2) — docs/NODA1-TECHBORGS-PATCHES.md
Copy on NODA1: /opt/microdao-daarion/docs/NODA1-TECHBORGS-PATCHES.md
Code commit: d0188fd
Docs commit: fb6b92b
Last sync: 2026-01-28
Документ містить зібрані з NODA1 YAML-фрагменти (read-only) і мінімальні, безпечні патчі для postgres-backup (pg16), render-pdf-worker (NATS hostname), Caddy vs nginx.
Compose-файли на NODA1:
- Основний стек:
/opt/microdao-daarion/docker-compose.node1.yml - Бекапи:
/opt/microdao-daarion/docker-compose.backups.yml - Effective config:
cd /opt/microdao-daarion && docker compose -f docker-compose.node1.yml config→ 766 рядків
0) Safety + rollback (~2 хв)
# 0.1 Backup compose файлів перед правкою
cd /opt/microdao-daarion
cp -a docker-compose.node1.yml docker-compose.node1.yml.bak.$(date +%F_%H%M%S)
cp -a docker-compose.backups.yml docker-compose.backups.yml.bak.$(date +%F_%H%M%S)
# 0.2 Зняти поточні images (для швидкого повернення)
docker images --format 'table {{.Repository}}\t{{.Tag}}\t{{.ID}}\t{{.CreatedSince}}' | head -50
# 0.3 Rollback (якщо треба)
# cp docker-compose.node1.yml.bak.<ts> docker-compose.node1.yml
# cp docker-compose.backups.yml.bak.<ts> docker-compose.backups.yml
# docker compose -f docker-compose.node1.yml up -d
# docker compose -f docker-compose.backups.yml up -d
Перед застосуванням — валідація YAML (ловить синтаксичні помилки до будь-якого up -d):
docker compose -f /opt/microdao-daarion/docker-compose.node1.yml config >/dev/null && echo "node1 compose OK"
docker compose -f /opt/microdao-daarion/docker-compose.backups.yml config >/dev/null && echo "backups compose OK"
Примітка про service name vs container_name (пастка №1): docker compose up -d <service> працює тільки з іменем сервісу з compose, а docker logs / docker exec — по імені контейнера (container_name). Щоб не плутатись:
# Показати відповідність service → container
cd /opt/microdao-daarion
docker compose -f docker-compose.node1.yml ps
docker compose -f docker-compose.backups.yml ps
1) Зібрані YAML-блоки (read-only)
1.1 qdrant (з effective config)
qdrant:
container_name: dagi-qdrant-node1
healthcheck:
test: ["CMD", "true"]
timeout: 10s
interval: 30s
retries: 3
image: qdrant/qdrant:v1.7.4
networks:
dagi-network: null
ports:
- "6333:6333"
- "6334:6334"
restart: unless-stopped
ulimits:
nofile: { soft: 65536, hard: 65536 }
volumes:
- qdrant-data-node1:/qdrant/storage
1.2 nats (з effective config)
nats:
command: ["-js"]
container_name: dagi-nats-node1
image: nats:2.10-alpine
networks:
dagi-network: null
ports:
- "4222:4222"
restart: unless-stopped
volumes:
- nats-data-node1:/data
1.3 render-pdf-worker (з effective config)
render-pdf-worker:
build:
context: /opt/microdao-daarion/services/render-pdf-worker
dockerfile: Dockerfile
container_name: render-pdf-worker-node1
depends_on:
artifact-registry: { condition: service_started, required: true }
minio: { condition: service_started, required: true }
nats: { condition: service_started, required: true }
environment:
ARTIFACT_REGISTRY_URL: http://artifact-registry:9220
MINIO_ACCESS_KEY: minioadmin
MINIO_BUCKET: artifacts
MINIO_ENDPOINT: minio:9000
MINIO_SECRET_KEY: minioadmin
MINIO_SECURE: "false"
NATS_URL: nats://nats:4222
networks:
dagi-network: null
restart: unless-stopped
1.4 postgres-backup (з docker-compose.backups.yml)
services:
postgres-backup:
image: prodrigestivill/postgres-backup-local:15
container_name: postgres-backup-node1
restart: unless-stopped
environment:
POSTGRES_HOST: dagi-postgres
POSTGRES_DB: daarion_main,daarion_memory,rag
POSTGRES_USER: daarion
POSTGRES_PASSWORD: DaarionDB2026!
SCHEDULE: "@every 6h"
BACKUP_KEEP_DAYS: 7
BACKUP_KEEP_WEEKS: 4
BACKUP_KEEP_MONTHS: 6
POSTGRES_EXTRA_OPTS: "-Z9 --schema=public --blobs"
volumes:
- /opt/backups/postgres:/backups
networks:
- dagi-network
networks:
dagi-network:
external: true
1.5 Проксі (системні сервіси, не compose)
- nginx: active (running), тримає 80/443, конфіг:
/etc/nginx/nginx.conf+sites-enabled/*,conf.d/*. - Caddy: failed — "listen tcp :443: bind: address already in use" (nginx вже слухає 443). Версія Caddy: v2.10.2.
2) Патч #1: postgres-backup (pg_dump 16.x)
Ціль: pg_dump версії 16 відповідає Postgres 16.11 на сервері.
Файл: /opt/microdao-daarion/docker-compose.backups.yml
Зміна (1 рядок):
- image: prodrigestivill/postgres-backup-local:15
+ image: prodrigestivill/postgres-backup-local:16
Якщо тегу :16 немає на Docker Hub для цього образу — варіанти:
- Використати інший образ на базі
postgres:16(наприклад, з postgresql-client-16). - Або зібрати власний Dockerfile на базі
postgres:16і викликатиpg_dumpз нього.
Після правки:
cd /opt/microdao-daarion
docker compose -f docker-compose.backups.yml pull postgres-backup
docker compose -f docker-compose.backups.yml up -d postgres-backup
docker logs --tail 50 postgres-backup-node1
3) Патч #2: render-pdf-worker (NATS hostname)
Проблема: NATS_URL=nats://nats:4222, а сервіс у мережі називається dagi-nats-node1 (container_name), тому DNS nats не резолвиться.
Рекомендація: варіант A (менш інвазивний) — додати alias nats для сервісу nats у dagi-network. Інші клієнти (якщо теж використовують nats:4222) продовжать працювати без змін.
Нюанс (alias тільки на рівні сервісу): зміна має бути саме у сервісі (не в секції top-level networks:). У docker-compose.node1.yml сервіс називається nats (container_name: dagi-nats-node1). Якщо у вашому файлі сервіс має інше ім'я (наприклад dagi-nats-node1), використовуйте його як ключ:
services:
nats:
container_name: dagi-nats-node1
networks:
dagi-network:
aliases:
- nats
Або якщо сервіс названо dagi-nats-node1:
services:
dagi-nats-node1:
networks:
dagi-network:
aliases:
- nats
Якщо зараз networks: [dagi-network] або dagi-network: null — замінити на мапу з aliases як вище.
Файл: /opt/microdao-daarion/docker-compose.node1.yml
Знайти блок nats: (приблизно такий):
nats:
command:
- -js
container_name: dagi-nats-node1
image: nats:2.10-alpine
networks:
dagi-network: null
Замінити на (додати aliases):
nats:
command:
- -js
container_name: dagi-nats-node1
image: nats:2.10-alpine
networks:
dagi-network:
aliases:
- nats
ports:
- "4222:4222"
restart: unless-stopped
volumes:
- nats-data-node1:/data
Тобто замість dagi-network: null використати:
networks:
dagi-network:
aliases:
- nats
Альтернатива B: у сервісі render-pdf-worker змінити env:
NATS_URL: nats://dagi-nats-node1:4222
Після правки (варіант A):
cd /opt/microdao-daarion
docker compose -f docker-compose.node1.yml up -d nats
docker compose -f docker-compose.node1.yml up -d render-pdf-worker
docker logs --tail 80 render-pdf-worker-node1
4) Render-PDF-Worker: діагностика та idle timeout
Висновок з перевірок A/B: Підключення воркера до NATS працює (alias nats резолвиться у dagi-network). Помилка nats.errors.TimeoutError у логах — це таймаут очікування повідомлень (sub.next_msg()), а не з’єднання. Subject підписки: artifact.job.render_pdf.requested. Якщо задач немає, воркер отримує timeout і без обробки винятку завершується (exit 1).
A) Довести підключення до NATS (read-only):
- TCP з воркера (коли контейнер Up):
docker exec render-pdf-worker-node1 sh -lc 'nc -vz -w 2 nats 4222 && echo NATS_TCP_OK'(у контейнері memory-service немаєnc, тому перевірку можна робити з іншого контейнера зncабо черезgetent hosts nats). - Логи NATS:
docker logs --tail 200 dagi-nats-node1 | egrep -i 'client|connect|disconnect'.
B) Env і subject: У воркера лише NATS_URL=nats://nats:4222; subject/queue задані в коді: artifact.job.render_pdf.requested.
C) Варіант без зміни коду: У compose вже є restart: unless-stopped; додано stop_grace_period: 30s. Воркер буде автоматично перезапускатися після exit при idle.
D) Варіант з мінімальною зміною коду (рекомендовано): У main.py обробити nats.errors.TimeoutError у циклі очікування повідомлень — не завершувати процес, а робити continue:
import nats.errors
# ...
while True:
try:
msg = await sub.next_msg()
except nats.errors.TimeoutError:
continue
# обробка msg
Після патчу D воркер залишається активним при відсутності задач без рестарт-циклів.
Спостережуваність NATS (без підвищеного логування): стандартні логи NATS не показують connect/disconnect клієнтів. Корисно мати health/metrics endpoint або періодичну перевірку connz (якщо дозволено конфігом NATS monitoring port), щоб швидко довести, що клієнт реально підключився. Не обов’язково зараз, але зменшує час діагностики.
5) Патч #3: Caddy vs nginx (конфлікт 80/443)
Поточний стан: nginx active, тримає 80/443; Caddy failed через "address already in use".
Рекомендація (найпростіший варіант): nginx єдиний reverse proxy — вимкнути Caddy, щоб уникнути конфлікту і подвійної конфігурації.
Мінімальний патч (без зміни nginx):
sudo systemctl stop caddy
sudo systemctl disable caddy
Якщо потрібно саме Caddy як єдиний проксі — тоді вимкнути nginx, перенести конфіг з nginx у Caddyfile і налаштувати Caddy на 80/443 (окремий техборг).
Порядок застосування патчів (рекомендований)
1.1 Патч #2 (NATS alias) — першим, бо розблоковує render-pdf-worker
Використовуйте ім'я сервісу з compose (nats, render-pdf-worker), не container_name (dagi-nats-node1, render-pdf-worker-node1) для docker compose up -d.
cd /opt/microdao-daarion
# Після внесення alias у docker-compose.node1.yml
docker compose -f docker-compose.node1.yml pull nats 2>/dev/null || true
docker compose -f docker-compose.node1.yml up -d nats
# Перевірка alias резолвиться з воркера
docker start render-pdf-worker-node1 2>/dev/null || true
docker exec render-pdf-worker-node1 sh -lc 'getent hosts nats && nc -vz -w 2 nats 4222' 2>/dev/null || true
# Перепідняти воркер (service name)
docker compose -f docker-compose.node1.yml up -d render-pdf-worker 2>/dev/null || true
docker logs --tail 120 render-pdf-worker-node1
Якщо render-pdf-worker-node1 не керується compose (standalone container):
docker restart render-pdf-worker-node1
docker logs --tail 120 render-pdf-worker-node1
Якщо alias додано, але worker все одно не бачить nats (пастка №2): найчастіше воркер не в тій же мережі, або він standalone і не приєднаний до dagi-network.
# 1) Переконатися, що nats і worker в одній мережі
docker inspect dagi-nats-node1 --format '{{json .NetworkSettings.Networks}}' | jq .
docker inspect render-pdf-worker-node1 --format '{{json .NetworkSettings.Networks}}' | jq .
# 2) Якщо worker standalone і не в dagi-network — тимчасово приєднати (операційний workaround)
# УВАГА: це змінює стан хоста (але не compose). Застосовувати лише як швидкий фікс.
# docker network connect dagi-network render-pdf-worker-node1
# 3) Повторна перевірка
docker exec render-pdf-worker-node1 sh -lc 'getent hosts nats && nc -vz -w 2 nats 4222' 2>/dev/null || true
1.2 Патч #1 (postgres-backup образ pg16)
cd /opt/microdao-daarion
# Після заміни tag :15 -> :16 у docker-compose.backups.yml
docker compose -f docker-compose.backups.yml pull postgres-backup 2>/dev/null || true
docker compose -f docker-compose.backups.yml up -d postgres-backup
docker ps --filter name=postgres-backup-node1 --format 'table {{.Names}}\t{{.Status}}'
docker logs --tail 200 postgres-backup-node1
docker inspect postgres-backup-node1 --format '{{.State.Health.Status}}'
1.3 Патч #3 (nginx-only proxy) — вимкнути Caddy
systemctl stop caddy || true
systemctl disable caddy || true
systemctl status caddy --no-pager -l || true
# Переконатися що nginx тримає 80/443
systemctl status nginx --no-pager -l
ss -ltnp | egrep ':(80|443)\s'
Sync from repo → NODA1
# локально (node2)
cd /Users/apple/node2
# (опційно) показати, що саме деплоїмо
git rev-parse --short HEAD
git show -s --format=%ci
# 1) Код воркера
scp -p services/render-pdf-worker/app/main.py \
root@144.76.224.179:/opt/microdao-daarion/services/render-pdf-worker/app/main.py
# 2) Документація
scp -p docs/NODA1-TECHBORGS-PATCHES.md \
root@144.76.224.179:/opt/microdao-daarion/docs/NODA1-TECHBORGS-PATCHES.md
scp -p docs/NODA1-MEMORY-RUNBOOK.md \
root@144.76.224.179:/opt/microdao-daarion/docs/NODA1-MEMORY-RUNBOOK.md
# 3) На NODA1: rebuild + restart + лог
ssh root@144.76.224.179 '
cd /opt/microdao-daarion &&
docker compose -f docker-compose.node1.yml ps &&
docker compose -f docker-compose.node1.yml build render-pdf-worker &&
docker compose -f docker-compose.node1.yml up -d render-pdf-worker &&
docker logs --tail 120 render-pdf-worker-node1
'
Якщо у compose сервіс називається інакше (наприклад render-pdf-worker-node1 лише як container_name), замінити лише аргумент у build / up -d. Командою docker compose ... ps це одразу видно.
Швидка валідація після синку (на NODA1):
docker ps --filter name=render-pdf-worker --format 'table {{.Names}}\t{{.Status}}'
docker logs --tail 120 render-pdf-worker-node1 | egrep -i 'idle, no messages|subscribed|error|exception' || true
Acceptance checks (ловлять регрес одразу)
# Memory/Qdrant (регрес на DOCKER-USER або мережу)
curl -s -o /dev/null -w "memory /health HTTP=%{http_code}\n" http://127.0.0.1:8000/health
docker exec dagi-memory-service-node1 sh -lc 'curl -s -o /dev/null -w "qdrant-from-memory HTTP=%{http_code}\n" --connect-timeout 5 http://dagi-qdrant-node1:6333/healthz' 2>/dev/null || true
iptables -L DOCKER-USER -n --line-numbers | head -25
# NATS: відрізнити "немає підключення" від "немає задач" (якщо воркер Exited — перевірка з іншого контейнера)
# Якщо в контейнері немає nc — альтернативи: getent hosts nats; curl -v --connect-timeout 2 telnet://nats:4222; python -c "import socket; s=socket.socket(); s.settimeout(2); s.connect(('nats',4222)); s.close(); print('TCP OK')"
docker exec render-pdf-worker-node1 sh -lc 'getent hosts nats && nc -vz -w 2 nats 4222' 2>/dev/null || echo "worker cannot reach nats"
# render-pdf-worker: якір — підтвердження що піднялась версія з idle-heartbeat
docker logs --tail 200 render-pdf-worker-node1 | egrep -i 'idle, no messages' && echo "worker heartbeat OK" || echo "heartbeat not seen (yet)"
# postgres-backup: mismatch зник (конкретний сигнал); якщо 10–15 хв лишається "starting" — зібрати Health.Log і логи
docker inspect postgres-backup-node1 --format '{{json .State.Health}}' | jq .
docker inspect postgres-backup-node1 --format '{{range .State.Health.Log}}{{println .End " exit=" .ExitCode " " .Output}}{{end}}'
docker logs --tail 200 postgres-backup-node1
docker logs --tail 200 postgres-backup-node1 | egrep -i 'pg_dump.*version|mismatch' && echo "STILL MISMATCH" || echo "mismatch not seen in tail"
docker inspect postgres-backup-node1 --format '{{.State.Health.Status}}'
# Proxy ownership: nginx ок, caddy off
systemctl is-active nginx
systemctl is-enabled caddy 2>/dev/null || true
ss -ltnp | egrep ':(80|443)\s'
6) Контрольний список приймання після патчів
docker compose -f docker-compose.node1.yml configбез помилок.docker compose -f docker-compose.backups.yml configбез помилок.docker compose up -d(відповідно до ваших файлів) без помилок.- postgres-backup: health 200/healthy або лог без "version mismatch"; успішний pg_dump.
- render-pdf-worker: не падає з NATS timeout; або стабільно працює як long-running worker.
- Проксі: або nginx, або Caddy стабільно тримає 80/443; інший вимкнений (disable).
Останнє оновлення: 2026-01-28
Джерело: зібрані фрагменти з NODA1 (read-only), effective config з docker-compose.node1.yml та docker-compose.backups.yml.