Init project

This commit is contained in:
2025-10-13 14:20:07 +09:00
commit 9ab919064a
8 changed files with 1436 additions and 0 deletions

18
.env.example Normal file
View File

@@ -0,0 +1,18 @@
# 環境変数設定ファイル(例)
# このファイルをコピーして.envファイルを作成し、実際の値を設定してください
# メールサーバー設定
IMAP_SERVER=imap.gmail.com
IMAP_PORT=993
USE_SSL=true
MAILBOX=INBOX
# メール認証情報
EMAIL_USER=your-email@gmail.com
EMAIL_PASSWORD=your-app-password
# Discord Webhook URL
DISCORD_WEBHOOK_URL=https://discord.com/api/webhooks/YOUR_WEBHOOK_URL
# 監視設定
CHECK_INTERVAL=60

28
Dockerfile Normal file
View File

@@ -0,0 +1,28 @@
FROM python:3.11-slim
# 作業ディレクトリを設定
WORKDIR /app
# システムパッケージを更新
RUN apt-get update && apt-get install -y \
ca-certificates \
&& rm -rf /var/lib/apt/lists/*
# Pythonの依存関係をコピーしてインストール
COPY requirements.txt .
RUN pip install --no-cache-dir -r requirements.txt
# アプリケーションファイルをコピー
COPY app.py .
# 非rootユーザーを作成
RUN useradd --create-home --shell /bin/bash appuser
RUN chown -R appuser:appuser /app
USER appuser
# ヘルスチェックを追加
HEALTHCHECK --interval=30s --timeout=10s --start-period=5s --retries=3 \
CMD python -c "import sys; sys.exit(0)"
# アプリケーションを実行
CMD ["python", "app.py"]

315
README.md Normal file
View File

@@ -0,0 +1,315 @@
# Email to Discord Webhook Forwarder
メールサーバーを監視して、新しいメールが到着したときにDiscordのWebhookを使用して通知を送信するPythonアプリケーションです。
## 🚀 機能
- **メールサーバー監視**: IMAP/IMAPS プロトコルでメールサーバーを監視
- **Discord通知**: 新しいメールが到着したときにDiscordに通知
- **環境変数設定**: 設定は全て環境変数で管理
- **Dockerサポート**: コンテナとして簡単にデプロイ可能
- **SSL/TLS対応**: セキュアな接続をサポート
- **エラーハンドリング**: 堅牢なエラー処理とログ出力
## 📋 必要な環境変数
| 変数名 | 説明 | 必須 | デフォルト値 |
|--------|------|------|-------------|
| `EMAIL_USER` | メールアドレス | ✅ | - |
| `EMAIL_PASSWORD` | メールパスワード/アプリパスワード | ✅ | - |
| `DISCORD_WEBHOOK_URL` | Discord Webhook URL | ✅ | - |
| `IMAP_SERVER` | IMAPサーバーアドレス | ❌ | `imap.gmail.com` |
| `IMAP_PORT` | IMAPポート番号 | ❌ | `993` |
| `USE_SSL` | SSL/TLS使用の有無 | ❌ | `true` |
| `MAILBOX` | 監視するメールボックス | ❌ | `INBOX` |
| `CHECK_INTERVAL` | チェック間隔(秒) | ❌ | `60` |
## 🔧 セットアップ
### 1. Discord Webhook URLの取得
1. Discordでメッセージを送信したいチャンネルを選択
2. チャンネル設定 → 連携サービス → ウェブフック
3. 新しいウェブフックを作成してURLをコピー
### 2. Gmail用アプリパスワードの作成Gmailを使用する場合
1. Googleアカウントの2段階認証を有効にする
2. Googleアカウント設定 → セキュリティ → アプリパスワード
3. メール用のアプリパスワードを生成
### 3. 環境変数の設定
```bash
# .env.exampleをコピーして.envファイルを作成
cp .env.example .env
# .envファイルを編集して実際の値を設定
```
## 🐳 Docker Composeでの実行
### 1. docker-compose.ymlの設定
```yaml
environment:
- EMAIL_USER=your-email@gmail.com
- EMAIL_PASSWORD=your-app-password
- DISCORD_WEBHOOK_URL=https://discord.com/api/webhooks/YOUR_WEBHOOK_URL
```
### 2. コンテナの起動
```bash
# コンテナをビルドして起動
docker-compose up -d
# ログの確認
docker-compose logs -f
# コンテナの停止
docker-compose down
```
## 🚀 Linux用シェルスクリプトでの実行
Linux環境では便利なシェルスクリプトを使用できます
### 1. スクリプトに実行権限を付与
```bash
chmod +x start.sh
```
### 2. 初期セットアップ
```bash
# .envファイルを作成
./start.sh setup
# .envファイルを編集して実際の値を設定
nano .env
```
### 3. よく使用するコマンド
```bash
# Dockerイメージをビルド
./start.sh build
# コンテナを起動
./start.sh start
# リアルタイムでログを表示
./start.sh logs-f
# コンテナの状態を確認
./start.sh status
# コンテナを停止
./start.sh stop
# コンテナを再起動
./start.sh restart
# docker-compose で起動
./start.sh compose-up
# Python直接実行
./start.sh python
# ヘルプを表示
./start.sh help
```
### 4. 利用可能なコマンド一覧
| コマンド | 説明 |
|----------|------|
| `setup` | 初期セットアップ(.envファイル作成 |
| `build` | Dockerイメージをビルド |
| `start` | コンテナを起動 |
| `stop` | コンテナを停止 |
| `restart` | コンテナを再起動 |
| `logs` | ログを表示 |
| `logs-f` | ログをリアルタイム表示 |
| `status` | コンテナの状態を確認 |
| `clean` | 停止済みコンテナとイメージを削除 |
| `compose-up` | docker-compose で起動 |
| `compose-down` | docker-compose で停止 |
| `python` | Python直接実行 |
| `help` | ヘルプを表示 |
## 🪟 Windows用PowerShellスクリプトでの実行
Windows環境では PowerShell スクリプトを使用できます:
### 1. 初期セットアップ
```powershell
# .envファイルを作成
.\start.ps1 setup
# .envファイルを編集して実際の値を設定
notepad .env
```
### 2. よく使用するコマンド
```powershell
# Dockerイメージをビルド
.\start.ps1 build
# コンテナを起動
.\start.ps1 start
# リアルタイムでログを表示
.\start.ps1 logs-f
# コンテナの状態を確認
.\start.ps1 status
# コンテナを停止
.\start.ps1 stop
# ヘルプを表示
.\start.ps1 help
```
## 🔨 Dockerでの直接実行
### 1. イメージのビルド
```bash
docker build -t email-to-discord .
```
### 2. コンテナの実行
```bash
docker run -d \
--name email-monitor \
--restart unless-stopped \
-e EMAIL_USER=your-email@gmail.com \
-e EMAIL_PASSWORD=your-app-password \
-e DISCORD_WEBHOOK_URL=https://discord.com/api/webhooks/YOUR_WEBHOOK_URL \
-e CHECK_INTERVAL=60 \
email-to-discord
```
## 🐍 Python直接実行
### 1. 依存関係のインストール
```bash
pip install -r requirements.txt
```
### 2. 環境変数の設定
```bash
# Windowsの場合
set EMAIL_USER=your-email@gmail.com
set EMAIL_PASSWORD=your-app-password
set DISCORD_WEBHOOK_URL=https://discord.com/api/webhooks/YOUR_WEBHOOK_URL
# Linux/macOSの場合
export EMAIL_USER=your-email@gmail.com
export EMAIL_PASSWORD=your-app-password
export DISCORD_WEBHOOK_URL=https://discord.com/api/webhooks/YOUR_WEBHOOK_URL
```
### 3. アプリケーションの実行
```bash
python app.py
```
## 📧 対応メールプロバイダー
### Gmail
```env
IMAP_SERVER=imap.gmail.com
IMAP_PORT=993
USE_SSL=true
```
### Outlook/Hotmail
```env
IMAP_SERVER=outlook.office365.com
IMAP_PORT=993
USE_SSL=true
```
### Yahoo Mail
```env
IMAP_SERVER=imap.mail.yahoo.com
IMAP_PORT=993
USE_SSL=true
```
### その他のプロバイダー
各プロバイダーのIMAP設定を確認して適切な値を設定してください。
## 📊 ログとモニタリング
アプリケーションは以下の情報をログ出力します:
- 起動/停止メッセージ
- メールサーバー接続状況
- 新しいメールの検出
- Discord送信の成功/失敗
- エラー情報
```bash
# Dockerコンテナのログを確認
docker logs email-to-discord-monitor
# リアルタイムでログを監視
docker logs -f email-to-discord-monitor
```
## 🔒 セキュリティ考慮事項
1. **アプリパスワードの使用**: 通常のパスワードではなくアプリ専用パスワードを使用
2. **環境変数での機密情報管理**: パスワードやWebhook URLは環境変数で管理
3. **SSL/TLS接続**: メールサーバーとの通信は暗号化
4. **非rootユーザー**: Dockerコンテナは非rootユーザーで実行
## 🛠️ トラブルシューティング
### メールサーバーに接続できない場合
1. IMAP設定が正しいか確認
2. アプリパスワードが正しく設定されているか確認
3. 2段階認証が有効になっているか確認Gmail
4. ファイアウォールの設定を確認
### Discord通知が送信されない場合
1. Webhook URLが正しいか確認
2. Discordサーバーの権限を確認
3. ネットワーク接続を確認
### ログの確認方法
```bash
# アプリケーションのログレベルを変更(開発時)
# app.py内のlogging.basicConfig levelをDEBUGに変更
```
## 📝 ライセンス
このプロジェクトはMITライセンスの下で公開されています。
## 🤝 コントリビューション
プルリクエストやイシューの報告を歓迎します。
## 📞 サポート
問題が発生した場合は、GitHubのIssueにて報告してください。

348
app.py Normal file
View File

@@ -0,0 +1,348 @@
#!/usr/bin/env python3
"""
Email to Discord Webhook Forwarder
メールサーバーを監視してDiscordに転送するアプリケーション
"""
import os
import sys
import time
import imaplib
import email
import json
import logging
import requests
from datetime import datetime
from typing import Dict, List, Optional
import signal
import threading
from dataclasses import dataclass
# ログ設定
logging.basicConfig(
level=logging.INFO,
format='%(asctime)s - %(levelname)s - %(message)s',
handlers=[
logging.StreamHandler(sys.stdout)
]
)
logger = logging.getLogger(__name__)
@dataclass
class EmailMessage:
"""メールメッセージのデータクラス"""
subject: str
sender: str
date: str
body: str
uid: str
class EmailMonitor:
"""メールサーバー監視クラス"""
def __init__(self):
self.running = False
self.mail_client = None
self.last_processed_uid = None
# 環境変数から設定を取得
self.imap_server = os.getenv('IMAP_SERVER', 'imap.gmail.com')
self.imap_port = int(os.getenv('IMAP_PORT', '993'))
self.email_user = os.getenv('EMAIL_USER')
self.email_password = os.getenv('EMAIL_PASSWORD')
self.discord_webhook_url = os.getenv('DISCORD_WEBHOOK_URL')
self.check_interval = int(os.getenv('CHECK_INTERVAL', '60')) # 秒
self.mailbox = os.getenv('MAILBOX', 'INBOX')
self.use_ssl = os.getenv('USE_SSL', 'true').lower() == 'true'
# 設定の検証
self._validate_config()
def _validate_config(self):
"""設定の検証"""
required_vars = ['EMAIL_USER', 'EMAIL_PASSWORD', 'DISCORD_WEBHOOK_URL']
missing_vars = [var for var in required_vars if not os.getenv(var)]
if missing_vars:
logger.error(f"必要な環境変数が設定されていません: {', '.join(missing_vars)}")
sys.exit(1)
logger.info("設定の検証が完了しました")
def connect_to_email(self) -> bool:
"""メールサーバーに接続"""
try:
if self.use_ssl:
self.mail_client = imaplib.IMAP4_SSL(self.imap_server, self.imap_port)
else:
self.mail_client = imaplib.IMAP4(self.imap_server, self.imap_port)
self.mail_client.login(self.email_user, self.email_password)
self.mail_client.select(self.mailbox)
logger.info(f"メールサーバーに接続しました: {self.imap_server}:{self.imap_port}")
return True
except Exception as e:
logger.error(f"メールサーバーへの接続に失敗しました: {str(e)}")
return False
def disconnect_from_email(self):
"""メールサーバーから切断"""
if self.mail_client:
try:
self.mail_client.close()
self.mail_client.logout()
logger.info("メールサーバーから切断しました")
except Exception as e:
logger.warning(f"メールサーバーからの切断でエラーが発生しました: {str(e)}")
def get_new_emails(self) -> List[EmailMessage]:
"""新しいメールを取得"""
try:
# UNSEENフラグのメールを検索
typ, data = self.mail_client.search(None, 'UNSEEN')
if typ != 'OK':
logger.warning("メール検索に失敗しました")
return []
email_ids = data[0].split()
new_emails = []
for email_id in email_ids:
try:
# メールを取得
typ, msg_data = self.mail_client.fetch(email_id, '(RFC822)')
if typ != 'OK':
continue
email_body = msg_data[0][1]
email_message = email.message_from_bytes(email_body)
# メールデータを解析
parsed_email = self._parse_email(email_message, email_id.decode())
if parsed_email:
new_emails.append(parsed_email)
except Exception as e:
logger.error(f"メール解析エラー (ID: {email_id}): {str(e)}")
continue
return new_emails
except Exception as e:
logger.error(f"新しいメールの取得に失敗しました: {str(e)}")
return []
def _parse_email(self, email_message, uid: str) -> Optional[EmailMessage]:
"""メールメッセージを解析"""
try:
# ヘッダー情報を取得
subject = self._decode_header(email_message.get('Subject', ''))
sender = self._decode_header(email_message.get('From', ''))
date = email_message.get('Date', '')
# メール本文を取得
body = self._get_email_body(email_message)
return EmailMessage(
subject=subject,
sender=sender,
date=date,
body=body,
uid=uid
)
except Exception as e:
logger.error(f"メール解析エラー: {str(e)}")
return None
def _decode_header(self, header: str) -> str:
"""メールヘッダーをデコード"""
if not header:
return ''
try:
decoded_header = email.header.decode_header(header)
result = ''
for text, encoding in decoded_header:
if isinstance(text, bytes):
if encoding:
text = text.decode(encoding, errors='ignore')
else:
text = text.decode('utf-8', errors='ignore')
result += text
return result
except Exception as e:
logger.warning(f"ヘッダーデコードエラー: {str(e)}")
return str(header)
def _get_email_body(self, email_message) -> str:
"""メール本文を取得"""
body = ''
try:
if email_message.is_multipart():
for part in email_message.walk():
content_type = part.get_content_type()
content_disposition = str(part.get('Content-Disposition'))
if content_type == 'text/plain' and 'attachment' not in content_disposition:
charset = part.get_content_charset() or 'utf-8'
body_bytes = part.get_payload(decode=True)
if body_bytes:
body = body_bytes.decode(charset, errors='ignore')
break
else:
charset = email_message.get_content_charset() or 'utf-8'
body_bytes = email_message.get_payload(decode=True)
if body_bytes:
body = body_bytes.decode(charset, errors='ignore')
# 本文が長い場合は切り詰めるDiscord制限対策
if len(body) > 1900:
body = body[:1900] + '\n...(本文が切り詰められました)'
return body
except Exception as e:
logger.warning(f"メール本文取得エラー: {str(e)}")
return '本文の取得に失敗しました'
def send_to_discord(self, email_msg: EmailMessage) -> bool:
"""DiscordにWebhookでメールを送信"""
try:
# Discord Embed形式でメッセージを構築
embed = {
"title": "📧 新しいメール",
"color": 0x3498db,
"fields": [
{
"name": "件名",
"value": email_msg.subject or "件名なし",
"inline": False
},
{
"name": "送信者",
"value": email_msg.sender or "送信者不明",
"inline": True
},
{
"name": "日時",
"value": email_msg.date or "日時不明",
"inline": True
},
{
"name": "本文",
"value": email_msg.body[:1000] if email_msg.body else "本文なし",
"inline": False
}
],
"timestamp": datetime.utcnow().isoformat()
}
webhook_data = {
"username": "Email Monitor",
"avatar_url": "https://cdn-icons-png.flaticon.com/512/732/732200.png",
"embeds": [embed]
}
response = requests.post(
self.discord_webhook_url,
json=webhook_data,
timeout=10
)
if response.status_code == 204:
logger.info(f"Discordにメールを送信しました: {email_msg.subject}")
return True
else:
logger.error(f"Discord送信エラー: {response.status_code} - {response.text}")
return False
except Exception as e:
logger.error(f"Discord送信エラー: {str(e)}")
return False
def start_monitoring(self):
"""メール監視を開始"""
logger.info("メール監視を開始します...")
self.running = True
while self.running:
try:
# メールサーバーに接続
if not self.connect_to_email():
logger.warning(f"{self.check_interval}秒後に再試行します...")
time.sleep(self.check_interval)
continue
# 新しいメールをチェック
new_emails = self.get_new_emails()
if new_emails:
logger.info(f"{len(new_emails)}件の新しいメールが見つかりました")
for email_msg in new_emails:
if self.send_to_discord(email_msg):
logger.info(f"処理完了: {email_msg.subject}")
else:
logger.warning(f"Discord送信失敗: {email_msg.subject}")
else:
logger.debug("新しいメールはありません")
# メールサーバーから切断
self.disconnect_from_email()
# 次のチェックまで待機
logger.debug(f"{self.check_interval}秒後に次のチェックを実行します")
time.sleep(self.check_interval)
except KeyboardInterrupt:
logger.info("キーボード割り込みを受信しました")
break
except Exception as e:
logger.error(f"監視ループでエラーが発生しました: {str(e)}")
self.disconnect_from_email()
time.sleep(self.check_interval)
logger.info("メール監視を停止しました")
def stop_monitoring(self):
"""メール監視を停止"""
self.running = False
self.disconnect_from_email()
def signal_handler(signum, frame, monitor):
"""シグナルハンドラー"""
logger.info(f"シグナル {signum} を受信しました。アプリケーションを終了します...")
monitor.stop_monitoring()
sys.exit(0)
def main():
"""メイン関数"""
logger.info("Email to Discord Webhook Forwarder を開始します")
logger.info(f"チェック間隔: {os.getenv('CHECK_INTERVAL', '60')}")
logger.info(f"メールボックス: {os.getenv('MAILBOX', 'INBOX')}")
logger.info(f"IMAPサーバー: {os.getenv('IMAP_SERVER', 'imap.gmail.com')}")
# メール監視インスタンスを作成
monitor = EmailMonitor()
# シグナルハンドラーを設定
signal.signal(signal.SIGINT, lambda s, f: signal_handler(s, f, monitor))
signal.signal(signal.SIGTERM, lambda s, f: signal_handler(s, f, monitor))
try:
# 監視開始
monitor.start_monitoring()
except Exception as e:
logger.error(f"アプリケーションエラー: {str(e)}")
sys.exit(1)
if __name__ == "__main__":
main()

38
docker-compose.yml Normal file
View File

@@ -0,0 +1,38 @@
version: '3.8'
services:
email-to-discord:
build: .
container_name: email-to-discord-monitor
restart: unless-stopped
environment:
# メールサーバー設定
- IMAP_SERVER=imap.gmail.com
- IMAP_PORT=993
- USE_SSL=true
- MAILBOX=INBOX
# メール認証情報(実際の値に変更してください)
- EMAIL_USER=your-email@gmail.com
- EMAIL_PASSWORD=your-app-password
# Discord Webhook URL実際のURLに変更してください
- DISCORD_WEBHOOK_URL=https://discord.com/api/webhooks/YOUR_WEBHOOK_URL
# 監視設定
- CHECK_INTERVAL=60 # チェック間隔(秒)
# ログの設定
logging:
driver: "json-file"
options:
max-size: "10m"
max-file: "3"
# リソース制限
deploy:
resources:
limits:
memory: 256M
reservations:
memory: 128M

2
requirements.txt Normal file
View File

@@ -0,0 +1,2 @@
requests==2.31.0
python-dotenv==1.0.0

343
start.ps1 Normal file
View File

@@ -0,0 +1,343 @@
# Email to Discord Monitor - Windows起動スクリプト
param(
[Parameter(Mandatory=$false)]
[string]$Action = ""
)
# 色付きメッセージ用の関数
function Write-ColoredText {
param(
[string]$Text,
[string]$Color = "White"
)
Write-Host $Text -ForegroundColor $Color
}
function Log-Info {
param([string]$Message)
Write-ColoredText "[INFO] $Message" "Green"
}
function Log-Warn {
param([string]$Message)
Write-ColoredText "[WARN] $Message" "Yellow"
}
function Log-Error {
param([string]$Message)
Write-ColoredText "[ERROR] $Message" "Red"
}
function Log-Blue {
param([string]$Message)
Write-ColoredText "[INFO] $Message" "Blue"
}
# ヘルプメッセージ
function Show-Help {
Write-Host "Email to Discord Monitor - Windows起動スクリプト"
Write-Host ""
Write-Host "使用方法:"
Write-Host " .\start.ps1 [オプション]"
Write-Host ""
Write-Host "オプション:"
Write-Host " build Dockerイメージをビルド"
Write-Host " start コンテナを起動"
Write-Host " stop コンテナを停止"
Write-Host " restart コンテナを再起動"
Write-Host " logs ログを表示"
Write-Host " logs-f ログをリアルタイムで表示"
Write-Host " status コンテナの状態を確認"
Write-Host " clean 停止済みコンテナとイメージを削除"
Write-Host " setup 初期セットアップ(.envファイル作成"
Write-Host " compose-up docker-compose で起動"
Write-Host " compose-down docker-compose で停止"
Write-Host " python Python直接実行"
Write-Host " help このヘルプを表示"
Write-Host ""
Write-Host "例:"
Write-Host " .\start.ps1 setup # 初期セットアップ"
Write-Host " .\start.ps1 build # イメージをビルド"
Write-Host " .\start.ps1 start # コンテナを起動"
Write-Host " .\start.ps1 logs-f # ログをリアルタイム表示"
}
# 必要なツールの確認
function Test-Requirements {
$missingTools = @()
if (!(Get-Command docker -ErrorAction SilentlyContinue)) {
$missingTools += "docker"
}
if (!(Get-Command docker-compose -ErrorAction SilentlyContinue)) {
$missingTools += "docker-compose"
}
if ($missingTools.Count -gt 0) {
Log-Error "以下のツールがインストールされていません: $($missingTools -join ', ')"
Log-Info "Dockerのインストール: https://docs.docker.com/desktop/windows/"
exit 1
}
}
# .envファイルのセットアップ
function Setup-Environment {
Log-Info "初期セットアップを開始します..."
if (Test-Path ".env") {
Log-Warn ".envファイルが既に存在します"
$response = Read-Host "上書きしますか? (y/N)"
if ($response -ne "y" -and $response -ne "Y") {
Log-Info "セットアップをキャンセルしました"
return
}
}
if (!(Test-Path ".env.example")) {
Log-Error ".env.exampleファイルが見つかりません"
exit 1
}
Copy-Item ".env.example" ".env"
Log-Info ".envファイルを作成しました"
Log-Blue "以下の設定を.envファイルに入力してください:"
Write-Host ""
Write-Host "1. EMAIL_USER=your-email@gmail.com"
Write-Host "2. EMAIL_PASSWORD=your-app-password"
Write-Host "3. DISCORD_WEBHOOK_URL=https://discord.com/api/webhooks/YOUR_WEBHOOK_URL"
Write-Host ""
Log-Warn "設定完了後、'.\start.ps1 build' でイメージをビルドしてください"
}
# Dockerイメージをビルド
function Build-Image {
Log-Info "Dockerイメージをビルドしています..."
docker build -t email-to-discord .
if ($LASTEXITCODE -eq 0) {
Log-Info "ビルドが完了しました"
} else {
Log-Error "ビルドに失敗しました"
exit 1
}
}
# コンテナを起動
function Start-Container {
Log-Info "コンテナを起動しています..."
# 既存のコンテナを確認・削除
$existingContainer = docker ps -a --format "table {{.Names}}" | Select-String "^email-monitor$"
if ($existingContainer) {
Log-Warn "既存のコンテナを削除します..."
docker rm -f email-monitor 2>$null
}
# .envファイルの確認
if (!(Test-Path ".env")) {
Log-Error ".envファイルが見つかりません"
Log-Info "'.\start.ps1 setup' を実行して設定を行ってください"
exit 1
}
# コンテナを起動
docker run -d `
--name email-monitor `
--restart unless-stopped `
--env-file .env `
email-to-discord
if ($LASTEXITCODE -eq 0) {
Log-Info "コンテナが起動しました"
Log-Blue "ログを確認するには: .\start.ps1 logs-f"
} else {
Log-Error "コンテナの起動に失敗しました"
exit 1
}
}
# コンテナを停止
function Stop-Container {
Log-Info "コンテナを停止しています..."
docker stop email-monitor 2>$null
docker rm email-monitor 2>$null
Log-Info "コンテナを停止しました"
}
# コンテナを再起動
function Restart-Container {
Log-Info "コンテナを再起動しています..."
Stop-Container
Start-Sleep -Seconds 2
Start-Container
}
# ログを表示
function Show-Logs {
param([bool]$Follow = $false)
if ($Follow) {
Log-Info "ログをリアルタイムで表示します (Ctrl+C で終了)"
docker logs -f email-monitor
} else {
Log-Info "ログを表示します"
docker logs email-monitor
}
}
# コンテナの状態を確認
function Get-Status {
Log-Info "コンテナの状態を確認しています..."
$runningContainer = docker ps --format "table {{.Names}}" | Select-String "email-monitor"
if ($runningContainer) {
Log-Info "コンテナは実行中です"
docker ps --format "table {{.Names}}`t{{.Status}}`t{{.CreatedAt}}" | Select-String "email-monitor"
} else {
Log-Warn "コンテナは実行されていません"
$stoppedContainer = docker ps -a --format "table {{.Names}}" | Select-String "email-monitor"
if ($stoppedContainer) {
Log-Info "停止済みコンテナが存在します"
docker ps -a --format "table {{.Names}}`t{{.Status}}`t{{.CreatedAt}}" | Select-String "email-monitor"
}
}
}
# クリーンアップ
function Invoke-Cleanup {
Log-Info "クリーンアップを実行しています..."
# コンテナを停止・削除
docker stop email-monitor 2>$null
docker rm email-monitor 2>$null
# イメージを削除
docker rmi email-to-discord 2>$null
# 未使用のリソースを削除
docker system prune -f
Log-Info "クリーンアップが完了しました"
}
# docker-compose で起動
function Start-Compose {
Log-Info "docker-compose でサービスを起動しています..."
docker-compose up -d
if ($LASTEXITCODE -eq 0) {
Log-Info "サービスが起動しました"
Log-Blue "ログを確認するには: .\start.ps1 compose-logs"
} else {
Log-Error "サービスの起動に失敗しました"
exit 1
}
}
# docker-compose で停止
function Stop-Compose {
Log-Info "docker-compose でサービスを停止しています..."
docker-compose down
Log-Info "サービスを停止しました"
}
# Python直接実行
function Start-Python {
Log-Info "Python環境で直接実行します..."
# Python3の確認
if (!(Get-Command python -ErrorAction SilentlyContinue)) {
Log-Error "Pythonがインストールされていません"
exit 1
}
# pipの確認
if (!(Get-Command pip -ErrorAction SilentlyContinue)) {
Log-Error "pipがインストールされていません"
exit 1
}
# 依存関係のインストール
Log-Info "依存関係をインストールしています..."
pip install -r requirements.txt
# .envファイルの確認
if (!(Test-Path ".env")) {
Log-Error ".envファイルが見つかりません"
Log-Info "'.\start.ps1 setup' を実行して設定を行ってください"
exit 1
}
# 環境変数を読み込んで実行
Log-Info "アプリケーションを起動しています..."
# .envファイルから環境変数を読み込み
Get-Content ".env" | ForEach-Object {
if ($_ -match "^([^=]+)=(.*)$") {
[Environment]::SetEnvironmentVariable($matches[1], $matches[2], "Process")
}
}
python app.py
}
# メイン処理
switch ($Action.ToLower()) {
"build" {
Test-Requirements
Build-Image
}
"start" {
Test-Requirements
Start-Container
}
"stop" {
Stop-Container
}
"restart" {
Test-Requirements
Restart-Container
}
"logs" {
Show-Logs
}
"logs-f" {
Show-Logs -Follow $true
}
"status" {
Get-Status
}
"clean" {
Invoke-Cleanup
}
"setup" {
Setup-Environment
}
"compose-up" {
Test-Requirements
Start-Compose
}
"compose-down" {
Stop-Compose
}
"compose-logs" {
docker-compose logs -f
}
"python" {
Start-Python
}
"help" {
Show-Help
}
"" {
Log-Error "引数が指定されていません"
Write-Host ""
Show-Help
}
default {
Log-Error "不明なオプション: $Action"
Write-Host ""
Show-Help
}
}

344
start.sh Normal file
View File

@@ -0,0 +1,344 @@
#!/bin/bash
# Email to Discord Monitor - Linux起動スクリプト
set -e # エラー時に終了
# 色付きメッセージ用の定数
RED='\033[0;31m'
GREEN='\033[0;32m'
YELLOW='\033[1;33m'
BLUE='\033[0;34m'
NC='\033[0m' # No Color
# ログ関数
log_info() {
echo -e "${GREEN}[INFO]${NC} $1"
}
log_warn() {
echo -e "${YELLOW}[WARN]${NC} $1"
}
log_error() {
echo -e "${RED}[ERROR]${NC} $1"
}
log_blue() {
echo -e "${BLUE}[INFO]${NC} $1"
}
# ヘルプメッセージ
show_help() {
echo "Email to Discord Monitor - 起動スクリプト"
echo ""
echo "使用方法:"
echo " $0 [オプション]"
echo ""
echo "オプション:"
echo " build Dockerイメージをビルド"
echo " start コンテナを起動"
echo " stop コンテナを停止"
echo " restart コンテナを再起動"
echo " logs ログを表示"
echo " logs-f ログをリアルタイムで表示"
echo " status コンテナの状態を確認"
echo " clean 停止済みコンテナとイメージを削除"
echo " setup 初期セットアップ(.envファイル作成"
echo " compose-up docker-compose で起動"
echo " compose-down docker-compose で停止"
echo " python Python直接実行"
echo " help このヘルプを表示"
echo ""
echo "例:"
echo " $0 setup # 初期セットアップ"
echo " $0 build # イメージをビルド"
echo " $0 start # コンテナを起動"
echo " $0 logs-f # ログをリアルタイム表示"
}
# 必要なツールの確認
check_requirements() {
local missing_tools=()
if ! command -v docker &> /dev/null; then
missing_tools+=("docker")
fi
if ! command -v docker-compose &> /dev/null && ! docker compose version &> /dev/null; then
missing_tools+=("docker-compose")
fi
if [ ${#missing_tools[@]} -ne 0 ]; then
log_error "以下のツールがインストールされていません: ${missing_tools[*]}"
log_info "Dockerのインストール: https://docs.docker.com/get-docker/"
exit 1
fi
}
# .envファイルのセットアップ
setup_env() {
log_info "初期セットアップを開始します..."
if [ -f ".env" ]; then
log_warn ".envファイルが既に存在します"
read -p "上書きしますか? (y/N): " -n 1 -r
echo
if [[ ! $REPLY =~ ^[Yy]$ ]]; then
log_info "セットアップをキャンセルしました"
return 0
fi
fi
if [ ! -f ".env.example" ]; then
log_error ".env.exampleファイルが見つかりません"
exit 1
fi
cp .env.example .env
log_info ".envファイルを作成しました"
log_blue "以下の設定を.envファイルに入力してください:"
echo ""
echo "1. EMAIL_USER=your-email@gmail.com"
echo "2. EMAIL_PASSWORD=your-app-password"
echo "3. DISCORD_WEBHOOK_URL=https://discord.com/api/webhooks/YOUR_WEBHOOK_URL"
echo ""
log_warn "設定完了後、'$0 build' でイメージをビルドしてください"
}
# Dockerイメージをビルド
build_image() {
log_info "Dockerイメージをビルドしています..."
docker build -t email-to-discord .
log_info "ビルドが完了しました"
}
# コンテナを起動
start_container() {
log_info "コンテナを起動しています..."
# 既存のコンテナを確認
if docker ps -a --format "table {{.Names}}" | grep -q "^email-monitor$"; then
log_warn "既存のコンテナを削除します..."
docker rm -f email-monitor 2>/dev/null || true
fi
# .envファイルの確認
if [ ! -f ".env" ]; then
log_error ".envファイルが見つかりません"
log_info "'$0 setup' を実行して設定を行ってください"
exit 1
fi
# 環境変数を読み込み
source .env
# 必須変数の確認
if [ -z "$EMAIL_USER" ] || [ -z "$EMAIL_PASSWORD" ] || [ -z "$DISCORD_WEBHOOK_URL" ]; then
log_error "必須の環境変数が設定されていません"
log_info ".envファイルを確認してください"
exit 1
fi
# コンテナを起動
docker run -d \
--name email-monitor \
--restart unless-stopped \
--env-file .env \
email-to-discord
log_info "コンテナが起動しました"
log_blue "ログを確認するには: $0 logs-f"
}
# コンテナを停止
stop_container() {
log_info "コンテナを停止しています..."
docker stop email-monitor 2>/dev/null || log_warn "コンテナが実行されていません"
docker rm email-monitor 2>/dev/null || log_warn "削除するコンテナがありません"
log_info "コンテナを停止しました"
}
# コンテナを再起動
restart_container() {
log_info "コンテナを再起動しています..."
stop_container
sleep 2
start_container
}
# ログを表示
show_logs() {
if [ "$1" = "-f" ]; then
log_info "ログをリアルタイムで表示します (Ctrl+C で終了)"
docker logs -f email-monitor
else
log_info "ログを表示します"
docker logs email-monitor
fi
}
# コンテナの状態を確認
check_status() {
log_info "コンテナの状態を確認しています..."
if docker ps --format "table {{.Names}}\t{{.Status}}\t{{.Ports}}" | grep -q "email-monitor"; then
log_info "コンテナは実行中です"
docker ps --format "table {{.Names}}\t{{.Status}}\t{{.CreatedAt}}" | grep email-monitor
else
log_warn "コンテナは実行されていません"
if docker ps -a --format "table {{.Names}}" | grep -q "email-monitor"; then
log_info "停止済みコンテナが存在します"
docker ps -a --format "table {{.Names}}\t{{.Status}}\t{{.CreatedAt}}" | grep email-monitor
fi
fi
}
# クリーンアップ
cleanup() {
log_info "クリーンアップを実行しています..."
# コンテナを停止・削除
docker stop email-monitor 2>/dev/null || true
docker rm email-monitor 2>/dev/null || true
# イメージを削除
docker rmi email-to-discord 2>/dev/null || log_warn "削除するイメージがありません"
# 未使用のリソースを削除
docker system prune -f
log_info "クリーンアップが完了しました"
}
# docker-compose で起動
compose_up() {
log_info "docker-compose でサービスを起動しています..."
if command -v docker-compose &> /dev/null; then
docker-compose up -d
else
docker compose up -d
fi
log_info "サービスが起動しました"
log_blue "ログを確認するには: $0 compose-logs"
}
# docker-compose で停止
compose_down() {
log_info "docker-compose でサービスを停止しています..."
if command -v docker-compose &> /dev/null; then
docker-compose down
else
docker compose down
fi
log_info "サービスを停止しました"
}
# Python直接実行
run_python() {
log_info "Python環境で直接実行します..."
# Python3の確認
if ! command -v python3 &> /dev/null; then
log_error "Python3がインストールされていません"
exit 1
fi
# pipの確認
if ! command -v pip3 &> /dev/null; then
log_error "pip3がインストールされていません"
exit 1
fi
# 依存関係のインストール
log_info "依存関係をインストールしています..."
pip3 install -r requirements.txt
# .envファイルの確認
if [ ! -f ".env" ]; then
log_error ".envファイルが見つかりません"
log_info "'$0 setup' を実行して設定を行ってください"
exit 1
fi
# 環境変数を読み込んで実行
log_info "アプリケーションを起動しています..."
export $(cat .env | xargs)
python3 app.py
}
# メイン処理
main() {
case "$1" in
"build")
check_requirements
build_image
;;
"start")
check_requirements
start_container
;;
"stop")
stop_container
;;
"restart")
check_requirements
restart_container
;;
"logs")
show_logs
;;
"logs-f")
show_logs -f
;;
"status")
check_status
;;
"clean")
cleanup
;;
"setup")
setup_env
;;
"compose-up")
check_requirements
compose_up
;;
"compose-down")
compose_down
;;
"compose-logs")
if command -v docker-compose &> /dev/null; then
docker-compose logs -f
else
docker compose logs -f
fi
;;
"python")
run_python
;;
"help"|"--help"|"-h")
show_help
;;
"")
log_error "引数が指定されていません"
echo ""
show_help
exit 1
;;
*)
log_error "不明なオプション: $1"
echo ""
show_help
exit 1
;;
esac
}
# スクリプト実行
main "$@"