版数: 2.6  |  更新日: 2026-06-15  |  補助: ナレッジ(FAQ / トラブルシュート)

ナレッジベース(Phase1)

この文書について(標準 FAQ / 運用ナレッジの構成)

目的手順書だけでは伝わらない背景・判断理由・エラー対処を残す
想定読者運用担当、開発者、後から参加するメンバー
いつ読むかエラー発生時、設計の「なぜ?」、セキュリティの前提理解時

Runbookやり方開発ログいつやったか、 本ページ=なぜ・どう調べたか(K-01〜K-31)。

WBS 1.1(サーバー環境構築・Tunnel 移行)を進める過程で発生した疑問・エラー・判断を、 後から読んでも再現できるよう丁寧に残した資料です。 2026-06-12 追記: 本番準備(WBS 3)のセキュリティ議論で生じた前提知識(OSI・HTTPS・FW・シークレット等)を K-24K-30 に整理しました。詳細設計は セキュリティ設計 を参照。

開発ログ は「いつ・何を実行したか」の時系列。 本ページは「なぜそうなったか」「どう調べて・何を直して解消したか」を中心に記述します。 各項目の末尾に、インフラ実務の視点からのSuperengineer からの一言を添えています。

関連: Phase1 サーバー手順(手順書) / 開発ログ(実施記録) / PoC ナレッジ Phase 1-4 ナレッジ資料:Cloudflare Named TunnelとDNS管理の勘所.md

この資料の読み方

見出し意味
きっかけ質問が生まれた背景、またはエラーに気づいた状況
経緯試したこと・判明したこと・次に何をしたか(解決までの流れ)
結論最終的な理解・正しい操作・再発防止
Superengineer からの一言現場のインフラ担当が伝えそうな助言(第3者視点)

目次

IDテーマ種別
K-01PoC から Windows PC への Tunnel 移行設計理解
K-02server/.env の役割設計理解
K-03TUNNEL_TOKEN 誤設定(Tunnel ID と Connector Token の混同)エラー解消
K-04Pi 側 cloudflared を止める理由設計理解
K-05Cloudflare 画面の cloudflared.exe 指示設計理解
K-06Docker Desktop 未起動(pipe エラー)エラー解消
K-07docker compose コマンドの意味設計理解
K-08Pi 上 Docker 停止(sudo・サービス名)エラー解消
K-09PowerShell の curl 問題エラー解消
K-10PostgreSQL を compose に含める理由設計理解
K-11疎通確認 — localhost と公開 URL設計理解
K-12MS-1 達成までの全体の流れまとめ
K-13エラー早見表参照
K-14Git — add / commit / push の順序運用
K-15次の段階 — WBS 1.2 スタブ Android アプリ移行
K-16実機検証 — USB デバッグ・ログの見方エラー解消
K-17実機 FB 送信 — HTTP 400「image must be an image file」解決済み(2026-06-08)
K-18Flutter 開発の基礎 — ライフサイクルと実機検証の手順基礎知識
K-19YAML とは — 書式とツールごとの意味の違い基礎知識
K-20GitHub Actions と android-build.yml — ビルド・artifact・APK設計理解
K-21月次モデル更新パイプライン — 学習から配布までまとめ
K-22PAT と Actions Secrets — Worker dispatch の認証設計理解
K-23Phase1 運用 — PAT ローテーション・artifact 保持・APK 世代管理運用

セキュリティ・本番準備(WBS 3 関連)

IDテーマ種別
K-24OSI 参照モデルとネットワークの前提 — ポート・FW・境界基礎知識
K-25HTTPS と Cloudflare Tunnel — 何を守り、何を守らないか基礎知識
K-26公開 URL・API 認証・アップロード制限設計理解
K-27シークレット管理 — .env・DB パスワード・PAT・ローテーション設計理解
K-28Cloudflare Access・ログ・PII・実測確認(WBS 3.1.5)基礎知識
K-29セキュリティを考える共通の型 — 詳細知識がなくても不足に気づく基礎知識
K-30リポジトリ構成と .env 配置 — FEEDBACK_API_KEY 誤設定と 401解決済み(2026-06-14)
K-31全体構成図(セキュリティ拡張版)— 元図解から Phase1 実装への対応まとめ

K-01 PoC から Windows PC への Tunnel 移行

きっかけ

runbook では「PoC の Tunnel を再利用し、cloudflared の接続先を Raspberry Pi から Windows PC に移す」とある。 一方で、「Pi の cloudflared を止めなくても、別 Tunnel を新規に立てればよいのでは?」という疑問が生じた。

経緯

1. 2026-06-07 の方針協議で、PoC(スマート農業)ですでに poc-api-tunnel・DNS・api.dammy-otoko.com が稼働していることを確認。 新規 Tunnel 作成は DNS 設定の重複・手戻りリスクがあるため不採用と決定。
2. 「別 Tunnel を立てる」こと自体は技術的に可能だが、 本プロジェクトの作業範囲外(WBS 1.1.1 は「移行」であり「新規構築」ではない)と整理。
3. Pi 上の cloudflared を止めず、PC でも同じ Tunnel(poc-api-tunnel) に接続する案は、 後述 K-04 のとおり、同一 Tunnel への二重接続(2台の cloudflared が同時に繋がる状態)を招くため採用しない。

結論

変更するのは cloudflared が動くマシン だけ。 Tunnel 名・公開 URL・DNS は PoC 資産をそのまま使う。 自宅 PC は CGNAT(キャリア側の NAT により、自宅回線にインターネットから直接届くグローバル IP が無い状態)のため、 ルーターのポート開放だけでは外部公開できない。cloudflared が Cloudflare へ外向きに接続し、 そのトンネル経由でリクエストを受け取る構成が必須。

flowchart LR
  subgraph before["移行前"]
    APP1[クライアント] --> CF1[Cloudflare]
    CF1 --> PI[cloudflared @ Pi] --> API1[PoC API]
  end
  subgraph after["移行後"]
    APP2[クライアント] --> CF2[Cloudflare]
    CF2 --> PC[cloudflared @ Windows] --> API2[FastAPI + DB]
  end
  before -.->|"Tunnel・URLは同じ"| after
          
Superengineer からの一言 Tunnel は「通信の穴」、cloudflared は「穴の出口をどのマシンに置くか」のスイッチだと考えれば混乱しにくい。 出口を2台同時にオンにしないのが鉄則。新しい穴を掘る前に、既存の穴を使い回せないか必ず検討する。

K-02 server/ ディレクトリと .env

きっかけ

リポジトリ内に server/ があり、runbook では server/.envTUNNEL_TOKEN を書くとある。 「server という名前のディレクトリは単にバックエンドをまとめただけ?」 「.env はなぜ Git に載せず、.env.example だけ載せるの?」という整理の質問。

経緯

1. 2026-06-05 頃、FastAPI + PostgreSQL + docker-compose を server/ 配下に実装。クライアント(将来の Flutter スタブ)と役割を分ける命名。
2. パスワード・Tunnel トークンは環境ごとに異なるため .env.example(テンプレ)を Git 管理し、実値の .env.gitignore で除外。
3. 実施順として、まず DB 用変数で db api を起動し localhost 疎通(1.1.2 / 1.1.3)。 その後 TUNNEL_TOKEN を設定して cloudflared(1.1.1)— 段階的に確認する方が効率的と runbook で並べ替え。

結論

flowchart TB
  EX[.env.example Git管理] -->|copy| ENV[.env ローカルのみ]
  ENV --> COMPOSE[docker-compose.yml]
  COMPOSE --> DB[(db)]
  COMPOSE --> API[api]
  COMPOSE --> CF[cloudflared]
          
  • server/ = バックエンド実行一式(API・DB・Tunnel)。物理サーバーそのものではない
  • .env = Compose 起動時に読み込む秘密情報・接続設定
  • .env.example = 必須キーの説明付きテンプレ。clone 後の最初の一歩
Superengineer からの一言 「.env を Git に載せない」のは慎重さの表れで正しい。 テンプレと実ファイルを分ける習慣は、将来チームで動くときに必ず効く。 変数名を増やしたら .env.example も同時に更新する — ここを忘れると次の担当者が止まる。

K-03 TUNNEL_TOKEN 誤設定 — Tunnel ID と Connector Token の混同

エラー解消 2026-06-07

きっかけ

Pi 側 cloudflared 停止後、PC で docker compose --profile tunnel up -d cloudflared を実行。 docker compose ps で cloudflared が Restarting (255) を繰り返す。 外部 URL は error code: 1033localhost の API は正常。

経緯

1. 当初、server/.envTUNNEL_TOKEN に UUID 形式(例: 716ae640-c39a-....)を設定していた。 Cloudflare ダッシュボードで Tunnel を選ぶと ID が目立つため、これがトークンだと誤解しやすい。
2. docker compose logs cloudflared を確認すると、 繰り返し Provided Tunnel token is not valid. が出力。 コンテナは起動直後に終了コード 255 で再起動ループに入る。
3. Cloudflare Zero Trust → Tunnels → poc-api-tunnel → Configure を開く。 Windows タブに次のような1行コマンドが表示される。
cloudflared.exe service install eyJhIioi...(ここから末尾までがトークン)
4. 上記のとおり、cloudflared.exe service install の直後に続く長い文字列全体が Connector Token(コネクタ認証用トークン)である。これを server/.envTUNNEL_TOKEN= に 1行で貼り付ける(引用符なし・改行なし)。
5. docker compose --profile tunnel up -d cloudflared を再実行。
6. ログに起動前の接続チェック(precheck)成功が出る(Cloudflare API への到達確認など)。 curl.exe https://api.dammy-otoko.com/api/v1/health{"status":"ok"}test-feedback.ps1 → HTTP 201。MS-1 達成。

結論

flowchart LR
  UUID["Tunnel ID UUID"] -->|誤| X["255 / 1033"]
  EYJ["Connector Token service install 以降"] -->|正| OK["cloudflared Up / MS-1"]
          
種類形式用途
Tunnel IDUUID 形式(716ae640-c39a-4fd0-.... のような短い識別子)ダッシュボード上で Tunnel を区別するための ID。TUNNEL_TOKEN には使えない
Connector Token(コネクタトークン)cloudflared.exe service install の後に続く長い1行の文字列(多くは eyJ で始まる)server/.envTUNNEL_TOKEN= に設定する値
Superengineer からの一言 Cloudflare の管理画面は「コネクタ(cloudflared)が1台も接続していない」とき、インストール手順を大きく表示する。 焦って cloudflared.exe を入れなくてよい。まず docker compose logs cloudflared を見る。 トークン系の不具合は、ログに token is not valid と正直に出ることが多い。 正しいトークンは、画面に表示される cloudflared.exe service install (ここから末尾まで) の部分だけをコピーする。Tunnel 一覧に出る UUID 形式の ID とは別物なので混同しない。

