最終的にやりたいこと
店の予約メールが入ったときにすぐわかるように家でパトランプ的なものを光らせたい。
- メールサーバに予約っぽいメールが来たら特定フォルダに振り分けておいて
- 未読メールがあるかどうか返すAPIをメールサーバに建てておいて
- 家のパトランプてきな端末から定期的にサーバをチェックしにいく
家の端末にサーバ側から投げるのは家のファイヤウォールあけるか端末側からつなげっぱにして切れたら再接続とか面倒なことになりそうなので端末側から定期的に見に行く。セキュリティ的にも件数返すだけAPIならあんま頑張んないでもよさげなので。
今回やる範囲
2のAPIを立てるとこ。nginxは未インストールの状態から。pythonはhttp.serverあたりを使ってnginxからproxyで飛ばす予定。あとは実作業しながら書いていく。
開始前の状況
postfix+dovecotでMaildir形式でメールサーバを構築済み。なお、”Ubuntu 22.04.3 LTS”
また、LetsEncriptでtls通信をさせているため証明書も入手済み。ただ、80番ポートが空いている前提のstandaloneモードで証明書の更新をしているためwebroot方式?だかに直さないといけないはず。
メールサーバに予約っぽいメールが来たときはsieveで規定フォルダに振り分けている。設定ファイルはしたので、題名に”予約確定通知”か”予約が確定”とあったら”INBOX.ジム.予約”フォルダに振り分けるというもの。(サンダーバードだと”「受信トレイ」の下の「ジム」の下の「予約」”)
require ["fileinto"];
if header :contains "Subject" "予約確定通知" {
fileinto "INBOX.ジム.予約";
}
予約確定通知if header :contains "Subject" "予約が確定" {
fileinto "INBOX.ジム.予約";
}
作業開始
nginxインストール&設定
sudo apt-get install nginx
して、とりあえず443ポートをあける。(letsencript使うのに80番も開ける必要があるはずなので空いてなければそちらも)
sudo apt-get install nginx
既に作成済みの証明書を見る形でhttpsを有効にする。ついでにhttpからhttpsへのリダイレクトもかけておく。あと、nginxのバージョンも一応隠す。
sudo vi /etc/nginx/conf.d/ssl.conf
で新規ファイルを作り下を追加
server {
listen 80;
server_tokens off;
server_name mail.hoge.co.jp;
return 301 https://$host$request_uri;
}
server {
listen 443 ssl;
server_tokens off;
ssl_certificate /etc/letsencrypt/live/mail.hoge.co.jp/fullchain.pem;
ssl_certificate_key /etc/letsencrypt/live/mail.hoge.co.jp/privkey.pem;
}
でnginx再起動
sudo systemctl restart nginx
これでとりあえずブラウザからサーバにサクセスするとhttpsに転送されてnginxにもとから入っているwelcome画面が表示されるはず。
pythonを使えるようにする
nginx側のproxy設定
ssl.conf(と深く考えずに名前を付けたので)に直接かくものじゃないのでインクルードして/etc/nginx/conf.d/mail_api.txt に設定を書く。mail_api.confと拡張子をconfにすると/etc/nginx/nginx.confからのinclude設定と重なり多分うまく動かない。
sudo vi /etc/nginx/conf.d/ssl.conf
でserver(443の方)の末尾にインクルード設定を追加
ssl_certificate_key /etc/letsencrypt/live/mail.hoge.co.jp/privkey.pem;
include /etc/nginx/conf.d/mail_api.txt; // これを追加
}
そしてmail_api.txtを作成
sudo vi /etc/nginx/conf.d/mail_api.txt
にて
location / {
proxy_pass http://127.0.0.1:8000;
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
}
これでsudo systemctl restart nginx したら8000番ポートで起動している奴があればそちらにリクエストをすべて飛ばすはず(location / で ルートディレクトリ以下全部)。8000番起動していなくてもとりあえずnginxを先に起動できると思う。
python側のリクエスト受信
http.serverにてnginxのproxyで指定した8000番ポートで待ち受け機能を作る。
(本来ローカルで完結するのでファイヤウォールの8000番は開かなくてよいけどVSCodeでやるとちょいちょいつなげに行こうという処理が走って面倒だった)
取り急ぎmailサーバの任意の場所に以下のmail_api.pyを作ってpython3 mail_api.pyで起動
from http.server import BaseHTTPRequestHandler, HTTPServer
import urllib.parse
class MyHandler(BaseHTTPRequestHandler):
def do_GET(self):
parsed_path = urllib.parse.urlparse(self.path)
query_params = urllib.parse.parse_qs(parsed_path.query)
if parsed_path.path == '/api/reserve_count':
self.send_response(200)
self.send_header('Content-type', 'text/html')
self.end_headers()
response_content = b'Hello, this is the root path.'
else:
self.send_response(404)
self.send_header('Content-type', 'text/html')
self.end_headers()
response_content = b'404 Not Found'
self.wfile.write(response_content)
if __name__ == '__main__':
host = 'localhost'
port = 8000
server = HTTPServer((host, port), MyHandler)
print(f'Starting server on http://{host}:{port}')
try:
server.serve_forever()
except KeyboardInterrupt:
server.shutdown()
print('Server stopped.')
ローカルのPCからhttps://mail.hoge.co.jp/api/reserve_countにアクセスするとstatus200 で Hello, this is the root path.という文字がかえる。
他だとstatus404 で 404 not foundという文字がかえる。
/api/reserve_count が呼ばれたときに実際に件数を返す処理を作る
response_content = b’Hello, this is the root path.’のところを件数返す関数に変更
from http.server import BaseHTTPRequestHandler, HTTPServer
from reserve_count import reserve_count // reserve_count.pyに作ったreserve_count関数を呼び出せるようにする
import urllib.parse
class MyHandler(BaseHTTPRequestHandler):
def do_GET(self):
parsed_path = urllib.parse.urlparse(self.path)
query_params = urllib.parse.parse_qs(parsed_path.query)
if parsed_path.path == '/api/reserve_count':
self.send_response(200)
self.send_header('Content-type', 'text/html')
self.end_headers()
count = reserve_count() // 呼び出して件数取得して
response_content = str(count).encode('utf-8') // 返却 もっとスマートな型変換できそうな気がするけどpythonよくしらん
してvi reserve_count.py し関数作る。例外処理ちゃんと入れていないので各自実装必要。
import imaplib
import imap_tools
from email.header import decode_header
def reserve_count():
# IMAPサーバの設定
mail_server = 'mail.hoge.co.jp'
username = 'user_login_account'
password = 'user_password'
# IMAPサーバに接続
mail = imaplib.IMAP4_SSL(mail_server)
mail.login(username, password)
# # メールボックスを選択
mailbox_name = "INBOX.ジム.予約"
status, messages = mail.select(imap_tools.imap_utf7.encode(mailbox_name))
# # メール検索(例: 未読メール)
status, messages = mail.search(None, "(UNSEEN)")
unread_count = len(messages[0].split())
print(f"未読メールの件数: {unread_count}")
# 接続を閉じる
mail.close()
mail.logout()
return unread_count
あんまり落ちなそうな気はするので実際には例外処理なしで突き進む。あと、ログイン代わりにquery_paramsみてパラメータがあっていたら件数確認処理呼び出すような緩いセキュリティ分岐を入れるつもり。