# NODE.DC Synology deploy Эта папка фиксирует текущий воспроизводимый NAS-deploy для `nodedc-platform` на Synology RS1221RP+. ## Правила - Не выполнять `docker stop`, `docker restart`, `docker compose down`, `docker system prune` для старых проектов. - Новый compose project: `nodedc-platform`. - Новая папка на NAS: `/volume1/docker/nodedc-platform`. - Внутренний HTTP edge использует `18080`, AI Workspace Hub — `18081`, Tasker upstream — `18090`, BIM Viewer upstream — `18100`, Ops Agents Gateway upstream — `18190`. - Старые порты `9000` и `5678` заняты старым `nodedc-demo` и не используются. ## Текущие внешние домены ```text https://id.nodedc.ru -> Authentik https://hub.nodedc.ru -> Launcher / Hub https://ops.nodedc.ru -> Tasker / Operational Core https://bim.nodedc.tech -> BIM Viewer https://ops-agents.nodedc.ru -> Ops Agents Gateway / MCP https://ai-hub.nodedc.ru -> AI Workspace Hub / WebSocket relay ``` `id.nodedc.ru` is the user-facing OIDC/login host. Authentik Admin is intentionally not exposed through this public host; `/if/admin/*` returns `404` there. `ai-hub.nodedc.ru` is intentionally routed by Synology DSM Reverse Proxy directly to `172.22.0.222:18081`, because this endpoint must carry WebSocket Upgrade traffic for remote Codex agents. В `Caddyfile.http` эти домены проксируются через локальный HTTP edge, но upstream получает `X-Forwarded-Proto: https` и `X-Forwarded-Port: 443`. `bim.nodedc.tech` can either be routed by DSM directly to the BIM host port `172.22.0.222:18100`, or through the platform HTTP edge `172.22.0.222:18080`. When it goes through the platform edge, `BIM_DOMAIN=bim.nodedc.tech` and `SYNOLOGY_BIM_VIEWER_UPSTREAM=host.docker.internal:18100` keep the same target service. ## AI Workspace Hub / Assistant contract `ai-workspace-hub` remains a thin public WebSocket relay. It is the only AI Workspace service exposed through DSM/Nginx as `https://ai-hub.nodedc.ru`. `ai-workspace-assistant` is an internal platform service for per-user executors, selected device, shared threads and installer generation. It must not be exposed as a public route. Launcher, Engine and Tasker use it through Docker networking: ```env NODEDC_AI_WORKSPACE_ASSISTANT_URL=http://ai-workspace-assistant:18082 PLANE_NODEDC_AI_WORKSPACE_ASSISTANT_URL=http://ai-workspace-assistant:18082 ``` Installer generation must always point remote Codex workers to the deployed Hub: ```env AI_WORKSPACE_HUB_PUBLIC_URL=wss://ai-hub.nodedc.ru/api/ai-workspace/hub AI_WORKSPACE_HUB_INTERNAL_URL=https://ai-hub.nodedc.ru AI_WORKSPACE_HUB_FALLBACK_URLS= ``` Server-side Hub API calls, including executor status checks, use `AI_WORKSPACE_HUB_INTERNAL_URL` and require a token accepted by Hub: `AI_WORKSPACE_HUB_TOKEN`, `NDC_AI_WORKSPACE_HUB_TOKEN`, or the shared `NODEDC_INTERNAL_ACCESS_TOKEN` where that token is intentionally common across platform services. AI Workspace Assistant also calls product entitlement adapters before every Codex run. Ops uses the Agent Gateway internal endpoint; the token must match `NODEDC_AGENT_GATEWAY_INTERNAL_TOKEN` from the Ops Agent Gateway deployment: ```env AI_WORKSPACE_OPS_ENTITLEMENT_URL=http://172.22.0.222:18190/api/internal/v1/ai-workspace/entitlements AI_WORKSPACE_OPS_ENTITLEMENT_TOKEN= AI_WORKSPACE_OPS_ENTITLEMENT_REQUIRED=false ``` ## Локальные домены для первичной проверки На Mac для первичной проверки добавить в `/etc/hosts`: ```text 172.22.0.222 auth.nas.nodedc 172.22.0.222 auth-admin.nas.nodedc 172.22.0.222 launcher.nas.nodedc 172.22.0.222 task.nas.nodedc ``` Первичные URL: ```text http://auth.nas.nodedc:18080 http://auth-admin.nas.nodedc:18080/if/admin/ http://172.22.0.222:18080/if/admin/ http://launcher.nas.nodedc:18080 http://task.nas.nodedc:18080 http://task.nas.nodedc:18090 ``` `auth-admin.nas.nodedc` is the technical Authentik Admin entrypoint. `172.22.0.222:18080` is a local IP fallback for workstations without `auth-admin.nas.nodedc` DNS. Both keep Authentik access separate from the public `id.nodedc.ru` login entrypoint and do not load NODE.DC auth-flow CSS. ## Что входит - `docker-compose.platform-http.yml` поднимает новый Authentik, Launcher, Notification Core, AI Workspace Hub и Caddy edge. - `Caddyfile.http` маршрутизирует локальные `auth/auth-admin/launcher/task.nas.nodedc`, IP fallback `172.22.0.222` для Authentik Admin и внешние `id/hub/ops.nodedc.ru`. - `deploy-current.sh` синхронизирует compose, Caddyfile, Notification Core source, AI Workspace Hub source и опционально Launcher/BIM source в NAS mount. Authentik templates синхронизируются только при явном `SYNC_AUTHENTIK_TEMPLATES=1`. - `backup-current.sh` делает snapshot Launcher runtime/uploads/Auth templates/config, BIM Viewer `server/data` и готовит команду `pg_dump` для Authentik Postgres. - Tasker поднимается отдельным compose из `NODEDC_TASKMANAGER/plane-app/docker-compose.yaml` на порту `18090`. - Ops Agents Gateway поднимается отдельным compose из `NODEDC_TASKMANAGER_CODEXAPI/docker-compose.synology.yml` на `172.22.0.222:18190`; Synology reverse proxy должен вести `ops-agents.nodedc.ru` на этот порт, а не на `18090`. ## Внутренний Authentik API для Launcher Launcher подключен и к platform identity-сети, и к engine-сети. В engine-сети тоже может быть сервис с DNS-именем `authentik-server`, поэтому это имя нельзя использовать для `NODEDC_AUTHENTIK_BASE_URL`: Docker DNS может отдать не тот Authentik, и platform API-token будет получать `403 Token invalid/expired`. Для platform Authentik зафиксирован отдельный alias: ```text http://nodedc-platform-authentik-server:9000 ``` Live `/volume1/docker/nodedc-platform/platform/.env.synology` должен содержать: ```env NODEDC_AUTHENTIK_BASE_URL=http://nodedc-platform-authentik-server:9000 AUTHENTIK_BASE_URL=http://nodedc-platform-authentik-server:9000 NODEDC_AUTHENTIK_SERVICE_TOKEN= AUTHENTIK_SERVICE_TOKEN= ``` Быстрая проверка из launcher-контейнера: ```bash sudo /usr/local/bin/docker exec nodedc-platform-launcher-1 sh -lc ' echo "$NODEDC_AUTHENTIK_BASE_URL" getent hosts nodedc-platform-authentik-server for attempt in 1 2 3 4 5 6 7 8 9 10; do wget -qSO- \ --header "Authorization: Bearer $NODEDC_AUTHENTIK_SERVICE_TOKEN" \ "$NODEDC_AUTHENTIK_BASE_URL/api/v3/core/groups/?search=nodedc_admin" \ 2>&1 | head -n 25 && exit 0 echo "authentik-api-not-ready attempt=$attempt" sleep 10 done exit 1 ' ``` Ожидаемый результат: `HTTP/1.1 200 OK`. После изменения platform Authentik alias пересоздавать нужно `authentik-server`, `authentik-worker` и `launcher`. Один `authentik-server` может временно отдавать `503 Service Unavailable`, пока worker и bootstrap не готовы. ## Синхронизация текущего состояния С Mac, при смонтированном `/Volumes/docker`: ```bash cd /Users/dcconstructions/Downloads/mnt/NODEDC/platform NAS_ROOT=/Volumes/docker/nodedc-platform \ LAUNCHER_REPO=/Users/dcconstructions/Downloads/mnt/data/nodedc_launcher \ BIM_REPO=/Users/dcconstructions/Downloads/mnt/NODEDC/NODEDC_BIM_VIEWER \ TASKER_REPO=/Users/dcconstructions/Downloads/mnt/data/dc_taskmanager/NODEDC_TASKMANAGER \ TASKER_CHANGED_BASE=533f8c6 \ GATEWAY_REPO=/Users/dcconstructions/Downloads/mnt/data/NODEDC_TASKMANAGER_CODEXAPI \ ./infra/synology/deploy-current.sh ``` Скрипт не запускает Docker сам: на NAS `sudo` интерактивный, поэтому команды применения печатаются в конце. По умолчанию sync source-копий на SMB/NAS не сохраняет owner/group/perms/times. Для Docker build важен контент, а macOS/Synology metadata может падать на `utimensat Operation timed out`. Если metadata действительно нужно сохранить для отдельного ручного случая, запускать с `RSYNC_PRESERVE_METADATA=1`. Что синхронизируется: - Platform compose/Caddy. - Notification Core source в `/volume1/docker/nodedc-platform/platform/notification-core`. - AI Workspace Hub source в `/volume1/docker/nodedc-platform/platform/ai-workspace-hub`. - Authentik templates только при `SYNC_AUTHENTIK_TEMPLATES=1`; по умолчанию они не трогаются, чтобы лёгкий Hub deploy не уносил экспериментальную тему/брендинг в prod. - Launcher source в `/volume1/docker/nodedc-platform/launcher/source`. - BIM Viewer source в `/volume1/docker/nodedc-platform/bim-viewer/source`; `server/data` не синхронизируется и остаётся live runtime-хранилищем моделей, shares, comments и refs. - Tasker `plane-app/docker-compose.yaml` и, если задан `TASKER_CHANGED_BASE`, только изменённые source-файлы из диапазона `TASKER_CHANGED_BASE..HEAD`. - Ops Agents Gateway source в `/volume1/docker/nodedc-platform/ops-agents`. Полный sync Tasker source по SMB тяжёлый для Plane fork. Использовать его только осознанно: ```bash TASKER_SYNC_SOURCE=1 ./infra/synology/deploy-current.sh ``` Секретные runtime env-файлы не перетираются: - `/volume1/docker/nodedc-platform/platform/.env.synology` - `/volume1/docker/nodedc-platform/bim-viewer/source/.env` - `/volume1/docker/nodedc-platform/tasker/plane-app/.env.synology` - `/volume1/docker/nodedc-platform/ops-agents/.env` Если emergency-fix был сделан прямо на Synology в этих env-файлах, перенести sanitized-значение в `.env.synology.example`/docs, а секрет оставить только в live env. ## AI Hub relay-only deploy Для локального тестирования с внешней Codex-машиной нельзя деплоить Launcher, Engine, Ops, Authentik или Tasker. Единственная допустимая удалённая точка в этой схеме — `ai-workspace-hub`, потому что удалённый worker должен иметь публичный WebSocket/HTTP relay. С Mac, при смонтированном `/Volumes/docker`, синхронизировать только Hub source, compose и узкий apply-script: ```bash cd /Users/dcconstructions/Downloads/mnt/NODEDC/platform NAS_ROOT=/Volumes/docker/nodedc-platform ./infra/synology/sync-ai-hub-relay.sh ``` На Synology применить только `ai-workspace-hub`: ```bash ssh dctouch@100.109.216.21 'cd /volume1/docker/nodedc-platform/platform && sudo bash apply-ai-hub-relay.sh' ``` Этот путь намеренно делает только: - build `nodedc/ai-workspace-hub:local`; - `docker compose up -d --no-deps --force-recreate ai-workspace-hub`; - container health check; - проверку `/api/ai-workspace/hub/v1/assistant-relays//poll`; - public route check через `https://ai-hub.nodedc.ru`. Он не меняет `.env.synology`, не синхронизирует Launcher/Tasker/Ops source, не пересоздаёт Authentik, reverse-proxy, базы, storage или product containers. После успешного relay-only deploy локальная цепочка для теста должна быть такой: ```text local Launcher/Engine/Ops/Auth/Assistant -> deployed AI Hub relay: https://ai-hub.nodedc.ru -> remote Codex worker -> deployed AI Hub relay -> local Assistant relay poll -> local product services ``` ## Лёгкое обновление Hub / Launcher Для правок только в Launcher/BFF не пересоздавать Authentik, reverse-proxy, Tasker и Ops Agents: ```bash cd /volume1/docker/nodedc-platform/launcher/source sudo /usr/local/bin/docker build --no-cache -t nodedc/launcher:local . cd /volume1/docker/nodedc-platform/platform sudo /usr/local/bin/docker compose \ --env-file /volume1/docker/nodedc-platform/platform/.env.synology \ -f /volume1/docker/nodedc-platform/platform/docker-compose.platform-http.yml \ up -d --force-recreate --no-deps ai-workspace-hub launcher ``` После такого deploy проверить `healthz`, запись в launcher storage/uploads и сценарий пользователя без аппрува: сохранение аватара не должно показывать экран `Заявка ожидает подтверждения`. Дополнительно проверить, что live bundle больше не содержит старый pending gate: ```bash launcher_asset="$( curl -k -sS --compressed -H 'Accept: text/html' https://hub.nodedc.ru/ \ | grep -aoE 'index-[A-Za-z0-9_-]+\.js' \ | head -n 1 )" test -n "$launcher_asset" if curl -k -sS --compressed "https://hub.nodedc.ru/assets/${launcher_asset}" \ | grep -aq 'Заявка ожидает подтверждения'; then echo 'old pending gate still present' exit 1 fi echo 'launcher-pending-gate-ok' ``` ## BIM Viewer deploy `bim.nodedc.tech` на Synology можно вести напрямую через DSM Reverse Proxy на `172.22.0.222:18100`. В этом режиме Platform Caddy не участвует в публичном BIM-трафике, но `Caddyfile.http` всё равно содержит BIM route как запасной вариант для маршрута через `172.22.0.222:18080`. Canonical BIM deploy now uses `nodedc-deploy` with `component=bim-viewer`; do not deploy BIM by manually running `docker compose up` from the source directory as the normal path. Live BIM root: ```text /volume1/docker/nodedc-platform/bim-viewer/source ``` Live env stays outside artifacts: ```text /volume1/docker/nodedc-platform/bim-viewer/source/.env ``` Before the first canonical apply, prepare `.env` from `.env.synology.example` and verify that `NODEDC_INTERNAL_ACCESS_TOKEN` matches `platform/.env.synology`. After that, `.env` must not be included in deploy artifacts. Canonical deploy commands: ```bash sudo /usr/local/sbin/nodedc-deploy verify-install sudo /usr/local/sbin/nodedc-deploy plan /volume1/docker/nodedc-deploy/inbox/.tgz sudo /usr/local/sbin/nodedc-deploy apply /volume1/docker/nodedc-deploy/inbox/.tgz ``` The `bim-viewer` runner component: - applies payload under `/volume1/docker/nodedc-platform/bim-viewer/source`; - rejects `.env`, `server/data`, upload/model/comment/share runtime storage, `node_modules`, `.git`, shell hooks and backup files; - prepares required live runtime directories under `server/data` before compose; - builds `nodedc/bim-converter:local` from `converter/Dockerfile`; - recreates only `ndc-beam-viewer` and `nodedc-bim-converter`; - healthchecks `http://127.0.0.1:18100/api/auth/session`. Минимальная проверка после apply: ```bash curl -k -fsS http://127.0.0.1:18100/api/auth/session curl -k -sS -o /dev/null -w '%{http_code}\n' https://bim.nodedc.tech/ ``` Ожидаемо: `/api/auth/session` возвращает `authRequired: true`, а прямой public root без BIM-сессии отдаёт `302` в Launcher login/launch. Public share `/share/` должен открываться без авторизации и без toolbar. ## Tasker / OPS BIM iframe rebuild OPS web bundle получает BIM URL на build-time через `VITE_BEAM_VIEWER_BASE_URL` и `VITE_BEAM_API_BASE_URL`. После смены домена пересобрать только web image: ```bash cd /volume1/docker/nodedc-platform/tasker/plane-src VITE_BEAM_VIEWER_BASE_URL=https://bim.nodedc.tech \ VITE_BEAM_API_BASE_URL=https://bim.nodedc.tech \ BUILD_BACKEND=0 BUILD_WEB=1 BUILD_ADMIN=0 \ sh rebuild-nas-legacy.sh ``` Это пересоздаёт только `web` и не трогает PostgreSQL/MinIO volumes. ## Authentik Admin и Brand CSS NODE.DC auth-flow CSS must stay template-scoped. Do not store it in Authentik `Brand.branding_custom_css`: Authentik passes that CSS into Admin/User web component runtime and breaks native controls. After syncing Authentik templates to NAS, recreate `reverse-proxy authentik-server authentik-worker launcher`, then clear existing global Brand CSS in the live DB: ```bash cd /volume1/docker/nodedc-platform/platform sudo bash clear-authentik-brand-css.sh ``` Verify: ```bash id_admin_status="$( curl -k -sS -o /dev/null -w '%{http_code}' https://id.nodedc.ru/if/admin/ )" if [[ "$id_admin_status" != "404" ]]; then echo "public id admin is not closed: status=${id_admin_status}" exit 1 fi echo 'public-id-admin-closed-ok' auth_admin_page="$( curl -k -fsS --compressed http://auth-admin.nas.nodedc:18080/if/admin/ )" printf '%s' "$auth_admin_page" \ | grep -aE '|authentikBrand.branding_custom_css = ""' auth_admin_flow="$( curl -k -fsS --compressed http://auth-admin.nas.nodedc:18080/if/flow/default-authentication-flow/ )" if printf '%s' "$auth_admin_flow" | grep -aq '