ナレッジベース(Phase1)
この文書について(標準 FAQ / 運用ナレッジの構成)
| 目的 | 手順書だけでは伝わらない背景・判断理由・エラー対処を残す |
|---|---|
| 想定読者 | 運用担当、開発者、後から参加するメンバー |
| いつ読むか | エラー発生時、設計の「なぜ?」、セキュリティの前提理解時 |
WBS 1.1(サーバー環境構築・Tunnel 移行)を進める過程で発生した疑問・エラー・判断を、 後から読んでも再現できるよう丁寧に残した資料です。 2026-06-12 追記: 本番準備(WBS 3)のセキュリティ議論で生じた前提知識(OSI・HTTPS・FW・シークレット等)を K-24〜K-30 に整理しました。詳細設計は セキュリティ設計 を参照。
開発ログ は「いつ・何を実行したか」の時系列。 本ページは「なぜそうなったか」「どう調べて・何を直して解消したか」を中心に記述します。 各項目の末尾に、インフラ実務の視点からのSuperengineer からの一言を添えています。
この資料の読み方
| 見出し | 意味 |
|---|---|
| きっかけ | 質問が生まれた背景、またはエラーに気づいた状況 |
| 経緯 | 試したこと・判明したこと・次に何をしたか(解決までの流れ) |
| 結論 | 最終的な理解・正しい操作・再発防止 |
| Superengineer からの一言 | 現場のインフラ担当が伝えそうな助言(第3者視点) |
目次
| ID | テーマ | 種別 |
|---|---|---|
| K-01 | PoC から Windows PC への Tunnel 移行 | 設計理解 |
| K-02 | server/ と .env の役割 | 設計理解 |
| K-03 | TUNNEL_TOKEN 誤設定(Tunnel ID と Connector Token の混同) | エラー解消 |
| K-04 | Pi 側 cloudflared を止める理由 | 設計理解 |
| K-05 | Cloudflare 画面の cloudflared.exe 指示 | 設計理解 |
| K-06 | Docker Desktop 未起動(pipe エラー) | エラー解消 |
| K-07 | docker compose コマンドの意味 | 設計理解 |
| K-08 | Pi 上 Docker 停止(sudo・サービス名) | エラー解消 |
| K-09 | PowerShell の curl 問題 | エラー解消 |
| K-10 | PostgreSQL を compose に含める理由 | 設計理解 |
| K-11 | 疎通確認 — localhost と公開 URL | 設計理解 |
| K-12 | MS-1 達成までの全体の流れ | まとめ |
| K-13 | エラー早見表 | 参照 |
| K-14 | Git — 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-18 | Flutter 開発の基礎 — ライフサイクルと実機検証の手順 | 基礎知識 |
| K-19 | YAML とは — 書式とツールごとの意味の違い | 基礎知識 |
| K-20 | GitHub Actions と android-build.yml — ビルド・artifact・APK | 設計理解 |
| K-21 | 月次モデル更新パイプライン — 学習から配布まで | まとめ |
| K-22 | PAT と Actions Secrets — Worker dispatch の認証 | 設計理解 |
| K-23 | Phase1 運用 — PAT ローテーション・artifact 保持・APK 世代管理 | 運用 |
セキュリティ・本番準備(WBS 3 関連)
| ID | テーマ | 種別 |
|---|---|---|
| K-24 | OSI 参照モデルとネットワークの前提 — ポート・FW・境界 | 基礎知識 |
| K-25 | HTTPS と Cloudflare Tunnel — 何を守り、何を守らないか | 基礎知識 |
| K-26 | 公開 URL・API 認証・アップロード制限 | 設計理解 |
| K-27 | シークレット管理 — .env・DB パスワード・PAT・ローテーション | 設計理解 |
| K-28 | Cloudflare 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 を新規に立てればよいのでは?」という疑問が生じた。
経緯
poc-api-tunnel・DNS・api.dammy-otoko.com が稼働していることを確認。
新規 Tunnel 作成は DNS 設定の重複・手戻りリスクがあるため不採用と決定。
結論
変更するのは 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
K-02 server/ ディレクトリと .env
きっかけ
リポジトリ内に server/ があり、runbook では server/.env に
TUNNEL_TOKEN を書くとある。
「server という名前のディレクトリは単にバックエンドをまとめただけ?」
「.env はなぜ Git に載せず、.env.example だけ載せるの?」という整理の質問。
経緯
server/ 配下に実装。クライアント(将来の Flutter スタブ)と役割を分ける命名。
.env.example(テンプレ)を Git 管理し、実値の .env は .gitignore で除外。
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 後の最初の一歩
K-03 TUNNEL_TOKEN 誤設定 — Tunnel ID と Connector Token の混同
きっかけ
Pi 側 cloudflared 停止後、PC で docker compose --profile tunnel up -d cloudflared を実行。
docker compose ps で cloudflared が Restarting (255) を繰り返す。
外部 URL は error code: 1033。localhost の API は正常。
経緯
server/.env の TUNNEL_TOKEN に
UUID 形式(例: 716ae640-c39a-....)を設定していた。
Cloudflare ダッシュボードで Tunnel を選ぶと ID が目立つため、これがトークンだと誤解しやすい。
docker compose logs cloudflared を確認すると、
繰り返し Provided Tunnel token is not valid. が出力。
コンテナは起動直後に終了コード 255 で再起動ループに入る。
poc-api-tunnel → Configure を開く。
Windows タブに次のような1行コマンドが表示される。
cloudflared.exe service install eyJhIioi...(ここから末尾までがトークン)
cloudflared.exe service install の直後に続く長い文字列全体が
Connector Token(コネクタ認証用トークン)である。これを server/.env の TUNNEL_TOKEN= に
1行で貼り付ける(引用符なし・改行なし)。
docker compose --profile tunnel up -d cloudflared を再実行。
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 ID | UUID 形式(716ae640-c39a-4fd0-.... のような短い識別子) | ダッシュボード上で Tunnel を区別するための ID。TUNNEL_TOKEN には使えない |
| Connector Token(コネクタトークン) | cloudflared.exe service install の後に続く長い1行の文字列(多くは eyJ で始まる) | server/.env の TUNNEL_TOKEN= に設定する値 |
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 でも繋いだらダメなのか?」
経緯
poc-api-tunnel に接続していた(2026-06-07 時点で /ping 応答確認済み)。
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 には無関係)。
/ping か、新 API の /api/v1/health か)で向き先を必ず確認する。
K-05 Cloudflare 画面の「cloudflared.exe をインストール」
きっかけ
PC 側 cloudflared が token エラーで落ちている間、Cloudflare ダッシュボードに
「Install cloudflared connector」および Windows 向け cloudflared.exe service install が表示された。
「これを実行しないと Tunnel が有効化されないのでは?」という不安。
経緯
cloudflared.exe service install の後に続く長いトークン文字列 を
server/.env の TUNNEL_TOKEN= に設定すること。
Docker タブを選んでも同じトークンが表示される。
結論
ダッシュボードの .exe 手順 = コネクタ未接続時のガイド。
本プロジェクトでは docker compose --profile tunnel up -d cloudflared が .exe インストールに相当する。
docker compose --profile tunnel up)で渡せているか」だけでよい。
K-06 Docker Desktop 未起動 — pipe エラー
きっかけ
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 は使えない。
経緯
%LOCALAPPDATA%\Programs\DockerDesktop\ 配下に置かれる。
インストール直後は docker コマンド(CLI)だけが使える状態になり、
Docker Desktop アプリ本体(Engine)がまだ起動していないことがある。
docker version を再実行。
出力に Server: 行(例: Docker Desktop 4.76.0)が表示されれば、
Engine(コンテナ実行エンジン)の起動完了。
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 エラー]
docker コマンドが打てることと、Docker でコンテナが動くことは別問題。
作業開始時は docker version を実行し、Server: の行があるかを習慣的に確認する。
PC 再起動後は、いつもより先に Docker Desktop アプリを起動する。経験者でもここを忘れがち。
K-07 docker compose コマンドの読み方
きっかけ
MS-1 確認用に4つのコマンドを実行したが、「何をしているか分からない」状態だった。 runbook の手順を理解するための整理。
経緯
2026-06-07、次の順で実行し MS-1 を達成した。
docker compose up -d --build db api— DB・API 起動(Tunnel なし)curl.exe http://localhost:8000/api/v1/health— ローカル疎通docker compose --profile tunnel up -d cloudflared— Tunnel 接続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 api | db と api サービスだけ起動(cloudflared はこの段階では起動しない) |
--profile tunnel | docker-compose.yml で profiles: [tunnel] が付いた cloudflared だけを追加で有効化する |
db・api・cloudflared を一括起動せず、
「DB+API 起動 → localhost 疎通 → cloudflared 起動 → 公開 URL 疎通」の順に進めたのは良い判断。
障害が出たとき「どの段階まで成功しているか」が分かりやすく、切り分けに時間がかからない。
K-08 Raspberry Pi 上 Docker 停止 — sudo とサービス名
きっかけ
Pi に SSH 接続し docker compose ps を実行。
permission denied while trying to connect to the docker API at unix:///var/run/docker.sock。
sudo 付きで再実行すると cloudflared が Up 5 weeks と確認できた。
停止後、PoC API(コンテナ名 poc-api)も止めたいが
docker compose stop poc-api → no such service。
経緯
damio01 は docker グループに所属しておらず、
Docker デーモン(/var/run/docker.sock)へのアクセス権がない。
そのため sudo docker compose ...(管理者権限で実行)が必要だった。
sudo docker compose ps の出力には NAME 列と SERVICE 列がある。
docker compose stop に指定するのは SERVICE 列 の名前
(cloudflared / api)。
NAME 列の poc-api はコンテナ名であり、stop の引数には使えない。
sudo docker compose stop cloudflared で Tunnel コネクタを停止 — 成功。
Tunnel 移行に必須。
sudo docker compose stop api(サービス名)。
コンテナ名 poc-api ではない。
結論
flowchart TB
PS[docker compose ps]
PS --> NAME[NAME = コンテナ名]
PS --> SVC[SERVICE = stop で指定する名前]
docker compose ps を見るときは SERVICE 列を確認する癖をつける。
permission denied が出たら、まず sudo を付けて再実行してよい。
恒久的には Pi のユーザーを docker グループに追加する方法もあるが、
今回の移行作業では sudo docker compose stop cloudflared だけで Tunnel 切り替えは足りる。
PoC の api まで止めるのは必須ではなく、旧 API と混同しないための任意作業。
K-09 PowerShell の curl — 別名問題
きっかけ
docker compose up 成功直後、
curl http://localhost:8000/api/v1/health が
「接続が予期せずに閉じられました」。
コンテナは Started なのに疎通失敗 — API が壊れたのかと誤解しやすい。
経緯
curl と打つと、実際には
Invoke-WebRequest(PowerShell 組み込みの HTTP コマンド)の別名が動く。
Linux や runbook で想定している curl とは挙動が異なる。
curl.exe http://localhost:8000/api/v1/health に変更 →
{"status":"ok"}。Invoke-RestMethod でも同様に成功。
test-feedback.ps1 の JSON 送信は
Windows curl のインライン JSON 問題(BOM・エスケープ)を避けるため、
事前に inference.json ファイルを生成する方式に修正済み(開発ログ参照)。
結論
Windows の runbook 疎通確認は curl.exe を明示する。runbook v1.3 で反映済み。
curl.exe と拡張子まで付けて実行する。
コンテナ起動直後の一瞬だけ失敗することもあるが、今回の主因は PowerShell の別名問題だった。
失敗したら API のログ(docker compose logs api)と curl.exe の両方で切り分ける。
K-10 なぜ PostgreSQL があるか
きっかけ
FB 受信で画像は data/storage/ に保存される。
それならファイルだけで足りないか — DB の役割の質問。
経緯
結論
flowchart LR
POST[POST /feedback] --> IMG[画像 storage/]
POST --> DB[(feedbacks)]
feedbacks テーブルは「いつ・どのファイルか」の索引とメタデータ。
最初は SQLite でも動くが、本番に近い Docker Compose の練習として PostgreSQL を入れた判断は妥当。
K-11 疎通確認 — localhost と公開 URL
きっかけ
test-feedback.ps1 に -BaseUrl がある。
引数なしと https://api.dammy-otoko.com 指定の違いが MS-1 確認で重要。
経緯
localhost:8000)で API 実装の正しさを確認 — 完了。
結論
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
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 別名)は、
個別に切り分け可能な問題だった。
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 未インストール / PATH | Desktop インストール・起動 |
dockerDesktopLinuxEngine | Docker Desktop 本体(Engine)が起動していない | Docker Desktop アプリを起動し、docker version で Server 行を確認してからターミナルを開き直す |
token is not valid | Tunnel ID(UUID)を TUNNEL_TOKEN に設定 | Configure 画面の cloudflared.exe service install 以降の文字列をコピー |
error code: 1033 | cloudflared が Tunnel に接続できていない(token 誤り、または Docker Desktop 未起動) | docker version → Desktop 起動 → docker compose ps → docker compose logs cloudflared |
image must be an image file(HTTP 400) | multipart の画像に Content-Type: image/* が付いていない(Flutter Android 典型) | XFile.mimeType を MultipartFile に渡す(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_stub → install-apk.ps1 再実行。v1・v2 両方で確認済み(2026-06-10) |
invalid api key(HTTP 401) | FEEDBACK_API_KEY の置き場所・シェル環境変数・コンテナ未再起動 | K-30 — server/.env のみ、Remove-Item Env:FEEDBACK_API_KEY、-RestartApi |
| PowerShell curl 失敗 | Invoke-WebRequest 別名 | curl.exe |
| Pi permission denied | docker.sock 権限 | sudo docker compose |
no such service: poc-api | サービス名とコンテナ名の混同 | stop api / stop cloudflared |
curl.exe .../health を叩く癖をつけると早い。
400 の「image must be…」は curl スクリプトとアプリの multipart 差分 — PC で通って実機で落ちる典型パターン。
K-14 Git — add / commit / push の順序
きっかけ
MS-1 達成後、README・開発ログ・Phase1 ナレッジなどの変更を GitHub へ反映しようとした。
git add . のあと、
git push -m "Cloudflaredをraspberry pi →Windowsへ置き換え" を実行したところ、
error: unknown switch 'm' となった。
「add の次は push ではなかったか?」という疑問。
経緯
git status で変更ファイルを確認。
修正済み HTML・README と、新規の docs/records.html#dev-log・docs/knowledge.html などが
「Changes not staged」または「Untracked」として表示された。
git add . を実行。
変更が「Changes to be committed」(ステージ済み)に移動。
この時点ではまだ GitHub には何も送られていない。ローカル PC 上の「コミット予定箱」に載っただけ。
git push -m "..." を実行 → 失敗。
push コマンドには -m(メッセージ)オプションがない。
-m は git commit 用。
git commit -m "メッセージ"(ローカルに記録)→
git push(GitHub へ送信)。
git status に
Your 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.json は test-feedback.ps1 実行時に毎回生成される一時ファイルのため、
必要なければ add しない(または今後 .gitignore に追加する)。
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 アプリ が続く。
経緯
test-feedback.ps1 から
https://api.dammy-otoko.com へ 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.1 | Flutter プロジェクト作成 | app/ 最小構成 |
| 1.2.2 | FB 送信実装 | 画像 + 推論 JSON を POST /api/v1/feedback |
| 1.2.3 | 端末検証 | 実機/エミュレータから api.dammy-otoko.com へ送信成功 |
前提(サーバー側): Windows PC で Docker Desktop 起動 → db・api・cloudflared が稼働していること。
参照: WBS・ガントチャート、機能・関数定義一覧(F-01〜F-03)、データフロー
K-16 実機検証 — USB デバッグ・ログの見方
きっかけ
Pixel 7a で flutter run 後、「フィードバックを送信」が失敗。
USB ログ(adb logcat 等)で原因を追えるか確認したい。
経緯
Device ... is not authorized。
Pixel 上の「USB デバッグを許可しますか?」を承認して解消。
flutter run -d 3C261JEHN19182 で APK ビルド・インストール成功。
NDK 26/27 警告は出るがビルドは通過。
送信失敗: Exception: HTTP ... が表示される。
現状のスタブは debugPrint 未実装のため、flutter run ターミナルに必ずしも HTTP 詳細は出ない。
adb logcat -s flutter を試したが、PowerShell で adb が PATH 未設定で失敗。
SDK 本体は %LOCALAPPDATA%\Android\Sdk\platform-tools\adb.exe に存在。
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 .../health | Tunnel / サーバー稼働の切り分け(実機より先に確認) |
PC: docker compose ps / logs | 1033・530 系の原因特定 |
Flutter を切る必要はない。 Docker 操作は別ターミナルで行い、修正後は Hot restart(R)で再試行可。
K-17 実機 FB 送信 — HTTP 400「image must be an image file」
きっかけ
Docker Desktop 復旧後、Pixel 7a から FB 送信。
ダミー推論(label / score)は画面に表示されるが、送信は
HTTP 400: {"detail":"image must be an image file"} で失敗。
経緯
feedback.py は
image.content_type.startswith("image/") を検証。空または application/octet-stream だと 400。
test-feedback.ps1 は
-F "image=@...;type=image/jpeg" で Content-Type を明示しており MS-1 成功。
feedback_client.dart は
MultipartFile.fromPath('image', imagePath) のみ。
Android ではギャラリー画像が拡張子なしキャッシュパスになり、MIME 推定が失敗しやすい。
結論 — 修正と結果
image_picker の XFile.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 完了。
K-18 Flutter 開発の基礎 — ライフサイクルと実機検証の手順
きっかけ
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 server → docker compose up -d db api → docker compose --profile tunnel up -d cloudflared |
curl.exe https://api.dammy-otoko.com/api/v1/health → {"status":"ok"} |
| 3 | スマホ | USB デバッグ ON、ケーブル接続、「許可」タップ | 初回は開発者オプションの有効化が必要 |
| 4 | PC | cd app → flutter 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 |
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 URL | https://api.dammy-otoko.com(app/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 後に画像を選び直す |
関連ナレッジ
flutter devices(見えるか)、flutter run(入れるか)、
R(直したコードを確実に反映)、q(終わる)。
インフラ検証プロジェクトでは「アプリが動く」より「正しい URL に正しい形式で届く」が本体。
届かないときはスマホより先に PC の curl と docker compose ps を見る癖をつけておくと楽。
K-19 YAML とは — 書式とツールごとの意味の違い
きっかけ
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.yml | Docker Compose | services, image, ports |
.github/workflows/android-build.yml | GitHub Actions | on, jobs, runs-on, steps |
比喩: YAML は「用紙のフォーマット」、各 YAML ファイルは「ツール専用の申請書テンプレート」。 CI/CD で YAML がよく使われるのは、ビルド手順をリポジトリに置き、人間が読んで修正しやすいから。
K-20 GitHub Actions と android-build.yml — ビルド・artifact・APK
きっかけ
WBS 1.4 の検証手順で「YAML をリモートに載せる」「Actions で手動ビルド」「artifact から APK をダウンロード」が出てくる。 それぞれが実体として何を意味するかを整理したい。
android-build.yml の役割
GitHub Actions 向けの作業指示書。次の3つを1ファイルに宣言する。
| 定義する内容 | 本プロジェクトでの例 |
|---|---|
| いつ動くか | workflow_dispatch(手動)、repository_dispatch(Worker から) |
| 何をビルドに含めるか | どの .tflite を app/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 されない |
端末へ反映する流れ:
- Actions 実行結果 → Artifacts → ZIP を PC にダウンロード
- ZIP を解凍 →
app-release.apkを取り出す 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
K-21 月次モデル更新パイプライン — 学習から配布まで
きっかけ
基本設計では月次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_dispatch(model-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/.env の GITHUB_TOKEN(PAT)と GITHUB_REPOSITORY。
配布 — Android と iPhone の違い
| 検証段階 | 配布方式 | 自動度(現状 / 目標) |
|---|---|---|
| ① Android(今) | APK + adb | artifact 取得 → 手動 install(Phase1) |
| ② iPhone(後) | TestFlight | GHA + Fastlane で upload を検証予定(design.html#detailed) |
android-build.yml だけでは TestFlight までは行かない。
参照: データフロー(通信方向 ②③)、 Phase1 Worker 手順、 Phase1 GHA・APK 手順
K-22 PAT と Actions Secrets — Worker dispatch の認証
きっかけ
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/.env の GITHUB_TOKEN |
PAT 発行手順(Classic)
- GitHub 右上アイコン → Settings(ユーザー)
- Developer settings → Personal access tokens → Generate new token (classic)
- Scope:
repo ghp_...をworker/.envに記載(gitignore、commit 禁止)
run-training-stub.ps1 は POST /repos/{owner}/{repo}/dispatches を送る(worker/github_dispatch.py)。
成功時は HTTP 204(body なし)。
参照: Phase1 Worker 手順 §3、 GHA 操作手順 §B
K-23 Phase1 運用 — PAT ローテーション・artifact 保持・APK 世代管理
きっかけ
方法 A/B の E2E が進み、月次運用を想定したときの「期限切れ」「成果物の消滅」「端末に残るバージョン」の整理が必要になった。
1. PAT — 生成・期限・ローテーション
| 観点 | 内容 |
|---|---|
| 必要な場面 | 方法 B(Worker dispatch)のみ。方法 A(Run workflow)は不要 |
| 期限切れ時 | dispatch 403 等 → GHA が自動起動しない |
| 運用 | 有効期限の 1〜2 週間前に新 PAT 発行 → worker/.env 差し替え。カレンダーリマインダー推奨 |
2. artifact — GitHub 上の保持期限
android-build.yml の upload-artifact は retention-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.apk、v2) - 直近 2〜3 世代を保持 — ロールバック・比較用
- 大容量ファイルは gitignore 推奨(リポジトリには載せない)
- v1 → v2 など世代更新時、
adb install -rがINSTALL_FAILED_UPDATE_INCOMPATIBLEになる場合は先にadb uninstall com.horsediagnosis.horse_stub(K-13)
4. 配布経路の整理
| 段階 | 配布 | 備考 |
|---|---|---|
| ① Android(Phase1) | artifact → adb install | 手動ダウンロードが必須 |
| ② iPhone(後) | TestFlight | GHA + Fastlane 検証予定。Android には使わない |
参照: GHA 操作手順 §F、 K-21(月次パイプライン)
K-24 OSI 参照モデルとネットワークの前提 — ポート・FW・境界
きっかけ
本番セキュリティの話(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 ポート 443、8000 |
| 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 の入口 | 中(契約・設定次第) |
| 役場 PC | API・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 HTTPS | Cloudflare で有効 | 維持(K-25) |
| L4 ポート 8000 | docker-compose で PC に公開 | localhost のみ + FW(SEC-03) |
| DB 5432 | Docker 内部のみ | 維持 + 強パスワード |
結論
セキュリティは一箇所の鍵だけでは足りない(多層防御)。 FW は「裏口(ポート)を閉じる」、API 認証は「正規の利用者だけ通す」、HTTPS は「途中で読まれにくくする」—— 層が違うので全部そろえて初めて意味が通る。
K-25 HTTPS と Cloudflare Tunnel — 何を守り、何を守らないか
きっかけ
「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://api.dammy-otoko.com/api/v1/feedbackの URL を知った人は、curl でも同じ HTTPS で POST できる- 暗号化されているので路上では内容が読まれにくいが、サーバーはリクエストを受け付けてしまう
- だから 層 7 の API 認証(K-26)が別途必要
結論
Tunnel + HTTPS は通信路として妥当で、本プロジェクトでも採用継続。 残るリスクは「誰が API を叩けるか」「どれだけ送れるか」で、これはアプリ層の設計課題。
K-26 公開 URL・API 認証・アップロード制限
きっかけ
「危ない 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 キー)と量の制限(サイズ上限)は別レイヤーで足す。
K-27 シークレット管理 — .env・DB パスワード・PAT・ローテーション
きっかけ
「シークレット管理とは何の話か」「DB もユーザー/パスワードで守るのに、.env は認証なし?」 「秘密を DB に入れた方がよい?」「ローテーションは本当に必要?」— 用語の整理が必要だった。
「シークレット」とは
漏れたらなりすまし・不正操作につながる、パスワードのような文字列の総称。
| シークレット | 誰が使う | 何のため | 置き場所(本プロジェクト) |
|---|---|---|---|
POSTGRES_PASSWORD | API プログラム | DB に接続 | server/.env |
TUNNEL_TOKEN | cloudflared | Tunnel に接続 | server/.env |
FEEDBACK_API_KEY | クライアントアプリ・疎通スクリプト | FB API を叩く(SEC-01) | server/.env のみ + アプリ --dart-define(K-30) |
| GitHub PAT | Worker | repository_dispatch | worker/.env(K-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_TOKEN | Tunnel 経路の悪用の可能性 |
| API キー | 不正 FB 投稿 |
| DB パスワード | データ読み取り・改ざん |
運用: PAT は四半期(K-23)、漏洩時は即 revoke / 再発行。
結論
運用: scripts/rotate-server-secrets.ps1 -ApiKey -RestartApi でランダム生成して server/.env に書き込み(値は画面に出さない)。
どのディレクトリに何を書くかの一覧: docs/index — 構成とシークレット・README。
K-28 Cloudflare Access・ログ・PII・実測確認(WBS 3.1.5)
きっかけ
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)を体験で補強する。記録は開発ログまたは本節に追記。
- 正規 FB 1 件 —
.\scripts\test-feedback.ps1 -BaseUrl "https://api.dammy-otoko.com" - DB 確認 —
feedbacksの中身(PII の有無) - Storage 確認 —
data/storage/のファイル - API ログ —
docker compose logs api --tail 50 - 無認証 curl — 現状、URL だけで POST できることを体感(K-26 の裏付け)
- (任意)LAN から :8000 —
http://<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 | id・inference_result(JSON)・image_path・created_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 キーなしで 201(2026-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/.env に FEEDBACK_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 で実測すると理解が定着する。 設計の正本は セキュリティ設計。
K-29 セキュリティを考える共通の型 — 詳細知識がなくても不足に気づく
きっかけ
セキュリティやインフラを学ぶたびに新概念が出てきて不安になる。 「結局、機械を組んだ経験やネットワークの深い理解がないと無理では?」という疑問。
結論 — 特別な経験がなくても使える「共通の型」はある
暗記力や配線経験より、データの流れを追い、境界で三つ質問する習慣の方が効く。 専門用語はその質問に名前を付けたもの、と捉えるとよい。
型 1 — 三つの質問(いちばん使う)
新しい構成を見たら、毎回これだけ聞く:
| 質問 | 意味 | 本プロジェクトの例 |
|---|---|---|
| 誰が | 送信者・操作者は誰か。正当性をどう証明するか | 本番アプリ? curl? → API キーは? |
| どこから | どのゾーン・経路か。裏口はないか | Cloudflare 経由? LAN の :8000 直? |
| 何に届く | 最終的に触れる資産は何か | API → DB / Storage / ディスク容量 |
「あれ、この境界で誰がは確認してないな」「どこから別ルートがありそう」 — こう思えれば、OSI を全部知らなくても不足に気づける。
型 2 — 四つの資産(何を守るか)
flowchart LR
subgraph assets["守るもの"]
P[通路\n盗聴されないか]
G[入口\n誰でも入れるか]
D[中身\nDB・ファイル]
K[鍵\nパスワード・トークン]
end
| 資産 | 守り方の例 | 用語との対応 |
|---|---|---|
| 通路 | HTTPS | TLS(K-25) |
| 入口 | FW・API 認証・Tunnel | ゾーン境界(K-24・K-26) |
| 中身 | DB 非公開・バックアップ | データ保護 |
| 鍵 | .env・ローテーション | シークレット(K-27) |
四つのどれかに「それ、どう守ってる?」の答えが弱いと感じたら、そこがギャップ。
型 3 — 境界ごとの門番チェック
ゾーンをまたぐ線を 1 本引くたびに、次の 3 種類の門番があるか見る:
- 暗号化 — 路上で読まれないか(HTTPS)
- 入口の選別 — IP/ポートで絞るか(FW)、利用者を確認するか(API キー)
- 中で何ができるか — 通ったあと、何を保存・実行できるか(サイズ上限、DB 権限)
本プロジェクトで「守られていないな」と分かったのも、この型の 2 番目: Cloudflare 経由の入口に利用者確認(API キー)がない、と気づいたから。
「物理経験・ネットワーク深理解」は必須か
| あると楽 | なくても代替できること |
|---|---|
| 機械を組んだ経験 | 「PC の中に API と DB がある」程度の箱図で十分な場面が多い |
| TCP/IP の細部 | 「住所(IP)と部屋番号(ポート)」の比喩で 80% は回る |
| 長年の運用 | 流れを追う・一度手を動かす(K-28 実測)で体感が補える |
不安の正体は「用語が増えること」より、全体の地図が頭にないことが多い。 地図は データフロー や K-24 のゾーン図で足りる。細部は必要になったときに足せばよい。
学習が不安なときの進め方
- まず データフロー 1 本 だけ追う(FB がどこを通るか)
- その線の上に 三つの質問 を置く
- 弱い答えを 1 つだけ WBS タスクにする(一度に全部理解しようとしない)
- 実測 1 回(curl、logs、DB)で理論を腹落ちさせる
K-30 リポジトリ構成と .env 配置 — FEEDBACK_API_KEY 誤設定と 401
きっかけ
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_KEY を worker/.env に誤って追加していた(のち削除)。
「シークレットを足した」という感覚は正しいが、置き場所が違った。
正しい配置(本プロジェクト)
| ファイル | 書くもの | 読む側 |
|---|---|---|
server/.env | FEEDBACK_API_KEY、TUNNEL_TOKEN、DB 接続など | Docker Compose(api コンテナ)、test-feedback.ps1 |
worker/.env | GITHUB_TOKEN、GITHUB_REPOSITORY のみ | 学習スタブ・repository_dispatch(K-22) |
一覧表: docs/index — 構成とシークレット。
test-feedback.ps1 がキーを読む順序
- 引数
-ApiKey(明示指定時) - PowerShell の
$env:FEEDBACK_API_KEY(セッションに残っていると最優先) server/.env(Get-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)。
-RestartApi は server/.env の変更を API コンテナへ反映するために必須。
.env だけ直してコンテナを再起動しないと、ファイルとコンテナ内のキーがずれたままになる。
結果(2026-06-14)
- POST
https://api.dammy-otoko.com/api/v1/feedback→ HTTP 201 - レスポンス例:
id58b80bce-22c1-4de1-a010-2961f5b9e4d3、created_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 が意図せずシェルに入り、今回のような混乱の原因になりうるため、オフのままでよい。
worker/.env は GitHub 用、server/.env は API 用 — 混ぜない。
health が通って POST だけ 401 なら、インフラは生きている。認証の鍵の不一致を疑え。
K-31 全体構成図(セキュリティ拡張版)— 元図解から Phase1 実装への対応
きっかけ
会議資料の全体構成図(サーバー・Tunnel・CI/CD・クライアント)だけでは Phase1 の実装理解に十分だったが、 SEC-01 以降「鍵はどこに置くか」「HTTPS と API キーの違い」「GHA Secret と PAT の違い」が増え、 1 枚の図だけでは追いにくくなった。
元の全体構成図(会議資料)
Phase1 Android 実装への読み替え
| 元図解の要素 | Phase1(① Android)の実装 | ② iPhone(後) |
|---|---|---|
| クライアント | Flutter スタブ(Pixel 7a) | Swift / SwiftUI |
| オンデバイスモデル | TFLite(APK 同梱) | CoreML |
| FB 送信 | HTTPS → Cloudflare → Tunnel → FastAPI | 同左 |
| CI/CD | GitHub Actions → APK artifact | GHA + Fastlane → TestFlight |
| 配布 | artifact ダウンロード → adb install | App 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 / 401 | K-30、サーバー runbook §5.1 |
| 次アクション一覧 | docs/index.html「次のアクション」 |
| Phase1 通し(SEC 前) | 開発ログ 1.5.2(2026-06-11、v2・FB 201 済み — 当時は SEC-01 前) |
読む順序(セキュリティで迷ったとき)
- 本節 K-31 — 全体像と鍵の種類
- シーケンス図 §0 — SEC 前後の時系列と抜け道
- K-25 — HTTPS / Tunnel が守るもの
- K-26 — API 認証の考え方
- K-30 — FEEDBACK_API_KEY の置き場所と 401
- セキュリティ設計 — SEC-01〜08 の正式定義
関連資料
| 資料 | 用途 |
|---|---|
| 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 等 |