Element
コミュニケーションツール
Element
概要
Elementは、Matrixプロトコルを基盤とした分散型でプライバシー重視のコミュニケーションプラットフォームです。完全なエンドツーエンド暗号化、セルフホスト可能な分散アーキテクチャ、オープンソースの透明性を提供します。Slack的なチーム機能とWhatsApp的なプライベートメッセージングを組み合わせ、企業レベルのセキュリティを実現します。
詳細
Element(エレメント)は、2019年にRiot.imから改名されたMatrix protocolベースのコミュニケーションプラットフォームです。Matrix.orgによって開発され、分散型アーキテクチャにより単一障害点のない通信インフラストラクチャを提供します。特に政府機関、医療機関、プライバシー重視の企業での採用が拡大しています。
2024-2025年には、Element Xモバイルアプリケーションの全面リニューアル、Rust SDK (matrix-rust-sdk) への移行、新しいSliding Sync API、Element Call(音声・ビデオ通話機能)の強化、Matrix 2.0仕様への対応が行われました。また、Self-Sovereign Identity (SSI) 対応、OIDC統合強化、Administratorsコンソール改善などの大幅な機能拡張が実装されています。
Matrix SDKはJavaScript、Python、Rust、Swift、Kotlinなど多言語対応により、Bot開発、カスタムクライアント作成、システム統合が可能です。また、Bridging機能によりSlack、Discord、Telegramなど既存のプラットフォームとの相互運用性を提供します。
メリット・デメリット
メリット
- 完全な分散化: 単一障害点なし、政府・企業による検閲耐性
- エンドツーエンド暗号化: デフォルトでE2EE、メッセージとファイルの完全暗号化
- オープンソース: 完全な透明性、コードレビュー可能
- セルフホスト対応: 完全なデータ主権、GDPR準拠
- Bridging機能: 既存プラットフォームとの相互運用性
- 豊富なAPI/SDK: JavaScript、Python、Rust等多言語サポート
- 政府・医療機関採用: ドイツ政府、フランス政府等での正式採用
- オープンスタンダード: 標準化されたMatrix protocol
デメリット
- 複雑なセットアップ: 初期設定とサーバー構築の技術的難易度
- リソース要求: セルフホスト時のサーバーリソース消費
- ユーザビリティ: SlackやDiscordと比べた学習コスト
- エコシステム規模: 主流プラットフォームと比較した利用者数
- モバイル体験: Element Xでの改善はあるものの、まだ発展中
- パフォーマンス: 大規模ルームでの同期速度
主要リンク
- Element公式サイト
- Matrix.org開発者ポータル
- Element GitHub
- Matrix JavaScript SDK
- Synapse ホームサーバー
- Element Docker
- Matrix Specification
書き方の例
JavaScript Matrix SDK を使用したBot開発
// Element/Matrix Bot実装 - matrix-js-sdk使用
import { MatrixClient, SimpleFsStorageProvider, AutojoinRoomsMixin } from 'matrix-bot-sdk';
class ElementBot {
constructor(homeserverUrl, accessToken) {
const storage = new SimpleFsStorageProvider('bot.json');
this.client = new MatrixClient(homeserverUrl, accessToken, storage);
// Auto-join invited rooms
AutojoinRoomsMixin.setupOnClient(this.client);
this.setupEventHandlers();
}
setupEventHandlers() {
// Message event handler
this.client.on('room.message', async (roomId, event) => {
if (event['sender'] === await this.client.getUserId()) return;
const messageBody = event['content']['body'];
console.log(`📩 Message in ${roomId}: ${messageBody}`);
await this.handleMessage(roomId, event, messageBody);
});
// Room join event handler
this.client.on('room.join', async (roomId, event) => {
console.log(`🚪 Joined room: ${roomId}`);
await this.sendWelcomeMessage(roomId);
});
// Encryption event handler
this.client.on('room.encrypted_message', async (roomId, event) => {
console.log(`🔐 Encrypted message received in ${roomId}`);
// Handle encrypted messages (requires device verification)
});
}
async handleMessage(roomId, event, messageBody) {
const senderId = event['sender'];
// Command processing
if (messageBody.startsWith('!')) {
await this.processCommand(roomId, messageBody, senderId);
return;
}
// Keyword detection
if (messageBody.toLowerCase().includes('help')) {
await this.sendHelpMessage(roomId);
}
// AI integration example
if (messageBody.toLowerCase().includes('@bot')) {
await this.handleAIResponse(roomId, messageBody);
}
}
async processCommand(roomId, messageBody, senderId) {
const [command, ...args] = messageBody.slice(1).split(' ');
switch (command.toLowerCase()) {
case 'ping':
await this.sendMessage(roomId, '🏓 Pong!');
break;
case 'status':
await this.sendSystemStatus(roomId);
break;
case 'weather':
await this.sendWeatherInfo(roomId, args[0] || 'Tokyo');
break;
case 'encrypt':
await this.enableEncryption(roomId);
break;
case 'members':
await this.listRoomMembers(roomId);
break;
case 'create':
if (args[0] === 'room') {
await this.createRoom(args.slice(1).join(' '), senderId);
}
break;
default:
await this.sendMessage(roomId, `❓ 不明なコマンド: ${command}. !helpと入力してください。`);
}
}
async sendMessage(roomId, message, msgtype = 'm.text') {
const content = {
msgtype: msgtype,
body: message,
format: 'org.matrix.custom.html',
formatted_body: message.replace(/\n/g, '<br/>')
};
try {
await this.client.sendMessage(roomId, content);
} catch (error) {
console.error('メッセージ送信エラー:', error);
}
}
async sendRichMessage(roomId, plainText, htmlText) {
const content = {
msgtype: 'm.text',
body: plainText,
format: 'org.matrix.custom.html',
formatted_body: htmlText
};
try {
await this.client.sendMessage(roomId, content);
} catch (error) {
console.error('リッチメッセージ送信エラー:', error);
}
}
async sendWelcomeMessage(roomId) {
const welcomeHtml = `
<h3>🚀 Element Bot へようこそ!</h3>
<p>利用可能なコマンド:</p>
<ul>
<li><code>!ping</code> - 接続テスト</li>
<li><code>!status</code> - システム状態</li>
<li><code>!weather [city]</code> - 天気情報</li>
<li><code>!encrypt</code> - 暗号化有効化</li>
<li><code>!members</code> - メンバー一覧</li>
<li><code>!help</code> - ヘルプ表示</li>
</ul>
`;
const plainText = `🚀 Element Bot へようこそ!
利用可能なコマンド:
- !ping - 接続テスト
- !status - システム状態
- !weather [city] - 天気情報
- !encrypt - 暗号化有効化
- !members - メンバー一覧
- !help - ヘルプ表示`;
await this.sendRichMessage(roomId, plainText, welcomeHtml);
}
async sendSystemStatus(roomId) {
const os = require('os');
const uptimeHours = Math.floor(os.uptime() / 3600);
const uptimeMinutes = Math.floor((os.uptime() % 3600) / 60);
const memoryUsage = Math.round((os.totalmem() - os.freemem()) / os.totalmem() * 100);
const statusHtml = `
<h4>📊 システム状態</h4>
<table>
<tr><td><strong>稼働時間</strong></td><td>${uptimeHours}時間 ${uptimeMinutes}分</td></tr>
<tr><td><strong>メモリ使用率</strong></td><td>${memoryUsage}%</td></tr>
<tr><td><strong>プラットフォーム</strong></td><td>${os.platform()} ${os.arch()}</td></tr>
<tr><td><strong>Node.js</strong></td><td>${process.version}</td></tr>
<tr><td><strong>Matrix SDK</strong></td><td>Connected ✅</td></tr>
</table>
`;
const plainText = `📊 システム状態
稼働時間: ${uptimeHours}時間 ${uptimeMinutes}分
メモリ使用率: ${memoryUsage}%
プラットフォーム: ${os.platform()} ${os.arch()}
Node.js: ${process.version}
Matrix SDK: Connected ✅`;
await this.sendRichMessage(roomId, plainText, statusHtml);
}
async sendWeatherInfo(roomId, city) {
try {
const response = await fetch(`https://api.openweathermap.org/data/2.5/weather?q=${city}&appid=${process.env.WEATHER_API_KEY}&units=metric&lang=ja`);
const weather = await response.json();
if (weather.cod === 200) {
const weatherHtml = `
<h4>🌤️ ${weather.name}の天気</h4>
<ul>
<li><strong>天気:</strong> ${weather.weather[0].description}</li>
<li><strong>気温:</strong> ${weather.main.temp}°C</li>
<li><strong>湿度:</strong> ${weather.main.humidity}%</li>
<li><strong>風速:</strong> ${weather.wind.speed} m/s</li>
</ul>
`;
const plainText = `🌤️ ${weather.name}の天気
天気: ${weather.weather[0].description}
気温: ${weather.main.temp}°C
湿度: ${weather.main.humidity}%
風速: ${weather.wind.speed} m/s`;
await this.sendRichMessage(roomId, plainText, weatherHtml);
} else {
await this.sendMessage(roomId, '❌ 天気情報の取得に失敗しました');
}
} catch (error) {
console.error('Weather API error:', error);
await this.sendMessage(roomId, '❌ 天気情報取得中にエラーが発生しました');
}
}
async enableEncryption(roomId) {
try {
await this.client.sendStateEvent(roomId, 'm.room.encryption', '', {
algorithm: 'm.megolm.v1.aes-sha2'
});
await this.sendMessage(roomId, '🔐 ルームの暗号化を有効にしました');
} catch (error) {
console.error('Encryption enable error:', error);
await this.sendMessage(roomId, '❌ 暗号化の有効化に失敗しました');
}
}
async listRoomMembers(roomId) {
try {
const members = await this.client.getRoomMembers(roomId);
const memberList = members
.filter(member => member.membership === 'join')
.map(member => member.stateKey)
.join('\n');
const memberHtml = `
<h4>👥 ルームメンバー (${members.length}人)</h4>
<ul>
${members.map(member => `<li>${member.stateKey}</li>`).join('')}
</ul>
`;
await this.sendRichMessage(roomId, `👥 ルームメンバー:\n${memberList}`, memberHtml);
} catch (error) {
console.error('List members error:', error);
await this.sendMessage(roomId, '❌ メンバー一覧の取得に失敗しました');
}
}
async createRoom(roomName, creatorId) {
try {
const options = {
name: roomName,
topic: `Created by bot for ${creatorId}`,
preset: 'private_chat',
is_direct: false,
invite: [creatorId]
};
const roomId = await this.client.createRoom(options);
await this.sendMessage(roomId, `🎉 ルーム "${roomName}" を作成しました!`);
return roomId;
} catch (error) {
console.error('Room creation error:', error);
throw error;
}
}
async uploadFile(roomId, filePath, fileName) {
const fs = require('fs');
try {
const fileBuffer = fs.readFileSync(filePath);
const mxcUrl = await this.client.uploadContent(fileBuffer, 'application/octet-stream', fileName);
const content = {
msgtype: 'm.file',
body: fileName,
filename: fileName,
url: mxcUrl
};
await this.client.sendMessage(roomId, content);
console.log(`📎 ファイルアップロード完了: ${fileName}`);
} catch (error) {
console.error('File upload error:', error);
}
}
async start() {
try {
await this.client.start();
console.log('🚀 Element Bot が起動しました');
// 定期タスクの設定
this.setupPeriodicTasks();
} catch (error) {
console.error('Bot起動エラー:', error);
}
}
setupPeriodicTasks() {
// 1時間ごとのヘルスチェック
setInterval(async () => {
console.log('⏰ 定期ヘルスチェック実行中...');
// 特定のルームにステータス報告(任意)
}, 3600000);
// 日次レポート (毎日9時)
const now = new Date();
const msUntil9AM = new Date(now.getFullYear(), now.getMonth(), now.getDate() + 1, 9, 0, 0, 0) - now;
setTimeout(() => {
setInterval(async () => {
console.log('📊 日次レポート生成中...');
// 管理ルームに日次レポート送信
}, 24 * 60 * 60 * 1000);
}, msUntil9AM);
}
async stop() {
await this.client.stop();
console.log('🛑 Element Bot を停止しました');
}
}
// 使用例
async function main() {
const homeserverUrl = process.env.MATRIX_HOMESERVER_URL || 'https://matrix.org';
const accessToken = process.env.MATRIX_ACCESS_TOKEN;
if (!accessToken) {
console.error('❌ MATRIX_ACCESS_TOKENが設定されていません');
process.exit(1);
}
const bot = new ElementBot(homeserverUrl, accessToken);
// グレースフルシャットダウン
process.on('SIGINT', async () => {
console.log('🔄 シャットダウン中...');
await bot.stop();
process.exit(0);
});
await bot.start();
}
main().catch(console.error);
Matrix Homeserver (Synapse) セットアップ
# docker-compose.yml for Synapse homeserver
version: '3.8'
services:
synapse:
image: matrixdotorg/synapse:latest
container_name: matrix-synapse
restart: unless-stopped
ports:
- "8008:8008"
volumes:
- ./synapse_data:/data
environment:
SYNAPSE_SERVER_NAME: your-domain.com
SYNAPSE_REPORT_STATS: "no"
SYNAPSE_HTTP_PORT: 8008
depends_on:
- postgres
networks:
- matrix_network
postgres:
image: postgres:15
container_name: matrix-postgres
restart: unless-stopped
environment:
POSTGRES_DB: synapse
POSTGRES_USER: synapse
POSTGRES_PASSWORD: ${POSTGRES_PASSWORD}
POSTGRES_INITDB_ARGS: "--encoding=UTF-8 --lc-collate=C --lc-ctype=C"
volumes:
- postgres_data:/var/lib/postgresql/data
networks:
- matrix_network
element:
image: vectorim/element-web:latest
container_name: matrix-element
restart: unless-stopped
ports:
- "80:80"
volumes:
- ./element_config.json:/app/config.json
networks:
- matrix_network
# Optional: Matrix-Discord Bridge
discord-bridge:
image: sorunome/mx-puppet-discord
container_name: matrix-discord-bridge
restart: unless-stopped
volumes:
- ./discord_config.yaml:/data/config.yaml
- discord_data:/data
networks:
- matrix_network
volumes:
postgres_data:
discord_data:
networks:
matrix_network:
driver: bridge
Element Web設定ファイル例
{
"default_server_config": {
"m.homeserver": {
"base_url": "https://your-domain.com",
"server_name": "your-domain.com"
},
"m.identity_server": {
"base_url": "https://vector.im"
}
},
"brand": "Element",
"integrations_ui_url": "https://scalar.vector.im/",
"integrations_rest_url": "https://scalar.vector.im/api",
"integrations_widgets_urls": [
"https://scalar.vector.im/_matrix/integrations/v1",
"https://scalar.vector.im/api",
"https://scalar-staging.vector.im/_matrix/integrations/v1",
"https://scalar-staging.vector.im/api",
"https://scalar-staging.riot.im/scalar/api"
],
"hosting_signup_link": "https://element.io/matrix-services?utm_source=element-web&utm_medium=web",
"bug_report_endpoint_url": "https://element.io/bugreports/submit",
"uisi_autorageshake_app": "element-auto-uisi",
"showLabsSettings": true,
"features": {
"feature_pinning": "labs",
"feature_custom_status": "labs",
"feature_custom_tags": "labs",
"feature_state_counters": "labs",
"feature_voice_messages": "labs",
"feature_poll": "labs",
"feature_location_share": "labs"
},
"default_country_code": "JP",
"map_style_url": "https://api.maptiler.com/maps/streets/style.json?key=YOUR_API_KEY"
}
Python Matrix SDK Bot開発
# Python Matrix Bot - matrix-nio SDK使用
import asyncio
import json
import os
from nio import AsyncClient, MatrixRoom, RoomMessageText, RoomMemberEvent
from nio.events.room_events import Event
import aiohttp
class ElementPythonBot:
def __init__(self, homeserver_url: str, user_id: str, access_token: str):
self.homeserver_url = homeserver_url
self.user_id = user_id
self.access_token = access_token
self.client = AsyncClient(homeserver_url, user_id)
self.client.access_token = access_token
# Event handlers
self.client.add_event_callback(self.message_callback, RoomMessageText)
self.client.add_event_callback(self.member_callback, RoomMemberEvent)
async def message_callback(self, room: MatrixRoom, event: RoomMessageText):
"""メッセージイベントのコールバック"""
if event.sender == self.user_id:
return # 自分のメッセージは無視
message_body = event.body
sender = event.sender
print(f"📩 {room.display_name} の {sender} からのメッセージ: {message_body}")
# コマンド処理
if message_body.startswith('!'):
await self.handle_command(room, event, message_body)
# キーワード検出
elif 'help' in message_body.lower():
await self.send_help_message(room.room_id)
# AI応答(例)
elif '@bot' in message_body.lower():
await self.handle_ai_response(room.room_id, message_body)
async def member_callback(self, room: MatrixRoom, event: RoomMemberEvent):
"""メンバー参加/退出イベントのコールバック"""
if event.membership == "join" and event.sender != self.user_id:
await self.send_welcome_message(room.room_id, event.sender)
async def handle_command(self, room: MatrixRoom, event: RoomMessageText, message_body: str):
"""コマンド処理"""
parts = message_body[1:].split()
command = parts[0].lower()
args = parts[1:] if len(parts) > 1 else []
if command == 'ping':
await self.send_message(room.room_id, "🏓 Pong!")
elif command == 'status':
await self.send_system_status(room.room_id)
elif command == 'weather':
city = args[0] if args else 'Tokyo'
await self.send_weather_info(room.room_id, city)
elif command == 'encrypt':
await self.enable_encryption(room.room_id)
elif command == 'room' and args:
if args[0] == 'info':
await self.send_room_info(room)
else:
await self.send_message(room.room_id, f"❓ 不明なコマンド: {command}")
async def send_message(self, room_id: str, message: str, formatted_message: str = None):
"""メッセージ送信"""
content = {
"msgtype": "m.text",
"body": message
}
if formatted_message:
content["format"] = "org.matrix.custom.html"
content["formatted_body"] = formatted_message
try:
await self.client.room_send(
room_id=room_id,
message_type="m.room.message",
content=content
)
except Exception as e:
print(f"❌ メッセージ送信エラー: {e}")
async def send_welcome_message(self, room_id: str, new_member: str):
"""ウェルカムメッセージ送信"""
message = f"🎉 {new_member} さん、ようこそ!\n\n利用可能なコマンド:\n!ping - 接続テスト\n!status - システム状態\n!weather [city] - 天気情報\n!help - ヘルプ表示"
formatted_message = f"""
<h3>🎉 {new_member} さん、ようこそ!</h3>
<p><strong>利用可能なコマンド:</strong></p>
<ul>
<li><code>!ping</code> - 接続テスト</li>
<li><code>!status</code> - システム状態</li>
<li><code>!weather [city]</code> - 天気情報</li>
<li><code>!help</code> - ヘルプ表示</li>
</ul>
"""
await self.send_message(room_id, message, formatted_message)
async def send_system_status(self, room_id: str):
"""システム状態送信"""
import psutil
import platform
cpu_percent = psutil.cpu_percent(interval=1)
memory = psutil.virtual_memory()
disk = psutil.disk_usage('/')
message = f"""📊 システム状態
CPU使用率: {cpu_percent}%
メモリ使用率: {memory.percent}%
ディスク使用率: {disk.percent}%
プラットフォーム: {platform.system()} {platform.machine()}"""
formatted_message = f"""
<h4>📊 システム状態</h4>
<table>
<tr><td><strong>CPU使用率</strong></td><td>{cpu_percent}%</td></tr>
<tr><td><strong>メモリ使用率</strong></td><td>{memory.percent}%</td></tr>
<tr><td><strong>ディスク使用率</strong></td><td>{disk.percent}%</td></tr>
<tr><td><strong>プラットフォーム</strong></td><td>{platform.system()} {platform.machine()}</td></tr>
</table>
"""
await self.send_message(room_id, message, formatted_message)
async def send_weather_info(self, room_id: str, city: str):
"""天気情報送信"""
api_key = os.getenv('WEATHER_API_KEY')
if not api_key:
await self.send_message(room_id, "❌ Weather API keyが設定されていません")
return
url = f"https://api.openweathermap.org/data/2.5/weather?q={city}&appid={api_key}&units=metric&lang=ja"
try:
async with aiohttp.ClientSession() as session:
async with session.get(url) as response:
if response.status == 200:
weather_data = await response.json()
message = f"""🌤️ {weather_data['name']}の天気
天気: {weather_data['weather'][0]['description']}
気温: {weather_data['main']['temp']}°C
湿度: {weather_data['main']['humidity']}%
風速: {weather_data['wind']['speed']} m/s"""
formatted_message = f"""
<h4>🌤️ {weather_data['name']}の天気</h4>
<ul>
<li><strong>天気:</strong> {weather_data['weather'][0]['description']}</li>
<li><strong>気温:</strong> {weather_data['main']['temp']}°C</li>
<li><strong>湿度:</strong> {weather_data['main']['humidity']}%</li>
<li><strong>風速:</strong> {weather_data['wind']['speed']} m/s</li>
</ul>
"""
await self.send_message(room_id, message, formatted_message)
else:
await self.send_message(room_id, "❌ 天気情報の取得に失敗しました")
except Exception as e:
print(f"Weather API error: {e}")
await self.send_message(room_id, "❌ 天気情報取得中にエラーが発生しました")
async def enable_encryption(self, room_id: str):
"""ルーム暗号化有効化"""
try:
await self.client.room_put_state(
room_id=room_id,
event_type="m.room.encryption",
content={"algorithm": "m.megolm.v1.aes-sha2"}
)
await self.send_message(room_id, "🔐 ルームの暗号化を有効にしました")
except Exception as e:
print(f"Encryption error: {e}")
await self.send_message(room_id, "❌ 暗号化の有効化に失敗しました")
async def send_room_info(self, room: MatrixRoom):
"""ルーム情報送信"""
member_count = len(room.users)
message = f"""🏠 ルーム情報
名前: {room.display_name or room.room_id}
メンバー数: {member_count}人
暗号化: {'有効' if room.encrypted else '無効'}
ルームID: {room.room_id}"""
formatted_message = f"""
<h4>🏠 ルーム情報</h4>
<table>
<tr><td><strong>名前</strong></td><td>{room.display_name or room.room_id}</td></tr>
<tr><td><strong>メンバー数</strong></td><td>{member_count}人</td></tr>
<tr><td><strong>暗号化</strong></td><td>{'有効' if room.encrypted else '無効'}</td></tr>
<tr><td><strong>ルームID</strong></td><td><code>{room.room_id}</code></td></tr>
</table>
"""
await self.send_message(room.room_id, message, formatted_message)
async def start(self):
"""Bot開始"""
print("🚀 Element Python Bot を開始します...")
try:
# 同期開始
sync_response = await self.client.sync(timeout=30000)
if hasattr(sync_response, 'transport_response'):
print(f"✅ 同期成功: {sync_response.transport_response.status}")
# 継続的な同期
await self.client.sync_forever(timeout=30000)
except Exception as e:
print(f"❌ Bot開始エラー: {e}")
finally:
await self.client.close()
# 使用例
async def main():
homeserver_url = os.getenv('MATRIX_HOMESERVER_URL', 'https://matrix.org')
user_id = os.getenv('MATRIX_USER_ID')
access_token = os.getenv('MATRIX_ACCESS_TOKEN')
if not all([user_id, access_token]):
print("❌ 環境変数 MATRIX_USER_ID, MATRIX_ACCESS_TOKEN を設定してください")
return
bot = ElementPythonBot(homeserver_url, user_id, access_token)
await bot.start()
if __name__ == "__main__":
asyncio.run(main())
環境変数設定
# .env ファイル
# Matrix Bot設定
MATRIX_HOMESERVER_URL=https://matrix.org
MATRIX_USER_ID=@your-bot:matrix.org
MATRIX_ACCESS_TOKEN=your-matrix-access-token
# Element Web設定
ELEMENT_WEB_URL=https://app.element.io
# Synapse Homeserver設定
SYNAPSE_SERVER_NAME=your-domain.com
POSTGRES_PASSWORD=your-secure-postgres-password
# 外部API連携
WEATHER_API_KEY=your-openweather-api-key
GITHUB_TOKEN=your-github-token
# Bridge設定 (Discord Bridge例)
DISCORD_BOT_TOKEN=your-discord-bot-token
DISCORD_CLIENT_ID=your-discord-client-id
# SSL/TLS設定
[email protected]
SSL_CERT_PATH=/etc/letsencrypt/live/your-domain.com/fullchain.pem
SSL_KEY_PATH=/etc/letsencrypt/live/your-domain.com/privkey.pem
# セキュリティ設定
MATRIX_SECRET_KEY=your-very-long-secure-secret-key
ENABLE_REGISTRATION=false
ENABLE_GUEST_ACCESS=false
セキュリティ強化設定
# synapse/homeserver.yaml セキュリティ設定例
# General config
server_name: "your-domain.com"
pid_file: /data/homeserver.pid
web_client_location: https://app.element.io/
# Security settings
enable_registration: false
enable_registration_without_verification: false
registrations_require_3pid:
- email
allowed_local_3pids:
- medium: email
pattern: '.*@your-company\.com'
# Rate limiting
rc_message:
per_second: 5
burst_count: 10
rc_registration:
per_second: 0.1
burst_count: 3
rc_login:
address:
per_second: 0.3
burst_count: 5
account:
per_second: 0.3
burst_count: 5
# Content repository
max_upload_size: 50M
max_image_pixels: 32M
dynamic_thumbnails: false
# Encryption settings
encryption_enabled_by_default_for_room_type: all
trusted_key_servers:
- server_name: "matrix.org"
# Privacy settings
allow_public_rooms_without_auth: false
allow_public_rooms_over_federation: false
# Federation
federation_domain_whitelist:
- matrix.org
- your-trusted-domain.com
# Metrics and monitoring
enable_metrics: true
report_stats: false
Nginx リバースプロキシ設定
# /etc/nginx/sites-available/matrix.your-domain.com
server {
listen 443 ssl http2;
listen [::]:443 ssl http2;
server_name matrix.your-domain.com;
ssl_certificate /etc/letsencrypt/live/your-domain.com/fullchain.pem;
ssl_certificate_key /etc/letsencrypt/live/your-domain.com/privkey.pem;
ssl_trusted_certificate /etc/letsencrypt/live/your-domain.com/chain.pem;
ssl_protocols TLSv1.2 TLSv1.3;
ssl_ciphers ECDHE-RSA-AES256-GCM-SHA512:DHE-RSA-AES256-GCM-SHA512;
ssl_prefer_server_ciphers off;
# Element web client
location / {
proxy_pass http://127.0.0.1:80;
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;
}
# Synapse homeserver
location /_matrix {
proxy_pass http://127.0.0.1:8008;
proxy_set_header X-Forwarded-For $remote_addr;
proxy_set_header X-Forwarded-Proto $scheme;
proxy_set_header Host $host;
# Nginx by default only allows file uploads up to 1M in size
client_max_body_size 50M;
}
# Security headers
add_header X-Frame-Options DENY;
add_header X-Content-Type-Options nosniff;
add_header X-XSS-Protection "1; mode=block";
add_header Strict-Transport-Security "max-age=31536000; includeSubDomains" always;
}
# HTTP redirect
server {
listen 80;
listen [::]:80;
server_name matrix.your-domain.com;
return 301 https://$server_name$request_uri;
}
モニタリング・アラート設定
# monitoring.py - Matrix homeserver monitoring
import asyncio
import aiohttp
import time
from prometheus_client import CollectorRegistry, Gauge, push_to_gateway
class MatrixMonitoring:
def __init__(self, homeserver_url: str, access_token: str):
self.homeserver_url = homeserver_url
self.access_token = access_token
self.registry = CollectorRegistry()
# Prometheus metrics
self.user_count = Gauge('matrix_users_total', 'Total number of users', registry=self.registry)
self.room_count = Gauge('matrix_rooms_total', 'Total number of rooms', registry=self.registry)
self.federation_count = Gauge('matrix_federation_total', 'Total federated servers', registry=self.registry)
async def collect_metrics(self):
"""システムメトリクス収集"""
headers = {'Authorization': f'Bearer {self.access_token}'}
async with aiohttp.ClientSession() as session:
# ユーザー数
try:
async with session.get(f'{self.homeserver_url}/_synapse/admin/v2/users', headers=headers) as resp:
if resp.status == 200:
data = await resp.json()
self.user_count.set(data.get('total', 0))
except Exception as e:
print(f"ユーザー数取得エラー: {e}")
# ルーム数
try:
async with session.get(f'{self.homeserver_url}/_synapse/admin/v1/rooms', headers=headers) as resp:
if resp.status == 200:
data = await resp.json()
self.room_count.set(data.get('total_rooms', 0))
except Exception as e:
print(f"ルーム数取得エラー: {e}")
async def check_federation_health(self):
"""Federation健全性チェック"""
test_servers = ['matrix.org', 'mozilla.org']
healthy_servers = 0
for server in test_servers:
try:
async with aiohttp.ClientSession() as session:
async with session.get(f'{self.homeserver_url}/_matrix/federation/v1/version/{server}', timeout=10) as resp:
if resp.status == 200:
healthy_servers += 1
except Exception as e:
print(f"Federation check failed for {server}: {e}")
self.federation_count.set(healthy_servers)
return healthy_servers / len(test_servers)
async def send_alert(self, message: str, severity: str = 'warning'):
"""アラート送信"""
# Element/Matrix roomにアラート送信
# ここではBot APIを使用
pass
async def run_monitoring(self):
"""監視実行"""
while True:
try:
await self.collect_metrics()
federation_health = await self.check_federation_health()
# Prometheusにメトリクス送信
push_to_gateway('localhost:9091', job='matrix_monitoring', registry=self.registry)
# アラートチェック
if federation_health < 0.5:
await self.send_alert("🚨 Federation connectivity issues detected")
print(f"✅ Monitoring cycle completed at {time.strftime('%Y-%m-%d %H:%M:%S')}")
except Exception as e:
print(f"❌ Monitoring error: {e}")
await asyncio.sleep(300) # 5分間隔
# 使用例
async def main():
monitor = MatrixMonitoring(
homeserver_url=os.getenv('MATRIX_HOMESERVER_URL'),
access_token=os.getenv('MATRIX_ADMIN_TOKEN')
)
await monitor.run_monitoring()
if __name__ == "__main__":
asyncio.run(main())