普通视图

Received before yesterday

利用Python实现Hexo站点的持续集成

2024年10月15日 20:00

引言

Hexo博客编写完文章需要从头构建并重新上传生成的静态文件到服务器,不能增量更新十分不便,每次上传的文件也越来越大,于是一个比较好的解决办法是在提交文章、资源和配置后,让服务器自动去从git远程仓库拉取最新的提交到服务器上的本地git仓库中,并部署。使用Jenkins等持续集成工具比较繁琐也消耗资源,于是我选择用python编写一个基于flask的webhook脚本来实现。

具体实现流程是:文章提交到git代码托管平台的远程仓库后,远程仓库发送HTTP回调请求给服务器上的webhook脚本,脚本程序根据回调请求的参数,先调用git拉取远程仓库最新提交内容,再去调用npm构建和部署最新版hexo博客到nginx下。

这里的git代码托管平台我选择Gitee:https://gitee.com/

1. 安装pip和python

首先,确保你已经安装了python。如果没有安装,可以使用以下命令来安装python和pip:

1.1 检查python版本

python3 --version

如果你已经安装了python 3.x版本,可以跳过安装python的步骤。否则,继续安装:

1.2 安装python3

sudo yum install python3 -y  # 适用于 CentOS 或其他 RHEL 系统

1.3 安装pip

安装pip的方法:

sudo yum install python3-pip -y  # CentOS/RHEL 系统

安装完成后,确认pip是否已经成功安装:

pip3 --version

2. 使用pip安装依赖

一旦 pip 安装好,你可以使用以下命令来安装需要的库:

pip3 install flask gitpython

3.编写脚本

vim webhook.py
import osimport subprocessfrom flask import Flask, request, jsonifyimport gitapp = Flask(__name__)# 配置你的本地仓库路径和构建命令REPO_PATH = "/path/to/your/hexo/blog"PUBLIC_PATH = os.path.join(REPO_PATH, 'public')# 拉取代码的函数def pull_code():    try:        repo = git.Repo(REPO_PATH)        origin = repo.remotes.origin        origin.pull()        return True    except Exception as e:        print(f"Failed to pull code: {e}")        return False# 构建 Hexo 站点的函数def build_hexo():    try:        # 执行 Hexo 命令        subprocess.run(["npm", "run", "build"], cwd=REPO_PATH, check=True)                return True    except subprocess.CalledProcessError as e:        print(f"Failed to build Hexo: {e}")        return False@app.route("/webhook", methods=["POST"])def webhook():    # 验证请求是否来自 Gitee    if request.headers.get("X-Gitee-Token") != "": #这里改成你设置的密码        return jsonify({"message": "Unauthorized"}), 401    # 获取事件类型,确保是 push 事件    event = request.headers.get("X-Gitee-Event")    if event != "Push Hook":        return jsonify({"message": "Not a push event"}), 400    # 拉取代码并构建    if pull_code() and build_hexo():        return jsonify({"message": "Hexo build success"}), 200    else:        return jsonify({"message": "Failed to pull or build"}), 500if __name__ == "__main__":    app.run(host="0.0.0.0", port=5000)

代码优化,加入线程控制,防止webhook链接被并发调用后,两个hook任务线程同时执行出现安全问题。

import osimport subprocessfrom flask import Flask, request, jsonifyimport gitimport threadingapp = Flask(__name__)# 配置你的本地仓库路径和构建命令REPO_PATH = "/blog"PUBLIC_PATH = os.path.join(REPO_PATH, 'public')lock = threading.Lock()is_building = False  # 标志位,用于指示是否有任务正在进行# 拉取代码的函数def pull_code():    try:        repo = git.Repo(REPO_PATH)        origin = repo.remotes.origin        origin.pull()        return True    except Exception as e:        print(f"Failed to pull code: {e}")        return False# 构建 Hexo 站点的函数def build_hexo():    try:        # 执行 Hexo 的清理和生成命令        subprocess.run(["npm", "run", "build"], cwd=REPO_PATH, check=True)        #subprocess.run(["hexo", "generate"], cwd=REPO_PATH, check=True)        return True    except subprocess.CalledProcessError as e:        print(f"Failed to build Hexo: {e}")        return False@app.route("/webhook", methods=["POST"])def webhook():    global is_building    # 验证请求是否来自 Gitee    if request.headers.get("X-Gitee-Token") != "":        return jsonify({"message": "Unauthorized"}), 401    # 获取事件类型,确保是 push 事件    event = request.headers.get("X-Gitee-Event")    if event != "Push Hook":        return jsonify({"message": "Not a push event"}), 400    if is_building:        return jsonify({"message": "Build in progress, try again later"}), 429    with lock:        is_building = True  # 设置标志位为 True,表示任务开始        try:            # 拉取代码并构建            if pull_code() and build_hexo():                return jsonify({"message": "Hexo build success"}), 200            else:                return jsonify({"message": "Failed to pull or build"}), 500        finally:            is_building = False  # 重置标志位,表示任务结束            if __name__ == "__main__":    app.run(host="0.0.0.0", port=5000)

