<?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 怠惰技術ブログ</title>
    <link>https://techblog.wasutech.dev/categories/%E3%82%A4%E3%83%B3%E3%83%95%E3%83%A9/</link>
    <description>Recent content in インフラ on 怠惰技術ブログ</description>
    <generator>Hugo -- 0.147.7</generator>
    <language>ja</language>
    <lastBuildDate>Wed, 06 May 2026 18:00:00 +0900</lastBuildDate>
    <atom:link href="https://techblog.wasutech.dev/categories/%E3%82%A4%E3%83%B3%E3%83%95%E3%83%A9/index.xml" rel="self" type="application/rss+xml" />
    <item>
      <title>Raspberry Pi 2台構成のWiFi APでSSIDが起動時に出ない問題を解決した</title>
      <link>https://techblog.wasutech.dev/posts/raspi-hostapd-nw-fix/</link>
      <pubDate>Wed, 06 May 2026 18:00:00 +0900</pubDate>
      <guid>https://techblog.wasutech.dev/posts/raspi-hostapd-nw-fix/</guid>
      <description>Raspberry Pi 2台でESS構成（同一SSID・別チャンネル）のWiFi APを組んだところ、OS起動時にSSIDが出ない問題が発生。NetworkManagerとhostapdの起動順序が原因だったのでsystemdのoverride.confで解決した。</description>
    </item>
    <item>
      <title>自宅NWをKeepalivedとdnsmasqで作り直した話</title>
      <link>https://techblog.wasutech.dev/posts/home-nw-redundancy/</link>
      <pubDate>Sat, 18 Apr 2026 19:00:00 +0900</pubDate>
      <guid>https://techblog.wasutech.dev/posts/home-nw-redundancy/</guid>
      <description>&lt;h2 id=&#34;背景&#34;&gt;背景&lt;/h2&gt;
&lt;p&gt;自宅のネットワーク構成が辛かった。&lt;/p&gt;
&lt;p&gt;具体的には以下の問題があった。&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;ルータが1台構成で、死んだら自宅NW全滅&lt;/li&gt;
&lt;li&gt;メンテナンスで定期的に落としたいとかあっても、コストが大きい&lt;/li&gt;
&lt;li&gt;DNS/DHCPがルータに同居していて、役割が混在している&lt;/li&gt;
&lt;li&gt;WireGuardで外部接続していたが、外部からの入り方がだるい。&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;特に「冗長化したい」という気持ちは何年も前からあったが、DNSとDHCPの同期という難問の前に何度も挫折してきた。&lt;/p&gt;
&lt;p&gt;結論からいえば、DNS,DHCPの冗長化は諦めた。&lt;/p&gt;
&lt;p&gt;諦めた上で出口のルータとDNS、DHCPは役割を分けた上で出口のルータとWIFIルータのみ冗長化を実施した。&lt;/p&gt;
&lt;h2 id=&#34;設計方針&#34;&gt;設計方針&lt;/h2&gt;
&lt;p&gt;過去の失敗から学んだ一番の教訓はやはり「DNS/DHCPの冗長化は難しい」ということだ。&lt;/p&gt;
&lt;p&gt;理論上可能ではあるが、私は万年初級自宅インフラエンジニアでいつまで経ってもできる目処が立たない。&lt;/p&gt;
&lt;p&gt;それでいて、生成AIという過ぎた兵器を持ってしても、この問題は解決しなかったので、難しいというよりは不可能とした。&lt;/p&gt;
&lt;p&gt;2台のサーバでDHCPを冗長化しようとすると、リースの同期問題が必ず発生する。同期ツールを使ったりしたが、どうしても同期しきれなかった。&lt;/p&gt;
&lt;p&gt;そこで今回は思い切って諦めた上で役割を分離する方針にした。&lt;/p&gt;
&lt;pre tabindex=&#34;0&#34;&gt;&lt;code&gt;ルータ（冗長化） → GWとTailscaleだけ担当
AP             → hostapdのみ
DNS/DHCP       → 専用機1台に集約（冗長化しない）
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;DNSとDHCPは単一障害点になるが、自宅ラボであれば許容範囲だと判断した。&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;てかどうしようもない。&lt;/strong&gt;&lt;/p&gt;
&lt;h2 id=&#34;構成&#34;&gt;構成&lt;/h2&gt;
&lt;p&gt;使用機材はすべてRaspberry Pi。封印していたタワーからも引っ張り出した。&lt;/p&gt;
&lt;table&gt;
  &lt;thead&gt;
      &lt;tr&gt;
          &lt;th&gt;ホスト&lt;/th&gt;
          &lt;th&gt;役割&lt;/th&gt;
      &lt;/tr&gt;
  &lt;/thead&gt;
  &lt;tbody&gt;
      &lt;tr&gt;
          &lt;td&gt;ルータA (MASTER)&lt;/td&gt;
          &lt;td&gt;Keepalived + Tailscale&lt;/td&gt;
      &lt;/tr&gt;
      &lt;tr&gt;
          &lt;td&gt;ルータB (BACKUP)&lt;/td&gt;
          &lt;td&gt;Keepalived + Tailscale&lt;/td&gt;
      &lt;/tr&gt;
      &lt;tr&gt;
          &lt;td&gt;DNS/DHCP機&lt;/td&gt;
          &lt;td&gt;dnsmasq専用&lt;/td&gt;
      &lt;/tr&gt;
      &lt;tr&gt;
          &lt;td&gt;AP x2&lt;/td&gt;
          &lt;td&gt;WIFI冗長化&lt;/td&gt;
      &lt;/tr&gt;
  &lt;/tbody&gt;
