起因

前段时间被封了一个圣何塞的甲骨文云账号,上面我开了一个 4C 24G 的 ARM 实例,跑了很多东西,几乎所有以 dijk.eu.org 结尾的域名都在上面跑着。

当然,就包括博客的图片服务器。

这下好了,博客的图片全都挂了。

然后我把博客的图片服务器指定成了家里的服务器。优点是国内访问的速度快,缺点是家里的服务器不稳定。

所以,一个自然的需求就是监控家里的服务器,看看它是否在线。

但是,如果监控服务器的服务器本身都不够稳定,那监控就失去意义了。(没错,我说的就是白嫖的服务器。)

又免费,又相对稳定的服务,我熟悉的就只有 GitHub Actions 了。

效果图效果图

https://wang-guangxin.github.io/sites

Why

A while ago, my Oracle Cloud account in San Jose was banned. I had a 4C 24G ARM instance there running all sorts of things — almost every domain ending in dijk.eu.org was running on it.

Including, of course, the blog’s image server.

So all the blog images went down.

Then I pointed the blog’s image server to my home server. The advantage is fast access from China; the disadvantage is that the home server isn’t stable.

So a natural need arose: monitor the home server to see if it’s online.

But if the monitoring server itself isn’t stable enough, the monitoring becomes meaningless. (Yes, I’m talking about free-riding servers.)

For something free and relatively stable, the only thing I know well is GitHub Actions.

EffectEffect

https://wang-guangxin.github.io/sites

代码

想要无限制的使用 GitHub Actions, 你需要一个公开的仓库。

所以我不得不把这套方案公开,尽管它实际上无比丑陋。

https://github.com/WANG-Guangxin/wang-guangxin.github.io

整体结构

这个图其实已经描述的比较清楚了,GitHub Actions 负责执行定时任务,然后 Shell 脚本负责执行 hexo 命令来生成静态页面,Python 脚本负责检查网站是否在线,然后根据结果来生成 Markdown 文件,以提供给 Hexo 来渲染。

下面就一一介绍这三部分。

GitHub Actions

https://github.com/WANG-Guangxin/wang-guangxin.github.io/blob/master/.github/workflows/main.yml

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
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84

name: Deploy Hexo Site

on:
push:
branches:
- master # Set a branch to trigger deployment
schedule:
- cron: '*/15 * * * *' # 每 15 分钟执行一次
jobs:
build-and-deploy:
runs-on: ubuntu-latest
steps:
- name: Checkout
uses: actions/checkout@v2
with:
# This depth parameter is optional - fetching the full history can improve the accuracy of the GH Pages deployment action's change detection.
# However, it can also increase the time required for the checkout step to complete.
fetch-depth: 0

- name: Use Node.js
uses: actions/setup-node@v2
with:
node-version: '18' # Specify your Node.js version here

- name: Install Dependencies
run: npm install

- name: Install Hexo
run: npm install -g hexo-cli

- name: Build-0
run: bash ./build.sh
env:
notice_host_server: ${{ secrets.notice_host_server }}
notice_user: ${{ secrets.notice_user }}
notice_pwd: ${{ secrets.notice_pwd }}
notice_mail: ${{ secrets.notice_mail }}
notice_receiver: ${{ secrets.notice_receiver }}

- name: Deploying-0
uses: peaceiris/actions-gh-pages@v3
with:
github_token: ${{ secrets.GITHUB_TOKEN }}
publish_dir: ./public

- name: Build-1
run: sleep 300 && bash ./build.sh
env:
notice_host_server: ${{ secrets.notice_host_server }}
notice_user: ${{ secrets.notice_user }}
notice_pwd: ${{ secrets.notice_pwd }}
notice_mail: ${{ secrets.notice_mail }}
notice_receiver: ${{ secrets.notice_receiver }}

- name: Deploying-1
uses: peaceiris/actions-gh-pages@v3
with:
github_token: ${{ secrets.GITHUB_TOKEN }}
publish_dir: ./public

- name: Build-2
run: sleep 300 && bash ./build.sh
env:
notice_host_server: ${{ secrets.notice_host_server }}
notice_user: ${{ secrets.notice_user }}
notice_pwd: ${{ secrets.notice_pwd }}
notice_mail: ${{ secrets.notice_mail }}
notice_receiver: ${{ secrets.notice_receiver }}

- name: Deploying-2
uses: peaceiris/actions-gh-pages@v3
with:
github_token: ${{ secrets.GITHUB_TOKEN }}
publish_dir: ./public

