Skip to content

Folders and files

NameName
Last commit message
Last commit date

Latest commit

 

History

10 Commits
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 

Repository files navigation

Культ Петры

Самостоятельно размещаемая медиагалерея на Nuxt 4, Vue 3, Bun, Tailwind CSS 4 и SQLite. Изображения и короткие видео хранятся локально, администратор управляет материалами, авторами, источниками, тегами и предложениями посетителей через отдельную админку.

Локальный запуск

Нужны Bun 1.3+, Node.js 22.5+ и Git. Node.js используется Nuxt dev-сервером на Windows; production-сервер работает под Bun. FFmpeg и FFprobe устанавливаются вместе с зависимостями.

Скопируйте или клонируйте проект, перейдите в его каталог и выполните:

bun install
bun run setup

Откройте созданный site.config.ts, задайте локальный site.origin, логин и длинный уникальный пароль, затем выполните:

bun run doctor
bun run dev

У каждого ассета есть приватный UUID assets.id, который используется и внутренними связями, и каталогом файла; отдельного physical_id нет. Наружу отдаётся только неизменяемый base62 public_id. Публичные адреса ассетов, источников, тегов и авторов получают безопасный читаемый префикс (slug-publicId), который не хранится в БД и не вводится вручную. После переименования старый slug перенаправляется на новый, а если из названия нельзя получить читаемый ASCII-slug, URL состоит только из public ID. Прежний порядок publicId-slug не поддерживается.

Источники — основной цветной справочник с описанием, внешней ссылкой и необязательной иконкой. Теги — отдельные второстепенные серые отметки, у которых хранится только public ID и название; даты создания, цвета, иконки, описания и ссылки для них отсутствуют. Галерея фильтрует материалы по обеим группам независимо.

Сайт откроется на http://127.0.0.1:3000, админка — на /admin. Nuxt работает с HMR: пересобирать проект после каждого изменения не нужно.

Конфигурация

Все параметры находятся в серверном site.config.ts. Файл исключён из Git и Docker-образа; образ получает его только через read-only mount. Полный безопасный шаблон — site.config.example.ts.

Раздел Назначение
site Название, описание, canonical origin, необязательная HTTPS-ссылка repositoryUrl и объект публичных ссылок links
admin Логин и пароль администратора
storage Переносимый каталог данных; обычно ./data
media Размеры загрузок, качество превью и параметры перекодирования
suggestions Включение предложки, лимит файла, частота и резерв диска
antibot Провайдер Turnstile либо disabled
analytics Необязательный Google tag ID
email SMTP, отправитель, reply-to и адрес администратора
limits Периоды дедупликации, ограничение входа и число одновременно обрабатываемых медиа (maxConcurrentMediaJobs, обычно 2)

Production не запускается с HTTP-origin, примерным доменом, путём внутри origin или слабым паролем. После редактирования конфигурации полезно запускать bun run doctor.

Google Analytics / gtag

Укажите Measurement ID или Google tag ID, например:

analytics: {
  googleTagId: 'G-XXXXXXXXXX',
},

Пустая строка полностью отключает интеграцию. Скрипт загружается только после подтверждения возраста и согласия в первом диалоге; административные страницы не отслеживаются. Идентификатор публичен по своей природе, секреты в этот раздел добавлять нельзя.

Почта

Включите email.enabled и заполните host, port, secure, при необходимости username/password, а также from, replyTo и adminTo. Для порта 465 обычно используется secure: true, для 587 — false с STARTTLS.

bun run email:check

Команда проверяет соединение и авторизацию SMTP, но не отправляет письмо. При отключённой почте публичная форма не собирает email.

Cloudflare Turnstile

Создайте виджет для текущего hostname, задайте antibot.provider: 'turnstile' и заполните site key и secret key. Secret остаётся только в site.config.ts. Для локальной разработки можно использовать disabled; production rate limits при этом продолжают действовать. При смене домена добавьте новый hostname в панели Cloudflare.

Команды

Команда Что делает
bun run dev Nuxt dev-сервер с HMR
bun run build Production-сборка Nitro
bun run start Запуск готовой .output под Bun
bun run setup Создание каталогов и site.config.ts без перезаписи
bun run doctor Проверка конфига, SQLite, хранилища и FFmpeg
bun run email:check Проверка SMTP без отправки письма
bun run backup:client -- … Исходная версия внешнего backup-клиента
bun run backup:build Сборка standalone backup-клиентов Windows/Linux
bun run backup:import -- <папка> Импорт проверенной копии в чистую установку той же версии
bun run db:generate Создать новую SQL-миграцию из server/db/schema.ts
bun run db:check Проверить согласованность Drizzle schema и migrations
bun run db:migrate Транзакционно применить ещё не выполненные миграции
bun run check:drizzle Запретить возврат неявного терминатора Drizzle .execute()
bun run lint ESLint
bun run typecheck Проверка TypeScript/Vue
bun run test Unit-тесты
bun run test:vue Nuxt/Vue component-тесты
bun run test:integration Интеграционные тесты Playwright
bun run test:e2e Браузерные сценарии desktop/mobile
bun run check Быстрый финальный набор без E2E
bun run check:all Все проверки и production-сборка

