記事一覧

技術メモを残していきます

日記 EatからVtermへ出戻りした話とかeglot(rust)動かない問題とか

TL;DR 以前vtermのC-kまわりの挙動を追いかけていて、その流れでEatへ移行していた 今回もう一度vtermの素の設定を試したところ、C-kは特別な回避策なしで普通に動いた Eatは実運用してみると自分のワークフローと噛み合わない部分がいくつかあり、結局vtermに出戻りすることにした 副次的にEglotでRustのサーバーが起動しない問題も出ていたので、原因と対処もついでにメモしておく 経緯 もともとvtermのC-kバインドを追いかけていたらEatに移行することになった話(wasutech, 2026-07-04)でvtermからEatへ移行していた。この記事は逆に、そのEatからvtermへ戻した記録になる。 ちなみに、上の記事内でEat移行のきっかけとしてリンクされていたのが、Ki_chi氏のEmacsのターミナルエミュレーターをvtermからEatに移行しました(Ki_chi@Blog, 2026-01-29)。 削除した設定 Eatのuse-packageブロック一式を削除した。内容としては、straight経由でのcodebergリポジトリ指定、eat-semi-char-non-bound-keysの除外リスト編集、eat-reload呼び出しなど。 復元したvterm設定 元記事内で「移行前の設定」として紹介されていたvterm用のuse-packageブロックをベースに復元した。 (defun my/vterm-send-C-k () "Send C-k to vterm terminal." (interactive) (let ((inhibit-read-only t)) (vterm-send-key "k" nil nil t))) (use-package vterm :straight t :bind (:map vterm-mode-map ("C-h" . vterm--self-insert) ("C-k" . my/vterm-send-C-k)) :bind (("C-c v" . vterm)) :config (setq vterm-term-environment-variable "xterm-256color")) defunをuse-packageより前に置いているのは、Emacs Lispが上から評価される言語だから。先に定義しておかないと、use-packageの:bindが参照する段階で未定義エラーになる。 my/vterm-send-C-k自体は、元記事の著者本人も根本原因は特定できなかったと明記している回避策で、いわば対症療法である。 実際に設定を戻してみたところ、C-kは回避策なしの素の状態で普通に動いた。以前この問題を追いかけていた時点との違い(Emacsのバージョン、vtermのバージョン、あるいは単に環境差)は特定できていないので、なぜ動くようになったのかは正直わかっていない。ただ動いている以上、今のところ回避策は入れずに使っている。 別件: eglot(rust-analyzer)が起動しない vterm/eatの話とは別件だが、ちょうど同じタイミングでEglotのRustサーバー起動に失敗する問題が出た。 原因は単純で、rustcとrust-analyzerはrustupのコンポーネントとして別扱いになっており、公式toolchainをインストールしただけではrust-analyzerは入らない。個別にインストールする必要がある。 rustup component add rust-analyzer これで解決した。 出戻りマップ 項目 Eat Vterm 未Git管理ディレクトリを開いたとき ディレクトリを聞かれてモヤっとする 特に気にならない レイアウト 崩れることがしばしばあった 安定している 実装 Emacs Lispのみで完結(要確認) libvterm(C)に依存 C-k 今回は未検証 素の設定で動作した 所感 Eatは実装がEmacs Lisp単体で完結しているというのは魅力的で、パッケージとしての完成度も高いと思う。とはいえ、自分の使い方だと未Git管理のディレクトリを開こうとしたときにディレクトリを聞かれたり、レイアウトがよく崩れたりすることがしばしばあり、実際に使ってみるとかえって煩わしさの方が勝ってしまった。Emacsで完結するツールとしては素晴らしいパッケージだと思うが、結果的には私にはvtermの方が合っていたので、恥ずかしながら出戻りすることになった。とはいえ、また別のターミナルエミュレータを試すかもしれないので、結論は一旦保留にしておく。今はvtermを使う。 ...

August 26, 2026 · 1 min

Androidアプリ開発の極意 4,5章ユーザー体験と非同期処理

