commit 9ab919064a061962962e119939531a05b0379a51 Author: mukan-bot Date: Mon Oct 13 14:20:07 2025 +0900 Init project diff --git a/.env.example b/.env.example new file mode 100644 index 0000000..0d09b86 --- /dev/null +++ b/.env.example @@ -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 \ No newline at end of file diff --git a/Dockerfile b/Dockerfile new file mode 100644 index 0000000..47ea1d5 --- /dev/null +++ b/Dockerfile @@ -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"] \ No newline at end of file diff --git a/README.md b/README.md new file mode 100644 index 0000000..b61b62e --- /dev/null +++ b/README.md @@ -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にて報告してください。 diff --git a/app.py b/app.py new file mode 100644 index 0000000..33c2f62 --- /dev/null +++ b/app.py @@ -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() \ No newline at end of file diff --git a/docker-compose.yml b/docker-compose.yml new file mode 100644 index 0000000..93284ef --- /dev/null +++ b/docker-compose.yml @@ -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 \ No newline at end of file diff --git a/requirements.txt b/requirements.txt new file mode 100644 index 0000000..1533d00 --- /dev/null +++ b/requirements.txt @@ -0,0 +1,2 @@ +requests==2.31.0 +python-dotenv==1.0.0 \ No newline at end of file diff --git a/start.ps1 b/start.ps1 new file mode 100644 index 0000000..fffc93b --- /dev/null +++ b/start.ps1 @@ -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 + } +} \ No newline at end of file diff --git a/start.sh b/start.sh new file mode 100644 index 0000000..c0e4d1c --- /dev/null +++ b/start.sh @@ -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 "$@" \ No newline at end of file