
9,800åã®å€ãMac miniã«ãããäžåºŠä»äºãã â Python ã® watchdog ãšsystemd ã§äœãåžžé§ç£èŠã第2åïŒå š5åã
ã¯ããã«
ååã¯ã9,800åã§è³Œå ¥ããå€ã Mac mini ã« Debian 13 ãå°å ¥ããèªåå°çšã®ç»åã©ã€ãã©ãªãžäœãæ¿ããå šäœåãã玹ä»ããŸããã
宿ããä»çµã¿ã§ã¯ãMacBookPro ããå ±æãã©ã«ããžç»åãå ¥ãããšãMac mini ããã®å°çãæ€ç¥ããŸããç»åã®æ å ±ã¯ããŒã¿ããŒã¹ãžèšé²ãããæåŸã«ã¯ãã©ãŠã¶ããæ€çŽ¢ã§ããããã«ãªããŸãã
ãã£ãšããæåãããããŸã§ãäœãããšããããã§ã¯ãããŸããã
ä»åã®ç®æšã¯ãååã®æåŸã«èšãããšããã§ãã
Samba å ±æã®äžã«èšãã inbox ããPython ã® watchdog ã§ç£èŠããŸããç»åãå±ããããšãæ€ç¥ããã³ããŒãçµãããŸã§åŸ ã¡ãsystemdã®ãµãŒãã¹ãšããŠåžžæåäœããããšãããŸã§é²ããŸãã
èšèã«ããã°ãããã»ã©è€éã«ã¯èŠããŸãããããã¡ã€ã«ããèŠãããããšãšãå®å¿ããŠãåãåãããããšã¯ãåãã§ã¯ãããŸããã§ããã
â³ïžãã®èšäºã§ã¯ãLinux ã®ãŠãŒã¶ãŒåã serveruser ãšè¡šèšããŸããå®éã«è©Šãå Žåã¯ããèªèº«ã®ç°å¢ã§äœ¿çšããŠãããŠãŒã¶ãŒåãžèªã¿æ¿ããŠãã ããã
ç»åãåãåãå Žæ
MacBookPro ãš Mac mini ã®éã§ãã¡ã€ã«ãåãæž¡ããããSamba ã«ããå ±æãã©ã«ããçšæããŠããŸãã
Samba ãšã¯ãLinux äžã®ãã©ã«ãããmacOS ã WindowsOS ãããããã¯ãŒã¯çµç±ã§å©çšã§ããããã«ããä»çµã¿ã§ãã
ä»åããã®å ±æé åã®äžã«inboxãšãããã©ã«ããèšããŸããã
/home/serveruser/share/inboxMacBookPro ã® 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 åŽãžãã¡ã€ã«ã®å Žæã衚瀺ãããŸããã

ãŸã ç»åã®å 容ã¯èª¿ã¹ãŠããŸãããããŒã¿ããŒã¹ããããŸãããããã§ããå ±æãã©ã«ããžçœ®ããç»åã«Mac miniãå¿ããããšã«ã¯ãå°ããªæå¿ãããããŸããã
å€ãæ©æ¢°ã«ãæ°ããä»äºãçãŸããæåã®ç¬éã§ãã
æ€ç¥ããæç¹ã§ã¯ãã³ããŒãçµãã£ãŠããªã
ããã§ãæåã®åé¡ãèŠã€ãããŸããã
watchdog ããã¡ã€ã«ã®äœæãç¥ãããæç¹ã§ã¯ãå¿ ãããã³ããŒãå®äºããŠãããšã¯éããŸããã容éã®å€§ããªç»åã¯ãäžç¬ã§çŸããããã§ã¯ãããŸããããŸããã¡ã€ã«ãäœããããã®äžãžå°ããã€ããŒã¿ãæžã蟌ãŸããŸãã
ã€ãŸããç£èŠããã°ã©ã ããã¯ãã¡ã€ã«ãèŠããŠããŠãããã®å 容ã¯ãŸã å±ããŠããéäžãããããŸããã

