FIX - PLATFORM DEPLOY: capture Synology real-domain baseline

This commit is contained in:
Codex 2026-05-14 14:27:56 +03:00
parent 61d373f076
commit 932b9bc7ec
7 changed files with 447 additions and 5 deletions

View File

@ -41,7 +41,9 @@ APP_SPECS = [
"redirect_uri_env": "LAUNCHER_OIDC_REDIRECT_URI", "redirect_uri_env": "LAUNCHER_OIDC_REDIRECT_URI",
"logged_out_redirect_uri_env": "LAUNCHER_OIDC_LOGGED_OUT_REDIRECT_URI", "logged_out_redirect_uri_env": "LAUNCHER_OIDC_LOGGED_OUT_REDIRECT_URI",
"default_logged_out_redirect_uri": "http://launcher.local.nodedc/auth/logged-out", "default_logged_out_redirect_uri": "http://launcher.local.nodedc/auth/logged-out",
"launch_url_env": "LAUNCHER_BASE_URL",
"launch_url": "http://launcher.local.nodedc", "launch_url": "http://launcher.local.nodedc",
"logout_uri_env": "LAUNCHER_LOGOUT_URI",
"logout_uri": "http://launcher.local.nodedc/logout", "logout_uri": "http://launcher.local.nodedc/logout",
"groups": ["nodedc:superadmin", "nodedc:launcher:admin", "nodedc:launcher:user"], "groups": ["nodedc:superadmin", "nodedc:launcher:admin", "nodedc:launcher:user"],
"description": "NODE.DC control plane launcher.", "description": "NODE.DC control plane launcher.",
@ -53,7 +55,9 @@ APP_SPECS = [
"client_id_env": "PLANE_OIDC_CLIENT_ID", "client_id_env": "PLANE_OIDC_CLIENT_ID",
"client_secret_env": "PLANE_OIDC_CLIENT_SECRET", "client_secret_env": "PLANE_OIDC_CLIENT_SECRET",
"redirect_uri_env": "PLANE_OIDC_REDIRECT_URI", "redirect_uri_env": "PLANE_OIDC_REDIRECT_URI",
"launch_url_env": "TASK_BASE_URL",
"launch_url": "http://task.local.nodedc", "launch_url": "http://task.local.nodedc",
"logout_uri_env": "TASK_LOGOUT_URI",
"logout_uri": "http://task.local.nodedc/logout", "logout_uri": "http://task.local.nodedc/logout",
"groups": ["nodedc:superadmin", "nodedc:taskmanager:admin", "nodedc:taskmanager:user"], "groups": ["nodedc:superadmin", "nodedc:taskmanager:admin", "nodedc:taskmanager:user"],
"description": "NODE.DC Plane-based task manager.", "description": "NODE.DC Plane-based task manager.",
@ -260,7 +264,7 @@ def ensure_provider(spec, mappings):
RedirectURI(RedirectURIMatchingMode.STRICT, redirect_uri) RedirectURI(RedirectURIMatchingMode.STRICT, redirect_uri)
for redirect_uri in dict.fromkeys(redirect_uri_values) for redirect_uri in dict.fromkeys(redirect_uri_values)
] ]
provider.logout_uri = spec["logout_uri"] provider.logout_uri = optional_env(spec.get("logout_uri_env", ""), spec["logout_uri"])
provider.logout_method = OAuth2LogoutMethod.FRONTCHANNEL provider.logout_method = OAuth2LogoutMethod.FRONTCHANNEL
provider.include_claims_in_id_token = True provider.include_claims_in_id_token = True
provider.sub_mode = SubModes.USER_UUID provider.sub_mode = SubModes.USER_UUID
@ -282,7 +286,7 @@ def ensure_application(spec, provider, groups):
application.slug = spec["slug"] application.slug = spec["slug"]
application.group = "NODE.DC" application.group = "NODE.DC"
application.provider = provider application.provider = provider
application.meta_launch_url = spec["launch_url"] application.meta_launch_url = optional_env(spec.get("launch_url_env", ""), spec["launch_url"])
application.meta_description = spec["description"] application.meta_description = spec["description"]
application.meta_publisher = "NODE.DC" application.meta_publisher = "NODE.DC"
application.open_in_new_tab = False application.open_in_new_tab = False

View File

