Files
2026-07-14 13:21:45 +07:00

7.3 KiB
Raw Permalink Blame History

CLAUDE.md

This file provides guidance to Claude Code (claude.ai/code) when working with code in this repository.

Что это

mcimport — однобинарный инструмент (Go, только stdlib) для массового создания почтовых ящиков в mailcow из CSV через его REST API. Веб-морда встроена в бинарь (//go:embed index.html). Пайплайн пользователя: загрузка CSV → предпросмотр с валидацией → потоковый импорт с журналом.

Команды

# Сборка статического бинаря
CGO_ENABLED=0 go build -trimpath -ldflags="-s -w" -o mcimport .

# Локальный запуск (UI на http://localhost:8080)
MAILCOW_URL=https://mail.dom.ru ./mcimport

# Docker (порт публикуется только на 127.0.0.1 — ходить через SSH-туннель)
docker compose up -d --build

go vet ./...
gofmt -l .        # список неотформатированных файлов; -w для правки

Тестов в проекте нет. go.mod не имеет внешних зависимостей — только stdlib, поэтому go mod tidy/go get при обычной работе не требуются.

Архитектура

Весь backend — один файл main.go, весь frontend — один index.html (встроен). Состояние на сервере не хранится: инструмент stateless, API-ключ и пароли не пишутся на диск и не логируются.

Четыре HTTP-эндпоинта (net/http ServeMux с method-роутингом Go 1.22):

  • GET /api/config — отдаёт дефолты в UI (base URL, есть ли ключ в env, дефолт-квота).
  • GET /api/example — отдаёт образец CSV (exampleCSV) как вложение для скачивания.
  • POST /api/test — проверка связи: читает домены и ящики, подтверждает ключ и allow-list.
  • POST /api/preview — разбор CSV (multipart), валидация, обогащение против живого mailcow. Form-поле domain включает режим override (см. ниже). В ответе — quotaWarnings (проверка доменной квоты).
  • POST /api/import — создание ящиков, потоковый NDJSON (по строке результата на ящик, с flusher.Flush()).

Три конвейерных этапа обработки строки CSV, каждый меняет Row.Status (createwarnexistserror) и флаг Importable:

  1. parseCSVvalidateRow — структурная проверка без сети (email, пароль, дубликаты в файле).
  2. enrichRow — сверка с живым mailcow: несуществующий домен → error, существующий ящик → exists (пропуск). Работает только если заданы креды.
  3. checkDomainQuota — воспроизводит проверку mailcow: свободная квота домена = max_quota_for_domain − Σ(выделенных квот существующих ящиков). Если Σ квот импортируемых строк домена превышает свободную — возвращает quotaWarn (в МиБ). UI показывает предупреждение и блокирует импорт до подъёма квоты домена (домены с квотой 0 = безлимит пропускаются).
  4. handleImport — POST на /api/v1/add/mailbox, ответ разбирается parseMailcowMsg. Используется только add — существующие ящики никогда не модифицируются (идемпотентность).

Ключевые соглашения

  • Разрешение кредовresolveCreds: значения из тела запроса приоритетнее env (MAILCOW_URL/MAILCOW_API_KEY). Ключ можно задать через env и не вводить в UI.

  • Ответы API mailcow имеют форму [{"type": "...", "msg": ...}]; type != "success" считается провалом. msg бывает строкой или массивом — разворачивается в flattenMsg.

  • CSV-парсинг терпимый: авто-определение разделителя (sniffDelimiter: ,/;/таб; на практике используется ;), снятие BOM, LazyQuotes, RU-алиасы заголовков (headerAliases + normHeader игнорирует регистр/пробелы/подчёркивания). email можно заменить парой localpart+domain.

  • Режим override-домена (parseCSV(..., overrideDomain)): если домен выбран в UI и передан form-полем domain, колонка логина (email/login/localpart) трактуется как localpart без домена, email собирается как localpart@overrideDomain. Полный email в значении терпится — берётся часть до @ с пометкой в Notes. Домен нормализуется в lowercase в handlePreview.

  • Ошибки пользователю — на русском; комментарии в коде — на английском. Держать этот сплит.

  • INSECURE_SKIP_VERIFY=true отключает проверку TLS (для внутреннего CA mailcow).

  • Дефолт квоты — константа defaultQuotaMiB (10240 МиБ). Одна точка правды; дублируется в docker-compose.yml, README.md и value поля #quota в index.html.

  • Единицы квот: fetchDomains/fetchMailboxes возвращают квоты в байтах (как отдаёт mailcow), Row.Quota — в МиБ (строка). checkDomainQuota сводит всё к байтам, quotaWarn наружу — в МиБ. UI форматирует МиБ→ГБ (gb()).

  • Повтор в UI без перезагрузки файла (index.html): previewRows/importResults держатся в памяти. «Повторить проверку» = повторный runPreview() (файл в fileInput сохраняется). «Повторить неудавшиеся» = runImport() по строкам с ok:false из importResults — идемпотентно, т.к. упавшие ящики не создались, успешные не трогаются.

При добавлении полей ящика править три места синхронно: заголовки/алиасы CSV (headerAliases), структуру Row + validateRow, и payload в handleImport.

Требования на стороне mailcow

  • API-ключ с правами Read-Write (Configuration → Access → API).
  • IP, с которого ходит mcimport, добавлен в API_ALLOW_FROM — иначе отказ доступа (401/403).