wa-dev-tools/docs/sessions/2026-09-05-0231.md
treamz dabc26d70f Sync prod: owner-реестр результатов, общая фабрика лимитов, crash-guard ffmpeg/gs, водяной знак PDF
Незакоммиченная работа сессии 2026-09-05 (уже на проде):
- lib/owner.js — реестр владельцев файлов-результатов, owner-check во всех download-роутах (IDOR)
- lib/limits.js — createLimiter(): skip для админа, ipKeyGenerator (фикс ERR_ERL_KEY_GEN_IPV6)
- video: обработчики 'error' у ffmpeg/ffprobe (spawn ENOENT больше не роняет сервер), таймауты
- pdf: runGhostscript/gsUnavailable, водяной знак с кириллицей (fontkit + TTF)
- снятие лимитов размера/количества для админа в pdf/video
- журнал сессии docs/sessions/2026-09-05-0231.md
2026-09-22 20:37:39 +03:00

104 lines
11 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

# 2026-09-05 02:31 — ffmpeg/ghostscript в образ, водяной знак PDF, снятие лимитов админа
**Сделано:**
- **Найдена причина «Ошибка загрузки» видео:** в контейнере не было ни `ffmpeg`/`ffprobe`, ни `gs`
(контейнер поднимался из голого `node:20-slim`). В `getVideoDuration()` не было обработчика
`'error'` → `spawn ENOENT` уходил в `uncaughtException` → `process.exit(1)`, то есть **загрузка
видео роняла весь сервер**. Лимиты размера были ни при чём.
- **Контейнер переведён на собственный образ:** создан `/opt/sites/wadevelop/Dockerfile`
(`FROM node:20-slim` + `ffmpeg` + `ghostscript`), в `/opt/sites/docker-compose.yml` сервис
`wadevelop` переключён с `image: node:20-slim` на `build: ./wadevelop` + `image: wadevelop-node:latest`.
Образ собран, контейнер пересоздан. Проверено на бою: `gs 10.00.0`, ffmpeg с
libx264/libvpx/libmp3lame/aac, тестовые mp4 и gif собираются.
- **PDF: водяной знак не работал вообще** — в `public/pdf.html` в `sendRequest()` отсутствовала ветка
`case 'watermark'`, запрос падал в `default` («Неизвестная операция: watermark»). Добавлена ветка,
параметры сделаны опциональными, кириллица — через встраиваемый TTF (`@pdf-lib/fontkit` + Manrope),
т.к. `StandardFonts.Helvetica` кодирует только WinAnsi.
- **Админ освобождён от всех ограничений** — единая фабрика лимитов `lib/limits.js` (`skip: isAdmin`),
сняты лимиты размера/количества в PDF и размера в video (включая клиентскую отсечку).
- **Закрыт IDOR на скачивании результатов** во всех четырёх download-роутах через новый реестр
владельцев `lib/owner.js` (SQLite, та же БД что и очередь) + случайные суффиксы в именах файлов.
- **Всё выкачено на прод и проверено:** сайт 200, `/health` 200, стартовый лог чистый — спам
`ERR_ERL_KEY_GEN_IPV6` (4 шт. при каждом старте) исчез; реестр владельцев проверен прогоном
на проде (свой `true`, чужой `false`, неизвестный `false`).
**Изменённые / созданные файлы:**
- [lib/owner.js](../../lib/owner.js) — **новый**: реестр владельцев файлов-результатов
(`file_owners` в `jobs.db`), `claim/isOwner/displayName`, авточистка старше 24 ч
- [lib/limits.js](../../lib/limits.js) — **новый**: `createLimiter()` — общая фабрика rate-limit,
`skip: isAdmin`, ключ через `ipKeyGenerator` (чинит `ERR_ERL_KEY_GEN_IPV6`)
- [routes/video.js](../../routes/video.js) — обработчики `'error'` для ffmpeg/ffprobe (crash-guard),
`FFMPEG_MISSING` → 503, таймаут `VIDEO_TIMEOUT_MS` (10 → 30 мин), `-ss` до `-i` и `-t` вместо `-to`,
прогресс от длительности результата, валидация `startTime`/`endTime`, owner-check в `/download`,
снят лимит размера для админа
- [routes/pdf.js](../../routes/pdf.js) — `runGhostscript()` + `gsUnavailable()` (503 вместо
`spawn gs ENOENT`) для preview/compress/toImages, водяной знак с TTF и опциональными параметрами,
owner-check в `/download`, `crypto.randomBytes` в именах, лимиты размера/количества сняты для админа
- [routes/compress.js](../../routes/compress.js) — случайные суффиксы в именах архива и single-файла,
`owner.claim`, owner-check в `/compress/download`, лимитер через общую фабрику
- [server.js](../../server.js) — owner-check в `/download/:filename`
- [routes/api.js](../../routes/api.js), [routes/auth.js](../../routes/auth.js),
[routes/favicon.js](../../routes/favicon.js), [routes/redirects.js](../../routes/redirects.js),
[routes/parser.js](../../routes/parser.js), [routes/httpclient.js](../../routes/httpclient.js) —
переведены на `createLimiter` (в auth — `skipAdmin: false`)
- [public/pdf.html](../../public/pdf.html) — ветка `case 'watermark'` в `sendRequest()`, убрана
обязательность текста, метка «(необязательно)»
- [public/video.html](../../public/video.html) — детект админа вынесен на загрузку страницы
(раньше `/auth/me` спрашивался уже после проверки размера — первый большой файл всегда отклонялся),
`MAX_SIZE = Infinity` для админа
- [package.json](../../package.json) — добавлен `@pdf-lib/fontkit`
- На VPS: `/opt/sites/wadevelop/Dockerfile` (новый), `/opt/sites/docker-compose.yml` (сервис на `build`)
**Решения:**
- **Бинарники через Dockerfile, а не `apt` в живом контейнере** — `docker exec apt-get install`
слетел бы при первом же пересоздании контейнера.
- **Кириллица в водяном знаке через `@pdf-lib/fontkit` + `public/vendor/fonts/manrope-600.ttf`**,
с откатом на Helvetica + транслитерацию, если пакет/шрифт недоступны — чтобы код работал
в любой среде, а не падал 500-й.
- **Владелец файла хранится в SQLite, а не в памяти** — переживает рестарт; отсутствие записи
трактуется как «чужой» (записи живут 24 ч против 30 мин у самих файлов).
- **Лимит входа `/auth/login` для админа НЕ снят** — на этапе логина сессии ещё нет, отличить админа
технически не от чего, и это защита самого аккаунта от подбора.
- Правки делались от локальной копии: перед деплоем сверены md5 всех 13 затрагиваемых файлов —
**VPS был идентичен git HEAD** (`a3fb5a2`), расхождений нет.
- Заливка — через PowerShell, а не Bash: Git Bash конвертирует POSIX-пути (`/opt/...` →
`C:/Program Files/Git/opt/...`) и ломает `sftp_put.py`.
**Открытые вопросы / следующие шаги:**
- [ ] **Проверить под залогиненной сессией:** водяной знак PDF (кириллица, дефолты) и конвертацию/
сжатие видео с прогрессом — у меня нет учётной записи для UI-проверки.
- [ ] **Очередь не ограничивает параллельность** — `queue.getPending()` нигде не вызывается, каждый
`/video/convert` спавнит ffmpeg немедленно. Пока ffmpeg отсутствовал, это не проявлялось;
теперь 3–4 одновременные конвертации положат VPS.
- [ ] **`/mnt/webdata/storage` живёт внутри контейнера, а не в томе** — пересоздание контейнера
(как сегодня) обнуляет `jobs.db` и папки результатов. Нужен том в `docker-compose.yml`.
- [ ] `routes/api.js` `/ai/explain` бьётся в `192.168.31.100:11434` (домашний PC) и `127.0.0.1:8080` —
с VPS недоступно, объяснялка regex и security-аудит на проде мертвы.
- [ ] `routes/logs.js` (68 строк) нигде не подключён — мёртвый файл, ссылок на `/logs` в HTML нет.
- [ ] `routes/api.js` — `req.on('close')` для `AbortController` регистрируется **после** дочитывания
стрима: отмена при отвале клиента не работает никогда.
- [ ] `server.js:44-47` — публичные `/api/*` смонтированы как `app.get('/api/settings', apiRouter)`;
путь внутри роутера не совпадает, запрос проваливается дальше и обрабатывается вторым
монтированием `app.use('/api', ...)`. Работает случайно, `publicLimiter` срабатывает не там.
- [ ] Хвосты прошлого аудита: DNS-rebinding pinning (HIGH-14), обновление 36 уязвимых npm-зависимостей,
сведение VPS-прод и rpi4-двойника в один git.
- [ ] Локальные правки **не закоммичены** — рабочее дерево грязное (14 изменённых + 2 новых файла).
**Как продолжить (для чистой сессии):**
- Контекст: **прод = VPS 195.140.146.84**, контейнер `wadevelop`, код `/opt/sites/www/wadevelop.ru/`,
теперь собирается из `/opt/sites/wadevelop/Dockerfile`. rpi4 — двойник, НЕ деплоить.
- SSH: `python3 C:/Users/tream/.claude/ssh_cmd.py root@195.140.146.84 "<cmd>"`.
Заливка: `python3 C:/Users/tream/.claude/sftp_put.py root@195.140.146.84 <remote_dir> <files>` —
**запускать через PowerShell**, Bash ломает POSIX-пути.
- Пересборка образа: `cd /opt/sites && docker compose build wadevelop && docker compose up -d wadevelop`.
Обычный деплой без смены зависимостей: заливка файлов + `docker restart wadevelop`.
- Проверка: `curl -s -o /dev/null -w "%{http_code}" https://wadevelop.ru/` (200);
`docker logs wadevelop --tail 20`; `docker exec wadevelop which ffmpeg ffprobe gs`.
- Откат: бэкапы `*.bak-20260905` в `/opt/sites/www/wadevelop.ru/` (13 файлов) и
`/opt/sites/docker-compose.yml.bak-20260905`.
**Ссылки:**
- Прод: https://wadevelop.ru — VPS 195.140.146.84 (FirstVDS, treamz.fvds.ru), контейнер `wadevelop`
- Предыдущая сессия: [2026-07-24 23:48 — аудит безопасности](2026-07-24-2348.md)
- Git: локаль на коммите `a3fb5a2` + незакоммиченные правки этой сессии