# Инструкции для работы с репозиторием ## Обзор проекта `iptvc` (IPTV Checker) — консольное приложение на Go для проверки IPTV-плейлистов в форматах M3U/M3U8. Программа умеет загружать плейлисты из локальных файлов, по URL и из ini-файла, проверять доступность плейлистов и отдельных каналов, выводить результаты в журнале или JSON, а также запускать встроенный веб-интерфейс. Проект является частью экосистемы `m3u.su`. Исходный код размещается на Gitea: `https://git.axenov.dev/IPTV/iptvc`. Основные технологии: - Go 1.23.6 или новее согласно `go.mod` и README. - Cobra для CLI-команд и флагов. - YAML для конфигурации. - INI для списка плейлистов. - Кеш через `github.com/redis/go-redis/v9` для кеширования результатов. - `godotenv` для автоматической загрузки `.env`. - Docker и Docker Compose для контейнерного запуска. - Встроенный HTTP-веб-сервер для просмотра плейлистов и результатов проверки. ## Структура репозитория - `main.go` — точка входа, вызывает `cmd.Execute()`. - `cmd/` — CLI-слой на Cobra: - `root.go` — корневая команда, глобальные persistent-флаги (`--config`, `--verbose`, `--debug`, `--log-level`). - `check.go` — команда `check`: одно-/многоитерационная проверка плейлистов с выводом в журнал или JSON. - `serve.go` — команда `serve`: HTTP-веб-сервер, при необходимости запускает фоновую проверку через `--check` или `check.startOnServe`. - `version.go` — команда `version`, печатает `iptvc v`. - `flags.go` — определение общих флагов для `check`/`serve` и функции `applyAppOverrides`/`applyCacheOverrides`/`applyCheckOverrides` (используют `cmd.Flags().Changed()`, поэтому срабатывают только при явной передаче). - `flags_test.go` — тесты применения CLI-переопределений к конфигурации. - `app/` — прикладная логика приложения. - `app/app.go` — инициализация приложения и глобальных структур `Args`/`Config`/`Cache`, точка входа `app.Init()`/`app.InitCache()`. - `app/checker/` — подготовка и проверка плейлистов и каналов. - `app/cache/` — абстракции и работа с кешем (через `go-redis/v9`). - `app/config/` — структуры конфигурации, значения по умолчанию, YAML, переменные окружения и тесты. - `app/inifile/` — чтение ini-файлов со списками плейлистов. - `app/playlist/` — модели и обработка IPTV-плейлистов. - `app/tagfile/` — чтение файла тегов каналов. - `app/web/` — веб-сервер, маршруты, шаблоны, OpenAPI-спецификация и отображение данных (`server.go`, `handlers.go`, `views.go`, `templates.go`, `openapi.go`). - `app/logger/` — настройка журналирования. - `app/utils/` — вспомогательные функции. - `docker/cache/` — конфигурация и данные для контейнера кеша (`valkey.conf`). - `channels.json` — локальный файл тегов каналов. - `playlists.ini.example` — пример ini-файла со списками плейлистов. - `config.yml.example` — пример конфигурации приложения. - `.env.example` — пример переменных окружения. - `compose.yml` — окружение из приложения, документации и кеша. - `Dockerfile` — минимальный runtime-образ на `alpine:3.22.5`; берёт уже собранный бинарь из `bin/linux_${TARGETARCH}/iptvc`. - `Makefile` — очистка, кросс-компиляция бинарей и образов (см. раздел «Сборка и запуск»). - `README.md` — пользовательская документация, команды запуска, конфигурация и API-маршруты. - `LICENSE` — лицензия MIT. ## Архитектура и поток выполнения 1. `main.go` вызывает `cmd.Execute()`. 2. `cmd/root.go` регистрирует глобальные флаги и дочерние Cobra-команды. 3. Команда `check` инициализирует приложение, применяет CLI-переопределения, подключает кеш, получает плейлисты и запускает проверку. 4. Команда `serve` поднимает веб-сервер и при необходимости запускает фоновую проверку через флаг `--check` или настройку `check.startOnServe`. 5. Конфигурация загружается со следующим приоритетом: встроенные значения по умолчанию, `config.yml`, переменные окружения и явно переданные CLI-флаги. 6. Результаты проверки могут сохраняться в кеш и отображаться веб-сервером. При недоступном кеше веб-интерфейс показывает неизвестный статус. Не следует помещать бизнес-логику в `main.go` или CLI-команды, если её можно разместить в соответствующем пакете `app/`. CLI-слой должен отвечать за разбор аргументов, инициализацию и координацию прикладных компонентов. ## Сборка и запуск Для локальной разработки требуется Go версии 1.23.6 или новее. `Dockerfile` использует `alpine:3.22.5` как базовый runtime-образ; бинарный файл собирается на хосте через `go build` и копируется в образ. Основные команды: ```bash # Показать доступные цели Makefile make help # Быстро запустить приложение из исходников go run . # Запустить CLI-справку go run . help # Проверить локальный плейлист go run . check -f mypls.m3u # Проверить плейлист по URL go run . check -u https://example.com/playlist.m3u # Запустить веб-интерфейс на порту 8800 go run . serve -p 8800 # Запустить веб-интерфейс с фоновой проверкой go run . serve -i playlists.ini -p 8800 --check # Собрать текущую платформу go build -o iptvc . # Запустить тесты всех пакетов go test ./... # Проверить форматирование gofmt -w . ``` В блоке выше команда форматирования приведена как ориентир: `gofmt` следует запускать по Go-файлам, например `gofmt -w main.go cmd app`, если оболочка или используемая версия инструмента не поддерживает обработку директорий так, как ожидается. Цели Makefile: - `make clear` — удалить каталог `bin/` с артефактами сборки. - `make linux GOARCH=amd64` — собрать Linux-бинарный файл и ZIP-архив. - `make win GOARCH=amd64` — собрать Windows-бинарный файл и ZIP-архив. - `make darwin GOARCH=amd64` — собрать macOS-бинарный файл и ZIP-архив. - `make release` — собрать архивы для Linux, Windows и macOS под `amd64` и `arm64`. - `make test` — запустить все тесты (`go test ./...`). - `make image` — собрать одноархитектурный runtime-образ `git.axenov.dev/iptv/iptvc:latest` (по умолчанию под `linux/amd64`; переопределяется через `GOARCH`). - `make image-all` — собрать и запушить multi-arch манифест в registry (по умолчанию `linux/amd64,linux/arm64`; переопределяется через `IMAGE_PLATFORMS`). По умолчанию `GOARCH=amd64`. Поддерживаемые значения: `amd64` и `arm64`. **Бинарники собираются на хосте через `go build`** с кросс-компиляцией (`CGO_ENABLED=0` + `GOOS`/`GOARCH`). Это справедливо для всех шести целевых платформ (linux/darwin/windows × amd64/arm64) и работает одинаково на любом хосте с Go 1.23+. **Образы собираются в Docker** через `docker build` (одноархитектурный) или `docker buildx build --platform ... --push` (multi-arch). `Dockerfile` берёт уже собранный бинарь из build context (`bin/linux_${TARGETARCH}/iptvc`), а не собирает его сам — поэтому build context должен содержать нужный бинарь перед запуском `make image`/`make image-all`. `make image` и `make image-all` автоматически зависят от `make linux`, чтобы контекст был полным. **Multi-arch образы (`make image-all`)** используют `docker buildx` и **всегда пушат** результат в registry. Это ограничение buildx: multi-arch манифест нельзя положить в локальный Docker daemon (`--load` не работает с несколькими платформами одновременно). Если нужен только локальный образ одной архитектуры — используйте `make image`. Сжатие UPX не используется намеренно: UPX ломает нотаризацию Mach-O на современных macOS и снижает надёжность запуска Windows-бинарей. Экономия от `-ldflags="-s -w"` (~30%) сохраняется. Версия и коммит вкомпилируются в бинарь через `-ldflags="-X axenov/iptv-checker/app.VERSION=... -X axenov/iptv-checker/app.COMMIT=..."`; переменные берутся из `git describe` и `git rev-parse` на хосте в момент запуска `make`. ### Сборка конкретной версии (по тегу или коммиту) `Makefile` всегда берёт код из текущей рабочей копии (`COPY . .` в Dockerfile) и версию из `git describe`/`git rev-parse` **на хосте в момент запуска**. Поэтому, чтобы собрать произвольную версию, нужно сначала переключить рабочую копию на нужный тег или коммит, а **затем** запустить `make`. `Makefile` сам не делает `git checkout` и не использует `git archive` — это осознанный выбор. Сборка остаётся детерминированной относительно того, что лежит в репозитории, а не относительно того, что пользователь хотел собрать. Любые попытки "обмануть" мету через `make VERSION=v1.0.0` без `git checkout` приведут к тому, что в бинарь попадёт код текущей ветки с чужой версией в ldflags. #### Пример: собрать релиз по тегу `v1.0.0` ```bash # 1. Убедиться, что рабочая копия чистая (без незакоммиченных правок). # Иначе `git checkout` откажется переключаться. git status # 2. Запомнить, на чём мы были, чтобы вернуться. git rev-parse --abbrev-ref HEAD # например: main git rev-parse --short HEAD # например: 0de883f # 3. Переключиться на нужный тег. Сборка по тегу даёт точно код этой версии. git checkout v1.0.0 # 4. Собрать всё. VERSION и COMMIT будут вычислены из тега автоматически. make release # 5. Проверить, что в бинаре именно та версия. ./bin/linux_amd64/iptvc version # ожидаем: iptvc v1.0.0 # 6. Вернуться туда, где были. git checkout main # или: git checkout 0de883f ``` #### Пример: собрать по конкретному коммиту ```bash git checkout 0de883f make release git checkout main ``` #### Сборка в отдельной worktree (рекомендуется) Переключение ветки в основной рабочей копии неудобно: IDE перезагружает индекс, открытые файлы могут стать невалидными, локальные правки блокируют checkout. Лучше использовать `git worktree` — это отдельная директория с привязкой к конкретному коммиту, основная рабочая копия не трогается. ```bash # 1. Создать временный worktree на теге v1.0.0. git worktree add /tmp/iptvc-v1.0.0 v1.0.0 # 2. Собрать там. cd /tmp/iptvc-v1.0.0 make release # 3. Убедиться, что бинарь содержит нужную версию. ./bin/linux_amd64/iptvc version # ожидаем: iptvc v1.0.0 # 4. Убрать worktree, когда он больше не нужен. cd /path/to/main/repo git worktree remove /tmp/iptvc-v1.0.0 ``` Этот способ безопаснее `git checkout`: основная рабочая копия и все незакоммиченные изменения остаются нетронутыми. #### Проверка, что собрано то, что нужно После сборки в бинаре зашиты `app.VERSION` и `app.COMMIT`, которые выводятся командой `version`: ```bash ./bin/linux_amd64/iptvc version # iptvc v1.0.0 ``` Если версия не совпадает с ожидаемой — значит, рабочая копия была не на том коммите. Перепроверьте `git log -1 --oneline` в каталоге, из которого запускался `make release`. #### Частые ошибки - **"build failed: COPY failed: uncommitted changes"** — забыли `git status`. `COPY . .` в Dockerfile включает только файлы, отслеживаемые git'ом, но локальные изменения в `Dockerfile`/`go.mod` и т.п. могут запутать. Всегда делайте `git status` перед сборкой чужой версии. - **"Permission denied" в `bin/`** — каталог `bin/` остался от предыдущей попытки с `--output=local` или от работы под другим пользователем. Решение: `make clear` (или `rm -rf bin`). - **Бинарь собран, но версия "dev"/"unknown"** — скорее всего, вы в свежем клоне без тегов. Выполните `git fetch --tags` и повторите `git checkout v1.0.0`. Перед запуском приложения для конфигурации обычно следует скопировать `.env.example` в `.env`, а `config.yml.example` — в `config.yml`, после чего адаптировать значения под окружение. Не добавляйте реальные пароли, токены или локальные URL в Git. ## Docker Для контейнерного окружения используется файл `compose.yml`, а не устаревшее имя `docker-compose.yml`. Ожидаемые локальные файлы для сервиса `iptvc`: - `playlists.ini` монтируется в `/app/playlists.ini`. - `channels.json` монтируется в `/app/channels.json`. - `config.yml` монтируется в `/app/config.yml`. - `.env` передаётся через `env_file`. Основные команды: ```bash # Собрать и запустить сервисы в фоне docker compose up -d --build # Посмотреть логи приложения docker compose logs -f iptvc # Остановить окружение docker compose down ``` В compose-окружение входят: - `iptvc` — веб-интерфейс и фоновая проверка плейлистов. - `cache` — хранилище результатов проверки. - `docs` — отдельный сервис документации, если его контекст доступен в окружении. Обратите внимание: текущий `compose.yml` ссылается на контексты `./iptvc` и `./docs`, поэтому запуск из каталога, содержащего только этот репозиторий, может потребовать внешней структуры проекта или корректировки контекстов. ## Конфигурация Поддерживаются `config.yml`, `.env` и CLI-флаги. CLI-флаги имеют наивысший приоритет только при явной передаче. ### Источники правды Единственными источниками правды для значений по умолчанию являются `config.yml.example` и `.env.example`. Значения, встроенные в код (`app/config/config.go` → `defaults()`, `validate()`), и дефолты CLI-флагов (`cmd/flags.go`, `cmd/root.go`, `cmd/serve.go`) обязаны **полностью совпадать** с этими файлами. Порядок внесения изменений в конфигурацию: 1. **Сначала** — правки в `config.yml.example` и `.env.example`. 2. **Затем** — правки в коде: `app/config/config.go` (`defaults()`, константы, `validate()`), `cmd/flags.go`, `cmd/root.go`, `cmd/serve.go`. 3. **Затем** — обновление тестов (`app/config/config_test.go`, `cmd/flags_test.go`). 4. **Затем** — обновление документации (`README.md`, `docs/content/...`). При рефакторинге и работе с документацией сверяйтесь в первую очередь с `config.yml.example` и `.env.example`, а не с кодом или тестами. Если обнаружено расхождение между example-файлами и кодом — example-файлы считаются эталоном, код приводится к ним. Ключевые группы настроек: - `app` — часовой пояс, режим отладки, уровень журнала и пути к `playlists.ini` и `channels.json`. - `server`/`site` — адрес и порт веб-сервера, базовый URL, размер страницы, ссылки сайта и заголовок. - `check.playlists` — User-Agent, тайм-ауты, задержки и параллелизм проверки плейлистов. - `check.channels` — User-Agent, тайм-аут, размер диапазона байтов, задержки и параллелизм проверки каналов. - `cache` — включение кеша, адрес подключения, база и TTL. Значения задержек и некоторых параметров могут задаваться как числом или диапазоном `[min, max]`. User-Agent может быть строкой или массивом строк; при массиве выбирается случайное значение. Веб-команда поддерживает как минимум следующие сценарии: ```bash # Сервер без фоновой проверки ./iptvc serve -i playlists.ini -p 8800 # Сервер с бесконечной фоновой проверкой каждые 60 секунд ./iptvc serve --check --repeat 0 --playlists-all-cooldown 60 ``` Основные HTTP-маршруты описаны в README: `/`, `/page/{N}`, `/{code}`, `/{code}/details`, `/api/playlists/{code}`, `/api/version`, `/api/health` и `/api/stats`. ## Тестирование Тесты находятся рядом с тестируемым кодом и используют стандартный пакет `testing`. Сейчас явно присутствуют тесты CLI-флагов в `cmd/flags_test.go`; они проверяют применение переопределений приложения, кеша и параметров проверки. Перед изменением CLI или конфигурации запускайте: ```bash go test ./... go vet ./... ``` При обновлении конфигурационных структур синхронно проверяйте `config.yml.example`, `.env.example`, `README.md` и документацию в `docs/content/common/config/config.md`, `docs/content/iptvc/commands/check.md`, `docs/content/iptvc/commands/serve.md` и связанных страницах. См. также раздел «Источники правды» выше — правки начинаются с example-файлов. При добавлении флага желательно добавить тесты на: - значение по умолчанию без флага; - явное включение и выключение boolean-флага; - переопределение значения из конфигурации; - корректный разбор чисел, диапазонов и списков; - взаимодействие с приоритетом `config.yml`, окружения и CLI. Отдельных интеграционных тестов веб-сервера, кеша и сетевой проверки плейлистов в просмотренных файлах не обнаружено. Такие тесты требуют изолированных фикстур и не должны зависеть от случайных внешних IPTV-ресурсов. ## Стиль разработки - Соблюдайте стандартный стиль Go и форматируйте изменённые Go-файлы через `gofmt`. - Используйте имена пакетов и экспортируемых сущностей в соответствии с идиомами Go. - Сохраняйте существующие заголовки лицензии в файлах, где они уже присутствуют. - Комментарии к экспортируемым типам и функциям должны объяснять их назначение. - Не добавляйте секреты, реальные данные доступа к кешу и рабочие плейлисты в репозиторий. - Для ошибок и сетевых операций сохраняйте существующую практику явной обработки ошибок и логирования. - Не меняйте формат CLI, конфигурации или API-маршрутов без обновления README и соответствующих тестов. - При работе с конфигурацией учитывайте различие между отсутствующим CLI-флагом и флагом, переданным явно: в `cmd/serve.go` это используется для корректного переопределения порта и хоста. - Избегайте сетевых запросов и зависимости от внешних сервисов в модульных тестах. ## Важные ограничения и наблюдения - `channels.json`, `playlists.ini`, `config.yml` и `.env` могут быть локальными входными данными; проверяйте их наличие перед запуском сценариев из документации. - Кеш включён по умолчанию в `config.yml.example` и `.env.example`; compose-окружение запускает кеш и передаёт настройки через `.env`. - Бинарные файлы и архивы сборки создаются в `bin/` и не должны вручную добавляться в исходный код. - README указывает минимальную версию Go 1.23.6; Dockerfile использует `alpine:3.22.5` и не собирает Go-код внутри контейнера. - При обновлении конфигурационных структур синхронно проверяйте `config.yml.example`, `.env.example`, README и тесты CLI-флагов. Example-файлы — источники правды (см. раздел «Источники правды»).