ãã®æ®µéã§ç»åãéãã°ãèªã¿èŸŒã¿ã«å€±æããããšããããŸããããã·ã¥å€ãèšç®ããŠãã宿åŸãšã¯ç°ãªãå€ã«ãªãå¯èœæ§ããããŸãã
æ¥ãã§åŠçãå§ããããšã¯ãããã£ãŠäžç¢ºããªèšé²ãæ®ãããšã«ãªããŸãã
ããã§ããã¡ã€ã«ãæ€ç¥ããŠããããã«ã¯æ¬¡ã®åŠçãžé²ãŸãªãããã«ããŸããã
å€åãæ¢ãŸããŸã§åŸ ã€
ã³ããŒã®å®äºã倿ããããããã¡ã€ã«ã®å®¹éãšæŽæ°æå»ãäžå®ééã§ç¢ºèªããŸãã

ä»åã®èšå®ã¯ã次ã®ãšããã§ãã
STABLE_SECONDS = 3
CHECK_INTERVAL = 1
MAX_WAIT_SECONDS = 601ç§ããšã«ãã¡ã€ã«ã®ç¶æ ã確èªãã容éãšæŽæ°æå»ã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 ãšããéåã§ç®¡çããŸããã
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.targetUserãšGroup ã«ã¯ããã®ããã°ã©ã ãåããå©çšè
ãæå®ããŸãã
ExecStart ã«ã¯ãä»®æ³ç°å¢å
ã® Pythonãšãç£èŠããã°ã©ã ã®å ŽæãèšããŸãã
Restart = on -failure ãæå®ãããšãäºæããªãç°åžžã§ããã°ã©ã ãçµäºããå Žåãsystemd ãåèµ·åã詊ã¿ãŸããNoNewPrivileges = true ã¯ãåäœäžã®ããã°ã©ã ãæ°ããªç¹æš©ãåŸãããšãé²ãèšå®ã§ãã
æ©èœãåããããšã ãã§ãªããäžããæš©éãå¿ èŠãªç¯å²ãžãšã©ããããšãæèããŸããã
ãµãŒãã¹ãæå¹ã«ãã
èšå®ãæžããåŸãsystemd ãžå 容ãèªã¿èŸŒãŸããŸãã
sudo systemctl daemon-reloadç¶ããŠããµãŒãã¹ãæå¹åãããã®å Žã§èµ·åããŸãã
sudo systemctl enable \
--now file-watcher.serviceenable ã¯ãMac mini ãåèµ·åããåŸããµãŒãã¹ãèªåçã«ç«ã¡äžããããã®æå®ã§ãã
--now ãä»ãããšãæå¹åãšåæã«ãµãŒãã¹ãèµ·åããŸãã
åäœç¶æ
ã¯ã次ã®ã³ãã³ãã§ç¢ºèªã§ããŸãã
systemctl status \
file-watcher.serviceæ£åžžã«åããŠããã°ãç¶æ æ¬ã«æ¬¡ã®ããã«è¡šç€ºãããŸãã
Active: active (running)ãã°ã¯ã次ã®ã³ãã³ãã§ç¢ºèªã§ããŸãã
journalctl \
-u file-watcher.service \
-n 30 \
--no-pagerMacBookPro ããç»åãäžæã³ããŒãããšããã°ãžæ€ç¥ãšå®å®ç¢ºèªã®èšé²ãæ®ããŸãã
ãã¡ã€ã«å°çãæ€ç¥ãã³ããŒå®äºåŸ
ã¡
ãã¡ã€ã«æºåå®äºããã§ãã¿ãŒããã«ãéããŠãç£èŠã¯ç¶ããŸãã
Mac mini ãåèµ·åããŠããå ±æãã©ã«ãã®ç£èŠã¯èªåçã«æ»ããŸãã
ãŸãã¯ãäžæã§ç¢ºããã
ç£èŠãåããåŸããããã«å€§éã®ç»åãå ¥ããããšã¯ããŸããã§ããã
æåã¯äžæã ãã§ãã
äžæãã³ããŒãããã°ã確èªããŸãã
æ¬¡ã«æ°æãå
¥ããåãããŒãããªããã確ãããŸãã
ããã«ãã³ããŒäžã®ãã¡ã€ã«ãæ©ãèªã¿å§ããŠããªãããé ããã¡ã€ã«ã誀ã£ãŠæŸã£ãŠããªãããèŠãŸãã
å°ããªè©Šéšã§ããã°ãåé¡ãèµ·ããŠã远ããããããŸãã
倧éã®ãã¡ã€ã«ãæå ¥ããŠããç°åžžã«æ°ã¥ããšãåå ãæ¢ãã ãã§ãçžå¿ã®æéãå¿ èŠã«ãªããŸãã
å®äŸ¡ãªæ©æ¢°ã ããæ éã«æ±ãã®ã§ã¯ãããŸããã
倧åãªãã®ãé ããä»çµã¿ã§ãã以äžãäŸ¡æ Œã«ãããããã確ãããªããé²ããå¿ èŠããããŸãã
ç£èŠã§ããªãã£ãæéãè£ã
ãããŸã§ã§ãæ°ããå±ãç»åã¯æ€ç¥ã§ããããã«ãªããŸããã
ãã ããã²ãšã€ã ãæ®ãåé¡ããããŸãã
ãµãŒãã¹ã忢ããŠããéã«çœ®ãããç»åã§ãã
ç£èŠããã°ã©ã ãåããŠããªããã°ããã¡ã€ã«ãäœæãããç¬éã®éç¥ã¯åãåããŸãããããã§å®éã®æ§æã§ã¯ããµãŒãã¹ã®èµ·åæã« inbox ã®äžã確èªãããŸã ç»é²ãããŠããªããã¡ã€ã«ãæ¢ããèµ·åæç §åããå ããŸããã
ããã¯ãç£èŠããã確ããªãã®ã«ããããã®è£å©ã§ãã
ãŸããåŸã«å€§éã®ç»åãæ±ã£ãéã«ã¯ãåŠçéšåã4䞊åãžå€æŽããŸããããã ããéåžžã®ãã¡ã€ã«å°çã§ã¯ãã³ããŒå®äºã確èªãã3ç§éã®åŸ
æ©ãæ®ããŠããŸãã
éãããããšããããæ£ããåãåãããšãå
ã«çœ®ããŸããã
èµ·åæç
§åã䞊ååŠçã«ã€ããŠã¯ãç»åæ
å ±ãããŒã¿ããŒã¹ãžèšé²ããæ¬¡å以éã§ãæ¹ããŠè§ŠããŸãã
éãã«åŸ ã€ãšããä»äº
ä»åäœã£ãã®ã¯ãå ±æãã©ã«ãã®å€åãèŠå®ãå°ããªããã°ã©ã ã§ãã

