Главное. Строка IPAddressAllow=any обесценивала весь список запретов ниже, и заявленная защита от SSRF существовала только на бумаге. Из cgroup сервиса оставались доступны Proxmox API на 10.20.0.1:8006 и соседняя виртуальная машина — проверено установлением соединения. Сбивало с толку то, что адрес метаданных 169.254.169.254 при этом блокировался, поэтому прежняя проверка выглядела успешной. Когда список состоит только из запретов, фильтр работает: внутренняя сеть закрыта, внешняя и DNS продолжают работать. Всё проверено отдельными запусками через systemd-run до применения на боевом юните. Петля не закрывается сознательно. Фильтр systemd не различает направление, и запрет 127.0.0.0/8 обрывает ВХОДЯЩИЕ соединения nginx к этому же сервису — проверено: слушатель внутри такой cgroup становится недоступен снаружи, то есть сайт лёг бы. Службы на 127.0.0.1 остаются достижимы; закрыть их можно, только переведя связку nginx→приложение на unix-сокет. Дисковый бюджет. Юнит вчетверо поднимал квоту относительно кода (20 ГБ против 5 ГБ) при 35 ГБ свободного на разделе гипервизора, где живут виртуальные машины. Приведено к 5 ГБ. /readyz больше не витрина. Наружу уходит только признак готовности; свободное место, глубина очереди и версия yt-dlp отдаются лишь локальному мониторингу — сайт публичный. Статика кэшируется на месяц: стили и восемь файлов шрифта отдаются этим же процессом, а потоки здесь дефицитны из-за SSE, и браузер ревалидировал все девять файлов на каждой навигации. Согласованы сроки хранения: карточка задачи считается от создания, а файл — от окончания скачивания, поэтому при равных TTL карточка истекала раньше файла, и файл оставался недостижимым, занимая квоту. Срок карточки поднят до 240 минут против 120 у файла. Также: .gitkeep больше не удаляется уборщиком по TTL (при подметании на старте он уже исключался), README приведён в соответствие с config.py — три значения по умолчанию были указаны неверно, тринадцать переменных не описаны вовсе. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01AwY2Cg54RK7WZdvMaLV5Di
95 lines
4.3 KiB
Desktop File
95 lines
4.3 KiB
Desktop File
[Unit]
|
||
Description=yt-grab — веб-GUI для yt-dlp (yt.zapret.moe)
|
||
After=network-online.target
|
||
Wants=network-online.target
|
||
|
||
[Service]
|
||
Type=simple
|
||
User=codex-pve
|
||
Group=codex-pve
|
||
WorkingDirectory=/home/codex-pve/ytdlp-gui
|
||
|
||
# Домашняя папка примонтирована только на чтение (ProtectHome),
|
||
# а gunicorn пишет туда управляющий сокет — уводим его в приватный /tmp.
|
||
Environment=HOME=/tmp
|
||
Environment=YTG_HOST=127.0.0.1
|
||
Environment=YTG_PORT=8090
|
||
Environment=YTG_TRUST_PROXY=1
|
||
Environment=YTG_MAX_CONCURRENT=3
|
||
Environment=YTG_MAX_FILESIZE_MB=2048
|
||
# Квота держится заметно ниже свободного места на разделе: это
|
||
# гипервизор, и заполнить его корень — значит остановить виртуальные
|
||
# машины, а не просто отдать пользователю ошибку.
|
||
Environment=YTG_DISK_QUOTA_MB=5120
|
||
Environment=YTG_QUIET_ACCESS_LOG=1
|
||
|
||
# Один воркер: состояние задач держится в памяти процесса.
|
||
# Потоки обслуживают SSE-соединения и параллельные запросы.
|
||
# Потоков заметно больше числа одновременных загрузок: каждое SSE-соединение
|
||
# занимает поток на всё время загрузки, и при 16 потоках сервис подвешивался
|
||
# двумя клиентами. В nginx стоит limit_conn 24 на адрес.
|
||
ExecStart=/home/codex-pve/ytdlp-gui/.venv/bin/gunicorn \
|
||
--workers 1 \
|
||
--threads 64 \
|
||
--timeout 0 \
|
||
--graceful-timeout 30 \
|
||
--bind 127.0.0.1:8090 \
|
||
--access-logfile /dev/null \
|
||
--error-logfile - \
|
||
app:app
|
||
|
||
Restart=on-failure
|
||
RestartSec=5
|
||
|
||
# --- Изоляция ---
|
||
NoNewPrivileges=yes
|
||
PrivateTmp=yes
|
||
ProtectSystem=strict
|
||
ProtectHome=read-only
|
||
ReadWritePaths=/home/codex-pve/ytdlp-gui/downloads
|
||
ReadWritePaths=/home/codex-pve/ytdlp-gui/data
|
||
ProtectKernelTunables=yes
|
||
ProtectKernelModules=yes
|
||
ProtectControlGroups=yes
|
||
RestrictSUIDSGID=yes
|
||
RestrictNamespaces=yes
|
||
LockPersonality=yes
|
||
|
||
# --- Запрет исходящих во внутреннюю сеть ---
|
||
# Проверка адреса в приложении принципиально не закрывает DNS rebinding
|
||
# (yt-dlp резолвит имя заново и сам ходит по редиректам), поэтому запрет
|
||
# ставится на уровне ядра. Хост — гипервизор: во внутренней сети живут
|
||
# его API и виртуальные машины.
|
||
# Здесь НЕТ IPAddressAllow=any, и это существенно: с ним запреты ниже
|
||
# фактически не работали — проверено, Proxmox API на 10.20.0.1:8006 и
|
||
# соседняя ВМ оставались доступны. Когда список состоит только из
|
||
# запретов, systemd пропускает всё остальное, и внешняя сеть с DNS
|
||
# продолжают работать (тоже проверено).
|
||
#
|
||
# Петля (127.0.0.0/8, ::1) сознательно НЕ закрыта: фильтр systemd не
|
||
# различает направление, и запрет петли обрывает входящие соединения от
|
||
# nginx к этому же сервису — проверено, слушатель становится недоступен
|
||
# снаружи и сайт ложится. Поэтому службы на 127.0.0.1 остаются достижимы;
|
||
# закрыть их можно, только переведя связку nginx→приложение на
|
||
# unix-сокет.
|
||
IPAddressDeny=10.0.0.0/8
|
||
IPAddressDeny=172.16.0.0/12
|
||
IPAddressDeny=192.168.0.0/16
|
||
IPAddressDeny=169.254.0.0/16
|
||
IPAddressDeny=100.64.0.0/10
|
||
IPAddressDeny=fc00::/7
|
||
IPAddressDeny=fe80::/10
|
||
|
||
# --- Ресурсы хоста ---
|
||
# Сервис делит гипервизор с виртуальными машинами: ffmpeg при
|
||
# перекодировании иначе забирает все ядра у соседей.
|
||
CPUWeight=20
|
||
IOWeight=20
|
||
Nice=10
|
||
|
||
# --- Лимиты ресурсов ---
|
||
MemoryMax=2G
|
||
TasksMax=256
|
||
|
||
[Install]
|
||
WantedBy=multi-user.target
|