Files
soul-yongping/soul-api/devloy.py

655 lines
26 KiB
Python
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

#!/usr/bin/env python3
# -*- coding: utf-8 -*-
"""
soul-api 一键部署到宝塔【测试环境】
两种模式均部署到测试环境 /www/wwwroot/self/soul-dev
- binaryGo 二进制 + 宝塔 soulDev 项目,用 .env.development
- dockerDocker 镜像 + 蓝绿无缝切换,用 .env.development 打包进镜像
环境变量DEPLOY_DOCKER_PATH、DEPLOY_NGINX_CONF、DEPLOY_HOST 等
"""
from __future__ import print_function
import hashlib
import os
import sys
import tempfile
import argparse
import subprocess
import shutil
import tarfile
import time
try:
import paramiko
except ImportError:
print("错误: 请先安装 paramiko")
print(" pip install paramiko")
sys.exit(1)
try:
import requests
try:
import urllib3
urllib3.disable_warnings(urllib3.exceptions.InsecureRequestWarning)
except Exception:
pass
except ImportError:
requests = None
# ==================== 配置 ====================
DEPLOY_PROJECT_PATH = "/www/wwwroot/self/soul-dev"
DEPLOY_DOCKER_PATH = os.environ.get("DEPLOY_DOCKER_PATH", "/www/wwwroot/self/soul-dev")
DEPLOY_NGINX_CONF = os.environ.get("DEPLOY_NGINX_CONF", "") # 如 /www/server/panel/vhost/nginx/soulapi.quwanzhi.com.conf
DEFAULT_SSH_PORT = int(os.environ.get("DEPLOY_SSH_PORT", "22022"))
# 宝塔 API 密钥(写死,用于部署后重启 Go 项目)
BT_API_KEY_DEFAULT = "qcWubCdlfFjS2b2DMT1lzPFaDfmv1cBT"
def get_cfg():
host = os.environ.get("DEPLOY_HOST", "43.139.27.93")
bt_url = (os.environ.get("BT_PANEL_URL") or "").strip().rstrip("/")
if not bt_url:
bt_url = "https://%s:9988" % host
return {
"host": host,
"user": os.environ.get("DEPLOY_USER", "root"),
"password": os.environ.get("DEPLOY_PASSWORD", "Zhiqun1984"),
"ssh_key": os.environ.get("DEPLOY_SSH_KEY", ""),
"project_path": os.environ.get("DEPLOY_PROJECT_PATH", DEPLOY_PROJECT_PATH),
"bt_panel_url": bt_url,
"bt_api_key": os.environ.get("BT_API_KEY", BT_API_KEY_DEFAULT),
"bt_go_project_name": os.environ.get("BT_GO_PROJECT_NAME", "soulDev"),
}
# ==================== 本地构建 ====================
def run_build(root):
"""交叉编译 Go 二进制Linux amd64"""
print("[1/4] 本地交叉编译 Go 二进制 ...")
env = os.environ.copy()
env["GOOS"] = "linux"
env["GOARCH"] = "amd64"
env["CGO_ENABLED"] = "0"
cmd = ["go", "build", "-o", "soul-api", "./cmd/server"]
try:
r = subprocess.run(
cmd,
cwd=root,
env=env,
shell=False,
timeout=120,
capture_output=True,
text=True,
encoding="utf-8",
errors="replace",
)
if r.returncode != 0:
print(" [失败] go build 失败,退出码:", r.returncode)
if r.stderr:
for line in (r.stderr or "").strip().split("\n")[-10:]:
print(" " + line)
return None
out_path = os.path.join(root, "soul-api")
if not os.path.isfile(out_path):
print(" [失败] 未找到编译产物 soul-api")
return None
print(" [成功] 编译完成: %s (%.2f MB)" % (out_path, os.path.getsize(out_path) / 1024 / 1024))
return out_path
except subprocess.TimeoutExpired:
print(" [失败] 编译超时")
return None
except FileNotFoundError:
print(" [失败] 未找到 go 命令,请安装 Go")
return None
except Exception as e:
print(" [失败] 编译异常:", str(e))
return None
# ==================== 打包 ====================
DEPLOY_PORT = 8081
def set_env_port(env_path, port=DEPLOY_PORT):
"""将 .env 文件中的 PORT 设为指定值(用于部署包)"""
if not os.path.isfile(env_path):
return
with open(env_path, "r", encoding="utf-8", errors="replace") as f:
lines = f.readlines()
found = False
new_lines = []
for line in lines:
s = line.strip()
if "=" in s and s.split("=", 1)[0].strip() == "PORT":
new_lines.append("PORT=%s\n" % port)
found = True
else:
new_lines.append(line)
if not found:
new_lines.append("PORT=%s\n" % port)
with open(env_path, "w", encoding="utf-8", newline="\n") as f:
f.writelines(new_lines)
def set_env_mini_program_state(env_path, state):
"""将 .env 中的 WECHAT_MINI_PROGRAM_STATE 设为 developer/formal"""
if not os.path.isfile(env_path):
return
key = "WECHAT_MINI_PROGRAM_STATE"
with open(env_path, "r", encoding="utf-8", errors="replace") as f:
lines = f.readlines()
found = False
new_lines = []
for line in lines:
s = line.strip()
if "=" in s and s.split("=", 1)[0].strip() == key:
new_lines.append("%s=%s\n" % (key, state))
found = True
else:
new_lines.append(line)
if not found:
new_lines.append("%s=%s\n" % (key, state))
with open(env_path, "w", encoding="utf-8", newline="\n") as f:
f.writelines(new_lines)
def pack_deploy(root, binary_path, include_env=True):
"""打包二进制和 .env 为 tar.gz"""
print("[2/4] 打包部署文件 ...")
staging = tempfile.mkdtemp(prefix="soul_api_deploy_")
try:
shutil.copy2(binary_path, os.path.join(staging, "soul-api"))
env_src = os.path.join(root, ".env.development")
staging_env = os.path.join(staging, ".env")
if include_env and os.path.isfile(env_src):
shutil.copy2(env_src, staging_env)
print(" [已包含] .env.development -> .env")
else:
env_example = os.path.join(root, ".env.example")
if os.path.isfile(env_example):
shutil.copy2(env_example, staging_env)
print(" [已包含] .env.example -> .env (请服务器上检查配置)")
if os.path.isfile(staging_env):
set_env_port(staging_env, DEPLOY_PORT)
set_env_mini_program_state(staging_env, "developer")
print(" [已设置] PORT=%s, WECHAT_MINI_PROGRAM_STATE=developer测试环境" % DEPLOY_PORT)
tarball = os.path.join(tempfile.gettempdir(), "soul_api_deploy.tar.gz")
with tarfile.open(tarball, "w:gz") as tf:
for name in os.listdir(staging):
tf.add(os.path.join(staging, name), arcname=name)
print(" [成功] 打包完成: %s (%.2f MB)" % (tarball, os.path.getsize(tarball) / 1024 / 1024))
return tarball
except Exception as e:
print(" [失败] 打包异常:", str(e))
return None
finally:
shutil.rmtree(staging, ignore_errors=True)
# ==================== 宝塔 API 重启 ====================
def restart_via_bt_api(cfg):
"""通过宝塔 API 重启 Go 项目"""
url = cfg.get("bt_panel_url") or ""
key = cfg.get("bt_api_key") or ""
name = cfg.get("bt_go_project_name", "soulDev")
if not url or not key:
return False
if not requests:
print(" [提示] 未安装 requestspip install requests")
return False
try:
req_time = int(time.time())
sk_md5 = hashlib.md5(key.encode()).hexdigest()
req_token = hashlib.md5(("%s%s" % (req_time, sk_md5)).encode()).hexdigest()
base = url.rstrip("/")
params = {"request_time": req_time, "request_token": req_token}
for action in ("stop_go_project", "start_go_project"):
data = dict(params)
data["action"] = action
data["project_name"] = name
r = requests.post(base + "/plugin?name=go_project", data=data, timeout=15, verify=False)
if r.status_code != 200:
continue
j = r.json() if r.headers.get("content-type", "").startswith("application/json") else {}
if action == "stop_go_project":
time.sleep(2)
if j.get("status") is False and j.get("msg"):
print(" [宝塔API] %s: %s" % (action, j.get("msg", "")))
data = dict(params)
data["action"] = "start_go_project"
data["project_name"] = name
r = requests.post(base + "/plugin?name=go_project", data=data, timeout=15, verify=False)
if r.status_code == 200:
j = r.json() if r.headers.get("content-type", "").startswith("application/json") else {}
if j.get("status") is True:
print(" [成功] 已通过宝塔 API 重启 Go 项目: %s" % name)
return True
return False
except Exception as e:
print(" [宝塔API 失败] %s" % str(e))
return False
# ==================== SSH 上传 ====================
def upload_and_extract(cfg, tarball_path, no_restart=False, restart_method="auto"):
"""上传 tar.gz 到服务器并解压、重启"""
print("[3/4] SSH 上传并解压 ...")
if not cfg.get("password") and not cfg.get("ssh_key"):
print(" [失败] 请设置 DEPLOY_PASSWORD 或 DEPLOY_SSH_KEY")
return False
client = paramiko.SSHClient()
client.set_missing_host_key_policy(paramiko.AutoAddPolicy())
try:
if cfg.get("ssh_key") and os.path.isfile(cfg["ssh_key"]):
client.connect(cfg["host"], port=DEFAULT_SSH_PORT, username=cfg["user"], key_filename=cfg["ssh_key"], timeout=15)
else:
client.connect(cfg["host"], port=DEFAULT_SSH_PORT, username=cfg["user"], password=cfg["password"], timeout=15)
sftp = client.open_sftp()
remote_tar = "/tmp/soul_api_deploy.tar.gz"
project_path = cfg["project_path"]
sftp.put(tarball_path, remote_tar)
sftp.close()
cmd = "mkdir -p %s && cd %s && tar -xzf %s && chmod +x soul-api && rm -f %s && echo OK" % (project_path, project_path, remote_tar, remote_tar)
stdin, stdout, stderr = client.exec_command(cmd, timeout=60)
out = stdout.read().decode("utf-8", errors="replace").strip()
exit_status = stdout.channel.recv_exit_status()
if exit_status != 0 or "OK" not in out:
print(" [失败] 解压失败,退出码:", exit_status)
return False
print(" [成功] 已解压到: %s" % project_path)
if not no_restart:
print("[4/4] 重启 soulDev 服务 ...")
ok = False
if restart_method in ("auto", "btapi") and (cfg.get("bt_panel_url") and cfg.get("bt_api_key")):
ok = restart_via_bt_api(cfg)
if not ok and restart_method in ("auto", "ssh"):
restart_cmd = (
"cd %s && T=$(readlink -f .) && for p in $(pgrep -f soul-api 2>/dev/null); do "
'[ "$(readlink -f /proc/$p/cwd 2>/dev/null)" = "$T" ] && kill $p 2>/dev/null; done; '
"sleep 2; setsid nohup ./soul-api >> soul-api.log 2>&1 </dev/null & "
"sleep 3; T=$(readlink -f .) && for p in $(pgrep -f soul-api 2>/dev/null); do "
'[ "$(readlink -f /proc/$p/cwd 2>/dev/null)" = "$T" ] && echo RESTART_OK && exit 0; done; echo RESTART_FAIL'
) % project_path
stdin, stdout, stderr = client.exec_command(restart_cmd, timeout=20)
out = stdout.read().decode("utf-8", errors="replace").strip()
err = (stderr.read().decode("utf-8", errors="replace") or "").strip()
if err:
print(" [stderr] %s" % err[:200])
ok = "RESTART_OK" in out
if ok:
print(" [成功] soulDev 已通过 SSH 重启")
else:
print(" [警告] 请手动启动: cd %s && ./soul-api" % project_path)
else:
print("[4/4] 跳过重启 (--no-restart)")
return True
except Exception as e:
print(" [失败] SSH 错误:", str(e))
return False
finally:
client.close()
# ==================== 宝塔 API - Nginx 配置与重载 ====================
def _bt_request(cfg, endpoint, data):
"""宝塔 API 通用请求request_time + request_token 签名)"""
if not requests:
return None, None
url = (cfg.get("bt_panel_url") or "").rstrip("/")
key = cfg.get("bt_api_key") or ""
if not url or not key:
return None, None
try:
req_time = int(time.time())
sk_md5 = hashlib.md5(key.encode()).hexdigest()
req_token = hashlib.md5(("%s%s" % (req_time, sk_md5)).encode()).hexdigest()
payload = dict(data)
payload["request_time"] = req_time
payload["request_token"] = req_token
r = requests.post(url + endpoint, data=payload, timeout=15, verify=False)
if r.status_code != 200:
return None, r
j = r.json() if r.headers.get("content-type", "").startswith("application/json") else {}
return j, r
except Exception as e:
print(" [宝塔API] 请求异常:", str(e))
return None, None
def deploy_nginx_via_bt_api(cfg, nginx_conf_path, new_port):
"""
通过宝塔 API 更新 Nginx 配置并重载。
- 使用 files.GetFileBody 读取配置
- 替换 proxy_pass 端口
- 使用 files.SaveFileBody 保存
- 尝试 service 插件重载 nginx
"""
if not nginx_conf_path or not new_port:
return False
if not requests:
print(" [提示] 未安装 requests无法使用宝塔 Nginx API。pip install requests")
return False
# 1. 读取配置
j, _ = _bt_request(cfg, "/files?action=GetFileBody", {"path": nginx_conf_path})
if not j or "status" in j and j.get("status") is False:
print(" [宝塔API] 读取 Nginx 配置失败:", j.get("msg", "未知错误") if j else "")
return False
content = j.get("data") or j.get("content") or ""
if isinstance(content, bytes):
content = content.decode("utf-8", errors="replace")
# 2. 替换 proxy_pass 端口
import re
new_content = re.sub(
r"proxy_pass\s+http://127\.0\.0\.1:\d+",
"proxy_pass http://127.0.0.1:%s" % new_port,
content,
flags=re.IGNORECASE,
)
if new_content == content:
print(" [宝塔API] 未找到 proxy_pass可能已是目标端口或格式不符")
# 3. 保存配置
j, _ = _bt_request(cfg, "/files?action=SaveFileBody", {
"path": nginx_conf_path,
"data": new_content,
"encoding": "utf-8",
})
if not j or "status" in j and j.get("status") is False:
print(" [宝塔API] 保存 Nginx 配置失败:", j.get("msg", "未知错误") if j else "")
return False
# 4. 重载 Nginx尝试 service 插件)
for try_action, try_name in [
("reload", "nginx"),
("RestartService", "nginx"),
]:
j, _ = _bt_request(cfg, "/service?action=%s" % try_action, {"name": try_name})
if j and j.get("status") is True:
print(" [成功] 已通过宝塔 API 重载 Nginx (端口 %s)" % new_port)
return True
# 部分面板无 service 接口,配置已保存,需手动重载
print(" [提示] Nginx 配置已通过宝塔 API 更新,重载请到面板操作或使用 SSH: nginx -s reload")
return True
# ==================== Docker 部署(蓝绿无缝切换) ====================
def run_docker_build(root, env_file=".env.development"):
"""本地构建 Docker 镜像(使用 Docker 内的 golang 镜像)"""
print("[1/5] 构建 Docker 镜像 ...(进度见下方 Docker 输出)")
try:
cmd = ["docker", "build", "--pull=false", "-f", "deploy/Dockerfile", "-t", "soul-api:latest", "--build-arg", "ENV_FILE=%s" % env_file, "--progress=plain", "."]
r = subprocess.run(cmd, cwd=root, shell=False, timeout=300)
if r.returncode != 0:
print(" [失败] docker build 失败,退出码:", r.returncode)
return None
print(" [成功] 镜像构建完成 soul-api:latest")
return True
except FileNotFoundError:
print(" [失败] 未找到 docker 命令,请安装 Docker")
return None
except subprocess.TimeoutExpired:
print(" [失败] 构建超时")
return None
except Exception as e:
print(" [失败] 构建异常:", str(e))
return None
def run_docker_build_local(root, env_file=".env.development"):
"""使用本地 Go 交叉编译后构建 Docker 镜像(不拉取 golang 镜像)"""
print("[1/5] 使用本地 Go 交叉编译 ...")
binary_path = run_build(root)
if not binary_path:
return None
print("[2/5] 使用 Dockerfile.local 构建镜像 ...(进度见下方 Docker 输出)")
try:
cmd = ["docker", "build", "-f", "deploy/Dockerfile.local", "-t", "soul-api:latest",
"--build-arg", "ENV_FILE=%s" % env_file, "--progress=plain", "."]
r = subprocess.run(cmd, cwd=root, shell=False, timeout=120)
if r.returncode != 0:
print(" [失败] docker build 失败,退出码:", r.returncode)
return None
print(" [成功] 镜像构建完成 soul-api:latest本地 Go 参与构建)")
return True
except FileNotFoundError:
print(" [失败] 未找到 docker 命令,请安装 Docker")
return None
except subprocess.TimeoutExpired:
print(" [失败] 构建超时")
return None
except Exception as e:
print(" [失败] 构建异常:", str(e))
return None
def pack_docker_image(root):
"""将 soul-api 与 redis 镜像一并导出为 tar.gz服务器无需拉取"""
import gzip
print("[2/5] 导出镜像为 tar.gzsoul-api + redis...")
out_tar = os.path.join(tempfile.gettempdir(), "soul_api_image.tar.gz")
try:
r = subprocess.run(
["docker", "save", "soul-api:latest", "docker.m.daocloud.io/library/redis:7-alpine"],
capture_output=True,
timeout=180,
cwd=root,
)
if r.returncode != 0:
stderr = (r.stderr or b"").decode("utf-8", errors="replace")[:300]
print(" [失败] docker save 失败:", stderr)
print(" [提示] 请确保本地有 redis 镜像,执行: docker images | findstr redis 查看名称")
return None
with gzip.open(out_tar, "wb") as f:
f.write(r.stdout)
if not os.path.isfile(out_tar) or os.path.getsize(out_tar) < 1000:
print(" [失败] 导出文件异常")
return None
print(" [成功] 导出完成: %.2f MBsoul-api + redis" % (os.path.getsize(out_tar) / 1024 / 1024))
return out_tar
except subprocess.TimeoutExpired:
print(" [失败] docker save 超时")
return None
except Exception as e:
print(" [失败] 导出异常:", str(e))
return None
def upload_and_deploy_docker(cfg, image_tar_path, include_env=True, deploy_method="ssh"):
"""上传镜像与配置到服务器执行蓝绿部署。deploy_method: ssh=脚本内 Nginx 切换, btapi=宝塔 API 更新 Nginx"""
deploy_path = os.environ.get("DEPLOY_DOCKER_PATH", DEPLOY_DOCKER_PATH)
nginx_conf = os.environ.get("DEPLOY_NGINX_CONF", DEPLOY_NGINX_CONF)
script_dir = os.path.dirname(os.path.abspath(__file__))
print("[3/5] SSH 上传镜像与配置 ...")
if not cfg.get("password") and not cfg.get("ssh_key"):
print(" [失败] 请设置 DEPLOY_PASSWORD 或 DEPLOY_SSH_KEY")
return False
client = paramiko.SSHClient()
client.set_missing_host_key_policy(paramiko.AutoAddPolicy())
try:
if cfg.get("ssh_key") and os.path.isfile(cfg["ssh_key"]):
client.connect(cfg["host"], port=DEFAULT_SSH_PORT, username=cfg["user"], key_filename=cfg["ssh_key"], timeout=30)
else:
client.connect(cfg["host"], port=DEFAULT_SSH_PORT, username=cfg["user"], password=cfg["password"], timeout=30)
sftp = client.open_sftp()
remote_tar = "/tmp/soul_api_image.tar.gz"
sftp.put(image_tar_path, remote_tar)
print(" [已上传] 镜像 tar.gz")
compose_local = os.path.join(script_dir, "deploy", "docker-compose.bluegreen.yml")
deploy_local = os.path.join(script_dir, "deploy", "docker-deploy-remote.sh")
env_local = os.path.join(script_dir, ".env.production")
if os.path.isfile(compose_local):
sftp.put(compose_local, deploy_path + "/docker-compose.bluegreen.yml")
print(" [已上传] docker-compose.bluegreen.yml")
if os.path.isfile(deploy_local):
sftp.put(deploy_local, deploy_path + "/docker-deploy-remote.sh")
print(" [已上传] docker-deploy-remote.sh")
if include_env and os.path.isfile(env_local):
sftp.put(env_local, deploy_path.rstrip("/") + "/.env")
print(" [已上传] .env.production -> .env覆盖镜像内配置")
# btapi 模式:需先读取 .active 计算新端口,脚本内跳过 Nginx
current_active = "blue"
if deploy_method == "btapi" and nginx_conf:
try:
active_file = deploy_path.rstrip("/") + "/.active"
with sftp.open(active_file, "r") as f:
current_active = (f.read().decode("utf-8", errors="replace") or "blue").strip() or "blue"
except Exception:
pass
new_port = 8082 if current_active == "blue" else 8081
sftp.close()
print("[4/5] 执行蓝绿部署 ...")
env_exports = ""
if nginx_conf:
env_exports += "export DEPLOY_NGINX_CONF='%s'; " % nginx_conf.replace("'", "'\\''")
env_exports += "export DEPLOY_DOCKER_PATH='%s'; " % deploy_path.replace("'", "'\\''")
script_args = remote_tar
if deploy_method == "btapi" and nginx_conf:
script_args += " --skip-nginx"
cmd = "mkdir -p %s && %s cd %s && chmod +x docker-deploy-remote.sh && ./docker-deploy-remote.sh %s" % (deploy_path, env_exports, deploy_path, script_args)
stdin, stdout, stderr = client.exec_command(cmd, timeout=180)
out = stdout.read().decode("utf-8", errors="replace")
err = stderr.read().decode("utf-8", errors="replace")
exit_status = stdout.channel.recv_exit_status()
print(out)
if err:
print(err[:500])
if exit_status != 0:
print(" [失败] 远程部署脚本退出码:", exit_status)
return False
# btapi 模式:通过宝塔 API 更新 Nginx 配置并重载new_port 已在上方计算)
if deploy_method == "btapi" and nginx_conf:
try:
print("[5/5] 宝塔 API 更新 Nginx ...")
deploy_nginx_via_bt_api(cfg, nginx_conf, new_port)
except Exception as e:
print(" [警告] 宝塔 Nginx API 失败:", str(e))
print("[5/5] 部署完成,蓝绿无缝切换")
return True
except Exception as e:
print(" [失败] SSH 错误:", str(e))
return False
finally:
client.close()
# ==================== 主函数 ====================
def main():
parser = argparse.ArgumentParser(description="soul-api 测试环境一键部署到宝塔")
parser.add_argument("--mode", choices=("binary", "docker"), default="docker",
help="docker=Docker 蓝绿部署 (默认), binary=Go 二进制")
parser.add_argument("--no-build", action="store_true", help="跳过本地编译/构建")
parser.add_argument("--no-env", action="store_true", help="不打包 .env")
parser.add_argument("--no-restart", action="store_true", help="[binary] 上传后不重启")
parser.add_argument("--restart-method", choices=("auto", "btapi", "ssh"), default="auto",
help="[binary] 重启方式: auto/btapi/ssh")
parser.add_argument("--local-go", action="store_true",
help="[docker] 使用本地 Go 交叉编译后打镜像,不拉取 golang 镜像")
parser.add_argument("--deploy-method", choices=("ssh", "btapi"), default="ssh",
help="[docker] 部署方式: ssh=脚本内 Nginx 切换, btapi=宝塔 API 更新 Nginx 配置并重载 (默认 ssh)")
args = parser.parse_args()
script_dir = os.path.dirname(os.path.abspath(__file__))
root = script_dir
cfg = get_cfg()
if args.mode == "docker":
docker_path = os.environ.get("DEPLOY_DOCKER_PATH", DEPLOY_DOCKER_PATH)
print("=" * 60)
print(" soul-api 测试环境 Docker 蓝绿部署(无缝切换)")
print("=" * 60)
print(" 服务器: %s@%s:%s" % (cfg["user"], cfg["host"], DEFAULT_SSH_PORT))
print(" 目标目录: %s" % docker_path)
print("=" * 60)
if not args.no_build:
ok = run_docker_build_local(root) if args.local_go else run_docker_build(root)
if not ok:
return 1
else:
print("[1/5] 跳过构建,使用现有 soul-api:latest")
image_tar = pack_docker_image(root)
if not image_tar:
return 1
if not upload_and_deploy_docker(cfg, image_tar, include_env=not args.no_env, deploy_method=args.deploy_method):
return 1
try:
os.remove(image_tar)
except Exception:
pass
print("")
print(" 部署完成!测试环境蓝绿无缝切换")
return 0
# ===== Binary 模式 =====
print("=" * 60)
print(" soul-api 测试环境 部署到宝塔,重启 soulDev")
print("=" * 60)
print(" 服务器: %s@%s:%s" % (cfg["user"], cfg["host"], DEFAULT_SSH_PORT))
print(" 目标目录: %s" % cfg["project_path"])
print("=" * 60)
binary_path = os.path.join(root, "soul-api")
if not args.no_build:
p = run_build(root)
if not p:
return 1
else:
if not os.path.isfile(binary_path):
print("[错误] 未找到 soul-api 二进制")
return 1
print("[1/4] 跳过编译")
tarball = pack_deploy(root, binary_path, include_env=not args.no_env)
if not tarball:
return 1
if not upload_and_extract(cfg, tarball, no_restart=args.no_restart, restart_method=args.restart_method):
return 1
try:
os.remove(tarball)
except Exception:
pass
print("")
print(" 部署完成!目录: %s" % cfg["project_path"])
return 0
if __name__ == "__main__":
sys.exit(main())