Развёртывание на VPS

Нужны Linux, Docker Engine, Compose plugin, открытые TCP 80/443 и UDP 443, а также A/AAAA-запись домена на VPS.

  1. Скопируйте проект на сервер и выполните bun run setup либо создайте site.config.ts из шаблона вручную.
  2. Укажите реальный HTTPS site.origin, сильный пароль и production-ключи.
  3. Запустите:
docker compose up -d --build
docker compose ps
docker compose logs -f app caddy

Init-контейнер валидирует конфигурацию и создаёт data/runtime/Caddyfile. Одноразовый сервис migrate применяет все migrations до запуска app; повторный запуск безопасен. Caddy автоматически получает HTTPS-сертификат. Наружу опубликованы только 80/443; порт приложения остаётся внутри Docker-сетей.

Обновление:

./scripts/update.sh

Перед первым запуском выдайте файлу право на выполнение: chmod +x scripts/update.sh. Скрипт проверяет Git/upstream и tracked-изменения, получает только fast-forward, собирает новые образы при работающем старом сайте, затем останавливает только app. После WAL checkpoint он сохраняет SQLite и JSON с version/commit в data/backups/db/, применяет migrations и запускает Compose с --wait. Caddy во время обновления не останавливается. В конце версия из /api/health сверяется с package.json.

Если migration завершается ошибкой, её транзакция откатывается и снова запускается прежний контейнер приложения. После успешной migration автоматический downgrade не выполняется: скрипт выводит путь к backup и старый commit для ручного восстановления.

Версия и миграции БД

Единственный номер версии сайта — поле version в package.json (SemVer). Перед каждым релизом его повышают вручную. Эта версия попадает в Nuxt appConfig, видна рядом с логотипом, а на мобильном экране располагается под публичными иконками-ссылками, и возвращается полем version из /api/health. Если в shared/release-notes.ts есть запись с тем же номером, рядом появляется пояснение с двумя или тремя короткими пунктами; без записи попап не показывается. Номер ведёт в site.repositoryUrl в новой вкладке, а при пустом значении остаётся обычным текстом. Последняя загруженная версия хранится в cult-petra:last-loaded-version:v1: вернувшемуся после обновления посетителю документированная новая версия на один визит подсвечивается малиновым огоньком, а первый визит и релизы без заметок не выделяются.

site.links — объект ссылок под названием сайта в публичной шапке. Ключ становится доступной подписью и подсказкой, а значение задаётся короткой строкой либо объектом с SVG-иконкой:

links: {
  Почта: 'mailto:hello@example.com',
  Телеграм: {
    link: 'https://t.me/example',
    icon: '<svg viewBox="0 0 24 24" fill="none" stroke="white"><path d="…"/></svg>',
  },
}

Разрешены только https: и mailto:. Без icon показывается стандартная иконка цепочки; пользовательский SVG проходит серверную проверку и отображается как изолированное изображение. Все ссылки открываются в новой вкладке.

Каноническая SQLite-схема находится в server/db/schema.ts, связи RQB v2 — в server/db/relations.ts, а неизменяемая история — в drizzle/. Новое изменение БД выполняется так:

bun run db:generate
# проверить новый drizzle/<timestamp>_*/migration.sql и snapshot.json
bun run db:check
bun run db:migrate

SQL/снимки уже выпущенной migration не редактируют. FTS5, PRAGMA и другие SQLite-специфичные операции оформляются параметризованным sql или custom migration. drizzle-kit push в проекте запрещён: production изменяется только коммитнутыми migrations. Для первого локального перехода со старой, немигрированной БД удалите или перенесите data/db/cult.sqlite, -wal и -shm; реальных данных до этого перехода проект не хранил. Updater сам распознаёт этот единственный pre-Drizzle случай, но удаляет старую БД только после создания backup.

Другие сайты на том же VPS

Один Caddy может обслуживать несколько поддоменов. Этот Compose создаёт сеть cult-petra-web. Подключите к ней контейнер второго приложения как к external network и добавьте data/runtime/sites/resume.caddy:

resume.example.com {
  reverse_proxy resume-app:3000
}

После этого перезапустите Caddy. Только один reverse proxy на VPS должен публиковать 80/443; контейнеры приложений наружу не открываются.

Резервная копия и перенос

Внешняя переносимая копия

