7.3 KiB
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
(create→warn→exists→error) и флаг Importable:
parseCSV→validateRow— структурная проверка без сети (email, пароль, дубликаты в файле).enrichRow— сверка с живым mailcow: несуществующий домен →error, существующий ящик →exists(пропуск). Работает только если заданы креды.checkDomainQuota— воспроизводит проверку mailcow: свободная квота домена =max_quota_for_domain− Σ(выделенных квот существующих ящиков). Если Σ квот импортируемых строк домена превышает свободную — возвращаетquotaWarn(в МиБ). UI показывает предупреждение и блокирует импорт до подъёма квоты домена (домены с квотой 0 = безлимит пропускаются).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).