前の記事同様に Androidアプリ開発の極意 | 技術評論社の「ユーザーストレス軽減」まわりの実装Tipsと、並行処理の選択肢をざっくり整理してExpo周りと絡めたもの。細かい実装には踏み込まず、要点だけ。 はじめに アプリが固まる原因の大半は、メインスレッド(UIスレッド)を長時間ブロックしていること。非同期処理の設計は結局「ユーザーを待たせない」という一点のためにある。 二部構成で一部は本を読んだ感想+α、二部がExpoだとどうなるかって話。 基本的にAIで出して、それを読んで調べたりしたもの。 第1部:Androidの話 Android(Java/Kotlin)側だけで完結する話。UXまわりの実装Tipsと、非同期処理の手段がsynchronizedからVirtual Threadsまでどう変遷してきたかを、Expoの話を挟まずに整理する。 UXまわりの小さな判断 時間のかかる処理にはProgressBarで進捗を見せる SharedPreferencesは永続化のためディスクI/Oが発生する セルラー通信時は重い処理の前に一言断りを入れると親切 エラーはその場で表示しつつ、Crashlytics/ACRAのような開発者向けツールで後から追える形が理想 NDKは高速化と引き換えにデバッグの複雑さが増す。基本的には奥の手 非同期処理はなぜ増え続けているのか synchronizedは「同時に入らない」ことしか保証しない。順番までは保証しない。 Threadを直接生成した場合も同様で、生成順と実行順は一致しない。順番を保証したいならExecutors.newSingleThreadExecutor()のような直列化の仕組みを使う。 ExecutorServiceは、スレッドを直接管理せず、プールとキューで管理する仕組み。スレッド数の制御や再利用をアプリ側のロジックから切り離せる。 AsyncTaskはAPI 30で非推奨になった。ライフサイクル管理が弱く、Activity/Fragment破棄後もタスクが動き続けてリークやクラッシュを招くのが理由。 今の主流はKotlin Coroutines。スレッドをブロックせず中断できる軽量な並行処理で、排他制御が必要ならMutexを使う。UIに紐づく短い処理はCoroutines、画面を離れても続けるべき処理はWorkManagerと使い分ける。 JVM側ではJDK 21でVirtual Threads(JEP 444)が正式機能になった。I/Oバウンドな処理を大量にさばくのに向くが、CPUバウンドには向かない。ただしこれはJDK/Java SE側の機能で、Android Runtime(ART)は非対応。Androidアプリでそのまま使えるわけではない。 Akkaはアクター単位でメッセージパッシングする設計思想。2022年にライセンスがApache 2.0からBSL(Business Source License)に変わり、2024年10月以降はproduction利用にライセンスキーが必須になった。個人・OSS・startupは無料キー、商用は原則サブスクという体系。汎用ツールというより大規模分散システム向けの専門ツールという位置づけに変わっている。 比較表(Android) 手段 排他制御 順序保証 位置づけ synchronized あり なし 低レベルの排他制御プリミティブ Thread直接生成 なし なし 基本的には避ける ExecutorService 設計次第 直列化すれば可 CPUバウンド処理で有効 AsyncTask なし なし API 30で非推奨 Kotlin Coroutines Mutexで対応 設計次第 Android/Kotlinの主流 Virtual Threads 従来通り 設計次第 JVM側、I/Oバウンド向け(ART非対応) Akka アクターモデル 条件付き 大規模分散システム向け 「排他制御」と「順序保証」は別物。ここを混同すると設計は破綻する。AsyncTaskはAPI 30で非推奨、今はCoroutines(短命処理)とWorkManager(永続処理)を使い分けるのが主流。Virtual Threads(JDK 21)はJVM側の話でARTは非対応。 ...

August 18, 2026 · 1 min

Androidアプリ開発の極意 2,3章あたり ANR、UIスレッド

