Skip to content

fix(mobile): CalDAV を TLS の vhost に直し、adopt と週次 drift CI を足す - #277

Merged
gapul merged 9 commits into
mainfrom
fix/mobile-caldav-tls
Aug 13, 2026
Merged

fix(mobile): CalDAV を TLS の vhost に直し、adopt と週次 drift CI を足す#277
gapul merged 9 commits into
mainfrom
fix/mobile-caldav-tls

Conversation

@gapul

@gapul gapul commented Aug 13, 2026

Copy link
Copy Markdown
Owner

#270 のマージ後に積んでいた 3 コミットが取り残されていたので出し直す。

1. CalDAV プロファイルの宛先が間違っていた (これが本命)

radicale の 127.0.0.1:5232homeserver.gapul.net:5232 として直接指し、CalDAVUseSSL = false にしていた。実際には nix/hosts/homeserver.nixsites 表が dav の vhost を立てていて Caddy が ACME 証明書で終端している。

そのままだと CalDAV/CardDAV の資格情報が平文で流れるうえ、宛先自体が違う。dav.gapul.net + UseSSL = true に修正。ポート指定も落とした (443)。

2. Aurora Store の担当をゼロにし、adopt で実機から回収できるようにした

  • Bitwarden は GitHub Releases に APK があったので playgithub に移した。これで play 行がゼロになり、宣言した Android アプリ全件が Obtainium の URL リスト import 一発で端末に載る。
  • apps.sh adopt — 端末に在って宣言に無いものを、配布元を引いて経路を判定した tsv 行として出す。Aurora Store で先に入れたものを packageId を目で追って書き写す作業を機械にやらせる。>>apps.tsv で追記してから整える。

3. 宣言の腐りを週次 CI で見る (.github/workflows/mobile-drift.yml)

