docker環境への移行

約一ヶ月ぶりの記事になりますが、夏休みということで。

この夏休みは、雨続きやら新型コロナの蔓延防止やら緊急事態やらの巣ごもりで、夏バテと闘いながら、従来から引続きpython(django)、javascript(Three.js)勉強行いながら、前回の記事でチラッと書いたdockerの勉強・実装実験を行っておりました。もともろvmwareやら仮想環境に興味があったので、比較的敷居が低かった感じで、いろいろ試しているウチに勢い余って、当サイトのWEB環境もApache2の仮想ホスト環境から、dockerコンテナのブリッジネットワーク構成に移行させてしまったというお話。さらにはメールサーバーもdockerコンテナ化・・・8月中に一通り移行を終えたところで一息ついている感じです。

とっかかりは楽しくて次々とdocker化が進んでいったが、やっぱり全く同じ環境を再現しようとすると色々と問題が出てきたり、ネット調査だけでは答えの出ない問題に突き当たったりとネタだけは貯えたので、覚え書きレベルですが追々と記事にして置こうかと思います。

つい今の今も、この記事を書こうとwordpress(これもdockerコンテナ化済み)の記事編集をしていたところ、メディアファイルがアップロード出来ない問題が発生(移行時に試していないのがいけない・・・)。半日がかりの原因究明でしたが、結局はアップロードパスの修正忘れという単純なポカミス。。まぁdockerという新しい環境への移行なので、まずそちら方面を疑ってしまい、思わぬ深みにはまってしまうというよくある罠でした。

今回の移行ですが、従来構成を下記に示す。普通のubuntuのままなのでapache単独サーバーでvhost.confの設定で仮想ホストを構成していた。

従来構成

同様の環境をdockerリバースプロキシ環境に移行した構成を下記に示します。

移行後のdocker構成

dockerブリッジネットワークでほぼ全てのサービスをコンテナ化してリバースプロキシサーバにて80番ポートと443番ポートを受けて文字通りの仮想ホスト構成としている。メールサーバーは受信のために25番ポートを空けているが、こちらは図のように直接外に向けてポートを空けてしまうと、ホスト内のサービスメール(cronやlogwatchなど)が動作しなくなってしまうので、実際はホスト側でもpostfixを動作させホスト内メールの面倒を見ながら外部から来たメールはコンテナのメールサーバーに転送する仕組みとしている。

一ヶ月の試行錯誤の結果ほぼ従来通りの動作を確認できたが、なんか妙に重いかも知れない。。。

久々のブログ記事でいきなりファイルアップロード不可とか、メッチャ焦りました。新環境では十分なテストが必要ですね。メールサーバーのdocker化もテキトウに確認して運用始めては→問題発覚→環境戻し→デバッグやり直し-の繰返しでした。たいていはショウモナイポカミスなんですが、そういうのに限って見つけるのに半日から1日作業となるのが定期。

Windows Terminal

しばらく最新の動向とか触れる機会が少なかったので、最近のツール事情にはいまだに驚かされたりする。当方では趣味でさくらVPSに契約して個人用のサイトを運営をしており、当たり前だが自宅のローカル環境から操作している。今までは通信ソフトのPuttyでサーバーにSSH接続して、サーバー上でemacsなどのエディタで構築作業をおこなっていたが、前回の投稿で書いたようにマイクロソフトのVScodeエディタでSSH接続することでローカル同じ環境で編集作業を行なうことが出来る。

それでも実際ターミナル操作をするときはPuttyを使用していたのだが、今度はWindows Terminalというものを知ることとなる。こちらはWindowsのストアアプリで、コマンドプロンプトからPowershell、WSLも対応しており、プロファイルを追加することでssh接続も可能となる優れもの。

しかもこちらはWindows版のOpenSSHのサービスで動いているので、SSH-Agentサービス(Windowsサービスで自動実行)ログイン時にパスフレーズを入れる必要もないので便利。