Androidアプリ開発の極意 | 技術評論社 この本読んでる。2017年に出版してて、内容には古いものがいくつかある。 そこはAIの使いどころ。とりあえず学習したことを軽くメモして、中身について古い場合は指摘を入れたり、今は何が主流なのかといったことを補完するような指示を出した。 サポート付きの読書っぽいことをしながら読み進めて、文章をまとめた。 概要 React Native/Expoでアプリを書いていると、「なぜかカクつくが、クラッシュログには何も残っていない」という現象に何度もぶつかる。 原因を追っていくと、大体はUIスレッドを塞いでいるか、メモリを食い潰しているかのどちらかという同じ根っこに行き着くことが多い気がする。 この記事では、Androidアプリ開発の極意 | 技術評論社の3章UIスレッド/ANRの基礎から、React NativeにおけるJS-ネイティブのスレッド分離、コルーチンとasync/awaitの正体、Android/iOS/RNの非同期処理対応、そして見落とされがちなメモリ管理(OOM・LMK・Javaのメモリリーク・useEffectのクリーンアップ)までを、実務で踏んだり踏みそうな地雷も交えて整理した。 なお、私は仕事でしばしReact Native Expoを利用しているため、なるべくそちらに沿った内容に寄せて本文を作成する。 それによって本の内容とは離れることがしばしあるが、そこは予め留意してほしい。 全体像 まずスレッドの分離構造をざっくり図にする。 [Android/ネイティブ] UIスレッド(メインスレッド) ── 描画・入力処理を担当 │ │ 5秒ブロック → ANRダイアログ ▼ ワーカースレッド/コルーチン(Dispatchers.IO等) ── 重い処理はここへ逃がす [React Native/Expo] JSスレッド ── ビジネスロジック・状態更新・レンダリング計算 │ │ (ブリッジ/JSI経由) ▼ ネイティブUIスレッド ── 実際の描画 │ (任意) Worklet用の別JSコンテキスト ── react-native-worklets-core / Reanimated ポイントは、Androidのネイティブ層には「ANR」という可視化された強制終了の仕組みがあるが、React NativeのJSスレッドが詰まっても同じ仕組みは働かないという非対称性にある。以下、順番に見ていく。 UIスレッド/ANRの基礎 Androidでは、UIスレッド(メインスレッド)が入力イベントに応答できない状態が一定時間続くと、システムが強制的にANR(Application Not Responding)ダイアログを出す。Android公式ドキュメントによれば、入力イベント(キー押下や画面タップなど)に5秒以内に応答しない場合にANRがトリガーされる。これはフォアグラウンドの入力ディスパッチに関する条件で、他にもブロードキャストレシーバーやサービスの応答遅延など複数のANR条件が存在する。 一方、React Native/Expoでは事情が異なる。RNのアーキテクチャはJSスレッドとネイティブUIスレッドが分離されている。JSスレッドが重い処理で詰まっても、ネイティブ側のUIスレッド自体がブロックされているわけではないため、Android OSが検知する「入力ディスパッチのタイムアウト」という意味でのANRダイアログは出にくい。ただし、体感としては「タップしても反応がない」「アニメーションがカクつく」という、ユーザーから見ればANRと区別のつかない現象が起きる。ANRダイアログが出ないぶん、むしろ発見が遅れて厄介とも言える。 補足: 「RNならANRが原理的に起きない」という理解は不正確。JSスレッドが完全にブロックされている間、そのJSスレッドに紐づくタッチイベント処理やRedux的な状態更新も止まるため、ユーザー体験としては同種の「固まり」が発生する。OS側の検知メカニズムが働かないだけで、問題自体は消えていない。 重い処理の逃がし方:InteractionManager(旧API)とWorkletsは別物 RN側で「重い処理をUI/JSスレッドから逃がす」ための手段として、従来は InteractionManager がよく使われていたが、2026年8月リリースのReact Native 0.87で InteractionManager は削除済みであり、公式は代替として requestIdleCallback を案内している。0.82〜0.86の時点では非推奨警告が出るようになっていたため、既存プロジェクトでこの警告を見た人も多いはずだ。以降は「過去にどう動いていたか」の説明として読んでほしい。 ...

August 16, 2026 · 2 min

自宅NW活動誌:固定IP撤去とdnsmasq移行で名前解決を建て直した話

