目标:通过 Python 脚本自动生成 Hexo 静态文件,并通过 SFTP 免密上传到服务器,支持单服务器部署,后续可扩展多服务器。

一、前置条件

  1. 本地环境

    • 已安装 Python 3.6+(推荐 3.9+,下载地址:Python 官网
    • 已安装 Hexo 并初始化博客(参考 Hexo 官方文档
    • 博客目录结构中包含 public 文件夹(Hexo 生成的静态文件存放目录)
  2. 服务器环境

    • 一台 Linux 服务器(如 CentOS、Ubuntu),已安装 OpenSSH 服务(默认已安装)
    • 服务器上已配置网站根目录(如 /opt/1panel/apps/openresty/.../public
    • 服务器 IP、SSH 端口(默认 22)、登录用户名(如 root)

二、配置 SSH 免密登录(核心步骤)

通过 SSH 密钥对实现本地与服务器的免密通信,避免每次上传输入密码。

1. 本地生成 ED25519 密钥对

ED25519 是更安全的密钥算法,推荐优先使用。

  • 打开本地 Windows 命令提示符(CMD)或 PowerShell,执行以下命令:
# 生成密钥对,替换为你的私钥保存路径(建议放在 .ssh 目录下)
ssh-keygen -t ed25519 -f C:/Users/你的用户名/.ssh/hexo_deploy_key -N ""
- 解释:
    - `-t ed25519`:指定密钥算法为 ED25519
    - `-f 路径`:私钥保存路径(如 `C:/Users/Administrator/.ssh/hexo_deploy_key`)
    - `-N ""`:设置空密码(免密登录)
  • 执行后会生成两个文件:

    • hexo_deploy_key:私钥(本地保存,不可泄露)
    • hexo_deploy_key.pub:公钥(需要上传到服务器)

2. 将公钥上传到服务器

让服务器信任本地私钥,实现免密登录:

  • 方法 1:手动复制(推荐)
  1. 本地查看公钥内容:
type C:/Users/你的用户名/.ssh/hexo_deploy_key.pub

复制输出的完整内容(以 ssh-ed25519 开头的一整行)。

2. 登录服务器(用密码登录):
ssh 用户名@服务器IP  # 如 ssh root@103.207.68.122
3. 服务器端创建授权文件并添加公钥:
# 确保 .ssh 目录存在并设置权限
mkdir -p ~/.ssh && chmod 700 ~/.ssh
# 创建授权文件并添加公钥(粘贴本地复制的公钥内容)
 echo "粘贴的公钥内容" >> ~/.ssh/authorized_keys
# 设置文件权限(必须为 600,否则 SSH 会拒绝)
 chmod 600 ~/.ssh/authorized_keys
  • 验证免密登录
    本地执行以下命令,若无需输入密码即可登录服务器,则配置成功:
ssh -i C:/Users/你的用户名/.ssh/hexo_deploy_key 用户名@服务器IP

三、部署脚本配置

使用支持 SFTP 协议的 Python 脚本,实现 “本地生成静态文件 + 自动上传服务器” 一键操作。

1. 下载脚本

创建 deploy.py 文件,复制以下代码(已适配 ED25519 密钥,支持单服务器):

Hexo 自动部署脚本

import os
import paramiko
import subprocess
from paramiko.ssh_exception import SSHException

# --------------------------
# 服务器配置(根据实际情况修改)
# --------------------------
SERVERS = [
    {
        "name": "主服务器",  # 服务器名称(用于日志区分)
        "ip": "103.207.68.122",  # 替换为你的服务器IP
        "port": 22,  # SSH端口(默认22)
        "user": "root",  # 服务器登录用户名
        "site_dir": "/opt/1panel/apps/openresty/openresty/www/sites/blog.mzxi.cn/index/public",  # 服务器网站根目录
        "private_key": "C:/Users/Administrator/.ssh/hexo_deploy_key"  # 本地私钥路径
    }
    # 如需多服务器,取消下面注释并修改配置
    # ,{
    #     "name": "备用服务器",
    #     "ip": "服务器IP",
    #     "port": 22,
    #     "user": "root",
    #     "site_dir": "/var/www/blog",
    #     "private_key": "C:/Users/Administrator/.ssh/hexo_deploy_key"
    # }
]

# 本地 Hexo 静态文件目录(默认 ./public,无需修改)
LOCAL_PUBLIC_DIR = "./public"

def run_command(cmd, description):
    """执行本地命令(生成 Hexo 静态文件)"""
    print(f"\n=== {description} ===")
    try:
        result = subprocess.run(
            cmd,
            shell=True,
            stdout=subprocess.PIPE,
            stderr=subprocess.STDOUT,
            text=True,
            encoding="utf-8",
            errors="ignore"
        )
        if result.stdout:
            print("成功日志(关键):", result.stdout.strip()[-500:])
        print(f"✅ {description} 完成")
        return True
    except Exception as e:
        print(f"❌ {description} 失败!错误:{str(e)}")
        input("\n按任意键退出...")
        return False

def upload_to_server(server):
    """上传文件到单个服务器"""
    print(f"\n===== 开始上传到【{server['name']}】=====")
    try:
        # 加载 ED25519 私钥
        try:
            private_key = paramiko.Ed25519Key.from_private_key_file(server["private_key"])
            print(f"✅ 【{server['name']}】私钥加载成功")
        except Exception as key_err:
            print(f"❌ 【{server['name']}】私钥加载失败:{str(key_err)}")
            return False
        
        # 建立 SFTP 连接
        try:
            transport = paramiko.Transport((server["ip"], server["port"]))
            transport.connect(username=server["user"], pkey=private_key)
            sftp = paramiko.SFTPClient.from_transport(transport)
            print(f"✅ 【{server['name']}】SFTP 连接成功")
        except SSHException as ssh_err:
            print(f"❌ 【{server['name']}】SSH 连接失败:{str(ssh_err)}")
            return False

        # 确保服务器网站目录存在
        try:
            sftp.stat(server["site_dir"])
        except FileNotFoundError:
            print(f"⚠️ 【{server['name']}】创建目录:{server['site_dir']}")
            sftp.mkdir(server["site_dir"])

        # 递归上传文件(全量覆盖)
        def upload_dir(local_path, remote_path):
            for item in os.listdir(local_path):
                local_item = os.path.join(local_path, item)
                remote_item = f"{remote_path}/{item}"
                
                if os.path.isdir(local_item):
                    # 处理子目录
                    try:
                        sftp.stat(remote_item)
                    except FileNotFoundError:
                        sftp.mkdir(remote_item)
                    upload_dir(local_item, remote_item)
                else:
                    # 上传文件
                    sftp.put(local_item, remote_item)
                    if upload_dir.counter % 10 == 0:
                        print(f"📤 【{server['name']}】已上传 {upload_dir.counter} 个文件...")
                    upload_dir.counter += 1
        upload_dir.counter = 1
        upload_dir(LOCAL_PUBLIC_DIR, server["site_dir"])

        # 关闭连接
        sftp.close()
        transport.close()
        print(f"✅ 【{server['name']}】上传完成(共 {upload_dir.counter-1} 个文件)")
        return True

    except Exception as e:
        print(f"❌ 【{server['name']}】上传失败:{str(e)}")
        return False

if __name__ == "__main__":
    print("===== Hexo 部署流程开始 =====")
    
    # 1. 本地生成静态文件(hexo clean + generate)
    if not run_command("hexo clean && hexo generate", "本地生成静态文件"):
        exit(1)
    
    # 2. 上传到所有配置的服务器(当前仅主服务器)
    all_success = True
    for server in SERVERS:
        if not upload_to_server(server):
            all_success = False
    
    # 3. 输出最终结果
    if all_success:
        print("\n===== 部署成功!网站已更新 =====")
    else:
        print("\n===== 部署失败,请查看日志 =====")
    input("按任意键退出...")

2. 修改配置项

打开 deploy.py,根据你的实际环境修改以下参数:

  • SERVERS 列表中的 ip:服务器公网 IP
  • site_dir:服务器上存放博客静态文件的目录(如 1Panel 或 Nginx 配置的网站根目录)
  • private_key:本地私钥 hexo_deploy_key 的绝对路径(如 C:/Users/Administrator/.ssh/hexo_deploy_key

四、安装依赖并运行脚本

1. 安装 Python 依赖

脚本需要 paramiko 库实现 SFTP 功能,本地执行:

# 打开 CMD,进入博客目录
cd E:/website/hexo/blog  # 替换为你的博客目录
# 安装依赖
pip install paramiko

2.运行部署脚本

# 在博客目录下执行
python deploy.py

3. 成功标志

脚本输出以下内容即表示部署成功:

===== Hexo 部署流程开始 =====

=== 本地生成静态文件 ===
...(Hexo 生成日志)
✅ 本地生成静态文件 完成

===== 开始上传到【主服务器】=====
✅ 【主服务器】私钥加载成功
✅ 【主服务器】SFTP 连接成功
📤 【主服务器】已上传 10 个文件...
...(中间进度)
✅ 【主服务器】上传完成(共 170 个文件)

===== 部署成功!网站已更新 =====
按任意键退出...

五、常见问题排查

  1. “ModuleNotFoundError: No module named ‘paramiko’”
    → 未安装依赖,执行 pip install paramiko 即可。

  2. “私钥加载失败” 或 “unpack requires a buffer of 4 bytes”
    → 私钥路径错误或格式不对:

    • 检查 private_key 路径是否正确(区分 / 和 \,建议用 /)。
    • 确保私钥是 ED25519 类型(开头为 -----BEGIN OPENSSH PRIVATE KEY-----)。
  3. “SSH 连接失败” 或 “Connection refused”
    → 服务器 IP、端口错误,或 SSH 服务未启动:

    • 验证服务器 IP 和端口是否可通(用 ping 服务器IP 测试网络)。
    • 服务器端执行 systemctl status sshd 确认 SSH 服务已启动。
  4. “服务器目录不存在”
    → 脚本会自动创建目录,若创建失败(权限不足),需手动在服务器创建目录并设置权限:

mkdir -p /opt/1panel/apps/openresty/.../public  # 替换为你的 site_dir
chmod 755 /opt/1panel/apps/openresty/.../public

六、扩展说明

  • 多服务器部署:取消 SERVERS 列表中第二个服务器的注释,修改对应参数即可。
  • 增量上传:如需只上传修改的文件,可在 upload_dir 函数中添加文件修改时间对比逻辑(参考前文)。
  • 自动化优化:可将脚本添加到 Hexo 部署钩子(如 hexo deploy 命令),实现更无缝的流程。

通过以上步骤,即可实现 Hexo 博客的一键生成与部署,无需手动上传文件,大幅提升更新效率。