From f7c5f222a3abc2980a41cdb8e6e716ef226b48a0 Mon Sep 17 00:00:00 2001 From: Maxim Dolgolyov Date: Fri, 19 Jun 2026 19:53:24 +0300 Subject: [PATCH] =?UTF-8?q?deploy(docker):=20self-init=20entrypoint=20(?= =?UTF-8?q?=D0=BC=D0=B8=D0=B3=D1=80=D0=B0=D1=86=D0=B8=D0=B8+=D0=B7=D0=B0?= =?UTF-8?q?=D1=81=D0=B5=D0=B2=20=D0=BF=D1=80=D0=B0=D0=B2)=20+=20=D0=B3?= =?UTF-8?q?=D0=B0=D0=B9=D0=B4=20=D0=BF=D0=BE=20TrueNAS?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - docker-entrypoint.sh: при старте node migrations-runner (идемпотентно) + seed-permissions только если role_permissions пуста → контейнер поднимается на чистом томе без ручных шагов (сервер раньше fail-fast без миграций). Dockerfile: ENTRYPOINT через tini + entrypoint, нормализация CRLF (sed) + chmod, label BQ-System → LearnSpace. - DEPLOY-TRUENAS.md: пошагово для TrueNAS SCALE (датасет → образ → Custom App compose с host-path томами и JWT_SECRET → авто-миграции → reverse-proxy/HTTPS/TURN → бэкапы), заметка про CORE. Co-Authored-By: Claude Opus 4.8 (1M context) --- DEPLOY-TRUENAS.md | 118 +++++++++++++++++++++++++++++++++++++++++++ Dockerfile | 13 +++-- docker-entrypoint.sh | 19 +++++++ 3 files changed, 145 insertions(+), 5 deletions(-) create mode 100644 DEPLOY-TRUENAS.md create mode 100644 docker-entrypoint.sh diff --git a/DEPLOY-TRUENAS.md b/DEPLOY-TRUENAS.md new file mode 100644 index 0000000..43695d1 --- /dev/null +++ b/DEPLOY-TRUENAS.md @@ -0,0 +1,118 @@ +# Запуск LearnSpace на TrueNAS + +Коротко: **TrueNAS SCALE** (Linux) запускает приложение как **Docker-контейнер** — в репозитории уже +есть `Dockerfile` и `docker-compose.yml`. **TrueNAS CORE** (FreeBSD) Docker не умеет — там нужен Linux-VM +или jail с Node (см. конец). Ниже — путь для **SCALE** (рекомендуемый). + +Контейнер **самоинициализируется**: при старте применяет миграции БД и при первом запуске засевает права +(`docker-entrypoint.sh`). Данные (SQLite-БД, загрузки, бэкапы) хранятся в томах, переживают пересоздание. + +--- + +## 1. Подготовка хранилища (dataset) + +В TrueNAS создайте датасет под приложение и в нём подпапки для постоянных данных: + +``` +Datasets → Add Dataset: tank/apps/learnspace +``` +Внутри (через Shell или по SMB) создайте три папки — БД, загрузки, бэкапы: +``` +/mnt/tank/apps/learnspace/data +/mnt/tank/apps/learnspace/uploads +/mnt/tank/apps/learnspace/backups +``` +Они монтируются в контейнер; их защищают снапшоты/репликация TrueNAS. + +## 2. Доставить образ на NAS (3 варианта) + +**A. Собрать на машине с Docker и перенести** (без реестра): +```bash +# на ПК с Docker, в корне репозитория: +docker build -t learnspace:latest . +docker save learnspace:latest | gzip > learnspace.tar.gz +# скопировать learnspace.tar.gz на NAS (SMB/scp), затем в Shell TrueNAS: +docker load < /mnt/tank/apps/learnspace/learnspace.tar.gz +``` + +**B. Через реестр** (Docker Hub / GHCR / локальный): `docker push /learnspace:latest`, +а в compose указать этот `image:`. + +**C. Собрать прямо на NAS** (SCALE ElectricEel 24.10+ имеет Docker CLI): склонировать репозиторий в +датасет и `docker compose build` (см. ниже). + +## 3. Развернуть как Custom App (Compose YAML) + +TrueNAS SCALE → **Apps → Discover Apps → Custom App → Install via YAML** (на ElectricEel+ это нативный +docker-compose). Вставьте (правьте пути пула, порт и **обязательно JWT_SECRET**): + +```yaml +services: + app: + image: learnspace:latest # из шага 2 (или build: . если собираете на NAS) + container_name: learnspace + restart: unless-stopped + ports: + - "3000:3000" # снаружи:внутри. Поменяйте левую часть при конфликте + volumes: + - /mnt/tank/apps/learnspace/data:/app/backend/data + - /mnt/tank/apps/learnspace/uploads:/app/backend/uploads + - /mnt/tank/apps/learnspace/backups:/app/backups + environment: + - NODE_ENV=production + - PORT=3000 + - JWT_SECRET=ЗАМЕНИТЕ_на_длинную_случайную_строку # обязательно! напр. `openssl rand -hex 32` + - JWT_EXPIRES_IN=7d + - DB_PATH=/app/backend/data/learnspace.db + - UPLOADS_DIR=/app/backend/uploads + - CLIENT_ORIGIN=https://learn.example.com # публичный адрес (для CORS); или http://:3000 + # необязательно — TURN для видеосвязи за NAT (классы/доска): + # - TURN_URL=turn:turn.example.com:3478 + # - TURN_USER=... + # - TURN_PASS=... + # необязательно — LLM «Спроси» (Groq и т.п.): + # - ASSISTANT_LLM_URL=https://api.groq.com/openai/v1/chat/completions + # - ASSISTANT_LLM_KEY=... + # - ASSISTANT_LLM_MODEL=llama-3.3-70b-versatile +``` + +Сгенерировать секрет: в Shell TrueNAS `openssl rand -hex 32`. + +## 4. Первый запуск + +При старте entrypoint сам накатит миграции и засеет права — отдельных команд не нужно. Проверьте: +- **Apps → learnspace → Logs**: должно быть `applying migrations...` → `starting server...` → `Server running on port 3000`. +- Healthcheck (`/api/health`) станет «healthy» через ~10–30 с. + +Откройте `http://:3000`. Создайте первого пользователя через регистрацию (или заведите админа — +скажите, добавлю команду/скрипт сидинга админа). + +## 5. HTTPS и домен (рекомендуется) + +Порт 3000 — внутренний. Для доступа по домену с HTTPS поставьте reverse-proxy: +- TrueNAS app **Traefik**/**Nginx Proxy Manager**, либо внешний nginx → проксировать на `:3000`. +- В `CLIENT_ORIGIN` укажите итоговый публичный URL (иначе CORS заблокирует фронт). +- Для классов/доски (WebRTC) за CGNAT/симметричным NAT нужен TURN-сервер (coturn) — иначе p2p может не подняться. + +## 6. Обновление версии + +1. Пересоберите образ новой версии (шаг 2) с тем же тегом или новым. +2. Apps → learnspace → **Edit** (обновите `image:` если тег сменился) → **Update**, либо пересоздайте app. +3. Данные сохранятся (тома на датасете). Миграции применятся автоматически при старте. + +## 7. Бэкапы + +Данные в `/mnt/tank/apps/learnspace/{data,uploads}`. Настройте **снапшоты датасета** + при желании +**репликацию**. SQLite-файл — `data/learnspace.db` (консистентный бэкап лучше делать при остановленном app +или через снапшот ZFS). + +--- + +## TrueNAS CORE (FreeBSD) — без Docker + +CORE не запускает Docker-контейнеры. Варианты: +- **Linux-VM** в TrueNAS CORE (если включён bhyve) → внутри VM поставить Docker и следовать инструкции SCALE выше. +- **Jail** с Node 22+: установить node, `npm ci --omit=dev` в `backend/`, прописать env, `npm run migrate && + npm run seed:permissions`, запускать `node src/server.js` под supervisor (pm2/rc.d). node:sqlite требует Node ≥ 22. + +> Какой у вас TrueNAS (SCALE или CORE) и версия — подскажу точные шаги под конкретный вариант. diff --git a/Dockerfile b/Dockerfile index 3752b6e..648089b 100644 --- a/Dockerfile +++ b/Dockerfile @@ -6,7 +6,7 @@ RUN npm ci --omit=dev # ── Stage 2: runtime ───────────────────────────────────────────────────── FROM node:22-alpine -LABEL maintainer="BQ-System" +LABEL maintainer="LearnSpace" RUN apk add --no-cache sqlite tini @@ -17,9 +17,12 @@ COPY backend/src ./backend/src COPY backend/seed.js ./backend/seed.js COPY frontend ./frontend COPY js ./js +COPY docker-entrypoint.sh ./docker-entrypoint.sh -# Ensure data & uploads dirs exist (volumes mount here) -RUN mkdir -p /app/backend/data /app/backend/uploads /app/backups +# Ensure data & uploads dirs exist (volumes mount here); normalize entrypoint EOL + make executable +RUN mkdir -p /app/backend/data /app/backend/uploads /app/backups \ + && sed -i 's/\r$//' /app/docker-entrypoint.sh \ + && chmod +x /app/docker-entrypoint.sh ENV NODE_ENV=production ENV PORT=3000 @@ -28,5 +31,5 @@ EXPOSE 3000 HEALTHCHECK --interval=30s --timeout=5s --start-period=10s --retries=3 \ CMD wget -qO- http://localhost:3000/api/health || exit 1 -ENTRYPOINT ["tini", "--"] -CMD ["node", "backend/src/server.js"] +# Entrypoint применяет миграции + засев прав, затем запускает сервер (tini — PID 1, сигналы/зомби) +ENTRYPOINT ["tini", "--", "/app/docker-entrypoint.sh"] diff --git a/docker-entrypoint.sh b/docker-entrypoint.sh new file mode 100644 index 0000000..6cddc8b --- /dev/null +++ b/docker-entrypoint.sh @@ -0,0 +1,19 @@ +#!/bin/sh +# LearnSpace — инициализация контейнера: применить миграции (идемпотентно), +# при первом старте засеять права, затем запустить сервер. +set -e +cd /app/backend + +echo "[entrypoint] applying migrations..." +node src/db/migrations-runner.js + +# Засев прав — только если реестр пуст (первый запуск): schema-check сервера +# требует непустую role_permissions. Идемпотентно и безопасно при перезапусках. +NEED_SEED=$(node -e "try{const {DatabaseSync}=require('node:sqlite');const db=new DatabaseSync(process.env.DB_PATH||'./data/learnspace.db');const n=db.prepare('SELECT COUNT(*) AS n FROM role_permissions').get().n;process.stdout.write(n>0?'0':'1')}catch(e){process.stdout.write('1')}") +if [ "$NEED_SEED" = "1" ]; then + echo "[entrypoint] seeding permissions (first run)..." + node src/db/seed-permissions.js || echo "[entrypoint] seed-permissions skipped/failed (non-fatal)" +fi + +echo "[entrypoint] starting server..." +exec node src/server.js