- name: Commit changes
uses: stefanzweifel/git-auto-commit-action@v4
with:
commit_message: Auto Commit
branch: ${{ github.head_ref }}
file_pattern: |
data.csv
siteenv

简单解释一下,为什么这个定时任务设置的是每 15 分钟执行一次,而不是每 5 分钟执行一次。

这是因为 GitHub Actions 的定时任务执行时间不准确,基本都会有延迟,如果设置的是每 5 分钟执行一次,那么实际上会有很多次执行是在 5 分钟之后的,甚至 10 分钟之后的。

另外,在 Actions 的条款里,对每次 Actions 的执行时长限制是 30 分钟。

为了遵守条款的同时让这个监控更加稳定,我采取的方案是每 15 分钟执行一次 Actions,但每次 Actions 会部署 3 次 Hexo 网站,每次间隔 5 分钟,这样操作相对来说执行频率更高一些。

负责生成 Hexo 网站的脚本是 build.sh,这里接收了 5 个环境变量,用于配置邮件通知。

1
2
3
4
5
6
7
8
- name: Build-0
run: bash ./build.sh
env:
notice_host_server: ${{ secrets.notice_host_server }}
notice_user: ${{ secrets.notice_user }}
notice_pwd: ${{ secrets.notice_pwd }}
notice_mail: ${{ secrets.notice_mail }}
notice_receiver: ${{ secrets.notice_receiver }}

Shell 脚本

https://github.com/WANG-Guangxin/wang-guangxin.github.io/blob/master/build.sh

1
2
3
4
5
6
7
8
9
hexo clean # 清理 Hexo 缓存
python3 -m pip install --upgrade pip # 更新 pip
pip install -r requirements.txt # 安装 Python 依赖
python3 uptime.py # 执行 Python 脚本 -- 检查网站是否在线 -- 生成 siteenv 文件
source siteenv # 加载 siteenv 文件
cat siteenv # 打印 siteenv 文件
envsubst < "./template_index.md" > "./source/sites/index.md" # 替换模板文件中的变量
cat ./source/sites/index.md # 打印生成的 Markdown 文件
hexo generate # 生成 Hexo 网站

解释一下 envsubst 这个命令,它的作用是替换模板文件中的变量。

https://github.com/WANG-Guangxin/wang-guangxin.github.io/blob/master/template_index.md

模板 template_index.md 文件中有很多shell变量,这些变量会在 siteenv 文件中被定义。

执行 envsubst < "./template_index.md" > "./source/sites/index.md" 这个命令,就会把 template_index.md 文件中的变量替换成 siteenv 文件中的变量的值,然后生成 index.md 文件。

siteenv 文件的内容由 Python 脚本生成。

https://github.com/WANG-Guangxin/wang-guangxin.github.io/blob/master/siteenv

Python 脚本

https://github.com/WANG-Guangxin/wang-guangxin.github.io/blob/master/uptime.py

这里是整个 Uptime 监控的核心代码了。

全局变量总共四个,分别是 g_config, g_data_file, g_data_list, g_notice_enable

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23

g_config = {
"https://wgxls.site":
{
"status": "STATUS_WGXLS_SITE='",
"uptime7d": "WGXLS_SITE_UP_7='",
"uptime24h": "WGXLS_SITE_UP_24='",
"ssl": "WGXLS_SITE_SSL='",
},
"https://opengrok.dijk.eu.org":
{
"status": "STATUS_OPENGROK_DIJK_EU_ORG='",
"uptime7d": "OPENGROK_DIJK_EU_ORG_UP_7='",
"uptime24h": "OPENGROK_DIJK_EU_ORG_UP_24='",
"ssl": "OPENGROK_DIJK_EU_ORG_SSL='"
}
}

g_data_file = 'data.csv'
g_data_list = []
g_data_list.append([])
g_notice_enable = True

g_config 是一个字典,存储了需要监控的网站的信息。这里面的 key 是网站的域名,value 是一个字典,存储了网站的状态、7 天的在线时间、24 小时的在线时间、是否启用 SSL。

它的Value的value是一个字符串,这个字符串是为了生成一个可以被 Linux Shell 执行 source 的文件。

g_config 是需要和 template_index.md 文件中的变量对应的。

g_data_file 是一个文件名,用于把监控的数据持久化到磁盘,这样每一次执行 Python 脚本时从 g_data_file 中读取以往的数据,来计算 7 天的在线时间和 24 小时的在线时间。

