feat(www): add Russian localization for homepage and articles

Complete Russian translations for homepage copy and all current www articles so the RU locale has native-quality content parity with EN.

Made-with: Cursor
This commit is contained in:
deytenit committed 2026-03-15 23:34:30 +03:00
1 parent bb4c71d51c
commit 561118b274
5 files changed
+539 -41

No files matched your search

@@ -5,6 +5,276 @@ createdDate: 2026-02-15
tags: ['matrix', 'deployment', 'guide']
---
{/* TODO: Russian translation pending */}
Это руководство предназначено **только для образовательных целей**. В нем показано, как развернуть приватную коммуникационную инфраструктуру, чтобы лучше понять децентрализованный протокол 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)
@@ -5,6 +5,11 @@ createdDate: 2026-01-31
tags: ['test', 'general']
---
{/* TODO: Russian translation pending */}
## Раздел 1
> Это руководство предназначено **только для образовательных целей**.
Немного содержимого здесь.
```typescript
const hello = 'world';
console.log(hello);
```
@@ -5,6 +5,178 @@ createdDate: 2026-03-08
tags: ['networking', 'guide']
---
{/* TODO: Russian translation pending */}
> Это руководство предназначено **исключительно для образовательных целей**. Оно демонстрирует маршрутизацию в 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/)
+45
View File
@@ -0,0 +1,45 @@
import { createKeyset } from '@ermnvldmr/i18n';
export const { t, tRaw } = createKeyset({
'Thoughts and creations of mine': {
en: 'Thoughts and creations of mine',
ru: 'Мои мысли и проекты',
},
'Welcome to my corner of the internet.': {
en: 'Welcome to my corner of the internet.',
ru: 'Добро пожаловать в мой уголок интернета.',
},
'You arrived right as the lights turned on. The core foundation is solid and the site is officially live, but the metaphorical boxes are still being unpacked. Expect this space to evolve continuously as everything finds its proper place.':
{
en: 'You arrived right as the lights turned on. The core foundation is solid and the site is officially live, but the metaphorical boxes are still being unpacked. Expect this space to evolve continuously as everything finds its proper place.',
ru: 'Вы зашли как раз в тот момент, когда здесь включили свет. Базовый фундамент уже крепкий, и сайт официально запущен, но метафорические коробки все еще распаковываются. Ожидайте, что это пространство будет постоянно развиваться, пока все не встанет на свои места.',
},
'Sharing the underlying architecture is a core philosophy here. If you are curious about the specifics of how it is all built, you can inspect the {repositoryLink} to see the complete blueprints.':
{
en: 'Sharing the underlying architecture is a core philosophy here. If you are curious about the specifics of how it is all built, you can inspect the {repositoryLink} to see the complete blueprints.',
ru: 'Открыто рассказывать о внутренней архитектуре - один из ключевых принципов этого места. Если вам интересно, как именно все устроено, загляните в {repositoryLink}, чтобы увидеть полную картину.',
},
'ermnvldmr.com repository': {
en: 'ermnvldmr.com repository',
ru: 'репозиторий ermnvldmr.com',
},
'For a look at the world away from terminal screens and code editors, the {instagramLink} visual diary captures those moments.':
{
en: 'For a look at the world away from terminal screens and code editors, the {instagramLink} visual diary captures those moments.',
ru: 'Если хочется посмотреть на мир вне терминалов и редакторов кода, эти моменты собраны в визуальном дневнике {instagramLink}.',
},
'Whenever you are ready to look around, feel free to dive into {articlesLink}. It is a dedicated space covering everything from technical deep-dives to everyday reflections.':
{
en: 'Whenever you are ready to look around, feel free to dive into {articlesLink}. It is a dedicated space covering everything from technical deep-dives to everyday reflections.',
ru: 'Когда будете готовы осмотреться, загляните в {articlesLink}. Это отдельное пространство для всего - от глубоких технических разборов до повседневных наблюдений.',
},
'the growing collection of articles': {
en: 'the growing collection of articles',
ru: 'коллекцию статей, которая постоянно растет',
},
'Even though the rest of the environment is still a work in progress, the communication lines are fully operational. Whether you want to discuss tech, share a perspective on a recent read, or simply make a connection, the mailbox is open at...':
{
en: 'Even though the rest of the environment is still a work in progress, the communication lines are fully operational. Whether you want to discuss tech, share a perspective on a recent read, or simply make a connection, the mailbox is open at...',
ru: 'Хотя остальная часть пространства еще дорабатывается, каналы связи уже работают в полном объеме. Если хотите обсудить технологии, поделиться мнением о прочитанном или просто познакомиться, почта всегда открыта:',
},
});
+41 -35
View File
@@ -2,16 +2,17 @@ import { localePath } from '@ermnvldmr/i18n';
import { Paragraph, Link, VStack, Header } from '@ermnvldmr/ui';
import React from 'react';
import { t, tRaw } from './index.i18n';
import { DefaultLayout } from '../components/Layout/DefaultLayout';
import { createPage } from '../lib/core/createPage';
import { SITE_DESCRIPTION, SITE_TITLE } from '../lib/shared/constants';
import { SITE_TITLE } from '../lib/shared/constants';
/**
* Home page component.
*
* @example
* ```tsx
* createPage(HomePage, { title: SITE_TITLE, description: SITE_DESCRIPTION });
* createPage(HomePage, { title: SITE_TITLE, description: t('Thoughts and creations of mine') });
* ```
*/
function HomePage(): React.JSX.Element {
@@ -23,49 +24,54 @@ function HomePage(): React.JSX.Element {
>
<VStack gap={24}>
<Header delay={0} level={1}>
Welcome to my corner of the internet.
{t('Welcome to my corner of the internet.')}
</Header>
<VStack gap={12}>
<Paragraph delay={300} size="l" type="title">
You are arrived right as the lights turned on. The core foundation is solid and the site
is officially live,
<br />
but the metaphorical boxes are still being unpacked. Expect this space to evolve
continuously as everything finds its proper place.
{t(
'You arrived right as the lights turned on. The core foundation is solid and the site is officially live, but the metaphorical boxes are still being unpacked. Expect this space to evolve continuously as everything finds its proper place.'
)}
</Paragraph>
<Paragraph delay={350} size="l" type="title">
Sharing the underlying architecture is a core philosophy here. If you are curious about
the specifics of how it is all built,
<br />
you can inspect the{' '}
<Link href="https://github.com/deytenit/ermnvldmr.com/" size="l" type="title">
ermnvldmr.com repository
</Link>{' '}
to see the complete blueprints.
{tRaw(
'Sharing the underlying architecture is a core philosophy here. If you are curious about the specifics of how it is all built, you can inspect the {repositoryLink} to see the complete blueprints.',
{
repositoryLink: (
<Link href="https://github.com/deytenit/ermnvldmr.com/" size="l" type="title">
{t('ermnvldmr.com repository')}
</Link>
),
}
)}
</Paragraph>
<Paragraph delay={400} size="l" type="title">
For a look at the world away from terminal screens and code editors, the{' '}
<Link href="https://ermnvldmr.com/instagram" size="l" type="title">
@deytenit
</Link>{' '}
visual diary captures those moments.
{tRaw(
'For a look at the world away from terminal screens and code editors, the {instagramLink} visual diary captures those moments.',
{
instagramLink: (
<Link href="https://ermnvldmr.com/instagram" size="l" type="title">
@deytenit
</Link>
),
}
)}
</Paragraph>
<Paragraph delay={450} size="l" type="title">
Whenever you are ready to look around, feel free to dive into{' '}
<Link href={localePath('/articles')} size="l" type="title">
the growing collection of articles
</Link>
.
<br />
It is a dedicated space covering everything from technical deep-dives to everyday
reflections.
{tRaw(
'Whenever you are ready to look around, feel free to dive into {articlesLink}. It is a dedicated space covering everything from technical deep-dives to everyday reflections.',
{
articlesLink: (
<Link href={localePath('/articles')} size="l" type="title">
{t('the growing collection of articles')}
</Link>
),
}
)}
</Paragraph>
<Paragraph delay={500} size="l" type="title">
Even though the rest of the environment is still a work in progress, the communication
lines are fully operational.
<br />
Whether you want to discuss tech, share a perspective on a recent read, or simply make a
connection, the mailbox is open at...
{t(
'Even though the rest of the environment is still a work in progress, the communication lines are fully operational. Whether you want to discuss tech, share a perspective on a recent read, or simply make a connection, the mailbox is open at...'
)}
</Paragraph>
</VStack>
<Link delay={1000} href="mailto:personal@ermnvldmr.com" size="l" type="title">
@@ -76,5 +82,5 @@ function HomePage(): React.JSX.Element {
);
}
createPage(HomePage, { title: SITE_TITLE, description: SITE_DESCRIPTION });
createPage(HomePage, { title: SITE_TITLE, description: t('Thoughts and creations of mine') });
export default HomePage;