Контекст
25 декабря. Рождество. У меня работающий туннель, но пользоваться им может только человек, который не боится терминала и YAML-конфигов. То есть я. А хочется, чтобы это был нормальный продукт — с кнопками, авторизацией и автоматическими обновлениями. План на день: веб-панель, десктопное приложение, база данных, CI/CD, релиз v1.0. За один день. Амбициозно, но я люблю такие челленджи.
Веб-панель
Каждый нормальный сервис должен иметь дашборд. Мой — на Vue 3 с Tailwind CSS. Тёмная тема, конечно. Интернационализация: русский и английский.
Что умеет:
- Управление токенами — создать, удалить, задать права
- Домены — зарезервировать поддомен, чтобы никто не занял
- Мониторинг — какие туннели сейчас активны
- Администрирование — управление пользователями, аудит-лог
Вишенка: вся панель встраивается прямо в бинарник сервера через go:embed. Это фишка Go — можно упаковать файлы прямо в скомпилированную программу. Один файл = весь продукт. Никакого nginx для статики, никаких отдельных деплоев фронтенда.
Десктопное приложение
Для GUI я выбирал между Electron и Wails. Electron — это по сути целый Chrome + Node.js, и вашему приложению понадобится 150+ мегабайт. Wails — это Go-бэкенд + системный WebView. Результат: ~15MB.
Главная фишка Wails: пишешь функцию на Go — и вызываешь её из фронтенда напрямую. Никаких REST API между клиентом и интерфейсом. Вызвал Connect() из Vue — и оно работает.
GUI получил:
- 6 экранов: дашборд, бандлы (наборы туннелей), история, логи, настройки, авторизация
- Синхронизация между устройствами — настроил бандлы на одном компе, залогинился на другом — всё на месте
- Безопасное хранение токенов в системном keyring (а не в текстовом файле)
REST API
Для бэкенда взял chi-router — легковесный и удобный для Go. Полный набор: - Регистрация по инвайт-кодам (контролируемый доступ) - Двухфакторная аутентификация (TOTP) - JWT-токены (access + refresh) - CRUD для всего: токены, домены, туннели, пользователи SQLite в качестве базы. Для self-hosted решения — самое то. Никакого отдельного PostgreSQL, файл рядом с бинарником.
CI/CD: ад в пяти актах
А вот тут начинается честная часть. Настроил GitHub Actions: тесты, линтер, билд, релиз. Запушил. И начался ад. Реальный git log:
2ad265d ci: add GitHub Actions workflows for CI and releases
fc81616 fix(ci): build frontends before tests and lint
4a3d7aa fix(ci): exclude GUI from CI checks, use placeholder dist
8b30028 fix(ci): disable errcheck linter, use exclude-dirs
c3359c7 fix(ci): explicitly disable errcheck linter
03da85c fix: remove unused variables and apply gosimple suggestion
Пять фиксов подряд. Каждый — push, ожидание CI (минуты!), чтение логов, исправление, снова push.
Акт 1: CI не билдит фронтенд перед Go-тестами. go:embed хочет папку dist/, а её нет. Упс.
Акт 2: GUI-код (Wails) требует специфические системные библиотеки. На CI они не нужны. Исключаем.
Акт 3: Линтер находит сотню мест, где код игнорирует ошибки. Типичный Go: defer conn.Close() — а Close может вернуть ошибку. Линтер в истерике. Отключаем errcheck.
Акт 4: Отключил errcheck в конфиге — а он всё равно включён. Оказывается, надо не только убрать из enable, но и добавить в disable. Спасибо, golangci-lint.
Акт 5: Мелочи — неиспользуемые переменные и предложения по упрощению.
Урок: настраивайте CI ДО того, как написали 24 тысячи строк кода. Или хотя бы запускайте линтер локально. Я не сделал ни того, ни другого.
Масштаб безумия
Первый коммит дня — 157 файлов, 23 922 строки добавлений. В одном коммите. Да, я знаю, что это плохая практика. Да, code review на такой коммит невозможен. Но когда ты один и дедлайн «сегодня» — так бывает. Что вошло: - 25+ Vue-компонентов для веб-панели - 6 экранов GUI-клиента - 10 файлов API-хэндлеров - 8 репозиториев базы данных - Система аутентификации (JWT + TOTP + инвайт-коды) - 5 GUI-сервисов с Wails-биндингами - Системный keyring для токенов - i18n (русский + английский)
Итог
К ночи 25 декабря — релиз v1.0:
a80e99b chore(master): release 1.0.0
За два дня есть: - Работающий туннельный сервис (HTTP, TCP, UDP) - Веб-панель управления - Десктопное приложение - REST API с полной аутентификацией - CI/CD с автоматическими релизами - Бинарники под все платформы Тестов — ноль. Документации — минимум. Errcheck — отключён. Но продукт работает, собирается автоматически и имеет номер версии. Не идеально, но выпущено — а дальше можно итерировать.
Коммиты: 536e074..a80e99b — от консоли к продукту
В следующей части: месяц перерыва, первый деплой на боевой сервер и загадочная проблема — localhost, который не совсем localhost. Спойлер: 127.0.0.1 и ::1 — это разные вещи, и это ломает всё.