g_data_list 是一个列表,用于在内存中存储监控的数据。

g_notice_enable 是一个布尔值,用于控制是否发送邮件通知。

监控的逻辑被我写成了一个纯面向过程的逻辑。

1
2
3
4
5
6
7
8
9
10
def main():
read_csv_to_list() # 读取 data.csv 文件到 g_data_list
remove_data_before_seven_days() # 删除 7 天之前的数据
for key, value in g_config.items(): # 遍历 g_config
check_url(key) # 检查网站是否在线 数据存储在 g_data_list 同时更新 g_config
calc_uptime() # 根据 g_data_list 计算 7 天的在线时间和 24 小时的在线时间 同时更新 g_config
write_list_to_csv() # 把 g_data_list 写入 data.csv
write_env() # 根据 g_config 生成 siteenv 文件
if g_notice_enable: # 如果 g_notice_enable 为 True
do_notice() # 发送邮件通知

网站状态发生改变的话,会发送邮件通知。

有兴趣的话可以看看完整的代码。

Star 一下也行

The Code

To use GitHub Actions without limits, you need a public repository.

So I had to make this solution public, even though it’s extremely ugly.

https://github.com/WANG-Guangxin/wang-guangxin.github.io

Overall Structure

This diagram is actually quite clear: GitHub Actions is responsible for running scheduled tasks, the Shell script executes hexo commands to generate static pages, and the Python script checks whether websites are online and generates a Markdown file based on the results for Hexo to render.

Below I’ll introduce these three parts one by one.

GitHub Actions

https://github.com/WANG-Guangxin/wang-guangxin.github.io/blob/master/.github/workflows/main.yml

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
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84

name: Deploy Hexo Site

on:
push:
branches:
- master # Set a branch to trigger deployment
schedule:
- cron: '*/15 * * * *' # Run every 15 minutes
jobs:
build-and-deploy:
runs-on: ubuntu-latest
steps:
- name: Checkout
uses: actions/checkout@v2
with:
# This depth parameter is optional - fetching the full history can improve the accuracy of the GH Pages deployment action's change detection.
# However, it can also increase the time required for the checkout step to complete.
fetch-depth: 0

- name: Use Node.js
uses: actions/setup-node@v2
with:
node-version: '18' # Specify your Node.js version here

- name: Install Dependencies
run: npm install

- name: Install Hexo
run: npm install -g hexo-cli

- name: Build-0
run: bash ./build.sh
env:
notice_host_server: ${{ secrets.notice_host_server }}
notice_user: ${{ secrets.notice_user }}
notice_pwd: ${{ secrets.notice_pwd }}
notice_mail: ${{ secrets.notice_mail }}
notice_receiver: ${{ secrets.notice_receiver }}

- name: Deploying-0
uses: peaceiris/actions-gh-pages@v3
with:
github_token: ${{ secrets.GITHUB_TOKEN }}
publish_dir: ./public

- name: Build-1
run: sleep 300 && bash ./build.sh
env:
notice_host_server: ${{ secrets.notice_host_server }}
notice_user: ${{ secrets.notice_user }}
notice_pwd: ${{ secrets.notice_pwd }}
notice_mail: ${{ secrets.notice_mail }}
notice_receiver: ${{ secrets.notice_receiver }}

- name: Deploying-1
uses: peaceiris/actions-gh-pages@v3
with:
github_token: ${{ secrets.GITHUB_TOKEN }}
publish_dir: ./public

- name: Build-2
run: sleep 300 && bash ./build.sh
env:
notice_host_server: ${{ secrets.notice_host_server }}
notice_user: ${{ secrets.notice_user }}
notice_pwd: ${{ secrets.notice_pwd }}
notice_mail: ${{ secrets.notice_mail }}
notice_receiver: ${{ secrets.notice_receiver }}

- name: Deploying-2
uses: peaceiris/actions-gh-pages@v3
with:
github_token: ${{ secrets.GITHUB_TOKEN }}
publish_dir: ./public

- name: Commit changes
uses: stefanzweifel/git-auto-commit-action@v4
with:
commit_message: Auto Commit
branch: ${{ github.head_ref }}
file_pattern: |
data.csv
siteenv

Let me briefly explain why this scheduled task runs every 15 minutes instead of every 5 minutes.