ローカル作業時も任意のフォルダで右クリックすると「Windowsターミナルで開く」のメニューが追加されており、Powershellが起動するようになる。(これは、以前よりシフトを押しながら右クリックでPowershellで開くことが可能だったが)

ということで、これも何かあったときのための覚え書き。

VScodeの件もそうだけど、今までは何だったのか?!ってくらい超便利な感じで、きっとまだ触っていないDockerやなんかも同じように感動するんだろうな、、、、と思いつつも、なんだかね。こんなすごい複雑なシステムの開発環境を使って、作っているものと言えば、趣味のオモチャ程度・・・。超豪華な財布の中に2000円くらいしかい入ってない感じw
精進せねばと思うこのごろ。。。

Bootstrap5対応その他

という記事を書こうとWorpressダッシュボードに移動したら、いつのまにかWordpressが5.8にバージョンアップしていた。例によってDBバックアップを取れとの忠告も聞かずに、速攻アップデートボタンポチり。

特に変った様子もないが、変化点は追々チェックするとしよう。

でBootsrtap5対応だが、先月辺りまでのこのサイトの引越作業の時点でベータ版が存在していたが、あまり気にしていなかったところ、ポータルサイト構築を兼ねたWEBアプリ開発の勉強がてら色々調べていたところバージョン5のリリースを知った(今ごろですが)。機能的にはあまり差は無い(というかよく読んでいない)がJQuery依存の削除やIEなど古いブラウザサポート終了など良い感じのアップデートのようなので、まずはdjangoテストサーバー環境で導入実験を行ない、ある程度問題ないことを確認したところで主ブランチに統合して本番環境のポータルサイトでも採用することとした。

見た目はあまり変らないが、テーマのデフォルトの文字サイズなどが異なるため、若干文字が小さかったり、メニューの文字が太字だったりと細かな気にならない違いが見られた。

4.5から5への対応に関する変更点であるが、前回までのSass導入でHTML(テンプレート)にBootstrapのクラスタグを直接書くことは少なくなったので、基本的には階層構造のスタイル定義をしているSassソースファイルを修正することで対応した。

主な違いは、marginやpaddingを表現するクラスで、たとえば右マージンはmr、左マージンはmlというようにrightとleftの頭文字で表現していたが、バージョン5より、左がstart、右がendと表現するようになりそれぞれマージンとしてはms、meと表記するようになった。これはおそらく多言語対応でRTL(右から書く文化)にも対応する事により水平方向の表記は右からと左からというより、始めと終わりという意味で統一したのだろう。

あとハマったのはアコーディオン型のメニューアニメーションの件で、若干名称が異なり、

  • data-toggle → data-bs-toggle
  • data-target → data-bs-target

になっていた。当サイトはこの位しか機能を使っていないので、以上の修正で無事以前と同様に動作するようになった。

というか、せっかくBootstrap5を導入したのに、新機能を調べて(あるのか?)導入実験とかしたいのだが、他にもいろいろやりたいことありで、なかなか優先順位に困っているところ・・・
思いつき順ということもありますが・・・

不正アクセスログ集計ツール④

前回の記事でパイプ接続したプロセスをmapのまま返すとプロセスが終らないまま関数から帰ってきてしまうので、気持ち悪いからlist関数を使ったが、

bipset =  list(map(lambda x: ipv4adrset(x.decode('UTF-8').rstrip('\n')), proc.stdout))
 ↓
bipset =  [ipv4adrset(x.decode('UTF-8').rstrip('\n')) for x in proc.stdout]

の様に、わざわざmap使うより、下のようにリスト内包表記で直接リストを返すような仕組みを用いた方がスマートな気がした。とくに今回のようにmap直後にlistするような使い方の場合特に。

mapやfilterはイテレータなので直後にforなどのループ処理を行なう場合に適しており、リストそのものが欲しいときにはmapを使わずに[リスト内包表記]をするのが良いきがする。リスト内包表記やlist式のリスト生成では各項目の変換式(関数)が一度に使用され結果のリストが返されるが、イテレータでは生成段階では変換式(関数)は実行されず、for文など繰返し処理が行なわれる時に一つずつ実行されることになる。

