Files
ISEP/scripts/deploy-web.sh
Leo c2638668e3 ISEP 0.1.0:環境設定收成一個 plugin,本機與雲端共用一份
leo 2026-08-20:「同一個 plugin 你用,薄殼也用,保證兩邊同步」
              「我要你幫雲端做薄殼,永遠都有問題,你要做的就是這組設定
                你自己可以 dogfooding」

搬進來:41 支 hook(51 條註冊)/7 支 command/2 支 skill/23 支腳本。
不搬 .env、wiki、docs——那些是知識不是環境。

51 條 hook 路徑全部從 $CLAUDE_PROJECT_DIR/.claude/hooks/ 改成 ${CLAUDE_PLUGIN_ROOT}/hooks/,
零漏網。那正是薄殼一直壞掉的根:雲端 cwd 不是真身,寫死路徑就斷。

尚未驗證:Claude Code 能不能從私有 Gitea repo 裝 marketplace(要憑證)。
下一步就是在本機實際裝一次,通了才動雲端 bootstrap.sh。

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-20 11:41:46 +08:00

129 lines
7.4 KiB
Bash
Executable File
Raw Permalink 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.
#!/bin/bash
# deploy-web.sh — install/landing 的唯一部署入口(2026-07-28 立)
#
# 為什麼存在:leo 問「以後會出現改了沒推的問題嗎?」誠實答案=文字規範會被忘,
# 只有機制可靠。本腳本把「驗語法→部署→驗版本有變→抓線上→記錄」焊成一步——
# 用它部署,就不可能出現 t79 那種「commit 了但部署失敗沒人發現」。
#
# 用法:scripts/deploy-web.sh installer|landing
# 部署狀態記錄在 system-dev/docs/4-guides/deploy-state.json(誰、何時、版本、檔案 sha)
# ⇒ 「改了沒推」隨時可查:比對該檔現在的 sha 與記錄裡的 sha。
set -euo pipefail
cd "$(dirname "$0")/.."
TOP="$(pwd)"
STATE="$TOP/system-dev/docs/4-guides/deploy-state.json"
TARGET="${1:-}"
# 部署源可用 DEPLOY_SRC 覆蓋(07-30:舊寫法寫死 scratchpad 且靜默 fallback
# scratchpad 是 session 專屬目錄,被清掉後部署源就斷——事故傳導路徑,故必印出本次部署源)
SCRATCH="${DEPLOY_SRC:-/private/tmp/claude-501/-Users-youlinhsieh-Documents-tech-projects-InkStoneCo/92a75156-c295-4c79-bfeb-de1a20e4ed26/scratchpad}"
case "$TARGET" in
installer)
DIR="$SCRATCH/rag-installer/installer/oauth-prototype"
if [ ! -d "$DIR" ]; then
echo "⚠️ 部署源 $DIR 不存在(scratchpad 被清?),改用 git 工作樹那份"
DIR="$TOP/products/arcrun-rag/installer/oauth-prototype"
[ -d "$DIR" ] || { echo "❌ 備援部署源也不存在(products 的 checkout 不在含 oauth-prototype 的分支);用 DEPLOY_SRC= 指定部署源"; exit 1; }
fi
FILE="$DIR/worker.js"; CFG="--config ./wrangler.toml"; URL="https://install.arcrun.dev/" ;;
landing)
DIR="$TOP/products/arcrun-rag/landing"
FILE="$DIR/worker.js"; CFG=""; URL="https://rag.arcrun.dev/" ;;
*) echo "用法:$0 installer|landing" >&2; exit 2 ;;
esac
echo "▶ 本次部署源=$DIR"
echo "① esbuild 驗語法(node --check 抓不到 template literal 內的錯,t79 教訓)"
# --external:cloudflare:* Workers runtime 內建模組(如 cloudflare:email),
# esbuild 不認識它們但 wrangler 認得;不標 external 會誤判成語法錯(07-29 landing 踩到)。
( cd "$DIR" && npx esbuild worker.js --bundle --format=esm --outfile=/dev/null \
--external:cloudflare:* --external:./migrations.json --external:./workflows.json 2>/dev/null \
|| npx esbuild worker.js --bundle --format=esm --outfile=/dev/null --external:cloudflare:* )
echo "①a 釘死 URL 必須真的存在(07-29 事故:釘碼用短碼拼湊出不存在的 commit,安裝全 404"
if [ "$TARGET" = "installer" ]; then
BASE=$(grep -o "https://cdn.jsdelivr.net/gh/[^']*" "$FILE" | head -1)
CODE=$(curl -s -m 30 -o /dev/null -w '%{http_code}' "$BASE/manifest.json")
[ "$CODE" = "200" ] || { echo "❌ BUNDLE_BASE 指向的 manifest 回 $CODE(釘碼錯或 bundle 沒推):$BASE"; exit 1; }
echo " $BASE/manifest.json → 200"
fi
echo "①b 文案契約測試(防「改好的又改錯」——禁句出現=拒絕部署)"
if [ -f "$DIR/copy-contract.test.mjs" ]; then ( cd "$DIR" && node copy-contract.test.mjs ); fi
echo "①c 防回退閘(07-30:版本號變動必須是人明示的決定,不能是部署源掉包的副作用)"
LIVE_VER="$(curl -s -m 25 "${URL}?cb=$RANDOM" | grep -o '20[0-9][0-9]-[0-9][0-9]-[0-9][0-9]+[0-9a-f]\{7\}' | head -1 || true)"
if [ "$TARGET" = "installer" ]; then
NEXT_BUILT="$(grep -o "BUNDLE_BUILT = '[^']*'" "$FILE" | head -1 | sed "s/.*'\(.*\)'/\1/" || true)"
NEXT_PIN="$(grep -o 'arcrun-rag-bundles@[0-9a-f]*' "$FILE" | head -1 | cut -d@ -f2 | cut -c1-7 || true)"
{ [ -n "$NEXT_BUILT" ] && [ -n "$NEXT_PIN" ]; } && NEXT_VER="${NEXT_BUILT}+${NEXT_PIN}" || NEXT_VER=""
else
NEXT_VER="$(grep -o 'SITE_BUNDLE_VERSION = "[^"]*"' "$DIR/wrangler.toml" 2>/dev/null | sed 's/.*"\(.*\)"/\1/' || true)"
fi
if [ -n "$LIVE_VER" ] && [ -n "$NEXT_VER" ] && [ "$LIVE_VER" != "$NEXT_VER" ] && [ "${ALLOW_VERSION_CHANGE:-}" != "1" ]; then
echo "❌ 版本號會變:線上 $LIVE_VER → 要部署 $NEXT_VER"
echo " 換版是人的決定:確定要換,重跑時帶 ALLOW_VERSION_CHANGE=1"
exit 1
fi
echo " 線上 ${LIVE_VER:-(抓不到)} / 待部 ${NEXT_VER:-(推導不出)}$([ "${ALLOW_VERSION_CHANGE:-}" = "1" ] && echo '(已明示換版)')"
echo "①d 部署源必須落在 git07-30:t143-t150 十筆只在本機沒推、雲端誤判程式碼失蹤的教訓)"
PENDING_GIT=false
if git -C "$DIR" rev-parse --is-inside-work-tree >/dev/null 2>&1; then
DIRTY="$(git -C "$DIR" status --porcelain -- "$(basename "$FILE")" | head -1 || true)"
UNPUSHED="$(git -C "$DIR" log --oneline '@{u}..HEAD' 2>/dev/null | wc -l | tr -d ' ' || true)"
[ -n "$DIRTY" ] && { echo " ⚠️⚠️ $(basename "$FILE") 有未 commit 修改——部署的東西 git 裡沒有!"; PENDING_GIT=true; }
if [ "$UNPUSHED" = "" ] || ! git -C "$DIR" rev-parse '@{u}' >/dev/null 2>&1; then
echo " ⚠️⚠️ 所在分支沒設 upstream——commit 了也沒人推得到 Gitea"; PENDING_GIT=true
elif [ "$UNPUSHED" != "0" ]; then
echo " ⚠️⚠️ 有 $UNPUSHED 筆 commit 未推上 Giteagit log @{u}..HEAD"; PENDING_GIT=true
fi
$PENDING_GIT || echo " 部署源乾淨且已同步 Gitea ✓"
else
echo " ⚠️⚠️ 部署源不在任何 git 工作樹裡——這份程式碼沒有版控!"; PENDING_GIT=true
fi
# 封測期不擋出貨,但留痕:pending_git=true =「線上跑的和 Gitea 不一致」查得到
STATE_PATH="$STATE" python3 - "$TARGET" "$PENDING_GIT" <<'PY'
import json,sys,os
p=os.environ.get('STATE_PATH'); t,pg=sys.argv[1],sys.argv[2]=='true'
try: d=json.load(open(p))
except Exception: d={}
d.setdefault(t,{})['pending_git']=pg
json.dump(d,open(p,'w'),ensure_ascii=False,indent=2)
PY
echo "② 部署(uncle6"
set -a; source "$TOP/.env" 2>/dev/null || true; set +a
OUT="$(cd "$DIR" && CLOUDFLARE_ACCOUNT_ID=58309bb90fd93ad6d0fe0aae99170e9d npx wrangler deploy $CFG 2>&1)"
VID="$(printf '%s' "$OUT" | grep -o 'Current Version ID: [a-f0-9-]*' | awk '{print $4}')"
[ -n "$VID" ] || { echo "❌ 部署失敗(沒有 Version ID):"; printf '%s\n' "$OUT" | tail -8; exit 1; }
echo " Version ID: $VID"
echo "③ 版本必須有變(防『部署了但還是舊版』)"
PREV="$(python3 -c "
import json,sys
try: print(json.load(open('$STATE')).get('$TARGET',{}).get('version',''))
except Exception: print('')" 2>/dev/null)"
if [ "$VID" = "$PREV" ] && [ -n "$PREV" ]; then
echo "❌ Version ID 與上次相同($VID)=內容沒變或部署被跳過"; exit 1
fi
echo "④ 線上實測(帶 cache-buster"
CODE="$(curl -s -m 25 -o /tmp/deploy_check.html -w '%{http_code}' "${URL}?cb=$RANDOM")"
[ "$CODE" = "200" ] || { echo "❌ 線上回 $CODE"; exit 1; }
echo " $URL → 200$(wc -c </tmp/deploy_check.html | tr -d ' ') B"
echo "⑤ 記錄(供 drift 檢查:檔案 sha ≠ 記錄 sha = 改了沒推)"
SHA="$(shasum -a 256 "$FILE" | cut -d' ' -f1)"
STATE_PATH="$STATE" python3 - "$TARGET" "$VID" "$SHA" <<'PY'
import json,sys,datetime,os
p=os.environ.get('STATE_PATH')
t,v,sha=sys.argv[1],sys.argv[2],sys.argv[3]
try: d=json.load(open(p))
except Exception: d={}
d[t]={'version':v,'file_sha256':sha,'deployed_at':datetime.datetime.now().isoformat(timespec='seconds')}
json.dump(d,open(p,'w'),ensure_ascii=False,indent=2)
print(f" {t}: {v} / sha {sha[:16]}")
PY
echo "✅ 完成。之後查「改了沒推」:shasum 該檔 vs $STATE"