TL;DR 電源断からの復旧で名前解決がおかしくなり、調べていったら「dnsmasqがhosts変更を未反映」「gw-a/gw-bがローカル固定IPとdns01のDNS管理で二重管理になっていた」の2つが絡んでいたという話。ついでに「自ホスト名pingが127.0.1.1を返す」件は仕様通りで実害なしと確認した。全部片付いたので、今回は「詰んだ」話ではなく「ハマったけど直した」話。 環境 セグメント: home.local(192.168.20.0/24) DNS/DHCP: dns01(dnsmasq) Keepalived構成のGW: gw-a, gw-b クライアント側ネットワーク管理: nmcli 発生した問題 電源断→起動後、あるノード(client01)から複数ノードにSSHできなくなった。調査を進める過程で、そもそもdns01自身の名前解決(dns01という名前そのもの)が引けなくなっていることが判明。/etc/hostsに手動でエントリを足しても、他ノードから引けない状態が続いた。 原因は1つではなく、「gw-a/gw-bがローカル固定IP設定+hosts記載を持っていたこと」と「dns01側のhostsエントリ消失」が絡み合っていた。 対応1:dnsmasqがhosts変更を反映していなかった 症状: /etc/hostsにエントリを書いても、他ノードからdnsmasq経由で引けない。 原因: dnsmasqは/etc/hostsを起動時に読み込むだけで、実行中のファイル変更を自動では拾わない。dhcp-hostsfileやdhcp-optsfileなどのDHCP関連設定ファイルは変更時にSIGHUPシグナルを送ることでdnsmasqに再読み込みさせられる仕様になっており、/etc/hostsについても同様に、SIGHUPを受け取るとキャッシュをクリアした上で再読み込みする挙動になっている。つまり「編集しただけ」では反映されず、シグナルを送る操作が別途必要というのがハマりどころだった。 対処: systemctl restart dnsmasq(reload = SIGHUP相当)で即解決。 学び: /etc/hostsをdnsmasq向けに編集したら、反射的に再起動する習慣をつける。 対応2:gw-a/gw-bを固定IP運用からdhcp-host予約運用に切り替え 目的: ローカル固定IP(dhcpcd.conf/nmcli static)とdns01のDNS管理が二重管理になっていたのを解消し、IP管理をdns01(dnsmasq)側に一元化する。 手順(実施順が重要) 1. dns01側を先に設定 /etc/dnsmasq.conf(またはdhcphostsファイル)に予約を追記する。 dhcp-host=<MACアドレス>,192.168.20.3 # gw-a dhcp-host=<MACアドレス>,192.168.20.4 # gw-b MACアドレスの大文字/小文字はdnsmasqでは区別されない(AA:BB:...でもaa:bb:...でも可)。区切り文字は:固定。設定後はsystemctl restart dnsmasq。 2. クライアント側(gw-a/gw-b)を動的取得に変更 nmcli管理の場合は以下の通り。 nmcli connection show # 対象接続名を確認 nmcli connection modify "<接続名>" ipv4.method auto nmcli connection modify "<接続名>" ipv4.addresses "" nmcli connection modify "<接続名>" ipv4.gateway "" nmcli connection modify "<接続名>" ipv4.dns "" nmcli connection up "<接続名>" 確実に反映させるため最終的にreboot。 ...

July 30, 2026 · 1 min

Terraform学習1:variable/outputが分離しているのは何のためか

Terraformを触り始めると、variableとoutputが真逆の役割を持っていることはすぐわかる。だが「なぜこの2つが分離しているのか」「なぜvalidationが型チェックと別枠なのか」まで理解しないと、秘匿情報をうっかりstateに残す事故につながる。今回は自分のTerraform構成(variables.tf, outputs.tf, versions.tfのbackend部分)を題材に、設計意図を整理する。 variableは「設定とロジックの分離」のための入口 variableは、値をリソース定義本体に直書きせず、外部から注入するための仕組みだ。値を変えるたびにリソース定義そのものを編集しなくて済むようにする、という目的がある。 値の入力元は主に4つある。 default(ブロック内で指定) terraform.tfvars 環境変数(TF_VAR_xxx) -varオプション(CLI実行時) defaultの有無で意味が変わる 意味 defaultあり 共通・変更頻度が低い値。省略可能(例: aws_region, project_name) defaultなし 環境固有・必須の値。指定しないとplan/apply時にエラー(例: budget_alert_email, github_org) defaultの有無は「省略可能かどうか」の表明であり、そのままその変数の性格(共通設定か、環境固有の必須値か)を表している。 validationは型チェックとは別レイヤーの検証 variableのtypeは形式のチェックしかしない。type = stringは「文字列であること」しか保証せず、「その文字列が正しい値かどうか」は見ない。 そこを埋めるのがvalidationブロックだ。値の中身・意味をチェックする。例えばSSMパラメータ名は/始まりである必要がある、EBSルートボリュームは8GiB以上必要、といった業務ルールをここに書く。 variable "image_id" { type = string description = "The ID of the machine image (AMI) to use for the server." validation { condition = length(var.image_id) > 4 && substr(var.image_id, 0, 4) == "ami-" error_message = "The image_id value must be a valid AMI ID, starting with \"ami-\"." } } validationはapply前、ローカルのplan段階でエラーを弾ける。無駄なapply実行を未然に防げるのが最大の利点で、クラウド側にリクエストが飛ぶ前に「その値はそもそもおかしい」と教えてくれる。 ...