たとえばファイルを読み込み関数があったとして、オープンしたファイルハンドルに対して読み出して検索や加工をする処理の結果をイテレータとして返してしまうと、関数からリターンした段階でファイルを閉じると、そのイテレータからは読み出せないという結果になる。

ということでその場合は、リストとして返す関数が正解となる。

あと、イテレータからリストに変換するときに一度繰返し処理が行なわれるという考え方からすると、for文に使用する前にリストに変換するのは効率が悪く。動作速度的にも不利になるようなので、そういう観点で使い分けていく目安になるのではないかと思う。

次に、ログファイルの解析部分。これも前回記事で、djangoの認証バックエンドをチョットいじって、ログイン失敗のログを残せるようにしたので、ここではそれを検出してリストとして返すこととする。ログファイルはApacheユーザーに読取り権限があるので、そのまま読見込めばいい。ただし、/var/logに保存している場合など、logrotateの処理が行なわれる場合それも追跡して行ないたい。また古いログはrotate時にgzip圧縮されるのでこれにも対応したい。

from blocklib import ipv4adrset
import os, glob, gzip
logger = getLogger(__name__)
import re, json

# djangoのアクセスログ(json形式)を読み込に認証履歴のレポートを返す
# 認証履歴のログはdjango認証バックエンドを継承して認証時のログ出力機能を追加したもの
# [引数]djlog : 解析するアクセスログ(json形式)をフルパスで与える(gzipもOK)
#              Noneの場合は、デフォルト django_log_json を使用(logorotateされたものも含み全てを読み込む)
#       st : レポート対象の開始日時(logrotateされたファイルはこの日時より新しいものを読み込み対象とする)
#       ed : レポート対象の終了日時
#       address : レポート対象のIPアドレスオブジェクト(Noneの場合は全て)サブネット検索対応
def get_django_loginfails(djlog, st, ed, address=None):
    if djlog is not None:
        djlogs = [djlog]
    else:
        djlogs = [x for x in glob.glob(django_log_json + '*') if re.match('.*(log|gz|[0-9])',x) and os.path.getmtime(x) > st.timestamp()]
        djlogs.sort(key=lambda x: os.path.getmtime(x))
    retlist = []
    for jlog in djlogs:
        logger.debug(jlog)
        with open(jlog, 'r') if not re.match('.*\.gz$', jlog) else gzip.open(jlog, 'rt') as jf:
            #jl = json.load(jf)
            decoder = json.JSONDecoder()
            jlines = map(lambda x: decoder.raw_decode(x), jf)
                #aa = decoder.raw_decode(line)
            for jl in map(lambda x: x[0], jlines):
                # fromisoformatで読み込むために末尾のZが邪魔なので削除
                # UTCのタイムゾーンを設定してJST時刻に変換する
                ts = datetime.fromisoformat(jl['timestamp'].rstrip('Z')).replace(tzinfo=timezone.utc).astimezone(tz_jst)
                ts = ts.replace(microsecond=0)
                if ts > st and ts < ed:
                    (dat, event) = (jl['ip'], jl['event'])
                    res0 = re.match('user logged in : (.+)$', event)
                    res1 = re.match('login faild.*: (.+)$', event)
                    event = False if res1 else True if res0 else None
                    if event is None:
                        continue
                    name = res1[1] if res1 else res0[1] if res0 else ''
                    if address is not None and address != ipv4adrset(dat):
                        continue
                    logger.debug('%s : %s - %s(%s)' % (str(ts), dat, name, event))
                    retlist.append(
                        {
                            'name':name,
                            'datetime':ts,
                            'address':ipv4adrset(dat),
                            'status':event,
                            'source':'django',
                        }
                    )
                    #print('%s : %s : %s' % (str(ts), dat, event))
    return retlist

