Mastodon инстанс — врываемся в fediverse!

Ты вообще о чём?!

Fediverse, далее будем называть — федиверс, слово образуется от федерация и вселеная. Федиверс получил свое название от того, что является вселенной федерации. Федерация в этом контексте означает множество подключенных серверов, которые могут работать на разных типах платформ и которые не принадлежат и не управляются организациями. Эти серверы могут обмениваться видео, публикациями или чем-то еще в зависимости от программного обеспечения, на котором они работают, но в основном все они обмениваются информацией друг с другом, используя открытый стандарт под названием ActivityPub.

ActivityPub — открытый и децентрализованный протокол для социальных сетей. Если совсем упростить, то это как если бы вы могли отправить сообщение своему товарищю из Telegram в Вконтакте или из Вконтакте написать кому то в whatsapp. Федиверс это место где достаточно иметь учётную запись на одном инстансе и общатся со всей федерацией! В контексте федерации каждый сервер имеет свои правила поведения и политики модерации, любой инстанс федерации может прекратить общение с другим инстансом, не будет видеть сообщения и прользователей друг друга, основываясь на своих правилах.

Что за инстансы?!

Любой, у кого есть немного знаний и достаточные серверные ресурсы, может запустить свой собственный сервер в федерации, это и есть инстанс. В этой статье мы поднимем инстанс — Mastodon. Так как наш инстанс будет частью федерации, мы сможем подписаться на кого угодно и общаться с кем угодно используя учётную запись со своего сервера. При желании учётную запись со своего инстанса можно будет переместить на любой другой публичный.

Для чего всё это нужно?

Mastodon — продавали аудитории как альтернативу Twitter. В 2022 в этой социальной сети начались брожения, изменения и нововведения которые в целом были восприняты негативно. Новое руководство, новые правила, массовые увольнения и самое главное и не приятное, достаточно сильное цензурирование. Тогда это было что-то новое, когда решение одного человека или одной компании значимо влияет на политическую элиту страны, эти решения затрагивают целые правительства, вдруг оказалось, что одна компания может ограничить доступ к своему продукту, просто потому что захотела так сделать. Это отталкивало часть аудитории, настораживало другую часть, в этот момент на сцене появился Mastodon, наверное это самое известное детище федиверс, хотя протокол да и сам Mastodon не является чем то новым, просто хорошо забытое старое. В Mastodon потянулись люди, много людей, очень много людей. Стали подниматься инстансы. Жизнь забурлила.

Обратная сторона успеха.

Люди пришли, но со временем оказалось, что тут нет умных алгоритмов, которые бы анализировали происходящее вокруг и выдавали вам десятки рекомендаций, что посмотреть и на кого подписаться. Тут нет назойливой рекламы. Тут внезапно, нужно общаться, нужно налаживать общение, владельцам серверов, необходимо договорится что они будут выступать релеями друг для друга, администраторам договорится о правилах модерации, пользователям, нужно самим найти то что их инетересует и самим же заблокировать то что они видеть не хотят, пришло понимание что в социальной сети — нужно общаться. Лично я считаю что в этом и есть сильная сторона федиверс. Каждый выбирает контент для себя. Каждый инстанс это часть сообщества, часть федерации, такой кирпичик из которых всё построено. Но кому то не зашло и они ушли, часть публики осталась. После большой волны успеха, Mastodon снова зажил своей прежней жизнью, всё же пополнив ряды своих сторонников.

И что в итоге?

В итоге у нас есть уникальная возможность получить свой собственный опыт в Mastodon и Федиверс. Можно выбрать экземпляр, которым управляют и населяют единомышленники. Администраторы экземпляра будут усердно работать над обслуживанием сервера и устанавливать правила относительно ожидаемого поведения. Это позволяет присоединиться к сообществу с правилами, которые мы уважаем. Можно легко найти людей, живущих на нашем сервере, или исследовать остальную часть Федиверс, но вполне вероятно, что администратор нашего сервера может ограничить обмен информацией с экземплярами, которые, по его мнению, противоречат его собственным правилам. Таким образом, наш опыт теперь будет основан на сообществах единомышленников, а также на изучении и установлении связей с другими интересными людьми. И, конечно же, здесь нет рекламы или корпоративных интересов, поэтому алгоритмы больше не манипулируют нами.

Мы откроем свой инстанс с блэджеком и правилами модерации!

Уже получилось достаточно много материала, но мы тут не для этого. Цель статьи поднять свой инстаннс. Вообще на сайте проекта есть достаточно подробные инструкции о том, как всё сделать самому и быть молодцом.

Какой результут хотелось бы получить:

  • Простое и быстрое развёртывание
  • Простое резервное копировение и восстановление
  • Само собой самохост)

У хостинга своего инстанса, есть минимальные требования:

  • Само собой сервер на Linux, желательно Ubuntu 20.04 или 22.04, на момент написания статьи, это может быть как VPS так и выделенная машина
  • Доменное имя или поддомен
  • Email provider — надёжный SMTP relay, который сможет отправлять сообщения от нашего сервера.
  • Достаточно много дискового простарнства, объёмный жёсткий диск (я выделил ~70Gb) или разработчики советуют S3 хранилище.

Будем считать, что прочие минимальные требования выполнены, как то: установлен Docker и Docker compose, git. Приступаем:

cd ~
git clone https://github.com/mastodon/mastodon
cd /mastodon
nano docker-compose.yml

Итоговое содержимое моего файла:

version: '3'
services:
  db:
    restart: always
    image: postgres:14-alpine
    shm_size: 256mb
    networks:
      - internal_network
    healthcheck:
      test: ['CMD', 'pg_isready', '-U', 'postgres']
    volumes:
      - ./postgres14:/var/lib/postgresql/data
    environment:
      - 'POSTGRES_HOST_AUTH_METHOD=trust'

  redis:
    restart: always
    image: redis:7-alpine
    networks:
      - internal_network
    healthcheck:
      test: ['CMD', 'redis-cli', 'ping']
    volumes:
      - ./redis:/data

  # es:
  #   restart: always
  #   image: docker.elastic.co/elasticsearch/elasticsearch:7.17.4
  #   environment:
  #     - "ES_JAVA_OPTS=-Xms512m -Xmx512m -Des.enforce.bootstrap.checks=true"
  #     - "xpack.license.self_generated.type=basic"
  #     - "xpack.security.enabled=false"
  #     - "xpack.watcher.enabled=false"
  #     - "xpack.graph.enabled=false"
  #     - "xpack.ml.enabled=false"
  #     - "bootstrap.memory_lock=true"
  #     - "cluster.name=es-mastodon"
  #     - "discovery.type=single-node"
  #     - "thread_pool.write.queue_size=1000"
  #   networks:
  #      - external_network
  #      - internal_network
  #   healthcheck:
  #      test: ["CMD-SHELL", "curl --silent --fail localhost:9200/_cluster/health || exit 1"]
  #   volumes:
  #      - ./elasticsearch:/usr/share/elasticsearch/data
  #   ulimits:
  #     memlock:
  #       soft: -1
  #       hard: -1
  #     nofile:
  #       soft: 65536
  #       hard: 65536
  #   ports:
  #     - '127.0.0.1:9200:9200'

  web:
#    build: .
    image: ghcr.io/mastodon/mastodon:v4.2.0
    restart: always
    env_file: .env.production
    command: bundle exec puma -C config/puma.rb
    networks:
      - external_network
      - internal_network
    healthcheck:
      # prettier-ignore
      test: ['CMD-SHELL', 'wget -q --spider --proxy=off localhost:3015/health || exit 1']
    ports:
      - '0.0.0.0:3015:3000'
    depends_on:
      - db
      - redis
      # - es
    volumes:
      - ./public/system:/mastodon/public/system

  streaming:
#    build: .
    image: ghcr.io/mastodon/mastodon:v4.2.0
    restart: always
    env_file: .env.production
    command: node ./streaming
    networks:
      - external_network
      - internal_network
    healthcheck:
      # prettier-ignore
      test: ['CMD-SHELL', 'wget -q --spider --proxy=off localhost:4000/api/v1/streaming/health || exit 1']
    ports:
      - '0.0.0.0:4000:4000'
    depends_on:
      - db
      - redis

  sidekiq:
#    build: .
    image: ghcr.io/mastodon/mastodon:v4.2.0
    restart: always
    env_file: .env.production
    command: bundle exec sidekiq
    depends_on:
      - db
      - redis
    networks:
      - external_network
      - internal_network
    volumes:
      - ./public/system:/mastodon/public/system
    healthcheck:
      test: ['CMD-SHELL', "ps aux | grep '[s]idekiq\ 6' || false"]

  ## Uncomment to enable federation with tor instances along with adding the following ENV variables
  ## http_hidden_proxy=http://privoxy:8118
  ## ALLOW_ACCESS_TO_HIDDEN_SERVICE=true
  # tor:
  #   image: sirboops/tor
  #   networks:
  #      - external_network
  #      - internal_network
  #
  # privoxy:
  #   image: sirboops/privoxy
  #   volumes:
  #     - ./priv-config:/opt/config
  #   networks:
  #     - external_network
  #     - internal_network

