←
Документация
Архитектура
Статус
Контекст
История
Продакшн
Ноут Олега
VPS
COM Oleg
COM Pavel
Коммиты VPS
Коммиты Цеха
CHANGES.md — журнал сессий
Последние три рабочие сессии. Старше — в git history. Формат: дата + буква при нескольких сессиях в один день. Правила —
CONTEXT.md. Статус —STATE.md. ТЗ —ARCHITECTURE.md.
2026-10-18 — Знимкарі Лабораторія, новый UI, отправка и чистка
Интерфейс Лаборатории (бывший Цех)
- ✅ Переименование в UI: «Знимкарі Лабораторія».
- ✅ Новый экран «Импорт»: таблица сопоставляет папки в
zyn-festivalsс ивентами в БД. - ✅ Добавлена модалка создания ивента прямо из таблицы (с автозаполнением
source_folder). - ✅ Маска ввода даты
ДД.ММ.ГГГГв модалке создания. - ✅ Новый экран «Отправка»: таблица готовых ивентов, упаковка (
pack_event) и отправка (push_event) с логами и прогресс-баром. - ✅ Экран «Чистка»: поиск и удаление папок-сирот, пустых ивентов, зависших задач импорта и старых архивов.
- ✅ На главную добавлен виджет «Проверить скорость» (Speedtest через Cloudflare).
- ✅ Сквозной скролл логов: если пользователь прокрутил вверх, лог больше не прыгает вниз.
Исправление багов
- ✅ Фикс «гонки потоков»: при отмене импорта фоновый поток больше не перезаписывает статус
CANCELLEDнаDONE. - ✅ Добавлен флаг
-uдляsubprocess, чтобы логиpush_eventшли в реальном времени. - ✅ Устранены ошибки CSRF при вызове
pack_eventиpush_eventиз UI. - ✅ Починено чтение даты из модалки (теперь строго
%d.%m.%Y).
2026-10-17-б — Деплой цеха, Gunicorn, оптимизация импорта
Развёртывание znymkari-data на ноуте
- ✅ Создана БД
znymkari_localи юзер, сделаны миграции, созданadmin. - ✅ Сгенерирован SSH-ключ на ноуте для
push_event(добавлен вauthorized_keysна VPS). - ✅ Настроены права на HDD (
/etc/fstab:umask=000), чтобыdeployмог писать. - ✅ Создан и запущен
gunicorn-oleg.service(1 воркер + 4 потока). - ✅ Настроен WhiteNoise, добавлены иконка и стили.
- ✅ Создана страница
/docs/, которая по SFTP читает.mdфайлы с VPS.
Интерфейс импорта
- ✅ Добавлен предохранитель: проверка свободного места на HDD и SSD перед стартом (с запасом 5% и 15%).
- ✅ Прогресс-бар обновлён: показывает ETA, прошедшее время, объём (ГБ/МБ) и три активных потока.
- ✅ Добавлен "пульс" (защита от залипания UI при обработке тяжелых видео).
Оптимизация import_service.py
- ✅
shutil.copyfileвместоcopy2— обход тормозов с метаданными на NTFS. - ✅ Кэширование QR-кода в памяти, наложение в памяти (без чтения превью с диска).
- ✅ Извлечение постера видео из уже созданного превью (ускорение в 2 раза).
- ✅
ThreadPoolExecutorна 3 потока для параллельной обработки файлов. - ✅
-preset ultrafastдля ffmpeg.
Скрипты деплоя
- ✅
deploy_data.bat— создан (с бэкапомsettings_local.py, стоп-кранами и очисткой "висящих"runningзадач). - ✅ Исправлены зависания Windows
sshв бат-файлах (флаги< NULи-T). - ✅ Создан
start_local_dev.batдля песочницы на8001порту.
2026-10-17 — Мини-админка цеха и рабочий транспорт
Мини-админка znymkari-data
- ✅ Модель
ImportJobвapps/importer/models.py: статус, прогресс, лог, статистика. Админка. Миграция0001. - ✅ Пять страниц цеха:
/,/import/,/import/<id>/,/storage/,/settings/. Прогресс — polling раз в секунду. - ✅ Фоновый импорт в отдельном потоке (
threading), кнопка «Отмена». - ✅ Кнопка «Проверить связь с VPS» в
/settings/: SSH-коннект, проверка наличияreceive_event, вывод времени отклика. - ✅
base.html,static/css/style.css— фирменные цвета, без витринных эффектов.
Транспорт ноут → VPS
- ✅ Команда
pack_event <event_id>(ноут): сериализация ивента вdata.jsonчерезdjango.core.serializers, копирование превью, сборкаtar.gz. - ✅ Команда
push_event <event_id>(ноут):paramikoSFTP на VPS + запускreceive_eventпо SSH. Флаг--dry-run. - ✅ Команда
receive_event <slug>(VPS): распаковка, мягкий merge через ORM по естественным ключам, копирование превью в/srv/previews/. - ✅ Транспорт переписан с
pg_dump+ON CONFLICTна JSON + ORM. Причина: несовпадение pk, FK, M2M, множественные ключи. См.ARCHITECTURE.md §3.11.
Мягкий merge
- ✅ Правило: при создании ставим всё из JSON, при обновлении — только белый список полей.
- ✅ Хозяин поля — VPS: лайки, покупки, заморозка, разметка (
block/category/performance), автор, прайс,organizer,staff_on_site— при merge не трогаются. - ✅ Идемпотентность подтверждена на живом цикле: повторный
push_eventне создаёт дублей, Event обновляется, Media находит по(event, storage_path), превью с совпадающим размером пропускаются.
Справочники — только на VPS
- ✅
OrganizerиTeamMember— источник данных один: VPS. На ноуте не заполняются и не показываются в админке. - ✅
Event.source_folder— служебное поле, заполняется автоматически при импорте, спрятано в свёрнутый блок «Служебное». - ✅ Команда
assign_media_authors <event_id>(VPS): эвристика автора по ролям команды ивента.
Мелкие правки кода
- ✅ Из
import_service.pyубрана эвристика автора (перенесена на VPS). - ✅
pack_eventне шлётOrganizer/TeamMember. - ✅
receive_eventчистит NUL-байты из строк (Postgres их не принимает). - ✅
receive_eventне переноситidиeventиз JSON (Django назначает свои).
VPS
- ✅ Миграция
0012_drop_event_storage_rootприменена. - ✅ Создана папка
/srv/previews/(владелецdeploy:deploy). - ✅ Почищены старые команды из
web/apps/catalog/management/commands/. - ✅
deploy.bat— прошёл, тесты 77 OK.
Доки
- ✅
ARCHITECTURE.md— §2.8, §3.11 полностью переписаны под реальность. - ✅
STATE.md— обновлены план работ, «что не начато», техдолг, таблицы. - ✅
CONTEXT.md— обновлён список management-команд. - ✅
CHANGES.md— эта запись.
Отложено на будущее
- ⏳
gunicorn-oleg.service+ деплойznymkari-dataна ноут Олега. - ⏳ Первый реальный импорт фестиваля.
- ⏳ Обратный канал VPS → ноут (
pull_protected+ файл чистки). Схема: VPS формирует JSON при чистке, ноут забирает командой, Олег согласует на/storage/. - ⏳ Кнопка «Удалить из storage» на
/storage/— послеpull_protected.
Техдолг
Mediaпри повторном merge показывает~2(обновление), хотя данные не меняются. Причина:exact_datetimeиз БД — объектdatetime, из JSON — строка. Лишний UPDATE. Косметика.- Страница доков
/catalog/docs/<slug>/на web устарела. Состав доков менялся, добавилисьSETUP_HOME.md,SETUP_VPS.md,COMMANDS.md. Сверить сdocs.html.
2026-10-16 — Отказ от HOT/COLD
Хранилище
- Понятия HOT и COLD упразднены. Было: SSD = горячее, HDD = холодное.
Стало: единое хранилище на HDD (
/mnt/hdd/znymkari-storage/). - Причина: SSD < 300 ГБ, ни один фестиваль (200–500 ГБ) целиком не влезает.
content_shop_archive→znymkari-storage.- SSD остаётся под ОС + PostgreSQL + venv + превью на время push.
- NTFS-3G → NTFS3 отложено до покупки новых винчестеров (Олег планирует).
Модель
Event.storage_root— дропается. Миграции:0012вweb/,0010вznymkari-data.
Доки
ARCHITECTURE.md— §2.8, §3.3, §3.6, §3.10, §4.1, §4.2, §4.4, §4.6, §4.8, §5.1, §6, §7.1, §7.3.CONTEXT.md— §0, §2, §4 (модели, импорт, ноут, Samba).STATE.md— «Что не начато совсем», «Что в очереди», техдолг, таблицы.CHANGES.md— эта запись.
2026-10-15-б — Реструктуризация, znymkari-data, три поля в Event
Реструктуризация D:\content_shop
- Проблема: корневой
D:\content_shopбыл одним git-репо — включаяweb/, старый мир (cli/,domain/,scripts/,services/,storage/) иdeploy.bat. ПоARCHITECTURE.md §7.1должно быть:web/— отдельный репо, корень без git. - ✅ Бэкап:
G:\My Drive\backups\content_shop_2026-10-15\— 1290 файлов, 10.07 МБ,.gitвнутри. Верифицировано. Compress-Archiveпропустил.git(1 МБ вместо 10 МБ, скрытые файлы).- Переключились на
robocopy /E /COPY:DAT /R:1 /W:1. - ✅ Установлен
git-filter-repo==2.47.0. - ✅ Клон:
git clone --no-local D:\content_shop _znymkari_filter_tmp—--no-localобязателен, иначе filter-repo отказывается работать. - ✅
git filter-repo --subdirectory-filter web/— 43 коммита переписаны, пути сокращены на один уровень (web/apps/…→apps/…). - ✅
.gitперенесён вD:\content_shop\web\.gitчерезrobocopy /MOVE. - ✅ Старый
.gitв корне удалён.
Чистка
- Битые
desktop.iniв.git/refs/,heads/,tags/,remotes/origin/(~130 файлов) — Windows-артефакты от Проводника. Удалены. Из-за них клон падал сfatal: 'refs/remotes/origin/desktop.ini' has neither a valid OID nor a target. desktop.ini(~20 шт.) вweb/— удалены.__pycache__— удалены.media/__init__.py(пустой) — удалён.- Создан
web/.gitignore(перенесён из корня, адаптированы пути). D:\_znymkari_filter_tmp\— удалён.
znymkari-data — каркас
- ✅
D:\content_shop\znymkari-data\— Django 6.1.1, отдельный git-репо. - ✅
config/settings.py—INSTALLED_APPS:apps.core→apps.catalog,apps.importer,apps.dashboard. Убраны Telegram, accounts, issues, orders. - ✅
config/settings_local.py— dev (SQLite), прод-пути в комментариях. - ✅
requirements.txt: Django, psycopg2-binary, gunicorn, Pillow, requests, tzdata. - ✅
apps/catalog/models.py— 10 моделей (Organizer,TeamMember,Event,ProgramScan,Expense,ExtraIncome,Block,Category,Performance,Media). БезFavorite,CartItem,UserEventDiscount— они только на VPS. - ✅ Миграции
0001–0008скопированы изweb/идентично — схема совпадает. - ✅
apps/catalog/admin.py— все 10 моделей,Mediaчерезraw_id_fields. - ✅
apps/importer/sanitize.py— транслит имён файлов и папок (§5.1-bis). - ✅
apps/importer/apps.py—name = 'apps.importer'. - ✅
apps/dashboard/apps.py—name = 'apps.dashboard'.
Три поля в Event — синхронизация ноут ↔ VPS
- ✅
external_id—UUIDField(default=uuid4, unique=True). - ✅
storage_root—CharField,hot/cold,db_index. - ✅
slug—CharField,{id:04d}_{sanitized_title},unique,null=True. - ✅ Миграция
0009вznymkari-data— паттерн «nullable →RunPythonfill →unique». Django предупреждает наCallable default on unique field; паттерн обходит. - ✅ Миграция
0011вweb/— та же,dependenciesна0010_usereventdiscount. - ✅ В
web/нетsave()— slug иstorage_rootзаполняются только на ноуте, на VPS приходят черезpush_event. - ✅ 77 тестов — OK.
Инцидент на VPS
- ⚠️
deploy.batобновил код, ноmigrateне запускался →catalog_event.external_id does not exist→ 500 на/и/catalog/events/. - ✅ Применено вручную:
ssh deploy@... "cd .../web && venv/bin/python manage.py migrate"→ 0011 OK. - ✅ Сайт восстановлен (200/200).
- ✅
deploy.bat— в шаг 5 добавленmanage.py migrate --noinputдоcollectstatic. Шапка-комментарий обновлена.
Техдолг
znymkari-data— ещё нетapps/importer/:import_service.py,media_metadata.py,management/commands/. Плановый шаг 2.sanitize.pyесть только вznymkari-data. Вweb/не нужен — импорта нет.media/znymkari_logo.jpg— надо скопировать вznymkari-data/media/для_apply_qr_watermark.- Remote на GitHub — не настроен (ни для
web/, ни дляznymkari-data). Страховка — zip наG:\My Drive\backups\. D:\content_shop\.gitignore— не привязан ни к какому репо, кандидат на удаление.D:\content_shop\desktop.ini— мусор.
2026-10-15 — Webhook guard, чистка проекта, фикс логотипа
Webhook
- Слетел webhook бота (
urlпустой,pending_update_count: 7). Причина — Telegram отключает после длительных ошибок/недоступности (рестарты gunicorn при деплоях). - ✅ Восстановлен через
set_telegram_webhook --delete→set_telegram_webhook. - ✅ Новая команда
check_telegram_webhook— проверяет и переустанавливает еслиurlпустой,pending >= 30, или естьlast_error_message. - ✅ Cron на VPS раз в 10 минут. Лог
/var/log/znymkari/webhook_guard.log. - ✅ Проверено: cron пишет «ОК, webhook живой» каждые 10 минут.
Баг логотипа
import_service._apply_qr_watermarkискалznymkari-logo.jpg(дефис), реально файл —znymkari_logo.jpg(подчёркивание). Превью генерились без вотермарка.- ✅ Фикс: заменено на
znymkari_logo.jpg.
Чистка проекта
- ✅ Удалены
__pycache__и.pyc(146 файлов). - ✅ Удалены артефакты:
content_shop.db,docs_upload.tar.gz,web_upload.tar.gz,fix_docs.py,find_cyr.py. - ✅
.gitignore— разделеныweb/media/*и whitelistznymkari_logo.jpg,__init__.py. - ✅
deploy.bat— авточистка локального и VPS-архива после деплоя; исключениеdesktop.iniиз архива.
Техдолг
- Ubuntu 26.04 LTS — доступно обновление. НЕ делать сейчас — Broadcom DKMS, PostgreSQL 20, Python 3.15, nginx 1.30 → неделя работы.
- MOTD на SSH — отключены
90-updates-availableи91-release-upgrade.
2026-10-14-в — Samba, ревизия HDD, решение о znymkari-data
Удаление старого мира
- ✅
media_metadata.pyперенесён изD:\content_shop\services\вweb/apps/catalog/services/(1:1). - ✅
web/apps/catalog/services/import_service.py— импорт переключён наapps.catalog.services.media_metadata. - ✅
web/config/settings.py— убранsys.path.insert(0, BASE_DIR.parent)иimport sys(если он был только для этого). - ✅ Удалены папки:
cli/,domain/,scripts/,services/,storage/. - ✅ Удалены файлы в корне:
.gitignore,start_shell.bat. - ✅ Тесты
web/— 77 OK. - ✅ Grep по
web/иznymkari-data/— ни одного импорта из старого мира. - ✅ Удалены из
web/:apps/catalog/services/(папка целиком),management/commands/import_media.py,management/commands/generate_video_posters.py. Импорт теперь только вznymkari-data. - ✅
client_api.py— комментарий проimport_service._apply_qr_watermarkпереписан. - ✅ 77 тестов
web/— OK.
Samba на ноуте Олега
- Установлена
samba. Юзерolegarh, отдельный SMB-пароль черезsmbpasswd -a. - Шары:
[zyn-festivals]→/mnt/hdd/zny-festivals,[zyn-heap]→/mnt/hdd/zny-heap. - Порт 445, только LAN
192.168.31.0/24. Наружу не пробрасывается. - Олег подключил
Z:иY:через Проводник Windows. System error 1219— решена черезnet use * /delete /y+ одну команду.ufwне включён. Правило для 445 добавлено. Техдолг.
HDD — ревизия и очистка
- HDD иногда монтируется «грязным» (Windows Fast Startup).
ntfs-3gчинит сам. - Все 10 фестивалей из
_Work/BRAVO OleG/2026/перенесены черезmvвzyn-festivals/. - Всё лишнее с корня HDD перенесено в
zny-heap/— bash-цикл с защитой 5 имён. - В корне HDD:
$RECYCLE.BIN,System Volume Information,content_shop_archive,zny-festivals,zny-heap.
Решение о znymkari-data
- Отказались от «полной копии сайта на ноуте». Отдельный Django-проект
znymkari-data— только импорт, sanitize, превью, push. - Модели
catalog— общие с сайтом. Остальное (cart, favorites, telegram, issues, orders) — не переносится. - Связь с сайтом — один endpoint
POST /internal/receive-event/+push_event. pg_dump/pg_restore— не JSON. Схема БД на VPS и ноуте одинакова.- Админка для Олега — Django templates + минимум JS:
/,/import/,/storage/,/settings/. - Размещение:
D:\content_shop\web\+D:\content_shop\znymkari-data\. Два независимых git-репо рядом. web/docs/— остаётся внутри сайта.
Ревизия доков
- Убрана нумерация сессий (
С24,С25). Только даты + буква. ARCHITECTURE.md§7 — структура кода была выдумана. Переписана под реальность.- Расхождения, зафиксированные для исправления:
Event.external_id / storage_root / slug— нет в коде, только план.Media.width / height— нет.Category.block— есть в коде.sanitize.py— не существует, надо создать.import_service.pyтянетservices.media_metadataиз старого мира.import_serviceне копирует оригиналы, только превью._apply_qr_watermark—quality=100, не85.STORAGE_ROOT = E:\imagesв dev-настройках.
Что дальше
- Создать
D:\content_shop\znymkari-data\— каркас Django. - Перенести модели +
import_service+generate_video_posters. - Написать
sanitize.py— транслит. - Добавить копирование из
zyn-festivalsв HOT/COLD. - Мини-админка для Олега.
pack_event/push_event/receive_event.- Первый реальный импорт.