ログはdjaongo.structlogでjson形式で出力したものを対象としている

  • 17行目globクラスを使用して指定したファイル名で始まるlogrotateファイルのリストを作成する(検索開始日付stよりも新しいタイムスタンプのファイルという条件も含める)
  • それぞれに対して、ファイルオープンし(拡張子gzだったらgzipオープンに変更)1行ずつ読み出し、対象パターンを見つけたら辞書形式のデータの配列として返す

ソースプログラムを貼付けてるけど、実際はそのままでは動かない一部の公開なので誰の役にも立たないただの覚え書きなのであった。
json形式だからさっくり読めるだろうとして始めたら何かエラーになってサッパリ読めない。。何でだろうと思って先人様たちの記事をたどって調べたら、こんなもんだと上記のようにデコーダを作って1行ずつ読み込んでいくようだ。
ぇぇぇーーー。同じくpython使ってjson形式のログを掃き出してるのになんで直接読めないん??ってなった。こんど時間があったら調べてみよう

不正アクセスログ集計ツール③

パート3と思ったけど、ちょっとdjango開発環境にて問題発生。

Ubuntuサーバー(さくらVPS)上のコンテンツを編集するのに、いろいろゴタゴタした顛末を下記に

  • sshログイン出来るユーザーでremote-SSHのVScodeでdjangoプロジェクトのコンテンツ編集を行なう。
  • このままではプロジェクトのファイル所有者やアクセス権がユーザーのものになってしまうので、Apacheユーザーで動作する本番環境を直接編集するわけには行かない。
  • そこで一旦GitHubプライベートリポジトリにpushして、Apacheユーザーで本番環境にpullすることで動作させることとした。
  • しかし、ちょっとした修正さえ編集して動作を確認するためにpush/pullを繰り返すこととなり、Git履歴がメッチャ汚くなる(一旦pushしてしまうと例え一人で使用しているとはいえrebaseとか履歴編集が容易ではない)。
  • いっそApacheユーザーでSSHログイン出来るようにしようかと、血迷ったことを考えたが、すんでの所で思いとどまる。
  • 既にApache2環境にデプロイ済のポータルプロジェクトだが、ユーザー環境ではもう一度python manage.py runserverで動作させてある程度動作検証した後に本番環境へpushするという流れにしようとした。
  • 注意点は、データベースは同じものを使用するので壊さないように注意。
  • ログファイルなど本番用と混ざらないように、別ログになるようにする
# settings.py で動作中のユーザー名を得る(テスト環境であればローカルユーザーになる)
RUN_USER = pwd.getpwuid(os.stat(__file__).st_uid).pw_name

# ユーザー名でApache2で動作中かどうかを判別してログのベースディレクトリを変更
LogBase = '/var/log/django' if RUN_USER == APACHE_USER else os.path.join(BASE_DIR, '.log')

# あとはログの仕様で設定する
  • 一応デプロイ後のものと同じ構成でdjango runserverテスト環境でも動作することを確認できたが、画像であるとかstaticが読み込めなくなっていた。(Apacheではサーバーの設定でstaticのエイリアス設定をしていたので、djangoのURLconfでの動作はあまり見ていなかった)
  • 調べたところ、静的ファイルの設定はsettings.pyでSTATIC_URLとSTATIC_ROOTで設定してあったのだが、ウチの環境では動作せず(素の状態でdjango-admin startproject XXXX して試してみたがstaticディレクトリを読み取ることが出来なかった)。代わりにSTATICFILES_DIRSという配列を設定することで無事読み込めることが分かった。
STATIC_URL = '/static/'    # これはOK

STATIC_ROOT = os.path.join(BASE_DIR, 'static')
#   ↓
STATICFILES_DIRS = [
    os.path.join(BASE_DIR, 'static'),
]

  • STATIC_ROOTとSTATICFILES_DIRSを同時に設定するのはダメ。
  • djangoの素の状態でこれでstaticが読み込めることが確認できたが、本番環境からpullしてきたポータルプロジェクトでは上記の修正を行なってもstaticが読み込めなかった。数時間掛けて原因究明した結果、単に404 Not Foundの状態をブラウザが覚え続けていただけで、ブラウザキャッシュクリアすることで無事読み込めるようになった。お粗末。