&lt;/table&gt;
&lt;p&gt;ネットワークセグメントはひとつ。VIPをデフォルトゲートウェイ兼DNSとして各クライアントに配布する。&lt;/p&gt;
&lt;h2 id=&#34;keepalivedの設定&#34;&gt;Keepalivedの設定&lt;/h2&gt;
&lt;p&gt;まずルータ2台にKeepalivedを入れる。&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;&#34;&gt;&lt;code class=&#34;language-bash&#34; data-lang=&#34;bash&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;sudo apt install -y keepalived
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;MASTER側の設定:&lt;/p&gt;
&lt;pre tabindex=&#34;0&#34;&gt;&lt;code&gt;vrrp_instance VI_1 {
    state MASTER
    interface lan-if
    virtual_router_id 51
    priority 100
    advert_int 1
    authentication {
        auth_type PASS
        auth_pass xxxxxxxx
    }
    virtual_ipaddress {
        192.168.xx.1/24
    }
}
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;BACKUP側は &lt;code&gt;state BACKUP&lt;/code&gt; と &lt;code&gt;priority 90&lt;/code&gt; に変えるだけ。シンプルだ。&lt;/p&gt;</description>
    </item>
    <item>
      <title>ドメイン購入後のやり残し作業 - メールとDNS設定編</title>
      <link>https://techblog.wasutech.dev/posts/cloudflare-dns-setup/</link>
      <pubDate>Tue, 14 Apr 2026 22:00:00 +0900</pubDate>
      <guid>https://techblog.wasutech.dev/posts/cloudflare-dns-setup/</guid>
      <description>&lt;p&gt;wasutech.dev を公開したはいいが、メール周りを何もしていなかった。放置するとなりすましに悪用される可能性があるので、Cloudflare Email Routing・SPF・DMARCを設定した。&lt;/p&gt;