実機は CI から触れないので、確かめるのは宣言が腐っていないかだけ。packageId / bundleId が配布元から消えていないか、GitHub の repo が改名されていないか。押した瞬間には気付けないものだけを定期実行に回す方針。PR で mobile/** を触ったときにも走る。

実機に触る側 (status / os.sh / install) は手動のまま。端末が常時繋がっていないので、繋がっていない間に「適用したつもり」の状態を作りたくない。この線引きは mobile/README.md に表で書いた。

検証

  • just mobile-test → 12 項目すべて ok (実機不要)
  • nix build .#ios-profiles → 生成物の宛先が dav.gapul.net / UseSSL=true であることを確認
  • just docs 再生成済み、pre-commit 通過

gapul added 9 commits August 13, 2026 12:42
radicale の 5232 を直接指していたが、hosts/homeserver.nix の sites 表が dav の
vhost を立てて Caddy が ACME 証明書で終端している。直接続だと資格情報が平文で
流れるうえ、そもそも指す先を間違えていた。
Play にしか無い行は Obtainium が追えず、端末側の import 1 回では載らない
唯一の穴だった。bitwarden/android は GitHub Releases に APK を出しているので
そちらに寄せた。これで宣言した全アプリが Obtainium の URL リスト import で
端末に載る。play の経路自体は残す (今後 Play 限定のものが出たとき用)。
「Aurora Store は?」「設定は自動か?」への答え。

apps.sh adopt: 端末に在って宣言に無いものを、配布元を引いて経路を判定した
tsv 行として出す。Aurora Store で入れたものを packageId を目で追って書き写す
作業を機械にやらせる。>>apps.tsv で追記してから整える運用。

週次 CI (mobile-drift.yml): 実機は触れないので、確かめるのは宣言が腐って
いないかだけ。packageId/bundleId が配布元から消えていないか、GitHub の repo が
改名されていないか。押した瞬間に気付けないものこそ定期実行の対象にする。
PR で mobile/ を触ったときにも走る。

実機に触る側 (status / os.sh / install) は手動のまま。端末が常時繋がって
いないので、繋がっていない間に「適用したつもり」の状態を作りたくない。
その線引きを mobile/README.md に表で書いた。
「アプリ側の import で完結できないか」への答え。両方できた。

apps.sh aurora: play 行を Aurora Store の Favourites に import できる JSON で
出す。Aurora 4.6 以降は Favourites の import/export と一括インストールを持って
いるので、Play しか配布元が無いものもファイルを渡す形で済む。形式は AuroraStore
の data/room/favourite/{ImportExport,Favourite}.kt に合わせた。added は 0 固定
(実行のたびに変えるとファイルが毎回差分になる)。

os.sh を端末内でも走るようにした。Termux に WRITE_SECURE_SETTINGS を一度 adb で
与えておけば、以降は母艦を出さずに settings を当てられる (Termux:Boot や cron から
呼べる)。uname -o が Android を返すことで判定し、adb を挟まない形に切り替える。
adb 呼び出しは dev() に寄せたので </dev/null の付け忘れも 1 箇所で済む。

debloat とアプリ個別は端末内からは通らない。pm uninstall は DELETE_PACKAGES、
pm grant は GRANT_RUNTIME_PERMISSIONS で、どちらも署名レベル。adb の shell uid
だから通るのであって、アプリの uid には与えられない。黙って飛ばさず理由を出す。

これで母艦とケーブルが要るのは、その 2 つと実機との差分を数える status だけに
なった。線引きは mobile/README.md の表を更新した。
手で組んだ plist が `shortcuts sign` に通らない件は、Cherri
(github.com/electrikmilk/cherri) で解決した。テキストから署名済みの .shortcut
(AEA1 コンテナ) を直接吐く Go 製コンパイラで、flake があるので nix run で足りる。
hello world を実際にコンパイルして AEA1 が出ることを確認済み。

既存のショートカットは母艦の ~/Library/Shortcuts/Shortcuts.sqlite から取れた。
ZSHORTCUTACTIONS.ZDATA が素の bplist で暗号化されていない。iCloud で iPhone と
往復しているので、端末を繋がずに 5 件とも書き出せた。XML plist に開いて
差分が読める形で置いてある。実体が iCloud にしか無く消えたら戻せないので、
これはバックアップとレビューを兼ねる。

BLOB は hex で受けて xxd で戻す。sqlite3 の writefile は出力先がファイルでないと
使えず、素の SELECT ではバイナリが壊れる。

書き出した Home / Haus は SESAME スマートロックのトグルで、機器の UUID が
入っている (URL やトークンは無い)。公開して困るなら 2 件だけ外す。
パソコン以外で Web UI が真実になっていたものを順に潰す。

DNS (scripts/check-dns-drift.sh):
hosts/homeserver.nix の sites 表は reverse proxy と死活監視を駆動しているのに、
A レコードだけ手作業で取り残されていた。Caddy の virtualHosts (sites 表そのもの
ではなく生成後) と Cloudflare の A レコードを突き合わせ、足りないものと宛先違いを
出す。--apply で寄せる。宛先は tailnet の homeserver で、Tailscale が割り当てる
ので nix には書けず tailscale status から引く。

実行したところ 21/21 一致で、Proxmox 時代の残骸が 7 件 (pve/dockge/jellyfin/
navidrome/backrest/wud/dns) 浮かんだ。EXTRA では落とさない — apex や Pages 向けの
正当なレコードが同じ zone に同居しているため。

tailnet (scripts/tailscale-policy.sh) と NextDNS (scripts/nextdns-profile.sh):
どちらも管理コンソールにしか設定が無く、repo に一文字も無かった。fetch で現物を
落とし、diff でずれを見て、apply で戻す。API トークンをまだ作っていないので
**ライブ API に対しては未検証**。最初の fetch が動作確認を兼ねる。

ESPHome (esphome/):
サービスは sites 表に載っているのにデバイスの YAML がコンテナの中にしか無かった。
gapul/esp32-plant-watering の docs/design.md をそのまま YAML にした。部品が届く前
なので閾値と給水秒数は仮置き。設計上の制約 (ADC1 系のみ / strapping pin 回避 /
センサーは測るときだけ通電 / ポンプは script 経由 / 再起動で回さない / 4 鉢は順番)
はコメントで理由ごと残した。1 鉢分を common/pot.yaml にして packages で 4 回展開する
構成は設計書の指示どおり。

validate.sh が実機も鍵も無しで構文検証する (雛形の secrets を使う)。CI に載せた。
esphome config は検証エラーを stdout に出すので、>/dev/null すると黙って失敗する。
A レコード (tailnet 内向け) しか見ておらず、CNAME でトンネルを向く公開面が
射程外だった。ingress は homelab/cloudflared.nix に宣言してあるので、CNAME が
それと食い違えば機械に言わせられる。「homeserver は生きているのにトンネルだけ
旧 IP を向いている」で Matrix の federation を一日落とした前科がある面なので、
ここを見ないのは片手落ちだった。

アカウントのトンネル一覧も出す。宣言の無いものに ? を付けるだけだが、それで
2 つ見つかった:

- homelab-pi (8fe36752) が down / 接続 0 のまま消されずに残っている。
  cloudflared.nix のコメントどおり homeserver へ作り直した後の残骸。
- manabi (5f908e74) は healthy で接続 4。manabi-api.gapul.net を実際に
  提供している別プロジェクトで、この repo の管理外。

管理外のトンネルを向く CNAME では落とさない。別プロジェクトが正当に同居して
いることがあるので、素性を出すだけにする (A レコードの EXTRA と同じ扱い)。

docs/HOMELAB.md の「Cloudflare に A レコード追加」という手作業の指示を
just dns への差し替えに直した。
DNS の EXTRA 7 件を素性ごとに分けた。消すのが正しいのは 5 件だけだった。

jellyfin / navidrome は消す側ではなかった。サービスは homelab で宣言して動いて
いて、ダッシュボードも https://jellyfin.gapul.net へリンクしているのに、sites 表に
vhost だけ無かった。つまり https では繋がらず、直接ポートを叩けば見えるので
気付かれていなかった。DNS を消すのではなく vhost を足して宣言側に載せる。

dns.gapul.net は AdGuard だが AdGuard は Pi に居て、レコードは homeserver を
指していた。誰も応答しないので、ダッシュボードのリンクを Pi の実アドレスへ
向け直したうえでレコードを消した。

Cloudflare 側で消したもの (repo の変更ではない):
- A: backrest / dns / dockge / pve / wud — いずれも Proxmox 時代の残骸で、
  vhost もサービスも無い
- トンネル: homelab-pi (down / 接続 0)。cloudflared.nix のコメントどおり
  homeserver へ作り直した後の残骸で、これを向く CNAME も無かった

これで just dns は 23/23 一致、EXTRA なし。残るのは manabi (稼働中の別
プロジェクト) だけで、これは管理外として素性を出すに留める。
ドメインのメールは Cloudflare Email Routing に載っているが、必要なレコードが
消えても誰も何も言わない。配送されなくなるだけで、気付くのは「返事が来ない」と
言われたときになる。静かに壊れる面なので機械に見せる。

期待値は Cloudflare 自身が持っている。/email/routing/dns が「この zone で
メールを受けるのに必要なレコード」を返すので、それを SSOT にして実レコードと
突き合わせる。こちらで MX の優先度や SPF の文面を書き写す必要が無く、
Cloudflare 側が要求を変えれば自動で追従する。

比較の前に均す必要があるものが 3 つあった。要求側は FQDN の末尾にドットが付く。
TXT は囲みの引用符の有無が食い違う。そして TXT は 1 文字列 255 バイト上限で
分割されるので、実レコード側が "前半" "後半" になる。DKIM がまさにこれで、
連結分の 3 文字だけずれて「存在するのに MISSING」と誤報していた。

これで just dns は A レコード / トンネルの CNAME / メールの 3 面を見る。
現状は全面一致で exit 0。
@gapul
gapul merged commit 2841715 into main Aug 13, 2026
6 checks passed
@gapul
gapul deleted the fix/mobile-caldav-tls branch August 13, 2026 11:45
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant