前言
之前,我一直想要搞一个自动化的东西,把静态网页改成半动态的,但是我发现,实现这一步非常的鸡肋。我的原本想法是,我以后更新内容的话,只输入标题和内容,就会自动相应的产生新的md文件。这个功能实现其实意义不大,然后就是实现起来很麻烦。就是解决了文件多,不想手动创建文件的问题。后面干脆把重点放到自动化部署上了。
自动化部署脚本
思路
这个脚本我是用字节的ai编程trae来写的的。我的思路是:先把文件打包,然后找到打包dist文件,然后上传。后面又进行了代码优化,就是更新了进度条,和上传速度。就是先压缩dist文件夹,然后上传到服务器,再解压。这样上传速度会快一点。
遇到问题
反正不知道为什么npm我添加到了环境变量,在执行脚本的时候,提示我npm命令不存在。但是我在命令行中直接执行npm命令,是可以的。只能写入详细npm.cmd的路径了
脚本源码
脚本源码
python
import subprocess
import time
import shutil
import os
import zipfile
from datetime import datetime
from tqdm import tqdm # 用于显示进度条
# ================= 配置区域 =================
# 服务器信息
SERVER_IP = '81.69.*.*' #你的ip地址
SERVER_PORT = 22 #端口号
SERVER_USER = 'root' # 用户名
SERVER_PASSWORD = 'password' # 密码或者使用SSH密钥
SERVER_PATH = '/www/wwwroot/81.69.*.*' # 上传到服务器的文件路径
# 本地路径配置
LOCAL_DIST_PATH = os.path.join('.vitepress', 'dist') # VitePress生成的dist目录
LOCAL_ZIP_PATH = 'dist.zip' # 压缩后的zip文件路径
# 日志文件路径
LOG_FILE = 'deploy_log.txt'
# ===========================================
def log(message):
"""记录日志"""
timestamp = datetime.now().strftime('%Y-%m-%d %H:%M:%S')
log_message = f'[{timestamp}] {message}\n'
print(log_message, end='')
with open(LOG_FILE, 'a', encoding='utf-8') as f:
f.write(log_message)
def clean_old_dist():
"""清理旧的dist文件夹"""
log(f'清理旧的dist文件夹: {LOCAL_DIST_PATH}')
if os.path.exists(LOCAL_DIST_PATH):
try:
shutil.rmtree(LOCAL_DIST_PATH)
log('dist文件夹清理完成!')
except Exception as e:
log(f'清理dist文件夹时发生错误: {str(e)}')
return False
else:
log('dist文件夹不存在,无需清理')
# 清理旧的zip文件
if os.path.exists(LOCAL_ZIP_PATH):
try:
os.remove(LOCAL_ZIP_PATH)
log(f'旧的压缩文件 {LOCAL_ZIP_PATH} 已删除')
except Exception as e:
log(f'删除旧压缩文件时发生错误: {str(e)}')
return False
return True
def run_npm_build():
"""执行npm打包命令"""
log('开始执行npm打包命令...')
try:
# 执行npm run docs:build命令
result = subprocess.run(
['C:\\Users\\LRGION\\AppData\\Roaming\\npm\\npm.cmd', 'run', 'docs:build'],# npm路径,切换自己的npm路径
check=True,
stdout=subprocess.PIPE,
stderr=subprocess.PIPE,
text=True,
encoding='utf-8',
cwd=os.getcwd() # 在当前目录执行命令
)
log('npm打包完成!')
log(f'打包输出:\n{result.stdout}')
return True
except subprocess.CalledProcessError as e:
log(f'npm打包失败: {str(e)}')
log(f'错误输出:\n{e.stderr}')
return False
except Exception as e:
log(f'执行npm命令时发生未知错误: {str(e)}')
return False
def check_dist_folder():
"""检查dist文件夹是否存在"""
log(f'检查dist文件夹: {LOCAL_DIST_PATH}')
if os.path.exists(LOCAL_DIST_PATH) and os.path.isdir(LOCAL_DIST_PATH):
log('dist文件夹存在!')
return True
else:
log('错误: dist文件夹不存在或不是目录!')
return False
def compress_dist():
"""压缩dist文件夹为zip文件"""
log(f'开始压缩dist文件夹到 {LOCAL_ZIP_PATH}')
try:
# 使用shutil创建zip压缩文件
shutil.make_archive('dist', 'zip', LOCAL_DIST_PATH)
log('dist文件夹压缩完成!')
return True
except Exception as e:
log(f'压缩dist文件夹时发生错误: {str(e)}')
return False
def upload_zip_to_server():
"""上传zip文件到服务器"""
log(f'开始上传压缩文件 {LOCAL_ZIP_PATH} 到服务器: {SERVER_IP}:{SERVER_PATH}')
try:
import paramiko
# 创建SSH客户端
ssh = paramiko.SSHClient()
ssh.set_missing_host_key_policy(paramiko.AutoAddPolicy())
# 连接服务器
ssh.connect(
hostname=SERVER_IP,
port=SERVER_PORT,
username=SERVER_USER,
password=SERVER_PASSWORD
)
# 创建SFTP客户端
sftp = ssh.open_sftp()
# 确保服务器目标目录存在
try:
sftp.stat(SERVER_PATH)
except FileNotFoundError:
log(f'创建服务器目录: {SERVER_PATH}')
ssh.exec_command(f'mkdir -p {SERVER_PATH}')
# 上传zip文件
remote_zip_path = os.path.join(SERVER_PATH, 'dist.zip').replace('\\', '/')
file_size = os.path.getsize(LOCAL_ZIP_PATH)
# 创建进度条
with tqdm(total=file_size, unit='B', unit_scale=True, desc='上传进度') as progress_bar:
# 自定义回调函数更新进度条
def callback(transferred, total):
progress_bar.update(transferred - progress_bar.n)
sftp.put(LOCAL_ZIP_PATH, remote_zip_path, callback=callback)
# 关闭连接
sftp.close()
ssh.close()
log('压缩文件上传完成!')
return True
except ImportError:
log('错误: 未找到paramiko或tqdm库。请先安装: pip install paramiko tqdm')
return False
except Exception as e:
log(f'上传压缩文件时发生错误: {str(e)}')
return False
def extract_zip_on_server():
"""在服务器上解压zip文件"""
log('开始在服务器上解压文件...')
try:
import paramiko
# 创建SSH客户端
ssh = paramiko.SSHClient()
ssh.set_missing_host_key_policy(paramiko.AutoAddPolicy())
# 连接服务器
ssh.connect(
hostname=SERVER_IP,
port=SERVER_PORT,
username=SERVER_USER,
password=SERVER_PASSWORD
)
# 执行解压命令
remote_zip_path = os.path.join(SERVER_PATH, 'dist.zip').replace('\\', '/')
command = f'unzip -o {remote_zip_path} -d {SERVER_PATH}'
stdin, stdout, stderr = ssh.exec_command(command)
# 等待命令执行完成
exit_status = stdout.channel.recv_exit_status()
if exit_status == 0:
log('文件解压成功!')
# 删除服务器上的zip文件
ssh.exec_command(f'rm -f {remote_zip_path}')
log('服务器上的临时zip文件已删除')
else:
log(f'解压失败,退出状态码: {exit_status}')
log(f'错误输出: {stderr.read().decode("utf-8", errors="ignore")}')
return False
# 关闭连接
ssh.close()
return True
except Exception as e:
log(f'解压文件时发生错误: {str(e)}')
return False
def main():
"""主函数"""
log('====== 开始部署流程 ======')
# 1. 清理旧的dist文件夹和zip文件
if not clean_old_dist():
log('部署失败: 清理旧文件失败')
log('====== 部署流程终止 ======')
return
# 2. 执行npm打包命令
if not run_npm_build():
log('部署失败: npm打包失败')
log('====== 部署流程终止 ======')
return
# 3. 检查dist文件夹是否存在
if not check_dist_folder():
log('部署失败: dist文件夹不存在')
log('====== 部署流程终止 ======')
return
# 4. 压缩dist文件夹
if not compress_dist():
log('部署失败: 压缩dist文件夹失败')
log('====== 部署流程终止 ======')
return
# 5. 上传zip文件到服务器
if not upload_zip_to_server():
log('部署失败: 上传压缩文件失败')
log('====== 部署流程终止 ======')
return
# 6. 在服务器上解压zip文件
if not extract_zip_on_server():
log('部署失败: 在服务器上解压文件失败')
log('====== 部署流程终止 ======')
return
# 7. 清理本地zip文件
if os.path.exists(LOCAL_ZIP_PATH):
try:
os.remove(LOCAL_ZIP_PATH)
log(f'本地临时压缩文件 {LOCAL_ZIP_PATH} 已删除')
except Exception as e:
log(f'删除本地压缩文件时发生错误: {str(e)}')
# 这里不终止部署流程,因为主要任务已经完成
log('====== 部署成功完成 ======')
if __name__ == '__main__':
main()