networks:
  external_network:
  internal_network:
    internal: true

Коментируем все строчки, начинающиеся с build. Создадим файл с переменными и запускаем контейнеры:

touch .env.production
docker compose up -d

Запустим первоначальную установку, здесь необходимо будет отвечать на вопросы инсталятора:

docker compose run --rm web bundle exec rake mastodon:setup

При первом запуске, у вас спросят: доменное имя, параметры БД, redis, SMTP релея, в конце предложат создать пользователя администратор, мы этого делать не будем и создадим пользователя позже.

Domain name: yourdomainame.com

Single user mode disables registrations and redirects the landing page to your public profile.
Do you want to enable single user mode? - n.

Are you using Docker to Run Mastodon? - y.

PostgreSQL host: db
PostgreSQL port: 5432
Name of PostgreSQL database: postgres
Name of PostgreSQL user: postgres
Password of PostgreSQL user - оставялем пустым, жмём ENTER.

Redis host: redis
Redis port: 6379
Redis Password  - оставялем пустым, жмём ENTER ENTER.

Do you want to store uploaded files on the cloud? - n.

Do you want to send e-mails from localhost? - n.

Look for your default SMTP login and password, and insert the details into the .env.production, it should look like this:

SMTP_LOGIN=
SMTP_PASSWORD=

После заполнения всех параметров, будет предложено отправить тестовое письмо, рекомендую это сделать и убедится что письма доходят. После этого нам будут сгенерированны все переменные для нашего .env.production файла, копируем их сохраняем в надёжное место.

Проверяем что всё поднялось и стартануло — docker ps и добавляем пользователя владельца:

$ docker compose run --rm web bin/tootctl accounts create USERNAME --email YOUR_EMAIL --confirmed --role Owner

Сгенерируется пароль, копируем все данные себе. Уже сейчас наш Mastodon должен работать. Ещё один важный момент, необходимо настроить очистку медиа контента, ранее вроде как Mastodon этого делать сам не мог, настроим задачи в crontab:

crontab -e

0 3 * * * docker exec -it mastodon-web-1 tootctl media remove --days=7
0 4 * * * docker exec -it mastodon-web-1 tootctl preview_cards remove --days=7
0 5 * * * docker exec -it mastodon-web-1 tootctl statuses remove --days=7

Настроим nginx:

nano /etc/nginx/sites-available/toot.iamninja.ru.conf
map $http_upgrade $connection_upgrade {
default upgrade;
''      close;
}

upstream backend_mast {
server 192.168.122.5:3015 fail_timeout=0;
}

upstream streaming {
server 192.168.122.5:4000 fail_timeout=0;
}

proxy_cache_path /var/cache/nginx levels=1:2 keys_zone=CACHE:10m inactive=7d max_size=1g;

