Commit 22c17fe0 authored by wenwen.tang's avatar wenwen.tang 😕

chore(deploy): 线上部署完成;修正 deploy_docker 的旧布局与管道缺陷

部署(192.168.68.121,容器 rentos,http://192.168.68.121:8899/)
- 代码改由服务器 git 工作副本 /opt/rentos/repo 提供,镜像 rentos:latest 已重建
- 线上台账完成两项迁移:AJ「下一账单日」64 行、车位备注 AA 31 条
- 实测线上 PDF 出单链:生成 302/303/305 室三份中文 PDF(%PDF- 头,
  302 室 22734.00 与本机测试一致),命名符合「{单元}-费用类型-收费通知书」
- 实测新增接口:批次详情含 receipt(收款/开票统计)、批次导出返回合法 xlsx

数据一致性
- 线上账期在本机之外推进过(2026-10-09 与 10-12 三次出单,共 44 行),
  本机 AC 落后 11 行;已用线上权威数据覆盖本机台账,两侧现逐格一致
- 本机台账备份于 data/_snap/20261013-1110-before-online-sync/

deploy_docker.py(降级通道)
- 适配仓库根构建上下文:APP_ITEMS 精确列出 deploy/Dockerfile、deploy/wheels、
  deploy/wecom/cli-linux-x64.tgz;解包目标改为 REPO_DIR,与 git 通道同目录
- build 改用 -f deploy/Dockerfile,并修复 `docker build … | tail` 吞掉退出码的问题
parent 08dbed4e
Pipeline #836 canceled with stages
......@@ -484,8 +484,8 @@
"Z": null,
"AA": null,
"AB": "2026-09",
"AC": "2026-11-01",
"AJ": "2026-11-01"
"AC": "2027-01-01",
"AJ": "2027-01-01"
},
{
"_id": "rent_parking-0005",
......@@ -517,8 +517,8 @@
"Z": null,
"AA": null,
"AB": "2026-10",
"AC": "2026-11-01",
"AJ": "2026-11-01"
"AC": "2027-01-01",
"AJ": "2027-01-01"
},
{
"_id": "rent_parking-0006",
......@@ -550,8 +550,8 @@
"Z": null,
"AA": null,
"AB": "2026-11",
"AC": "2026-12-01",
"AJ": "2026-12-01"
"AC": "2027-02-01",
"AJ": "2027-02-01"
},
{
"_id": "rent_parking-0007",
......@@ -649,8 +649,8 @@
"Z": null,
"AA": null,
"AB": "2026-11",
"AC": "2026-12-01",
"AJ": "2026-12-01"
"AC": "2027-03-01",
"AJ": "2027-03-01"
},
{
"_id": "rent_parking-0010",
......@@ -715,8 +715,8 @@
"Z": null,
"AA": null,
"AB": "2026-11",
"AC": "2026-12-01",
"AJ": "2026-12-01"
"AC": "2027-02-01",
"AJ": "2027-02-01"
},
{
"_id": "rent_parking-0012",
......@@ -748,8 +748,8 @@
"Z": 29154.69,
"AA": null,
"AB": "2026-10",
"AC": "2026-11-01",
"AJ": "2026-11-01"
"AC": "2027-01-01",
"AJ": "2027-01-01"
},
{
"_id": "rent_parking-0013",
......@@ -916,11 +916,11 @@
"Z": null,
"AA": null,
"AB": "2026-11",
"AC": "2026-12-01",
"AC": "2027-02-01",
"AD": 180.7,
"AE": 3246430.2,
"AI": 23797.7259,
"AJ": "2026-12-01"
"AJ": "2027-02-01"
},
{
"_id": "rent_parking-0018",
......@@ -1051,8 +1051,8 @@
"Z": 41300.0,
"AA": null,
"AB": "2026-11",
"AC": "2026-12-01",
"AJ": "2026-12-01"
"AC": "2027-02-01",
"AJ": "2027-02-01"
},
{
"_id": "rent_parking-0022",
......@@ -1084,8 +1084,8 @@
"Z": null,
"AA": null,
"AB": "2026-10",
"AC": "2026-11-01",
"AJ": "2026-11-01"
"AC": "2027-02-01",
"AJ": "2027-02-01"
},
{
"_id": "rent_parking-0023",
......@@ -1249,8 +1249,8 @@
"Z": 23725,
"AA": null,
"AB": "2026-11",
"AC": "2026-12-01",
"AJ": "2026-12-01"
"AC": "2027-02-01",
"AJ": "2027-02-01"
},
{
"_id": "rent_parking-0028",
......@@ -1282,8 +1282,8 @@
"Z": 15815,
"AA": null,
"AB": "2026-09",
"AC": "2026-12-01",
"AJ": "2026-12-01"
"AC": "2027-02-01",
"AJ": "2027-02-01"
},
{
"_id": "rent_parking-0029",
......@@ -2892,6 +2892,6 @@
],
"tail": [],
"next_seq": 95,
"updated_at": "2026-10-13 10:50:42",
"updated_by": "出单推进账期"
"updated_at": "2026-10-13 11:04:15",
"updated_by": "导入车位备注"
}
\ No newline at end of file
# -*- coding: utf-8 -*-
"""把 rentos 部署到远程服务器(Docker 方式)。
"""把 rentos 部署到远程服务器(Docker 方式)—— **降级通道**。
日常部署请优先用 `tools/deploy_git.py`(服务器自行 git pull,增量小、一键同步)。
本脚本在本机打包后用 SFTP 上传,适用于**服务器访问不到 Git 站点**的场景。
两条通道共用同一份 `deploy/Dockerfile` 与同一个构建上下文布局(仓库根),
且都落在 /opt/rentos/repo,可互相接管:
git 通道 → cd /opt/rentos/repo && git pull && docker build -f deploy/Dockerfile .
SFTP 通道 → 上传 app.tar.gz 解包进 /opt/rentos/repo,再走同样的 build
用法:
set RENTOS_SSH_PASSWORD=12345678
......@@ -39,8 +47,14 @@ CONTAINER = "rentos"
STAGE = os.path.join(BASE, "deploy", "_stage")
# 打进镜像的应用代码(相对项目根)
APP_ITEMS = ["rentos", "build_notices.py", "web", "assets", "templates"]
# 构建上下文目录(与 tools/deploy_git.py 的 git 工作副本同路径,可互相接管)
REPO_DIR = os.environ.get("RENTOS_REPO_DIR", REMOTE_DIR + "/repo")
# 打进镜像的构建上下文(相对项目根)—— 必须是**仓库根布局**,
# 因为 deploy/Dockerfile 里的 COPY 路径就是按仓库结构写的。
# deploy/wecom/cli-linux-x64.tgz 由 Dockerfile 自行解压,无需先解开。
APP_ITEMS = ["rentos", "build_notices.py", "web", "assets", "templates", "tools",
"deploy/Dockerfile", "deploy/wheels", "deploy/wecom/cli-linux-x64.tgz"]
# 作为 volume 的目录(首次部署时把本地现状带上去)
DATA_ITEMS = ["data"]
OUT_ITEMS = ["output"]
......@@ -94,21 +108,9 @@ def pack():
import shutil
shutil.rmtree(STAGE)
os.makedirs(STAGE)
# Dockerfile + wheels 直接复制
import shutil
shutil.copy2(os.path.join(BASE, "deploy", "Dockerfile"),
os.path.join(STAGE, "Dockerfile"))
shutil.copytree(os.path.join(BASE, "deploy", "wheels"),
os.path.join(STAGE, "wheels"))
# 企业微信官方 CLI(Linux 静态二进制)—— 打进镜像,凭据另挂
wc = os.path.join(BASE, "deploy", "wecom", "x", "package", "bin", "wecom-cli")
if not os.path.exists(wc):
sys.exit("缺少 deploy/wecom/x/package/bin/wecom-cli\n"
"先执行:python tools/setup_wecom_cloud.py push")
os.makedirs(os.path.join(STAGE, "wecom"), exist_ok=True)
shutil.copy2(wc, os.path.join(STAGE, "wecom", "wecom-cli"))
log(" 纳入 wecom-cli → %.1f MB(企业微信直发能力)" % (os.path.getsize(wc) / 1048576.0))
a = make_tar("app/", APP_ITEMS, "app.tar.gz")
# app.tar.gz 解包后**直接对应 repo/(仓库根布局)**:
# Dockerfile、wheels、企微 tgz 都在包内,不再单独复制/传输。
a = make_tar("", APP_ITEMS, "app.tar.gz")
d = make_tar("", DATA_ITEMS, "data.tar.gz")
o = make_tar("", OUT_ITEMS, "output.tar.gz")
return [a, d, o]
......@@ -142,46 +144,46 @@ def run(cli, cmd, timeout=1800, quiet=False):
def push(cli, tars):
step("2/6 上传")
sf = cli.open_sftp()
run(cli, "mkdir -p %s" % REMOTE_DIR, quiet=True)
run(cli, "mkdir -p %s %s" % (REMOTE_DIR, REPO_DIR), quiet=True)
for p in tars:
name = os.path.basename(p)
remote = "%s/%s" % (REMOTE_DIR, name)
sf.put(p, remote)
sf.put(p, "%s/%s" % (REMOTE_DIR, name))
log(" 已上传 %s" % name)
# 目录与 Dockerfile
run(cli, "mkdir -p %s/app %s/data %s/output %s/logs %s/uploads %s/wecom-config" % (
REMOTE_DIR, REMOTE_DIR, REMOTE_DIR, REMOTE_DIR, REMOTE_DIR, REMOTE_DIR), quiet=True)
run(cli, "mkdir -p %s" % " ".join(
REMOTE_DIR + "/" + d
for d in ("data", "output", "logs", "uploads", "wecom-config")), quiet=True)
rc, o, _ = run(cli, "ls -1 %s/wecom-config 2>/dev/null" % REMOTE_DIR, quiet=True)
if "credentials.enc" not in o:
log(" !! %s/wecom-config 里没有凭据 → 云端无法发企业微信。" % REMOTE_DIR)
log(" 请先执行:python tools/setup_wecom_cloud.py push")
else:
log(" 凭据已在位:%s/wecom-config" % REMOTE_DIR)
with sf.file("%s/Dockerfile" % REMOTE_DIR, "w") as f:
f.write(open(os.path.join(STAGE, "Dockerfile"), encoding="utf-8").read())
log(" 已上传 Dockerfile")
sf.close()
log(" 解包…")
run(cli, "cd %s && tar xzf app.tar.gz && tar xzf data.tar.gz && tar xzf output.tar.gz" % REMOTE_DIR)
# wheels 也要上去(Dockerfile 里 COPY wheels/)
sftp2 = cli.open_sftp()
run(cli, "rm -rf %s/wheels && mkdir -p %s/wheels" % (REMOTE_DIR, REMOTE_DIR), quiet=True)
for fn in sorted(os.listdir(os.path.join(STAGE, "wheels"))):
sftp2.put(os.path.join(STAGE, "wheels", fn), "%s/wheels/%s" % (REMOTE_DIR, fn))
log(" 已上传 wheels/%s" % fn)
sftp2.close()
log(" 解包…(应用代码 → repo/,台账与产物 → %s/)" % REMOTE_DIR)
run(cli, "cd %s && tar xzf %s/app.tar.gz" % (REPO_DIR, REMOTE_DIR))
run(cli, "cd %s && tar xzf %s/data.tar.gz && tar xzf %s/output.tar.gz"
% (REMOTE_DIR, REMOTE_DIR, REMOTE_DIR))
run(cli, "rm -f %s/*.tar.gz" % REMOTE_DIR, quiet=True)
# --------------------------------------------------------------------- Docker
def build(cli):
step("3/6 构建镜像")
log(" 构建上下文:%s(仓库根布局)" % REPO_DIR)
log(" Dockerfile :deploy/Dockerfile")
log(" 基础镜像:python:3.11.3-slim(服务器本地已有,无需拉取)")
rc, o, e = run(cli, "cd %s && docker build -t %s . 2>&1 | tail -30" % (REMOTE_DIR, IMAGE),
timeout=1800)
# 两个坑:
# 1) Dockerfile 在 deploy/ 下,必须 -f 指定,否则报 "failed to read dockerfile"
# 2) 不能写成 `docker build … | tail`:管道退出码来自 tail,
# 会把构建失败吞掉(症状是报错后仍用旧镜像重启容器)
cmd = ("cd %s && docker build -f deploy/Dockerfile -t %s . "
"> /tmp/rentos-build.log 2>&1; rc=$?; "
"tail -30 /tmp/rentos-build.log; echo \"---- build rc=$rc ----\"; exit $rc"
% (REPO_DIR, IMAGE))
rc, o, e = run(cli, cmd, timeout=1800)
if rc != 0:
log("!! 构建失败(rc=%d)" % rc)
log("!! 构建失败(rc=%d)—— 完整日志在服务器 /tmp/rentos-build.log" % rc)
return False
rc2, o2, _ = run(cli, "docker images %s --format '{{.Repository}}:{{.Tag}} {{.Size}}'"
% IMAGE, quiet=True)
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment