#!/usr/bin/env bash set -e cdir=$(cd "$(dirname "$0")" && pwd) cd "$cdir" echo "=== Pipeline App Build ===" # 1. Create venv if [ ! -d py3 ]; then python3 -m venv py3 fi source py3/bin/activate # 2. Install foundation packages (clone to pkgs) mkdir -p pkgs for m in apppublic sqlor ahserver bricks-for-python xls2ddl rbac appbase dapi; do echo "install $m ..." cd "$cdir/pkgs" if [ ! -d "$m" ]; then git clone https://git.opencomputing.cn/yumoqing/$m || echo "SKIP: $m clone failed" fi if [ -d "$m" ]; then cd "$m" "$cdir/py3/bin/pip" install . 2>&1 | tail -1 fi cd "$cdir" done # 3. Build bricks frontend cd "$cdir/pkgs" if [ ! -d bricks ]; then git clone https://git.opencomputing.cn/yumoqing/bricks || true fi if [ ! -f bricks/dist/bricks.js ]; then mkdir -p bricks/dist if [ -d bricks/bricks ]; then cd bricks/bricks bash build.sh 2>&1 | tail -3 cd "$cdir/pkgs" fi fi # Link dist to wwwroot rm -rf "$cdir/wwwroot/bricks" 2>/dev/null ln -sf "$cdir/pkgs/bricks/dist" "$cdir/wwwroot/bricks" echo "bricks: $(readlink "$cdir/wwwroot/bricks")" # 4. Move local business modules into pkgs/ cd "$cdir" for mod in pipeline_core pipeline_ops pipeline_dist; do if [ -d "$mod" ]; then mv "$mod" "pkgs/$mod" fi done # 5. Install business modules (clone external, local already in pkgs) for mod in pipeline-sdlc showcase evaluate pipeline-service pipeline-task tenant app_audit product_management discount pricing unipay smssend accounting; do cd "$cdir/pkgs" if [ ! -d "$mod" ]; then git clone https://git.opencomputing.cn/yumoqing/$mod || echo "SKIP: $mod" fi cd "$cdir" done # 6. pip install all modules from pkgs/(pipeline-service 是普通 install 非 editable,部署必 pull+pip install 都做) for mod in pipeline_core pipeline_ops pipeline_dist pipeline-sdlc showcase pipeline-service tenant app_audit product_management discount pricing unipay smssend accounting; do if [ -d "pkgs/$mod" ]; then "$cdir/py3/bin/pip" install "pkgs/$mod/" 2>&1 | tail -1 fi done # 6c. Runtime deps: pymupdf(office-text-extract 技能的 pdf 提取脚本依赖) "$cdir/py3/bin/pip" install pymupdf 2>&1 | tail -1 # 6b. Deploy skills: global(public skills) + packs(skill pack manifests) if [ -d "pkgs/pipeline_core/skills_library" ]; then mkdir -p "$cdir/skills/global" # 公共技能:预置技能全量复制到 global(所有 agent 默认加载,目录层按触发词匹配,正文按需 load_skill) cp -r "pkgs/pipeline_core/skills_library/all/." "$cdir/skills/global/" # 技能集清单(manifest 引用 global 里的技能名) rm -rf "$cdir/skills/packs" cp -r "pkgs/pipeline_core/skills_library/packs" "$cdir/skills/packs" # 产线技能:pipelines/{pipeline_id}/common/ + roles/{role}/(SDLC 等产线默认规范,loader 按此路径扫描) rm -rf "$cdir/skills/pipelines" if [ -d "pkgs/pipeline_core/skills_library/pipelines" ]; then cp -r "pkgs/pipeline_core/skills_library/pipelines" "$cdir/skills/pipelines" echo " skills: pipelines(产线技能) 已部署,共 $(ls "$cdir/skills/pipelines" | wc -l) 个产线" fi echo " skills: global(公共技能)+packs(技能集) 已部署,global 共 $(ls "$cdir/skills/global" | wc -l) 个技能" fi # 7. Regenerate CRUD from json for all pipeline modules for mod in appbase pipeline_core pipeline_ops pipeline_dist pipeline-sdlc rbac dapi accounting; do json_dir="pkgs/$mod/json" models_dir="pkgs/$mod/models" wwwroot_dir="pkgs/$mod/wwwroot" if [ -d "$json_dir" ] && ls "$json_dir"/*.json >/dev/null 2>&1; then cd "$json_dir" "$cdir/py3/bin/xls2ui" -m "../models" -o "../wwwroot" "$mod" *.json 2>&1 | grep -c 'handle' | xargs -I{} echo " xls2ui $mod: {} tables" cd "$cdir" fi done # 8. Link module wwwroot directories (symlinks from pkgs/) for mod in pipeline-sdlc showcase tenant pipeline_core pipeline_ops pipeline_dist appbase app_audit product_management discount pricing unipay rbac dapi accounting; do src="pkgs/$mod/wwwroot" dst="wwwroot/$mod" if [ -d "$src" ]; then rm -rf "$dst" 2>/dev/null ln -sf "../pkgs/$mod/wwwroot" "$dst" echo " wwwroot: $mod linked" fi done # pipeline_task has no separate module mkdir -p wwwroot/pipeline_task # 9. Sync password_key from Sage (not databases — those are app-specific) if [ -f /d/apitest/sage/conf/config.json ]; then python3 -c " import json cs=json.load(open('/d/apitest/sage/conf/config.json')) c=json.load(open('$cdir/conf/config.json')) c['password_key']=cs.get('password_key','') json.dump(c,open('$cdir/conf/config.json','w'),ensure_ascii=False,indent=2) print(' config: password_key synced') " fi # 10. Create runtime dirs mkdir -p "$cdir/logs" "$cdir/files" "$cdir/conf" "$cdir/bin" # 10.5 Install bwrap (zero-root: 系统已有则复用,否则 apt download + dpkg -x) if [ ! -x "$cdir/bin/bwrap" ]; then if command -v bwrap >/dev/null 2>&1; then cp "$(command -v bwrap)" "$cdir/bin/bwrap" 2>/dev/null || true else tmpd=$(mktemp -d) ( cd "$tmpd" && apt download bubblewrap >/dev/null 2>&1 && dpkg -x bubblewrap*.deb . && cp usr/bin/bwrap "$cdir/bin/bwrap" ) 2>/dev/null || true rm -rf "$tmpd" fi fi if [ -x "$cdir/bin/bwrap" ]; then echo " bwrap: $("$cdir/bin/bwrap" --version 2>/dev/null | head -1)" else echo " WARN: bwrap not available (deploy sandbox will degrade to dir isolation)" fi # 10.8 Install Node(office 技能脚本依赖,无 sudo:官方预编译二进制) # 解压到 bin/nodejs/,symlink node/npm/npx 到 bin/(start.sh 已把 $cdir/bin 加进 PATH) NODE_VER="v22.23.2" if [ ! -x "$cdir/bin/nodejs/bin/node" ]; then echo " node: downloading $NODE_VER (linux-x64) ..." tmpn=$(mktemp -d) if curl -sL --max-time 180 "https://nodejs.org/dist/$NODE_VER/node-$NODE_VER-linux-x64.tar.xz" -o "$tmpn/node.tar.xz" && \ tar -xJf "$tmpn/node.tar.xz" -C "$tmpn"; then mkdir -p "$cdir/bin/nodejs" cp -a "$tmpn/node-$NODE_VER-linux-x64/." "$cdir/bin/nodejs/" else echo " WARN: node 下载失败(office 技能脚本不可用)" fi rm -rf "$tmpn" fi ln -sf nodejs/bin/node "$cdir/bin/node" ln -sf nodejs/bin/npm "$cdir/bin/npm" ln -sf nodejs/bin/npx "$cdir/bin/npx" if [ -x "$cdir/bin/node" ]; then echo " node: $("$cdir/bin/node" --version)" fi # 6. Fix auto-assign created_by in CRUD dspys (xls2ui overwritten) for d in sd_projects sd_iterations; do for t in add update; do f="$cdir/wwwroot/pipeline-sdlc/$d/${t}_${d}.dspy" [ -f "$f" ] && grep -q 'created_by' "$f" || sed -i "s/ns\['org_id'\] = userorgid/ns['org_id'] = userorgid\nns['created_by'] = userorgid/" "$f" done done # 6b. Inject session-current-project filter into generated get dspys (迭代/Bug/测试用例 用会话当前项目,无项目报错) if [ -f "$cdir/scripts/inject_project_filter.py" ]; then "$cdir/py3/bin/python" "$cdir/scripts/inject_project_filter.py" "$cdir/wwwroot/pipeline-sdlc" || echo " WARN: inject_project_filter.py failed" fi # 10.6 Create module tables (models -> DDL -> execute) echo "=== Module table creation ===" if [ -f "$cdir/scripts/create_tables.py" ]; then "$cdir/py3/bin/python" "$cdir/scripts/create_tables.py" || echo " WARN: create_tables.py failed (DB not ready?)" else echo " WARN: scripts/create_tables.py not found" fi # 10.7 Import module init data (appcodes dictionaries) echo "=== Module init data import ===" if [ -f "$cdir/scripts/import_init.py" ]; then "$cdir/py3/bin/python" "$cdir/scripts/import_init.py" || echo " WARN: import_init.py failed (DB not ready?)" else echo " WARN: scripts/import_init.py not found" fi # 11. Import RBAC permissions from conf/rp.json (r:p 数据 -> permission + rolepermission) echo "=== RBAC permission import ===" if [ -f "$cdir/scripts/import_rp.py" ]; then "$cdir/py3/bin/python" "$cdir/scripts/import_rp.py" || echo " WARN: import_rp.py failed (DB not ready?)" else echo " WARN: scripts/import_rp.py not found" fi # 11.5 汇率自动获取定时任务(每日 8:00 抓取中国银行外汇牌价) # accounting 模块 fetch_forex_rates.dspy:USD/JPY/GBP → CNY,幂等写入 exchange_rate if [ -f "$cdir/pkgs/accounting/wwwroot/api/fetch_forex_rates.dspy" ]; then forex_cron="0 8 * * * curl -s http://localhost:9090/accounting/api/fetch_forex_rates.dspy" cc=$(crontab -l 2>/dev/null || true) if ! echo "$cc" | grep -Fq "fetch_forex_rates"; then (echo "$cc"; echo "$forex_cron") | crontab - echo " cron: 汇率任务已添加(每日 8:00)" else echo " cron: 汇率任务已存在,未重复添加" fi fi chmod +x "$cdir/start.sh" "$cdir/stop.sh" 2>/dev/null || true echo "=== Build complete ==="