Налаштування Fedi-сервера Snac для мережі Yggdrasil

Невдовзі, після своїх попередніх роздумів про p2p, вирішив спробувати підняти власний експериментальний інстанс Fediverse. При чому, зробити це засобами оверлейної мережі Yggdrasil, оскільки я не планую купувати для цієї іграшки виділений IP чи VPS, натомість буду хоститись з модему, одноплатника або взагалі з ПК, коли буваю онлайн, з динамічною адресою за NAT.

Пишу цю нотатку в першу чергу - для себе, а також, вона може бути корисною для тих, хто як і я тільки починає свої експерименти у сфері адміністрування власного вузла Fediverse і цікавиться альтернативними мережами, в контексті Linux.

Що таке Snac

Snac (https://codeberg.org/grunfink/snac2) - це мінімалістична, JS-less, написана мовою C альтернатива серверу Mastodon, яка також не потребує інсталяції PostgreSQL, натомість зберігає усі дані профілю у файлах JSON. Нещодавно, до цього серверу було додано підтримку IPv6 (PR#256), а отже - він буде працювати й з діапазоном Yggdrasil `0200::/7`

Оскільки Yggdrasil дозволяє безкоштовно генерувати не обмежену кількість статичних IP (на базі приватного ключа Ed25519), тут немає звичної потреби в DNS. Хоча, можна опціонально прикрутити Alfis (https://github.com/Revertron/Alfis), але особисто я цим ділом не користуюсь (зокрема, й через досі не вирішену проблему #364, тому також не хочу нав'язувати його в рамках протоколу ActivityPub - буде просто формат `username@IPv6`, який мені не потрібно а ні оновлювати, а ні майнити потім.

Встановлення

1. Точний перелік пакетів для Debian я не знаю, оскільки моя система не нова і вже має встановлені раніше пакунки. Як вказано в README, я тільки встановив `libssl-dev` і `libcurl4-openssl-dev` (для Fedora має бути приблизно те само з постфіксом `*-devel`)

2. Далі, створюємо окремого системного юзера, щоб ізолюватись від потенційних вразливостей:

useradd -m snac

3. Змінюємо для зручності середовище на `bash` у файлі `/etc/passwd`

4. Логінимось через `su snac` і переходимо в домашню директорію цього юзера: `cd`

5. Завантажуємо останній вихідний код: `git clone https://codeberg.org/grunfink/snac2.git`

6. Заходимо в робочу директорію `cd snac2`

7. Компілюємо `make && sudo make install` і встановлюємо з відповідними правами

8. Ініціалізуємо серверне сховище: `snac init /home/snac/storage`

9. І додамо до нього нашого першого юзера `snac adduser /home/snac/storage`

10. Далі продовжуємо від `root` виконавши команду `exit`

Налаштування

Я вже маю встановлений і налаштований вузол Yggdrasil, якщо комусь цікавий процес встановлення, скористайтесь попередньою публікацією:

Yggdrasil - мережа з децентралізованим роутингом

або офіційною документацією:

Адреса підмережі Yggdrasil

Можна пропустити цей крок і використовувати основну адресу `2*`, якщо порти `80` чи `8001` не зайняті. Але зауважте, що в рамках API протоколу ActivityPub, сервер Snac надаватиме вашу адресу хосту іншим нодам, а ті - її кешуватимуть, як частину ID і оскільки локально адреса хосту зберігається по файлам, а не в БД, потім буде важко її замінити. Тому краще виділити окрему, особливо - якщо це продакшн:

1. `yggdrasilctl getself` - дізнаємось свій айпішник, зокрема вивід `IPv6 subnet`

2. `ifconfig lo inet6 add IP` - замість IP вказуємо довільну адресу для отриманого діапазону, наприклад `3xx:xxxx:xxxx:xxxx::fed/64` , де `fed` - така собі гра слів в рамках "словника" IPv6 (0-9A-F).

* варто зауважити, що дані маршрутизації `ifconfig` не зберігаються після ребуту системи, для цього потрібно додати відповідний запис (команду з пункту 2), наприклад до `/etc/netplan/01-ygglo.yaml`, `/etc/network/interfaces`, або безпосередньо до systemd `yggdrasil.service` (секція `ExecStartPost=`) - залежно від операційної системи.

Проксі Nginx

На моєму сервері вже встановлено веб-сервер Nginx, який займає порт `80`, я поки не хочу нічого змінювати, а також не хочу мати публічні адреси Snac з його стандартним портом `8001`. Тому, оскільки вже маю виділену адресу підмережі, просто запроксую API на `80` порт через новий віртуальний хост, частково використавши оригінальний приклад конфігурації:

default.conf
server {
    listen [3xx:xxxx:xxxx:xxxx::fed]:80;
    server_name 3xx:xxxx:xxxx:xxxx::fed;

    location @proxy {
        proxy_http_version      1.1;
        proxy_set_header        Upgrade $http_upgrade;
        proxy_set_header        Connection "upgrade";
        proxy_redirect          off;
        proxy_connect_timeout   90;
        proxy_send_timeout      90;
        proxy_read_timeout      90;
        proxy_set_header        Host $host;
        proxy_set_header        X-Real-IP $remote_addr;
        proxy_set_header        X-Forwarded-For $proxy_add_x_forwarded_for;
        proxy_set_header        X-Forwarded-Proto $scheme;
        proxy_set_header        Proxy "";
        proxy_pass_header       Server;
        proxy_buffering on;
        tcp_nodelay on;
        proxy_pass http://[3xx:xxxx:xxxx:xxxx::fed]:8001;
        proxy_set_header Host $http_host;
    }

    location /.well-known/webfinger {
        try_files $uri @proxy;
    }

    location /.well-known/nodeinfo {
        try_files $uri @proxy;
    }

    location / {
        try_files $uri @proxy;
    }

    location /fedi/ {
        try_files $uri @proxy;
    }
}

Як бачите, на прикладі вище не вказано порт `443`, а також немає сертифікатів SSL. Це зроблено спеціально, оскільки Yggdrasil вже має захищений канал, і я не хочу створювати тут зайвий шар.

Оскільки клієнтські підключення Yggdasil також мають статичну адресу, я вирішив обмежити доступ до адміністративного API (адмінка усіх акаунтів + `oauth`) по IP. Наскільки це ефективно і чи не забув про інші адреси - я не знаю, але додам свій приклад регулярного виразу для `location`:

location ~ /([^\/]+/admin|oauth) {
    allow ADMIN_IP;
    deny all;
    try_files $uri @proxy;
}

Конфігурація Snac

Відредагуємо раніше згенерований командою `snac init` файл `/home/snac/storage/server.json`:

{
    "host": "[3xx:xxxx:xxxx:xxxx::fed]",
    "prefix": "",
    "address": "3xx:xxxx:xxxx:xxxx::fed",
    "port": 8001,
    "layout": 2.7,
    "dbglevel": 0,
    "queue_retry_minutes": 2,
    "queue_retry_max": 10,
    "queue_timeout": 6,
    "queue_timeout_2": 8,
    "cssurls": [
        ""
    ],
    "def_timeline_entries": 50,
    "max_timeline_entries": 50,
    "timeline_purge_days": 120,
    "local_purge_days": 0,
    "min_account_age": 0,
    "admin_email": "",
    "admin_account": "",
    "title": "",
    "short_description": "",
    "short_description_raw": false,
    "protocol": "http",
    "fastcgi": false
}

Доступи iptables

Конфігурація у прикладах не передбачає доступу до ноди з мережі Інтернет, тому я відкрив порт тільки для Yggdrasil, щоб інші вузли в рамках цієї мережі могли взаємодіяти між собою на івентах типу фоловінгу (обидва вузли мають бути онлайн для транзакції):

ufw allow from 0200::/7 to any port 80

Налаштування systemd

Є готовий офіційний приклад конфігурації:

snac.service

але я його трохи доповнив:

[Unit]
After=network-online.target
Wants=network-online.target

[Service]
Type=simple
User=snac
Group=snac
ExecStart=/usr/local/bin/snac httpd /home/snac/storage
StandardOutput=file:/home/snac/debug.log
StandardError=file:/home/snac/error.log

[Install]
WantedBy=multi-user.target

Резервне копіювання

Оскільки база даних Snac зберігається у файловому форматі, досить просто бекапити профіль по лише одній локації.

Я роблю це засобами `rsync` для різних часових інтервалів наступною командою `crontab -e`:

@daily /usr/bin/rsync -av --delete /home/snac/storage /path/to/snac/daily
@weekly /usr/bin/rsync -av --delete /home/snac/storage /path/to/snac/weekly
@monthly /usr/bin/rsync -av --delete /home/snac/storage /path/to/snac/monthly

Користування

Після запуску Snac командою `snac httpd /home/snac/storage` або через сервіс `systemd`, можна спробувати відкрити у браузері `http://[3xx:xxxx:xxxx:xxxx::fed]`.

Тестування взаємодії (API)

Щоб перевірити взаємодію з іншим вузлом Yggdrasil, повторюємо для нього ті само дії і робимо тестовий фоловінг чи переписку між користувачами через Web UI або підключений зовнішній клієнтський застосунок.

Тюнінг браузеру

Якщо вперше користуєтесь сайтами Yggdrasil у Firefox, можливо знадобиться оптимізувати обробку "сирих" IPv6 адрес в `about:config`:

Теми Web UI

В README є перелік посилань на CSS теми, за допомогою яких можна кастомізувати веб-інтерфейс Snac на власне вподобання:

Спочатку я не зрозумів, як підключати нові теми, і додав посилання на умовний файл `/theme.css` до `/home/snac/storage/server.json`, а також створив на нього аліас локального шляху в Nginx (щоб задовольнити браузерну політику CORS):

location /theme.css {
    alias /var/www/snac/theme.css;
}

Але згодом виявилось, що при створенні інстансу, генерується стандартний файл у теці `/home/snac/storage/style.css` який буде конфліктувати з новою темою (адже він підключатиметься окремо від масиву конфігурації `cssurls`).

Таким чином, потрібно просто переписати вміст стандартного файлу `/home/snac/storage/style.css` обраною темою, а масив `cssurls` потрібен тільки для тюнінгу поточної теми, без правки її оригіналу. Щоб повернутись до оригінальної теми - достатньо видалити цей файл, після чого згенерується стандартний файл Snac.

Специфіка клієнтських застосунків

Особисто, я встиг перевірити тільки Tuba (https://tuba.geopjr.dev/). Як виявилось, даний клієнт має захардкожену обробку схеми HTTPS, тому якщо користуєтесь цим застосунком, доведеться налаштувати окремий інтерфейс Nginx на порті `443` з використанням сертифікату, хоч у випадку з Yggdrasil - це зайва "капуста" і мабуть поки що лишусь на веб-інтерфейсі або зроблю і викладу потім патч.

UPD 1. розробник виявився супер-оперативним і вже створив гілку з патчем:

Flatpak є в Артефактах:

Єдине що - тільки поки не "завезли" валідацію IPv6 у вікно авторизації, тому я тимчасово користуюсь аліасом в `/etc/hosts` і вказую у якості URL авторизації `http://alias`. Можливо, у наступних випусках додатка це вже буде не актуально.

UPD 2. Наразі, гілка об'єднана з `main`, тому замість аліасів для IPv6, можна просто додати при запуску `TUBA_SKIP_STRICT_VALIDATION=1`, наприклад:

TUBA_SKIP_STRICT_VALIDATION=1 'builddir/dev.geopjr.Tuba'

UPD 3. Особисто я користуюсь окремою гілкою, де ця нубська валідація HTTP випиляна, і жодних додаткових флагів для запуску не потрібно:

Для збірки з форку:

1. `git clone https://github.com/YGGverse/Tuba.git`

2. `git checkout multiprotocol-address-support`

3. `make && make install`

* або для Flatpak:

flatpak-builder --force-clean build\
                --install-deps-from=flathub\
                --install\
                --repo=repo\
                --user\
                build-aux/dev.geopjr.Tuba.Devel.json

Також, в мене є ще одна, окрема гілка, де я форсовано застосовую українську локаль для постів, адже сервер Snac має певний недопил по синхронізації даного API:

Скачати All-in-One збірку можна тут, виконавши для встановлення кроки вище, але з `git checkout ps`:

Моніторинг трафіку

Оскільки сервер Snac не передбачає користування JS інтерфейсом, а поточна конфігурація використовує Nginx, можу по ходу справи також порадити goaccess (https://goaccess.io/) - CLI утиліту для зручного моніторингу статистики користувацького трафіку, якщо такий буде:

goaccess /var/log/nginx/access.log

Приватний режим

Yggdrasil дозволяє маскувати реальний IP, якщо ви користуєтесь власним вихідним вузлом (https://publicpeers.neilalexander.dev/). Звісно, таку можливість не варто розглядати в контексті анонімізації окремо без додаткових шарів, оскільки використання протоколу Yggdrasil без наприклад таких проксі, як Shadowsocks - легко виявляється.

Ця тема виходить за рамки матеріалу, але зверну увагу на деякі аспекти, якщо ви плануєте користуватись збіркою Snac + Yggdrasil в режимі "інкогніто". Протокол ActivityPub передбачає "спілкування" між серверами для обміну івентами. Тобто потенційний фоловер може відправити запит підписки на на дозволений у фаєрвол інтерфейс `0200::/7`, але вказати в заголовках події ActivityPub - зворотній DNS на вузол в мережі Інтернет. Таким чином, ваша система здійснить транзакцію з білого IP через системний резольвер або без нього, використовуючи локальний Curl API від Snac.

Потенційних сценаріїв витоку можна придумати багато. Я переглянув вихідний код Snac, та не знайшов у ньому жодних фільтрів взаємодії на вихідні підключення. Тому, як і для іншого не спеціалізованого софту, для цієї мети бажано використовувати ізоляцію роутера з контейнера Docker, LXC або віртуалізуватись засобами QEMU.

При використанні клієнтського API, окремого аудиту потребуватиме обробка віддаленого вмісту постів, аватарів та іншого. У цьому напрямку, є перші кроки, зокрема по частині Web UI (PR#394), але я не впевнений, що буду пиляти це довгий час, адже з цих причин, давно користуюсь протоколом Gemini. Якщо вам все таки цікавий напрямок "довіри" HTML/HTTP, можете спробувати цю гілку, або продовжити її розробку для себе:

Можна, в принципі, додати правило на "останній рубіж" `iptables`, наприклад заблокувати вихідні пакети в Інтернет:

ufw default deny outgoing

Якщо користуєтесь Yggdrasil в оверлейному режимі (тобто через публічний пір), важливо додати його до білого списку:

ufw allow out to PUBLIC_PEER_IP

На останок, дозволяємо локальні запити для взаємодії між вузлами:

ufw allow out to 0200::/7 from 0200::/7

Багато-мережевий режим

Можливо, згодом (коли розберусь) окремо опишу, як запустити інстанс у різних мережах одночасно, наприклад Інтернет + Yggdrasil, але наскільки бачу по коду Snac, його файлова реалізація сховища дозволяє працювати тільки в рамках однієї мережі / хосту.

Думаю, тут можна буде погратись з проксуванням з авто-заміною, організувати реплікацію або використанням "білого" DNS і записів `A`/`AAAA` відповідно до типу з'єднання клієнтського резольвера. Так чи інакше, це - вже зовсім інша історія!

Посилання

Інших вузлів я поки не зустрічав, тому підписуйтесь на мій - для тестів і спілкування:

Можливо, колись сформується нове локальне сузір'я :)