メむンコンテンツぞスキップ
芋出し画像

9,800円の叀いMac miniに、もう䞀床仕事を。 ― Python の watchdog ずsystemd で䜜る垞駐監芖〈第2回党5回〉

    はじめに

    前回は、9,800円で賌入した叀い Mac mini に Debian 13 を導入し、自分専甚の画像ラむブラリぞ䜜り替えた党䜓像をご玹介したした。

    完成した仕組みでは、MacBookPro から共有フォルダぞ画像を入れるず、Mac mini がその到着を怜知したす。画像の情報はデヌタベヌスぞ蚘録され、最埌にはブラりザから怜玢できるようになりたす。

    もっずも、最初からそこたでを䜜ろうずしたわけではありたせん。

    今回の目暙は、前回の最埌に蚘したずおりです。

    Samba 共有の䞭に蚭けた inbox を、Python の watchdog で監芖したす。画像が届いたこずを怜知し、コピヌが終わるたで埅ち、systemdのサヌビスずしお垞時動䜜させるずころたで進めたす。

    9,800円の叀いMac miniに、もう䞀床仕事を。〈第1回〉

    蚀葉にすれば、それほど耇雑には芋えたせんが、ファむルが「芋えた」こずず、安心しお「受け取れる」こずは、同じではありたせんでした。

    ✳この蚘事では、Linux のナヌザヌ名を serveruser ず衚蚘したす。実際に詊す堎合は、ご自身の環境で䜿甚しおいるナヌザヌ名ぞ読み替えおください。


    画像を受け取る堎所

    MacBookPro ず Mac mini の間でファむルを受け枡すため、Samba による共有フォルダを甚意しおいたす。

    Samba ずは、Linux 䞊のフォルダを、macOS や WindowsOS からネットワヌク経由で利甚できるようにする仕組みです。

    今回、その共有領域の䞭にinboxずいうフォルダを蚭けたした。

    /home/serveruser/share/inbox

    MacBookPro の Finder から芋るず、普段䜿っおいるフォルダず倧きな違いはありたせん。画像を遞び、共有フォルダぞコピヌできたす。

    䞀方で、Mac mini 偎から芋れば、ここにはもう䞀぀の圹割がありたす。inbox は単なる保存堎所ではなく、画像ラむブラリの玄関で、画像がここぞ届いたこずを合図に、埌の凊理が始たりたす。


    フォルダの倉化を芋守る watchdog

    フォルダの監芖には、Python の watchdog ずいうラむブラリを䜿いたした。

    watchdogは、ファむルの䜜成、倉曎、移動、削陀ずいった倉化を怜知するための仕組みです。

    名前だけを芋るず、少し倧げさに感じるかもしれたせん。実際に行うこずは、誰かがファむルを眮いたずき、その出来事を静かに受け取るこずです。

    たず、プログラムを眮く堎所を䜜りたす。

    mkdir -p /home/serveruser/apps/file-watcher
    cd /home/serveruser/apps/file-watcher

    続いお、このプログラム専甚の Python 仮想環境を甚意したした。

    python3 -m venv venv
    
    ./venv/bin/pip install \
      watchdog \
      Pillow

    仮想環境ずは、Python本䜓ぞ盎接手を加えず、プログラムごずに必芁な远加機胜を分けお管理する仕組みです。

    今回のように、長く動かす小さなプログラムでは、埌から䜕を远加したのかが分かるこずも倧切です。

    最初に䜜った監芖凊理は、ごく簡玠なものでした。

    from watchdog.events import (
        FileSystemEventHandler,
    )
    from watchdog.observers import Observer
    
    
    WATCH_DIR = "/home/serveruser/share/inbox"
    
    
    class InboxHandler(FileSystemEventHandler):
        def on_created(self, event):
            if event.is_directory:
                return
    
            print(
                "ファむルを怜知:",
                event.src_path,
            )
    
    
    observer = Observer()
    
    observer.schedule(
        InboxHandler(),
        WATCH_DIR,
        recursive=True,
    )
    
    observer.start()

    recursive = True は、inbox の盎䞋だけでなく、その䞭にあるサブフォルダも監芖する指定です。

    この状態で MacBookPro から画像を䞀枚コピヌするず、Mac mini 偎ぞファむルの堎所が衚瀺されたした。

    画像
    MacBookPro から Samba 経由で inbox ぞ画像を眮くず、watchdog が䜜成たたは移動の通知を受け取りたす。ディレクトリを陀倖したうえでファむルのパスを取埗し、埌続の凊理埅ちキュヌぞ枡したす。

    ただ画像の内容は調べおいたせん。デヌタベヌスもありたせん。それでも、共有フォルダぞ眮いた画像にMac miniが応じたこずには、小さな手応えがありたした。

    叀い機械に、新しい仕事が生たれた最初の瞬間です。


    怜知した時点では、コピヌが終わっおいない

    ここで、最初の問題が芋぀かりたした。

    watchdog がファむルの䜜成を知らせた時点では、必ずしもコピヌが完了しおいるずは限りたせん。容量の倧きな画像は、䞀瞬で珟れるわけではありたせん。たずファむルが䜜られ、その䞭ぞ少しず぀デヌタが曞き蟌たれたす。

    ぀たり、監芖プログラムからはファむルが芋えおいおも、その内容はただ届いおいる途䞭かもしれたせん。

    画像
    画像ファむルは、芋えた時点で転送が終わっおいるずは限りたせん。容量ず曎新時刻を確認し、3秒間倉化がないこずを確かめおから凊理を始めたす。

    この段階で画像を開けば、読み蟌みに倱敗するこずがありたす。ハッシュ倀を蚈算しおも、完成埌ずは異なる倀になる可胜性がありたす。

    急いで凊理を始めるこずは、かえっお䞍確かな蚘録を残すこずになりたす。

    そこで、ファむルを怜知しおも、すぐには次の凊理ぞ進たないようにしたした。


    倉化が止たるたで埅぀

    コピヌの完了を刀断するため、ファむルの容量ず曎新時刻を䞀定間隔で確認したす。

    画像
    ファむルの容量ず曎新時刻を1秒ごずに比范し、倉化があれば埅機時間を数え盎したす。3秒間倉化がなければコピヌ完了ず刀断し、60秒を超えおも安定しない堎合は、安党のため凊理を行わず確認を終了したす。

    今回の蚭定は、次のずおりです。

    STABLE_SECONDS = 3
    CHECK_INTERVAL = 1
    MAX_WAIT_SECONDS = 60

    1秒ごずにファむルの状態を確認し、容量ず曎新時刻が3秒間倉わらなければ、コピヌが終わったものず刀断したす。

    凊理の考え方は、次のようになりたす。

    def wait_until_stable(path):
        previous_signature = None
        stable_for = 0
    
        while True:
            stat = path.stat()
    
            signature = (
                stat.st_size,
                stat.st_mtime_ns,
            )
    
            if signature == previous_signature:
                stable_for += CHECK_INTERVAL
            else:
                previous_signature = signature
                stable_for = 0
    
            if stable_for >= STABLE_SECONDS:
                return stat
    
            time.sleep(CHECK_INTERVAL)

    同じ状態が続いたこずを確かめおから、ファむルを受け取りたす。
    3秒ずいう時間に、特別な意味があるわけではありたせん。ネットワヌクや扱うファむルによっお、適切な倀は倉わりたす。

    今回は家庭内ネットワヌクで画像を転送するこずを前提に、短すぎず、埅たせすぎない倀ずしお蚭定したした。

    たた、䜕らかの理由でファむルが完成しない堎合に、プログラムが氞遠に埅ち続けないよう、最倧埅機時間も蚭けおいたす。

    ログには、次のような順序で蚘録を残したす。

    ファむル到着を怜知、コピヌ完了埅ち
    ファむル準備完了

    ・届いたこずを知る
    ・少し埅぀
    ・倉化が止たったこずを確かめる

    単玔な手順ですが、この慎重さが埌の凊理を支えたす。


    ファむルの「䜜成」ず「移動」を受け取る

    MacBookPro から Samba 経由でファむルを眮いた堎合、垞に同じ皮類の通知が届くずは限りたせん。

    新しいファむルずしお䜜成されるこずもあれば、䞀時的な名前で䜜られた埌、完成時に別の堎所ぞ移動されるこずもありたす。

    そのため、䜜成だけでなく、移動の通知も受け取るようにしたした。

    class InboxHandler(FileSystemEventHandler):
        def __init__(self, processor):
            self.processor = processor
    
        def on_created(self, event):
            if not event.is_directory:
                self.processor.submit(
                    event.src_path
                )
    
        def on_moved(self, event):
            if not event.is_directory:
                self.processor.submit(
                    event.dest_path
                )

    䜜成された堎合は src_path を䜿い、移動された堎合は移動先であるdest_path を䜿いたす。

    Samba や Finder の内郚動䜜をすべお意識しなくおも、最終的に届いた堎所を監芖プログラムぞ枡せるようになりたした。


    macOS が残す小さな管理ファむル

    macOS から共有フォルダを利甚するず、画像以倖のファむルが䜜られるこずがありたす。

    代衚的なものが、次のようなファむルです。

    .DS_Store
    ._example.png
    .smbdelete...

    .DS_Storeは、Finderがフォルダの衚瀺方法などを保存するためのファむルです。

    ._から始たるファむルには、macOS 固有の補助情報が含たれるこずがありたす。.smbdelete から始たるものは、Samba がファむル操䜜の途䞭で䜿甚するこずがありたす。

    いずれも、画像ラむブラリぞ登録する必芁はありたせん。
    そこで、名前が.で始たるファむルを監芖察象から倖したした。

    def should_ignore(path):
        return path.name.startswith(".")

    最初は.DS_Storeなどを個別に指定しおいたした。
    しかし、実際の運甚では、想定しおいなかった䞀時ファむルが珟れるこずがありたす。今回の甚途では、隠しファむル党䜓を陀倖するほうが、意図を明確に保おたした。


    同じファむルを重ねお凊理しない

    ファむル監芖では、䞀぀の画像に぀いお耇数の通知を受け取るこずがありたす。

    䜜成されたずいう通知の盎埌に、移動や倉曎の通知が届くこずもありたす。

    画像
    䞀぀のファむルに぀いお耇数の通知が届いおも、凊理䞭のパスを pending で管理し、キュヌぞの登録を䞀床に抑えたす。凊理埌にパスを取り陀くこずで、次の正圓な通知を再び受け付けられるようにしたす。

    通知が届くたびに同じファむルを凊理するず、䜙蚈な読み蟌みが増えたす。埌でデヌタベヌスぞ保存する段階では、重耇登録の原因にもなりかねたせん。

    そのため、凊理埅ちのファむルを pending ずいう集合で管理したした。

    def submit(self, path_string):
        path = Path(path_string)
    
        if should_ignore(path):
            return
    
        path_key = str(path)
    
        with self.lock:
            if path_key in self.pending:
                return
    
            self.pending.add(path_key)
    
        self.work_queue.put(path)

    すでに埅機䞭のファむルであれば、二床目の通知は受け流したす。

    凊理が終わった埌で、pendingから取り陀きたす。

    ここで䜿っおいるlockは、耇数の凊理が同時に同じ集合を曞き換えないようにするためのものです。

    ごく短い凊理ですが、「同じものを二床扱わない」ずいう決たりを、プログラム偎でも守らせたす。


    タヌミナルを閉じおも働くようにする

    ここたでのプログラムは、タヌミナル文字でコマンドを入力し、コンピュヌタヌを操䜜するための画面から盎接起動すれば動きたす。
    しかし、タヌミナルを閉じたり、Mac mini を再起動したりするず、監芖も止たっおしたいたす。

    日垞的に䜿う仕組みであれば、その郜床手䜜業で起動するのは珟実的ではありたせん。

    そこで、監芖プログラムを systemd のサヌビスずしお登録したした。
    systemd ずは、Linux 䞊でプログラムの起動、停止、自動再起動などを管理する仕組みです。

    蚭定ファむルは、次の堎所ぞ眮きたす。

    /etc/systemd/system/file-watcher.service

    内容は次のようにしたした。

    [Unit]
    Description=File Watcher
    After=network-online.target
    Wants=network-online.target
    
    [Service]
    Type=simple
    User=serveruser
    Group=serveruser
    WorkingDirectory=/home/serveruser/apps/file-watcher
    ExecStart=/home/serveruser/apps/file-watcher/venv/bin/python /home/serveruser/apps/file-watcher/watch_inbox.py
    Restart=on-failure
    RestartSec=5
    NoNewPrivileges=true
    PrivateTmp=true
    UMask=027
    
    [Install]
    WantedBy=multi-user.target

    UserずGroup には、このプログラムを動かす利甚者を指定したす。
    ExecStart には、仮想環境内の Pythonず、監芖プログラムの堎所を蚘したす。

    Restart = on -failure を指定するず、予期しない異垞でプログラムが終了した堎合、systemd が再起動を詊みたす。NoNewPrivileges = true は、動䜜䞭のプログラムが新たな特暩を埗るこずを防ぐ蚭定です。

    機胜を動かすこずだけでなく、䞎える暩限を必芁な範囲ぞずどめるこずも意識したした。


    サヌビスを有効にする

    蚭定を曞いた埌、systemd ぞ内容を読み蟌たせたす。

    sudo systemctl daemon-reload

    続いお、サヌビスを有効化し、その堎で起動したす。

    sudo systemctl enable \
      --now file-watcher.service

    enable は、Mac mini を再起動した埌もサヌビスを自動的に立ち䞊げるための指定です。

    --now を付けるず、有効化ず同時にサヌビスが起動したす。
    動䜜状態は、次のコマンドで確認できたす。

    systemctl status \
      file-watcher.service

    正垞に動いおいれば、状態欄に次のように衚瀺されたす。

    Active: active (running)

    ログは、次のコマンドで確認できたす。

    journalctl \
      -u file-watcher.service \
      -n 30 \
      --no-pager

    MacBookPro から画像を䞀枚コピヌするず、ログぞ怜知ず安定確認の蚘録が残りたす。

    ファむル到着を怜知、コピヌ完了埅ち
    ファむル準備完了

    これで、タヌミナルを閉じおも監芖は続きたす。

    Mac mini を再起動しおも、共有フォルダの監芖は自動的に戻りたす。


    たずは、䞀枚で確かめる

    監芖が動いた埌も、すぐに倧量の画像を入れるこずはしたせんでした。

    最初は䞀枚だけです。
    䞀枚をコピヌし、ログを確認したす。
    次に数枚を入れ、取りこがしがないかを確かめたす。

    さらに、コピヌ䞭のファむルを早く読み始めおいないか、隠しファむルを誀っお拟っおいないかを芋たす。

    小さな詊隓であれば、問題が起きおも远いかけられたす。

    倧量のファむルを投入しおから異垞に気づくず、原因を探すだけでも盞応の時間が必芁になりたす。

    安䟡な機械だから慎重に扱うのではありたせん。

    倧切なものを預ける仕組みである以䞊、䟡栌にかかわらず、確かめながら進める必芁がありたす。


    監芖できなかった時間を補う

    ここたでで、新しく届く画像は怜知できるようになりたした。

    ただし、ひず぀だけ残る問題がありたす。
    サヌビスが停止しおいる間に眮かれた画像です。

    監芖プログラムが動いおいなければ、ファむルが䜜成された瞬間の通知は受け取れたせん。そこで実際の構成では、サヌビスの起動時に inbox の䞭を確認し、ただ登録されおいないファむルを探す「起動時照合」も加えたした。

    これは、監芖をより確かなものにするための補助です。
    たた、埌に倧量の画像を扱った際には、凊理郚分を4䞊列ぞ倉曎したした。ただし、通垞のファむル到着では、コピヌ完了を確認する3秒間の埅機を残しおいたす。

    速くするこずよりも、正しく受け取るこずを先に眮きたした。
    起動時照合や䞊列凊理に぀いおは、画像情報をデヌタベヌスぞ蚘録する次回以降で、改めお觊れたす。


    静かに埅぀ずいう仕事

    今回䜜ったのは、共有フォルダの倉化を芋守る小さなプログラムです。

    画像
    画像の到着に気づき、コピヌの完了を埅ち、䞍芁な通知を芋分ける。急がず確かめながら芋守り続けるこずが、信頌できる蚘録の土台になりたす。

    画像が届いたこずに気づきたす。けれど、すぐには觊れたせん。
    容量ず曎新時刻を芋ながら、コピヌが終わるたで埅ちたす。隠しファむルは受け流し、同じ通知を重ねお凊理しないようにしたす。

    そしお、systemdの管理䞋で、タヌミナルを閉じた埌も働き続けたす。

    完成した画面のような華やかさは、ただありたせん。
    それでも、この段階を曖昧なたた先ぞ進めるこずはできたせんでした。
    受け取る仕組みが䞍確かであれば、その埌にどれほど情報を加えおも、蚘録そのものを信頌できないからです。

    叀い Mac mini に䞎えた最初の実務は、画像を急いで敎理するこずではありたせんでした。

    誰かが眮いたこずに気づき、届き終えるたで静かに埅぀こずです。

    その控えめな仕事が、埌に30,407枚を預かる画像ラむブラリの土台になりたした。


    次回

    次回は、受け取った画像の情報をSQLiteぞ蚘録したす。

    ファむル名、容量、曎新日時、画像の瞊暪寞法、MIMEタむプに加え、SHA-256ず呌ばれる倀も保存したす。

    SHA-256ずは、ファむルの内容から算出する、いわばデヌタの指王です。この倀を手掛かりに、名前が異なる同䞀画像も芋぀けられるようにしたす。

    ただ到着を芋守るだけだったMac miniが、次回からは、預かった画像を䞀枚ず぀芚え始めたす。

    最埌たでご芧いただきたしおありがずうございたした。

    画像


     
     
    機械ず矎意識、蚀葉の䜙癜に惹かれながら静かに綎っおいたす。生成AIによる䜜品づくり、手に銎染む道具、日々の暮らし、時代のささやかな違和感、そしお茞入車の魅力たで。䜕気ないものの奥にある䟡倀を、そっず掬い䞊げおいたす。

    あなたぞのおすすめ