代码还可以进一步优化,只对master分支提交推送进行触发构建。

import osimport subprocessfrom flask import Flask, request, jsonifyimport gitimport threadingapp = Flask(__name__)# 配置你的本地仓库路径和构建命令REPO_PATH = "/blog"PUBLIC_PATH = os.path.join(REPO_PATH, 'public')lock = threading.Lock()is_building = False  # 标志位,用于指示是否有任务正在进行# 拉取代码的函数def pull_code():    try:        repo = git.Repo(REPO_PATH)        origin = repo.remotes.origin        origin.pull()        return True    except Exception as e:        print(f"Failed to pull code: {e}")        return False# 构建 Hexo 站点的函数def build_hexo():    try:        # 执行 Hexo 的清理和生成命令        subprocess.run(["npm", "run", "build"], cwd=REPO_PATH, check=True)        #subprocess.run(["hexo", "generate"], cwd=REPO_PATH, check=True)        return True    except subprocess.CalledProcessError as e:        print(f"Failed to build Hexo: {e}")        return False@app.route("/webhook", methods=["POST"])def webhook():    global is_building    # 验证请求是否来自 Gitee    if request.headers.get("X-Gitee-Token") != "":        return jsonify({"message": "Unauthorized"}), 401    # 获取事件类型,确保是 push 事件    event = request.headers.get("X-Gitee-Event")    if event != "Push Hook":        return jsonify({"message": "Not a push event"}), 400    # 解析推送数据    payload = request.get_json()    if not payload:        return jsonify({"message": "Invalid payload"}), 400    # 检查是否是 master 分支的推送    ref = payload.get("ref")    if ref != "refs/heads/master":        return jsonify({"message": "Not a master branch push, ignored"}), 200    if is_building:        return jsonify({"message": "Build in progress, try again later"}), 429    with lock:        is_building = True  # 设置标志位为 True,表示任务开始        try:            # 拉取代码并构建            if pull_code() and build_hexo():                return jsonify({"message": "Hexo build success"}), 200            else:                return jsonify({"message": "Failed to pull or build"}), 500        finally:            is_building = False  # 重置标志位,表示任务结束            if __name__ == "__main__":    app.run(host="0.0.0.0", port=5000)

4.执行脚本

执行前,服务器还需要安装好node、npm、git并配置好环境变量

nohup python3 webhook.py &

5.配置hook到gitee

设置好签名(密码),设置回调地址,勾选两项

除此外,还要将自己gitee账户相匹配的ssh密钥设置在服务器的上用于拉取我们私有仓库的内容。

基于Hexo实现一个静态的个人博客

2024年9月20日 00:00

引言

Hexo是中国台湾开发者Charlie在2012年创建的一个开源项目,旨在提供一个简单、快速且易于扩展的静态博客生成器。

Hexo的设计理念是轻量级、易用和支持插件扩展,因此它非常适合那些有技术背景的用户,尤其是喜欢使用Markdown和Git进行内容管理的开发者。Hexo使用Node.js构建,并且支持通过主题和插件来扩展功能。

1.初始化Hexo

Hexo基于node.js,所以首先要安装node和npm

1.全局安装hexo

npm install hexo -g

2.生成hexo项目

安装完成后,选择一个目录,执行以下命令,生成一个hexo脚手架

hexo init myblog

然后生成脚手架结构如下

myblog/ ├── node_modules ├── scaffolds │── scripts    ├── source/ │    ├── _posts │    └── _drafts ├── themes ├── _config.landscape.yml ├── _config.yml └── package.json

文件目录解释

  • node_modules不解释
  • scaffolds 存放博客内容模板的地方
  • source 资源文件夹,下面用来放建站需要的各种资源,包括markdown格式的博客原稿,图片,css,js,robots.txt等等
  • source/_posts 博客的”源码”,里面放我们写作的markdown文件,会被hexo引擎按照一定规则转换成html页面
  • source/_drafts 草稿,不适合放在_posts里面打包发布的草稿就放在这里
  • themes 存放第三方主题
  • _config.yml hexo配置文件
  • _config.landscape.yml 默认主题landscape的配置文件
  • package.json不解释

提前剧透:hexo使用过程中,还会经常用到两个文件夹

  • scripts hexo自定义脚本,在构建过程中执行,用于扩展hexo的功能或实现一些特殊需求
  • public 构建生成的静态网站,可以直接使用nginx root反代访问

