Add nginx config and rewrite README with usage and deployment guide

nginx: path allowlist, GET/HEAD only, CSP and security headers, gzip.
README: what the app is, how to use it, CSV format, step-by-step deploy.

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
This commit is contained in:
IgorVolochay
2026-11-14 22:33:04 +03:00
co-authored by Claude Sonnet 5.5
parent 401833104b
commit 743389b133
2 changed files with 253 additions and 20 deletions
+172 -20
View File
@@ -1,27 +1,179 @@
# thumbnail-tinder # Thumbnail Tinder
Приложение для итогового отбора превью из расширения Outlier Saver. Приложение для итогового отбора превью. Оно работает с файлами браузерного расширения Outlier Saver, которое сохраняет видео с аномально высокими просмотрами (outliers).
## Запуск Вы загружаете CSV из расширения и листаете видео как в Tinder: превью, название, просмотры, подписчики и outlier score. Свайп вправо — нравится, влево — нет. В конце приложение отдаёт новый CSV в том же формате, но только с понравившимися видео. Отдельная страница показывает отобранное сеткой, как главная YouTube.
Откройте `index.html` в браузере (двойной клик). Сервер и установка не нужны. Приложение статическое: три файла, без сервера, базы данных и сборки. Файл CSV читается прямо в браузере и никуда не отправляется.
Файлы `index.html`, `liked.html` и `style.css` должны лежать в одной папке. Шрифты и превью подгружаются из интернета.
1. Перетащите CSV из расширения на страницу или нажмите «Выбрать файл». ## Как пользоваться
2. Оцените видео:
- свайп вправо / `→` / ♥ — нравится;
- свайп влево / `←` / ✕ — не нравится;
- кнопка «Пропустить» вверху / свайп вверх / `↑` / `S` — отложить;
- `Z` — отменить последнее решение, `Enter` — открыть видео на YouTube.
3. Нажмите «Скачать ♥». Получится файл `<имя>-liked.csv` в том же формате, что и исходный
(BOM, CRLF, те же колонки и кавычки), но только с понравившимися строками в исходном порядке.
Пропущенные видео в файл не попадают. Когда основной проход закончится, их можно пересмотреть. ### 1. Отбор: `index.html`
Прогресс сохраняется в браузере: если снова открыть тот же файл, работа продолжится с того места, где вы остановились.
## Галерея понравившихся 1. Перетащите CSV на страницу или нажмите «Выбрать файл».
2. Оценивайте видео:
Откройте `liked.html` (или кнопку «Галерея» в приложении) и загрузите скачанный `…-liked.csv`. | Действие | Мышь / тач | Клавиатура |
Видео покажутся сеткой, как на главной YouTube: 2 колонки на узком экране, 3 на среднем и 4 на широком. |---|---|---|
У каждой карточки есть превью, название, канал, просмотры, подписчики и outlier score на превью. | Нравится | свайп вправо или кнопка «Нравится» | `→` или `D` |
Клик ведёт на видео, сортировать можно по score, просмотрам, подписчикам или дате добавления. | Не нравится | свайп влево или кнопка «Не нравится» | `←` или `A` |
| Пропустить (отложить) | кнопка «Пропустить» вверху или свайп вверх | `↑` или `S` |
| Отменить последнее решение | «Отменить» | `Z` или `Backspace` |
| Открыть видео на YouTube | «Открыть на YouTube» или клик по названию | `Enter` |
3. Нажмите «Скачать CSV». Получится файл `<имя исходного файла>-liked.csv`. Скачать можно в любой момент, не обязательно оценивать всё.
**Формат результата.** Понравившиеся строки копируются из исходного файла без изменений: те же колонки, кавычки, BOM и переводы строк (CRLF), исходный порядок. Если выгрузить все строки, получится файл, идентичный исходному.
**Пропущенные видео** в файл не попадают. Когда основной проход закончится, их можно пересмотреть отдельно.
**Прогресс сохраняется в браузере.** Если закрыть вкладку и снова загрузить тот же файл, работа продолжится с того места, где вы остановились. Прогресс привязан к содержимому файла, браузеру и адресу страницы.
На карточке под числом outlier score есть шкала от 1× до 1000× (логарифмическая), на ней отмечено, где находится видео.
### 2. Галерея: `liked.html`
Откройте её кнопкой «Галерея» и загрузите скачанный `…-liked.csv`. Подойдёт и любой CSV из расширения.
- Сетка: 2 колонки на узком экране, 3 на среднем, 4 на широком.
- На карточке: превью, название, канал, просмотры, подписчики, outlier score.
- Сортировка: как в файле, по outlier score, по просмотрам, по подписчикам, по дате добавления.
- Клик по превью или названию открывает видео, клик по каналу открывает канал.
### Формат входного CSV
Обязательные колонки: `videoId`, `title`, `thumbnailUrl`. Остальные показываются, если есть:
```
videoId,title,url,thumbnailUrl,channelTitle,channelUrl,subscribers,views,outlierScore,isShort,foundOn,firstSeenAt,updatedAt
```
Пример лежит в репозитории: `outliers-2026-11-14.csv`.
## Локальный запуск
Откройте `index.html` в браузере двойным кликом. Ничего устанавливать не нужно.
Файлы `index.html`, `liked.html` и `style.css` должны лежать в одной папке. Шрифты (Google Fonts) и превью (`i.ytimg.com`) подгружаются из интернета. Без сети интерфейс работает, но превью не покажутся.
Если нужен локальный сервер:
```bash
python3 -m http.server 8080
# http://localhost:8080
```
## Развёртывание на сервере (nginx)
Нужен сервер с Linux (ниже команды для Debian/Ubuntu) и домен, у которого A/AAAA-запись указывает на сервер. Порты 80 и 443 должны быть открыты.
Конфиг: [`deploy/nginx.conf`](deploy/nginx.conf).
### 1. Установите nginx
```bash
sudo apt update
sudo apt install nginx
```
### 2. Выложите файлы приложения
Нужны только три файла. CSV и остальное на сервер класть не надо.
```bash
git clone <адрес-репозитория> ~/thumbnail-tinder
cd ~/thumbnail-tinder
sudo mkdir -p /var/www/thumbnail-tinder
sudo install -m 644 index.html liked.html style.css /var/www/thumbnail-tinder/
```
### 3. Подключите конфиг
```bash
sudo cp deploy/nginx.conf /etc/nginx/sites-available/thumbnail-tinder
sudo sed -i 's/thumbnails.example.com/ВАШ.ДОМЕН/' /etc/nginx/sites-available/thumbnail-tinder
sudo ln -s /etc/nginx/sites-available/thumbnail-tinder /etc/nginx/sites-enabled/
sudo nginx -t && sudo systemctl reload nginx
```
Если `nginx -t` ругается на конфликт `server_name` или на порт, проверьте, не включён ли стандартный сайт `default`: `sudo rm /etc/nginx/sites-enabled/default`.
Проверьте: `http://ВАШ.ДОМЕН` должен открыть приложение.
### 4. Включите HTTPS
```bash
sudo apt install certbot python3-certbot-nginx
sudo certbot --nginx -d ВАШ.ДОМЕН
```
Certbot сам добавит блок `listen 443 ssl`, редирект с HTTP и настроит автопродление сертификата. Проверить продление: `sudo certbot renew --dry-run`.
После этого по желанию включите HSTS: добавьте в блок `server` с `listen 443` строку
`add_header Strict-Transport-Security "max-age=31536000" always;`
Делайте это только когда уверены, что HTTPS работает стабильно.
### 5. Обновление
Статика раздаётся без сборки, перезапускать nginx не нужно:
```bash
cd ~/thumbnail-tinder
git pull
sudo install -m 644 index.html liked.html style.css /var/www/thumbnail-tinder/
```
HTML отдаётся с `Cache-Control: no-cache`, а `style.css` кэшируется на 1 час, поэтому изменения стилей могут появиться у пользователей с задержкой до часа.
### Что делает конфиг
- **Белый список путей.** Отдаются только `/`, `/index.html`, `/liked.html` и `/style.css`. Всё остальное (CSV, `.git`, README) отвечает 404, даже если файл случайно окажется в папке.
- **Только GET и HEAD.** Остальные методы получают 405.
- **Content-Security-Policy.** Разрешены только ресурсы самого сайта, шрифты с `fonts.googleapis.com` и `fonts.gstatic.com` и превью с `i.ytimg.com`. Запросы из скриптов запрещены (`connect-src 'none'`): приложение ничего не отправляет по сети, CSV остаётся в браузере. Встроенные скрипты и стили разрешены через `'unsafe-inline'`, потому что они лежат прямо в HTML.
- **Прочие заголовки:** `X-Content-Type-Options`, `X-Frame-Options: DENY`, `Referrer-Policy: no-referrer`, `Permissions-Policy`.
- **Сжатие gzip**, кодировка UTF-8, отдельные логи `thumbnail-tinder.*.log`.
Если вы добавите новую внешнюю зависимость (другой шрифт, CDN-скрипт), обновите CSP в конфиге, иначе браузер её заблокирует.
### Проверка конфига без сервера
Конфиг можно проверить в Docker:
```bash
docker run --rm -p 8080:80 \
-v "$PWD/deploy/nginx.conf":/etc/nginx/conf.d/default.conf:ro \
-v "$PWD":/var/www/thumbnail-tinder:ro \
nginx:alpine
# http://localhost:8080
```
### Доступ только для себя
Приложение не хранит данных на сервере, но если не хотите, чтобы им пользовались посторонние, добавьте в блок `server` базовую авторизацию:
```nginx
auth_basic "Thumbnail Tinder";
auth_basic_user_file /etc/nginx/.htpasswd;
```
```bash
sudo apt install apache2-utils
sudo htpasswd -c /etc/nginx/.htpasswd ИМЯ
```
## Структура
```
index.html отбор: свайпы и экспорт CSV
liked.html галерея понравившихся
style.css общие стили и цвета
deploy/nginx.conf конфиг nginx
outliers-2026-11-14.csv пример входного файла
```
## Ограничения
- Прогресс хранится в `localStorage` браузера: он не переносится между браузерами и устройствами, и очистка данных сайта его удалит. Результат лучше скачать до этого.
- Для превью берётся `maxresdefault.jpg`, а если его нет, ссылка из CSV. Видео, удалённые с YouTube, покажутся без картинки.
+81
View File
@@ -0,0 +1,81 @@
# Thumbnail Tinder: static site.
#
# Куда положить: /etc/nginx/sites-available/thumbnail-tinder
# (затем ссылка в sites-enabled; на RHEL-подобных системах просто /etc/nginx/conf.d/thumbnail-tinder.conf).
# Перед запуском замените thumbnails.example.com на свой домен.
# HTTPS добавляется одной командой после запуска: sudo certbot --nginx -d <домен>
# (подробности в README.md, раздел «Развёртывание»).
server {
listen 80;
listen [::]:80;
server_name thumbnails.example.com;
root /var/www/thumbnail-tinder;
index index.html;
charset utf-8;
access_log /var/log/nginx/thumbnail-tinder.access.log;
error_log /var/log/nginx/thumbnail-tinder.error.log;
# Приложение только отдаёт статику.
if ($request_method !~ ^(GET|HEAD)$) {
return 405;
}
# ---------- Сжатие ----------
gzip on;
gzip_comp_level 5;
gzip_min_length 512;
gzip_vary on;
gzip_types text/css text/plain application/javascript application/json image/svg+xml;
# text/html сжимается по умолчанию
# ---------- Заголовки безопасности ----------
# Страницам нужны: шрифты с Google Fonts и превью с i.ytimg.com.
# Скрипты и стили встроены в HTML, поэтому разрешён 'unsafe-inline'.
# connect-src 'none': приложение не делает сетевых запросов, файл CSV читается локально в браузере.
# Важно: add_header в location сбрасывает унаследованные заголовки,
# поэтому ниже в location используется только expires.
add_header Content-Security-Policy "default-src 'self'; script-src 'self' 'unsafe-inline'; style-src 'self' 'unsafe-inline' https://fonts.googleapis.com; font-src https://fonts.gstatic.com; img-src 'self' data: https://i.ytimg.com; connect-src 'none'; object-src 'none'; base-uri 'none'; form-action 'none'; frame-ancestors 'none'" always;
add_header X-Content-Type-Options "nosniff" always;
add_header X-Frame-Options "DENY" always;
add_header Referrer-Policy "no-referrer" always;
add_header Permissions-Policy "camera=(), microphone=(), geolocation=()" always;
# ---------- Маршруты: белый список ----------
# Отдаются только три файла приложения. Всё остальное (в том числе случайно
# скопированные CSV, .git, README) отвечает 404.
location = / {
try_files /index.html =404;
expires epoch; # Cache-Control: no-cache, страница всегда свежая
}
location = /index.html {
expires epoch;
}
location = /liked.html {
expires epoch;
}
location = /style.css {
expires 1h; # файл без хеша в имени, поэтому кэш короткий
}
location = /favicon.ico {
access_log off;
return 204;
}
# Для certbot (если используется метод webroot, а не --nginx)
location ^~ /.well-known/acme-challenge/ {
root /var/www/certbot;
default_type text/plain;
}
location / {
return 404;
}
}