server {
listen 80;
listen [::]:80;
server_name toot.iamninja.ru;

location /.well-known/acme-challenge/ { allow all; }
location / { return 301 https://$host$request_uri; }
}

server {
listen 443 ssl http2;
listen [::]:443 ssl http2;
server_name toot.iamninja.ru;

ssl_protocols TLSv1.2 TLSv1.3;
ssl_ciphers HIGH:!MEDIUM:!LOW:!aNULL:!NULL:!SHA;
ssl_prefer_server_ciphers on;
ssl_session_cache shared:SSL:10m;
ssl_session_tickets off;

# Uncomment these lines once you acquire a certificate:
ssl_certificate     /etc/letsencrypt/live/iamninja.ru/fullchain.pem;
ssl_certificate_key /etc/letsencrypt/live/iamninja.ru/privkey.pem;
#ssl_dhparam                    /etc/letsencrypt/ssl-dhparams.pem;
#ssl_stapling                   on;
#ssl_stapling_verify            on;
#ssl_trusted_certificate        /etc/letsencrypt/live/matrix.rocks/chain.pem;

keepalive_timeout    70;
sendfile             on;
client_max_body_size 80m;

#root /home/mastodon/live/public;
root /opt/mastodon/public/;

gzip on;
gzip_disable "msie6";
gzip_vary on;
gzip_proxied any;
gzip_comp_level 6;
gzip_buffers 16 8k;
gzip_http_version 1.1;
gzip_types text/plain text/css application/json application/javascript text/xml application/xml application/xml+rss text/javascript image/svg+xml image/x-icon;

location / {
try_files $uri @proxy;
}

# If Docker is used for deployment and Rails serves static files,
# then needed must replace line `try_files $uri =404;` with `try_files $uri @proxy;`.
location = /sw.js {
add_header Cache-Control "public, max-age=604800, must-revalidate";
add_header Strict-Transport-Security "max-age=63072000; includeSubDomains";
try_files $uri @proxy;
}

location ~ ^/assets/ {
add_header Cache-Control "public, max-age=2419200, must-revalidate";
add_header Strict-Transport-Security "max-age=63072000; includeSubDomains";
try_files $uri @proxy;
}

location ~ ^/avatars/ {
add_header Cache-Control "public, max-age=2419200, must-revalidate";
add_header Strict-Transport-Security "max-age=63072000; includeSubDomains";
try_files $uri @proxy;
}

location ~ ^/emoji/ {
add_header Cache-Control "public, max-age=2419200, must-revalidate";
add_header Strict-Transport-Security "max-age=63072000; includeSubDomains";
try_files $uri @proxy;
}

location ~ ^/headers/ {
add_header Cache-Control "public, max-age=2419200, must-revalidate";
add_header Strict-Transport-Security "max-age=63072000; includeSubDomains";
try_files $uri @proxy;
}

location ~ ^/packs/ {
add_header Cache-Control "public, max-age=2419200, must-revalidate";
add_header Strict-Transport-Security "max-age=63072000; includeSubDomains";
try_files $uri @proxy;
}

location ~ ^/shortcuts/ {
add_header Cache-Control "public, max-age=2419200, must-revalidate";
add_header Strict-Transport-Security "max-age=63072000; includeSubDomains";
try_files $uri @proxy;
}

location ~ ^/sounds/ {
add_header Cache-Control "public, max-age=2419200, must-revalidate";
add_header Strict-Transport-Security "max-age=63072000; includeSubDomains";
try_files $uri @proxy;
}

location ~ ^/system/ {
add_header Cache-Control "public, max-age=2419200, immutable";
add_header Strict-Transport-Security "max-age=63072000; includeSubDomains";
add_header X-Content-Type-Options nosniff;
add_header Content-Security-Policy "default-src 'none'; form-action 'none'";
try_files $uri @proxy;
}

location ^~ /api/v1/streaming {
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 http://streaming;
proxy_buffering off;
proxy_redirect off;
proxy_http_version 1.1;
proxy_set_header Upgrade $http_upgrade;
proxy_set_header Connection $connection_upgrade;

add_header Strict-Transport-Security "max-age=63072000; includeSubDomains";

tcp_nodelay on;
}

location @proxy {
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_pass http://backend_mast;
proxy_buffering on;
proxy_redirect off;
proxy_http_version 1.1;
proxy_set_header Upgrade $http_upgrade;
proxy_set_header Connection $connection_upgrade;

proxy_cache CACHE;
proxy_cache_valid 200 7d;
proxy_cache_valid 410 24h;
proxy_cache_use_stale error timeout updating http_500 http_502 http_503 http_504;
add_header X-Cached $upstream_cache_status;

tcp_nodelay on;
}

error_page 404 500 501 502 503 504 /500.html;
}

Включаем сайт:

ln -s /etc/nginx/sites-available/toot.iamninja.ru.conf /etc/nginx/sites-enabled/toot.iamninja.ru.conf

Проверяем конфигурацию, перезапускаем веб сервер:

nginx -t
systemctl restart nginx

Идём в браузер — https://toot.iamninja.ru, попадаем на страницу about, здесь указана оснавная информация о сервере. Логинимся, радуемся что стали частью сообщества, дальше уже обычная рутина, пробегаемся по настройкам, ставим чек боксы, описываем модерационную политику, описываем правила нашей ноды и так далее.

Рекомендации, как наполнить свою ленту

Рекомендации были взяты с темы на сайте 4pda.

Рекомендации для новичков, которые только пришли в Мастодон и не знают, что делать дальше (посты Злого Гика)

Возможно, ты используешь Mastodon неправильно поможет понять отличие от корпоративных социальных сетей и сервисов.

Заключительная часть, а что там по бэкапам?

Будем всё бэкапить!

Снимаем дамп БД:

cd ~
cd /mastodon
docker compose down
docker compose up -d db
docker exec -t mastodon-db-1 pg_dump -Fc -U postgres -f '/export.dump'
docker cp mastodon-db-1:/export.dump export.dump
docker compose down
docker compose up -d

Далее дамп, docker-compose.yml и .env.production копируем в надёжное место.