@ -266,7 +266,19 @@
function getLauncherBaseUrl() { function getLauncherBaseUrl() {
const hostname = window.location.hostname; const hostname = window.location.hostname;
const launcherHostname = hostname.startsWith("auth.") ? `launcher.${hostname.slice(5)}` : "launcher.local.nodedc"; const launcherHostnames = {
"id.nodedc.ru": "hub.nodedc.ru",
"auth.nodedc.ru": "hub.nodedc.ru",
"id.notdc.ru": "launcher.notdc.ru",
"auth.notdc.ru": "launcher.notdc.ru",
};
const launcherHostname =
launcherHostnames[hostname] ||
(hostname.startsWith("auth.")
? `launcher.${hostname.slice(5)}`
: hostname.startsWith("id.")
? `hub.${hostname.slice(3)}`
: "hub.nodedc.ru");
const port = window.location.port ? `:${window.location.port}` : ""; const port = window.location.port ? `:${window.location.port}` : "";
return `${window.location.protocol}//${launcherHostname}${port}/`; return `${window.location.protocol}//${launcherHostname}${port}/`;
@ -691,8 +703,8 @@
try { try {
const url = new URL(rawUrl); const url = new URL(rawUrl);
const allowedHosts = new Set([ const allowedHosts = new Set([
"launcher.local.nodedc", "hub.nodedc.ru",
"launcher.local.notdc", "hub.notdc.ru",
"launcher.notdc.ru", "launcher.notdc.ru",
"platform.notdc.ru", "platform.notdc.ru",
"notdc.ru", "notdc.ru",

View File

@ -0,0 +1,46 @@
# Parallel Synology HTTP deployment.
# This intentionally uses high ports and does not touch existing nodedc-demo.
AUTH_DOMAIN=auth.nas.nodedc
LAUNCHER_DOMAIN=launcher.nas.nodedc
TASK_DOMAIN=task.nas.nodedc
NODEDC_PUBLIC_HTTP_PORT=18080
PLATFORM_HTTP_PORT=18080
SYNOLOGY_TASK_MANAGER_UPSTREAM=host.docker.internal:18090
AUTHENTIK_IMAGE=ghcr.io/goauthentik/server
AUTHENTIK_TAG=2026.2.2
AUTHENTIK_ERROR_REPORTING__ENABLED=false
AUTHENTIK_LISTEN__TRUSTED_PROXY_CIDRS=127.0.0.0/8,172.16.0.0/12
PG_DB=authentik
PG_USER=authentik
PG_PASS=replace-with-random-synology-secret
AUTHENTIK_SECRET_KEY=replace-with-random-synology-secret
AUTHENTIK_BOOTSTRAP_EMAIL=admin@nodedc.local
AUTHENTIK_BOOTSTRAP_PASSWORD=replace-with-random-synology-secret
AUTHENTIK_BOOTSTRAP_TOKEN=replace-with-random-synology-secret
LAUNCHER_BASE_URL=https://hub.nodedc.ru
TASK_BASE_URL=https://ops.nodedc.ru
TASK_LOGOUT_URI=https://ops.nodedc.ru/logout
TASK_INTERNAL_LOGOUT_URL=https://ops.nodedc.ru/api/internal/nodedc/logout/
LAUNCHER_OIDC_ISSUER=https://id.nodedc.ru/application/o/launcher/
LAUNCHER_OIDC_CLIENT_ID=nodedc-launcher
LAUNCHER_OIDC_CLIENT_SECRET=replace-with-random-synology-secret
LAUNCHER_OIDC_REDIRECT_URI=https://hub.nodedc.ru/auth/callback
LAUNCHER_OIDC_LOGGED_OUT_REDIRECT_URI=https://hub.nodedc.ru/auth/logged-out
LAUNCHER_LOGOUT_URI=https://hub.nodedc.ru/logout
LAUNCHER_COOKIE_DOMAIN=.nodedc.ru
PLANE_OIDC_ISSUER=https://id.nodedc.ru/application/o/task-manager/
PLANE_OIDC_CLIENT_ID=nodedc-task-manager
PLANE_OIDC_CLIENT_SECRET=replace-with-random-synology-secret
PLANE_OIDC_REDIRECT_URI=https://ops.nodedc.ru/auth/oidc/callback
NODEDC_AUTHENTIK_BASE_URL=http://authentik-server:9000
NODEDC_INTERNAL_ACCESS_TOKEN=replace-with-random-synology-secret
SESSION_SECRET=replace-with-random-synology-secret
COOKIE_DOMAIN=.nas.nodedc
COOKIE_SECURE=false

View File

@ -0,0 +1,71 @@
{
auto_https off
}
http://{$AUTH_DOMAIN} {
@auth_root path /
redir @auth_root http://{$LAUNCHER_DOMAIN}:{$NODEDC_PUBLIC_HTTP_PORT}/ 302
@auth_user_dashboard path /if/user /if/user/*
redir @auth_user_dashboard http://{$LAUNCHER_DOMAIN}:{$NODEDC_PUBLIC_HTTP_PORT}/ 302
reverse_proxy authentik-server:9000 {
header_up Host {http.request.host}
header_up X-Forwarded-Proto {scheme}
header_up X-Forwarded-For {remote_host}
}
}
http://{$LAUNCHER_DOMAIN} {
reverse_proxy launcher:5173 {
header_up Host {http.request.host}
header_up X-Forwarded-Proto {scheme}
header_up X-Forwarded-For {remote_host}
}
}
http://{$TASK_DOMAIN} {
reverse_proxy {$SYNOLOGY_TASK_MANAGER_UPSTREAM} {
header_up Host {http.request.host}
header_up X-Forwarded-Host {http.request.host}
header_up X-Forwarded-Proto {scheme}
header_up X-Forwarded-Port {$NODEDC_PUBLIC_HTTP_PORT}
header_up X-Forwarded-For {remote_host}
}
}
http://id.nodedc.ru {
@auth_root path /
redir @auth_root https://hub.nodedc.ru/ 302
@auth_user_dashboard path /if/user /if/user/*
redir @auth_user_dashboard https://hub.nodedc.ru/ 302
reverse_proxy authentik-server:9000 {
header_up Host id.nodedc.ru
header_up X-Forwarded-Host id.nodedc.ru
header_up X-Forwarded-Proto https
header_up X-Forwarded-Port 443
header_up X-Forwarded-For {remote_host}
}
}
http://hub.nodedc.ru {
reverse_proxy launcher:5173 {
header_up Host hub.nodedc.ru
header_up X-Forwarded-Host hub.nodedc.ru
header_up X-Forwarded-Proto https
header_up X-Forwarded-Port 443
header_up X-Forwarded-For {remote_host}
}
}
http://ops.nodedc.ru {
reverse_proxy {$SYNOLOGY_TASK_MANAGER_UPSTREAM} {
header_up Host ops.nodedc.ru
header_up X-Forwarded-Host ops.nodedc.ru
header_up X-Forwarded-Proto https
header_up X-Forwarded-Port 443
header_up X-Forwarded-For {remote_host}
}
}

100
infra/synology/README.md Normal file
View File

@ -0,0 +1,100 @@
# 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`, Tasker upstream — `18090`.
- Старые порты `9000` и `5678` заняты старым `nodedc-demo` и не используются.
## Текущие внешние домены
```text
https://id.nodedc.ru -> Authentik
https://hub.nodedc.ru -> Launcher / Hub
https://ops.nodedc.ru -> Tasker / Operational Core
```
В `Caddyfile.http` эти домены проксируются через локальный HTTP edge, но upstream получает `X-Forwarded-Proto: https` и `X-Forwarded-Port: 443`.
## Локальные домены для первичной проверки
На Mac для первичной проверки добавить в `/etc/hosts`:
```text
172.22.0.222 auth.nas.nodedc
172.22.0.222 launcher.nas.nodedc
172.22.0.222 task.nas.nodedc
```
Первичные URL:
```text
http://auth.nas.nodedc:18080
http://launcher.nas.nodedc:18080
http://task.nas.nodedc:18080
http://task.nas.nodedc:18090
```
## Что входит
- `docker-compose.platform-http.yml` поднимает новый Authentik, Launcher и Caddy edge.
- `Caddyfile.http` маршрутизирует локальные `auth/launcher/task.nas.nodedc` и внешние `id/hub/ops.nodedc.ru`.
- `deploy-current.sh` синхронизирует compose, Caddyfile, Authentik templates и опционально Launcher source в NAS mount.
- Tasker поднимается отдельным compose из `NODEDC_TASKMANAGER/plane-app/docker-compose.yaml` на порту `18090`.
## Синхронизация текущего состояния
С 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 \
./infra/synology/deploy-current.sh
```
Скрипт не запускает Docker сам: на NAS `sudo` интерактивный, поэтому команды применения печатаются в конце.
## Что нужно перед запуском
- Собрать или загрузить `linux/amd64` images:
- `nodedc/launcher:local`
- `nodedc/plane-frontend:ru`
- `nodedc/plane-admin:ru`
- `nodedc/plane-space:ru`
- `nodedc/plane-live:local`
- `nodedc/plane-backend:local`
- `nodedc/plane-proxy:ru`
- Создать `.env.synology` из `.env.synology.example` и заменить все `replace-with-*`.
- Создать `plane.env.synology` для Tasker из `plane.env.staging.example`, но с HTTP URL на `*.nas.nodedc:18080` и портами `18090/18490`.
## Обязательные runtime-права
Launcher пишет runtime snapshot и uploads под пользователем `node` (`uid=1000`). После создания NAS-папок:
```bash
cd /volume1/docker/nodedc-platform/platform
sudo mkdir -p ../launcher/server-storage ../launcher/uploads
sudo chown -R 1000:1000 ../launcher/server-storage ../launcher/uploads
sudo chmod -R u+rwX,g+rwX ../launcher/server-storage ../launcher/uploads
```
Проверка внутри контейнера:
```bash
sudo /usr/local/bin/docker exec nodedc-platform-launcher-1 sh -lc \
'touch /app/server/storage/.write-test && rm /app/server/storage/.write-test && echo storage-ok'
```
## Проверки после деплоя
```bash
curl -k -sS --compressed https://id.nodedc.ru/if/flow/default-authentication-flow/ \
| grep -aE 'hub.nodedc.ru|launcher.local|getLauncherBaseUrl|Запросить доступ'
```
В выводе должны быть `id.nodedc.ru -> hub.nodedc.ru` и не должно быть `launcher.local`.

View File

@ -0,0 +1,73 @@
#!/usr/bin/env bash
set -euo pipefail
SCRIPT_DIR="$(cd -- "$(dirname -- "${BASH_SOURCE[0]}")" && pwd)"
PLATFORM_REPO="$(cd -- "${SCRIPT_DIR}/../.." && pwd)"
NAS_ROOT="${NAS_ROOT:-/Volumes/docker/nodedc-platform}"
LAUNCHER_REPO="${LAUNCHER_REPO:-}"
if [[ ! -d "${NAS_ROOT}" ]]; then
echo "NAS_ROOT not found: ${NAS_ROOT}" >&2
echo "Set NAS_ROOT=/path/to/nodedc-platform when the Synology share is mounted elsewhere." >&2
exit 1
fi
mkdir -p "${NAS_ROOT}/platform" "${NAS_ROOT}/authentik/custom-templates"
rsync -av \
"${PLATFORM_REPO}/infra/synology/docker-compose.platform-http.yml" \
"${NAS_ROOT}/platform/docker-compose.platform-http.yml"
rsync -av \
"${PLATFORM_REPO}/infra/synology/Caddyfile.http" \
"${NAS_ROOT}/platform/Caddyfile.http"
rsync -av --delete \
"${PLATFORM_REPO}/infra/authentik/custom-templates/" \
"${NAS_ROOT}/authentik/custom-templates/"
if [[ -n "${LAUNCHER_REPO}" ]]; then
if [[ ! -d "${LAUNCHER_REPO}" ]]; then
echo "LAUNCHER_REPO not found: ${LAUNCHER_REPO}" >&2
exit 1
fi
mkdir -p "${NAS_ROOT}/launcher/source"
rsync -av --delete \
--exclude='.git/' \
--exclude='node_modules/' \
--exclude='dist/' \
--exclude='server/storage/*' \
--exclude='public/storage/uploads/*' \
"${LAUNCHER_REPO}/" \
"${NAS_ROOT}/launcher/source/"
else
echo "LAUNCHER_REPO is not set; launcher source was not synced."
fi
cat <<'EOF'
Synced files to NAS mount. Run on Synology to apply runtime changes:
cd /volume1/docker/nodedc-platform/platform
sudo mkdir -p ../launcher/server-storage ../launcher/uploads
sudo chown -R 1000:1000 ../launcher/server-storage ../launcher/uploads
sudo chmod -R u+rwX,g+rwX ../launcher/server-storage ../launcher/uploads
cd /volume1/docker/nodedc-platform/launcher/source
sudo /usr/local/bin/docker build -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 launcher reverse-proxy authentik-server authentik-worker
Verify:
sudo /usr/local/bin/docker exec nodedc-platform-launcher-1 sh -lc \
'touch /app/server/storage/.write-test && rm /app/server/storage/.write-test && echo storage-ok'
curl -k -sS --compressed https://id.nodedc.ru/if/flow/default-authentication-flow/ \
| grep -aE 'hub.nodedc.ru|launcher.local|getLauncherBaseUrl|Запросить доступ'
EOF

View File

@ -0,0 +1,136 @@
name: nodedc-platform
services:
reverse-proxy:
image: caddy:2-alpine
restart: unless-stopped
env_file:
- ${NODEDC_SYNOLOGY_ENV_FILE:-.env.synology}
ports:
- "${PLATFORM_HTTP_PORT:-18080}:80"
volumes:
- ./Caddyfile.http:/etc/caddy/Caddyfile:ro
- caddy-data:/data
- caddy-config:/config
depends_on:
authentik-server:
condition: service_started
launcher:
condition: service_started
extra_hosts:
- "id.nodedc.ru:host-gateway"
- "hub.nodedc.ru:host-gateway"
- "ops.nodedc.ru:host-gateway"
- "host.docker.internal:host-gateway"
networks:
- edge
- identity
launcher:
image: nodedc/launcher:local
restart: unless-stopped
env_file:
- ${NODEDC_SYNOLOGY_ENV_FILE:-.env.synology}
environment:
NODE_ENV: production
PORT: 5173
NODEDC_LAUNCHER_STORAGE_DIR: /app/server/storage
expose:
- "5173"
volumes:
- ../launcher/server-storage:/app/server/storage
- ../launcher/uploads:/app/dist/storage/uploads
- ../launcher/uploads:/app/public/storage/uploads
extra_hosts:
- "id.nodedc.ru:host-gateway"
- "hub.nodedc.ru:host-gateway"
- "ops.nodedc.ru:host-gateway"
- "${AUTH_DOMAIN:-auth.nas.nodedc}:host-gateway"
- "${LAUNCHER_DOMAIN:-launcher.nas.nodedc}:host-gateway"
- "${TASK_DOMAIN:-task.nas.nodedc}:host-gateway"
networks:
- edge
- identity
postgresql-authentik:
image: postgres:16-alpine
restart: unless-stopped
env_file:
- ${NODEDC_SYNOLOGY_ENV_FILE:-.env.synology}
environment:
POSTGRES_DB: ${PG_DB:-authentik}
POSTGRES_PASSWORD: ${PG_PASS:?database password required}
POSTGRES_USER: ${PG_USER:-authentik}
healthcheck:
test: ["CMD-SHELL", "pg_isready -d $${POSTGRES_DB} -U $${POSTGRES_USER}"]
interval: 30s
timeout: 5s
retries: 5
start_period: 20s
volumes:
- authentik-database:/var/lib/postgresql/data
networks:
- identity
authentik-server:
image: ${AUTHENTIK_IMAGE:-ghcr.io/goauthentik/server}:${AUTHENTIK_TAG:-2026.2.2}
command: server
restart: unless-stopped
env_file:
- ${NODEDC_SYNOLOGY_ENV_FILE:-.env.synology}
environment:
AUTHENTIK_POSTGRESQL__HOST: postgresql-authentik
AUTHENTIK_POSTGRESQL__NAME: ${PG_DB:-authentik}
AUTHENTIK_POSTGRESQL__PASSWORD: ${PG_PASS:?database password required}
AUTHENTIK_POSTGRESQL__USER: ${PG_USER:-authentik}
AUTHENTIK_SECRET_KEY: ${AUTHENTIK_SECRET_KEY:?secret key required}
AUTHENTIK_ERROR_REPORTING__ENABLED: ${AUTHENTIK_ERROR_REPORTING__ENABLED:-false}
AUTHENTIK_LISTEN__TRUSTED_PROXY_CIDRS: ${AUTHENTIK_LISTEN__TRUSTED_PROXY_CIDRS:-127.0.0.0/8,172.16.0.0/12}
depends_on:
postgresql-authentik:
condition: service_healthy
expose:
- "9000"
- "9443"
shm_size: 512mb
volumes:
- authentik-data:/data
- ../authentik/custom-templates:/templates:ro
networks:
- identity
authentik-worker:
image: ${AUTHENTIK_IMAGE:-ghcr.io/goauthentik/server}:${AUTHENTIK_TAG:-2026.2.2}
command: worker
restart: unless-stopped
env_file:
- ${NODEDC_SYNOLOGY_ENV_FILE:-.env.synology}
environment:
AUTHENTIK_POSTGRESQL__HOST: postgresql-authentik
AUTHENTIK_POSTGRESQL__NAME: ${PG_DB:-authentik}
AUTHENTIK_POSTGRESQL__PASSWORD: ${PG_PASS:?database password required}
AUTHENTIK_POSTGRESQL__USER: ${PG_USER:-authentik}
AUTHENTIK_SECRET_KEY: ${AUTHENTIK_SECRET_KEY:?secret key required}
AUTHENTIK_ERROR_REPORTING__ENABLED: ${AUTHENTIK_ERROR_REPORTING__ENABLED:-false}
depends_on:
postgresql-authentik:
condition: service_healthy
shm_size: 512mb
volumes:
- authentik-data:/data
- authentik-certs:/certs
- ../authentik/custom-templates:/templates:ro
networks:
- identity
networks:
edge:
identity:
internal: true
volumes:
authentik-database:
authentik-data:
authentik-certs:
caddy-data:
caddy-config: