#!/bin/bash # pipeline-bidding 一键部署脚本(在宿主应用根目录执行或从本目录定位宿主)。 # 职责:建表(运行期禁止 schema 变更,建表只在部署期)+ 种子数据(appcodes + pipelines 产线记录) # + wwwroot 软链 + pip install 本包。 # 参考模式:app_audit/build.sh + sage-database-design-conventions 的种子数据步骤。 set -e SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" # 定位宿主应用根目录(含 wwwroot + py3) APP_ROOT="" for candidate in "$SCRIPT_DIR/../../pipeline-app" "$SCRIPT_DIR/../.." "$HOME/work/pipeline/pipeline-app"; do if [ -d "$candidate/wwwroot" ] && [ -d "$candidate/py3" ]; then APP_ROOT="$(cd "$candidate" && pwd)" break fi done if [ -z "$APP_ROOT" ]; then echo "ERROR: host app (pipeline-app) root not found" exit 1 fi PYTHON="$APP_ROOT/py3/bin/python" echo "Host app root: $APP_ROOT" # ── 1. 建表(幂等:CREATE TABLE IF NOT EXISTS 由 json2ddl 产物改造)── cd "$SCRIPT_DIR" "$PYTHON" - "$APP_ROOT" <<'PYEOF' import re, sys, os ddl = open('mysql.ddl.sql', encoding='utf-8').read() # json2ddl 产物含 drop table if exists,部署期改为 IF NOT EXISTS 幂等建表 ddl = re.sub(r'drop table if exists (\w+);', '', ddl) ddl = re.sub(r'CREATE TABLE (\w+)\s*\(', r'CREATE TABLE IF NOT EXISTS \1 (', ddl) open('/tmp/pipeline_bidding_ddl.sql', 'w', encoding='utf-8').write(ddl) print('DDL prepared:', ddl.count('CREATE TABLE IF NOT EXISTS'), 'tables') PYEOF "$PYTHON" - "$APP_ROOT" <<'PYEOF' import sys, os, subprocess sys.path.insert(0, os.getcwd()) from appPublic.jsonConfig import getConfig from appPublic.aes import aes_decode_b64 cfg = getConfig(sys.argv[1], {'workdir': sys.argv[1]}) kw = cfg.databases['pipeline'].kwargs pwd = aes_decode_b64(cfg.password_key, kw.password) with open('/tmp/pipeline_bidding_ddl.sql', 'rb') as f: r = subprocess.run(['mysql', '-h', str(kw.host), '-P', str(kw.port), '-u', str(kw.user), '-p%s' % pwd, str(kw.db)], stdin=f, capture_output=True) if r.returncode == 0: print('tables applied') else: print('ERROR:', r.stderr.decode('utf-8', 'replace').strip()[:300]) sys.exit(1) PYEOF # ── 2. 种子数据:appcodes 字典 + pipelines 产线记录(数据源 init/data.json,幂等)── cd "$APP_ROOT" "$PYTHON" - "$SCRIPT_DIR" "$APP_ROOT" <<'PYEOF' import sys, os, json, asyncio CDIR = sys.argv[1] sys.path.insert(0, os.getcwd()) from appPublic.jsonConfig import getConfig from sqlor.dbpools import DBPools from appPublic.uniqueID import getID data = json.load(open(os.path.join(CDIR, 'init', 'data.json'))) async def seed(): config = getConfig(sys.argv[2], {'workdir': sys.argv[2]}) db = DBPools(config.databases) async with db.sqlorContext('pipeline') as sor: # pipelines 产线主记录(checklist 第 0 步) for pl in data.get('pipelines', []): recs = await sor.sqlExe("SELECT id FROM pipelines WHERE id=${i}$", {"i": pl['id']}) if not recs: await sor.C('pipelines', { 'id': pl['id'], 'name': pl['name'], 'description': pl.get('description', ''), 'pipeline_type': pl.get('pipeline_type', 'bidding'), 'version': '1.0.0', 'status': pl.get('status', 'published'), 'org_id': pl.get('org_id', '0'), 'created_by': 'system', }) print('pipeline registered:', pl['id']) # appcodes 字典 for ac in data.get('appcodes', []): pid = ac['parentid'] rows = await sor.R('appcodes', {'id': pid}) if not rows: await sor.C('appcodes', {'id': pid, 'name': ac.get('parentname', pid), 'hierarchy_flg': '0'}) for item in ac.get('items', []): chk = await sor.sqlExe( "SELECT id FROM appcodes_kv WHERE parentid=${p}$ AND k=${k}$", {'p': pid, 'k': item['k']}) if not chk: await sor.C('appcodes_kv', {'id': getID(), 'parentid': pid, 'k': item['k'], 'v': item['v']}) await sor.sqlExe("COMMIT", {}) print('seeded %d code groups' % len(data.get('appcodes', []))) asyncio.run(seed()) PYEOF # ── 3. wwwroot 软链 + pip install ── rm -f "$APP_ROOT/wwwroot/pipeline-bidding" ln -sf "$SCRIPT_DIR/wwwroot" "$APP_ROOT/wwwroot/pipeline-bidding" echo "wwwroot linked: pipeline-bidding" "$APP_ROOT/py3/bin/pip" install "$SCRIPT_DIR/" 2>&1 | tail -1 echo "pipeline-bidding module deploy complete." echo "后续(宿主负责):app 入口 import load_pipeline_bidding;RBAC 用 scripts/load_path.py;i18n 用 merge_i18n.py。"