&lt;h2 id=&#34;cloudflare-email-routing&#34;&gt;Cloudflare Email Routing&lt;/h2&gt;
&lt;p&gt;自前のメールサーバーを建てるのはコストがかかる。Cloudflareには &lt;strong&gt;Email Routing&lt;/strong&gt; という機能があり、&lt;code&gt;@wasutech.dev&lt;/code&gt; 宛のメールを既存のGmailなどに転送できる。無料。&lt;/p&gt;
&lt;p&gt;仕組みとしては、Cloudflareが自動でMXレコードを追加し、受信したメールを指定のアドレスへ転送する。&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;&#34;&gt;&lt;code class=&#34;language-bash&#34; data-lang=&#34;bash&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;dig MX wasutech.dev
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#75715e&#34;&gt;# → isaac.mx.cloudflare.net 等が返ってくる&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;MXレコードはドメインレベルの情報なので、転送先のメールアドレスは外部に公開されない。&lt;code&gt;dig&lt;/code&gt; で見えるのはCloudflareのサーバーだけで、「どこに転送しているか」は誰にもわからない。&lt;/p&gt;
&lt;p&gt;設定はCloudflareのダッシュボードから数クリックで完了する。&lt;/p&gt;
&lt;p&gt;転送先のアクションは3種類から選べる。&lt;/p&gt;
&lt;table&gt;
  &lt;thead&gt;
      &lt;tr&gt;
          &lt;th&gt;アクション&lt;/th&gt;
          &lt;th&gt;動作&lt;/th&gt;
      &lt;/tr&gt;
  &lt;/thead&gt;
  &lt;tbody&gt;
      &lt;tr&gt;
          &lt;td&gt;Forward to email&lt;/td&gt;
          &lt;td&gt;指定のメールアドレスへ転送&lt;/td&gt;
      &lt;/tr&gt;
      &lt;tr&gt;
          &lt;td&gt;Drop&lt;/td&gt;
          &lt;td&gt;受信して即破棄。転送しない&lt;/td&gt;
      &lt;/tr&gt;
      &lt;tr&gt;
          &lt;td&gt;Send to Worker&lt;/td&gt;
          &lt;td&gt;Cloudflare Workersで独自処理&lt;/td&gt;
      &lt;/tr&gt;
  &lt;/tbody&gt;
&lt;/table&gt;
&lt;p&gt;今回はメールを受け取る必要がないため &lt;strong&gt;Drop&lt;/strong&gt; に設定した。スパム対策にもなるし、転送先アドレスを管理する必要もない。Workers連携は受信メールをSlackに流したり、自動返信を実装したりする場合に使う。&lt;/p&gt;
&lt;h2 id=&#34;spf---送信元を証明するレコード&#34;&gt;SPF - 送信元を証明するレコード&lt;/h2&gt;
&lt;h3 id=&#34;spfとは&#34;&gt;SPFとは&lt;/h3&gt;
&lt;p&gt;&lt;strong&gt;SPF（Sender Policy Framework）&lt;/strong&gt; は、「このドメインからメールを送信していいサーバーはどこか」を定義するDNSレコード。&lt;/p&gt;
&lt;p&gt;なぜ必要か。メールのプロトコル（SMTP）は設計上、送信元アドレスを自由に詐称できる。つまり誰でも &lt;code&gt;admin@wasutech.dev&lt;/code&gt; を名乗ってメールを送れる。SPFはこれを受信側が検証できるようにする仕組み。&lt;/p&gt;
&lt;p&gt;受信側のメールサーバーは、届いたメールの送信元IPアドレスを確認し、そのドメインのSPFレコードに記載されたIPと照合する。一致しなければ怪しいメールとして処理できる。&lt;/p&gt;
&lt;h3 id=&#34;設定したレコード&#34;&gt;設定したレコード&lt;/h3&gt;
&lt;pre tabindex=&#34;0&#34;&gt;&lt;code&gt;v=spf1 include:_spf.mx.cloudflare.net ~all
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;各要素の意味：&lt;/p&gt;
&lt;table&gt;
  &lt;thead&gt;
      &lt;tr&gt;
          &lt;th&gt;要素&lt;/th&gt;
          &lt;th&gt;意味&lt;/th&gt;
      &lt;/tr&gt;
  &lt;/thead&gt;
  &lt;tbody&gt;
      &lt;tr&gt;
          &lt;td&gt;&lt;code&gt;v=spf1&lt;/code&gt;&lt;/td&gt;
          &lt;td&gt;SPFバージョン1&lt;/td&gt;
      &lt;/tr&gt;
      &lt;tr&gt;
          &lt;td&gt;&lt;code&gt;include:_spf.mx.cloudflare.net&lt;/code&gt;&lt;/td&gt;
          &lt;td&gt;CloudflareのメールサーバーからのSMTPを許可&lt;/td&gt;
      &lt;/tr&gt;
      &lt;tr&gt;
          &lt;td&gt;&lt;code&gt;~all&lt;/code&gt;&lt;/td&gt;
          &lt;td&gt;上記以外は「疑わしい」扱い（ソフトフェイル）&lt;/td&gt;
      &lt;/tr&gt;
  &lt;/tbody&gt;
&lt;/table&gt;
&lt;p&gt;&lt;code&gt;~all&lt;/code&gt; は疑わしいメールを迷惑メール扱いにする。&lt;code&gt;-all&lt;/code&gt; にすると完全拒否になるが、DMARCと組み合わせて制御するのが一般的。&lt;/p&gt;
&lt;p&gt;確認：&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;&#34;&gt;&lt;code class=&#34;language-bash&#34; data-lang=&#34;bash&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;dig TXT wasutech.dev
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#75715e&#34;&gt;# v=spf1 include:_spf.mx.cloudflare.net ~all&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;h2 id=&#34;dmarc---spfの結果を使って何をするか決めるレコード&#34;&gt;DMARC - SPFの結果を使って何をするか決めるレコード&lt;/h2&gt;
&lt;h3 id=&#34;dmarcとは&#34;&gt;DMARCとは&lt;/h3&gt;
&lt;p&gt;&lt;strong&gt;DMARC（Domain-based Message Authentication, Reporting, and Conformance）&lt;/strong&gt; は、SPF（やDKIM）の検証結果に基づいて、受信側が「そのメールをどう扱うか」を指示するレコード。&lt;/p&gt;</description>
    </item>
    <item>
      <title>Cloudflareの全通知をDiscord Webhookに飛ばす</title>
      <link>https://techblog.wasutech.dev/posts/cloudflare-notifications-discord/</link>
      <pubDate>Sun, 12 Apr 2026 18:00:00 +0900</pubDate>
      <guid>https://techblog.wasutech.dev/posts/cloudflare-notifications-discord/</guid>
      <description>&lt;p&gt;CloudflareのNotificationsはUIから1個ずつ設定するのがとにかくだるい。
