feat: CI 流水线 — lint + 单测 + OpenAPI 校验 + 脱敏扫描 + 镜像构建 [tsk_P5b5nIrEsfrV]
新增 .gitea/workflows/ci.yml 五个 Job:
1. lint — shellcheck -S warning 扫描全部 deploy/ shell 脚本
2. unit-test — docker-compose config 语法校验 + nginx -t(桩证书)
3. openapi-check — openapi-spec-validator 验证 design/server/openapi.yaml
4. redline-scan — ci/scan-redline.sh 扫描 UI 文案红线词(design/ jsx/dart/html)
5. image-build — docker build pangolin-edge:ci
附带:
- ci/scan-redline.sh:脱敏扫描脚本,过滤注释行与外部渠道 handle
- ci/nginx-test.sh:自签桩证书 + nginx -t,CI 免依赖真实 Let's Encrypt
- design/server/openapi.yaml:依据 ARCHITECTURE.md §3 展开的 OAS 3.0 完整契约
- dparts.jsx / parts.jsx:修复 killSwitchSub EN 文案「the VPN drops」红线词
→ 改为「connection drops」(行为描述,不提产品类别)
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -0,0 +1,46 @@
|
||||
#!/usr/bin/env bash
|
||||
# nginx-test.sh — CI 环境下校验 nginx 配置,用自签桩证书代替真实 Let's Encrypt 证书。
|
||||
# 依赖: docker、openssl
|
||||
set -euo pipefail
|
||||
|
||||
REPO_ROOT="$(cd "$(dirname "$0")/.." && pwd)"
|
||||
CERT_DIR="$(mktemp -d)"
|
||||
|
||||
cleanup() {
|
||||
rm -rf "$CERT_DIR"
|
||||
}
|
||||
trap cleanup EXIT INT TERM
|
||||
|
||||
echo "→ 生成自签桩证书 ..."
|
||||
|
||||
# 三个需要证书的域名
|
||||
for domain in vpn.51yanmei.com blog.51yanmei.com jiu.51yanmei.com; do
|
||||
mkdir -p "${CERT_DIR}/live/${domain}"
|
||||
openssl req -x509 -nodes -newkey ec -pkeyopt ec_paramgen_curve:P-256 -days 1 \
|
||||
-keyout "${CERT_DIR}/live/${domain}/privkey.pem" \
|
||||
-out "${CERT_DIR}/live/${domain}/fullchain.pem" \
|
||||
-subj "/CN=${domain}" 2>/dev/null
|
||||
done
|
||||
|
||||
# options-ssl-nginx.conf 桩(nginx -t 仅检查文件可读,不校验内容)
|
||||
printf '# stub\n' > "${CERT_DIR}/options-ssl-nginx.conf"
|
||||
|
||||
# ssl-dhparams.pem 桩(生成 512-bit DH,足够 nginx 解析,CI 不要求安全强度)
|
||||
openssl dhparam -out "${CERT_DIR}/ssl-dhparams.pem" 512 2>/dev/null
|
||||
|
||||
# 桩目录(nginx alias/root 在 -t 时不校验是否存在,但 volume 挂载需目录存在)
|
||||
mkdir -p "${CERT_DIR}/jiu/images" "${CERT_DIR}/jiu/web" "${CERT_DIR}/jiu/marketing"
|
||||
mkdir -p "${CERT_DIR}/www-pay"
|
||||
|
||||
echo "→ 运行 nginx -t ..."
|
||||
|
||||
docker run --rm \
|
||||
-v "${REPO_ROOT}/deploy/edge/nginx.conf:/etc/nginx/nginx.conf:ro" \
|
||||
-v "${REPO_ROOT}/deploy/edge/stream.conf:/etc/nginx/stream.conf:ro" \
|
||||
-v "${REPO_ROOT}/deploy/edge/conf.d:/etc/nginx/conf.d:ro" \
|
||||
-v "${CERT_DIR}:/etc/letsencrypt:ro" \
|
||||
-v "${CERT_DIR}/jiu:/opt/jiu:ro" \
|
||||
-v "${CERT_DIR}/www-pay:/var/www/pay:ro" \
|
||||
nginx:1.27-alpine nginx -t
|
||||
|
||||
echo "✅ nginx 配置校验通过。"
|
||||
@@ -0,0 +1,84 @@
|
||||
#!/usr/bin/env bash
|
||||
# scan-redline.sh — 脱敏红线词扫描(UI 文案资源)。
|
||||
#
|
||||
# 扫描范围: design/ui_kits/**/*.{jsx,js,html}
|
||||
# design/flutter/**/*.{dart,html}
|
||||
# 不扫描: README.md、CLAUDE.md 等纯文档文件
|
||||
#
|
||||
# 红线词(来源: design/CLAUDE.md §1 铁律 13):
|
||||
# VPN 翻墙 科学上网 突破封锁 自由穿越 Go anywhere
|
||||
#
|
||||
# 允许例外:
|
||||
# - 纯注释行 (// ... 或 /* ... 或 * ... 开头)
|
||||
# - 外部渠道 handle (@PangolinVPN_bot / @pangolinvpn 等)
|
||||
#
|
||||
# 发现违规 → 非零退出码 (CI fail)。
|
||||
set -euo pipefail
|
||||
|
||||
REPO_ROOT="$(cd "$(dirname "$0")/.." && pwd)"
|
||||
cd "$REPO_ROOT"
|
||||
|
||||
VIOLATIONS=0
|
||||
SCANNED=0
|
||||
|
||||
# 逐文件扫描函数
|
||||
# 用法: scan_file <文件路径>
|
||||
scan_file() {
|
||||
local file="$1"
|
||||
local file_violations=0
|
||||
|
||||
# 逐个红线词扫描
|
||||
for word in 'VPN' '翻墙' '科学上网' '突破封锁' '自由穿越' '[Gg]o anywhere'; do
|
||||
# 1. 找出含有红线词的行
|
||||
# 2. 排除纯注释行 (行首可选空白 + // 或 /* 或 *)
|
||||
# 3. 排除外部渠道 handle 行 (@PangolinVPN_bot / @pangolinvpn)
|
||||
local hits
|
||||
hits="$(grep -nE "$word" "$file" 2>/dev/null \
|
||||
| grep -vE '^[0-9]+:[[:space:]]*(//|/\*|\*)' \
|
||||
| grep -vE '@[Pp]angolin[Vv][Pp][Nn]_?[Bb]ot' \
|
||||
| grep -vE '@pangolinvpn' \
|
||||
|| true)"
|
||||
|
||||
if [ -n "$hits" ]; then
|
||||
if [ "$file_violations" -eq 0 ]; then
|
||||
echo "❌ $file" >&2
|
||||
fi
|
||||
echo "$hits" | while IFS= read -r line; do
|
||||
printf ' 红线词 [%s] %s\n' "$word" "$line" >&2
|
||||
done
|
||||
file_violations=1
|
||||
fi
|
||||
done
|
||||
|
||||
return "$file_violations"
|
||||
}
|
||||
|
||||
echo "→ 开始脱敏扫描 ..."
|
||||
|
||||
# 扫描 design/ui_kits 下的 jsx / js / html 文件
|
||||
while IFS= read -r -d '' f; do
|
||||
SCANNED=$((SCANNED + 1))
|
||||
if ! scan_file "$f"; then
|
||||
VIOLATIONS=$((VIOLATIONS + 1))
|
||||
fi
|
||||
done < <(find design/ui_kits -type f \( -name "*.jsx" -o -name "*.js" -o -name "*.html" \) -not -name "README.md" -print0)
|
||||
|
||||
# 扫描 design/flutter 下的 dart / html 文件
|
||||
while IFS= read -r -d '' f; do
|
||||
SCANNED=$((SCANNED + 1))
|
||||
if ! scan_file "$f"; then
|
||||
VIOLATIONS=$((VIOLATIONS + 1))
|
||||
fi
|
||||
done < <(find design/flutter -type f \( -name "*.dart" -o -name "*.html" \) -not -name "README.md" -print0)
|
||||
|
||||
echo "→ 扫描完成:共 ${SCANNED} 个文件,${VIOLATIONS} 个违规。"
|
||||
echo ""
|
||||
|
||||
if [ "$VIOLATIONS" -ne 0 ]; then
|
||||
echo "脱敏检查失败!请根据 design/CLAUDE.md §1 铁律 13 修改以上文案:" >&2
|
||||
echo " - 禁用词:VPN、翻墙、科学上网、突破封锁、自由穿越、Go anywhere" >&2
|
||||
echo " - 代用词:网络加速、极速畅连、稳定、加速线路、隐私保护、无日志" >&2
|
||||
exit 1
|
||||
fi
|
||||
|
||||
echo "✅ 脱敏扫描通过 — 未发现红线词。"
|
||||
Reference in New Issue
Block a user