← 一覧に戻る

kowai-operation

GitHub ↗ Python 最終push: 2026/6/9 18:49

WIP(現在進行中)

Work In Progress

このプロジェクトで現在進行中の作業と、過去のスナップショットを記録する。

現在の状況

Android クラッシュ修正完了(2026-06-09)

5/24 の Lightsail 移管以降に発生していた Android クラッシュ(Enter ボタン押下・詳細ページ遷移)を緊急修正。エミュレーターでの検証で OK 確認。

  • 原因 1: nginx の HTTPS 強制が、旧アプリの http://kh.thomsonsapp.com ハードコードと衝突 → /khApi/* のみ HTTP パススルーに戻して救済
  • 原因 2: is_html / is_info を str に変えていたのが Android Realm モデル Boolean 定義と不整合 → bool に戻し、S3 上の既存 4189 件も bool 版で全件再 put

いま見守り中:

  • Play Console / レビュー欄でクラッシュ報告が収まるか(5/29 / 5/31 のレビュー以降)
  • OneSignal 予約配信が予定時刻に届くか
  • cron / S3 同期が継続するか

次にやること(任意・優先度低め):

  • Route53 の子 hosted zone kh.thomsonsapp.com の削除(親に一本化済み・月 $0.50 節約)
  • 旧 Cake (fn-* EC2) の KhApi コントローラ・関連コード・vhost のクリーンアップ
  • 次の iOS / Android リリースで baseURL を https:// に統一 → 旧 80 番 /khApi/ パススルーを撤去
  • 次の Android リリースで MainActivity.kt:232getBoolean(2)getInt(2) != 0 に変更(サーバー側型変更への耐性向上)

ペンディング(中期):

  • sendPushNotification 配信実績(errored 件数)の検知・アラート
  • OneSignal SDK の更新検討
  • kowai-android: Realm 10.19.0 からの脱却(MongoDB が EOL 化)

過去のWIPアーカイブ

(新しい「現在の状況」を書く前に、古いものをここに追記でアーカイブする。新しいものが上)

2026-06-09 18:00 時点のスナップショット

kowai サーバー機能の独立 Lightsail 移管が完了(2026-05-24)