種別も多いし、手動でDiscord Webhookを登録していくのは非現実的。&lt;/p&gt;
&lt;p&gt;Cloudflare APIとWorkersを組み合わせて全通知を一括登録する。&lt;/p&gt;
&lt;h2 id=&#34;構成&#34;&gt;構成&lt;/h2&gt;
&lt;pre tabindex=&#34;0&#34;&gt;&lt;code&gt;Cloudflare Notifications
        ↓
  Cloudflare Worker (受け口)
        ↓
  Discord Webhook
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;Cloudflare NotificationsはWebhook送信に対応しているので、Workerを受け口にしてDiscordに転送する。&lt;/p&gt;
&lt;h2 id=&#34;1-discord-webhookを作成&#34;&gt;1. Discord Webhookを作成&lt;/h2&gt;
&lt;p&gt;通知を飛ばしたいDiscordチャンネルの設定から作成する。&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;チャンネル設定 → Integrations → Webhooks → New Webhook → Copy Webhook URL&lt;/strong&gt;&lt;/p&gt;
&lt;h2 id=&#34;2-cloudflare-workerを作成&#34;&gt;2. Cloudflare Workerを作成&lt;/h2&gt;
&lt;p&gt;Cloudflareダッシュボードから &lt;strong&gt;Workers &amp;amp; Pages → Create → Hello World&lt;/strong&gt; で作成する。&lt;/p&gt;
&lt;p&gt;名前は &lt;code&gt;cf-notify-discord&lt;/code&gt; など適当につけてDeploy。&lt;/p&gt;
&lt;p&gt;エディタ画面で以下のコードに置き換える。&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;&#34;&gt;&lt;code class=&#34;language-javascript&#34; data-lang=&#34;javascript&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#66d9ef&#34;&gt;export&lt;/span&gt; &lt;span style=&#34;color:#66d9ef&#34;&gt;default&lt;/span&gt; {
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  &lt;span style=&#34;color:#66d9ef&#34;&gt;async&lt;/span&gt; &lt;span style=&#34;color:#a6e22e&#34;&gt;fetch&lt;/span&gt;(&lt;span style=&#34;color:#a6e22e&#34;&gt;request&lt;/span&gt;, &lt;span style=&#34;color:#a6e22e&#34;&gt;env&lt;/span&gt;) {
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#66d9ef&#34;&gt;if&lt;/span&gt; (&lt;span style=&#34;color:#a6e22e&#34;&gt;request&lt;/span&gt;.&lt;span style=&#34;color:#a6e22e&#34;&gt;method&lt;/span&gt; &lt;span style=&#34;color:#f92672&#34;&gt;!==&lt;/span&gt; &lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;POST&amp;#34;&lt;/span&gt;) {
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;      &lt;span style=&#34;color:#66d9ef&#34;&gt;return&lt;/span&gt; &lt;span style=&#34;color:#66d9ef&#34;&gt;new&lt;/span&gt; &lt;span style=&#34;color:#a6e22e&#34;&gt;Response&lt;/span&gt;(&lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;ok&amp;#34;&lt;/span&gt;);
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    }
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#66d9ef&#34;&gt;let&lt;/span&gt; &lt;span style=&#34;color:#a6e22e&#34;&gt;body&lt;/span&gt;;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#66d9ef&#34;&gt;try&lt;/span&gt; {
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;      &lt;span style=&#34;color:#a6e22e&#34;&gt;body&lt;/span&gt; &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; &lt;span style=&#34;color:#66d9ef&#34;&gt;await&lt;/span&gt; &lt;span style=&#34;color:#a6e22e&#34;&gt;request&lt;/span&gt;.&lt;span style=&#34;color:#a6e22e&#34;&gt;json&lt;/span&gt;();
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    } &lt;span style=&#34;color:#66d9ef&#34;&gt;catch&lt;/span&gt; {
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;      &lt;span style=&#34;color:#66d9ef&#34;&gt;return&lt;/span&gt; &lt;span style=&#34;color:#66d9ef&#34;&gt;new&lt;/span&gt; &lt;span style=&#34;color:#a6e22e&#34;&gt;Response&lt;/span&gt;(&lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;invalid json&amp;#34;&lt;/span&gt;, { &lt;span style=&#34;color:#a6e22e&#34;&gt;status&lt;/span&gt;&lt;span style=&#34;color:#f92672&#34;&gt;:&lt;/span&gt; &lt;span style=&#34;color:#ae81ff&#34;&gt;400&lt;/span&gt; });
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    }
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#66d9ef&#34;&gt;const&lt;/span&gt; &lt;span style=&#34;color:#a6e22e&#34;&gt;message&lt;/span&gt; &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; {
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;      &lt;span style=&#34;color:#a6e22e&#34;&gt;username&lt;/span&gt;&lt;span style=&#34;color:#f92672&#34;&gt;:&lt;/span&gt; &lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;Cloudflare&amp;#34;&lt;/span&gt;,
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;      &lt;span style=&#34;color:#a6e22e&#34;&gt;embeds&lt;/span&gt;&lt;span style=&#34;color:#f92672&#34;&gt;:&lt;/span&gt; [{
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        &lt;span style=&#34;color:#a6e22e&#34;&gt;title&lt;/span&gt;&lt;span style=&#34;color:#f92672&#34;&gt;:&lt;/span&gt; &lt;span style=&#34;color:#a6e22e&#34;&gt;body&lt;/span&gt;.&lt;span style=&#34;color:#a6e22e&#34;&gt;text&lt;/span&gt;&lt;span style=&#34;color:#f92672&#34;&gt;?&lt;/span&gt;.&lt;span style=&#34;color:#a6e22e&#34;&gt;title&lt;/span&gt; &lt;span style=&#34;color:#f92672&#34;&gt;||&lt;/span&gt; &lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;Cloudflare Notification&amp;#34;&lt;/span&gt;,
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        &lt;span style=&#34;color:#a6e22e&#34;&gt;description&lt;/span&gt;&lt;span style=&#34;color:#f92672&#34;&gt;:&lt;/span&gt; &lt;span style=&#34;color:#a6e22e&#34;&gt;body&lt;/span&gt;.&lt;span style=&#34;color:#a6e22e&#34;&gt;text&lt;/span&gt;&lt;span style=&#34;color:#f92672&#34;&gt;?&lt;/span&gt;.&lt;span style=&#34;color:#a6e22e&#34;&gt;description&lt;/span&gt; &lt;span style=&#34;color:#f92672&#34;&gt;||&lt;/span&gt; &lt;span style=&#34;color:#a6e22e&#34;&gt;JSON&lt;/span&gt;.&lt;span style=&#34;color:#a6e22e&#34;&gt;stringify&lt;/span&gt;(&lt;span style=&#34;color:#a6e22e&#34;&gt;body&lt;/span&gt;, &lt;span style=&#34;color:#66d9ef&#34;&gt;null&lt;/span&gt;, &lt;span style=&#34;color:#ae81ff&#34;&gt;2&lt;/span&gt;),
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        &lt;span style=&#34;color:#a6e22e&#34;&gt;color&lt;/span&gt;&lt;span style=&#34;color:#f92672&#34;&gt;:&lt;/span&gt; &lt;span style=&#34;color:#ae81ff&#34;&gt;0xF6821F&lt;/span&gt;,
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        &lt;span style=&#34;color:#a6e22e&#34;&gt;timestamp&lt;/span&gt;&lt;span style=&#34;color:#f92672&#34;&gt;:&lt;/span&gt; &lt;span style=&#34;color:#66d9ef&#34;&gt;new&lt;/span&gt; Date().&lt;span style=&#34;color:#a6e22e&#34;&gt;toISOString&lt;/span&gt;()
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;      }]
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    };
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#66d9ef&#34;&gt;await&lt;/span&gt; &lt;span style=&#34;color:#a6e22e&#34;&gt;fetch&lt;/span&gt;(&lt;span style=&#34;color:#a6e22e&#34;&gt;env&lt;/span&gt;.&lt;span style=&#34;color:#a6e22e&#34;&gt;DISCORD_WEBHOOK&lt;/span&gt;, {
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;      &lt;span style=&#34;color:#a6e22e&#34;&gt;method&lt;/span&gt;&lt;span style=&#34;color:#f92672&#34;&gt;:&lt;/span&gt; &lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;POST&amp;#34;&lt;/span&gt;,
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;      &lt;span style=&#34;color:#a6e22e&#34;&gt;headers&lt;/span&gt;&lt;span style=&#34;color:#f92672&#34;&gt;:&lt;/span&gt; { &lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;Content-Type&amp;#34;&lt;/span&gt;&lt;span style=&#34;color:#f92672&#34;&gt;:&lt;/span&gt; &lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;application/json&amp;#34;&lt;/span&gt; },
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;      &lt;span style=&#34;color:#a6e22e&#34;&gt;body&lt;/span&gt;&lt;span style=&#34;color:#f92672&#34;&gt;:&lt;/span&gt; &lt;span style=&#34;color:#a6e22e&#34;&gt;JSON&lt;/span&gt;.&lt;span style=&#34;color:#a6e22e&#34;&gt;stringify&lt;/span&gt;(&lt;span style=&#34;color:#a6e22e&#34;&gt;message&lt;/span&gt;)
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    });
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#66d9ef&#34;&gt;return&lt;/span&gt; &lt;span style=&#34;color:#66d9ef&#34;&gt;new&lt;/span&gt; &lt;span style=&#34;color:#a6e22e&#34;&gt;Response&lt;/span&gt;(&lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;ok&amp;#34;&lt;/span&gt;);
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  }
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;}
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;Discord WebhookのURLはWorkerの&lt;strong&gt;Settings → Variables and Secrets&lt;/strong&gt;でシークレットとして登録する。&lt;/p&gt;</description>
    </item>
    <item>
      <title>歴史地図アプリを雑にk3sへデプロイした</title>
      <link>https://techblog.wasutech.dev/posts/k3s-history-map-deploy/</link>
      <pubDate>Wed, 25 Feb 2026 22:00:00 +0900</pubDate>
      <guid>https://techblog.wasutech.dev/posts/k3s-history-map-deploy/</guid>
      <description>React&#43;Vite&#43;MapLibreで作った歴史地図アプリをk3sにデプロイした話。hostPathマウント、Viteの罠、ngrokとTailscaleを駆使したファイル転送など泥臭い記録。</description>
    </item>
  </channel>
</rss>
