fix(ci): 配信先 appdevelopsk.com が main と一致しているか検証する #28
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| # GitHub Pages の配信元 (pages-blank) を main から自動で組み立てる。 | |
| # | |
| # 2026-08-22: 配信元を pages-blank に切り替えた際 privacy-policy/ を持ち込まなかった | |
| # ため、Play Console に申告済みの全 URL が 404 になり、ポリシー違反通知が届いた。 | |
| # ファイルは main に残っていたので「存在する=配信されている」と取り違えられた。 | |
| # 人手で2ブランチを揃える運用は同じ事故を必ず繰り返すので、機械で揃える。 | |
| name: sync pages-blank | |
| on: | |
| push: | |
| branches: [main] | |
| paths: ['privacy-policy/**', 'support/**', '.github/workflows/sync-pages-blank.yml'] | |
| schedule: | |
| - cron: '17 3 * * *' # 毎日、配信状態そのものを見張る | |
| workflow_dispatch: | |
| permissions: | |
| contents: write | |
| jobs: | |
| sync: | |
| runs-on: ubuntu-latest | |
| steps: | |
| - uses: actions/checkout@v4 | |
| with: { ref: main, fetch-depth: 0 } | |
| - name: pages-blank を組み立て直す | |
| run: | | |
| set -euo pipefail | |
| git config user.name "github-actions[bot]" | |
| git config user.email "github-actions[bot]@users.noreply.github.com" | |
| # 集約先は appdevelopsk.com のままにしたいので、ルートは転送スタブ、 | |
| # 法務ページは canonical(.com) + noindex で配信する。 | |
| tmp=$(mktemp -d); cp -R privacy-policy support "$tmp/" | |
| git checkout pages-blank | |
| rm -rf privacy-policy support | |
| cp -R "$tmp/privacy-policy" "$tmp/support" . | |
| python3 - <<'PY' | |
| import pathlib | |
| for p in list(pathlib.Path('.').glob('privacy-policy/*/index.html')) + \ | |
| list(pathlib.Path('.').glob('support/*/index.html')): | |
| s = p.read_text() | |
| if 'name="robots"' not in s: | |
| p.write_text(s.replace('<head>', '<head>\n<meta name="robots" content="noindex, follow">', 1)) | |
| PY | |
| git add privacy-policy support | |
| git diff --cached --quiet || git commit -m "pages: main の privacy/support を配信元へ同期" | |
| git push origin pages-blank | |
| - name: 申告先 URL が実際に 200 か確かめる | |
| run: | | |
| set -euo pipefail | |
| sleep 60 # Pages のビルド待ち | |
| fail=0 | |
| for d in privacy-policy/*/ support/*/; do | |
| c=0 | |
| # Pages のビルド反映や CDN 側の一時的な 503 で誤検知しないよう、 | |
| # 200 が返るか上限に達するまで間隔を空けて再確認する。 | |
| for i in 1 2 3 4 5; do | |
| c=$(curl -s -o /dev/null -w '%{http_code}' "https://appdevelopsk.github.io/$d") | |
| [ "$c" = 200 ] && break | |
| sleep 15 | |
| done | |
| [ "$c" = 200 ] || { echo "NG $c $d"; fail=$((fail+1)); } | |
| done | |
| echo "404: $fail 件" | |
| [ "$fail" -eq 0 ] | |
| # 2026-09-12: 上の検証は github.io しか見ておらず、**実際の配信先である | |
| # appdevelopsk.com を一度も確認していなかった**。独自ドメインは GitHub Pages では | |
| # なく VPS(nginx, root /var/www/html) が配信しており、そこへの同期機構が無いため、 | |
| # 9/9 に直したポリシーが本番では旧版のまま3日間配信され続けた(Play 申告URLは .com 側)。 | |
| # ジョブは全て success だったので誰も気づけなかった。 | |
| # 鍵を Secrets に置きたくないので同期は手動 rsync のままとし、 | |
| # **乖離したらここで落として気づけるようにする**のが役割。 | |
| # 復旧手順: | |
| # rsync -av privacy-policy/ pickly-vps:/var/www/html/privacy-policy/ (--delete は付けない) | |
| - name: 配信先 appdevelopsk.com が main と一致しているか確かめる | |
| run: | | |
| set -euo pipefail | |
| UA='Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 Chrome/131.0 Safari/537.36' | |
| miss=0; stale=0 | |
| for d in privacy-policy/*/ support/*/; do | |
| url="https://appdevelopsk.com/$d" | |
| # nginx 側に limit_req が入っており、連続アクセスすると弾かれて | |
| # 接続失敗(000)や429になる。2026-09-12 実測で317件の連打中1件が000だったが、 | |
| # 単独の再試行では200。CI を落とすのは「本当に落ちている」時だけにしたいので、 | |
| # 間隔を空けて3回まで試す。 | |
| c=000 | |
| for try in 1 2 3; do | |
| c=$(curl -s -A "$UA" -o /tmp/live.html -w '%{http_code}' "$url" || echo 000) | |
| [ "$c" = 200 ] && break | |
| sleep 3 | |
| done | |
| if [ "$c" != 200 ]; then echo "NG $c $url"; miss=$((miss+1)); continue; fi | |
| sleep 0.3 # レート制限に当てないための間隔 | |
| # 本文(可視テキスト)の一致だけを見る。head 内の meta/link は | |
| # 本番にだけ後から足されたものが在り(canonical・description、2026-09-12 実測9件)、 | |
| # 生のHTML比較では全件 STALE になって使い物にならない。 | |
| # script/style/head を落とし、タグと空白を潰してから比べる。 | |
| if ! python3 - "$url" /tmp/live.html "${d}index.html" <<'PY'; then | |
| import re, sys, html as H | |
| def vis(p): | |
| s = open(p, encoding='utf-8', errors='replace').read() | |
| s = re.sub(r'<head[\s\S]*?</head>|<script[\s\S]*?</script>|<style[\s\S]*?</style>', ' ', s, flags=re.I) | |
| return re.sub(r'\s+', ' ', H.unescape(re.sub(r'<[^>]+>', ' ', s))).strip() | |
| url, live, repo = sys.argv[1], sys.argv[2], sys.argv[3] | |
| if vis(live) != vis(repo): | |
| print(f'STALE {url}') | |
| sys.exit(1) | |
| PY | |
| stale=$((stale+1)) | |
| fi | |
| done | |
| echo "到達不能: $miss 件 / 内容が古い: $stale 件" | |
| if [ $((miss+stale)) -ne 0 ]; then | |
| echo "::error::appdevelopsk.com の配信が main と一致していない。VPS へ rsync が必要(詳細はこのステップのコメント)" | |
| exit 1 | |
| fi |