📋 概要
2026-05-15 にコード君が実施した mikyun-portal (cloud.k2-o.net) の大規模バグ修繕作業ログ。35 件以上のバグを修正し、新規契約のカード決済フロー end-to-end が完全に動作するように整備した。
🚨 緊急対応
- DEV-001 / DEV-002 peer 消失事故の復旧 — add-child テスト操作で reload_wireguard() が DB の wg_iface='wg0' 旧名残デバイスを除外し、顧客 wan の親端末 (10.200.0.2) と iPhone (10.200.0.30) の WG peer が /etc/wireguard/wg-clients.conf から消失。顧客 VPN 接続が断。/opt/mikyun/output/client-configs/ の stored conf から PrivateKey/PresharedKey を抽出し、PublicKey を再計算して手動再追加 → wg syncconf で復旧。
- DB の wg_iface マイグレーション漏れ補完 — DEV-001/002 を wg_iface='wg-clients' + preshared_key も DB に流し込み、今後の reload_wireguard で再発しないように。
🔒 セキュリティ修繕
- /client/qr/{cid}/{did} および /client/conf/{did} の path traversal — device_id を正規表現で厳格バリデーション
- /client/* ハンドラ 5 か所で contract_id を device_id 文字列分割で導出していた auth bypass を DB ルックアップに置換
- /client/qr に device 所有権検証を追加 — 別契約の device_id を渡すと 403
- /customers エンドポイントが無認証で呼べた件 → X-API-Key (WordPress 用) 必須に
- engine の WireGuard サーバー秘密鍵がハードコードだった件 → WG_CLIENTS_PRIVKEY 環境変数化 (fallback あり)
- 顧客側 portal is_local_admin に Basic 認証 fallback を追加 — admin が非 WG ネットワーク (darkhero など) からでも /client/* を利用可能に
- LOCAL_ADMIN_SUBNETS を拡張 (192.168.30., 10.250.0., 10.251.0. 追加) — R640 SNI relay 経由の admin アクセスを許可
🧱 データ整合性
- engine の全 except Exception ブロック 11 か所に except HTTPException: raise を前置 — 404/422 が 500 に化けるバグを根絶
- allocate_next_ip に SELECT … FOR UPDATE を追加 — 並行 add-child での IP 重複割当を防止
- engine import time 欠落 — Telegram 通知再試行で NameError を起こしていた
- main.py の重複 issue_token / consume_token 削除 — 後発の定義が未定義 _tokens 辞書を参照していたバグ
- delete_device の順序修正 — DB UPDATE を先に commit、WG 後処理は best-effort
- add_child_device の WG / file 書込順序を修正 + 各ステップを try/except でガード — 一部失敗時の orphan を最小化
- main.py /api/billing/init の rowcount チェックを commit より前へ
- /api/contracts/check-username を form / JSON 両対応 + duplicate-key の list 取扱を追加
- main.py の重複 ui_traffic v2 (return 抜けの死コード) を削除
🎨 UI / UX 修繕
- client_ui.py の WG_INTERFACE = wg0 ハードコード → wg-clients へ修正。これだけで顧客側 portal 全機能が復活
- /ui/contracts/{id} に端末追加・削除ボタンを追加 (POST /ui/devices/{did}/delete, /ui/contracts/{cid}/devices/add)
- dashboard の bond0/bond1 ハードコード → bond 名を自動検出 (2026-05-13 のスイッチ再設定で SM-MGMT, SM-STG 等に改名されていた)
- dashboard の get_server_stats.sh の Supermicro/R640 case 論理が逆転 → Supermicro=local, R640/hirame=remote SSH に修正
- SSH に UserKnownHostsFile=/dev/null を付加 (host key 変更耐性)
- maguro 公開鍵を R640 katuo の authorized_keys に追加 → dashboard で R640 stats 取得可能に
- nextcloud-stack-app-1 ハードコード × 4 ファイルを compose-app-1 に修正 — 新規契約時の NC アカウント作成が止まっていた根本原因
- compose.yml に /srv/storage:/srv/storage:ro mount を追加
- Dockerfile に qrencode を永続追加 (container 内に未 install で QR が 22byte テキストに化けていた)
- identity_passphrase のリテラル残骸 (古い API 経由の JSON エスケープ漏れ) を 260403-865C で除去 → 顧客 wan がログインできるように
- client_ui.py の render_parent_passphrase_page(dev=None) による NoneType crash を修正
- get_contract_json で WHERE deleted_at IS NULL を撤廃 + HTTPException 隔離を追加 — terminated 契約も admin が履歴閲覧可能に
- validate_nc_username の policy を緩和 — アンダースコア / ハイフン許可
- ui_admin の confirm() ダイアログから user-data 補間を除去 (XSS ベクタ封じ)
⏱ 永続化 (systemd timer)
- mikyun-traffic-collect.timer — 60 秒毎に traffic_collect_v2.py 実行 (顧客視点 rx=download / tx=upload swap 済、delta + counter reset 検知付き)
- mikyun-traffic-rollover.timer — hourly check で 24h / 7d カウンタを境界跨いだら自動リセット
- 全 115 QR を qrencode で再生成 + 孤児ファイルを掃除 (conf = qr = 115 件で整合)
💳 顧客カード決済フロー検証
WordPress (R640 上 astra-child theme の functions.php) から portal への接続が 10.250.0.2:9000 (Phase 2+3 で消失した WG-storage ルート) を使っていたため、verify-contract, purchase-points, create-subscription, check-username, my-billing の全 WP REST 経路が「接続エラー」になっていた件を判明。10 か所すべて 192.168.10.31:9000 (maguro LAN 直接) に書き換えて即解決。
- 新規契約 (Stripe → /ui/contracts/new → engine /contracts/create) → contract + device + NC アカウント + conf + QR まで自動生成 OK
- サンキューページ (?contract_id=XXX) → /ui/contracts/{id}/info 経由で NC user / password を取得して顧客に表示 OK
- 継続ポイント購入 (/point-my-page/) → verify → Stripe payment_intent → /api/points/charge → 残高加算 OK
- 顧客 /client/login (passphrase 認証) → 契約管理ページ表示 OK
- 顧客 260403-865C wan さんに +59,800pt (10 ヶ月分) を管理者手動チャージ済 (tx#7)
📱 モバイル対応
顧客向け portal の 3 ページ (/point-my-page/, /my-page/, /thanks/) が固定幅 700-800px でスマホで窮屈だった件。wp_head フックで @media (max-width: 768px) ルールを注入。
- コンテナ幅 100% / 左右 margin 8px に
- 3 列 grid (残高/月額/次回支払) を縦 1 列にスタック
- label / value の横並びを縦並びに
- ボタン min-height 48px (タップ領域確保)
- input font-size 16px 維持 (iOS 自動ズーム防止)
- テーブル横スクロール対応
- WP-super-cache パージ済
🔧 その他の点検結果
- DNS は正常 — katuocloud.com は 114.164.250.167 (現在の自宅 WAN IP) と一致、外部 (Tokyo VPS) から HTTP 200 で WP 表示確認済。当初「DNS ステール」と判定したのは Windows 端末からの NAT hairpin による誤検知。
- WP_DEBUG=false 化を R640 本番 + xserver VPS 控え 両方で適用
- ポイント計算 calc_monthly_amount(storage_gb, max_devices) = 1980 + (max_devices-3)×500 + ((storage_gb-200)/100)×500 が DB 値と完全一致 (260403-865C で 1000GB×3端末 = 5,980 円、テスト契約 500GB×5端末 = 4,480 円)
- 契約容量整合 — 親 + 子の storage_quota_gb 合計が contracts.storage_quota_gb と一致 (1000GB = 980 + 10 + 10)
- point_transactions 元帳 も balance_after が累積一致
⚠️ 残課題 (別セッションで対応可)
- create_contract_and_parent の NC 作成失敗時の完全 rollback (現状は部分コミット可能性)
- hirame@192.168.10.40 の authorized_keys に maguro 公開鍵を追加 (dashboard で hirame stats 取得用、別途パスワード要)
- Stripe public key を functions.php から env 化
- ui_admin ui_billing の N+1 query 最適化
- /check-username JS 事前バリデーション (WP signup form 側、policy 緩和で必須ではない)
- R640 別 Code-kun セッションでの Cockpit / Portainer / NPM インストール作業との同時進行ガード (互いの作業領域を住み分け済み)
🗂 バックアップ場所
- maguro: /etc/wireguard/wg-clients.conf.bak.audit-20260515-013213 (peer 復旧前)
- maguro: /root/mikyun-cloud/mikyun-portal/app/main.py.bak.thorough-20260515-033634 等
- maguro: /root/mikyun-cloud/mikyun-portal/mikyun_vpn_engine.py.bak.thorough-20260515-033634 等
- R640: /var/www/katuocloud/wp-content/themes/astra-child/functions.php.bak.audit-20260515
- R640: /var/www/katuocloud/wp-config.php.bak.audit-20260515
- xserver VPS: /var/www/katuocloud/wp-config.php.bak.audit-20260515 (xserver 解約までの控え)
作成: コード君 (Claude) / 2026-05-15