K-04 Pi 側 cloudflared を止める理由(同一 Tunnel への二重接続)

きっかけ

runbook では移行前に Pi で docker compose stop cloudflared とある。 「止めずに PC でも繋いだらダメなのか?」

経緯

1. PoC 段階では Pi 上の Docker で cloudflared が poc-api-tunnel に接続していた(2026-06-07 時点で /ping 応答確認済み)。
2. 本 PC へ移行する場合、同一 Tunnel に Pi と PC の2コネクタが共存すると、 Cloudflare がリクエストを両方に振り分ける。runbook トラブルシュートにも 「移行後も /ping が返る」症状として記載。
3. 実際の作業では SSH で Pi に入り、 sudo docker compose stop cloudflared で停止してから PC 側を起動。

結論

flowchart TB
  REQ[api.dammy-otoko.com] --> CF[Cloudflare]
  CF --> PI[Pi cloudflared]
  CF --> PC[PC cloudflared]
  PI --> OLD[PoC API]
  PC --> NEW[新 FastAPI]
          

手順の順序: Pi 停止 → PC 起動。PoC の api サービス停止は任意(Tunnel には無関係)。

Superengineer からの一言 移行作業は「古い出口(Pi 上の cloudflared)を止めてから、新しい出口(PC 上の cloudflared)を繋ぐ」作業だと考える。 両方が生きたまま疎通テストすると、成功したように見えて実は古い PoC API に当たっている、という最悪パターンがある。 切り替え前後は、応答内容(PoC の /ping か、新 API の /api/v1/health か)で向き先を必ず確認する。

K-05 Cloudflare 画面の「cloudflared.exe をインストール」

きっかけ

PC 側 cloudflared が token エラーで落ちている間、Cloudflare ダッシュボードに 「Install cloudflared connector」および Windows 向け cloudflared.exe service install が表示された。 「これを実行しないと Tunnel が有効化されないのでは?」という不安。

経緯

1. ダッシュボードは「アクティブなコネクタが0台」の状態を検知すると、 汎用的なインストール手順(Windows / macOS / Docker タブ)を表示する。
2. 本プロジェクトは runbook どおり Docker Compose の cloudflared コンテナ で運用。 Pi の PoC も Docker だった。Windows ネイティブの .exe インストールは別ルート。
3. 必要なのはインストール手順そのものの実行ではなく、手順内に含まれる cloudflared.exe service install の後に続く長いトークン文字列server/.envTUNNEL_TOKEN= に設定すること。 Docker タブを選んでも同じトークンが表示される。
4. 正しいトークン設定後、ダッシュボード上もコネクタ接続済みとなり、 MS-1 疎通が取れた。

結論

ダッシュボードの .exe 手順 = コネクタ未接続時のガイド。 本プロジェクトでは docker compose --profile tunnel up -d cloudflared が .exe インストールに相当する。

Superengineer からの一言 Cloudflare の管理画面は「とにかく早く1台繋げる」ための一般手順を出す。 自分たちの runbook(Docker Compose で cloudflared を動かす)と表示が違っても慌てない。 確認すべきは「同じ Connector Token を、自分たちの runbook の方法(docker compose --profile tunnel up)で渡せているか」だけでよい。

K-06 Docker Desktop 未起動 — pipe エラー

エラー解消 2026-06-07

きっかけ

Windows PC で docker compose up を実行したところ、 最初は docker コマンド自体が見つからない。 インストール後は docker version で Client(クライアント)の情報だけ表示され、 Server(サーバー=コンテナを実際に動かす Docker Desktop 本体)の行がなく、 open //./pipe/dockerDesktopLinuxEngine: The system cannot find the file specified というエラー。 Raspberry Pi 用の sudo docker を Windows で試すも、Windows 標準では sudo は使えない。

経緯

1. Docker Desktop 4.76.0 をインストール。 インストール方式は「PC 全ユーザー共通(All users / 共有アカウント)ではなく、 ログイン中の個人アカウント(personal アカウント)向け」。 実体は %LOCALAPPDATA%\Programs\DockerDesktop\ 配下に置かれる。 インストール直後は docker コマンド(CLI)だけが使える状態になり、 Docker Desktop アプリ本体(Engine)がまだ起動していないことがある。
2. スタートメニューから Docker Desktop アプリを起動。 タスクバーのクジラアイコンが「Docker Desktop is running」になるまで待つ(1〜3 分)。
3. 新しい PowerShell ウィンドウを開き、docker version を再実行。 出力に Server: 行(例: Docker Desktop 4.76.0)が表示されれば、 Engine(コンテナ実行エンジン)の起動完了。
4. その後 docker compose up -d --build db api が成功。

結論

docker コマンドは「リモコン(Client)」、Docker Desktop 本体は「エンジン(Server)」。 リモコンだけあっても、エンジンが止まっていればコンテナは動かない。 Windows では Linux と違い sudo は不要。

flowchart LR
  PS[PowerShell の docker コマンド]
  CLI[Docker Client]
  ENG[Docker Engine Docker Desktop本体]
  PS --> CLI
  CLI -->|"名前付きパイプで接続"| ENG
  ENG --> CNT[db api cloudflared コンテナ]
  CLI -.->|"Engine 未起動"| ERR[pipe not found エラー]
          
Superengineer からの一言 docker コマンドが打てることと、Docker でコンテナが動くことは別問題。 作業開始時は docker version を実行し、Server: の行があるかを習慣的に確認する。 PC 再起動後は、いつもより先に Docker Desktop アプリを起動する。経験者でもここを忘れがち。

K-07 docker compose コマンドの読み方

きっかけ

MS-1 確認用に4つのコマンドを実行したが、「何をしているか分からない」状態だった。 runbook の手順を理解するための整理。

経緯

2026-06-07、次の順で実行し MS-1 を達成した。

  1. docker compose up -d --build db api — DB・API 起動(Tunnel なし)
  2. curl.exe http://localhost:8000/api/v1/health — ローカル疎通
  3. docker compose --profile tunnel up -d cloudflared — Tunnel 接続
  4. test-feedback.ps1 -BaseUrl "https://api.dammy-otoko.com" — 外部経路で FB 送信

結論

sequenceDiagram
  participant U as 操作者
  participant DC as docker compose
  participant DB as db
  participant API as api
  participant CF as cloudflared
  U->>DC: up -d --build db api
  DC->>DB: Healthy 待ち
  DC->>API: 起動 port 8000
  U->>DC: --profile tunnel up cloudflared
  DC->>CF: TUNNEL_TOKEN で接続
          
オプション意味
-dバックグラウンド実行(detached)。ターミナルを占有せず、裏でコンテナを動かし続ける
--build起動前に Docker イメージをソースから再ビルドする(コード変更を反映)
db apidbapi サービスだけ起動(cloudflared はこの段階では起動しない)
--profile tunneldocker-compose.ymlprofiles: [tunnel] が付いた cloudflared だけを追加で有効化する
Superengineer からの一言 最初から dbapicloudflared を一括起動せず、 「DB+API 起動 → localhost 疎通 → cloudflared 起動 → 公開 URL 疎通」の順に進めたのは良い判断。 障害が出たとき「どの段階まで成功しているか」が分かりやすく、切り分けに時間がかからない。

K-08 Raspberry Pi 上 Docker 停止 — sudo とサービス名

エラー解消 2026-06-07

きっかけ

Pi に SSH 接続し docker compose ps を実行。 permission denied while trying to connect to the docker API at unix:///var/run/docker.socksudo 付きで再実行すると cloudflared が Up 5 weeks と確認できた。 停止後、PoC API(コンテナ名 poc-api)も止めたいが docker compose stop poc-apino such service

経緯

1. ユーザー damio01docker グループに所属しておらず、 Docker デーモン(/var/run/docker.sock)へのアクセス権がない。 そのため sudo docker compose ...(管理者権限で実行)が必要だった。
2. sudo docker compose ps の出力には NAME 列と SERVICE 列がある。 docker compose stop に指定するのは SERVICE 列 の名前 (cloudflared / api)。 NAME 列の poc-api はコンテナ名であり、stop の引数には使えない。
3. sudo docker compose stop cloudflared で Tunnel コネクタを停止 — 成功。 Tunnel 移行に必須。
4. PoC API を止める場合は sudo docker compose stop api(サービス名)。 コンテナ名 poc-api ではない。

結論

flowchart TB
  PS[docker compose ps]
  PS --> NAME[NAME = コンテナ名]
  PS --> SVC[SERVICE = stop で指定する名前]
          
Superengineer からの一言 docker compose ps を見るときは SERVICE 列を確認する癖をつける。 permission denied が出たら、まず sudo を付けて再実行してよい。 恒久的には Pi のユーザーを docker グループに追加する方法もあるが、 今回の移行作業では sudo docker compose stop cloudflared だけで Tunnel 切り替えは足りる。 PoC の api まで止めるのは必須ではなく、旧 API と混同しないための任意作業。

K-09 PowerShell の curl — 別名問題

エラー解消 2026-06-07

きっかけ

docker compose up 成功直後、 curl http://localhost:8000/api/v1/health が 「接続が予期せずに閉じられました」。 コンテナは Started なのに疎通失敗 — API が壊れたのかと誤解しやすい。

経緯

1. PowerShell では curl と打つと、実際には Invoke-WebRequest(PowerShell 組み込みの HTTP コマンド)の別名が動く。 Linux や runbook で想定している curl とは挙動が異なる。
2. curl.exe http://localhost:8000/api/v1/health に変更 → {"status":"ok"}Invoke-RestMethod でも同様に成功。
3. 別件として、test-feedback.ps1 の JSON 送信は Windows curl のインライン JSON 問題(BOM・エスケープ)を避けるため、 事前に inference.json ファイルを生成する方式に修正済み(開発ログ参照)。

結論

Windows の runbook 疎通確認は curl.exe を明示する。runbook v1.3 で反映済み。