That’s because GitHub Actions’ scheduled tasks aren’t precise in timing — there’s basically always a delay. If set to every 5 minutes, many executions would actually happen 5+ minutes late, or even 10+ minutes late.

Also, in Actions’ terms of service, each Actions run is limited to 30 minutes.

To comply with the terms while making the monitoring more stable, my approach is to run Actions every 15 minutes, but each run deploys the Hexo site 3 times, 5 minutes apart, so the effective monitoring frequency is higher.

The script responsible for generating the Hexo site is build.sh, which receives 5 environment variables for configuring email notifications.

1
2
3
4
5
6
7
8
- name: Build-0
run: bash ./build.sh
env:
notice_host_server: ${{ secrets.notice_host_server }}
notice_user: ${{ secrets.notice_user }}
notice_pwd: ${{ secrets.notice_pwd }}
notice_mail: ${{ secrets.notice_mail }}
notice_receiver: ${{ secrets.notice_receiver }}

Shell Script

https://github.com/WANG-Guangxin/wang-guangxin.github.io/blob/master/build.sh

1
2
3
4
5
6
7
8
9
hexo clean # Clean Hexo cache
python3 -m pip install --upgrade pip # Update pip
pip install -r requirements.txt # Install Python dependencies
python3 uptime.py # Run the Python script -- check if websites are online -- generate the siteenv file
source siteenv # Load the siteenv file
cat siteenv # Print the siteenv file
envsubst < "./template_index.md" > "./source/sites/index.md" # Replace variables in the template file
cat ./source/sites/index.md # Print the generated Markdown file
hexo generate # Generate the Hexo site

Let me explain the envsubst command: it replaces variables in the template file.

https://github.com/WANG-Guangxin/wang-guangxin.github.io/blob/master/template_index.md

The template template_index.md has many shell variables, which are defined in the siteenv file.

Running envsubst < "./template_index.md" > "./source/sites/index.md" replaces the variables in template_index.md with the values from the siteenv file, generating the index.md file.

The content of the siteenv file is generated by the Python script.

https://github.com/WANG-Guangxin/wang-guangxin.github.io/blob/master/siteenv

Python Script

https://github.com/WANG-Guangxin/wang-guangxin.github.io/blob/master/uptime.py

This is the core code of the entire Uptime monitoring.

There are four global variables in total: g_config, g_data_file, g_data_list, and g_notice_enable.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23

g_config = {
"https://wgxls.site":
{
"status": "STATUS_WGXLS_SITE='",
"uptime7d": "WGXLS_SITE_UP_7='",
"uptime24h": "WGXLS_SITE_UP_24='",
"ssl": "WGXLS_SITE_SSL='",
},
"https://opengrok.dijk.eu.org":
{
"status": "STATUS_OPENGROK_DIJK_EU_ORG='",
"uptime7d": "OPENGROK_DIJK_EU_ORG_UP_7='",
"uptime24h": "OPENGROK_DIJK_EU_ORG_UP_24='",
"ssl": "OPENGROK_DIJK_EU_ORG_SSL='"
}
}

g_data_file = 'data.csv'
g_data_list = []
g_data_list.append([])
g_notice_enable = True

g_config is a dictionary storing the info of the websites to monitor. The key is the website’s domain, and the value is a dictionary storing the site’s status, 7-day uptime, 24-hour uptime, and whether SSL is enabled.

The value’s value is a string, designed to generate a file that can be sourced by a Linux shell.

g_config must correspond to the variables in template_index.md.

g_data_file is a filename used to persist the monitoring data to disk, so each time the Python script runs, it reads the past data from g_data_file to calculate the 7-day and 24-hour uptime.

g_data_list is a list used to store the monitoring data in memory.

g_notice_enable is a boolean controlling whether to send email notifications.

The monitoring logic is written in a purely procedural style.

1
2
3
4
5
6
7
8
9
10
def main():
read_csv_to_list() # Read data.csv into g_data_list
remove_data_before_seven_days() # Remove data older than 7 days
for key, value in g_config.items(): # Iterate over g_config
check_url(key) # Check if the website is online; data stored in g_data_list, also update g_config
calc_uptime() # Calculate 7-day and 24-hour uptime from g_data_list, also update g_config
write_list_to_csv() # Write g_data_list to data.csv
write_env() # Generate the siteenv file from g_config
if g_notice_enable: # If g_notice_enable is True
do_notice() # Send email notification

When a website’s status changes, an email notification is sent.

If you’re interested, you can look at the full code.

A star would be nice too