何ともアホらいいオチでしたが、ブラウザキャッシュは意外に見落としがち。以前のサーバー引越の時も新サーバーに以前のドメインを割り当ててても一向に変化が無くて困ったのも、結局はブラウザキャッシュが原因でした。

不正アクセスログ集計ツール②

不正アクセスに対する対応は当サイトでは悪質な(連続で何度もログインアタック攻撃をするなど)場合、手動でblocklist(ufwの場合、/etc/ufw/before.rules)に放り込むことにしている。そのため集計対象としてはブロックリストに含まれるIPアドレスは対象外としたい。よってapacheユーザーでブロックリストを読み込む必要があるが、こちらのファイルはroot権限でしか開くことが出来ない。apacheユーザーを管理者グループに入れることも考えたが、ここはひとつ。

  1. root権限でブロックリストを読み込むコマンドを作成する(getblocklist)。
  2. apacheユーザーに getblocklistコマンドだけパスワードなしでsudo出来るようにする。
  3. apacheユーザー権限で動作するpythonプログラム(djangoなど)でsubproccessにより上記コマンドを実行し、結果をリストとして得る関数を作る

で、バッチリではないかと、この方針で進めることとする。

1.root権限でブロックリストを取得するコマンド

これは全く問題なしで、以前のblockset.pyより該当部分のみを抜き出したプログラムとなる。

getblocklist.py

ipアドレスを利用するクラスipv4addressはライブラリとして分離している。

2.apacheユーザーに実行権限付加

表示プログラムを/usr/local/sbinなどに置きapacheユーザー(www-data)にsudo実行権限を与える

www-data        ALL=(ALL)       NOPASSWD:/usr/local/sbin/getblocklist.py

www-dataパスワード未設定のsu不可ユーザーなのでsudo ALLでも他のコマンドは実行出来ない。

3. apacheユーザー権限でリストを取得する

subproccessモジュールを利用して標準出力を得るには、Popenでプロセスを起動してプロセスの標準出力を接続する必要がある。プロセスのstdoutは一行づつ取り出すイテレータとなるので、mapによりipv4adrsetのリストとして取得している。(また、なぜかプロセスの出力がバイトとなっているので、UTF-8で文字列にデコードする必要があった”x.decode('UTF-8').rstrip('\n')”)

import subprocess

get_blocklist_proccess_cmd = ['sudo','/usr/local/sbin/getblocklist.py']

# root権限でブロックリスト取得コマンドを走らせて標準入力を得る
def get_blocklist():
    proc = subprocess.Popen(get_blocklist_proccess_cmd, stdout=subprocess.PIPE)
    bipset =  list(map(lambda x: ipv4adrset(x.decode('UTF-8').rstrip('\n')), proc.stdout))
    return bipset

良く分かっていなかったのだけど、関数からmapのまま返すとイテレータなので関数の外にでてもプロセスが終わってないのね。。listとして全部取得してプロセスを正常に終らせておく必要がありましたとさ。

不正アクセスログ集計ツール①

目的

不正アクセスのログを集計してポータルの管理ページに結果を表示する

不正アクセスの定義

今回の場合はWEBページの認証機構へのログインアタックとする。パスワードを知っていて入って来てしまう場合は対象外。この場合はログイン履歴表示機能で様子をみることが可能とする。

機能

  • djangoポータルページの管理グループのみ表示(ページへのリンクも管理グループのみに表示)
  • django認証ログからの集計と、Wordpressのプラグイン(User Login History)の認証履歴データベーステーブルからの履歴も取得し、合計として集計する。
  • 集計開始日時ー終了日時、リストアップするための認証失敗回数(5回など)設定。
  • 特定のIPアドレスのアクセス詳細を表示ー上記リスト表示にボタン追加

その他機能追加として、直接関係は無いが

  • エラー404などのページカスタマイズ
  • djangoいろいろ勉強用テストページ

現在の所、最終的な完成形は上記のような仕様になるが、管理ページの追加なので訪問者には何も関係が無いです。その他思いついたら仕様追記する予定。