Superengineer からの一言 Windows で「curl したのに変」はよくある。runbook どおり HTTP 疎通を確認するときは、 最初から curl.exe と拡張子まで付けて実行する。 コンテナ起動直後の一瞬だけ失敗することもあるが、今回の主因は PowerShell の別名問題だった。 失敗したら API のログ(docker compose logs api)と curl.exe の両方で切り分ける。

K-10 なぜ PostgreSQL があるか

きっかけ

FB 受信で画像は data/storage/ に保存される。 それならファイルだけで足りないか — DB の役割の質問。

経緯

1. MS-1 完了条件に「feedbacks テーブルにレコード記録」が含まれる。
2. 将来の再学習パイプラインで、いつ・どんな FB を受け取ったかを 構造化データとして扱う必要がある。
3. 基本設計では PostgreSQL または SQLite。検証環境として PostgreSQL を Docker で採用。

結論

flowchart LR
  POST[POST /feedback] --> IMG[画像 storage/]
  POST --> DB[(feedbacks)]
          
Superengineer からの一言 画像ファイルは「中身」、PostgreSQL の feedbacks テーブルは「いつ・どのファイルか」の索引とメタデータ。 最初は SQLite でも動くが、本番に近い Docker Compose の練習として PostgreSQL を入れた判断は妥当。

K-11 疎通確認 — localhost と公開 URL

きっかけ

test-feedback.ps1-BaseUrl がある。 引数なしと https://api.dammy-otoko.com 指定の違いが MS-1 確認で重要。

経緯

1. WBS 1.1.3: 引数なし(localhost:8000)で API 実装の正しさを確認 — 完了。
2. WBS 1.1.1 / MS-1: 公開 URL 指定で Tunnel 経路全体を確認。 localhost OK でも Tunnel NG(今回の token 問題)があり得る — 両方必要。

結論

flowchart TB
  subgraph A["経路A localhost"]
    L1[PC] --> L2[api:8000]
  end
  subgraph B["経路B 公開URL MS-1"]
    T1[PC] --> T2[Cloudflare] --> T3[cloudflared] --> T4[api]
  end
          
Superengineer からの一言 localhost で health が通って安心した段階は、あくまで「PC 内の API が動いている」証明まで。 MS-1 の完了条件は「インターネット経由(https://api.dammy-otoko.com)でも同じ API が応答する」こと。 公開 URL で test-feedback.ps1 が成功するまで、Tunnel 移行は完了と言わないでほしい。

K-12 MS-1 達成までの全体の流れ(2026-06-07)

経緯(時系列まとめ)

flowchart TD
  A[Docker Desktop 起動] --> B[db + api 起動]
  B --> C[localhost 疎通 OK]
  C --> D[Pi cloudflared 停止 sudo]
  D --> E[PC cloudflared 起動]
  E --> F{TUNNEL_TOKEN 正しい?}
  F -->|Tunnel ID を誤設定| G[255 / 1033]
  G --> H[service install 以降のトークンに修正]
  F -->|Connector Token 正| I[cloudflared Up]
  H --> I
  I --> J[公開 URL health OK]
  J --> K[test-feedback.ps1 MS-1]
          

最大のハマりどころは TUNNEL_TOKEN に Tunnel ID(UUID)を入れてしまったこと。 正しくは Cloudflare 画面の cloudflared.exe service install の後に続く長い文字列。 それ以外(Docker Desktop 未起動、Pi で sudo が必要、PowerShell の curl 別名)は、 個別に切り分け可能な問題だった。

Superengineer からの一言 1 日で PoC 移行 + MS-1 まで来たのは十分速い。 詰まった時間はナレッジに変換できている — 次の人(未来の自分含む)のコスト削減になる。 WBS 1.2 に進む前に、PC 再起動後の起動手順(Docker Desktop 起動 → docker compose up)を一度だけ試しておくと安心。

K-13 エラー早見表

flowchart TD
  START[問題発生]
  START --> Q1{docker version Server?}
  Q1 -->|No| A1[Docker Desktop 起動]
  Q1 -->|Yes| Q2{cloudflared STATUS}
  Q2 -->|Restarting 255| A2[logs: token invalid → service install 以降を設定]
  Q2 -->|Up| Q3{外部 URL}
  Q3 -->|1033| A3[Pi 残存? token?]
  Q3 -->|OK| OK[疎通成功]
          
症状今回の経緯での原因最初の一手
docker 未認識Desktop 未インストール / PATHDesktop インストール・起動
dockerDesktopLinuxEngineDocker Desktop 本体(Engine)が起動していないDocker Desktop アプリを起動し、docker version で Server 行を確認してからターミナルを開き直す
token is not validTunnel ID(UUID)を TUNNEL_TOKEN に設定Configure 画面の cloudflared.exe service install 以降の文字列をコピー
error code: 1033cloudflared が Tunnel に接続できていない(token 誤り、または Docker Desktop 未起動docker version → Desktop 起動 → docker compose psdocker compose logs cloudflared
image must be an image file(HTTP 400)multipart の画像に Content-Type: image/* が付いていない(Flutter Android 典型)XFile.mimeTypeMultipartFile に渡す(K-17
adb が認識されないAndroid SDK の platform-tools が PATH 未設定$env:LOCALAPPDATA\Android\Sdk\platform-tools\adb.exe をフルパスで実行
INSTALL_FAILED_UPDATE_INCOMPATIBLE 端末に別署名の同一 package が残っている(flutter run デバッグ版、または旧 release) adb uninstall com.horsediagnosis.horse_stubinstall-apk.ps1 再実行。v1・v2 両方で確認済み(2026-06-10)
invalid api key(HTTP 401)FEEDBACK_API_KEY の置き場所・シェル環境変数・コンテナ未再起動K-30server/.env のみ、Remove-Item Env:FEEDBACK_API_KEY-RestartApi
PowerShell curl 失敗Invoke-WebRequest 別名curl.exe
Pi permission denieddocker.sock 権限sudo docker compose
no such service: poc-apiサービス名とコンテナ名の混同stop api / stop cloudflared
Superengineer からの一言 1033 は「Tunnel の向こうに誰もいない」サイン。token 以外にDocker Desktop 停止でも毎回出る。 実機テストの朝は、スマホより先に PC で curl.exe .../health を叩く癖をつけると早い。 400 の「image must be…」は curl スクリプトとアプリの multipart 差分 — PC で通って実機で落ちる典型パターン。

K-14 Git — add / commit / push の順序

操作ミス 2026-06-07

きっかけ

MS-1 達成後、README・開発ログ・Phase1 ナレッジなどの変更を GitHub へ反映しようとした。 git add . のあと、 git push -m "Cloudflaredをraspberry pi →Windowsへ置き換え" を実行したところ、 error: unknown switch 'm' となった。 「add の次は push ではなかったか?」という疑問。

経緯

1. git status で変更ファイルを確認。 修正済み HTML・README と、新規の docs/records.html#dev-logdocs/knowledge.html などが 「Changes not staged」または「Untracked」として表示された。
2. git add . を実行。 変更が「Changes to be committed」(ステージ済み)に移動。 この時点ではまだ GitHub には何も送られていない。ローカル PC 上の「コミット予定箱」に載っただけ。
3. git push -m "..." を実行 → 失敗。 push コマンドには -m(メッセージ)オプションがない。 -mgit commit 用。
4. 正しい順序は git commit -m "メッセージ"(ローカルに記録)→ git push(GitHub へ送信)。
5. なお git statusYour branch is ahead of 'origin/main' by 1 commit と出ている場合、 以前に commit 済みで push だけされていないコミット がある。 新しい commit のあと、push 1回で複数コミットまとめて送れる。

結論

flowchart LR
  W[作業ディレクトリの変更] --> A["git add"]
  A --> S[ステージ領域]
  S --> C["git commit -m メッセージ"]
  C --> L[ローカル履歴]
  L --> P[git push]
  P --> R[GitHub origin]
          
コマンド意味メッセージ -m
git add次の commit に含めるファイルを選ぶ使わない
git commit -m "..."ステージ内容をローカル履歴に記録するここで書く
git pushローカルの commit を GitHub へ送る使わない

実行例:

git status
git add .
git commit -m "CloudflaredをRaspberry PiからWindows PCへ移行し、MS-1達成のドキュメントを更新"
git push

コミットに含めない方がよいもの: server/.env(秘密情報)は .gitignore 済み。 scripts/inference.jsontest-feedback.ps1 実行時に毎回生成される一時ファイルのため、 必要なければ add しない(または今後 .gitignore に追加する)。

Superengineer からの一言 「add した=GitHub に上がった」ではない。commit するまでローカル、push するまでリモート、の2段階を意識する。 コミットメッセージは「何をしたか」より「なぜ/何が達成されたか」を短く書くと、後から log を追いやすい。 push 前に git status で「ahead of origin by N commits」を見れば、送る commit の数も分かる。 .env が accidentally staged されていないかだけは毎回目視確認する習慣を。

K-15 次の段階 — WBS 1.2 スタブ Android アプリ

きっかけ

WBS 1.1(サーバー環境構築)と MS-1(Tunnel 経由 FB 受信)が完了した。 次に何をするか — WBS・検証計画では 1.2 スタブ Android アプリ が続く。

経緯

1. MS-1 では PC 上の test-feedback.ps1 から https://api.dammy-otoko.com へ FB を送り、インフラ経路を確認した。
2. 本番に近い検証では、モバイル端末(またはエミュレータ)上のアプリ から 同じ URL へ FB を送る必要がある(WBS 1.2.3)。
3. 診断精度そのものは検証対象外のため、Flutter のスタブ (画像選択・ダミー推論結果・FB 送信の最小実装)で足りる。

結論 — WBS 1.2 の内訳

flowchart LR
  S1["1.2.1 Flutter プロジェクト app/"]
  S2["1.2.2 画像選択・ダミー推論・FB 送信"]
  S3["1.2.3 端末から Tunnel 経由 FB 検証"]
  S1 --> S2 --> S3
          
WBS内容成果物イメージ
1.2.1Flutter プロジェクト作成app/ 最小構成
1.2.2FB 送信実装画像 + 推論 JSON を POST /api/v1/feedback
1.2.3端末検証実機/エミュレータから api.dammy-otoko.com へ送信成功

前提(サーバー側): Windows PC で Docker Desktop 起動 → dbapicloudflared が稼働していること。

参照: WBS・ガントチャート機能・関数定義一覧(F-01〜F-03)、データフロー

Superengineer からの一言 1.2 以降は「サーバーが動く」前提でクライアント側の話になる。 スタブは見た目より通信設定(HTTPS の Base URL、multipart 形式)が本体。 PC の curl で通った URL を、そのままアプリの設定値に持たせる — ここを変えないこと。 2026-06-08: Pixel 7a で 1.2.3 着手。1033 は Docker 未起動、400 は Content-Type 不足で切り分け済み。

K-16 実機検証 — USB デバッグ・ログの見方

WBS 1.2.3 2026-06-08

きっかけ

Pixel 7a で flutter run 後、「フィードバックを送信」が失敗。 USB ログ(adb logcat 等)で原因を追えるか確認したい。

経緯

1. 初回 USB 接続で Device ... is not authorized。 Pixel 上の「USB デバッグを許可しますか?」を承認して解消。
2. flutter run -d 3C261JEHN19182 で APK ビルド・インストール成功。 NDK 26/27 警告は出るがビルドは通過。
3. 送信失敗時、画面に 送信失敗: Exception: HTTP ... が表示される。 現状のスタブは debugPrint 未実装のため、flutter run ターミナルに必ずしも HTTP 詳細は出ない。
4. adb logcat -s flutter を試したが、PowerShell で adb が PATH 未設定で失敗。 SDK 本体は %LOCALAPPDATA%\Android\Sdk\platform-tools\adb.exe に存在。
5. PC 側 curl.exe .../health が 1033 のとき、実機も同じ 1033 — インフラ問題と確定。 Docker Desktop 起動後は 400 に変化(API 到達は成功)。

結論

確認手段用途
アプリ画面のステータス文字列HTTP ステータス・レスポンス body(いま一番手軽)
flutter run ターミナルビルド・I/flutter ログ(HTTP 詳細は未出力)
adb.exe logcat -s flutter端末側 Flutter ログ(PATH 未設定時はフルパス)
PC: curl.exe .../healthTunnel / サーバー稼働の切り分け(実機より先に確認)
PC: docker compose ps / logs1033・530 系の原因特定

Flutter を切る必要はない。 Docker 操作は別ターミナルで行い、修正後は Hot restart(R)で再試行可。

Superengineer からの一言 実機デバッグで困ったら「スマホの logcat」より先に「PC の curl」と「docker ps」。 530/1033 は端末ログを読んでも Tunnel が死んでいるだけ、ということが多い。

K-17 実機 FB 送信 — HTTP 400「image must be an image file」

解決済み 2026-06-08

きっかけ

Docker Desktop 復旧後、Pixel 7a から FB 送信。 ダミー推論(label / score)は画面に表示されるが、送信は HTTP 400: {"detail":"image must be an image file"} で失敗。

経緯

1. 530/1033 解消後、400 が返る — リクエストは Cloudflare → API まで到達している。
2. サーバー feedback.pyimage.content_type.startswith("image/") を検証。空または application/octet-stream だと 400。
3. PC の test-feedback.ps1-F "image=@...;type=image/jpeg" で Content-Type を明示しており MS-1 成功。
4. Flutter feedback_client.dartMultipartFile.fromPath('image', imagePath) のみ。 Android ではギャラリー画像が拡張子なしキャッシュパスになり、MIME 推定が失敗しやすい。

結論 — 修正と結果

image_pickerXFile.mimeType を保持し、multipart 送信時に Content-Type を付与。2026-06-08 に Pixel 7a で HTTP 201 を確認。

// main.dart: 画像選択時
_imageMimeType = picked.mimeType;

// feedback_client.dart: 送信時
await http.MultipartFile.fromPath(
  'image',
  imagePath,
  contentType: MediaType.parse(mimeType ?? 'image/jpeg'),
);

修正後: Hot restart → 画像再選択 → 送信成功(HTTP 201)→ data/storage/ にファイル追加、で WBS 1.2.3 完了。

Superengineer からの一言 「curl では通るのにアプリでは 400」は multipart の Content-Type 差分を疑え。 サーバー側を緩めるより、クライアントで正しい MIME を付ける方が本番に近い。

K-18 Flutter 開発の基礎 — ライフサイクルと実機検証の手順

基礎知識 2026-06-08 追記

きっかけ

WBS 1.2.3 の実機検証で flutter run を使ったが、Flutter のライフサイクル (Hot reload と restart の違い、いつ何をするか)を把握していない状態だった。 本プロジェクトでは AI エージェントがビルド・修正を代行することが多いため、 開発者が最低限押さえる操作をここにまとめる。

Flutter 開発の全体像(3 段階だけ覚える)

flowchart LR
  P1["① 準備\nDocker・USB デバッグ"]
  P2["② flutter run\nビルド・インストール・起動"]
  P3["③ アプリ操作\n画像選択 → FB 送信"]
  P1 --> P2 --> P3
          

flutter run は「ソースをビルド → スマホに APK を入れる → アプリを起動 → PC と接続してログを表示する」 コマンドを一括で実行する。終了するまで PC とスマホは開発セッションとしてつながったまま。

開発セッションの標準手順(本プロジェクト)

順序どこでコマンド / 操作確認ポイント
1 PC Docker Desktop 起動 タスクバーにクジラアイコン
2 PC cd serverdocker compose up -d db apidocker compose --profile tunnel up -d cloudflared curl.exe https://api.dammy-otoko.com/api/v1/health{"status":"ok"}
3 スマホ USB デバッグ ON、ケーブル接続、「許可」タップ 初回は開発者オプションの有効化が必要
4 PC cd appflutter devices Android 端末が 1 台表示される(例: Pixel 7a)
5 PC flutter run(複数台あるときは flutter run -d <id> 初回は数分かかる。完了後スマホにアプリ起動
6 スマホ 画像選択 → ダミー推論確認 → FB 送信 「送信成功 (HTTP 201)」と ID 表示
7 PC dir data\storage 新しい画像ファイルが増えている

flutter run 中のキー操作(ライフサイクルの核心)

flutter run が動いているターミナルでは、キー 1 文字で操作できる。 コードを直したあと「反映されない」ときは、reload と restart の違いを疑う。

キー名称何が起きるかいつ使うか
r Hot reload 動いているアプリの上に変更を載せる。速い 文言・色など UI の軽い変更
R Hot restart アプリを最初から再起動(状態はリセットされやすい) ロジック修正後(例: K-17 の Content-Type 修正後)。確実に反映したいとき
q Quit セッション終了。アプリは端末に残るがデバッグ接続は切れる 作業終了時。完全にやり直すなら再度 flutter run
Hot reload と Hot restart の覚え方: reload(r)は「走っているアプリに差し替え」、restart(R)は「一度リセットして起動し直し」。 画像選択後の状態(mimeType など)を含む修正では R のあと画像を選び直すのが安全。

初回セットアップで一度だけやること

項目コマンド / 操作備考
Android ライセンス承認 flutter doctor --android-licenses すべて y。未実施だとビルド失敗することがある
Windows 開発者モード start ms-settings:developers → 開発者モード ON flutter pub get で symlink 警告が出たとき。プラグイン利用に必要な場合あり
Docker Hub サインイン スキップ可 ローカル docker compose のみなら不要

本プロジェクト固有の設定

項目値 / パス
Flutter プロジェクトapp/(パッケージ名 horse_stub
デフォルト API URLhttps://api.dammy-otoko.comapp/lib/config.dart。画面でも変更可)
送信先エンドポイントPOST /api/v1/feedback
サーバー保存先data/storage/(リポジトリルート、Docker ボリュームマウント)

よくあるつまずき(Flutter 編)

症状原因の例対処
flutter devices にスマホが出ない USB デバッグ OFF、未許可、ケーブル不良 開発者オプション確認・許可ダイアログ・差し直し
初回ビルドが長い Gradle が依存を取得している 正常。2 回目以降は短くなる
コードを直したのに挙動が変わらない Hot reload だけでは不十分 R(Hot restart)または q 後に再 flutter run
送信が 1033 / タイムアウト サーバー・Tunnel 停止 先に PC で docker compose ps と health 確認(K-06
送信が HTTP 400 image must be… multipart の Content-Type 不足 K-17 参照。R 後に画像を選び直す

関連ナレッジ

  • K-15 — WBS 1.2 の位置づけとスタブの目的
  • K-16 — USB デバッグ・ログの見方・1033 の切り分け
  • K-17 — 実機 FB 送信の HTTP 400 対応(解決済み)
Superengineer からの一言 Flutter のライフサイクルで覚えるのは実質 4 つで足りる: flutter devices(見えるか)、flutter run(入れるか)、 R(直したコードを確実に反映)、q(終わる)。 インフラ検証プロジェクトでは「アプリが動く」より「正しい URL に正しい形式で届く」が本体。 届かないときはスマホより先に PC の curldocker compose ps を見る癖をつけておくと楽。

K-19 YAML とは — 書式とツールごとの意味の違い

基礎知識 2026-06-09

きっかけ

WBS 1.4 で android-build.yml を扱うにあたり、Docker の docker-compose.yml と GitHub Actions の YAML の関係が分かりにくい。「CI/CD でよく使う形式なのか」「どこでも同じ書き方なのか」という疑問。

結論 — YAML とは

YAML(.yml / .yaml)は、設定やデータを人間が読みやすいテキストで書くためのファイル形式(書き方のルール)である。 プログラミング言語ではなく、設定ファイルの書き方に近い。

  • 字下げ(スペース)で階層を表す
  • キー: 値 が基本
  • - でリスト、# でコメント

共通する部分 / 違う部分

観点内容
共通(文法) 字下げ・キー: 値・リスト — Docker でも GitHub でも同じ YAML 文法
異なる(スキーマ) どんなキーを書けるか・何を意味するかはツールが決める
ファイルツール主なキー例
server/docker-compose.ymlDocker Composeservices, image, ports
.github/workflows/android-build.ymlGitHub Actionson, jobs, runs-on, steps

比喩: YAML は「用紙のフォーマット」、各 YAML ファイルは「ツール専用の申請書テンプレート」。 CI/CD で YAML がよく使われるのは、ビルド手順をリポジトリに置き、人間が読んで修正しやすいから。

Superengineer からの一言 「YAML が分かった」=「全部書ける」ではない。 困ったらそのツールの公式リファレンス(Compose file / Workflow syntax)を見る。 本プロジェクトでは Docker 用と GHA 用でファイルが2つ — 中身の辞書は別物。

K-20 GitHub Actions と android-build.yml — ビルド・artifact・APK

設計理解 2026-06-09

きっかけ

WBS 1.4 の検証手順で「YAML をリモートに載せる」「Actions で手動ビルド」「artifact から APK をダウンロード」が出てくる。 それぞれが実体として何を意味するかを整理したい。

android-build.yml の役割

GitHub Actions 向けの作業指示書。次の3つを1ファイルに宣言する。

定義する内容本プロジェクトでの例
いつ動くかworkflow_dispatch(手動)、repository_dispatch(Worker から)
何をビルドに含めるかどの .tfliteapp/assets/models/ にコピーするか
成果物をどうするかupload-artifact で APK を 30 日間保存

「リモートに載せる」とは

git push.github/workflows/android-build.yml を GitHub 上に置くこと。 GitHub がこのファイルをワークフロー定義として読み込む。

載せただけでは自動ビルドされない。 本プロジェクトの on:push: はない。 main へ push しても APK ビルドは走らず、Run workflow または repository_dispatch が必要。

追加の GitHub 設定: default ブランチに YAML があること、Settings → Actions が有効であること(多くは初期状態で ON)。

Actions で手動ビルド

GitHub 上の仮想マシン(ubuntu-latest)で flutter build apk --release 等を実行する。 ビルドはローカル PC ではなく GitHub クラウドで行われる。

artifact と APK

用語意味
APK Android アプリのインストール用ファイル(本プロジェクトでは app-release.apk
artifact GitHub Actions が1回の実行ごとに成果物を一時保管する仕組み。リポジトリには commit されない

端末へ反映する流れ:

  1. Actions 実行結果 → Artifacts → ZIP を PC にダウンロード
  2. ZIP を解凍 → app-release.apk を取り出す
  3. adb install または install-apk.ps1 で Pixel 7a へインストール

APK は GitHub 上に置かれたまま端末には届かない。PC 経由の手動インストールが Phase1 Android の想定(WBS 1.5.1)。

flowchart LR
  YML["android-build.yml を push"]
  GH["GitHub Actions クラウド"]
  ART["artifact ZIP"]
  PC["PC にダウンロード"]
  DEV["Pixel 7a adb install"]
  YML -->|"Run workflow または dispatch"| GH
  GH --> ART
  ART --> PC
  PC --> DEV
          

参照: Phase1 GHA・APK 手順

Superengineer からの一言 YAML=指示書、artifact=その回の納品物置き場、APK=Android に入れる本体。 「ビルドできた=端末に入った」ではない。ダウンロードと adb の2段が Phase1 の配布。

K-21 月次モデル更新パイプライン — 学習から配布まで

まとめ 2026-06-09

きっかけ

基本設計では月次1回のモデル更新を想定。 「人間が学習 → API で GitHub にビルド依頼 → 配布(TestFlight?)」という全体像を、このプロジェクトの実装に即して整理したい。

月次運用の流れ(Phase1)

flowchart TB
  H["人間: 月1回 学習を実施"]
  PS["run-training-stub.ps1"]
  TR["学習スタブ: models/versions/vN/"]
  API["GitHub API repository_dispatch"]
  GHA["android-build.yml 起動"]
  APK["artifact: app-release.apk"]
  ADB["adb install → Pixel 7a"]
  H --> PS
  PS --> TR
  PS --> API
  API --> GHA
  GHA --> APK
  APK --> ADB
          

自動 / 手動の境界

工程自動?備考
学習の開始手動月1回、人が run-training-stub.ps1 を実行
学習完了 → GHA 起動自動スクリプト内で repository_dispatchmodel-updated
APK ビルド・artifact 生成自動GitHub Actions(android-build.yml
端末へのインストール手動artifact ダウンロード → install-apk.ps1(① Android)

repository_dispatch について

「学習後に別途 PowerShell で API を叩く」のではなく、 run-training-stub.ps1学習スタブと dispatch を連続実行する。 Worker は POST /repos/{owner}/{repo}/dispatches を送信(worker/github_dispatch.py)。 要 worker/.envGITHUB_TOKEN(PAT)と GITHUB_REPOSITORY

配布 — Android と iPhone の違い

検証段階配布方式自動度(現状 / 目標)
① Android(今) APK + adb artifact 取得 → 手動 install(Phase1)
② iPhone(後) TestFlight GHA + Fastlane で upload を検証予定(design.html#detailed
TestFlight は iPhone 向け。 Android には使わない。 現行の android-build.yml だけでは TestFlight までは行かない。

参照: データフロー(通信方向 ②③)、 Phase1 Worker 手順Phase1 GHA・APK 手順

Superengineer からの一言 月次運用のトリガーは人、ビルドは GitHub、配布は Phase1 では人(adb)。 ② で TestFlight 自動化を足す — ① でパイプラインの骨格を固めるのがこのプロジェクトの順番。

K-22 PAT と Actions Secrets — Worker dispatch の認証

設計理解 2026-06-10

きっかけ

WBS 1.4.3 の Worker dispatch 準備で、リポジトリの Settings → Secrets and variables → Actions を開いたが、 PAT 発行画面ではなかった。

結論 — 2 種類の「トークン置き場」

種類置き場所誰が使うか今回の用途
Actions Secrets リポジトリ Settings → Secrets and variables → Actions GitHub クラウド上で動くワークフロー(YAML 内 secrets.* 不要(方法 B では使わない)
PAT(Personal Access Token) ユーザー Settings → Developer settings → Personal access tokens 開発者の PC から GitHub REST API を叩く 必要worker/.envGITHUB_TOKEN

PAT 発行手順(Classic)

  1. GitHub 右上アイコン → Settings(ユーザー)
  2. Developer settings → Personal access tokens → Generate new token (classic)
  3. Scope: repo
  4. ghp_...worker/.env に記載(gitignore、commit 禁止)

run-training-stub.ps1POST /repos/{owner}/{repo}/dispatches を送る(worker/github_dispatch.py)。 成功時は HTTP 204(body なし)。

参照: Phase1 Worker 手順 §3、 GHA 操作手順 §B

Superengineer からの一言 Actions Secrets は「クラウド側の金庫」、PAT は「あなたの PC が API を叩く通行証」。 リポジトリ Settings を漁っても PAT は出てこない — Developer settings が正解。

K-23 Phase1 運用 — PAT ローテーション・artifact 保持・APK 世代管理

運用 2026-06-10

きっかけ

方法 A/B の E2E が進み、月次運用を想定したときの「期限切れ」「成果物の消滅」「端末に残るバージョン」の整理が必要になった。

1. PAT — 生成・期限・ローテーション

観点内容
必要な場面方法 B(Worker dispatch)のみ。方法 A(Run workflow)は不要
期限切れ時dispatch 403 等 → GHA が自動起動しない
運用有効期限の 1〜2 週間前に新 PAT 発行 → worker/.env 差し替え。カレンダーリマインダー推奨

2. artifact — GitHub 上の保持期限

android-build.ymlupload-artifactretention-days: 30。 30 日経過後、GitHub 上の artifact は削除される(リポジトリ commit とは無関係)。

flowchart LR
  GHA["GitHub Actions ビルド"]
  ART["artifact(最大30日)"]
  PC["PC に ZIP/APK 保存"]
  DEV["Pixel adb install"]
  GHA --> ART
  ART -->|"期限内にダウンロード"| PC
  PC --> DEV
          

3. ローカル APK 世代管理(推奨)

  • ダウンロードした APK にバージョンラベルを付けて PC に保管(例: app-release-v1.apkv2
  • 直近 2〜3 世代を保持 — ロールバック・比較用
  • 大容量ファイルは gitignore 推奨(リポジトリには載せない)
  • v1 → v2 など世代更新時、adb install -rINSTALL_FAILED_UPDATE_INCOMPATIBLE になる場合は先に adb uninstall com.horsediagnosis.horse_stubK-13

4. 配布経路の整理

段階配布備考
① Android(Phase1)artifact → adb install手動ダウンロードが必須
② iPhone(後)TestFlightGHA + Fastlane 検証予定。Android には使わない

参照: GHA 操作手順 §FK-21(月次パイプライン)

Superengineer からの一言 GitHub artifact は「30日限りの受取所」。届いた APK は自分の PC が倉庫。 PAT も同様 — 期限切れ前に差し替えないと、月次の自動起動が静かに止まる。

K-24 OSI 参照モデルとネットワークの前提 — ポート・FW・境界

基礎知識 2026-06-12 — セキュリティ設計の前提

きっかけ

本番セキュリティの話(API 認証・FW・DB 非公開)を理解するには、 「ネットワーク境界」「FW(ファイアウォール)」「ポート」などの前提が必要だった。 Raspberry Pi 構築時に FW 設定をしたが、OSI 参照モデルとの対応は整理されていなかった。

OSI 参照モデル — 7 層を「郵便」でイメージする

全部暗記する必要はない。層 3〜4(IP・ポート)と層 7(HTTP/API)が本プロジェクトでは重要

名前たとえ本プロジェクトの例
7アプリケーション手紙の内容(日本語で書いた依頼)HTTP、POST /api/v1/feedback
6表現内容を暗号化して封筒に入れるTLS(HTTPS の暗号化)
5セッション(本件ではほぼ意識しない)
4輸送建物の部屋番号(どの窓口か)TCP ポート 4438000
3ネットワーク建物の住所(IP アドレス)インターネット、LAN の 192.168.x.x
2データリンク(省略可)Wi‑Fi、LAN ケーブル
1物理道路そのもの光回線、LAN ハブ

ネットワーク境界 — 「誰がどこから届くか」の線

flowchart TB
  subgraph zone_internet["ゾーン: インターネット(全世界)"]
    Client[スマホ・curl]
  end
  subgraph zone_cf["ゾーン: Cloudflare"]
    Edge[エッジ HTTPS :443]
  end
  subgraph zone_pc["ゾーン: 役場 PC"]
    Tunnel[cloudflared]
    API[FastAPI :8000]
    DB[(PostgreSQL :5432 内部のみ)]
  end
  subgraph zone_lan["ゾーン: 同一 LAN(社内 Wi-Fi 等)"]
    LANPC[別の PC・スマホ]
  end
  Client --> Edge --> Tunnel --> API --> DB
  LANPC -.->|"裏口になりうる"| API
          

境界 = ゾーンの境目。セキュリティは「各境界に門番を置く」イメージ。 DB は PC の内側(Docker ネットワーク)だけにいるのが内側の境界

ゾーン(Zone)とは

「信頼度や到達範囲が違うエリア」に名前を付けたもの。厳密な IT 用語というより、 図を読みやすくするためのラベル。 「ここにいる人/機器は、あそことはルールが違う」と区切る箱。

ゾーン(本プロジェクト)中にいるものざっくりした信頼度
インターネット世界中の端末・curl低(誰でもいる)
Cloudflareエッジ・Tunnel の入口中(契約・設定次第)
役場 PCAPI・DB・Storage高(自分たちの資産)
同一 LAN同じ Wi‑Fi の別端末中〜高(社内なら比較的安全だが、インターネットよりは近い)

ゾーンをまたぐたびに「門番はいるか?」と聞くのがセキュリティ思考の基本(K-29)。 商用 FW 製品では「DMZ」「内部網」など正式なゾーン名を使うが、考え方は同じ。

FW(ファイアウォール)とは

層 3〜4 で動く門番。「この IP からの、このポート番号への入り口は開ける/閉じる」を決める。 ソフトウェアが多い(Windows Defender ファイアウォール、Linux の iptables/nftables)。 ルーターやクラウドにも FW 機能が入っている(ハードとソフトの境目は曖昧で、役割は同じ)。 Raspberry Pi で設定したのも同種。

FW だけがやること(本質):

  • 通す / 通さない を、主に 方向 × IP × ポート で決める
  • 例: 「外向き(PC → 外)は OK」「内向き(LAN → PC の 8000)は NG」

FW だけではやらないこと:

  • HTTP の内容を読んで「正規アプリか」判断する(それは層 7・API 認証)
  • パスワードの中身を検証する(それはアプリや DB)
  • ファイルサイズの制限(それも層 7)

つまり FW は「内〜ポート〜外」の出入り口の開閉が主役で合っている。 ただし「通信の中身までは見ない門番」なので、開けた穴の先には別の門番も要る(多層防御)。

設定例意味
外向き通信は許可cloudflared が Cloudflare に接続できる(Tunnel に必須)
内向き 8000 を拒否LAN から PC の API に直で入れない
5432 を外に出さないDB への直接接続を防ぐ(Docker でも ports 未公開)

本プロジェクトの現状と目標

現状目標(本番)
L7 API誰でも POST 可API キー必須(K-26
L6 HTTPSCloudflare で有効維持(K-25
L4 ポート 8000docker-compose で PC に公開localhost のみ + FW(SEC-03
DB 5432Docker 内部のみ維持 + 強パスワード

結論

セキュリティは一箇所の鍵だけでは足りない(多層防御)。 FW は「裏口(ポート)を閉じる」、API 認証は「正規の利用者だけ通す」、HTTPS は「途中で読まれにくくする」—— 層が違うので全部そろえて初めて意味が通る

Superengineer からの一言 OSI は「全部覚える教科書」ではなく、トラブル時に「今どの層の話か」を切り分ける地図。 「繋がらない」は L3〜4、「変なデータが入る」は L7、と分けると調査が速い。

K-25 HTTPS と Cloudflare Tunnel — 何を守り、何を守らないか

基礎知識 2026-06-12

きっかけ

「Cloudflare Tunnel 経由の HTTPS は安全か?」「SSL っぽい設定も見たがどういうことか?」 — セキュリティ設計を読む前に、通信の保護範囲を押さえる必要があった。

HTTPS が守るもの

転送中(路上)の盗み見・改ざんを困難にする。カフェ Wi‑Fi など不信頼な回線でも有効。

  • ブラウザやアプリの鍵マーク = 「この区間は暗号化されている」
  • 層 6(TLS)の仕事。Cloudflare が証明書を管理するので自前で Let's Encrypt 等は不要

通信の流れ(本プロジェクト)

sequenceDiagram
  participant App as スマホアプリ
  participant CF as Cloudflare エッジ
  participant Tun as cloudflared
  participant API as FastAPI

  App->>CF: HTTPS 暗号化(インターネット上)
  Note over App,CF: ここが「HTTPS が守る区間」
  CF->>Tun: Tunnel(専用の道)
  Tun->>API: 多くは HTTP(PC 内 localhost)
  Note over Tun,API: PC の中。外からは見えない
          

Cloudflare Tunnel の追加メリット

従来のサーバー公開Tunnel(本プロジェクト)
自宅/役場のグローバル IP を全世界に公開 グローバル IP 非公開。cloudflared が外向きに接続
ルーターでポート開放が必要 ポート開放不要(CGNAT でも可)
Let's Encrypt 等で証明書管理 Cloudflare が HTTPS 終端

HTTPS が守らないもの

重要: HTTPS は「通信内容を暗号化する」のであって、「正規アプリだけが使える」ことを保証しない。
  • https://api.dammy-otoko.com/api/v1/feedback の URL を知った人は、curl でも同じ HTTPS で POST できる
  • 暗号化されているので路上では内容が読まれにくいが、サーバーはリクエストを受け付けてしまう
  • だから 層 7 の API 認証K-26)が別途必要

結論

Tunnel + HTTPS は通信路として妥当で、本プロジェクトでも採用継続。 残るリスクは「誰が API を叩けるか」「どれだけ送れるか」で、これはアプリ層の設計課題。

Superengineer からの一言 HTTPS は「封筒に鍵付きで送る」。中身が許可された依頼かどうかは封筒では分からない。 Tunnel は「住所を隠して届ける配送ルート」。ルートが安全でも、受取人が誰の荷物でも受け取る設定なら困る。

K-26 公開 URL・API 認証・アップロード制限

設計理解 2026-06-12

きっかけ

「危ない URL とは何か?」「PC の IP が漏れないなら大丈夫では?」「API キーを付けたら傍受で危しくないか?」 「アプリで動画禁止にすれば十分では?」— 本番前に整理したい論点が多かった。

公開 URL とは何か

https://api.dammy-otoko.com/api/v1/feedbackサーバー PC が勝手に作った URL ではない。 DNS → Cloudflare → Tunnel → PC 内 API という意図して作ったインターネット入口

flowchart LR
  subgraph ok["正規の使い方"]
    App[本番アプリ] -->|HTTPS| URL[api.dammy-otoko.com]
  end
  subgraph ng["同じ入口を使う別ルート"]
    Curl[curl・いたずら] -->|HTTPS| URL
  end
  URL --> CF[Cloudflare] --> API[FastAPI]
          
  • 端末は Cloudflare エッジに届ける — その理解で正しい
  • 危険なのは「PC IP がバレる」ことより、公開入口に門番(認証)がないこと
  • 検証環境では意図的に無認証。本番では SEC-01 で API キーを導入予定

API 認証 — 何をするか

「このリクエストを送っていいのは誰か」をサーバー(層 7)で確認する。

# 本番案
POST /api/v1/feedback
Authorization: Bearer <秘密の API キー>
心配答え
キーを付けたら傍受で危しくない? HTTPS 区間ではキーは暗号化される(TLS の役割)。路上傍受の心配は小さい
じゃあ完璧? アプリに埋め込んだキーは逆コンパイルで取られる可能性あり。小規模運用では「無認証よりはるかにマシ」として許容しつつ認識する
DB 非公開だけでは? DB は外から直接叩けないが、API が通れば API 経由で DB に届く。だから API にも門番が必要(K-24

アップロード制限 — アプリだけでは足りない理由

対策の場所防げること防げないこと
アプリ(動画禁止等) 正規ユーザーの誤操作 curl で直接 API を叩く人
サーバー(サイズ上限・MIME) 上記 + 直接叩き + 巨大ファイル —(本番ではこちらも必須)

現状の API は image/* のチェックのみでサイズ上限なしfeedback.py)。本番では SEC-02 で上限を設ける。

結論

公開 URL は Cloudflare 経由の正面入口。HTTPS はその路上を守る。 入口の利用者確認(API キー)と量の制限(サイズ上限)は別レイヤーで足す。

Superengineer からの一言 「IP が分からないから安全」は誤解。Tunnel の URL 自体が入口。 アプリの仕様はプロダクト設計、API の制限はセキュリティ設計 — 両方書く。

K-27 シークレット管理 — .env・DB パスワード・PAT・ローテーション

設計理解 2026-06-12

きっかけ

「シークレット管理とは何の話か」「DB もユーザー/パスワードで守るのに、.env は認証なし?」 「秘密を DB に入れた方がよい?」「ローテーションは本当に必要?」— 用語の整理が必要だった。

「シークレット」とは

漏れたらなりすまし・不正操作につながる、パスワードのような文字列の総称。

シークレット誰が使う何のため置き場所(本プロジェクト)
POSTGRES_PASSWORDAPI プログラムDB に接続server/.env
TUNNEL_TOKENcloudflaredTunnel に接続server/.env
FEEDBACK_API_KEYクライアントアプリ・疎通スクリプトFB API を叩く(SEC-01)server/.env のみ + アプリ --dart-defineK-30
GitHub PATWorkerrepository_dispatchworker/.envK-22

.env と DB — なぜ秘密を DB に入れないのか

flowchart TD
  subgraph boot["API 起動時"]
    ENV["server/.env\n(DBパスワード等)"]
    API[FastAPI 起動]
    DB[(PostgreSQL)]
    ENV -->|"読み込み"| API
    API -->|"DBユーザー/パスワードで接続"| DB
  end
  subgraph ng["避けたいパターン"]
    DB2[(PostgreSQL)]
    SEC[secrets テーブルに APIキー等]
    API2[API 起動]
    API2 -->|"接続にパスワードが要る"| DB2
    DB2 --> SEC
  end
          
  • API が DB に繋ぐ時点で、すでに DB パスワードがどこかにある(鶏と卵)
  • 秘密を DB テーブルに入れると、DB 侵害 = 全部の秘密が一度に漏れる
  • 小規模の定石: .env(gitignore)+ PC へのアクセス制限。大規模は Vault / Secret Manager

.env は「認証なしで誰でも読める」ではなく、その PC にログインできる人だけが読めるファイルとして守る。

ローテーション(取り替え)が必要な理由

「必須の儀式」ではなく漏洩したときのダメージを減らす保険

漏れたら影響
PAT他人が GitHub API で dispatch 等を実行できる
TUNNEL_TOKENTunnel 経路の悪用の可能性
API キー不正 FB 投稿
DB パスワードデータ読み取り・改ざん

運用: PAT は四半期(K-23)、漏洩時は即 revoke / 再発行。

結論

運用: scripts/rotate-server-secrets.ps1 -ApiKey -RestartApi でランダム生成して server/.env に書き込み(値は画面に出さない)。

どのディレクトリに何を書くかの一覧: docs/index — 構成とシークレットREADME

Superengineer からの一言 シークレット管理は「金庫の場所と鍵の本数を決める」仕事。 DB はデータの金庫、.env は鍵束。鍵束を金庫の中にだけ入れると、金庫を開く鍵をどこに置くかで詰む。

K-28 Cloudflare Access・ログ・PII・実測確認(WBS 3.1.5)

基礎知識 2026-06-12

きっかけ

Cloudflare Access が Pages のメール認証と同じか?ログに個人情報が残るか? — 設計だけでは実感しづらいので、体験で確認する方針(WBS 3.1.5)も決めた。

Cloudflare Access と Pages のメール認証

概念は同じ(コンテンツの前に「誰か」を確認)。向かう先が違う

Pages のメール認証(開発者の docs 公開)Cloudflare Access
守るもの静的 HTML(docs)API・管理画面など
利用者人間(ブラウザで読む)人間(ブラウザ)
FB API(アプリ自動 POST)対象外不向き — API キーが適する(K-26

ログと個人情報(PII)

基本設計の「FB に個人情報を含めない」は方針。実態は実装次第なので実測で確認する。

保存先何が入るか(スタブ時点)確認方法
DB feedbacks画像パス、inference_result JSON、UUID、日時psql で SELECT
data/storage/画像ファイル実体フォルダを開く
Docker / Uvicorn ログアクセスログ(URL・時刻等)。body 全文は設定次第docker compose logs api

本番設計: inference_result に氏名・位置情報を入れない。ログにリクエスト body を出さない方針。

実測確認手順(WBS 3.1.5・学習目的)

理論(K-24〜K-27)を体験で補強する。記録は開発ログまたは本節に追記。

  1. 正規 FB 1 件.\scripts\test-feedback.ps1 -BaseUrl "https://api.dammy-otoko.com"
  2. DB 確認feedbacks の中身(PII の有無)
  3. Storage 確認data/storage/ のファイル
  4. API ログdocker compose logs api --tail 50
  5. 無認証 curl — 現状、URL だけで POST できることを体感(K-26 の裏付け)
  6. (任意)LAN から :8000http://<PC LAN IP>:8000/api/v1/health で裏口の理解(K-24)

詳細コマンド: セキュリティ設計 §9、WBS 3.1.5

実測記録(WBS 3.1.5 実施 — 2026-06-13)

手順結果学び
1. Tunnel 経由 FB test-feedback.ps1 -BaseUrl https://api.dammy-otoko.com → health OK、POST 201(id: df37d39d-... 公開 URL から正規スクリプトで届く
2. DB idinference_result(JSON)・image_pathcreated_at のみ。氏名・位置情報なし スタブの FB は PII を含まない
3. Storage コンテナ内 /app/storage/03260e3e-....jpg(339B)。ホスト data/storage/ は gitignore のためエクスプローラでは空に見える場合あり 実体は Docker ボリュームマウント先に保存
4. API ログ POST /api/v1/feedback HTTP/1.1" 201 等のみ。リクエスト body・画像内容は出ていない 現状ログはアクセスログ程度
5. 無認証 POST 手順 1 と同じ(curl.exe 使用)。API キーなしで 2012026-06-13 時点。SEC-01 有効後は 401 — シーケンス §3 K-26 の裏付け: HTTPS があっても「誰でも POST 可」だった
6. localhost :8000 http://localhost:8000/api/v1/health{"status":"ok"}(Tunnel バイパス) K-24 の裏付け: 裏口(ポート公開)の存在

次の設計タスク: 本番では server/.envFEEDBACK_API_KEY を設定(SEC-01 実装済み・2026-06-14)。MS-6 前チェックリスト参照。

セキュリティナレッジの読む順(推奨)

flowchart LR
  K24[K-24 OSI・FW・境界]
  K25[K-25 HTTPS・Tunnel]
  K26[K-26 公開URL・API認証]
  K27[K-27 シークレット]
  K28[K-28 Access・実測]
  K29[K-29 考える型]
  K24 --> K25 --> K26 --> K27 --> K28 --> K29
          

結論

セキュリティは前提知識(ネットワーク・層)なしでは読みにくい。 K-24 から順に、最後に WBS 3.1.5 で実測すると理解が定着する。 設計の正本は セキュリティ設計

Superengineer からの一言 設計書は地図、実測は現地踏査。curl で 1 回叩いてみると「公開入口」の意味が一気に腹落ちする。 怖がらず WBS 3.1.5 をやって、結果をメモに残しておいてほしい。

K-29 セキュリティを考える共通の型 — 詳細知識がなくても不足に気づく

基礎知識 2026-06-12

きっかけ

セキュリティやインフラを学ぶたびに新概念が出てきて不安になる。 「結局、機械を組んだ経験やネットワークの深い理解がないと無理では?」という疑問。

結論 — 特別な経験がなくても使える「共通の型」はある

暗記力や配線経験より、データの流れを追い、境界で三つ質問する習慣の方が効く。 専門用語はその質問に名前を付けたもの、と捉えるとよい。

型 1 — 三つの質問(いちばん使う)

新しい構成を見たら、毎回これだけ聞く:

質問意味本プロジェクトの例
誰が 送信者・操作者は誰か。正当性をどう証明するか 本番アプリ? curl? → API キーは?
どこから どのゾーン・経路か。裏口はないか Cloudflare 経由? LAN の :8000 直?
何に届く 最終的に触れる資産は何か API → DB / Storage / ディスク容量

「あれ、この境界で誰がは確認してないな」「どこから別ルートがありそう」 — こう思えれば、OSI を全部知らなくても不足に気づける

型 2 — 四つの資産(何を守るか)

flowchart LR
  subgraph assets["守るもの"]
    P[通路\n盗聴されないか]
    G[入口\n誰でも入れるか]
    D[中身\nDB・ファイル]
    K[鍵\nパスワード・トークン]
  end
          
資産守り方の例用語との対応
通路HTTPSTLS(K-25
入口FW・API 認証・Tunnelゾーン境界(K-24K-26
中身DB 非公開・バックアップデータ保護
.env・ローテーションシークレット(K-27

四つのどれかに「それ、どう守ってる?」の答えが弱いと感じたら、そこがギャップ。

型 3 — 境界ごとの門番チェック

ゾーンをまたぐ線を 1 本引くたびに、次の 3 種類の門番があるか見る:

  1. 暗号化 — 路上で読まれないか(HTTPS)
  2. 入口の選別 — IP/ポートで絞るか(FW)、利用者を確認するか(API キー)
  3. 中で何ができるか — 通ったあと、何を保存・実行できるか(サイズ上限、DB 権限)

本プロジェクトで「守られていないな」と分かったのも、この型の 2 番目: Cloudflare 経由の入口に利用者確認(API キー)がない、と気づいたから。

「物理経験・ネットワーク深理解」は必須か

あると楽なくても代替できること
機械を組んだ経験 「PC の中に API と DB がある」程度の箱図で十分な場面が多い
TCP/IP の細部 「住所(IP)と部屋番号(ポート)」の比喩で 80% は回る
長年の運用 流れを追う・一度手を動かすK-28 実測)で体感が補える

不安の正体は「用語が増えること」より、全体の地図が頭にないことが多い。 地図は データフロー や K-24 のゾーン図で足りる。細部は必要になったときに足せばよい。

学習が不安なときの進め方

  1. まず データフロー 1 本 だけ追う(FB がどこを通るか)
  2. その線の上に 三つの質問 を置く
  3. 弱い答えを 1 つだけ WBS タスクにする(一度に全部理解しようとしない)
  4. 実測 1 回(curl、logs、DB)で理論を腹落ちさせる
Superengineer からの一言 専門家も毎回「誰が・どこから・何に届く」を口にする。用語は 用語集 で引ける。 不安なら K-28 の実測を 1 回 — 地図を歩いた感覚が残る。

K-30 リポジトリ構成と .env 配置 — FEEDBACK_API_KEY 誤設定と 401

操作ミス 2026-06-14 — 解決済み

きっかけ

WBS 3.1.4(SEC-01)実装後、test-feedback.ps1 で Tunnel 経由の疎通を確認したところ、 health は {"status":"ok"} だが POST が {"detail":"invalid api key"}(HTTP 401)。 警告「FEEDBACK_API_KEY looks like a placeholder」も表示された。

調査の結果、FEEDBACK_API_KEYworker/.env に誤って追加していた(のち削除)。 「シークレットを足した」という感覚は正しいが、置き場所が違った

正しい配置(本プロジェクト)

ファイル書くもの読む側
server/.envFEEDBACK_API_KEYTUNNEL_TOKEN、DB 接続などDocker Compose(api コンテナ)、test-feedback.ps1
worker/.envGITHUB_TOKENGITHUB_REPOSITORY のみ学習スタブ・repository_dispatchK-22

一覧表: docs/index — 構成とシークレット

test-feedback.ps1 がキーを読む順序

  1. 引数 -ApiKey(明示指定時)
  2. PowerShell の $env:FEEDBACK_API_KEYセッションに残っていると最優先
  3. server/.envGet-DotEnvValue.ps1

そのため、worker/.env に書いてもスクリプトは読まない。 逆に、過去にシェルへ export したプレースホルダが残っていると、server/.env を直しても 401 が続く。

解消手順(再現用)

# リポジトリ直下
Remove-Item Env:FEEDBACK_API_KEY -ErrorAction SilentlyContinue
.\scripts\rotate-server-secrets.ps1 -ApiKey -RestartApi
.\scripts\test-feedback.ps1 -BaseUrl "https://api.dammy-otoko.com"

GitHub Actions Secret も同様 — 値は server/.env= 以降のみFEEDBACK_API_KEY=... 行全体を Secret 欄に貼ると length がずれ、GHA ビルド APK からの実機 FB が 401 になる(runbook §4b-1)。

-RestartApiserver/.env の変更を API コンテナへ反映するために必須。 .env だけ直してコンテナを再起動しないと、ファイルとコンテナ内のキーがずれたままになる。

結果(2026-06-14)

  • POST https://api.dammy-otoko.com/api/v1/feedbackHTTP 201
  • レスポンス例: id 58b80bce-22c1-4de1-a010-2961f5b9e4d3created_at 付き
  • プレースホルダ警告なし

SEC-01 deny 確認 — 意図的に誤ったキーを流す

正しいキーで 201 が出たあと、認証が本当に効いているかを確認する。 K-28 で「無認証 POST が通る」ことを体感したのと対になる実測(SEC-01 有効時)。

テスト送ったもの結果(2026-06-14 実測)
誤った API キー -ApiKey "intentionally-wrong-key-for-deny-test" HTTP 401{"detail":"invalid api key"}
認証ヘッダなし Authorization なしで curl POST HTTP 401{"detail":"missing or invalid authorization header"}
正しいキー server/.env のキー(-ApiKey 省略) HTTP 201
# 誤ったキー → 401 invalid api key
.\scripts\test-feedback.ps1 -BaseUrl "https://api.dammy-otoko.com" -ApiKey "wrong-key-on-purpose"

# 認証なし → 401 missing or invalid authorization header
curl.exe -s -w "`nHTTP_CODE:%{http_code}`n" `
  -X POST "https://api.dammy-otoko.com/api/v1/feedback" `
  -F "image=@scripts\sample.jpg;type=image/jpeg" `
  -F "inference_result=<scripts\inference.json;type=application/json"

health はどのケースでも {"status":"ok"}。 POST だけが認証で弾かれる — app/deps/auth.py の SEC-01 どおり。 手順の写経: runbook §5.1

IDE 通知(python.terminal.useEnvFile)について

Cursor / VS Code から「環境ファイルはあるがターミナル注入が無効」と出ることがある。 これは Python 拡張向けの任意機能で、本プロジェクトの PowerShell 運用には不要。 有効にすると $env:FEEDBACK_API_KEY が意図せずシェルに入り、今回のような混乱の原因になりうるため、オフのままでよい

Superengineer からの一言 「.env が複数ある」=「金庫が複数ある」ではない。どの金庫の鍵かが名前で決まっている。 worker/.env は GitHub 用、server/.env は API 用 — 混ぜない。 health が通って POST だけ 401 なら、インフラは生きている。認証の鍵の不一致を疑え。

K-31 全体構成図(セキュリティ拡張版)— 元図解から Phase1 実装への対応

まとめ 2026-06-14

きっかけ

会議資料の全体構成図(サーバー・Tunnel・CI/CD・クライアント)だけでは Phase1 の実装理解に十分だったが、 SEC-01 以降「鍵はどこに置くか」「HTTPS と API キーの違い」「GHA Secret と PAT の違い」が増え、 1 枚の図だけでは追いにくくなった。

元の全体構成図(会議資料)

会議資料 — サーバー・Tunnel・CI/CD・iPhone クライアントの全体構成
図 K-31a: 元図解(iPhone / CoreML / TestFlight 想定)。Phase1 Android 検証はクライアント・配布部分が Flutter APK + adb に読み替わる。

Phase1 Android 実装への読み替え

元図解の要素Phase1(① Android)の実装② iPhone(後)
クライアントFlutter スタブ(Pixel 7a)Swift / SwiftUI
オンデバイスモデルTFLite(APK 同梱)CoreML
FB 送信HTTPS → Cloudflare → Tunnel → FastAPI同左
CI/CDGitHub Actions → APK artifactGHA + Fastlane → TestFlight
配布artifact ダウンロード → adb installApp Store Connect
学習完了通知repository_dispatch(Worker + PAT)同様の Webhook 想定

セキュリティ拡張版 — 全体(データの流れ + 鍵の流れ)

実線=データ・処理の流れ、点線=秘密情報(ビルド時・設定時のみ。実行中の HTTP 本体には載せない)。

flowchart TB
  subgraph Client["クライアント(Pixel / Flutter)"]
    APP["馬体診断スタブ APK"]
    KEY_IN["鍵: ビルド時 --dart-define で APK 内定数"]
    APP --- KEY_IN
  end

  subgraph Internet["インターネット"]
    CF["Cloudflare Edge\nHTTPS 終端"]
    GHA["GitHub Actions\nandroid-build.yml"]
    GH_SEC["Actions Secret\nFEEDBACK_API_KEY"]
    GH_SEC -.->|"ビルド時 inject"| GHA
    GHA -->|"artifact APK"| PC_DL["PC ダウンロード"]
  end

  subgraph Home["自宅 PC — Docker"]
    TUN["cloudflared"]
    API["FastAPI\nSEC-01 ミドルウェア"]
    ENV["server/.env\nFEEDBACK_API_KEY"]
    DB[(PostgreSQL)]
    STG[(Storage)]
    WK["Worker スタブ"]
    ENV -.->|"compose env"| API
  end

  subgraph LocalDev["開発者 PC(ビルド・運用)"]
    SENV["server/.env"]
    WENV["worker/.env\nGITHUB_TOKEN PAT"]
    PS1["build-flutter-apk.ps1\ntest-feedback.ps1"]
    SENV -.-> PS1
    WENV -.-> WK
    PS1 -->|"ローカル release APK"| APP
  end

  APP -->|"① FB POST\nAuthorization: Bearer 鍵"| CF
  CF -->|"Tunnel 中継"| TUN
  TUN --> API
  API -->|"鍵一致?"| STG
  API --> DB

  WK -->|"② repository_dispatch\nPAT で認証"| GHA
  GHA --> PC_DL
  PC_DL -->|"adb install"| APP

  style KEY_IN fill:#fef3c7
  style ENV fill:#fef3c7
  style GH_SEC fill:#fef3c7
  style WENV fill:#e0e7ff
          

秘密情報 — 3 種類を混同しない

名前置き場所誰が使うか何のためか
FEEDBACK_API_KEY server/.env + APK(--dart-define)+ GHA Secret サーバー検証 / アプリ FB 送信 / クラウドビルド FB API の正規クライアントか(SEC-01)。鍵不一致 → 401
GITHUB_TOKEN(PAT) worker/.env のみ 開発者 PC の Worker GitHub にビルドを依頼できるか(dispatch 204)。FB とは無関係
TUNNEL_TOKEN server/.env cloudflared コンテナ Tunnel 接続の正当性。公開 URL 到達には必須

詳細: K-22(PAT)、K-27(シークレット全般)、K-30(FEEDBACK_API_KEY 配置)。

SEC-01 が効く場所 / 効かない場所

通信SEC-01(API キー)備考
GET /api/v1/health不要死活監視用
POST /api/v1/feedback必須(キー設定時)Authorization: Bearer
HTTPS(TLS)別レイヤーCloudflare Edge で暗号化(K-25)。鍵の代わりではない
GitHub repository_dispatch対象外PAT で GitHub API を認証(別系統)

FB 送信 1 本の流れ(SEC-01 有効時)

時系列の詳細・SEC 前との対比: シーケンス図 §2(旧: §1)。

sequenceDiagram
  participant Pixel as Pixel アプリ
  participant CF as Cloudflare Edge
  participant API as FastAPI
  participant STG as Storage

  Note over Pixel: ビルド済み APK 内に
FEEDBACK_API_KEY 定数 Pixel->>CF: POST /feedback
Authorization Bearer 鍵
画像 multipart CF->>API: Tunnel 経由(TLS 済み) API->>API: SEC-01: 鍵一致? alt 鍵 OK API->>STG: 画像保存 API-->>Pixel: 201 Created else 鍵 NG / なし API-->>Pixel: 401 end

APK に鍵が入るまで(ビルド経路)

flowchart LR
  subgraph A["2-A ローカル"]
    SE["server/.env"]
    BF["build-flutter-apk.ps1"]
    DD1["--dart-define"]
    APK1["app-release.apk"]
    SE --> BF --> DD1 --> APK1
  end
  subgraph B["2-B GHA"]
    GS["Actions Secret\nFEEDBACK_API_KEY"]
    YML["android-build.yml"]
    DD2["--dart-define"]
    APK2["artifact APK"]
    GS --> YML --> DD2 --> APK2
  end
  APK1 --> Pixel["adb install"]
  APK2 --> Pixel
          

ソースコード(config.dart)にはロジックだけ書き、鍵のはビルド引数で渡す。 これが「コードに書く」のではなく --dart-define になる理由(runbook §4b)。

「実機 FB 201 確認」とは — どこに書いてあるか

意味: PC スクリプトではなく実機アプリから FB を送り、画面に 送信成功 (HTTP 201) が出ること。 SEC-01 導入後は、APK に鍵が同梱されているかの最終確認になる。

資料場所
手順(主)Phase1 GHA・APK 手順 §4c
鍵の渡し方同 §4b
サーバー側 201 / 401K-30サーバー runbook §5.1
次アクション一覧docs/index.html「次のアクション」
Phase1 通し(SEC 前)開発ログ 1.5.2(2026-06-11、v2・FB 201 済み — 当時は SEC-01 前)
2026-06-14 更新: SEC-01 有効後の実機 FB 201 は runbook §4c確認済み(2-A ローカル APK)。 2026-06-11 の v2 実機 FB は SEC-01 導入前の通し確認。

読む順序(セキュリティで迷ったとき)

  1. 本節 K-31 — 全体像と鍵の種類
  2. シーケンス図 §0 — SEC 前後の時系列と抜け道
  3. K-25 — HTTPS / Tunnel が守るもの
  4. K-26 — API 認証の考え方
  5. K-30 — FEEDBACK_API_KEY の置き場所と 401
  6. セキュリティ設計 — SEC-01〜08 の正式定義
Superengineer からの一言 元図解は「荷物の流れ」、セキュリティ追加後は「荷物 + 通行手形の流れ」。 手形は3種類 — FB 用・GitHub 用・Tunnel 用。混ぜると 401 も dispatch 失敗も「どこも繋がらない」ように見える。 まず K-31 の表でどの鍵かを特定してから runbook を開く。

関連資料

資料用途
Phase1 サーバー手順手順の再現
構成とシークレットリポジトリ地図・.env 配置(README 同期)
開発ログ日付付き実施記録
データフローFB 受信の論理構造
Phase1 GHA・APK 手順APK ビルド・artifact・adb・SEC-01 実機確認(§4c)
セキュリティ設計SEC-01〜08・脅威モデル
Phase1 Worker 手順学習スタブ・repository_dispatch
① 完了レビューMS-3 達成判定・チェックリスト証跡
用語集セキュリティ用語(OSI・ゾーン・FW 等)v1.5
セキュリティ設計SEC 対策・チェックリスト・実測手順
用語集YAML / APK / artifact / PAT 等