ç»åãå±ããããšã«æ°ã¥ããŸããããã©ãããã«ã¯è§ŠããŸããã
容éãšæŽæ°æå»ãèŠãªãããã³ããŒãçµãããŸã§åŸ
ã¡ãŸããé ããã¡ã€ã«ã¯åãæµããåãéç¥ãéããŠåŠçããªãããã«ããŸãã
ãããŠãsystemdã®ç®¡çäžã§ãã¿ãŒããã«ãéããåŸãåãç¶ããŸãã
宿ããç»é¢ã®ãããªè¯ãããã¯ããŸã ãããŸããã
ããã§ãããã®æ®µéãææ§ãªãŸãŸå
ãžé²ããããšã¯ã§ããŸããã§ããã
åãåãä»çµã¿ãäžç¢ºãã§ããã°ããã®åŸã«ã©ãã»ã©æ
å ±ãå ããŠããèšé²ãã®ãã®ãä¿¡é Œã§ããªãããã§ãã
å€ã Mac mini ã«äžããæåã®å®åã¯ãç»åãæ¥ãã§æŽçããããšã§ã¯ãããŸããã§ããã
誰ãã眮ããããšã«æ°ã¥ããå±ãçµãããŸã§éãã«åŸ ã€ããšã§ãã
ãã®æ§ãããªä»äºããåŸã«30,407æãé ããç»åã©ã€ãã©ãªã®åå°ã«ãªããŸããã
次å
次åã¯ãåãåã£ãç»åã®æ å ±ãSQLiteãžèšé²ããŸãã
ãã¡ã€ã«åã容éãæŽæ°æ¥æãç»åã®çžŠæšªå¯žæ³ãMIMEã¿ã€ãã«å ããSHA-256ãšåŒã°ããå€ãä¿åããŸãã
SHA-256ãšã¯ããã¡ã€ã«ã®å 容ããç®åºãããããã°ããŒã¿ã®æçŽã§ãããã®å€ãææããã«ãååãç°ãªãåäžç»åãèŠã€ããããããã«ããŸãã
ãã å°çãèŠå®ãã ãã ã£ãMac miniããæ¬¡åããã¯ãé ãã£ãç»åãäžæãã€èŠãå§ããŸãã
æåŸãŸã§ã芧ããã ããŸããŠããããšãããããŸããã