開発に関しては、djangoのプロジェクトディレクトリをgit管理とし、GitHubプライベートリポジトリ経由でsshログインユーザーにclone。VScodeで編集してテストブランチとしてpush。apacheユーザーでプロジェクトディレクトリ(テスト用仮想サーバー)にpullして動作試験。デバッグを行い完成形(公開レベルになった段階)をmasterブランチにmergeしてGitHubにpush。公開用プロジェクトディレクトリにpullして運用するという流れ。

単純にapacheユーザーのプロジェクトをVScodeのssh-remoteで編集したいという欲求のため、このような複雑は構造となってしまった。GitHubの使い方の勉強できて丁度良いか。

python環境整備

ubuntuのインストール直後はpythonコマンドはpython2.7系、3.8系のpythonを使いたければpython3と打たなければならなかった。まぁ仮想環境を構築すれば良いんだけど、サーバー設定環境等全ユーザーで3系を使いたいので、対策した時の覚書き

ググったサイトを参考に、update-alternativesコマンドを使用してデフォルトの起動バージョンを3系とした。

root@server:~# update-alternatives --config python
There are 2 choices for the alternative python (providing /usr/bin/python).

  Selection    Path                Priority   Status
------------------------------------------------------------
  0            /usr/bin/python3.8   2         auto mode
  1            /usr/bin/python2.7   1         manual mode
* 2            /usr/bin/python3.8   2         manual mode

Press &lt;enter> to keep the current choice[*], or type selection number:

こんな感じでpython3が選択されている

ここで、勉強は各種実験用に個人ディレクトリにpython仮想環境を構築(venvを使用)

xxxx@server:~$ python -m venv venv
xxxx@server:~$ cd venv/
xxxx@server:~/venv$ ls
bin  include  lib  lib64  pyvenv.cfg  share
xxxx@server:~/venv$ source bin/activate
(venv) xxxx@server:~/venv$ ls
bin  include  lib  lib64  pyvenv.cfg  share
(venv) xxxx@server:~/venv$ which python
/home/xxxx/venv/bin/python

まずはpipのアップデート

(venv) xxxx@server:~/venv$ pip install -U pip
Collecting pip
  Downloading pip-21.1.2-py3-none-any.whl (1.5 MB)
     |████████████████████████████████| 1.5 MB 4.0 MB/s
Installing collected packages: pip
  Attempting uninstall: pip
    Found existing installation: pip 20.0.2
    Uninstalling pip-20.0.2:
      Successfully uninstalled pip-20.0.2
Successfully installed pip-21.1.2

あとは色々モジュールを入れたりすれば良いですが、折角なので最新バージョンを使用出来るようにと

こちらを参考にpyenv仮想環境を構築した。

git clone https://github.com/pyenv/pyenv.git ~/.pyenv
cd ~/.pyenv
git pull

下記のように環境変数を設定するよう.profileを書き換えて再ログイン

echo 'export PYENV_ROOT="$HOME/.pyenv"' >> ~/.profile
echo 'export PATH="$PYENV_ROOT/bin:$PATH"' >> ~/.profile
echo 'eval "$(pyenv init --path)"' >> ~/.profile

pyenv install -l | grep 3.9
  3.9.0
  3.9-dev
  3.9.1
  3.9.2
  3.9.3
  3.9.4
  3.9.5

最新は3.9.5なのでこれをインストール

pyenv install 3.9.5

なんか色々エラーが出たらコンパイルのためのライブラリが足らんようです。当方の場合はbz2が足りなかったので、

sudo apt install -y libbz2-dev libsqlite3-dev

あとググって足らなさそうなものも入れておくと、無事インストール終了

pyenv global 3.9,5   -----  3.9,5 を使用する
pyenv global system  -----  システムにインストールされているバージョンを使用

python -V

バージョン確認して無事変更されていたら、さらにvenvによる仮想環境も構築しておく

python -m venv venv
cd venv
source bin/activate
python -V

deactivate

