Как выложить приложение в опенсорс: секреты в гите, чужие PR и немного советов

Обсудить

Этот текст написан в Сообществе, в нем сохранены авторский стиль и орфография

Аватар автора

Антон Черкасов

Страница автора

Месяц назад я писал сюда, как от лени и жадности собрал себе costwatch — утилиту, которая обходит все мои облака и считает, сколько где натикало. По ходу статьи я упомянул, что намереваюсь выложить непосредственно сам проект, как причешу — и вот на этом «причешу» я и хочу остановиться.

Потому что «выложить приложение в опенсорс» звучит как git push, а на деле это отдельная немаленькая задача, о которой никто не предупреждает. Ниже я поведаю о нюансах, которые ждут почти любого, кто решится показать свой пет-проект интернету.

Краткая напоминалка

Если совсем в двух словах о моём costwatch — это маленький self-hosted-сервис на Go, один бинарь в контейнере через Docker Compose. Раз в час обходит все мои облака, сводит траты в одно число, прикидывает счёт к концу месяца и пишет в личку, если находит ресурс, который тикает деньги, но в деплое не значится. Ключи провайдеров лежат в гите зашифрованными через sops+age, метрики уходят в мои же VictoriaMetrics и Grafana, сам сервис крутится на VPS в Selectel, а бэкапы его базы в S3 у Cloud4U. Вот, собственно, и всё, что нужно держать в голове дальше.

Теперь речь про непосредственно публикацию costwatch. В целом, главное что надо понимать — «работает у меня», «можно показать другим» и «другие смогут это запустить» — три очень разных состояния. Между ними и выстроена моя статья. Дальше пойду по нюансам, в том порядке, в каком я на них натыкался.

Нюанс первый: секреты в истории гита

Самая опасная и самая частая проблема. Правило простое: гит помнит всё. Даже если вы удалили файл с ключом несколько лет назад, он живёт в истории, и первый же особо любопытный человек его оттуда достанет. Публиковать текущее состояние репозитория безопасно ровно настолько, насколько чиста вся его история.

Я здесь был осторожен. Сами по себе sops-зашифрованные ключи выкладывать не страшно — на то он и sops. Но до того как я прикрутил sops, в самых первых коммитах жил один токен открытым текстом. Перед публикацией я прогнал историю сканером — и, разумеется, нашёл его:

gitleaks detect --source. --log-opts="--all"

Дальше два пути. Аккуратный — вычистить конкретные файлы из истории через git filter-repo. Ленивый, но честный, — обнулить историю целиком, если она вам не дорога:

rm -rf.git
git init
git add.
git commit -m "initial public release"

Мне история была не дорога (и вдобавок она вышла чересчур уродливой), так что я выбрал второе: один коммит, прошлого нет. Но что бы вы ни выбрали, есть железное правило: секрет, который хоть раз попал в гит, надо считать скомпрометированным и отзывать.

Нюанс второй: README и лицензия работа, которую делать не хочется

Неприятная правда: без README ваш проект никто не запустит, а без лицензии — юридически никто не имеет права даже трогать. И то и другое — унылая работа, которую хочется отложить, но откладывать нельзя. Лицензию проще всего взять готовую и не думать (MIT или Apache-2.0 закрывают большинство пет-проектов). А README держится на трёх тезисах: что это, как запустить и — отдельным разделом — чего оно НЕ умеет.

Последний пункт особенно важен: именно он экономит вам будущие issue (замечания и вопросы) от людей. Я, например, честно написал, что costwatch не считает счёт до рубля (почему — разбирал в первой части, если коротко: egress провайдер сообщает задним числом). README я сначала надиктовал нейросети — вышло гладко и ожидаемо мёртво, из разряда «данное решение позволяет эффективно…». Переписал руками. А quickstart дал самым коротким из возможных — куском docker-compose, чтобы человек скопировал и начал работу:

# docker-compose.yml из README, минимальный запуск
services:
costwatch:
image: costwatch:latest # или собери сам из исходников
volumes:
—./providers.yaml:/etc/costwatch/providers.yaml:ro
environment:
— COSTWATCH_AGE_KEY_FILE=/run/secrets/age.key

Доки я ненавижу писать примерно как CSS. Но без них проект, как говорится, как без рук.

Нюанс третий: пользователи не молчат о проблемах

Как только код становится публичным, к вам придут люди с ситуациями, которых вы не предусматривали. И почти всегда выясняется, что в самом ядре у вас зашито допущение, которое вы считали единственно возможным, — просто потому что у вас-то было именно так.

Поясню на примере. Первый (и пока единственный, ха-ха) issue к моему costwatch был буквально о следующем: «вам стоит добавить вот этого провайдера», которым я не пользуюсь и протестировать не могу. Интерфейс Provider держался ровно на двух облаках — тех, что есть у меня. А первый внешний PR принёс бы провайдера с посекундным биллингом, и мой fractionOfMonthElapsed() бы посыпался, потому что я зашил в него помесячную логику. Пришлось вынуть расчёт цены из ядра и сделать его сменным:

// было: цену считали одинаково для всех
// стало: провайдер сам объясняет, как из инвентаря получаются деньги
type Pricer interface {
Estimate(inv []Resource, at time.Time) []Charge
}

И тут же вылезает вопрос, или как я её называю, дилемма мейнтейнера: мержить ли код под случай, который ты не можешь проверить? Универсального ответа нет, но рабочий компромисс есть: такой код живёт в отдельной папке contrib/ с честной пометкой «поддерживается автором PR, а не мной». Работает — ну и хорошо. Сломалось — issue к тому, кто его принёс.

Нюанс четвертый: поддержка это работа, и бесплатная

Про это стоит знать до git push, а не после: выложив код, вы бесплатно нанимаетесь его поддерживать. Если вы активно развиваете свой проект, важно учитывать замечания пользователей, постоянно дополнять документацию и тестировать свой же функционал с разных сторон.

И конечно держаться это может не только на энтузиазме. Ещё в первой части я сказал, что SaaS из costwatch не делаю, потому что считать чужие счета — значит просить у людей ключи от всех их облаков, а отдавать биллинговые ключи третьей стороне не станет ни один разумный человек. Так вот, у чистого опенсорса цена тоже есть — просто платится она не деньгами, а вашими вечерами. И это нормально.

Что в итоге

Ради чего всё это, если денег опенсорс не приносит, а вечера ест? Ради двух вещей, которые в одиночку не получить.

Первое — чужие глаза. По опыту знаю, что большая часть багов и недоработок вскрывается в ходе тестирования или продакшена со стороны независимых пользователей. Второе — и это важнее — ощущение, что твоя поделка кому-то пригодилась. Незнакомый человек искал решение проблемы, нашёл ваш небольшой проект и получил ровно тот выигрыш, ради которого вы это когда-то затевали для себя одного. Это непередаваемые ощущения.

Что бы я сделал иначе? Ну, прогнал бы gitleaks до первого публичного коммита, а не перед ним. Не хардкодил бы прайс-листы в ядро. И написал бы README в тот же вечер, что и первую рабочую версию, пока ещё сам помнишь, как эта штука запускается.

Модуль под GPU-траты у меня всё ещё в планах, как и алерт на аномалии. Дойдут руки — будет третья часть. Не дойдут — ну, тут как с облаками: обещать не берусь.

сервисыприложения