# Инструкции для работы с репозиторием

## Обзор проекта

`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<VERSION>`.
    - `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-файлы — источники правды (см. раздел «Источники правды»).