こっちの方がライブラリが探しやすいが、pyenvで最新版のpythonが使う環境も欲しいということでの2本立てとしてみた。ライブラリ何度も入れないといけなさそうだけど、色々体で覚えていけそうw

サーバー設定作業メモ

何をやったか忘れないように、メモメモ記事を残すことにした。これも投稿数稼ぎかなw

ポータルサイト http://www.marochanet.org/ の djangoアップデート作業

ウェブユーザでpython仮想環境に入り、まずはpipのアップデート

(venv0) xxx@jagha:~/html/python$ python -m pip install --upgrade pip

次に仮想環境のアップデート可能なモジュールリストをとる(一部表示)

(venv0) xxx@jagha:~/html/python$ pip list -
Package                Version Latest Type
---------------------- ------- ------ -----
……
Django                 3.0.8   3.2.4  wheel
django-bootstrap4      2.2.0   3.0.1  wheel
django-hitcount        1.3.2   1.3.3  wheel
django-tinymce         3.0.2   3.3.0  wheel
……

他にもリストアップされたモジュールを全てアップデートする

(venv0) xxx@jagha:~/html/python$ pip install -U (module name)

ここで、リストには無いがデータベースエンジンとの接続に必要なmysqlclientのアップデートに失敗。

これに関してはpython側のライブラリが不足していたため下記コマンドをrootで打って、ライブラリをインストールする

apt-get install python3-dev libmysqlclient-dev

無事mysqlclientのアップデートに成功して終了。

メールボックス整理ツール

本サーバーでは個人の興味や勉強を兼ねてメールサーバーを運用している。昔はプロバイダメールはPOPで端末に落としてくるのが主流で、例えば家と職場、携帯電話で同じ受信メールを処理することが難しかったし、Exchangeを始めIMAPも使われ始めたが、サーバー負荷が高いため普通のプロバイダでは利用できない場合が殆どだった。今ならフリーのアカウントなどWEBメールを初めとして受信メールをサーバーを置くタイプが多くなってきた。

私が使用しているプロバイダメールも今はWEBメール対応でサーバーにメール本体を置くことが出来るが、やはりPOPがデフォルトのようだ。そこで、まだPOPしか使えない頃から、外部にサーバーを立ち上げて(一昔流行ったおうちサーバーや、現在の当方のような「さくらVPS」)プロバイダからのメール自動転送(昔はPOP)を受けてIMAPサービスを行っている。

サーバーの引越においてもメールデータの移行も必要であった。引越ついでにサーバー上のユーザーのメールボックス形式をmboxからIMAPとの親和性の高いMaildir形式に移行する。二つの形式には大きな違いがあり、

  • mboxのメールボックスは巨大な1ファイル、Maildirはメール1通1ファイル
  • mboxのメールボックスフォルダ階層はファイルシステムに準ずる、Maildirはフォルダの階層はピリオドで接続した構造で表現され、ファイルシステム的には一階層である
  • mboxの受信トレイは/var/spool階層のユーザー名ファイル、Maildirは各ユーザーディレクトリのMaildir直下に配信される
  • どちらも漢字フォルダ名はutf-7で、端末では普通読めない。

そのあたりの作業を簡単に行えるような行き当りばったりツールをまたpythonで作成した。pythonのmailboxライブラリを使用するとmbox形式やMaildir形式のメールボックスを操作することが出来、相互に移動したりフォルダに振り分けたり出来る。というライブラリお任せで作成したツール「mailboxutil」についてまた追々と解説・覚書きを投稿したいと思う。

このツールをつかってメールボックス移行を行ったのは実際には前のサーバーで約1年前CentOS6からCentOS8にOS変更した時でした。まぁ、話がややこしくなるので、今回やったことにさせてくださいw
ていうか、このタイミングでCentOSサポート終了のお知らせは正直凹みました。。。時間を返せーー!と。
(CentOS同士でもメジャーバージョンを一個飛ばしということもありアップグレードインストールは諦めて、バックアップ→新規インストール→データリストアをやっていたのでした。。。)