Настройка неймспейсов для libSQL
В одной из прошлых статей я уже восхищался PocketBase — штука действительно классная. Но знаете, что делает её ещё круче? Если заменить стандартный SQLite на libSQL. Сегодня я хочу рассказать, как можно прокачать PocketBase до нового уровня. Для этого нам понадобится свой собственный сервер libSQL, причём с “шахматами и библиотекаршами” — то есть со всеми плюшками и удобствами. Мы соберём docker-compose файл со всеми необходимыми контейнерами и настройками, настроим хост с сабдоменами для неймспейсов и даже разберёмся, как генерировать токены для авторизации в базе.
А теперь вопрос: зачем вообще заморачиваться с этой непонятной libSQL, если есть проверенные временем PostgreSQL и MySQL? Всё просто — это всё из-за моей большой любви к PocketBase. Она того стоит!
Инсталляция и настройка с помощью Docker
Запустим наш сервер libSQL на виртуалке. Чтобы всё было удобно и не зависело от окружения, будем использовать Docker-контейнер. Это круто тем, что с Docker мы сможем легко управлять всеми зависимостями, настройками и версиями, а ещё это сделает наше решение более переносимым — если вдруг захотим перенести его на другую машину, всё будет работать без лишних танцев с бубном.
Первым делом нужно подготовить docker-compose.yml
— это файл, в котором мы опишем, как именно должен запускаться наш сервер libSQL. К счастью, разработчики libSQL уже сделали за нас часть работы и выложили готовый Docker-образ с сервером sqld. Его можно найти в GitHub Container Registry (GHCR) по адресу: ghcr.io/tursodatabase/libsql-server:latest
.
В этом docker-compose.yml
нам нужно указать, какой образ использовать, а также прописать все нужные настройки: порты, переменные окружения, тома (volumes) для хранения данных и прочее. Это позволит нам быстро и без проблем развернуть сервер libSQL и даст гибкость в управлении.
В общем, с помощью docker-compose.yml
мы сможем легко запустить сервер libSQL в контейнере, и это сильно упростит нам жизнь как при развертывании, так и при дальнейшем поддержании системы.
1version: "3"
2services:
3 primary:
4 image: ghcr.io/tursodatabase/libsql-server:latest
5 platform: linux/amd64
6 ports:
7 - 8080:8080
8 - 5001:5001
9 - 8082:8082
10 command: [ 'sqld', '--admin-listen-addr', '0.0.0.0:8082', '--enable-namespaces' , '--disable-default-namespace' ]
11 environment:
12 - SQLD_NODE=primary
13 - SQLD_HTTP_LISTEN_ADDR=0.0.0.0:8080
14 - SQLD_GRPC_LISTEN_ADDR=0.0.0.0:5001
15 - RUST_LOG=debug
16 # - SQLD_HTTP_AUTH=basic:xxx=
17 - SQLD_AUTH_JWT_KEY=yyy
18 volumes:
19 - ./data:/var/lib/sqld
Разберемся что тут к чему. Начнем с задания портов, они задаются оч криво - часть через переменные окружения, часть через флаги.
- ’–admin-listen-addr’, ‘0.0.0.0:8082’ - тут указываем порт, на котором будет висеть админское API. Через него будем управлять неймспейсами
- SQLD_HTTP_LISTEN_ADDR=0.0.0.0:8080 - это основной порт, через который будет происходить вся работа
- SQLD_GRPC_LISTEN_ADDR=0.0.0.0:5001 - gRPC порт для настройки реплик
С портами разобрались. Теперь разберемся с флагами и переменными окружения:
- –enable-namespaces - включаем функционал, который позволяет создавать множество неймспейсов на сервере sqld
- –disable-default-namespace - для корректной работы с неймспейсами нужно отключить создание дефолтного неймспейса
- SQLD_NODE=primary - стартует наш сервер sqld как мастер ноду
- SQLD_AUTH_JWT_KEY=yyy - указываем публичный ключ, который будет использоваться для проверки JWT авторизации
- SQLD_HTTP_AUTH=basic:xxx= - тоже можно использовать для авторизации но это небезопасно
Этого почти достаточно для работы сервера libSQL, можем заливать docker-compose.yml
на сервер и стартовать. Но в таком виде порты будут торчать наружу сервера, а порт 8082 должен быть доступен только на самом сервере. Для этого можно воспользоваться ufw. С помощью этой утилиты можно закрыть доступ к необходимым портам
Настройка сервера
Работа с утилитой ufw (Uncomplicated Firewall) — это действительно просто и удобно. Она отлично подходит для тех, кто хочет быстро настроить брандмауэр на сервере без лишних сложностей. Давай разберем как с ней работать.
Для начала нужно установить саму утилиту. Делается это одной командой через apt:
1sudo apt-get install ufw
После установки ufw уже готов к работе, но пока что он выключен. Чтобы начать им пользоваться, нужно настроить правила и активировать его.
Допустим, тебе нужно доступ к какому-то порту, например, 2222. Для этого просто выполни команду:
1sudo ufw allow 2222
Эта команда добавит правило, которое разрешит входящие соединения на порт 2222. Когда все нужные порты добавлены в белый список, можно включать ufw.
Чтобы активировать брандмауэр, используй команду:
1sudo ufw enable
После этого ufw начнет применять все заданные правила и блокировать всё, что не разрешено. Теперь твой сервер защищен, и можно спать спокойно.
Если хочешь глубже разобраться в ufw, есть куча документации и руководств в интернете. Например, официальная документация Ubuntu — отличное место для старта. А вот тут можно почитать примеры работы с утилитой ufw
Всё выглядит просто, но есть один подводный камень. ufw не всегда дружит с портами, которые проброшены из Docker-контейнеров. Дело в том, что Docker использует свои сетевые интерфейсы и правила iptables, которые могут конфликтовать с настройками ufw.
Но не переживай — эту проблему уже решили. Благодаря умным ребятам (в том числе из Китая) появились решения, которые позволяют ufw корректно работать с портами из Docker-контейнеров. Теперь можно управлять доступом к портам, даже если они используются внутри контейнеров.
Одно из таких решений — команда ufw route. Она позволяет настраивать правила для маршрутизации трафика, включая порты, которые используются Docker-контейнерами. Например, если тебе нужно открыть доступ к порту 8082 (на котором крутится API для администрирования libSQL), используй такую команду:
1ufw route allow proto tcp from any to any port 8082
Эта команда разрешает входящие TCP-соединения на порт 8082, независимо от того, используется ли он внутри контейнера или на хосте. В нашем случае порт 8082 нужен для API, который управляет неймспейсами в libSQL.
Управление неймспейсами
Неймспейсы — это, по сути, отдельные базы данных. Каждый неймспейс изолирован и может содержать свои таблицы, данные и настройки. API на порту 8082 как раз позволяет создавать новые неймспейсы и управлять ими, что делает его важной частью системы.
Если нужно создать новый неймспейс, можно воспользоваться утилитой libsqltui. Для этого её нужно закинуть на сервер. Ещё можно временно открыть порт 8082, чтобы всё настроить, но это не самый безопасный вариант, так что лучше так не делать, если можно избежать.
Подключаемся, выбираем опцию создать новый неймспейс и, казалось бы, всё готово — можно радоваться результату. Но, как всегда, есть свои подводные камни. Чтобы подключиться к созданному неймспейсу, его название должно быть указано в URL. Например, если ты создал неймспейс с именем foo
, то URL для подключения будет выглядеть примерно так: http://foo.db.example.com:8080
. Здесь db.example.com
— это адрес твоего сервера, а foo
— это как раз название твоего неймспейса.
Но и это ещё не всё. Чтобы можно было динамически добавлять новые неймспейсы без лишних заморочек, нужно на домене настроить A-запись вида *.db.example.com
. Это позволит подключаться к любым неймспейсам через поддомены, что очень удобно.
Вот такие вот особенности протокола — без них никуда!
Авторизация
Итак, неймспейс создан, URL для подключения настроен. Но теперь нужно сделать так, чтобы всё это было защищено авторизацией. И вот это оказалось для меня самой сложной частью. В документации про это буквально пара предложений: используйте JWT для авторизации, и ключ нужно указать в переменной окружения SQLD_AUTH_JWT_KEY
. И вот тут мне пришлось лезть в код и разбираться самому.
В процессе я наткнулся на тест, где был описан JSON, который используется в JWT для создания ключа авторизации. Вот пример:
1#[test]
2fn multi_scopes() {
3 let (enc, dec) = generate_key_pair();
4 let token = serde_json::json!({
5 "id": "foobar",
6 "a": "ro",
7 "p": {
8 "rw": { "ns": ["foo"] },
9 "roa": { "ns": ["bar"] }
10 }
11 });
12}
Здесь в параметре p перечисляются неймспейсы с указанием уровня доступа, а id — это просто любой UUID.
Чтобы подписать JWT, нужно сгенерировать приватный и публичный ключи, которые будут использоваться для проверки авторизации. Делается это так:
1openssl genpkey -algorithm ed25519 -out test-priv.pem
2openssl pkey -in test-priv.pem -pubout -out test-pub.pem
После долгих проб и ошибок я написал скрипт на Go, который формирует правильный JWT. В этом скрипте указываются приватный и публичный ключи, которые мы получили на предыдущем шаге.
1package main
2
3import (
4 "crypto/ed25519"
5 "encoding/base64"
6 "fmt"
7 "log"
8 "time"
9
10 "github.com/golang-jwt/jwt/v5"
11)
12
13var private = `-----BEGIN PRIVATE KEY-----
14xxx
15-----END PRIVATE KEY-----
16`
17
18var public = `-----BEGIN PUBLIC KEY-----
19xxx
20-----END PUBLIC KEY-----
21`
22
23func main() {
24 token := jwt.NewWithClaims(jwt.SigningMethodEdDSA, jwt.MapClaims{
25 "id": "81220406-4854-4d4b-817e-61f4a092cdc4", // TODO: generate uuid
26 "a": "rw",
27 "p": map[string]interface{}{
28 "rw": map[string]interface{}{"ns": []string{"example", "test-db", "foo"}},
29 },
30 "iat": time.Now().Unix(),
31 "nbf": time.Date(2015, 10, 10, 12, 0, 0, 0, time.UTC).Unix(),
32 })
33
34 k, _ := jwt.ParseEdPrivateKeyFromPEM([]byte(private))
35 p, _ := jwt.ParseEdPublicKeyFromPEM([]byte(public))
36
37 ts, err := token.SignedString(k)
38 if err != nil {
39 log.Fatal(err)
40 }
41
42 tt, err := jwt.Parse(ts, func(t *jwt.Token) (interface{}, error) {
43 return p, nil
44 })
45
46 if err != nil {
47 log.Fatal(err)
48 }
49
50 fmt.Println("token:", ts)
51 fmt.Println("public:", base64.URLEncoding.EncodeToString([]byte(p.(ed25519.PublicKey))))
52 fmt.Println("claims:", tt.Claims)
53}
В результате работы скрипта получаем:
- token — это подписанный JWT токен, который мы будем использовать для подключения. Его нужно передавать в параметре запроса, например:
http://foo.db.example.com:8080?authToken={token}
. - public — это JWT ключ, который нужно указать в переменной окружения
SQLD_AUTH_JWT_KEY
.
Вот так, шаг за шагом, удалось разобраться с авторизацией. А это значит, что можно подключаться к неймспейсу и записывать данные.
Настройка подключения VS code
В прошлой статье я уже рассказывал, как можно использовать libSQL в проектах на PocketBase. А какой клиент использовать для разработки? Ведь удобство работы с базой данных — это важно, особенно когда ты постоянно что-то тестируешь, пишешь запросы и смотришь, что там внутри твоих таблиц.
И вот тут я не могу не поделиться с вами одной офигенной находкой — плагином для VS Code под названием DBCode. Это просто бомба, честно. Плагин поддерживает кучу разных баз данных, и, что самое приятное, в их числе есть и libSQL.
Что в нём такого крутого? Ну, во-первых, он выглядит просто шикарно — интерфейс минималистичный, но при этом всё интуитивно понятно. Во-вторых, он работает на удивление шустро. Можно легко подключаться к базе, писать запросы, смотреть структуру таблиц, экспортировать данные и многое другое. В общем, всё, что нужно для комфортной разработки, прямо в твоём редакторе кода.
Если вы ещё не пробовали DBCode, настоятельно рекомендую. Это тот случай, когда плагин не просто делает жизнь проще, а реально приносит удовольствие от работы. Попробуйте — и сами всё поймёте
Заключение
Вот что у нас получилось: мы настроили сервер для sqld, и теперь можем создавать новые неймспейсы и генерировать JWT токены для авторизации на нашем сервере, чтобы подключаться к нужным неймспейсам. В этой статье я собрал несколько полезных утилит, которые делают работу с libSQL гораздо удобнее. Остался буквально один шаг до того, чтобы полностью заменить turso.tech на собственное решение.
Конечно, libSQL — не идеальный выбор. У него есть свои минусы, и он не всегда работает так гладко, как хотелось бы. Но с другой стороны, он здорово расширяет возможности PocketBase, и от такого функционала просто так не откажешься. Да, есть проблемы, но их можно решить, и в итоге libSQL вполне реально заставить работать как качественный и надёжный сервис. Главное — разобраться и настроить всё под свои нужды.
Ссылки
- Фаервол для портов докера
- Официальная документация Ubuntu по ufw
- Туториал по по использованию ufw
- PocketBase и libSQL
- Документация по авторизации в libSQL
- DBCode - плагин для VS Code