Files
2026gaokaozhiyuan/deploy_nas_v5.py
T

179 lines
6.3 KiB
Python
Raw Normal View History

2026-06-24 13:11:34 +08:00
"""
NAS 部署脚本 v5 —— 修复 Docker 全路径 + 直接启动
"""
import paramiko
import os
import sys
import time
import base64
import tarfile
import io
NAS_HOST = "100.85.152.112"
NAS_PORT = 22
NAS_USER = "simon"
NAS_PASS = "Nas82829330"
NAS_DEPLOY_PATH = "/volume1/docker/gaokao-portal"
LOCAL_DEPLOY_DIR = r"C:\Users\simon\WorkBuddy\2026-05-20-15-22-53\deploy"
# 群晖上 Docker 的实际路径(不在 sudo secure_path 中)
DOCKER_BIN = "/usr/local/bin/docker"
SUDO_DOCKER = f"echo '{NAS_PASS}' | sudo -S {DOCKER_BIN}"
def connect():
client = paramiko.SSHClient()
client.set_missing_host_key_policy(paramiko.AutoAddPolicy())
client.connect(NAS_HOST, port=NAS_PORT, username=NAS_USER, password=NAS_PASS, timeout=15)
return client
def run_cmd(client, cmd, desc="", timeout=30):
if desc:
print(f"[执行] {desc}")
disp = cmd[:150] + ('...' if len(cmd) > 150 else '')
print(f" $ {disp}")
stdin, stdout, stderr = client.exec_command(cmd, timeout=timeout)
out = stdout.read().decode('utf-8', errors='replace')
err = stderr.read().decode('utf-8', errors='replace')
status = stdout.channel.recv_exit_status()
if out.strip():
for line in out.strip().split('\n')[:30]:
print(f" > {line}")
if err.strip():
for line in err.strip().split('\n')[:10]:
if 'sudo' not in line.lower() or status != 0:
print(f" ! {line}")
return out.strip(), err.strip(), status
def run_cmd_stdin(client, cmd, stdin_data, desc="", timeout=60):
"""通过 channel 的 stdin 传输数据"""
if desc:
print(f"[传输] {desc}")
transport = client.get_transport()
channel = transport.open_session()
channel.exec_command(cmd)
chunk_size = 8192
total = len(stdin_data)
sent = 0
while sent < total:
chunk = stdin_data[sent:sent+chunk_size]
channel.sendall(chunk)
sent += len(chunk)
channel.shutdown_write()
out = b""
err = b""
while not channel.exit_status_ready():
if channel.recv_ready():
out += channel.recv(4096)
if channel.recv_stderr_ready():
err += channel.recv_stderr(4096)
time.sleep(0.05)
while channel.recv_ready():
out += channel.recv(4096)
while channel.recv_stderr_ready():
err += channel.recv_stderr(4096)
out_str = out.decode('utf-8', errors='replace')
err_str = err.decode('utf-8', errors='replace')
status = channel.recv_exit_status()
if out_str.strip():
for line in out_str.strip().split('\n')[:10]:
print(f" > {line}")
if err_str.strip() and status != 0:
for line in err_str.strip().split('\n')[:5]:
print(f" ! {line}")
print(f" 传输: {sent} 字节 -> 退出码 {status}")
return out_str, err_str, status
def main():
print("=" * 60)
print(" 高考志愿填报门户 - NAS 部署 v5")
print("=" * 60)
# 1. 打包
print("\n─── 打包文件 ───")
buf = io.BytesIO()
with tarfile.open(fileobj=buf, mode="w:gz") as tar:
tar.add(LOCAL_DEPLOY_DIR, arcname=".")
tar_data = buf.getvalue()
print(f" 打包: {len(tar_data)/1024:.1f} KB")
# 2. base64
b64_data = base64.b64encode(tar_data).decode('ascii')
# 3. 连接
client = connect()
print("[连接] SSH OK")
try:
# 4. 测试 Docker 全路径
print("\n─── Docker 检查 ───")
out, _, status = run_cmd(client, f"{SUDO_DOCKER} --version", "Docker 版本")
if status != 0:
print("[错误] Docker 不可用")
return
run_cmd(client, f"{SUDO_DOCKER} compose version", "Compose 版本")
# 5. 传输文件
print("\n─── 传输文件 ───")
decode_cmd = "base64 -d > /tmp/gaokao-deploy.tar.gz 2>/dev/null && echo DECODE_OK || echo DECODE_FAIL"
out, _, status = run_cmd_stdin(client, decode_cmd, b64_data.encode('ascii'), "Base64 传输")
# 6. 解压
print("\n─── 解压部署 ───")
run_cmd(client, f"rm -rf '{NAS_DEPLOY_PATH}' && mkdir -p '{NAS_DEPLOY_PATH}'", "准备目录")
run_cmd(client, f"tar -xzf /tmp/gaokao-deploy.tar.gz -C '{NAS_DEPLOY_PATH}'", "解压")
run_cmd(client, f"find '{NAS_DEPLOY_PATH}' -type f | wc -l", "文件计数")
# 7. 修改 docker-compose.yml 端口(8080 空闲,直接用 8080)
# 确认 nginx.conf 端口
run_cmd(client, f"grep -n 'listen' '{NAS_DEPLOY_PATH}/nginx/nginx.conf'", "nginx 监听端口")
# 8. 启动 Docker Compose
print("\n─── 启动 Docker Compose ───")
compose = f"{SUDO_DOCKER} compose"
# 先停旧容器
run_cmd(client, f"cd '{NAS_DEPLOY_PATH}' && {compose} down 2>&1", "停止旧容器")
time.sleep(2)
# 构建并启动(拉取 nginx 镜像 + 构建后端)
print(" 构建镜像并启动(可能需要几分钟)...")
out, err, status = run_cmd(client,
f"cd '{NAS_DEPLOY_PATH}' && {compose} up -d --build 2>&1",
"docker compose up", timeout=180)
# 等启动
print("\n等待容器就绪...")
for i in range(10):
time.sleep(6)
out, _, _ = run_cmd(client, f"cd '{NAS_DEPLOY_PATH}' && {compose} ps 2>&1", f"检查 {i+1}/10")
if "Up" in out:
print(" 容器已运行!")
break
if i == 5:
# 看看日志
run_cmd(client, f"cd '{NAS_DEPLOY_PATH}' && {compose} logs --tail=20 2>&1", "中途日志")
else:
print("[警告] 容器可能未正常启动,检查日志...")
# 9. 日志
print("\n─── 完整日志 ───")
run_cmd(client, f"cd '{NAS_DEPLOY_PATH}' && {compose} logs --tail=50 2>&1", "容器日志")
# 10. HTTP 测试
print("\n─── HTTP 测试 ───")
run_cmd(client, "curl -s http://localhost:8080/ | head -20", "首页内容")
# 11. 清理
run_cmd(client, "rm -f /tmp/gaokao-deploy.tar.gz", "清理")
print("\n" + "=" * 60)
print(" 部署完成!")
print(f" 内网: http://100.85.152.112:8080")
print("=" * 60)
finally:
client.close()
if __name__ == "__main__":
main()