Основной внешний backup не копирует SQLite и не зависит от внутренних UUID или раскладки data/media. Отдельная Windows/Linux-машина входит через обычную admin session, получает /api/admin/backups/manifest, потоково скачивает канонические оригиналы, аватары и иконки, а затем проверяет размер и SHA-256 каждого файла. Незавершённая .partial-папка никогда не считается резервной копией.

Backup manifest хранит богатые источники в sources и assets[].sourceIds, а простые теги — в tags и assets[].tagIds. Формат 0.2.0 не содержит fallback-полей прежней модели.

Готовые cult-backup binaries публикуются в GitHub Releases для Windows и Linux x64/ARM64. Для запуска из исходников используйте bun run backup:client -- перед аргументами клиента.

Каждый релиз сайта обязан иметь Git-тег v<version>, точно соответствующий версии в package.json: без него backup этой версии нельзя гарантированно воспроизвести. site.config.ts, admin/SMTP-секреты и сам Git-репозиторий в контентную копию не входят; актуальный конфиг храните отдельно от VPS в зашифрованном хранилище.

Пример backup.config.json:

{
  "origin": "https://cult.gwynerva.net",
  "destination": "./copies",
  "datasetId": "cult-gwynerva"
}

Плановый запуск получает пароль из отдельного credentials.json:

{
  "username": "admin",
  "password": "ADMIN-PASSWORD"
}

На Linux выдайте файлу права chmod 600 credentials.json. На Windows ограничьте ACL текущим пользователем. Не передавайте пароль аргументом команды, не помещайте credentials рядом с публичными файлами и не синхронизируйте его вместе с backup-папками.

Планировщик должен ежедневно выполнять:

cult-backup backup --if-due --config /path/backup.config.json --credentials /path/credentials.json

Клиент самостоятельно пропустит запуск, пока от последней успешной копии не прошло 168 часов. Внеплановый запуск выполняется вручную и скрыто спрашивает логин и пароль:

cult-backup backup --now --config /path/backup.config.json

Успешный ручной запуск становится новой точкой недельного отсчёта; ошибка срок не сдвигает. Проверить существующую папку можно без доступа к сайту:

cult-backup verify /path/cult-gwynerva-backup_site-0.1.0_2026-07-18T12-00-00Z

Обзор /admin показывает последний подтверждённый backup, текущую стадию и heartbeat. Через восемь суток без успеха виджет становится аварийным, а при включённом SMTP раз в сутки ставится письмо администратору через общую очередь.

Для восстановления найдите Git-тег версии из manifest.json, разверните эту версию с пустой БД и пустыми data/media, data/authors, data/tags, остановите app, примените migrations и запустите importer:

docker compose stop app
docker compose run --rm migrate
docker compose run --rm --no-deps -v /absolute/backup:/backup:ro migrate \
  bun run backup:import -- /backup/cult-gwynerva-backup_site-0.1.0_2026-07-18T12-00-00Z
docker compose up -d --build

Importer сначала повторно проверяет все контрольные суммы, требует точного совпадения версии и отказывается работать с непустой коллекцией. Он сохраняет публичные ID, тексты, даты и связи, создаёт новые внутренние UUID и заново строит превью, размеры, цвет карточки, FTS и link previews. После проверки старой версии сайт обновляется обычным scripts/update.sh вместе со всеми migrations.

Физический перенос VPS

Всё переносимое состояние находится в data/; отдельно нужен только site.config.ts. Для согласованной копии SQLite остановите сервисы:

docker compose down

Скопируйте проект, site.config.ts и весь data/. На новом VPS измените site.origin, обновите DNS и hostname Turnstile, затем выполните docker compose up -d --build. Публичные ID и относительные ссылки сохранятся. Тома сертификатов Caddy переносить необязательно.

Никогда не публикуйте data/ и site.config.ts, не добавляйте их в Git и не помещайте внутрь public/. Перед обновлением или переносом всегда делайте резервную копию.

Для ручного восстановления после уже применённой migration остановите app, сохраните текущую БД отдельно, верните указанный updater-файл на место data/db/cult.sqlite, удалите относящиеся к заменённой БД cult.sqlite-wal/cult.sqlite-shm, переключите Git на выведенный старый commit и пересоберите сервисы. Такое восстановление намеренно не автоматизировано: код, SQL-схема и backup должны возвращаться согласованно.

Диагностика

bun run doctor
docker compose config
docker compose ps
docker compose logs --tail=200 app caddy config-init migrate

Проверьте DNS и доступность 80/443, если Caddy не получает сертификат. Ошибки обработки медиа обычно указывают на неподдерживаемый файл, превышение лимита или проблему FFmpeg; статус и текст ошибки видны в админке.

Архитектурная памятка для AI-агентов находится в AGENTS.md. При изменении API, хранения, конфигурации или команд её следует обновлять вместе с README.

About

Культ Петры — галерея мемов и артов

Resources

Stars

0 stars

Watchers

0 watching

Forks

Releases

Packages

Contributors

Languages