Various build shenenigans (#5)

This PR extends the build pipeline of the www service to support
locale-prefixed static builds (EN/RU) with build-time SSG output
injection, while also refactoring UI typography to a tokenized Tailwind
v4 @theme-driven approach.

Changes:

- Add build-time SSG plugin to @ermnvldmr/rsbuild-config and wire
multi-locale builds (/en/, /ru/) into services/www.
- Introduce new @ermnvldmr/i18n package (locale constants, path helpers,
keysets, pluralization) and apply it to www navigation/text.
- Refactor ui typography to a shared getTypographyClassNames utility +
Tailwind token definitions, with stl/cn updated for correct
Tailwind-merge behavior.
This commit is contained in:
deytenit authored and GitHub committed 2026-03-16 00:00:20 +03:00
1 parent ac9c455e5a
commit b84ef775b8
68 files changed
+2991 -397

No files matched your search

@@ -0,0 +1,280 @@
---
title: 'Руководство по развёртыванию Matrix-сервера'
description: 'Развёртывание серверов Synapse и LiveKit с нуля'
createdDate: 2026-02-15
tags: ['matrix', 'deployment', 'guide']
---
Это руководство предназначено **только для образовательных целей**. В нем показано, как развернуть приватную коммуникационную инфраструктуру, чтобы лучше понять децентрализованный протокол Matrix и техническую реализацию медиа-релеев.
## Зачем поднимать сервер самостоятельно?
Централизованные платформы нередко жертвуют приватностью данных ради аналитики или зависят от внешних изменений политики. При развертывании собственного Matrix-инстанса вы получаете:
- **Суверенитет данных**: железо и база данных находятся под вашим контролем.
- **Сквозное шифрование (E2EE)**: сообщения шифруются локально на устройствах, сервер передает только шифртекст.
- **Устойчивость инфраструктуры**: ваши коммуникации не зависят от простоев крупных провайдеров.
- **Приватность метаданных**: вы сами определяете, какие логи о взаимодействиях пользователей сохраняются.
---
## Обзор технического стека
1. **Synapse**: "homeserver", который реализует Matrix-протокол, логику учетных записей и синхронизацию сообщений.
2. **Postgres**: база данных. Для production обязательна по соображениям производительности (SQLite подходит только для тестов).
3. **Coturn (STUN/TURN)**: при звонках между двумя участниками фаерволы часто мешают прямому соединению. Coturn работает как релей и помогает обойти NAT-ограничения.
4. **LiveKit**: современный медиадвижок (SFU). Если Coturn нужен в первую очередь для 1:1-звонков, то LiveKit эффективно обрабатывает групповые звонки через мультиплексирование аудио/видеопотоков.
5. **lk-jwt-service**: сервис-аутентификатор. Проверяет, что пользователь авторизован в вашем Synapse-сервере, и выдает временный токен (JWT) для входа в звонок.
---
## 1. Стек развертывания
Эта конфигурация универсальна, использует **Docker Volumes** и настроена под **UID 1000:1000** для базового харднинга.
```yaml
networks:
proxy:
name: matrix-proxy
internal:
internal: true
volumes:
synapse_data:
postgres_data:
traefik_certs:
services:
traefik:
image: traefik:v3.0
container_name: matrix-proxy
command:
- '--providers.docker=true'
- '--providers.docker.exposedbydefault=false'
- '--entrypoints.web.address=:80'
- '--entrypoints.websecure.address=:443'
- '--entrypoints.web.http.redirections.entrypoint.to=websecure'
- '--certificatesresolvers.myresolver.acme.dnschallenge.provider=cloudflare'
- '--certificatesresolvers.myresolver.acme.email=${ADMIN_EMAIL}'
- '--certificatesresolvers.myresolver.acme.storage=/letsencrypt/acme.json'
environment:
- CF_DNS_API_TOKEN=${CF_DNS_API_TOKEN}
ports:
- '80:80'
- '443:443'
volumes:
- traefik_certs:/letsencrypt
- /var/run/docker.sock:/var/run/docker.sock:ro
networks:
- proxy
restart: unless-stopped
synapse:
image: ghcr.io/element-hq/synapse:v1.146.0
container_name: matrix-synapse
user: '1000:1000'
depends_on:
postgres: { condition: service_healthy }
volumes:
- synapse_data:/data
environment:
SYNAPSE_SERVER_NAME: '${DOMAIN}'
SYNAPSE_REPORT_STATS: 'no'
SYNAPSE_CONFIG_PATH: '/data/homeserver.yaml'
networks:
- proxy
- internal
labels:
- 'traefik.enable=true'
- 'traefik.http.routers.synapse.rule=Host(`${DOMAIN}`)'
- 'traefik.http.services.synapse.loadbalancer.server.port=8008'
restart: unless-stopped
postgres:
image: postgres:16-alpine
container_name: matrix-db
user: '1000:1000'
volumes:
- postgres_data:/var/lib/postgresql/data
environment:
POSTGRES_DB: synapse
POSTGRES_USER: synapse
POSTGRES_PASSWORD: ${DB_PASSWORD}
POSTGRES_INITDB_ARGS: '--encoding=UTF-8 --lc-collate=C --lc-ctype=C'
networks:
- internal
healthcheck:
test: ['CMD-SHELL', 'pg_isready -U synapse']
interval: 10s
restart: unless-stopped
livekit:
image: livekit/livekit-server:v1.9
container_name: matrix-livekit
command: --config /etc/livekit.yaml --node-ip ${SERVER_IP}
environment:
LIVEKIT_KEYS: '${LK_KEY}: ${LK_SECRET}'
volumes:
- ./livekit.yaml:/etc/livekit.yaml:ro
ports:
- '7880:7880'
- '7881:7881'
- '50000-50020:50000-50020/udp'
networks:
- proxy
labels:
- 'traefik.enable=true'
- 'traefik.http.routers.lk.rule=Host(`${DOMAIN}`) && PathPrefix(`/livekit`)'
- 'traefik.http.middlewares.lk-strip.stripprefix.prefixes=/livekit'
- 'traefik.http.routers.lk.middlewares=lk-strip'
restart: unless-stopped
lk-jwt:
image: ghcr.io/element-hq/lk-jwt-service:0.4.1
container_name: matrix-lk-jwt
environment:
LIVEKIT_URL: wss://${DOMAIN}/livekit
LIVEKIT_KEY: '${LK_KEY}'
LIVEKIT_SECRET: '${LK_SECRET}'
networks:
- proxy
labels:
- 'traefik.enable=true'
- 'traefik.http.routers.lk-jwt.rule=Host(`${DOMAIN}`) && PathPrefix(`/livekit/jwt`)'
- 'traefik.http.middlewares.lk-jwt-strip.stripprefix.prefixes=/livekit/jwt'
- 'traefik.http.routers.lk-jwt.middlewares=lk-jwt-strip'
restart: unless-stopped
coturn:
image: coturn/coturn:4.8-alpine
container_name: matrix-coturn
user: '1000:1000'
ports:
- '3478:3478'
- '3478:3478/udp'
- '5349:5349'
- '5349:5349/udp'
- '49152-49172:49152-49172/udp'
volumes:
- ./turnserver.conf:/etc/coturn/turnserver.conf:ro
restart: unless-stopped
```
---
## 2. Конфигурация
Создайте файл `.env` в той же директории. **Не пропускайте шаг генерации секретов ниже.**
```bash
# --- Infrastructure ---
DOMAIN=matrix.example.com
SERVER_IP=1.2.3.4
ADMIN_EMAIL=admin@example.com
# --- Cloudflare (for Traefik SSL) ---
CF_DNS_API_TOKEN=your_cloudflare_api_token_here
# --- Database ---
DB_PASSWORD=generate_this_with_openssl
# --- LiveKit Secrets ---
# API Key can be any alphanumeric string
LK_KEY=matrix_lk_key
# Secret MUST be generated
LK_SECRET=generate_this_with_openssl
# --- Coturn / Synapse VoIP ---
# Used for STUN/TURN authentication
TURN_SECRET=generate_this_with_openssl
```
### Генерация секретов
Используйте `openssl`, чтобы создать надежные случайные строки для параметров в `.env`:
```bash
# Generate DB_PASSWORD, LK_SECRET, and TURN_SECRET
openssl rand -hex 32
```
---
## 3. Вариант с Nginx и сертификатами
Если вы не используете Traefik, получите сертификаты через **Certbot**:
```bash
sudo certbot certonly --standalone -d matrix.example.com
```
### Конфиг Nginx
```nginx
server {
listen 443 ssl;
server_name matrix.example.com;
ssl_certificate /etc/letsencrypt/live/matrix.example.com/fullchain.pem;
ssl_certificate_key /etc/letsencrypt/live/matrix.example.com/privkey.pem;
location /_matrix {
proxy_pass http://synapse:8008;
proxy_set_header Host $host;
}
location /livekit/ {
rewrite ^/livekit/(.*) /$1 break;
proxy_pass http://livekit:7880;
proxy_http_version 1.1;
proxy_set_header Upgrade $http_upgrade;
proxy_set_header Connection "Upgrade";
}
location /livekit/jwt/ {
rewrite ^/livekit/jwt/(.*) /$1 break;
proxy_pass http://lk-jwt:8080;
}
}
```
---
## 4. Регистрация пользователей
### Ручная регистрация (администратором)
Для приватных серверов отключите публичную регистрацию в `homeserver.yaml` и добавляйте пользователей через CLI:
```bash
docker exec -it matrix-synapse register_new_matrix_user \
-c /data/homeserver.yaml http://localhost:8008 \
--user <username> --password <password> --admin
```
### SMTP (внешняя регистрация)
Добавьте в `homeserver.yaml`, чтобы включить подтверждение по email:
```yaml
email:
smtp_host: 'smtp.gmail.com'
smtp_port: 587
smtp_user: 'yourname@gmail.com'
smtp_pass: 'xxxx xxxx xxxx xxxx' # Google App Password
force_tls: true
```
---
## Ресурсы
- [Synapse GitHub](https://github.com/element-hq/synapse)
- [LiveKit GitHub](https://github.com/livekit/livekit)
- [lk-jwt-service GitHub](https://github.com/element-hq/lk-jwt-service)
- [Element X Apps](https://www.google.com/search?q=https://element.io/element-x)
@@ -0,0 +1,15 @@
---
title: 'Привет, мир'
description: 'Моя первая статья в новой системе контента.'
createdDate: 2026-01-31
tags: ['test', 'general']
---
## Раздел 1
Немного содержимого здесь.
```typescript
const hello = 'world';
console.log(hello);
```
@@ -0,0 +1,182 @@
---
title: 'Мимикрия подсетей Xray'
description: 'Построение распределённой приватной сети 10.0.0.0/8 с использованием Xray-core'
createdDate: 2026-03-08
tags: ['networking', 'guide']
---
> Это руководство предназначено **исключительно для образовательных целей**. Оно демонстрирует маршрутизацию в Xray-core и подход к мимикрии приватной подсети. Материал НЕ предназначен для обхода ограничений или незаконной деятельности. Используйте эти знания ответственно и в соответствии с местным законодательством.
## Зачем нужна мимикрия подсети?
Если построить "плоскую сеть" (Layer 3) поверх публичного интернета, можно получить единое приватное адресное пространство (`10.0.0.0/8`), распределенное между несколькими отдельными серверами.
- **Распределенная топология**: каждый узел выступает в роли основного шлюза для своего сегмента `/16` (например, Hub для `10.1.0.0/16`, Spoke для `10.2.0.0/16` и `10.3.0.0/16`).
- **Преодоление NAT**: Spoke-узлы (Bridges) сами инициируют соединения с Hub-узлом (Portal), благодаря чему даже узлы за NAT остаются доступными.
- **Централизованное разрешение DNS**: сетевые DNS-политики единообразно применяются через центральный резолвер на Hub.
---
## Обзор технического стека
1. **Xray-core**: движок маршрутизации для VLESS-соединений, reverse-туннелей и TPROXY-перехвата.
2. **TPROXY**: прозрачное проксирование в таблице Linux Mangle, которое перехватывает TCP/UDP-трафик и сохраняет исходный IP назначения.
3. **Policy Routing**: использование `ip rule` и таблицы маршрутизации 100 для принудительной отправки перехваченных пакетов в локальный loopback, где их обрабатывает Xray.
4. **Mux.cool**: протокол мультиплексирования, который упаковывает сотни виртуальных соединений (TCP/UDP-потоков) в одно физическое TLS-соединение.
5. **AdGuard Home**: централизованный DNS-резолвер для всей подсети.
6. **Nginx**: используется как маскировка на основном публичном входном порту, чтобы скрыть инстанс Xray.
---
## 1. Стек развертывания
Эта конфигурация описывает центральный Hub (сервер A), который выступает как Portal для подключений Spoke-узлов. На Hub размещены централизованный DNS и fallback-маскировка через Nginx.
```yaml
name: 'a'
services:
xray:
image: ghcr.io/xtls/xray-core:26.1.23
container_name: a-xray
network_mode: host
volumes:
- ./xray.jsonc:/etc/xray/config.jsonc:ro
- /etc/letsencrypt/live:/etc/letsencrypt/live:ro
command: -c '/etc/xray/config.jsonc'
restart: unless-stopped
cap_add:
- NET_ADMIN
- NET_RAW
nginx:
image: nginxinc/nginx-unprivileged:1.29-alpine
container_name: a-nginx
networks:
direct:
ipv4_address: 10.1.1.4
restart: unless-stopped
security_opt:
- no-new-privileges:true
adguard:
image: adguard/adguardhome:v0.107.72
container_name: root-adguard
volumes:
- adguard-work:/opt/adguardhome/work
- adguard-conf:/opt/adguardhome/conf
networks:
direct:
ipv4_address: 10.1.1.3
restart: unless-stopped
cap_add:
- NET_BIND_SERVICE
- SYS_NICE
- CAP_CHOWN
security_opt:
- no-new-privileges:true
networks:
direct:
name: a-direct
driver: bridge
ipam:
config:
- subnet: 10.1.0.0/16 # Using the full /16 as defined for Server A
gateway: 10.1.0.1
volumes:
adguard-work:
name: a-adguard-work
adguard-conf:
name: a-adguard-conf
```
---
## 2. Конфигурация и маршрутизация
### Генерация UUID-идентификаторов
Каждому соединению нужен собственный криптографический "ключ". Для сопоставления с конфигами вам понадобятся 4 UUID.
```bash
# Generate UUIDs for the Server B and Server C mappings
docker run --rm ghcr.io/xtls/xray-core uuid
```
Соответствие такое:
- `UUID_B_REVERSE`: управляющий туннель для сервера B.
- `UUID_B_DIRECT`: пользовательский трафик сервера B.
- `UUID_C_REVERSE`: управляющий туннель для сервера C.
- `UUID_C_DIRECT`: пользовательский трафик сервера C.
### Маршрутизация по политикам (таблица 100)
Примените скрипт маршрутизации на **каждом** серверном узле, чтобы настроить параметры ядра и таблицу маршрутизации 100. Это заставляет ядро передавать пакет в наш TPROXY inbound, который слушает `127.0.0.1:12345`.
```bash
#!/usr/bin/env bash
# 1. Enable IP Forwarding
sudo sysctl -w net.ipv4.ip_forward=1
# 2. IP Rule for mark 0x1 -> table 100
sudo ip rule add fwmark 0x1 lookup 100 priority 100
# 3. Local route in table 100
sudo ip route add local 0.0.0.0/0 dev lo table 100
```
---
## 3. Перехват в firewall
Откройте `/etc/ufw/before.rules` и сразу после обязательного заголовка `*filter` добавьте правила конкретного узла. Ниже пример для сервера A (Hub, `10.1.0.0/16`):
```text
*mangle
:XRAY - [0:0]
:XRAY_MASK - [0:0]
:DIVERT - [0:0]
# --- PREROUTING CHAIN (Traffic from other containers/nodes) ---
-A XRAY -d 10.1.0.0/16 -j RETURN
-A XRAY ! -d 10.0.0.0/8 -j RETURN
-A XRAY -m mark --mark 0xff -j RETURN
-A XRAY -p udp -j TPROXY --on-ip 127.0.0.1 --on-port 12345 --tproxy-mark 1
-A XRAY -p tcp -j TPROXY --on-ip 127.0.0.1 --on-port 12345 --tproxy-mark 1
-A PREROUTING -j XRAY
# --- OUTPUT CHAIN (Traffic originating from the server itself) ---
-A XRAY_MASK -d 10.1.0.0/16 -j RETURN
-A XRAY_MASK ! -d 10.0.0.0/8 -j RETURN
-A XRAY_MASK -m mark --mark 0xff -j RETURN
-A XRAY_MASK -p udp -j MARK --set-mark 1
-A XRAY_MASK -p tcp -j MARK --set-mark 1
-A OUTPUT -j XRAY_MASK
# --- DIVERT Optimization ---
-A DIVERT -j MARK --set-mark 1
-A DIVERT -j ACCEPT
-I PREROUTING -p tcp -m socket -j DIVERT
COMMIT
```
После применения перезагрузите UFW:
```bash
sudo ufw reload
```
---
## Ресурсы
- [Официальная документация Xray-core](https://xtls.github.io/)
- [Документация Xray Reverse Proxy](https://xtls.github.io/en/config/reverse.html)
- [Project X (Xray) на GitHub](https://github.com/XTLS/Xray-core)
- [Документация Linux Kernel по TPROXY](https://www.kernel.org/doc/Documentation/networking/tproxy.txt)
- [Блог Cloudflare: TPROXY](https://blog.cloudflare.com/how-we-built-spectrum/)
+3 -3
View File
@@ -1,7 +1,7 @@
import React from 'react';
import * as TestArticle from './articles/2026/test-article.mdx';
import * as MatrixServerDeploymentGuide from './articles/2026/matrix-server-deployment-guide.mdx';
import * as XrayCoreSubnetMimicry from './articles/2026/xray-core-subnet-mimicry.mdx';
import * as TestArticle from './articles/2026/test-article.en.mdx';
import * as MatrixServerDeploymentGuide from './articles/2026/matrix-server-deployment-guide.en.mdx';
import * as XrayCoreSubnetMimicry from './articles/2026/xray-core-subnet-mimicry.en.mdx';
export interface Article {
slug: string;