3.启动预览

修改_config.yml配置文件

# Sitetitle: '我的博客'subtitle: '我的技术博客'description: '分享一些实用的东西'author: 'liuzijian'language: zh-CNtimezone: 'Asia/Shanghai'

然后,运行package.json里面的目标npm run server

直到输出提示Hexo is running at http://localhost:4000/ . Press Ctrl+C to stop.以后,访问http://127.0.0.1:4000进行预览,浏览器出现这个界面说明启动成功

4.构建打包

运行npm run build后,根目录就会生成一个public文件夹,就是构建成的静态网站,上传到服务器并使用nginx代理,访问/index.html即可打开博客首页。

除了部署到自己的服务器,还可以通过VercelGitHub Pages等进行部署。

2.整合主题Fluid

hexo默认自带一个叫landscape的主题,根目录下的_config.landscape.yml就是它的配置文件,landscape比较简陋,也不美观,所以很多人选择美观且功能强大的第三方主题,在这里,我使用主题fluid来建站。

集成第三方主题到hexo中非常简单,只需要将主题解压放在theme文件夹,复制主题的配置文件到根目录下,然后到hexo配置文件内切换主题即可。

1.下载主题fluid

项目首页和文档地址: https://hexo.fluid-dev.com/

下载地址: https://github.com/fluid-dev/hexo-theme-fluid/releases

找到最新的1.9.8版本下载连接

https://github.com/fluid-dev/hexo-theme-fluid/archive/refs/tags/v1.9.8.zip

2.集成主题

将压缩包下载下来,解压,压缩包内文件夹名字叫hexo-theme-fluid-1.9.8,这个就是主题,不过要把解压后的hexo-theme-fluid-1.9.8文件夹重命名为fluid,紧接着要再将fluid文件夹里面的_config.yml文件复制一个副本,将副本重命名为_config.fluid.yml,再将_config.fluid.yml剪切到hexo项目根目录下和hexo脚手架自带的的_config.yml文件放在一起,接下来还要再把整个fluid文件夹拷贝到根目录下的themes文件夹内。

⚠️注意: 主题文件夹内的_config.yml一定要创建副本,而不是直接改名,副本_config.fluid.yml剪切走后,原有的_config.yml文件必须原样保留! hexo-theme-fluid-1.9.8文件夹下的_config.yml是主题的配置,与脚手架生成在根目录的hexo配置文件_config.yml是不同的,重命名后的主题配置文件_config.fluid.yml和hexo脚手架自带的配置文件_config.yml放在一起共同作为hexo的配置文件,其他的第三方主题基本也遵循这个方式。关于配置文件的更多解释可以参考Hexo官方文档以及主题的官方文档。
实际上和根目录下脚手架自带的_config.landscape.yml文件是一样的道理,现在换成了别的主题,_config.landscape.yml也就没用了,可以直接删除

myblog/    ├── ... ...    ├── themes    │     └── fluid    ├── _config.fluid.yml    ├── _config.yml    └── ... ...

要想主题生效,还需要在配置文件进行指定,修改_config.yml,找到以下配置

# Extensions## Plugins: https://hexo.io/plugins/## Themes: https://hexo.io/themes/theme: landscape

修改为以下,然后保存

# Extensions## Plugins: https://hexo.io/plugins/## Themes: https://hexo.io/themes/theme: fluid

再次启动npm run server,浏览器预览博客的效果已经变为主题的风格,即为集成主题成功

主题更多玩法,在主题的官网都有文档说明,主题配置文件_config.fluid.yml内也有详细注释,这里不再详细一一介绍,集成其他主题的方式方法同理。

3.部署评论系统Waline

评论系统一般由主题提供支持,支持的评论系统有许许多多,这里我使用的是Waline,并且采用Docker独立部署Waline

Waline的使用部署等可参考官网,其官网地址为: https://waline.js.org/,源码位于GitHub: https://github.com/walinejs/waline.git

1.构建镜像

先用git clone将源码下载到本地,然后进入对应目录,构建镜像。

docker build -t lizheming/waline -f packages/server/Dockerfile .

2.启动Waline服务

使用参数启动镜像,参数根据Waline指定的服务端运行参数而来,这里采用MySQL作为数据存储为例,常见参数有:

  • OAUTH_URL 变量是因为oauth服务也是我自己私有部署的,如果使用waline公共的则不用这个变量
  • IPQPS IP评论频次限制
  • MYSQL_DB 数据库名,使用MySQL部署,需要提前导入数据库脚本,详情查阅waline官网文档
  • MYSQL_USER 不解释
  • MYSQL_PASSWORD 不解释
  • MYSQL_PORT 不解释
  • SERVER_URL 访问waline系统时的地址前缀,因为docker部署由nginx反代,建议设置为反代后的地址
  • COMMENT_AUDIT 评论审核,布尔值
  • SITE_NAME 邮件中展示的网站名
  • SITE_URL 邮件中展示的网站地址
  • AUTHOR_EMAIL 发送者邮件地址
  • SMTP_PASS 邮件服务密码
  • SMTP_USER 邮件用户名
  • SMTP_SERVICE 邮件提供商,具体列表可见官方文档

