<?xml version="1.0" encoding="utf-8" standalone="yes"?><rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:content="http://purl.org/rss/1.0/modules/content/"><channel><title>ブログ(過去ログ) on Off-Grid</title><link>https://offgrid.taikiito.com/posts/</link><description>Recent content in ブログ(過去ログ) on Off-Grid</description><generator>Hugo</generator><language>ja-JP</language><lastBuildDate>Sat, 22 Aug 2026 09:00:00 +0800</lastBuildDate><atom:link href="https://offgrid.taikiito.com/posts/index.xml" rel="self" type="application/rss+xml"/><item><title>このブログにはてな風カレンダーを実装したら、Hugoの自動エスケープでJSONが壊れていた</title><link>https://offgrid.taikiito.com/posts/2026-08-22-sidebar-calendar/</link><pubDate>Sat, 22 Aug 2026 09:00:00 +0800</pubDate><guid>https://offgrid.taikiito.com/posts/2026-08-22-sidebar-calendar/</guid><description>はてなブログ風のカレンダー・タグクラウド・月別ボタンをこのブログ自体のサイドバーに実装した記録。Hugoのhtml/templateによる自動エスケープでJSONが壊れ、カレンダーが描画されなくなったバグの原因調査も含む。</description><content:encoded><![CDATA[<p>前回、ブログと一緒にシングリッシュ用語集サイトも公開した話を書いた。今回はまたこのブログ自体の話に戻る。サイドバーを作った記録だ。</p>
<h2 id="記事一覧とアーカイブだけでは物足りなかった">記事一覧とアーカイブだけでは物足りなかった</h2>
<p>Hugo + PaperMod で組んだこのブログは、最初はヘッダーに「記事一覧」「アーカイブ」の2つのメニューがあるだけだった。記事数が増えてくると、日付やタグから辿れた方が使いやすい。PaperModはシングルカラムのテーマで、サイドバーという概念自体がない。ただ、コンテンツ幅(<code>--nav-width: 1024px</code>)は画面中央に固定されているので、広い画面では左右に何もない余白ができる。ここに何か置けるはずだと考えた。</p>
<h2 id="まずはボタンだけ置いてみた">まずはボタンだけ置いてみた</h2>
<p>PaperModには <code>extend_footer.html</code> という空のフック(パーシャル)が用意されていて、上書きするだけでフッター末尾に任意のHTMLを差し込める。CSSも <code>assets/css/extended/*.css</code> に置いたファイルが自動で本体CSSに連結される仕組みがあるので、これを使って、画面右端に固定表示する丸ボタンを2つ置いてみた。「📅 日付別」は <code>/archives/</code> へ、「🏷️ タグ別」は <code>/tags/</code> へのリンクだけの、ただのショートカットだ。</p>
<h2 id="はてなブログを見て作り直すことにした">はてなブログを見て、作り直すことにした</h2>
<p>ボタンだけでは芸がない。はてなブログのサイドバーにあるような、実際にカレンダーの月表示が出て投稿日がハイライトされるもの、タグも名前と件数がその場に並ぶものにしたいと思った。</p>
<p>タグクラウドの方は簡単だった。PaperMod自身の <code>/tags/</code> ページ(<code>taxonomy.html</code>)が既に同じことをしていたので、そのロジックをそのまま借りた。</p>
<pre tabindex="0"><code class="language-gotemplate" data-lang="gotemplate">{{ range $tagsPage.Data.Terms.Alphabetical }}
  {{ with site.GetPage (printf &#34;/tags/%s&#34; .Name) }}
    &lt;a href=&#34;{{ .Permalink }}&#34;&gt;{{ .LinkTitle }} ({{ $count }})&lt;/a&gt;
  {{ end }}
{{ end }}
</code></pre><p>カレンダーは静的サイトジェネレータなので少し工夫が要る。ビルド時に、その言語の全記事の日付・タイトル・URLをJSONとしてページに埋め込み(<code>{{ $calItems | jsonify }}</code>)、バニラJSで月表示のグリッドを描画する方式にした。外部ライブラリは使っていない。</p>
<h2 id="カレンダーが何も表示されなかった">カレンダーが、何も表示されなかった</h2>
<p>書き終えてデプロイしたら、タグクラウドは正しく出るのに、カレンダー部分だけが空白だった。前後の月ボタン(‹ ›)だけが表示されて、日付のマス目もタイトルも出ない。</p>
<p>ブラウザの開発者ツールで実際のHTMLを見て気づいた。埋め込んだはずのJSONが、こうなっていた。</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-html" data-lang="html"><span class="line"><span class="cl"><span class="p">&lt;</span><span class="nt">script</span> <span class="na">type</span><span class="o">=</span><span class="s">&#34;application/json&#34;</span> <span class="na">id</span><span class="o">=</span><span class="s">&#34;quick-cal-data&#34;</span><span class="p">&gt;</span><span class="s2">&#34;[{\&#34;date\&#34;:\&#34;2026-08-21\&#34;,\&#34;...\&#34;}]&#34;</span><span class="p">&lt;/</span><span class="nt">script</span><span class="p">&gt;</span>
</span></span></code></pre></div><p>配列全体が、エスケープされた<strong>文字列として</strong>二重にクオートで包まれている。<code>JSON.parse()</code> にこれを渡すと、配列ではなく1本の文字列が返ってくる。直後の <code>.forEach()</code> はもちろん文字列には存在しないので、その場でエラーが起きてスクリプト全体が止まっていた。</p>
<p>原因はHugoのテンプレートエンジン(<code>html/template</code>)の仕様だった。<code>&lt;script&gt;</code> タグの中に値を差し込むと、Hugoは自動的にそれをJSの文字列リテラルとして安全にエスケープしようとする。<code>jsonify</code> が返すのはただの <code>string</code> 型なので、信頼できない値として扱われ、丸ごとクオートで包まれてしまっていた。</p>
<p>直したのは1箇所、<code>safeJS</code> を挟んで「このJSは安全だから素通ししてくれ」と明示するだけだった。</p>
<pre tabindex="0"><code class="language-gotemplate" data-lang="gotemplate">{{ $calItems | jsonify | safeJS }}
</code></pre><p>これでJSONがそのまま配列として埋め込まれるようになり、カレンダーが動くようになった。</p>
<h2 id="月別ボタンも追加した">月別ボタンも追加した</h2>
<p>カレンダーが動くようになったところで、月ごとにジャンプできるボタンも欲しくなった。前後の月を1つずつ辿るだけでは、記事が増えてきたときに不便になる。投稿のある年月を新しい順にリストアップし、クリックするとページ遷移なしでカレンダーがその月に切り替わる仕組みを、既存のカレンダーの状態管理に乗せる形で追加した。</p>
<p>つづく。</p>
]]></content:encoded></item><item><title>ブログを立てた勢いで、シングリッシュ用語集サイトも作った</title><link>https://offgrid.taikiito.com/posts/2026-08-22-singlish-lah/</link><pubDate>Sat, 22 Aug 2026 08:30:00 +0800</pubDate><guid>https://offgrid.taikiito.com/posts/2026-08-22-singlish-lah/</guid><description>ブログと同じ日に公開した小さいサイドプロジェクトの記録。ビルドツールなしの素のHTML/CSS/JSで作ったシングリッシュ用語集と、後の別プロジェクトでも使い回すことになるサブドメイン+リポジトリのパターンについて。</description><content:encoded><![CDATA[<p>前回、このブログの器を作った話を書いた。同じ日に、勢いでもう一つ小さいサイトを公開した。シンガポールの日常会話に出てくるシングリッシュ表現をまとめた、日本語話者向けの用語集だ。</p>
<h2 id="あえて違うスタックにした">あえて違うスタックにした</h2>
<p>ブログはHugoだが、こちらはビルドツールなしの素のHTML/CSS/JSにした。ポートフォリオとして公開するなら、同じスタックばかりより手数の幅を見せた方がいい、という考えからだ。データは <code>terms.json</code> に切り出し、検索とカテゴリ絞り込みのUIをその上に乗せた。公開時点で33語、5カテゴリ(定番表現、感情・リアクション、食・生活、人間関係・呼び方、仕事・現場で聞いた表現)。</p>
<p>内容は一般的によく知られたシングリッシュ表現に加えて、実際に自分が生活の中で聞いた表現もいくつか混ぜてある。今後も会話の中で拾ったものを <code>terms.json</code> に足していく運用にするつもりだ。</p>
<h2 id="サブドメインのパターンが確立した">サブドメインのパターンが確立した</h2>
<p>ドメインとリポジトリの組み方も、このタイミングで型として固まった。プロジェクトごとに専用のGitHubリポジトリを作り、<code>&lt;プロジェクト名&gt;.taikiito.com</code> のサブドメインをCloudflareのCNAMEで紐付ける。GitHub Pages側の自動HTTPS証明書発行を妨げないよう、CNAMEは必ずProxyなし(DNSのみ)にする、というのも今回学んだ点だった。Proxyを有効にしたままだと証明書の発行が遅れる、あるいは失敗する。</p>
<h2 id="一言添えるかどうかで迷った">一言添えるかどうかで迷った</h2>
<p>フッターの文言は2回書き直した。最初は「誰かの役に立てばいいなと思って作っています」という一文を入れていたが、書いてみて少し押しつけがましく感じ、「ニッチすぎて誰得だけど、自分用に作ったのでついでに公開してます」という自虐寄りの言い回しに変えた。それも見返して、結局どちらも要らないという結論になり、最終的には何も足さずに削除した。何を作ったかだけを書けば十分で、なぜ作ったかは読む人が決めることだ、と考え直した形だ。</p>
<p>つづく。</p>
]]></content:encoded></item><item><title>このブログを作ることにして、まずGitHubのユーザー名から変えた</title><link>https://offgrid.taikiito.com/posts/2026-08-22-building-this-blog/</link><pubDate>Sat, 22 Aug 2026 08:00:00 +0800</pubDate><guid>https://offgrid.taikiito.com/posts/2026-08-22-building-this-blog/</guid><description>ここまでの試行錯誤を公開の日記として書き残すことにした経緯。Hugo(i18n)+PaperMod、GitHub Actions経由のGitHub Pagesデプロイ、非公開ソースリポジトリから公開用リポジトリへforce pushする2リポジトリ構成、そしてついでに行ったGitHubユーザー名の変更まで。</description><content:encoded><![CDATA[<p>前回はRSSリーダーを作って同じ日に壊した話を書いた。同じ頃、もう少し大きい決断もしていた。ここまでやってきたことを、公開の場に書き残すことだ。今読んでいるこのブログ自体を、その日に立ち上げた。</p>
<h2 id="きっかけ">きっかけ</h2>
<p>中島聡さんのニュースレター経由で以前MulmoClaudeを知ったのと同じように、彼のニュースレターで「エンジニアを目指すなら、まず実物をGitHubに公開するといい」という趣旨のアドバイスを目にしていた。ドメイン運用やセルフホスティング、メールまわりで試行錯誤してきたことが、同じ問題にぶつかった誰かの検索結果に引っかかれば十分だと思い、公開することにした。</p>
<h2 id="形式は時系列の日記にした">形式は「時系列の日記」にした</h2>
<p>最初はテーマ別のハウツー記事にしようかと考えたが、途中で「実際に起きた順番のまま書く日記形式」に変更した。うまくいかなかったこと、迷ったこと、あとから直したことも含めて、そのまま時系列で残す方が実態に近い。</p>
<h2 id="スタックを決める">スタックを決める</h2>
<p>静的サイトジェネレータはHugoにした。決め手は多言語対応(i18n)が標準で入っていること。テーマはPaperMod。GitHub Actionsでビルドし、GitHub Pagesにデプロイ、独自ドメイン <code>blog.taikiito.com</code> をCloudflareのCNAMEで割り当てた。</p>
<p>リポジトリは2つに分けた。実際に編集するHugoのソース一式(<code>blog-source</code>)は非公開、ビルド後の静的ファイルだけを別リポジトリ(<code>blog</code>)にforce pushで公開する構成にしている。GitHub Actionsの1ジョブの中で、ビルド→出力先リポジトリへのpushまで完結させた。ソース側の編集履歴や下書きを外に出さずに、成果物だけを公開できるのが気に入っている。</p>
<h2 id="ついでにユーザー名も変えた">ついでにユーザー名も変えた</h2>
<p>GitHubのユーザー名も、このタイミングで変えた。もともと登録したときに深く考えず入れた生年月日由来の文字列が入っていて、前から気に入っていなかった。理想の名前は既に他の人に使われていたので、末尾に<code>-dev</code>を足した形に落ち着いた。公開リポジトリがまだ0個の状態だったので、切り替えのコストは低かった。</p>
<p>合わせて、ユーザー名と同じ名前のリポジトリを作ると自動でプロフィールページに表示される、GitHubのプロフィールREADME機能も使った。中身は最小限、2行だけ。</p>
<blockquote>
<p>Tinkering with computers on the side. Hoping it helps someone.</p>
</blockquote>
<p>つづく。</p>
]]></content:encoded></item><item><title>Minifluxで自分用のRSSリーダーを作って、同じ日に壊した</title><link>https://offgrid.taikiito.com/posts/2026-08-21-rss-reader-teardown/</link><pubDate>Fri, 21 Aug 2026 10:00:00 +0800</pubDate><guid>https://offgrid.taikiito.com/posts/2026-08-21-rss-reader-teardown/</guid><description>MulmoClaudeと同じサーバーにMinifluxを自前で立てて、ニュースをジャンル別に集約しようとした話。同じ日のうちに『情報過多になりそう』と判断してDocker一式を全部削除した。</description><content:encoded><![CDATA[<p>前回はメールの送信者評判の話で終わった。技術的に打てる手は尽きて、あとは時間待ちの状態になっていた。その待ち時間に、別のことを試してみることにした。ニュースやブログをまとめて読むための、自分専用のRSSリーダーだ。</p>
<h2 id="minifluxを選んだ理由">Minifluxを選んだ理由</h2>
<p>候補はいくつかあったが、軽量でセルフホストしやすいという理由でMinifluxに決めた。構成はDocker Compose1本で、Miniflux本体とPostgreSQLのコンテナを立てるだけ。アクセスはMulmoClaudeと同じパターンで、手前にCloudflare Accessの認証ゲートを置いた。</p>
<p>対象はSingapore Straits Times、NYT、その他いくつかのブログをジャンル別に分類する構想だった。TwitterやXは2023年以降無料のRSSフィードを廃止しているので、これは最初から対象外にした。</p>
<h2 id="フィードを繋いでみて分かったこと">フィードを繋いでみて分かったこと</h2>
<p>実際にフィードを購読してみると、いくつか想定外のことが分かった。</p>
<ul>
<li>NHKのフィードは8月8日付でサイレントにブロックされていた。理由の開示はなく、クラウド事業者のIPレンジ自体が対象になっている可能性が高い。</li>
<li>Straits TimesとNYTのフィード自体は生きていたが、どちらも本文はペイウォールの手前、見出しと要約までしか取得できない。</li>
</ul>
<p>技術的には一通り動く状態まで持っていけた。</p>
<h2 id="それでもやめることにした">それでも、やめることにした</h2>
<p>一通り動くようになったところで、少し立ち止まって考えた。ジャンルを分けて集約する、という構想自体は悪くないが、実際に運用したら未読が積み上がっていく「情報過多」の受け皿になるだけではないか、という懸念の方が強くなった。読む量を増やすためではなく、読む量をコントロールするために始めた話のはずだった。</p>
<p>結局、同じ日のうちにDockerコンテナ、nginxの設定、DNSレコード、Cloudflare Accessのアプリ設定まで、一式をすべて削除した。作ってから壊すまで、24時間もかかっていない。</p>
<p>技術的に動くところまで持っていくのと、実際に生活に組み込んで使い続けるのとは別の話だと分かった一件だった。</p>
<p>つづく。</p>
]]></content:encoded></item><item><title>SPF/DKIM/DMARCを正しく設定しても、メールが届かなかった話</title><link>https://offgrid.taikiito.com/posts/2026-08-21-mail-delivery-reputation/</link><pubDate>Fri, 21 Aug 2026 09:00:00 +0800</pubDate><guid>https://offgrid.taikiito.com/posts/2026-08-21-mail-delivery-reputation/</guid><description>SPF/DKIM/DMARCを教科書通り全部揃えても、それでも届かないメールがある。原因は設定ミスではなく、ドメインの送信履歴(センダーレピュテーション)だった。</description><content:encoded><![CDATA[<p>前回、<code>taikiito.com</code>でメールを使えるようにしたところまで書いた。SPF/DKIM/DMARCを設定し、テスト送信もしたが、一部の宛先に届かないという問題が残っていた。今回はその続き。</p>
<h2 id="dmarcを足してもまだ届かない">DMARCを足しても、まだ届かない</h2>
<p>前回の時点ではDMARCを設定していなかったので、まずそこを埋めた。</p>
<ul>
<li><strong>DMARC</strong>(TXT、<code>_dmarc.taikiito.com</code>): <code>v=DMARC1; p=none; rua=mailto:(レポート送付先)</code></li>
</ul>
<p>外部のDNSチェッカーで反映を確認し、伝播も待って再送してみたが、やはり届かなかった。バウンス(エラー返信)は一切なく、送信が失敗したのかどうかすら分からない状態だった。</p>
<p>SPF・DKIM・DMARCを教科書通り全部揃えてもこの状態だったので、原因は認証設定ではないと考えた。調べると、<strong>センダーレピュテーション</strong>(そのドメインがどれだけ送信履歴を積んで信頼されているか)の問題だと分かった。フィルタが厳しい受信サーバーほど強く効くらしい。SPF/DKIM/DMARCは「なりすましでないこと」の証明であって、「信頼できる送信元であること」の証明ではない。似ているが別物だ、というのをここで理解した。技術的に打てる手はなく、あとは時間をかけて送信履歴を積み上げるしかない。</p>
<h2 id="icloudのカスタムドメインメールで運用することにした">iCloud+のカスタムドメインメールで運用することにした</h2>
<p>土台はApple iCloud+の「カスタムメールドメイン」機能。必要なレコードはMX・SPF・DKIMの3つだけで、Appleの設定ウィザードが値をそのまま出してくれるので、ほぼコピペ作業だった。</p>
<ul>
<li><strong>MX</strong>: <code>mx01.mail.icloud.com</code> / <code>mx02.mail.icloud.com</code></li>
<li><strong>SPF</strong>(TXT): <code>v=spf1 include:icloud.com ~all</code></li>
<li><strong>DKIM</strong>(CNAME): <code>sig1._domainkey</code> → <code>sig1.dkim.taikiito.com.at.icloudmailadmin.com</code></li>
</ul>
<h2 id="fastmailと迷ったが思い込みが一つ崩れた">Fastmailと迷ったが、思い込みが一つ崩れた</h2>
<p>有名な対抗馬としてFastmailがある。料金以外の軸で比較してみた: 標準プロトコル対応、Apple以外の端末での使い勝手、ドメイン・エイリアスの上限(iCloud+はApple ID一つにつき数個、Fastmailはほぼ無制限+catch-all)、サーバー側のフィルタルール、送信ドメインとしての評判、データの持ち出しやすさ。</p>
<p>比較の途中で、自分の思い込みが一つ崩れた。iCloud MailはApple製品専用だとばかり思っていたが、実際には<code>imap.mail.me.com</code> / <code>smtp.mail.me.com</code>にアプリ専用パスワードでアクセスすれば、Thunderbirdのような普通のIMAP/SMTPクライアントからも使える。Apple Mailの「書き出す」機能で<code>.mbox</code>形式のバックアップも取れる。「ロックインされて出られなくなる」という一番の懸念は、調べてみたらほぼ杞憂だった。</p>
<p>結局、iPhoneのバックアップ用に元々iCloud+を契約しているので、カスタムドメインのメールボックスを足しても実質タダ(容量を共有するだけ)。ロックインの心配もほぼ無いと分かった以上、今Fastmailに乗り換える動機はない。Apple以外の端末がメインになるとか、エイリアスを大量に使うようになったら、その時また考える。</p>
<p>つづく。</p>
]]></content:encoded></item><item><title>taikiito.comの専用サブドメインに、静的HTML1枚のデジタル名刺を作った</title><link>https://offgrid.taikiito.com/posts/2026-08-19-digital-business-card/</link><pubDate>Wed, 19 Aug 2026 10:00:00 +0800</pubDate><guid>https://offgrid.taikiito.com/posts/2026-08-19-digital-business-card/</guid><description>ドメインが使えるようになった勢いで作った、静的HTML1枚だけのデジタル名刺の記録。vCardダウンロードとWhatsAppのクリック・トゥ・チャットボタン、そしてCloudflareのメールアドレス難読化にハマった小話。</description><content:encoded><![CDATA[<p>前回、<code>taikiito.com</code> というドメインを確保し、自分のパソコンからブラウザ経由で自分のMulmoClaudeにアクセスできるところまで整えた。せっかくドメインの下にサブドメインを作れる状態になったので、その日のうちにもう一つ別のものを作ることにした。デジタル名刺だ。</p>
<h2 id="サブドメインを1つ切って静的ページだけを置く">サブドメインを1つ切って、静的ページだけを置く</h2>
<p>taikiito.comの下に専用のサブドメインを新しく作り、Lightsailの同じサーバー上にnginxのserver blockをもう1つ追加する形で対応した(実際の名刺ページには自分の連絡先が載っているので、このブログではURLは伏せる)。ポイントは、MulmoClaude本体のNode.jsアプリとは完全に切り離したこと。<code>proxy_pass</code> は使わず、ただの静的HTMLファイル1枚を配信するだけの設定にしたので、名刺ページのアクセスがMulmoClaude本体の負荷に影響することはない。</p>
<p>中身は実際に普段使っている名刺の情報をそのまま移した。ボタンは2つだけに絞った。</p>
<ul>
<li>「連絡先に保存」— クリックするとJSのBlobで <code>.vcf</code> ファイルをその場で生成し、スマホの連絡先追加シートを開く</li>
<li>「WhatsApp」— <code>wa.me</code> のクリック・トゥ・チャットのディープリンクを踏むだけ</li>
</ul>
<p>最初はメール送信ボタンも用意していたが、後で削って情報行(<code>mailto:</code>リンク)だけに縮小した。ボタンは絞れるだけ絞った方が結局使いやすい。</p>
<h2 id="色で少し迷った">色で少し迷った</h2>
<p>配色は最初からネイビーで作っていたが、比較のために白黒版も一度作ってみた。並べて見て、結局ネイビーの方が良いという結論に落ち着き、白黒版は削除した。</p>
<h2 id="地味なハマりどころ">地味なハマりどころ</h2>
<p>公開直後、このアプリ自体に埋め込んだプレビュー用のiframe内でカードを確認したところ、メールアドレスの表示が記号だけの謎の文字列になっていた。最初は自分のミスかと疑ったが、正体はCloudflareの「メールアドレス難読化」機能(Scrape Shield)だった。ページ内のメールアドレスをスパム避けのため自動でエンコードして表示する仕組みで、通常のブラウザではJSが実際のアドレスに戻して表示するが、このアプリのプレビュー用iframe内ではそのデコード用スクリプトが動かなかった、というだけの話だった。ゾーン全体の設定なので個別にOFFにはせず、実際のブラウザでは問題なく表示されることを確認して終わりにした。</p>
<p>自分の名前をドメインにしたことで、こういう小さいサブドメインを次々に切れるようになった。この後もいくつか同じパターンで増やしていくことになる。</p>
<p>つづく。</p>
]]></content:encoded></item><item><title>taikiito.comを本格運用にして、ハマった3つの問題と直し方</title><link>https://offgrid.taikiito.com/posts/2026-08-19-domain-and-email/</link><pubDate>Wed, 19 Aug 2026 09:00:00 +0800</pubDate><guid>https://offgrid.taikiito.com/posts/2026-08-19-domain-and-email/</guid><description>自分の名前のドメインを実際に使える状態に仕上げていく中で見つかった、Cloudflare Accessを回避できる抜け穴、522エラー、SPF/DKIM/DMARCを正しく設定してもメールが届かなかった話。</description><content:encoded><![CDATA[<p>前回、<code>taikiito.com</code>という自分の名前のドメインを確保したところまで書いた。今回は、そのドメインを実際に使える状態に仕上げていく話。</p>
<h2 id="cloudflare-accessをバイパスできる抜け穴があった">Cloudflare Accessをバイパスできる抜け穴があった</h2>
<p>アクセス周りの設定を見直している最中、ドメインではなくLightsailのオリジンIPに直接アクセスしてみたところ、普通に開けてしまうことに気づいた。</p>
<p>ドメイン経由のアクセスにはCloudflare Accessによる認証ゲートを設けていたが、オリジンIPに直接アクセスすればそのゲートを完全にバイパスできる状態になっていた。</p>
<p>同日中に <code>ufw</code> を導入し、inbound の80/443番ポートを <strong>Cloudflareの公開IPレンジ(<code>https://www.cloudflare.com/ips-v4</code>)からのみ許可</strong>、それ以外はデフォルトで拒否する設定にした。SSH(22番)は明示的に <code>ufw allow OpenSSH</code> で許可を先に入れてから有効化した(この順番を間違えると、ufw有効化と同時にSSH接続自体が失われるリスクがある)。適用後、ドメイン経由のアクセスは変わらず機能し、オリジンIP直接アクセスは接続不可になった。</p>
<h2 id="2つ目のオリジンを追加したときの522エラー">2つ目のオリジンを追加したときの522エラー</h2>
<p>このドメインの下に、MulmoClaude用の2つ目の入り口と、デジタル名刺用のサブドメインを追加した(どちらも実在のURLはここには書かない。前者はCloudflare Access越しとはいえサブドメイン名自体を晒したくないし、後者は名刺ページに連絡先が載っている)。</p>
<p>2つ目の入り口のDNS(Aレコード)とCloudflare Accessのポリシーを設定した直後、アクセスするとCloudflareの <strong>522エラー(オリジンへの接続タイムアウト)</strong> が出た。原因は、SSL/TLSの暗号化モードがゾーン(ドメイン)ごとに個別管理されていること。新しい <code>taikiito.com</code> ゾーンはデフォルトの <strong>Full</strong> モードになっていて、Cloudflareがオリジンの443番ポートへHTTPS接続を試みるが、nginx側は80番しか listen していなかった。既存のドメインと同じ <strong>Flexible</strong> モードに変更したら、即座に解消した。</p>
<h2 id="spfdkimdmarcを設定してメールも作った">SPF/DKIM/DMARCを設定してメールも作った</h2>
<p>ドメインを持ったので、iCloud+の <strong>Custom Email Domain</strong> 機能を使って <code>me@taikiito.com</code> を作った。追加契約は不要で、案内された通りにDNSレコードを3つ登録するだけだった。</p>
<ul>
<li><strong>MX</strong>: <code>mx01.mail.icloud.com</code> / <code>mx02.mail.icloud.com</code></li>
<li><strong>SPF</strong>(TXT): <code>v=spf1 include:icloud.com ~all</code></li>
<li><strong>DKIM</strong>(CNAME): <code>sig1._domainkey</code> → <code>sig1.dkim.taikiito.com.at.icloudmailadmin.com</code></li>
</ul>
<p>これらは「このメールは、なりすましではなく本当に自分のドメインから送られている」ことを証明する仕組みで、値をコピーして貼り付けるだけの単純作業だった。</p>
<h2 id="一部の宛先にだけ届かない">一部の宛先にだけ届かない</h2>
<p>設定は正しく完了したはずだったが、試しに別のメールアドレス宛に送ってみたところ、そちらには届かなかった。バウンス(エラー返信)は一切なかった。</p>
<p>念のため <strong>DMARC</strong>(TXT、<code>v=DMARC1; p=none; rua=mailto:(レポート送付先)</code>)も追加し、外部のDNSチェッカーで正しく反映されていることも確認した。それでも届かない。原因は、SPF/DKIM/DMARCがすべて正しくても解決できない、ドメインの送信履歴(センダーレピュテーション)の問題だった。フィルタが厳しいメールサーバーほど、この影響を強く受けるらしい。</p>
<p>技術的に打てる手は尽きたので、これは時間が解決してくれるのを待つことにした。</p>
<p>つづく。</p>
]]></content:encoded></item><item><title>taikiito.comという自分の名前のドメインを、空いているうちに確保した</title><link>https://offgrid.taikiito.com/posts/2026-08-19-domain-motivation/</link><pubDate>Wed, 19 Aug 2026 08:00:00 +0800</pubDate><guid>https://offgrid.taikiito.com/posts/2026-08-19-domain-motivation/</guid><description>自分の名前がそのままドメインとして空いていることに気づいた。名前をドメインにする人たちのパターンを見比べて、今のうちに確保しておくことにした。</description><content:encoded><![CDATA[<p>サーバーを常時稼働にして(前回参照)、取れそうな名前を適当に選んだだけのドメインでアクセスできるようにはなっていた。ただこれは、自分の名前が入っているわけではなかった。</p>
<h2 id="自分の名前がまだ空いていた">自分の名前が、まだ空いていた</h2>
<p>ある日、自分の名前をそのままドメインにできないか調べてみた。<code>taikiito.com</code> — 空いていた。特にひねりのない、名前をつなげただけのドメインが、まだ誰にも取られていなかった。</p>
<p>自分の名前をドメインにしている人たちのパターンをいくつか見比べてみた。苗字だけを使う人、下の名前と苗字をそのまま一続きにする人、ニックネームをブランド化して実体は別サイトにリダイレクトさせている人。ハッカー文化だと、本名と切り離した完全に別のハンドルを使うことも多い。</p>
<p>自分の名前を看板にするなら、変にひねらず、名前をそのまま素直に繋げる形が一番しっくりきた。苗字を先にする(<code>itotaiki.com</code>)か、ハイフンを入れる(<code>taiki-ito.com</code>)かも迷ったが、結局一番シンプルな <code>taikiito.com</code> に決めた。</p>
<h2 id="今のうちに取っておくことにした理由">今のうちに取っておくことにした理由</h2>
<p>ドメインは取りたてのうちは、ネットワーク製品の自動フィルタなどから「作られたばかりで分類のない(uncategorized)ドメイン」として警戒されやすい、と聞いたことがあった。これを回避する方法は基本的になく、ドメインの年齢とアクセス・送信履歴が積み上がって「信頼できるカテゴリ」に分類されるのを待つしかないらしい。</p>
<p>だったら、本格的に使う予定が固まっていない今のうちに取っておいて、気長に信頼が育つのを待てばいい。そう考えて、Cloudflare Registrarで <code>taikiito.com</code> を取得した(実費のみ、WHOISプライバシー込み)。<code>.jp</code> や <code>.net</code> も保険で押さえておくか迷ったが、<code>.com</code> 一本で十分だと判断した。</p>
<p>つづく。</p>
]]></content:encoded></item><item><title>MulmoClaudeを常時稼働にしたら、裏側の自動処理が無限ループしていた</title><link>https://offgrid.taikiito.com/posts/2026-08-17-journal-loop-bug/</link><pubDate>Mon, 17 Aug 2026 09:00:00 +0800</pubDate><guid>https://offgrid.taikiito.com/posts/2026-08-17-journal-loop-bug/</guid><description>常時稼働サーバーに移行した翌日、バックグラウンドの自動処理(journal機能)が特定セッションを延々とリトライし続けるバグを追いかけた記録。原因はPythonとNode.js間のミリ秒精度のズレだった。</description><content:encoded><![CDATA[<p>前回、MulmoClaudeをAWS Lightsailの常時稼働サーバーに移行したところまで書いた。移行自体は終わったが、次の日、今度は裏側の自動処理がおかしくなっていることに気づいた。</p>
<h2 id="何が起きていたか">何が起きていたか</h2>
<p>MulmoClaudeには、会話ログから日々の要約を自動生成する <code>journal</code> という裏側の機能がある。この処理が、毎時間動くたびに異常に時間がかかるようになっていた。1回あたり25〜90秒かかっている回もあった。裏で何度もリトライが走っている感触があり、消費されるトークン量も明らかに増えていた。</p>
<h2 id="原因を追う">原因を追う</h2>
<p>調べていくと、直前の移行作業(前回の記事参照)で発生した大量のセッション(625個)が、未処理のまま残留していることが分かった。毎時間動く自動処理は、この残留分を毎回すべて処理しようとしてリトライを繰り返していた。処理が終わらない→次の時間にまた同じ分をやり直す、という無限ループに近い状態になっていた。</p>
<p>さらに掘ると、根っこにはもう一段深い原因があった。処理済みかどうかを判定するタイムスタンプの比較で、Python側とNode.js側でミリ秒の扱いが微妙に食い違っていた。片方は切り捨て、もう片方は四捨五入、というようなズレがあり、本来「処理済み」と判定されるべきセッションが毎回「未処理」に見えてしまっていた。何度か修正を試したが、このズレそのものを直さない限り再発する状態だった。</p>
<h2 id="応急処置と本修正">応急処置と本修正</h2>
<p>まず <code>journal</code> 機能そのものを一時的にOFFにして、それ以上の消費を止めた。そのうえで、タイムスタンプ精度の扱いを両言語で揃える形に修正。修正後、<code>daysSkipped: 0</code> で毎時処理の所要時間が2〜3ミリ秒まで落ちたのを確認できた。</p>
<p>設定も見直して、<code>journal</code> は日次1回(<code>dailyIntervalHours: 24</code>)に固定。毎時のスケジューラーチェック自体は軽い処理だけに留めるようにした。</p>
<p>常時稼働は、アプリ本体だけでなく裏側の自動処理まで含めて「壊れない」ようにする話だと分かった。表から見えない場所ほど、地味にコストがかかっていたりする。</p>
<p>つづく。</p>
]]></content:encoded></item><item><title>MacBookからAWS Lightsailへ移行して、ハマった3つの問題と直し方</title><link>https://offgrid.taikiito.com/posts/2026-08-16-moving-to-a-server/</link><pubDate>Sun, 16 Aug 2026 09:00:00 +0800</pubDate><guid>https://offgrid.taikiito.com/posts/2026-08-16-moving-to-a-server/</guid><description>MulmoClaudeをAWS Lightsailに移行した記録。CSRFのtrusted originエラー、Docker Desktop/Docker Engineのネットワークの違いによる原因不明のエラー、メモリ増設と縮小の顛末まで。</description><content:encoded><![CDATA[<p>MulmoClaudeはそれまでローカルのMacBookで動かしていた。ノートを閉じたら止まる、持ち歩いたら止まる。外出先からもちゃんとしたWeb UIで使いたかったのと、Telegram bot運用は複数スレッドの並行やMarkdown表示のあたりで限界を感じていたので、常時稼働のサーバーに移すことにした。</p>
<h2 id="構成を決めるまで">構成を決めるまで</h2>
<p>自分のマシンから直接SSHで繋ぐ案は、会社のネットワークがoutboundのポート22を塞いでいることが多いので早々に除外した。代わりに、ドメイン経由でHTTPSアクセスし、手前にCloudflare Accessで認証ゲートを置く構成にした。ログインはGoogleアカウント、加えてアプリ側の共有シークレットでも二重にロックしている。</p>
<p>クラウドはさくらのクラウドとAWS Lightsailで迷ったが、Singaporeリージョンのレイテンシを優先してLightsailにした。最初は$7/月(1GB RAM、2 vCPU)の一番小さいバンドルから始め、足りなければ後で上げる方針にした。</p>
<h2 id="詰まったところ">詰まったところ</h2>
<p><strong>1. 送信しても無反応になる(CSRF)</strong></p>
<p>移行作業自体は終わったのに、ブラウザからメッセージを送っても何も起きない。エラーも出ない。原因は、アプリ内蔵のCSRFガードが<code>Origin</code>ヘッダーをチェックしていて、新しいドメインを<code>.env</code>の<code>MULMOCLAUDE_TRUSTED_ORIGINS</code>に追加し忘れていたことだった。自分の公開ドメインをここに足して<code>systemctl restart</code>したら直った。同じようにセルフホストしていて、公開ドメインからの送信だけ無言で失敗する場合は、まずここを疑うといい。</p>
<p><strong>2. 内部API呼び出しが断続的に失敗する</strong></p>
<p>数日後、<code>presentForm</code>や<code>manageCollection</code>など内部のMCPブリッジ呼び出しが「Network error calling &hellip;: fetch failed」で落ちるようになった。最初はメモリ不足を疑い、zram-toolsで圧縮swapを作ったり、Lightsailのプランを$7/月(1GB)から$12/月(2GB)に上げたりしたが、症状は変わらなかった。</p>
<p>結局、アプリのソース(<code>server/index.ts</code>)を読んで根本原因が分かった。アプリは<code>127.0.0.1</code>にしかbindしない設計になっており、Mac(Docker Desktop)ではこれがhypervisor越しにloopbackへ届くのに対し、Linux(Docker Engine)では単に<code>docker0</code>ブリッジの実IP(このケースでは<code>172.17.0.1</code>)に解決されるため、接続を受け付けていなかった。Macでは表面化しなかった設計上の穴が、Linux移行で初めて出てきた形だ。修正は、<code>socat</code>でブリッジ側からloopbackへTCPを1本中継するだけで済んだ。</p>
<pre tabindex="0"><code>ExecStart=/usr/bin/socat TCP-LISTEN:3001,bind=172.17.0.1,fork,reuseaddr TCP:127.0.0.1:3001
</code></pre><p>これをsystemdサービスとして登録したら、<code>curl http://host.docker.internal:3001/</code>が200を返すようになり、機能もすぐに正常化した。メモリ・zramの対応が無駄だったわけではないが(実際に空きは増えた)、このエラーの直接原因ではなかった。</p>
<p><strong>3. プランを下げようとしたら作り直しになった</strong></p>
<p>原因判明後、節約のため$7/月(1GB)に戻そうとしたが、Lightsailはスナップショット経由でのプラン縮小に対応しておらず、1GBインスタンスを作り直す必要があった。作り直したインスタンスは、Dockerのサンドボックスイメージビルドのような負荷時にたびたびハングし、SSHもブラウザコンソールも反応しなくなることが複数回あった。このワークロード(本体+Dockerサンドボックス+Telegram bridge)は1GBでは支えきれないと判断し、$12/月(2GB)を最終プランとして確定した。</p>
<p>いくつかのつまずきを経て、今はこのサーバーが24時間動き続けている。</p>
]]></content:encoded></item><item><title>MulmoClaudeをMacBookに導入して、個人用AIアシスタント環境を作った</title><link>https://offgrid.taikiito.com/posts/2026-07-25-mulmoclaude-genesis/</link><pubDate>Sat, 25 Jul 2026 09:00:00 +0800</pubDate><guid>https://offgrid.taikiito.com/posts/2026-07-25-mulmoclaude-genesis/</guid><description>サラリーマンをしながら趣味でパソコンをいじっている自分が、個人用のAIアシスタント環境を一から構築し、実際に使える状態にするまでの記録。ERR_MODULE_NOT_FOUNDのつまずきも含む。</description><content:encoded><![CDATA[<p>サラリーマンをしながら、趣味でパソコンをいじっている。エンジニアではないが、このブログでは、そのなかでやったことを起きた順番のまま記録していく。</p>
<p>まずは、そもそもの始まりの話から書く。</p>
<h2 id="きっかけ">きっかけ</h2>
<p>中島聡さんのニュースレターで「MulmoClaude」というものを知った。ChatGPTやClaudeのようなAIアシスタントを、SaaSとしてではなく自分のマシン上でセルフホストできるOSSプロジェクトで、会話履歴や記憶させた情報が全部ローカルのファイルとして自分の手元に残る、という点に惹かれた。試しに自分のMacBookに導入してみることにした。環境は Node.js v24.18.0、git 2.50.1、Claude Code CLI v2.1.220。</p>
<h2 id="最初のつまずき-err_module_not_found">最初のつまずき: <code>ERR_MODULE_NOT_FOUND</code></h2>
<p>導入方法はいくつかあり、最初は <code>receptron/mulmoclaude</code> を <code>git clone</code> する開発者向けの方法を選んだ。<code>yarn install</code> して <code>yarn dev</code> で起動しようとしたところ、<code>ERR_MODULE_NOT_FOUND</code> のビルドエラーで起動しなかった。原因は、パッケージ群のビルドが済んでいない状態で <code>dev</code> を起動しようとしていたこと。<code>yarn build:packages:dev</code> を先に実行してからだと、あっさり起動した。同じエラーで詰まっている人がいたら、まずこの順番を疑ってみてほしい。</p>
<p>動くようにはなったが、調べると公式が推奨しているのは <code>npx mulmoclaude@latest</code> というもっとシンプルな方法だった。ビルドの手間もgit cloneの管理も不要で、同じ日のうちに乗り換えた。ポートも <code>git clone</code>/<code>yarn dev</code> 版の <code>5173</code> から、npx版の <code>3001</code> に変わった。今もこのnpx版をベースに使い続けている。最初からこちらを選んでいれば、<code>ERR_MODULE_NOT_FOUND</code> にも遭遇しなかったはずだ。</p>
<h2 id="実際に使える環境に仕上げる">実際に使える環境に仕上げる</h2>
<p>動くようになってからは、日常的に使えるように何点か手を加えた。まず、パソコンの前にいないときにも使いたかったので、Telegramのbot(<code>taiki_mulmoclaude_bot</code>)をBotFather経由で作成し、公式のTelegram Bot APIの手順どおりに連携させた。外出先からでもチャットできるのは地味に便利だった。</p>
<p>あわせて、それまで公式のClaudeアプリで会話してきた蓄積(仕事の話、住んでいる場所、興味関心、日々の記録など)を、この新しい環境の「記憶」(ローカルのMarkdownファイルとして蓄積される仕組み)として移行した。ゼロから育てるのではなく、これまでの会話の続きから始められたのは大きかった。</p>
<p>最後に、ローカル環境が壊れたら全部消える、というのは避けたかったので、早い段階でGitHubにprivateリポジトリを作り、<code>git push</code> による定期的な自動バックアップの仕組みを整えた。この習慣は今も続いていて、後々サーバー移行を決める安心材料にもなった。</p>
<p>ここまでで、パソコンの中にMulmoClaudeを構築し、Telegram経由でも使えて、データもGitHubで守られている状態になった。</p>
<p>つづく。</p>
]]></content:encoded></item></channel></rss>