Files
microdao-daarion/docs/NODA1-TECHBORGS-PATCHES.md
Apple ef3473db21 snapshot: NODE1 production state 2026-02-09
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>
2026-02-09 08:46:46 -08:00

19 KiB
Raw Blame History

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 зник (конкретний сигнал); якщо 1015 хв лишається "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.