旧 Cake (fn-* EC2、footballnext と同居) で動いていた /khApi/*/fearstoryPortal/* 一式を、新 Lightsail インスタンス kowai-server に独立移管した。Codex レビュー(2 ラウンド)反映、DNS 切替、HTTPS 化まですべて完了済み。

  • 本番URL: https://kh.thomsonsapp.com
  • Lightsail: 54.249.174.108 / Ubuntu 24.04 / Python 3.12 / 1GB
  • DB は既存 RDS (kowai schema) を共用、スキーマ無変更
  • 旧 fn-* EC2 への kowai 関連トラフィックは無し(footballnext は継続使用)
  • 詳細: .devnotes/lightsail-migration-report.md.gods-talk/lightsail-migration-review-{001,002}-{issue,feedback}.md

次にやること(任意・優先度低め):

  • Route53 の子 hosted zone kh.thomsonsapp.com の削除(親に一本化済み・月 $0.50 節約)
  • 旧 Cake (fn-* EC2) の KhApi コントローラ・関連コード・kh.thomsonsapp.com vhost のクリーンアップ
  • 数日〜1週間の稼働観察(OneSignal 予約配信が予定時刻に届くか、S3 同期が継続するか)

ペンディング(中期):

  • sendPushNotification 配信実績(errored 件数)の検知・アラート
  • OneSignal SDK の更新検討
  • kowai-android: Realm 10.19.0 からの脱却(MongoDB が EOL 化)
  • kowai-android: 古い androidx 群(appcompat 1.0.2 等)の近代化

2026-05-23 19:00 時点のスナップショット

kowai-android の Google Play 16KBページサイズ対応(対応期限 2026-05-31)

技術作業はすべて完了。署名済みリリースAAB(versionCode 10 / versionName 1.0.2)をビルド済み。

  • 成果物: kowai-android/app/build/outputs/bundle/release/app-release.aab
  • 内容: 16KB対応 / targetSdk 35 / nend SDK削除 / AdMob 25.2.0・ADG 2.36.0・Realm 10.19.0 / 初回タップ無反応バグ修正
  • 16KBアラインメント・タップ修正とも検証済み(実機 YAL-L21)

次にやること:

  • vc10 のAABを Play Console にアップロードして公開(先にアップした vc9 は破棄)
  • アップロード時の3警告(minSdk低下 / 難読化解除ファイルなし / ネイティブシンボルなし)はすべて無視可

ビルド環境メモ:

  • JAVA_HOME は Android Studio 同梱 JBR(/Applications/Android Studio.app/Contents/jbr/Contents/Home
  • 署名は kowai-android/keystore.properties(gitignore対象・要パスワード記入)から読む
  • リリースAAB生成: cd kowai-android && ./gradlew :app:bundleRelease

未決・改善余地:

  • Realm 10.19.0 は deprecated(MongoDB が Realm を EOL化)。中期的に脱却検討
  • 古い androidx 群(appcompat 1.0.2 等・2018年版)は今回未更新。Android 15 実機での表示崩れがあれば個別対応
  • loadPieceItemonFailure が握り潰し・タップ時フィードバックなし(今回のタップ修正とは別の改善余地)

2026-05-21 18:27 時点のスナップショット

iOS プッシュ通知の不達は復旧済み(2026-05-21)。

  • 原因: OneSignal に登録された APNs 証明書の Bundle ID が jp.thomsons.ThomsonK002(誤)で、実アプリ jp.thomsons.Thomson002 と不一致 → APNs が全 push を拒否(errored)。4月の証明書更新時の打ち間違いが原因
  • 対処: 正しい Bundle ID の証明書に更新済み。自分の端末へのテスト送信で successful:1・実機着信を確認
  • リポジトリ構成: kowai-operation 配下の kowai-ios / kowai-android / footballnext-server は .gitignore で分離(各リポは独立して扱う)

次にやること:

  • 次の怖い話追加時に管理画面から本番送信し、約1万台への配信を実地確認
  • Apple Developer の誤 App ID jp.thomsons.ThomsonK002 を削除

未決・改善余地:

  • sendPushNotification() が errored を検知できない(送信後に配信実績を見る監視の追加余地)
  • OneSignal のタグ上限(プラン上限2 / アプリは3タグ送信)の見直し

ROADMAP(計画)

ロードマップ

今週

  • kowai-android: vc10 のリリースAABを Play Console にアップロード・公開(16KB対応期限 2026-05-31)
  • kowai サーバー機能を独立 Lightsail (FastAPI) に移管・DNS 切替・HTTPS 化
  • Android クラッシュ(Enter / 詳細ページ)緊急修正 + S3 全件 bool 化
  • レビュー欄でクラッシュ報告が収まるか見守り
  • kowai-server の数日稼働観察(OneSignal 予約配信が予定時刻に届くか、S3 同期が継続するか)
  • Apple Developer の誤 App ID jp.thomsons.ThomsonK002 を削除

今月

  • Route53 の子 hosted zone kh.thomsonsapp.com を削除(親に一本化済み・月 $0.50 節約)
  • 旧 Cake (fn-* EC2) の KhApi コントローラ・関連コード・kh.thomsonsapp.com vhost をクリーンアップ(fn-* EC2 自体は footballnext で継続)
  • sendPushNotification() に配信実績(errored 件数)の検知・アラートを追加
  • OneSignal のタグ上限(プラン上限2 / アプリは3タグ送信)の見直し — movie 通知のターゲティング影響を確認
  • API レスポンス型変更時のクロスチェック手順をチェックリスト化(クライアント側 Realm / Swift モデル定義の grep を必須に)

今四半期

  • 次の iOS リリース: kowai-ios/Kowai/Const.swift:14baseUrlStringhttps:// に変更
  • 次の Android リリース: HomeArticleViewModel.kt / HomeMovieViewModel.kt / StoryListViewModel.kt:33http://https:// に、MainActivity.kt:232getBoolean(2)getInt(2) != 0 に変更(サーバー側型変更への耐性向上)
  • 全クライアントが HTTPS 化したら nginx の 80 番 /khApi/ パススルーを撤去して HTTPS only に戻す
  • kowai-server の S3 出力を ACL=public-read → bucket policy ベース / CloudFront 経由に移行(Block Public Access 適合化)
  • kowai-server の OneSignal 失敗時 candidate 戻し or 手動再試行 UI(現状はログ強化のみ)

いつか

  • OneSignal SDK の更新検討(iOS は現状 2.12.6 / 最新は 5.x 系)
  • kowai-android: Realm からの脱却検討(10.19.0 は deprecated・MongoDB が EOL化)
  • kowai-android: 古い androidx 群(appcompat 1.0.2 等・2018年版)の近代化
  • kowai-android: loadPieceItem のエラーハンドリング改善(onFailure の握り潰し・タップ時フィードバックなし)
  • kowai-server の RSS クローラ強化(旧 RssAnalytics 互換: data-lazy-src / 相対 URL 絶対化 / URL リダイレクト解決)
  • kowai-server の Cake 互換性 golden test(API / S3 JSON の代表 fixture を Cake 出力と diff)

DECISIONS(意思決定)

意思決定記録

このプロジェクトで下した重要な意思決定を記録する。 最新が上に来る。


2026-06-09: 旧アプリ救済のため /khApi/* だけは HTTP 受付を残す

背景: リリース済み iOS / Android アプリが http://kh.thomsonsapp.com/khApi/... をハードコードしており、5/24 の 80→301→HTTPS 化で旧クライアントが詰まる事態に。レビュー欄に「読めなくなった」報告が発生した。

決定: nginx の 80 番 server で /khApi/* だけは HTTP のままパススルーする。/login/fearstoryPortal/internal 等の認証 / 副作用系は HTTPS 強制を維持。

理由: /khApi/* は読み出し専用・認証情報なしなので HTTP 公開しても攻撃面はほぼ変化なし。代替案として「iOS / Android アプリの baseURL を https に変えて再リリース」も検討したが、審査含めて即時救済にならない。次のリリース時に全クライアントが https に揃ったらこのパススルーは撤去する短期措置として位置づけ。

2026-06-09: is_html / is_info はクライアント側 Realm モデルに合わせて bool で返す

背景: Codex round1 medium-7 で「API と S3 で型を str "0"/"1" に揃える」と判断したが、Android Realm モデル StoryContent.kt:34-35is_html: Boolean / is_info: Boolean で受けており、realm.createOrUpdateObjectFromJson"0" を Boolean 変換できず JSONException → クラッシュしていた。

決定: schemas.py:StoryItem / stories.py:_to_item / cron.py:_story_structis_html / is_info を bool に戻す。S3 上の既存 4189 件も resync_all_stories.py で bool 版に一括上書き。

理由: ① クライアントが Boolean を要求しているのが実装で確認できた事実 ② iOS の SwiftyJSON は .boolValue で int / bool / "true"/"false" 文字列いずれも柔軟に扱うため bool に統一しても影響なし ③ 今後 API レスポンス型を変更するときは、必ずクライアントの Realm / Swift モデル定義を grep してクロスチェックする運用に切り替える。

2026-05-24: kowai サーバーは独立 Lightsail (FastAPI) に移管、Cake は使わない

背景: kowai 関連の /khApi/*/fearstoryPortal/* が footballnext-server (CakePHP, EC2) に間借りしていた。クロール / S3 同期 / 予約 push まで巻き込んで動いていて、footballnext 側の障害・改修の影響を直接受ける構造だった。

決定: 新 Lightsail インスタンス kowai-server を独立で立て、FastAPI + Jinja2 + SQLAlchemy 2.0 で全面移植する。DB は既存 RDS (kowai schema) を共用してスキーマは触らない。kh.thomsonsapp.com の DNS を新サーバーに切り替えて完全分離する。

理由: ① 言語スタックを Python に統一して保守性を上げる ② footballnext と物理的に分けて互いの障害を遮断 ③ RDS は同じスキーマで動作実績がある ④ 旧 fn-* EC2 自体は footballnext で継続使用するので即廃止コストは無し。代替案として「Cake 側に閉じこめたまま改修」も検討したが、PHP 5 系コードベースを今後も保守する負債が大きすぎるため不採用。

2026-05-24: 副作用エンドポイントは /internal/*(127.0.0.1 限定)と /fearstoryPortal/api/*(ログイン必須)に分離、/khApi/* は読み出し API のみ

背景: 移植初期は Cake 互換で /khApi/* 配下に sendPushNotification や cron 系 13 本まで全て無認証で並べていた。Codex レビュー round1 で「外部 IP から push 全配信や DB 書き換えが叩ける」と high 指摘。

決定: ルーティングを 3 系統に再構成する。

  • /khApi/*: モバイル公開読み出し API(stories / movies / articles / spots)のみ
  • /internal/*: cron 用副作用エンドポイント(13 本)+ /internal/dbhealth。nginx で allow 127.0.0.1; allow ::1; deny all;
  • /fearstoryPortal/api/*: 管理画面 JS から叩く副作用 API。require_login + CSRF dependency

理由: モバイル公開と内部処理が同じ prefix に並んでいると認証境界が曖昧になる。prefix で物理的に分ければ nginx の location ベースで防御でき、設計の意図がコードからも読める。crontab は 127.0.0.1:8000/internal/* を叩く形に変更。

2026-05-24: 時刻基準は Python 側 JST に統一、func.now() は使わない

背景: RDS の @@global.time_zone が UTC のまま動いていて、stories.pypublished_date < func.now() で公開判定が 9 時間ズレていた(旧 Cake は datetime() で JST 文字列を渡しており、DB の文字列比較で完結していたため問題が表面化しなかった)。

決定: すべての時刻判定を Python ホスト時刻(Asia/Tokyo の naive datetime)に統一。MySQL の NOW() / func.now() は使わない。RDS の time_zone は触らない。

理由: ① RDS の time_zone を変えると他システムへの影響が読めない ② アプリ側で datetime.now() に揃える方が変更箇所が少ない(2 ヶ所だけ) ③ YouTube publishedAt (UTC) も datetime.fromisoformat(...).replace(tzinfo=utc).astimezone() で JST naive に変換して保存することで、DB 側は一貫して JST 文字列のままに保てる。

2026-05-24: 予約 push は atomic claim(UPDATE WHERE candidate=:val)で二重送信を防ぐ

背景: sendReservedNotification が「全 type を SELECT → 条件一致なら candidate=0 を commit → OneSignal 送信」の順だった。--workers 2 で同じ分に走ると、両 worker が同じ candidate を読み終えてから両方 0 にして両方送る race が成立する。

決定: UPDATE kh_notification_types SET candidate=0 WHERE id=:id AND candidate=:val の atomic claim にする。rowcount==1 の呼び出しだけが OneSignal 送信に進む。

理由: 重複送信は本番ユーザーに直接迷惑がかかる。advisory lock や SELECT FOR UPDATE も検討したが、MySQL の UPDATE 自体が行ロックを取るため最小実装で十分。flock を crontab 側にも入れて多段でガード。

2026-05-24: S3 ACL=public-read を維持(短期)、Block Public Access への移行は将来

背景: Cake 由来の s3:PutObject + ACL=public-read を kowai-server でも踏襲。Codex から「将来の Block Public Access / Object Ownership Bucket owner enforced と衝突する」と low 指摘。

決定: 短期は ACL=public-read を維持。将来 bucket を作り直す / Object Ownership を切り替えるタイミングで、bucket policy ベース or CloudFront 経由に移行する。

理由: モバイルアプリ側の取得 URL を変えると再リリースが必要になる。S3 → CloudFront 化はキャッシュ戦略まで含めた別案件として切り出した方が安全。

2026-05-21: kowai-android の nend SDK を完全削除する

背景: Google Play の16KBページサイズ要件対応で、ネイティブ依存SDKを16KB対応版へ更新する必要があった。nend 広告SDKは更新版が必要だが、nend は2024/3にサービス終了しており16KB対応版が存在しない。

決定: nend SDK をアプリから完全に削除する(依存・専用ファイル・レイアウト・広告ローテーション分岐すべて)。空いた広告枠は AdMob / ADG にフォールバックさせる。

理由: nend は16KB対応版が出ない上、そもそも2024/3以降は広告が配信されていない(空表示)。残す意味がなく、削除が16KB対応の前提でもある。広告比率データ(Realm)は変更せず、enum変換で UNKNOWN→既存の else 分岐に落ちる形にして影響を最小化。

2026-05-21: kowai-android の minSdk を 21→24 に引き上げる

背景: 16KB対応の ADG SDK 2.36.0 が minSdk 23 以上を要求。AdMob 等の新版も古いAndroidを切り捨てる方向。

決定: minSdk を 21→24(Android 7.0)に引き上げる。

理由: API 21〜23(Android 5.0〜6.0)は2026年時点で利用シェアがほぼゼロ。前回更新が2023年で長期未更新だったこともあり実害は小さい。tools:overrideLibrary での強制回避は実行時クラッシュのリスクがあり不採用。24 にしておけば新しい広告SDK群の要求も満たせる。

2026-05-21: Play への提出形式を APK→AAB に移行する

背景: 前回リリース(2023)は APK 形式。16KB対応リリースにあたり提出形式を確認した。

決定: AAB 形式で提出する。

理由: 調査の結果、このアプリは既に Play App Signing 登録済み(配信版が Google署名鍵で署名されている)と判明。kowai 鍵はアップロード鍵として機能し、AAB 提出に追加手続きは不要だった。AAB は今後の標準形式。

2026-05-21: 多重タップガードの窓を 3.5秒→0.6秒に短縮する

背景: 一覧で項目をタップしても初回が無反応で再タップが必要、という既存バグを logcat で調査。原因は BasicUtil.isDisableClickEvent() の3.5秒グローバル多重タップガードが、通常のテンポの操作まで全部ブロックしていたこと(10タップ中2遷移を確認)。

決定: ガードの窓を 3500ms→600ms に短縮(定数 CLICK_GUARD_MS として明示)。

理由: ガード自体(誤連打で詳細画面が二重に開くのを防ぐ)は残す価値がある。誤連打は200ms程度で発生するため600msで十分防げ、かつ通常の閲覧操作はブロックしない。ガード撤廃(0秒)は二重遷移リスクが残るため不採用。

2026-05-21: 古い androidx 群は今回まとめて更新しない

背景: appcompat 1.0.2 など2018年版の androidx 依存が多数。compileSdk 35 化にあたり一括近代化も検討した。

決定: 16KB対応に必要な変更のみに留め、古い androidx 群の一括アップグレードは今回行わない。

理由: compileSdk/targetSdk 35 でビルドは通っており、大量アップグレードはテーマ・リソース属性・Navigation API 等の広範な破壊を招くリスクが高い。5/31期限を優先し変更範囲を限定。Android 15 実機での表示崩れがあれば個別対応する。

2026-05-21: ネストした独立リポは gitignore で分離する

背景: kowai-operation(運用リポ)配下に、独立した git リポジトリである kowai-ios / kowai-android / footballnext-server をクローンして配置した。このままだと親リポが各ディレクトリの中身を gitlink として中途半端に記録してしまう。

決定: 3リポを kowai-operation の .gitignore に追加して追跡対象から外す。git submodule 化はしない。

理由: 各プラットフォームのリポを完全に独立して扱える。submodule の運用(--recurse-submodules、コミットポインタ管理)の煩雑さを避けられる。kowai-operation 自体は運用ドキュメント・スクリプトのみを管理する方針とした。

DEVLOG(作業ログ)

開発日誌

このプロジェクトでの作業を時系列で記録する。 最新のエントリが上に来る。


2026-06-09

18:00 - Android クラッシュ修正(HTTPS 301・is_html/is_info 型不一致 / S3 全件再 sync)

やったこと:

  • ユーザーレビュー報告で「Android で読めなくなった」「Enter ボタン押下でクラッシュ」「詳細ページに行くときクラッシュ」を受けて緊急修正
  • 原因 1(HTTPS 強制で旧クライアント詰まり)対応:
    • リリース済み iOS / Android アプリが http://kh.thomsonsapp.com/khApi/... を URL ハードコードしていたため、5/24 の 80 → 301 → HTTPS 化で詰まっていた
    • nginx の 80 番 server で /khApi/ だけ HTTP のままパススルー、認証系・管理画面は HTTPS 強制を維持(commit 0214fe2
  • 原因 2(is_html / is_info 型不一致)対応:
    • Codex round1 medium-7 で「型統一」と判断して bool → str に変えた箇所が、Android Realm モデル StoryContent.ktis_html: Boolean / is_info: Boolean と不整合
    • realm.createOrUpdateObjectFromJson"0" を Boolean 変換できず JSONException → Realm transaction 内 uncaught → クラッシュ
    • schemas.py:StoryItem / stories.py:_to_item / cron.py:_story_struct の3箇所で str → bool に戻す(commit 0dcb65d
    • all_story_contents_ids[i][2] も同様に bool(is_info) で出すよう修正(commit 2c345c9
  • S3 既存ファイルの一括上書き:
    • cronStorySyncToS3is_s3_synced=0 のものだけ再 put する仕様のため、修正前に作られた 4000+ 個別 stories/{id:05d}.json が str のまま残っていた
    • 一回限りのスクリプト kowai-server/resync_all_stories.py を作って全 4189 件を bool 版で再 put(162 秒、失敗 0)
  • iOS への影響なし(SwiftyJSON の .boolValue は int / bool / "true"/"false" 文字列いずれも柔軟に解釈)
  • AVD kowai16k でエミュレーター起動、ユーザー側で実機 / エミュレーター検証 → OK 報告

詰まったこと / 気づき:

  • Codex round1 の「API と S3 で型を揃える」指摘を反映するときに、実際にこの JSON をパースしている Android Realm モデル(Boolean フィールド)の存在を確認しなかったのが致命的だった
  • 「Cake が string で返してた」は推測で、現役クライアントが何を要求するかは別問題
  • cronStorySyncToS3is_s3_synced=1 を信頼するクエリ設計は、JSON 仕様変更時に「全件再生成」できる経路を持たない弱点
  • 旧 80 番 HTTP は将来も残さざるを得ない(リリース済みアプリの baseUrl ハードコードを変えるには再リリースが必要)

次回やること:

  • 数日見守り(クラッシュレビューが収まるか)
  • 次の iOS リリース: kowai-ios/Kowai/Const.swift:14http://kh.thomsonsapp.comhttps://
  • 次の Android リリース: HomeArticleViewModel.kt / HomeMovieViewModel.kt / StoryListViewModel.kt:33http://https:// に、MainActivity.kt:232getBoolean(2)getInt(2) != 0
  • 全クライアントが HTTPS に揃ったら nginx の 80 番 /khApi/ パススルーを撤去して HTTPS only に戻す

2026-05-24

17:00 - Codex レビュー反映 → DNS 切替 → HTTPS 化(kowai-server 仕上げ)

やったこと:

  • Codex レビュー round1 反映(high 3 / medium 7 / low 数件):

    • high-1: 副作用エンドポイントを /khApi/* から分離。cron を /internal/*(nginx で 127.0.0.1 / ::1 のみ許可)、sendPushNotification / updateNotification* を /fearstoryPortal/api/*(require_login)。/khApi/* は読み出し API 4 本のみに整理
    • high-2: func.now()datetime.now() に置換。RDS が UTC・アプリが JST 想定だったため、stories.py の公開判定で 9 時間ズレていた問題を解消
    • high-3: sendReservedNotificationUPDATE ... WHERE candidate=:val の atomic claim に変更。rowcount==1 の呼び出しだけが OneSignal 送信に進む
    • medium-1: YouTube publishedAt を aware UTC として epoch 取得、JST naive に変換して upload_date に保存
    • medium-2: RSS クローラの 1 サイト取り込みを 2 件に制限、PR/AD タイトル除外
    • medium-3: OneSignal 送信失敗を握って {"errors":[...], "status_code":N} の JSON で返す + 予約 push のログ message に OK/FAIL ステータスを埋める
    • medium-4: CSRF token 機構を追加(app/csrf.pyensure_csrf_token / verify_csrf dependency、_base.html<meta name="csrf-token">{% macro csrf() %} を定義し全 POST form に展開)
    • medium-5: /login?next= の open redirect を修正(/fearstoryPortal 配下の相対パスのみ許可)
    • medium-7: is_html / is_info の API vs S3 JSON 型不一致を str "0"/"1" に統一
    • low: cron に flock -ncurl --max-time を付与、DB URL を URL.create() でエスケープ
  • Codex round2 反映:

    • message 生成失敗時に [reserved/{tag}/{candidate}/FAIL:empty-message] をログ追加
    • /dbhealth/internal/dbhealth に移して loopback 限定に
    • 本番では FastAPI の /docs /redoc /openapi.json を非公開化(KOWAI_ENABLE_DOCS=1 で開発時のみ有効)
    • deploy/README.md をフル更新(swap・cron 適用手順・ルーティング設計・安全運用メモを追加)
    • .env.example を全変数(DB / SESSION / ONESIGNAL / YOUTUBE / AWS)網羅
    • cron.txt に「廃止 / 参考用」のヘッダコメントを追加し誤適用を防ぐ
  • DNS 切替(kh.thomsonsapp.com → 54.249.174.108):

    • Route53 の親 thomsonsapp.comkh A 54.249.174.108 を直接追加、親側の kh NS 委任レコードを削除(子 hosted zone への委任を停止)
    • dig で複数 resolver から 54.249.174.108 が返ることを確認
  • HTTPS 化:

    • Lightsail に snap で certbot をインストール、certbot certonly --webroot で Let's Encrypt 証明書取得(有効期限 2026-08-22)
    • Lightsail のファイアウォールで 443/TCP を Any IPv4 で許可
    • nginx を HTTPS server block に再構成: HSTS(max-age=1y; includeSubDomains)、TLSv1.2/1.3、HTTP→HTTPS 301 redirect、limit_req_zone/login に rate limit(10req/min/IP)
    • SessionMiddlewarehttps_only=True を組み込み(KOWAI_INSECURE_COOKIES=1 で開発時無効化可)
  • 動作確認: https://kh.thomsonsapp.com/healthz 200 / HTTP→HTTPS 301 / HSTS 有効 / /khApi/stories 200 / /internal/* 外部から 403 / /fearstoryPortal 未認証 303→/login

詰まったこと / 気づき:

  • nginx 1.24 では http2 on; ディレクティブ未対応 → listen 443 ssl http2; の旧形式に直して解決
  • Lightsail はデフォルトで 443 が閉じている。インスタンスのファイアウォールで明示的に Any IPv4 で開けるまで HTTPS が通らない
  • RDS の time_zone が UTC のまま(Cake は datetime() を文字列で渡していたので JST 完結だったが、FastAPI で func.now() を使うと比較が UTC ベースになって 9 時間ズレる)。MySQL 側の @@time_zone を変えずに Python 側で datetime.now() に揃える方が変更量が小さい
  • CSRF dependency の await request.form() 先読みは、Starlette が form を cache するため後続の FastAPI Form(...) と協調する。ライブラリ実装に依存して動く前提のメモ
  • Lightsail に IAM ロールが直接アタッチできないため、S3 用に専用 IAM ユーザー kowai-server を新規発行(fearstory bucket に PutObject / GetObject / PutObjectAcl のみ)

次回やること:

  • 任意: 子 hosted zone kh.thomsonsapp.com を Route53 から削除(月 $0.50 節約)
  • 任意: 旧 Cake (fn-* EC2) の KhApi コントローラ・関連コード・kh.thomsonsapp.com vhost のクリーンアップ(fn-* EC2 自体は footballnext で継続使用)
  • 数日〜1週間の稼働観察(OneSignal 予約配信が予定時刻に届くか、S3 同期が継続するか)

2026-05-23

19:00〜26:00 - kowai-server (FastAPI) への移管・管理画面・cron 13本

やったこと:

  • 旧 Cake (footballnext-server) に間借りしていた /khApi/*/fearstoryPortal/* を新 Lightsail インスタンス kowai-server に独立移管(FastAPI + Jinja2 + SQLAlchemy 2.0)
  • Lightsail Tokyo 1GB / 2 vCPU / 40GB を新規作成、VPC peering で既存 RDS (kowai schema) を共用、Static IP 54.249.174.108
  • 読み出し API 4 本(stories / movies / articles / spots)を Cake と完全一致になるよう移植(ID 系は str、bool 系は str "0"/"1"、digest_body は body[:70] → 改行除去 → HTML タグ除去)
  • fearstoryPortal 管理画面を Jinja2 SSR で全面移植: 広告比率(iOS/Android 各 5 テーブル一括)/ 作者・タグ(CRUD + ページング)/ 怖い話・心霊スポット・記事・RSS(一覧・編集・必要に応じて追加)/ 予約配信 send_time 編集・候補変更(SSR テーブル・現候補ハイライト・候補クリア)/ プッシュ通知(全配信・テスト送信)
  • 管理画面デザインを共通 CSS + Jinja2 base テンプレで刷新(モダンライト + 赤アクセント、カードレイアウト、テーブル hover、削除済み行ハイライト、sticky 保存バー、breadcrumb)
  • プッシュ通知: OneSignal credentials を旧 Cake KhOneSignal.php から移管、/khApi/sendPushNotification(全配信)と /khApi/sendPushNotificationTest(Subscription ID 指定)を実装、特定端末への送信を実機検証で OK
  • cron 13 本を FastAPI に移植: A 群 4 本(DB のみ)→ D 群 3 本(RSS / YouTube)→ B+C 群 6 本(S3 同期 / 集計データ)。S3 用に専用 IAM ユーザー(fearstory bucket への PutObject / GetObject / PutObjectAcl)を発行
  • crontab を新サーバーへ完全切り替え(Cake 側 cron は OFF、fn-* EC2 自体は footballnext で継続)
  • Lightsail 1GB プランで安定運用するためのチューニング: swap 1GB(fstab 永続化)、uvicorn --workers 2 --log-level warning、systemd LimitNOFILE=65536 / OOMScoreAdjust=-100、nginx gzip + keep-alive + /static cache

詰まったこと / 気づき:

  • Lightsail は EC2 IAM ロールが直接付けられないため、S3 認証用に専用 IAM ユーザーを発行する形になった
  • RDS の time_zone は UTC のまま(後の round1 review で公開判定 9 時間ズレに気付く)
  • 一部 RSS が Server disconnected without sending a response を返す。並行稼働中は Cake が拾っていたが、新サーバー完全切り替え後は user-agent 等の調整余地
  • 既存 KhOnesignalLogs テーブルに updated カラムが無い(created のみ)。models.py 生成時に気付かず修正
  • ssh 経由 curl で日本語を送ると URL encode の関係で文字化けする。ブラウザ経由なら問題なし
  • index.html のテーブルヘッダが sticky + table-wrap の overflow との組み合わせで崩れる → sticky を撤去
  • ボタンの色が出なかった: input[type=button].btn (specificity 21) が .btn-primary (specificity 10) より勝って background を白で上書きしていた CSS バグを修正

次回やること:

  • Codex に移管内容をレビュー依頼(.gods-talk)

18:34 - kowai-android 16KBページサイズ対応・新規リリース整備

やったこと:

  • 開発環境を新PCで再構築: Android Studio 2025.3(JBR 21内蔵)/ Android SDK(API 35・build-tools 35・platform-tools)/ コマンドラインツール
  • ビルドツールチェーン更新: Gradle 8.0→8.9 / AGP 8.1.0→8.6.1 / Kotlin 1.8.0→2.1.0、廃止された jcenter() を除去
  • jcenter廃止の巻き添えを解消: flexbox を新座標 com.google.android.flexbox へ、未使用の youtube-player-view(実体消失・コード未使用)を削除
  • compileSdk / targetSdk を 33→35(Play更新要件)、minSdk 21→24 に変更
  • nend SDK を完全削除(2024/3サービス終了・16KB対応版なし): 専用Kotlin5本+レイアウト5本を削除、広告ローテーション処理13ファイルから nend 分岐を除去
  • 広告SDKを16KB対応版へ: AdMob 22.3.0→25.2.0、ADG を +→2.36.0 にピン留め、Realm 10.15.1→10.19.0
  • ADG 2.36.0 のAPI変更(リスナー引数@NonNull化・getter群@Nullable化)に追従
  • リリース署名を keystore.properties 方式(gitignore対象)で build.gradle に組み込み
  • 署名済みリリースAABをビルドし16KBアラインメントを検証(ELF 2**14+zip整列、bundletool生成APKでも合格)
  • 既存バグ「リスト初回タップが無反応」を logcat で原因特定・修正、versionCode 10 を再ビルドし実機検証

詰まったこと / 気づき:

  • ネイティブ.soは Realm の librealm-jni.so のみ(AdMob・ADGは.so非同梱)。Realm 10.19.0 が16KB対応版で要件クリア
  • youtube-player-view 1.4.0 は jcenter専用で .aar がどこにも残っておらず、かつコード上完全に未使用 → 削除で解決
  • タップ無反応の原因は通信失敗ではなく BasicUtil.isDisableClickEvent() の3.5秒グローバル多重タップガード。logで10タップ中2遷移を確認し確定
  • このアプリは既に Play App Signing 登録済み(配信版が Google署名鍵)。kowai 鍵はアップロード鍵で、vc9アップロードが署名エラーなく通った事実が裏付け

次回やること:

  • vc10 のリリースAAB(kowai-android/app/build/outputs/bundle/release/app-release.aab)を Play Console にアップロードして公開(対応期限 2026-05-31)

18:27 - リポジトリ整備 & iOS通知失敗の原因調査・復旧

やったこと:

  • kowai-operation 配下にネストした独立リポ3つ(kowai-ios / kowai-android / footballnext-server)を .gitignore で分離
  • iOS の OneSignal プッシュ通知が4月以降届かない問題を調査
    • 送信経路を特定: footballnext-server の KhApiController.php::sendPushNotification()KhOneSignal::send() → OneSignal REST API(included_segmentsstory / old_app に送信)
    • kh_onesignal_logs テーブルと OneSignal API(GET /notifications/{id})で配信実績を確認 → iOS 約1万台が全件 errored(APNs に拒否されている状態)
    • 原因を特定: OneSignal に登録された APNs 証明書の Bundle ID が jp.thomsons.ThomsonK002(誤・余計な K)。実アプリ(App Store / Xcode 両方で確認)は jp.thomsons.Thomson002。不一致のため APNs が全 push を拒否
  • 復旧テスト: 自分の端末1台に直接テスト送信 → ①更新前 errored:1 → 証明書更新 → ②更新後 successful:1(実機着信も確認)

詰まったこと / 気づき:

  • OneSignal 管理画面では証明書が「Active」「期限内(2026-12-28)」と表示され一見問題なく見えた。見るべきは expiry ではなく Bundle ID 行だった
  • sendPushNotification() は OneSignal が受理さえすれば「成功」扱いで、全件 errored でも検知できない。失敗発覚が遅れた一因
  • 当初「証明書の期限切れ」と推測したが誤り。実際は4月の証明書更新時に担当者が Bundle ID を打ち間違えたのが原因

次回やること:

  • 次の怖い話追加時に管理画面から本番送信し、約1万台への配信を確認
  • Apple Developer の誤 App ID jp.thomsons.ThomsonK002 を削除

最近のコミット

README

kowai-operation

概要

(記入予定)

セットアップ

(記入予定)

使い方

(記入予定)