前言

昨天收到一封用户邮件,提到CodeLab的IoT服务器证书过期了。

原因是Let’s Encrypt每三个月会更新一次证书(推荐使用acme.sh),而我的mqtt broker又要求直接把证书放在它的特定目录下。于是我此前的做法是,每次Let’s Encrypt更新证书,我就手动复制一份到mqtt broker配置目录里,然后重启它。

麻烦是,有时候我会忘掉这件事,这不,前头的用户邮件就是一个例子。

DRY(Don’t repeat yourself)是面向对象编程中的基本原则,程序员的行事准则。旨在软件开发中,减少重复的信息。 DRY的原则是“系统中的每一部分,都必须有一个单一的、明确的、权威的代表”,指的是(由人编写而非机器生成的)代码和测试所构成的系统,必须能够表达所应表达的内容,但是不能含有任何重复代码。

将DRY的概念延伸到日常工作上,会让我们视图将周期性/重复劳动自动化。

今年比较热门的RPA(机器人流程自动化), 这个想法便与此有关。

我挺喜欢将编程技能用于解决日常生活里的问题,此前做过一些有关笔记:

解决问题

为了避免每三个月手动更新一次证书,我准备将其自动化

思路

思路其实很简单,使用watchdog监控证书文件,当其更新时,触发一个脚本,该脚本会将证书复制到目标路径,并重启mqtt broker。犹豫服务器基本不会关机,所有整个程序运行在tmux里即可,而不必构建开机自启程序(supervisor).

我原先想采用schedule定时触发程序,但证书的确切更新时间我是不知的。所以采用watchdog会更合适,这是一种典型的事件驱动: 当发生x时触发y。

动手

首先安装watchdog:

1
pip3 install watchdog

pip install watchdog会同时为我们装上watchdog(Python库)和watchmedo(命令行工具)

我们先来看看命令行工具的用法:

和watchmedo

1
2
3
4
5
watchmedo log \
    --patterns="*.py;*.txt" \
    --ignore-directories \
    --recursive \
    .

以上脚本的含义是: 递归记录与当前目录下*.py和*.txt文件有关的事件

使用shell-command参数,来触发脚本

1
2
3
4
5
watchmedo shell-command \
    --patterns="*.py;*.txt" \
    --recursive \
    --command='echo "${watch_event_type}"' \
    .

我接下来想表达这个逻辑: 如果${watch_event_type}created,则执行目标脚本,结果写bash if条件语句老是出错,原本以为是个hello world级的工作,bash语法真迷。

watchdog

watchdog是Python库!

上述逻辑我们可以轻松写出, 微调以下官方示例即可:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
# python3 mycertsdog.py .
import logging
import sys
import time
import subprocess
from watchdog.events import FileSystemEventHandler
from watchdog.observers import Observer

logging.basicConfig(level=logging.DEBUG)


class MyEventHandler(FileSystemEventHandler):

    def my_task(self):
        subprocess.call("cp /home/wwj/.acme.sh/codelab.club/codelab.club.key /home/wwj/mqtt/emqx/etc/certs/key.pem",shell=True)
        subprocess.call("cp /home/wwj/.acme.sh/codelab.club/fullchain.cer /home/wwj/mqtt/emqx/etc/certs/cert.pem",shell=True)
        subprocess.call("/home/wwj/mqtt/emqx/bin/emqx restart",shell=True)
        

    def on_modified(self, event):
        # test: cp ~/privkey.pem privkey.pem
        # import IPython;IPython.embed()
        if "codelab.club.key" in event.src_path:
            logging.info(event)
            time.sleep(1)
            self.my_task()


path = sys.argv[1]

event_handler = MyEventHandler()
observer = Observer()
observer.schedule(event_handler, path, recursive=True)
observer.start()
try:
    while True:
        time.sleep(1)
except KeyboardInterrupt:
    observer.stop()
observer.join()

在tmux中运行

1
python3 mycertsdog.py /home/wwj/.acme.sh/codelab.club/

通知

我想让下次更新证书时,给我发个通知。

准备采用ifttt webhook

1
curl -X POST https://maker.ifttt.com/trigger/cert_update/with/key/xxxxxx

塞到my_task,使用subprocess调用它即可。

如果你准备给自己发邮件(SMTP)的话,可以试试notifiers

参考