July 25, 2026 · 2 min

自宅NW活動誌: スロットリング監視を入れたら即日で電源劣化を検出した話 等

自作監視ツールにメトリクスを追加したら、その日のうちに電源劣化を検出してしまった。ついでにRaspberry Pi 3でWPA3が使えるか実験して散った。今日一日の活動記録。 メトリクス追加: swap / throttled 自宅Raspberry Piクラスタを監視している自作ツール (Go製・SSH pull型・エージェントレス) に、メトリクスを2種追加した。 swap使用量: free -b で取得。メモリ収集と同一コマンドなので実行を統合 スロットリング状態: vcgencmd get_throttled の生hex値を保存し、表示側でビットデコード throttledのビットフラグは以下の通り。 bit 意味 0 低電圧 (現在) 1 ARM周波数制限 (現在) 2 スロットリング中 (現在) 16 低電圧 (過去) 17 周波数制限 (過去) 18 スロットリング (過去) アラートは現在ビット (bit0-2) のみ対象にした。過去ビットは再起動までクリアされないので、対象にすると鳴りっぱなしになる。この設計判断が後で効いてくる。 あわせてメモリ利用率の表示・アラートも追加。利用率はDBに保存せず、(total - available) / total を表示・判定時に都度計算する方式にした。used / total だとキャッシュ込みで常時高く出て、1GiBノードではアラートが無意味化するため、available基準が実態に合う。 監視が初日から仕事をした デプロイ後ほどなくして、AP役ノードの1台 (Pi 3、以下AP#1) からthrottledアラートが鳴り始めた。 アラート種別: throttled 状態: 発生 値: 0x50005 アラート種別: throttled 状態: 復旧 値: 0x50000 0x50005 = bit0 (低電圧・現在) + bit2 (スロットリング中) + 過去ビット。稼働中に本当に低電圧が起きている。 ...

July 20, 2026 · 1 min

go-ts-modeでgofmt(goimports)の設定を修正した

TL;DR Go開発をgo-ts-mode(tree-sitterベース)に寄せたら、問題が発生していた。 1つ目: gofmt(goimportsで代替)を実行した直後は正しく整列されているのに、TABキーを1回押すとインデントが縮む。 2つ目: before-save-hookにgofmt-before-saveを登録しても、保存時に一切フォーマットされない。しかもエラーは出ない。 両方とも「go-mode.el(tree-sitter以前のパッケージ)がgo-ts-modeの存在を想定していない」ことが根っこの原因だった。 環境 Emacs、Go開発はgo-ts-mode(tree-sitterベース)を使用 OS: Arch Linux gofmt-commandは"goimports"に上書き設定(gofmtの代わりにgoimportsを使う) 対象ファイル例: internal/api/server.go(構造体フィールドやjsonタグを縦に整列させるコードスタイル) 試したこと 症状1: 手動でTABキーを押すとインデントが縮む 手動でM-x gofmtを実行して整形した直後のファイルは正しく整列されているが、既存行にカーソルを置いてTABキーを押すと、その行のインデントが浅くなる。 思い出したがこれはかなり前から存在してる不備だ。 ちょうどよかったのでここで直してみよう。 切り分けは以下の手順で進めた。 M-x describe-variable RET indent-tabs-mode RET → 値はt。タブを使う設定として正常。 対象行の文字をC-u C-x =で確認 → タブ文字であることを確認(Char: TAB)。 タブの表示幅を疑い、行頭でタブ1個分右に移動してからC-x = → column=8。tab-widthも8(Emacsのデフォルト)。表示幅そのものは正常。 「短くなった行」と「正常に見える上の行」でM-m(back-to-indentation)からのC-x =を比較 → 両方column=16で一致。インデント幅そのものは揃っていた。 ここで「タブ幅の表示問題ではない」と判明。 真因は次の通り。gofmtは構造体フィールドやjsonタグを縦に整列させるために、構文的に必要な数以上のタブを意図的に挿入する(整列目的の余分なタブ)。一方go-ts-modeはtree-sitterによる構文解析で「その行の構文的必要インデント深さ」だけを計算し、TABキー押下時に行頭の空白をその計算値に置き換える。gofmtが足した「整列用の余分なタブ」は構文的には不要なので、go-ts-modeがそれを認識せず削ってしまう。 これはEmacs設定のバグではなく仕様通りの動作だ。go-ts-modeには「整列のための余分な空白」を認識する機能がそもそもない。 調査の途中で、副原因ももう1つ見つかった。go-ts-mode-indent-offsetが4に設定されていた。 (use-package treesit :straight (:type built-in) :config (setq treesit-font-lock-level 4) (setq go-ts-mode-indent-offset 4)) go-ts-mode-indent-offset = 4だと「1インデントレベル=4列」で計算される。一方tab-widthはEmacsデフォルトの8のまま。gofmtの出力は「1インデントレベル=タブ1個(実質8列相当)」が前提なので、この不一致でTABキー押下時の再インデント計算が4列刻みでずれる。 対処はgo-ts-mode-indent-offsetをtab-widthと同じ値に揃えるだけ。 ;; 変更前 (setq go-ts-mode-indent-offset 4) ;; 変更後(効果確認済み) (setq go-ts-mode-indent-offset 8) これで症状は解消した。 ...

July 18, 2026 · 2 min

EmacsのTRAMPで多段SSH越しにファイルを開いた話

きっかけ 作業機Aで書いたEmacsの設定はそのままに、B経由でCにあるファイルを直接開きたくなった。CにEmacsを立てるほどでもないし、B自体も経由するだけの踏み台。よくある構成だと思う。 「TRAMPで多段SSHすれば一発で開けるはず」という認識だけはあったので、記憶を頼りに適当な構文を書いたら普通に動かなかった。 最初に書いた(間違った)構文 C-x C-f /ssh:userB@B:/ssh:userC@C:/path/to/file RET コロン区切りでリモートパスを入れ子にすれば多段になるだろう、という思い込みで書いたが、これは単に存在しないパスとして扱われて開けない。 正しい構文: パイプ区切り 調べ直すと、現行のTRAMP(Ad-hoc multi-hops)では各ホップを | で連結する仕様になっていた。 各プロキシはファイル名部分を除いたリモートホスト指定と同じ構文で指定し、ホップごとに | で区切って、起点ホストから最終目的地までを連結する、という説明になっている1。 C-x C-f /ssh:userB@B|ssh:userC@C:/path/to/file RET パスの : は最後のホップにだけ付く。それ以前のホップは | で単純に繋げるだけでよかった。 dired でディレクトリブラウズしたい場合も同じ構文でいける。 C-x d /ssh:userB@B|ssh:userC@C:/path/to/dir RET 一度使うと短縮形が使えるようになる TRAMPはこのアドホックな多段定義を、そのEmacsセッション中は tramp-default-proxies-alist に一時的なレコードとして追加する。そのため同じセッション内であれば、以降は /ssh:you@remotehost:/path という単純な形式だけで同じリモートホストに再接続できるようになる1。 セッションをまたいで使い回したい場合は tramp-save-ad-hoc-proxies を非nilにしておけば、設定ファイル側に多段定義そのものが保存される1。 ついでにsudoも同じノリで組める C上でrootが必要な作業をしたい場合、sudo を最後のホップとして追加するだけでよい。 C-x C-f /ssh:userB@B|ssh:userC@C|sudo:root@C:/path/to/file RET su、sudo、doas、run0 のようなメソッドを別ホスト上で実行したい場合、先頭にsshなどのメソッドを組み合わせて使う。つまりTRAMPはまず非管理者権限でそのホストに接続し、そのあとでそのホスト上で管理者権限に切り替える、という2段階の動作になっている1。 現在開いているバッファをそのままsudo権限で開き直したいだけなら、tramp-revert-buffer-with-sudo というコマンドが用意されている2。ファイルを開き直すたびにパスを書き直さなくていいので、これが一番出番が多いかもしれない。 M-x tramp-revert-buffer-with-sudo RET まとめ TRAMPの多段SSHは、以前は tramp-default-proxies-alist を事前に設定しておく必要があったが、Emacs 24以降は設定なしでその場でパイプ区切りの多段パスを書くだけで通るようになっている3。この「昔の記憶のまま新しい仕様を疑わなかった」せいで無駄にハマったので、次から多段SSH系のパスを書くときは一旦マニュアルを見に行くことにする。 Ad-hoc multi-hops (TRAMP User Manual) ↩︎ ↩︎ ↩︎ ↩︎ TRAMP 2.8.1 User Manual ↩︎ TRAMP - WikEmacs ↩︎

July 13, 2026 · 1 min

iptablesルールを可視化するツールを作った話

きっかけ 暇つぶしとインフラの勉強を兼ねて、何か作りたいと思っていた。最初に思いついたのは「iptablesのルールを抽出して、何が許可されてて何が拒否されてるのかをUI的に見せるアプリ」だった。 思いつきはよかったが、実際に手を動かしてみると想像より何段階も難しかった(動いたのはほぼClaude)。この記事はその過程の記録。 言語化しづらい分野だったが、ふんわりとした説明でも個人的にはかなり納得のいくものを生成してくれたので満足ではある。 作ったもの iptables-rel GitHub - wasuken/iptables-rel · GitHub 既存ツールは無かったのか 作り始める前に軽く調べた。同じ発想のツールは既にいくつかある。 gressgraph は iptables -L -vx の出力をグラフ化するツールで、開発者自身が小規模なルールセットでしか検証していないと明言している。iptable_vis はAWKスクリプトでiptablesの出力を読み、blockdiagでフローチャートを生成する。星が700超えていて、それなりに使われているようだった。 ただ、これらは基本的に「チェーンのジャンプ構造」をそのままグラフにする方向性で、IPアドレスやインターフェースの意味を解釈して「ホストAからホストBへの通信」という粒度に変換するような作り方はしていなかった。個人の環境に合わせて意味づけをするなら、これは自分で作った方が早いと判断した。 最初の設計: グラフ、失敗 D3.jsのforce-directedグラフで、ノードをホスト、エッジをルールとして表示するプロトタイプを最初に作った。動くには動いたが、実際に見てみると「関連はわかるけど、間のルールがわからない」という致命的な問題があった。エッジの上に小さくラベルを乗せるだけでは、プロトコルやポート、チェーンの情報を表現しきれない。 次に表形式(ホストごとにルールを一覧表示)を試したが、今度は逆に「どこと繋がってるか」の直感性が失われた。 最終的に落ち着いたのは、グラフとテーブルのハイブリッドだった。ノード間の関係は力学モデルのグラフで表示し、エッジをクリックすると、その2ノード間の全ルールが表形式でサイドバーに展開される。これで「全体像」と「詳細」を両立できた。 インターフェースでつなげる 実データを流し込んでみて気づいたのが、FORWARDチェーンのルールをホスト同士のエッジとしてマッピングすると意味が壊れるケースがあることだった。-i eth0 -o wg0 のようなルールは、2つのホスト間の通信を表しているのではなく、1台のマシンの中で「あるインターフェースから別のインターフェースへ中継してよいか」を表している。 ここでモデルを作り直した。ホストをそのまま1つのノードにするのではなく、ホスト:eth0 ホスト:wg0 のようにインターフェース単位でノードを分割し、ルールでない「そもそも物理的/論理的に繋がっている」という関係は、クリックできない点線のtopologyエッジとして別扱いにした。実線はルール、点線は構造。この区別を入れてから、ようやく「何が起きているか」が読み取れるグラフになった。 パーサ: 再帰的チェーン解決 iptables-save の出力は、INPUT や FORWARD のようなビルトインチェーンから、ユーザー定義のチェーンへ -j でジャンプしていく構造になっている。UFWの設定などはこのジャンプが何段にもネストしていて、素直に1行ずつ読むだけでは意味のあるグラフにならない。 実装したパーサは、ビルトインチェーンを起点に、ACCEPT / DROP / REJECT / DNAT / MASQUERADE のような終端アクションに到達するまで再帰的にチェーンを辿る。テストケースとして、意図的に未定義のチェーンへジャンプするルールや、循環参照(AがBを呼び、BがAを呼ぶ)を仕込んで、無限ループせずに unresolved として記録されることを確認した。完全なNetfilterのセマンティクスを再現しているわけではなく、あくまでヒューリスティックな近似であることは、公開ページのフッターにも明記している。 エリア表示: 凸包から矩形へ 同じサブネットに属するノードを視覚的にグルーピングしたくなった。最初は d3.polygonHull() を使って、ノード群を囲む凸包を薄い背景として描画する方式を試した。技術的には動いたが、見た目が思っていたより有機的すぎて、期待していた「かっちりした枠」の印象にならなかった。 矩形(bounding box)に切り替えた。同じグループのノード座標からmin/maxを取り、パディングを足して角丸の<rect>を描画するだけなので、実装としては凸包より単純になった。CIDRが入れ子になっているケース(/24の中に/28があるなど)は、prefix長が短いほど背面・大きいパディング・薄い色、長いほど前面・小さいパディング・濃い色にすることで、二重の枠として表現した。 インターフェースのノードについては、エリアの境界をノードの座標だけで決めると、繋がっているエッジが箱の外からいきなり生えているように見えて不自然だった。接続エッジの中間点までを座標の計算対象に含めることで、エッジが箱の内側から伸びているように見せている。 公開、しかし・・・ Cloudflare Pagesにデプロイした。ビルドコマンドとアウトプットディレクトリを指定するだけで、静的サイトとして問題なく配信できた。バックエンドは無く、ルールのパースは全部ブラウザ内で完結する構成にした。 一個ドメイン持ってたので、サブドメイン割り当てて公開し、最初デプロイしたとき問題なく表示された。 ところが微修正を加えてPushし、自動デプロイが走った直後、突然サイトにアクセスできなくなった。Cloudflare Pagesが自動で割り当てる *.pages.dev のドメインからは問題なくアクセスできる一方、独自サブドメインだけが ERR_NAME_NOT_RESOLVED で弾かれる状態だった。 ...

July 12, 2026 · 1 min

vtermのC-kバインドを追いかけていたらEatに移行することになった話

vterm -> eatした理由 vtermでC-hとかは動いたけどC-kがうまく動かなかった。 色々ガチャガチャやってたけどだるくなったときにeatというものを知ったので試してみたところ、 ターミナルとして要求していたことをほぼ達成できたため、移行することにした。 EmacsのターミナルエミュレーターをvtermからEatに移行しました | Ki_chi@Blog 今回のEat移行の設定はこの記事を参考にした。本当に感謝。 また、これまで利用していたvtermについても感謝を伝えたい。恐らく要求されることはできたとは思うが、私の熱量が足りないためにそれを実現できなかったと見ている。 発端: vtermのC-kが動かない vtermで以下のような設定を書いていた。 (defun my/vterm-send-C-k () "Send C-k to vterm terminal." (interactive) (let ((inhibit-read-only t)) (vterm-send-key "k" nil nil t))) (use-package vterm :bind (:map vterm-mode-map ("C-h" . vterm--self-insert) ("C-k" . my/vterm-send-C-k)) :config (setq vterm-term-environment-variable "xterm-256color")) C-hは狙い通り動くのにC-kだけBuffer is read-onlyエラーで弾かれる。 最初は「vterm-keymap-exceptionsのデフォルトにC-kが含まれているせいだろう」と当たりをつけたが、実際にvtermのソースを確認したところこれは誤りだった。デフォルトの除外リストは以下で、C-kは含まれていない。 '("C-c" "C-x" "C-u" "C-g" "C-h" "C-l" "M-x" "M-o" "C-y" "M-y") さらにvterm-send-key自体の実装を見ると、関数内部でinhibit-read-onlyを自前でletしている。つまりmy/vterm-send-C-kが本当に呼ばれているなら、read-onlyエラーはそもそも起きようがない。 ということは、C-kは自分が定義した関数にディスパッチされておらず、別の何か(kill-lineのような通常のEmacs編集コマンドなど)が割り込んでいることになる。C-h k C-kで実際の解決結果を確認すれば犯人は分かるはずだが、ここまで調べた時点で「そもそもvtermをやめてEatに乗り換える」という選択肢が視野に入ってきたので、根本原因の特定は一旦保留にした。 なぜEatなのか そもそもvtermのキーマップあれこれで詰まってしまったのがすべてではあるが、後付の理由もあって、 Eatは実装がすべてEmacs Lispで完結している点は素晴らしいと思う。コンパイル済みモジュールに依存しないので初期でcmakeを要求されたりしない。所詮cmakeかつlibvtermのため、ほとんどの環境で動作するとは思うが、個人的にはElispオンリーというのは少し惹かれた。 逆に怖いところ GitHub - akermu/emacs-libvterm: Emacs libvterm integration · GitHub akib/emacs-eat: Emulate A Terminal, in a region, in a buffer and in Eshell - Codeberg.org ...

July 4, 2026 · 2 min