
С 1 июля 2025 года хранить персональные данные россиян в иностранных сервисах больше нельзя. Назрел вопрос, что же делать, например, с регистрациями на мероприятия, которые так удобно складывались из формочек Tilda напрямую в Google Sheets без СМС и регистрации. В статье расскажу, как я эту задачу решил с помощью бесплатного open-source сервиса Grist, платного, но копеечного Яндекс Облака и сомнительного Go кода.
Задача — натараканить self-hosted сервис для замены Google Sheets, чтобы также легко и непринужденно сохранять данные из формочек Tilda, но уже куда-то себе за пазуху. В идеале решение должно быть open-source. На то есть две причины:
1. Это бесплатно. Значит, будет просто согласовать и внедрить;
2. Гипотетические ИБ-шники смогут проверить код на все уязвимости.
Исходные требования к сервису
Во-первых, сервис точно должен быть бесплатным, так как оплатить что-то из РФ сейчас довольно проблематично, а любое решение с припиской enterprise настолько дорогое, что на сайте даже цену не пишут.
Во-вторых, должен быть удобный API, так как иначе данные из формочек Tilda автоматизировано никуда не положишь. Удобный, потому что хочется работать как разработчик, а не как исследователь далеких документаций.
В-третьих, нужна авторизация в каком-то виде. Исходно проблема в том, что данные нужно хранить секурно, все дела. Бонусом хочется иметь возможность кастомизировать авторизацию. Нужно уметь использовать существующие в компании методы, чтобы не генерить логин-пароль для конкретного сервиса. Таким пользоваться никто просто не будет. Конкретно в нашем случае планировалось подключать старый добрый ldap.
В-четвертых, должен быть субъективно красивый UI и как можно больше кастомизации под себя.
В-пятых, нужна возможность разграничивать доступ на просмотр и редактирование табличек.
Кандидаты
Я рассматривал исключительно self-hosted и open-source варианты. Вот что нашел.
Nocodb — выпадает сразу и в поисковике и GPT, если ищешь аналоги Google Sheets. Сразу не прошел по пункту с авторизацией, так как сторонняя авторизация доступна только в enterprise-версии.
Baserow — тоже самое, но по UI больше понравился.
OnlyOffice — пушка с точки зрения UI, ldap тоже есть. Поначалу показалось, что все прекрасно, но, побороздив океаны их сомнительно документации, я не нашел нормальных API-методов для взаимодействия с таблицами. Даже за деньги нет, хотя казалось бы.
Grist — вот этот касатик с ходу показался самым хорошим. В UI чуть странновато, но минут через 20 работа с таблицами становится понятной. Есть возможности писать формулы, но не как в Excel. По всем пунктам требований Grist проходил, кроме авторизации через ldap
но там бабуля надвое сказала.
Изучил и другие аналогичные сервисы. Не вижу смысла их упоминать из-за ранней стадии разработки и аналогичных проблем с авторизацией.
Погружение в Grist
Сам по себе кор Grist поднимать смысла нет. У него есть старший брат под названием grist-omnibus, который сразу в себя включает кор, сервис для авторизации – Dex, Traefik и некий Middleware, чтобы связать все это дело вместе.
Этот кофе 4-в-1 разворачивается в docker одной командой и с порога дает возможность авторизации через учетки Google и Microsoft, если прописать в нужном месте ключики. Под нужным местом подразумеваю dex.yaml, который дан как шаблон с репозиторием Omnibus. Переключение Dex на ldap не потребовало много усилий, так как у него есть ничего такая документация (и даже с примерами).
И вот, казалось бы, все отлично, все хорошо, все требы выполняются, можно бежать за пивом и звонить маме, чтобы рассказать, какой я молодец, но нет.
Вампирский нюанс
Dex — это просто сервис, который разными способами может или пустить тебя или не пустить. Grist же — самодостаточный сервис, и в целом может жить и работать без Dex. Но, правда, и без ldap тогда тоже.
В самом Grist при первом запуске создается некая организация, в которой и будет храниться всякое разное в табличках. Странность заключается в том, что каждого нового пользователя нужно персонально пригласить в эту самую организацию. Без этого ничего нельзя сделать — access denied. Возможно, создатели вдохновлялись вампирами, или сами вампиры, тут не уверен.
Если совместить Grist и Dex, получается интересная ситуация: новый пользователь через ldap без проблем проходит Dex, но тут же упирается в то, что в Grist его никто не пригласил, и поэтому делать он ничего не может. Эта задача висит на форуме Grist с 2023 года, но до сих пор не была решена.
It’s not stupid if it works
Этот маленький вампирский нюанс с приглашениями отделял меня от сервиса, который в остальном полностью устраивал. Я решил разобраться, как устроен этот кофе 4-в-1. Может, есть некая лазейка между Dex и Grist, куда можно влететь и написать категорически дружелюбный код, который будет приглашать всех, кто прошел проверку через ldap?..
Оказывается, все это время в пачке grist-omnibus был traefik-forward-auth, который, по великому совпадению, как раз и находился ровно между Dex и Grist. Вишенкой на торте стало то, что код там написан на Golang и, по счастливой случайности, я на нем уже три года как что-то пишу.
Не придумал ничего умнее, как по API проверять в Grist наличие человека в рядах организации после успешного прохождения ldap. Если бедолаги в организации не нашлось, то с превеликой радостью его добавляем и даем полный доступ к работе с сервисом. Мое решение тут.
Легкая неидеальность решения заключается в том, что для работы прослойки traefik-forward-auth нужно сообщить API token. Токен же можно получить, только авторизовавшись в Grist. Но без автоприглашения в организацию до конца авторизоваться в Grist нельзя. Попахивает «поправкой 22», но, слава богу, есть админский аккаунт, который при первом запуске становится сразу членом организации. И через него можно добыть желаемый ключ.
Решение
Для того, чтобы все работало, как задумывалось, нужно один раз сделать последовательность действий, детально описанных в моем fork grist-omnibus:
Собрать docker image из fork’а.
Запустить контейнер с Grist.
Залогиниться с логин-паролем, которые были заданы при запуске.
Зайти в настройки и запомнить API token.
Остановить контейнер и запустить заново, но уже с API token в переменной окружения.
А чего там с Тilda-то?
Tilda поддерживает отправку Webhook по регистрации в формочке. Идем в «Настройки сайта» → «Формы», там все настраивается максимально просто:

Опять же не стал сильно думать и в URL решил пихать название таблицы (на скрине название «masker»), куда было бы неплохо сохранять полученные данные в Grist. Понадобилось это решение ради того, чтобы подключать разные таблички к разным формам, а не лепить все в одно место. Из обидного — Tilda не дает в URL писать на русском название таблицы.
Если с Google Sheets можно было не переживать за потерю новых регистраций, то со своим Webhook это чуть сложнее. Оптимально использовать связку producer – consumer.
Producer — это максимально глупый, но стойкий скрипт, единственная задача которого — положить полученные данные из формочки в очередь. Так как скрипт глупый, то для него оформили самый немощный сервер. Но скрипт должен быть стойкий, поэтому он в облаке Яндекса, чтобы была высокая доступность.
В том же облаке Яндекса организовали и Yandex Queue, который работает на библиотеках AWS.
Consumer, в свою очередь (каламбур намерен), — более сложный код, который читает Yandex Queue и по полученным данным добавляет новые строки в таблички в Grist. В интеграции Google Sheets и Tilda достаточно было указать, в какой файлик класть данные, и далее все как по волшебству создавалось и заполнялось. Интеграция с Grist максимально мимикрирует под исходное решение, чтобы конечный пользователь в лице back-office мог безболезненно перейти на новый сервис и соблюсти законодательство.
В итоге получилась вот такая схема от регистрации до записи в таблице:

Если остались вопросы — отвечу в комментариях.