完成https 服务
This commit is contained in:
+337
-83
@@ -1,103 +1,357 @@
|
|||||||
import requests
|
#!/usr/bin/env python3
|
||||||
|
"""
|
||||||
|
虚拟换装网关 —— 统一 FastAPI 服务(HTTPS / 443)
|
||||||
|
|
||||||
|
路由规则(与原 change_app.py 保持一致):
|
||||||
|
kuzi_img 存在 → service3 逻辑(模特 + 上衣 + 裤子)
|
||||||
|
cloth_img 存在(无裤子)→ service2 逻辑(模特 + 上衣)
|
||||||
|
仅 human_img → service1 逻辑(单图编辑)
|
||||||
|
"""
|
||||||
|
|
||||||
|
import asyncio
|
||||||
|
import base64
|
||||||
|
import io
|
||||||
|
import json
|
||||||
import os
|
import os
|
||||||
from flask import Flask, jsonify, request, send_from_directory
|
import tempfile
|
||||||
|
import time
|
||||||
|
import uuid
|
||||||
|
from pathlib import Path
|
||||||
|
|
||||||
app = Flask(__name__, static_folder='static')
|
import httpx
|
||||||
|
import oss2
|
||||||
|
import uvicorn
|
||||||
|
from fastapi import FastAPI, HTTPException, Request
|
||||||
|
from fastapi.middleware.cors import CORSMiddleware
|
||||||
|
from fastapi.responses import FileResponse, JSONResponse
|
||||||
|
from fastapi.staticfiles import StaticFiles
|
||||||
|
from pydantic import BaseModel
|
||||||
|
|
||||||
|
# ---------------------------------------------------------------------------
|
||||||
|
# ComfyUI 配置
|
||||||
|
# ---------------------------------------------------------------------------
|
||||||
|
COMFYUI_URL = "http://112.126.94.241:28188"
|
||||||
|
COMFYUI_URL_BACKUP = "http://112.126.94.241:38188"
|
||||||
|
|
||||||
|
_root = Path(__file__).parent
|
||||||
|
COMFYUI_USER = (_root / "user.txt").read_text(encoding="utf-8").strip()
|
||||||
|
COMFYUI_PASS = (_root / "password.txt").read_text(encoding="utf-8").strip()
|
||||||
|
COMFYUI_AUTH = (COMFYUI_USER, COMFYUI_PASS)
|
||||||
|
|
||||||
|
# ---------------------------------------------------------------------------
|
||||||
|
# 工作流路径
|
||||||
|
# ---------------------------------------------------------------------------
|
||||||
|
WORKFLOW1 = _root / "change1" / "change1.json" # 单图编辑
|
||||||
|
WORKFLOW2 = _root / "change2" / "change2_1_ex.json" # 模特 + 上衣
|
||||||
|
WORKFLOW3 = _root / "change3" / "change2_2_0308.json" # 模特 + 上衣 + 裤子
|
||||||
|
|
||||||
|
# ---------------------------------------------------------------------------
|
||||||
|
# 工作流节点 ID
|
||||||
|
# ---------------------------------------------------------------------------
|
||||||
|
NODE_MODEL = "11" # 模特图(LoadImage)
|
||||||
|
NODE_SHIRT = "10" # 上衣图(LoadImage)
|
||||||
|
NODE_PANTS = "4" # 裤子图(LoadImage)
|
||||||
|
NODE_OUTPUT = "33" # 输出(SaveImage)
|
||||||
|
NODE_PROMPT = "31" # 文本提示(PrimitiveStringMultiline)
|
||||||
|
|
||||||
|
# ---------------------------------------------------------------------------
|
||||||
|
# OSS 配置
|
||||||
|
# ---------------------------------------------------------------------------
|
||||||
|
OSS_KEY_ID = "LTAI5tGp1sLzedqxihcNC1eb"
|
||||||
|
OSS_KEY_SECRET = "IFZE1b8YYreCP6zfA6GaZ9uBT678qO"
|
||||||
|
OSS_ENDPOINT = "oss-cn-beijing.aliyuncs.com"
|
||||||
|
OSS_BUCKET = "xiangsilian"
|
||||||
|
|
||||||
|
# ---------------------------------------------------------------------------
|
||||||
|
# HTTPS 证书路径
|
||||||
|
# ---------------------------------------------------------------------------
|
||||||
|
SSL_CERT = "/etc/letsencrypt/live/xiangsilian.com/fullchain.pem"
|
||||||
|
SSL_KEY = "/etc/letsencrypt/live/xiangsilian.com/privkey.pem"
|
||||||
|
|
||||||
|
# ---------------------------------------------------------------------------
|
||||||
|
# FastAPI 应用
|
||||||
|
# ---------------------------------------------------------------------------
|
||||||
|
app = FastAPI(title="虚拟换装服务")
|
||||||
|
|
||||||
|
app.add_middleware(
|
||||||
|
CORSMiddleware,
|
||||||
|
allow_origins=["*"],
|
||||||
|
allow_methods=["*"],
|
||||||
|
allow_headers=["*"],
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
@app.route('/')
|
@app.exception_handler(HTTPException)
|
||||||
def index():
|
async def http_exception_handler(request: Request, exc: HTTPException):
|
||||||
return send_from_directory(app.static_folder, 'index.html')
|
"""将 HTTPException 统一转换为业务错误格式"""
|
||||||
|
return JSONResponse(
|
||||||
|
status_code=exc.status_code,
|
||||||
|
content={"ret": -1, "state": -1, "msg": exc.detail},
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
@app.route('/static/<path:filename>')
|
# ---------------------------------------------------------------------------
|
||||||
def static_files(filename):
|
# OSS 工具
|
||||||
return send_from_directory(app.static_folder, filename)
|
# ---------------------------------------------------------------------------
|
||||||
|
def upload_to_oss(image_path: str, object_name: str) -> str | None:
|
||||||
|
auth = oss2.Auth(OSS_KEY_ID, OSS_KEY_SECRET)
|
||||||
|
bucket = oss2.Bucket(auth, OSS_ENDPOINT, OSS_BUCKET)
|
||||||
|
try:
|
||||||
|
bucket.put_object_from_file(object_name, image_path)
|
||||||
|
url = bucket.sign_url("GET", object_name, 3600 * 24 * 365 * 10)
|
||||||
|
print(f"OSS 上传成功: {url}")
|
||||||
|
return url
|
||||||
|
except Exception as e:
|
||||||
|
print(f"OSS 上传失败: {e}")
|
||||||
|
return None
|
||||||
|
|
||||||
|
|
||||||
@app.route('/change_cloth_base64', methods=['POST'])
|
# ---------------------------------------------------------------------------
|
||||||
def change_cloth_base64():
|
# ComfyUI 工具函数
|
||||||
|
# ---------------------------------------------------------------------------
|
||||||
|
def decode_base64_image(b64_str: str) -> bytes:
|
||||||
|
"""解码 base64 图片,自动去除 data:image/... 前缀"""
|
||||||
|
if "," in b64_str:
|
||||||
|
b64_str = b64_str.split(",", 1)[1]
|
||||||
|
return base64.b64decode(b64_str)
|
||||||
|
|
||||||
data = request.get_json()
|
|
||||||
print(f"change_cloth_base64 input data keys:{list(data.keys()) if data else None}")
|
|
||||||
if not data:
|
|
||||||
return jsonify({"ret": -1, "state": -1, "msg": "No JSON data provided"}), 400
|
|
||||||
|
|
||||||
human_img = data.get('human_img')
|
async def check_comfyui_alive(base_url: str, timeout: float = 3.0) -> bool:
|
||||||
cloth_img = data.get('cloth_img')
|
try:
|
||||||
|
async with httpx.AsyncClient(timeout=timeout, auth=COMFYUI_AUTH) as client:
|
||||||
|
resp = await client.get(f"{base_url}/system_stats")
|
||||||
|
return resp.status_code < 500
|
||||||
|
except Exception as e:
|
||||||
|
print(f"ComfyUI 健康检查失败 {base_url}: {e}")
|
||||||
|
return False
|
||||||
|
|
||||||
if not human_img:
|
|
||||||
return jsonify({"ret": -1, "state": -1, "msg": "human_img is required"}), 400
|
|
||||||
|
|
||||||
kuzi_img = data.get('kuzi_img')
|
async def pick_comfyui_url() -> str:
|
||||||
|
if await check_comfyui_alive(COMFYUI_URL):
|
||||||
|
print(f"使用正式服务器: {COMFYUI_URL}")
|
||||||
|
return COMFYUI_URL
|
||||||
|
print(f"正式服务器不可用,切换备份: {COMFYUI_URL_BACKUP}")
|
||||||
|
if await check_comfyui_alive(COMFYUI_URL_BACKUP):
|
||||||
|
print(f"使用备份服务器: {COMFYUI_URL_BACKUP}")
|
||||||
|
return COMFYUI_URL_BACKUP
|
||||||
|
raise HTTPException(status_code=503, detail="正式与备份 ComfyUI 服务器均不可用")
|
||||||
|
|
||||||
change_desc = data.get('change_desc')
|
|
||||||
if change_desc is None:
|
async def upload_image(client: httpx.AsyncClient, base_url: str, image_bytes: bytes, filename: str) -> str:
|
||||||
change_desc = ''
|
files = {"image": (filename, io.BytesIO(image_bytes), "image/jpeg")}
|
||||||
elif not isinstance(change_desc, str):
|
resp = await client.post(f"{base_url}/upload/image", files=files, data={"overwrite": "true"})
|
||||||
return jsonify({"ret": -1, "state": -1, "msg": "change_desc must be a string"}), 400
|
resp.raise_for_status()
|
||||||
|
return resp.json()["name"]
|
||||||
|
|
||||||
|
|
||||||
|
async def queue_prompt(client: httpx.AsyncClient, base_url: str, workflow: dict) -> str:
|
||||||
|
payload = {"prompt": workflow, "client_id": str(uuid.uuid4())}
|
||||||
|
resp = await client.post(f"{base_url}/prompt", json=payload)
|
||||||
|
resp.raise_for_status()
|
||||||
|
return resp.json()["prompt_id"]
|
||||||
|
|
||||||
|
|
||||||
|
async def wait_for_result(
|
||||||
|
client: httpx.AsyncClient, base_url: str, prompt_id: str, timeout: int = 300
|
||||||
|
) -> dict:
|
||||||
|
deadline = time.time() + timeout
|
||||||
|
while time.time() < deadline:
|
||||||
|
resp = await client.get(f"{base_url}/history/{prompt_id}")
|
||||||
|
resp.raise_for_status()
|
||||||
|
history = resp.json()
|
||||||
|
if prompt_id in history:
|
||||||
|
entry = history[prompt_id]
|
||||||
|
status = entry.get("status", {})
|
||||||
|
if status.get("completed"):
|
||||||
|
return entry.get("outputs", {})
|
||||||
|
if status.get("status_str") == "error":
|
||||||
|
messages = status.get("messages", [])
|
||||||
|
raise HTTPException(status_code=500, detail=f"ComfyUI 工作流执行出错: {messages}")
|
||||||
|
await asyncio.sleep(2)
|
||||||
|
raise HTTPException(status_code=504, detail="等待 ComfyUI 超时(300秒)")
|
||||||
|
|
||||||
|
|
||||||
|
async def fetch_image_bytes(
|
||||||
|
client: httpx.AsyncClient, base_url: str, filename: str,
|
||||||
|
subfolder: str = "", type_: str = "output"
|
||||||
|
) -> bytes:
|
||||||
|
params = {"filename": filename, "subfolder": subfolder, "type": type_}
|
||||||
|
resp = await client.get(f"{base_url}/view", params=params)
|
||||||
|
resp.raise_for_status()
|
||||||
|
return resp.content
|
||||||
|
|
||||||
|
|
||||||
|
# ---------------------------------------------------------------------------
|
||||||
|
# 通用工作流执行
|
||||||
|
# ---------------------------------------------------------------------------
|
||||||
|
async def run_workflow(
|
||||||
|
workflow_path: Path,
|
||||||
|
node_images: dict[str, bytes],
|
||||||
|
change_desc: str = "",
|
||||||
|
) -> str:
|
||||||
|
"""
|
||||||
|
执行 ComfyUI 工作流并返回 OSS URL。
|
||||||
|
|
||||||
|
node_images: {节点ID: 图片字节},按顺序并发上传并注入工作流。
|
||||||
|
"""
|
||||||
|
comfyui_url = await pick_comfyui_url()
|
||||||
|
workflow = json.loads(workflow_path.read_text(encoding="utf-8"))
|
||||||
|
|
||||||
|
desc = (change_desc or "").strip()
|
||||||
|
if desc:
|
||||||
|
workflow[NODE_PROMPT]["inputs"] = {"value": desc}
|
||||||
|
|
||||||
|
uid = uuid.uuid4().hex[:8]
|
||||||
|
|
||||||
|
async with httpx.AsyncClient(timeout=60.0, auth=COMFYUI_AUTH) as client:
|
||||||
|
node_ids = list(node_images.keys())
|
||||||
|
upload_tasks = [
|
||||||
|
upload_image(client, comfyui_url, node_images[nid], f"{nid}_{uid}.jpg")
|
||||||
|
for nid in node_ids
|
||||||
|
]
|
||||||
|
try:
|
||||||
|
names = await asyncio.gather(*upload_tasks)
|
||||||
|
except httpx.HTTPError as e:
|
||||||
|
raise HTTPException(status_code=502, detail=f"上传图片到 ComfyUI 失败: {e}")
|
||||||
|
|
||||||
|
for node_id, name in zip(node_ids, names):
|
||||||
|
workflow[node_id]["inputs"]["image"] = name
|
||||||
|
|
||||||
|
try:
|
||||||
|
prompt_id = await queue_prompt(client, comfyui_url, workflow)
|
||||||
|
except httpx.HTTPError as e:
|
||||||
|
raise HTTPException(status_code=502, detail=f"提交工作流失败: {e}")
|
||||||
|
|
||||||
|
async with httpx.AsyncClient(timeout=30.0, auth=COMFYUI_AUTH) as poll_client:
|
||||||
|
outputs = await wait_for_result(poll_client, comfyui_url, prompt_id, timeout=300)
|
||||||
|
|
||||||
|
node_output = outputs.get(NODE_OUTPUT)
|
||||||
|
if not node_output:
|
||||||
|
raise HTTPException(status_code=500, detail=f"工作流未返回节点 {NODE_OUTPUT} 的输出")
|
||||||
|
|
||||||
|
images = node_output.get("images", [])
|
||||||
|
if not images:
|
||||||
|
raise HTTPException(status_code=500, detail="输出节点没有图片")
|
||||||
|
|
||||||
|
img_info = images[0]
|
||||||
|
filename = img_info["filename"]
|
||||||
|
subfolder = img_info.get("subfolder", "")
|
||||||
|
type_ = img_info.get("type", "output")
|
||||||
|
|
||||||
|
async with httpx.AsyncClient(timeout=30.0, auth=COMFYUI_AUTH) as dl_client:
|
||||||
|
result_bytes = await fetch_image_bytes(dl_client, comfyui_url, filename, subfolder, type_)
|
||||||
|
|
||||||
|
suffix = Path(filename).suffix or ".png"
|
||||||
|
object_name = f"tryon/{uuid.uuid4().hex}{suffix}"
|
||||||
|
with tempfile.NamedTemporaryFile(suffix=suffix, delete=False) as tmp:
|
||||||
|
tmp.write(result_bytes)
|
||||||
|
tmp_path = tmp.name
|
||||||
|
|
||||||
try:
|
try:
|
||||||
if kuzi_img:
|
result_url = upload_to_oss(tmp_path, object_name)
|
||||||
payload = {
|
finally:
|
||||||
'model_image': human_img,
|
os.unlink(tmp_path)
|
||||||
'shirt_image': cloth_img,
|
|
||||||
'pants_image': kuzi_img,
|
|
||||||
'change_desc': change_desc,
|
|
||||||
}
|
|
||||||
response = requests.post('http://127.0.0.1:12223/try-on', json=payload, timeout=360)
|
|
||||||
elif cloth_img:
|
|
||||||
payload = {
|
|
||||||
'model_image': human_img,
|
|
||||||
'shirt_image': cloth_img,
|
|
||||||
'change_desc': change_desc,
|
|
||||||
}
|
|
||||||
response = requests.post('http://127.0.0.1:12222/try-on', json=payload, timeout=360)
|
|
||||||
else:
|
|
||||||
payload = {
|
|
||||||
'model_image': human_img,
|
|
||||||
'change_desc': change_desc,
|
|
||||||
}
|
|
||||||
response = requests.post('http://127.0.0.1:12221/try-on', json=payload, timeout=360)
|
|
||||||
|
|
||||||
response.raise_for_status()
|
if not result_url:
|
||||||
result = response.json()
|
raise HTTPException(status_code=500, detail="上传结果图片到 OSS 失败")
|
||||||
result_image = result.get('result_url')
|
|
||||||
|
|
||||||
if not result_image:
|
return result_url
|
||||||
return jsonify({"ret": -1, "state": -1, "msg": "Backend returned no image"}), 500
|
|
||||||
|
|
||||||
except requests.exceptions.Timeout:
|
|
||||||
return jsonify({"ret": -1, "state": -1, "msg": "Backend service timeout"}), 504
|
|
||||||
except requests.exceptions.ConnectionError:
|
|
||||||
return jsonify({"ret": -1, "state": -1, "msg": "Cannot connect to backend service"}), 502
|
|
||||||
except requests.exceptions.HTTPError as e:
|
|
||||||
return jsonify({"ret": -1, "state": -1, "msg": f"Backend error: {e}"}), 502
|
|
||||||
|
|
||||||
return jsonify({
|
|
||||||
"ret": 0,
|
|
||||||
"state": 0,
|
|
||||||
"msg": "success",
|
|
||||||
"data": result_image,
|
|
||||||
})
|
|
||||||
|
|
||||||
|
|
||||||
if __name__ == '__main__':
|
# ---------------------------------------------------------------------------
|
||||||
app.run(host="0.0.0.0", port=28888, debug=True)
|
# API 模型
|
||||||
|
# ---------------------------------------------------------------------------
|
||||||
|
class ChangeClothRequest(BaseModel):
|
||||||
|
human_img: str
|
||||||
|
cloth_img: str = ""
|
||||||
|
kuzi_img: str = ""
|
||||||
|
change_desc: str = ""
|
||||||
|
|
||||||
# if __name__ == '__main__':
|
|
||||||
# # 证书路径(替换为你的域名)
|
# ---------------------------------------------------------------------------
|
||||||
# ssl_cert = '/etc/letsencrypt/live/change.xiangsilian.com/fullchain.pem'
|
# 接口
|
||||||
# ssl_key = '/etc/letsencrypt/live/change.xiangsilian.com/privkey.pem'
|
# ---------------------------------------------------------------------------
|
||||||
|
@app.post("/change_cloth_base64")
|
||||||
# # 检查证书文件是否存在
|
async def change_cloth_base64(req: ChangeClothRequest):
|
||||||
# if not os.path.exists(ssl_cert) or not os.path.exists(ssl_key):
|
print(f"收到请求,字段: human_img={'有' if req.human_img else '无'}, "
|
||||||
# print("❌ 证书文件不存在,请检查路径是否正确!")
|
f"cloth_img={'有' if req.cloth_img else '无'}, "
|
||||||
# else:
|
f"kuzi_img={'有' if req.kuzi_img else '无'}")
|
||||||
# # 启动HTTPS服务(443端口需要root权限,所以运行时要加sudo)
|
|
||||||
# app.run(
|
if not req.human_img:
|
||||||
# host='0.0.0.0', # 允许外网访问
|
raise HTTPException(status_code=400, detail="human_img is required")
|
||||||
# port=443, # HTTPS默认端口(非443需加端口访问,如8443)
|
|
||||||
# ssl_context=(ssl_cert, ssl_key), # 加载Let's Encrypt证书
|
try:
|
||||||
# debug=True # 生产环境务必关闭debug
|
model_bytes = decode_base64_image(req.human_img)
|
||||||
# )
|
except Exception as e:
|
||||||
|
raise HTTPException(status_code=400, detail=f"human_img 解码失败: {e}")
|
||||||
|
|
||||||
|
if req.kuzi_img:
|
||||||
|
try:
|
||||||
|
shirt_bytes = decode_base64_image(req.cloth_img)
|
||||||
|
pants_bytes = decode_base64_image(req.kuzi_img)
|
||||||
|
except Exception as e:
|
||||||
|
raise HTTPException(status_code=400, detail=f"图片解码失败: {e}")
|
||||||
|
result_url = await run_workflow(
|
||||||
|
WORKFLOW3,
|
||||||
|
{NODE_MODEL: model_bytes, NODE_SHIRT: shirt_bytes, NODE_PANTS: pants_bytes},
|
||||||
|
req.change_desc,
|
||||||
|
)
|
||||||
|
elif req.cloth_img:
|
||||||
|
try:
|
||||||
|
shirt_bytes = decode_base64_image(req.cloth_img)
|
||||||
|
except Exception as e:
|
||||||
|
raise HTTPException(status_code=400, detail=f"cloth_img 解码失败: {e}")
|
||||||
|
result_url = await run_workflow(
|
||||||
|
WORKFLOW2,
|
||||||
|
{NODE_MODEL: model_bytes, NODE_SHIRT: shirt_bytes},
|
||||||
|
req.change_desc,
|
||||||
|
)
|
||||||
|
else:
|
||||||
|
result_url = await run_workflow(
|
||||||
|
WORKFLOW1,
|
||||||
|
{NODE_MODEL: model_bytes},
|
||||||
|
req.change_desc,
|
||||||
|
)
|
||||||
|
|
||||||
|
return {"ret": 0, "state": 0, "msg": "success", "data": result_url}
|
||||||
|
|
||||||
|
|
||||||
|
@app.get("/")
|
||||||
|
async def root():
|
||||||
|
return FileResponse(str(_root / "static" / "index.html"))
|
||||||
|
|
||||||
|
|
||||||
|
@app.get("/health")
|
||||||
|
async def health():
|
||||||
|
primary_ok = await check_comfyui_alive(COMFYUI_URL)
|
||||||
|
backup_ok = await check_comfyui_alive(COMFYUI_URL_BACKUP)
|
||||||
|
active = COMFYUI_URL if primary_ok else (COMFYUI_URL_BACKUP if backup_ok else None)
|
||||||
|
return {
|
||||||
|
"status": "ok" if active else "degraded",
|
||||||
|
"comfyui_active": active,
|
||||||
|
"comfyui_primary": {"url": COMFYUI_URL, "alive": primary_ok},
|
||||||
|
"comfyui_backup": {"url": COMFYUI_URL_BACKUP, "alive": backup_ok},
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
app.mount("/static", StaticFiles(directory=str(_root / "static")), name="static")
|
||||||
|
|
||||||
|
# ---------------------------------------------------------------------------
|
||||||
|
# 启动入口
|
||||||
|
# ---------------------------------------------------------------------------
|
||||||
|
if __name__ == "__main__":
|
||||||
|
use_https = os.path.exists(SSL_CERT) and os.path.exists(SSL_KEY)
|
||||||
|
|
||||||
|
if use_https:
|
||||||
|
print(f"启动 HTTPS 服务,端口 443,证书: {SSL_CERT}")
|
||||||
|
uvicorn.run(
|
||||||
|
app,
|
||||||
|
host="0.0.0.0",
|
||||||
|
port=443,
|
||||||
|
ssl_certfile=SSL_CERT,
|
||||||
|
ssl_keyfile=SSL_KEY,
|
||||||
|
log_level="info",
|
||||||
|
)
|
||||||
|
else:
|
||||||
|
print("⚠ 证书文件不存在,降级为 HTTP,端口 28888")
|
||||||
|
uvicorn.run(app, host="0.0.0.0", port=28888, log_level="info")
|
||||||
|
|||||||
Executable
+39
@@ -0,0 +1,39 @@
|
|||||||
|
#!/bin/bash
|
||||||
|
# Let's Encrypt 证书续期脚本
|
||||||
|
# 流程:停服 → certbot renew(standalone)→ 修正证书权限 → 重启服务
|
||||||
|
# 由 crontab 每月执行一次(证书90天过期,提前30天续期)
|
||||||
|
|
||||||
|
ROOT="/home/ubuntu/change_cloth_server"
|
||||||
|
LOG="/home/ubuntu/log/renew_cert.log"
|
||||||
|
mkdir -p "$(dirname "$LOG")"
|
||||||
|
|
||||||
|
echo "[$(date '+%Y-%m-%d %H:%M:%S')] === 开始证书续期 ===" >> "$LOG"
|
||||||
|
|
||||||
|
# 停止服务,释放 443 端口(certbot standalone 需要 80 端口,实际上不需要停止443)
|
||||||
|
# 但 certbot --standalone 需要 80 端口空闲即可,无需停止 443 服务
|
||||||
|
# 保险起见仍记录当前状态
|
||||||
|
pkill -f "${ROOT}/change_app.py" 2>/dev/null
|
||||||
|
echo " 服务已停止" >> "$LOG"
|
||||||
|
|
||||||
|
sleep 2
|
||||||
|
|
||||||
|
# 续期(standalone 模式使用 80 端口验证)
|
||||||
|
certbot renew --standalone --non-interactive >> "$LOG" 2>&1
|
||||||
|
RENEW_EXIT=$?
|
||||||
|
|
||||||
|
if [ $RENEW_EXIT -eq 0 ]; then
|
||||||
|
echo " 证书续期成功" >> "$LOG"
|
||||||
|
else
|
||||||
|
echo " 证书续期失败,退出码: $RENEW_EXIT" >> "$LOG"
|
||||||
|
fi
|
||||||
|
|
||||||
|
# 修正证书文件权限(确保非 root 用户可读)
|
||||||
|
chmod 644 /etc/letsencrypt/archive/xiangsilian.com/privkey*.pem 2>/dev/null
|
||||||
|
chmod 755 /etc/letsencrypt/live /etc/letsencrypt/archive 2>/dev/null
|
||||||
|
|
||||||
|
# 重启服务
|
||||||
|
export PATH="/home/ubuntu/.local/bin:$PATH"
|
||||||
|
nohup python3 "${ROOT}/change_app.py" >> "/home/ubuntu/log/change_app.log" 2>&1 &
|
||||||
|
echo " 服务已重启 (PID: $!)" >> "$LOG"
|
||||||
|
|
||||||
|
echo "[$(date '+%Y-%m-%d %H:%M:%S')] === 续期流程结束 ===" >> "$LOG"
|
||||||
+10
-18
@@ -1,35 +1,27 @@
|
|||||||
#!/bin/bash
|
#!/bin/bash
|
||||||
# 启动本机换装网关与 service1 / service2 / service3。
|
# 启动虚拟换装服务(统一 FastAPI,HTTPS 443 端口)
|
||||||
# ComfyUI 在其它机器运行;service1/service2/service3 内已配置远端 COMFYUI_URL。
|
# ComfyUI 在外部机器运行;change_app.py 直接调用 service1/2/3 逻辑,无需单独启动子服务。
|
||||||
|
|
||||||
ROOT="/home/ubuntu/change_cloth_server"
|
ROOT="/home/ubuntu/change_cloth_server"
|
||||||
LOG_DIR="/home/ubuntu/log"
|
LOG_DIR="/home/ubuntu/log"
|
||||||
mkdir -p "$LOG_DIR"
|
mkdir -p "$LOG_DIR"
|
||||||
|
|
||||||
echo "[$(date '+%Y-%m-%d %H:%M:%S')] 开始停止旧进程..."
|
echo "[$(date '+%Y-%m-%d %H:%M:%S')] 停止旧进程..."
|
||||||
|
|
||||||
# 按命令行中的脚本绝对路径结束旧实例(与下方 nohup 路径一致)
|
|
||||||
pkill -f "${ROOT}/change1/service1.py" 2>/dev/null && echo " 已停止 service1" || echo " service1 未在运行"
|
|
||||||
pkill -f "${ROOT}/change2/service2.py" 2>/dev/null && echo " 已停止 service2" || echo " service2 未在运行"
|
|
||||||
pkill -f "${ROOT}/change3/service3.py" 2>/dev/null && echo " 已停止 service3" || echo " service3 未在运行"
|
|
||||||
pkill -f "${ROOT}/change_app.py" 2>/dev/null && echo " 已停止 change_app" || echo " change_app 未在运行"
|
pkill -f "${ROOT}/change_app.py" 2>/dev/null && echo " 已停止 change_app" || echo " change_app 未在运行"
|
||||||
|
|
||||||
|
# 兼容:若旧的子服务仍在运行,一并停止
|
||||||
|
pkill -f "${ROOT}/change1/service1.py" 2>/dev/null && echo " 已停止 service1(旧)" || true
|
||||||
|
pkill -f "${ROOT}/change2/service2.py" 2>/dev/null && echo " 已停止 service2(旧)" || true
|
||||||
|
pkill -f "${ROOT}/change3/service3.py" 2>/dev/null && echo " 已停止 service3(旧)" || true
|
||||||
|
|
||||||
sleep 2
|
sleep 2
|
||||||
|
|
||||||
echo "[$(date '+%Y-%m-%d %H:%M:%S')] 开始启动服务..."
|
echo "[$(date '+%Y-%m-%d %H:%M:%S')] 启动 change_app(HTTPS 443)..."
|
||||||
|
|
||||||
export PATH="/home/ubuntu/.local/bin:$PATH"
|
export PATH="/home/ubuntu/.local/bin:$PATH"
|
||||||
|
|
||||||
nohup python3 "${ROOT}/change1/service1.py" >> "$LOG_DIR/service1.log" 2>&1 &
|
|
||||||
echo " service1 已启动 (PID: $!)"
|
|
||||||
|
|
||||||
nohup python3 "${ROOT}/change2/service2.py" >> "$LOG_DIR/service2.log" 2>&1 &
|
|
||||||
echo " service2 已启动 (PID: $!)"
|
|
||||||
|
|
||||||
nohup python3 "${ROOT}/change3/service3.py" >> "$LOG_DIR/service3.log" 2>&1 &
|
|
||||||
echo " service3 已启动 (PID: $!)"
|
|
||||||
|
|
||||||
nohup python3 "${ROOT}/change_app.py" >> "$LOG_DIR/change_app.log" 2>&1 &
|
nohup python3 "${ROOT}/change_app.py" >> "$LOG_DIR/change_app.log" 2>&1 &
|
||||||
echo " change_app 已启动 (PID: $!)"
|
echo " change_app 已启动 (PID: $!)"
|
||||||
|
|
||||||
echo "[$(date '+%Y-%m-%d %H:%M:%S')] 启动完毕(ComfyUI 请在外部机器保证可用)。日志目录: $LOG_DIR"
|
echo "[$(date '+%Y-%m-%d %H:%M:%S')] 启动完毕。日志: $LOG_DIR/change_app.log"
|
||||||
|
|||||||
Reference in New Issue
Block a user