启动命令示例:

docker run -d \-e OAUTH_URL=https://oauth.liuzijian.com \-e IPQPS=10 \-e MYSQL_DB=waline \-e MYSQL_USER=****** \-e MYSQL_PASSWORD=********** \-e MYSQL_PORT=3306 \-e SERVER_URL=https://waline.liuzijian.com \-e COMMENT_AUDIT=true \-e SITE_NAME="Liu Zijian's Blog"  \-e SITE_URL=https://blog.liuzijian.com  \-e SMTP_PASS=****************  \-e AUTHOR_EMAIL=****@foxmail.com  \-e SMTP_USER=******@foxmail.com  \-e SMTP_SERVICE=QQ  \-p 8360:8360 \--network=host \lizheming/waline

有时,我们选取的服务器配置很低,运行一个MySQL后,内存资源可能就所剩无几,而且我们的个人博客可能初期影响力也不是那么大,评论寥寥无几,使用MySQL等数据库就性价比很低了,因此可以先换成SQLite数据库,SQLite将数据保存在单文件,使用代码本地调用SQLite的API方法实现增删改查,对于初期的评论功能足够用了,使用下面命令就可以启动一个采用SQLite数据库的Waline,Waline官方已经为我们准备好了一个空的waline.sqlite数据文件,里面初始化好了库和表,直接按照官方文档指定的地址下载即可。

  • SQLITE_PATH 本地数据库文件路径,指定到文件夹,不是文件
  • JWT_TOKEN Waline官方要求,我也不知道为什么
docker run -d \-e IPQPS=10 \-e SQLITE_PATH=/opt/sqlite \-e JWT_TOKEN=******** \-e SERVER_URL=https://waline.liuzijian.com \-e COMMENT_AUDIT=true \-e SITE_NAME="Liu Zijian's Blog"  \-e SITE_URL=https://blog.liuzijian.com  \-e SMTP_PASS=****************  \-e AUTHOR_EMAIL=****@foxmail.com  \-e SMTP_USER=******@foxmail.com  \-e SMTP_SERVICE=QQ  \-p 8360:8360 \-v /opt/sqlite_data:/opt/sqlite  \--network=host \lizheming/waline

怎样在fluid主题上使用waline评论,主题文档有说明,不再赘述。

4.采用Nginx部署

页面和评论插件都可以通过nginx根据不同域名进行反代访问,同时https证书也是集成在nginx上的。

主要配置如下:

server {    listen       80;    listen       [::]:80;    server_name  *.liuzijian.com;    return 301 https://$host$request_uri;}## 静态页面server {    listen 443 ssl;    server_name blog.liuzijian.com ;    # SSL 证书路径,按照实际情况填写    ssl_certificate /xxxxx/domain.cert;    ssl_certificate_key /xxxxxx/domain.key;    # 推荐的 SSL 配置    ssl_protocols TLSv1.2 TLSv1.3;    ssl_ciphers HIGH:!aNULL:!MD5;    location / {        try_files $uri $uri/ $uri.html   =404;        root /opt/blog/public;    }    error_page 404 /404.html;}## 评论插件server {    listen 443 ssl;    server_name    waline.liuzijian.com;    # SSL 证书路径,按照实际情况填写    ssl_certificate /xxxxx/domain.cert;    ssl_certificate_key /xxxxxx/domain.key;    # 推荐的 SSL 配置    ssl_protocols TLSv1.2 TLSv1.3;    ssl_ciphers HIGH:!aNULL:!MD5;    location / {        proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;        proxy_set_header Host $host;        proxy_set_header X-Real-IP $remote_addr;        proxy_pass http://127.0.0.1:8360;    }}    ## 自动部署hook,选用server {    listen 443 ssl;    server_name hook.liuzijian.com;    # SSL 证书路径,按照实际情况填写    ssl_certificate /xxxxx/domain.cert;    ssl_certificate_key /xxxxxx/domain.key;    # 推荐的 SSL 配置    ssl_protocols TLSv1.2 TLSv1.3;    ssl_ciphers HIGH:!aNULL:!MD5;    # 代理到其他端口    location / {        proxy_pass http://127.0.0.1:5000;    }}

Hexo站点可以采用持续集成的方式部署在服务器,参考:利用Python实现Hexo的持续集成

❌