<?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>omegaatt</title>
    <link>https://www.omegaatt.com/</link>
    <description>Recent content on omegaatt</description>
    <generator>Hugo -- 0.164.0</generator>
    <language>zh-TW</language>
    <managingEditor>raiven.kao@gmail.com (Raiven Kao)</managingEditor>
    <webMaster>raiven.kao@gmail.com (Raiven Kao)</webMaster>
    <copyright>Raiven Kao 2020 - 2026</copyright>
    <lastBuildDate>Wed, 26 Aug 2026 00:00:00 +0000</lastBuildDate>
    <atom:link href="https://www.omegaatt.com/index.xml" rel="self" type="application/rss+xml" />
    <item>
      <title>Unrounded Scaling：Go 1.27 浮點數轉換演算法的深度拆解</title>
      <link>https://www.omegaatt.com/blogs/develop/2026/golang_unrounded_scaling/</link>
      <pubDate>Wed, 26 Aug 2026 00:00:00 +0000</pubDate><author>raiven.kao@gmail.com (Raiven Kao)</author>
      <guid>https://www.omegaatt.com/blogs/develop/2026/golang_unrounded_scaling/</guid>
      <description>&lt;h2 id=&#34;前言&#34;&gt;前言&lt;/h2&gt;
&lt;p&gt;開始之前先看這支&lt;a href=&#34;https://www.youtube.com/watch?v=g1r3iLejTw0&amp;amp;list=PLwy0WTzBokTMHqq_-TQRNmaHWEuPgsGUc&#34;&gt;影片&lt;/a&gt;，講的是 Quake III 那個 &lt;code&gt;0x5f3759df&lt;/code&gt;。它跟本文沒有直接關係，但用的是同一類招式：把浮點數的 bit pattern 當成整數讀，用整數的乘加移位取代超越函數。差別在於那一招接受 3.4% 的誤差，而本文接下來要講的東西，一個 bit 的誤差都不能有。這個對照後面&lt;a href=&#34;#%E5%AE%9A%E9%BB%9E%E5%B0%8D%E6%95%B8%E5%85%88%E7%A2%BA%E5%AE%9A%E7%AD%94%E6%A1%88%E6%9C%89%E5%B9%BE%E4%BD%8D%E6%95%B8&#34;&gt;定點對數&lt;/a&gt;那節會再回來收。&lt;/p&gt;
&lt;iframe width=&#34;560&#34; height=&#34;315&#34; src=&#34;https://www.youtube.com/embed/g1r3iLejTw0?si=bRcAcBeUxO5bfX-m&#34; title=&#34;YouTube video player&#34; frameborder=&#34;0&#34; allow=&#34;accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture; web-share&#34; referrerpolicy=&#34;strict-origin-when-cross-origin&#34; allowfullscreen&gt;&lt;/iframe&gt;
&lt;p&gt;TL;DR 中間一大段是演算法拆解，有興趣再啃。只想知道「Go 1.27 實際上改了什麼、變多快、輸出有沒有跑掉」的話，可以直接跳到 &lt;a href=&#34;#go-127-%E5%88%B0%E5%BA%95%E6%94%B9%E4%BA%86%E4%BB%80%E9%BA%BC&#34;&gt;Go 1.27 到底改了什麼&lt;/a&gt;，結論是 878 行三套演算法變成 290 行一套、快了 11–21%、輸出零變化。如果你在意的是「那我平常記帳該用什麼」，跳到 &lt;a href=&#34;#%E9%99%84%E9%8C%84%E4%B8%8D%E7%94%A8-float64-%E7%9A%84%E8%A9%B1%E4%BB%A3%E5%83%B9%E6%98%AF%E4%BB%80%E9%BA%BC&#34;&gt;附錄&lt;/a&gt;，那一節比較了 &lt;code&gt;float64&lt;/code&gt;、&lt;code&gt;math/big&lt;/code&gt; 三兄弟與 &lt;code&gt;shopspring/decimal&lt;/code&gt;。&lt;/p&gt;
&lt;p&gt;我把 Go 升到 1.27 之後，照慣例翻了一遍 &lt;a href=&#34;https://go.dev/doc/go1.27&#34;&gt;release notes&lt;/a&gt;，也寫了&lt;a href=&#34;https://www.omegaatt.com/blogs/develop/2026/golang_1_27_new_features/&#34;&gt;新特性整理&lt;/a&gt;。整份文件從頭到尾沒有一個字提到 &lt;code&gt;strconv&lt;/code&gt;。&lt;/p&gt;
&lt;p&gt;但是 Go 1.27 把 &lt;code&gt;fmt.Sprintf(&amp;quot;%v&amp;quot;, f)&lt;/code&gt;、&lt;code&gt;strconv.ParseFloat&lt;/code&gt;、&lt;code&gt;json.Marshal&lt;/code&gt; 底下那顆浮點數轉換引擎整個換掉了。舊的實作是三套演算法各自為政：最短列印用 Dragonbox、固定位數列印用 Ryū 風格的程式碼、解析用 Eisel-Lemire，新的實作是一個叫 unrounded scaling 的東西，作者是 Russ Cox，論文是 2026 年 1 月才發表的。&lt;/p&gt;
&lt;p&gt;這件事之所以值得寫一篇，是因為 Russ Cox 在 2011 年寫過一篇 &lt;a href=&#34;https://research.swtch.com/ftoa&#34;&gt;Floating Point to Decimal Conversion is Easy&lt;/a&gt;，副標題是 Part 1。文章的論點是：&lt;/p&gt;</description>
    </item>
    <item>
      <title>Spec-First API：用 codegen 約束 LLM 與人類的 drift</title>
      <link>https://www.omegaatt.com/blogs/develop/2026/spec_first_api_against_drift/</link>
      <pubDate>Tue, 11 Aug 2026 00:00:00 +0000</pubDate><author>raiven.kao@gmail.com (Raiven Kao)</author>
      <guid>https://www.omegaatt.com/blogs/develop/2026/spec_first_api_against_drift/</guid>
      <description>&lt;h2 id=&#34;前言&#34;&gt;前言&lt;/h2&gt;
&lt;p&gt;事情是這樣開始的。&lt;/p&gt;
&lt;p&gt;我請 agent 幫忙加一個 endpoint，它三分鐘寫完，handler 乾淨、error handling 完整、unit test 也跟著補齊，&lt;code&gt;go test ./...&lt;/code&gt; 全綠。此時我想到的是，agent 真的有做完嗎？route 真的有被註冊嗎？&lt;/p&gt;
&lt;p&gt;換成三年前的我，也會漏，那要怎麼樣讓 Agent 不會漏？這個系統從一開始就沒有任何機制能發現有人漏改。以前它靠的是 reviewer 的眼力與同一批人的肌肉記憶撐著；現在把產出速度乘上十倍，這條防線就直接被沖垮了。這不是 agent 的問題，是架構債被提早引爆。&lt;/p&gt;
&lt;p&gt;這篇想記錄的，就是後來把這個服務從 code-first 遷移到 spec-first 的過程：用 OpenAPI 當單一真值來源(SSOT)，用 oapi-codegen 產生 typed interface，讓 compiler 與 CI 去做原本靠人記憶的事情。&lt;/p&gt;
&lt;h2 id=&#34;問題不是有三份檔案&#34;&gt;問題不是「有三份檔案」&lt;/h2&gt;
&lt;p&gt;遷移前，同一條 API 的定義散落在三個地方：&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;-webkit-text-size-adjust:none;&#34;&gt;&lt;code class=&#34;language-text&#34; data-lang=&#34;text&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;infra route definition     ← path / method / authorizer
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;cmd/*/main.go              ← mux.HandleFunc 手寫註冊
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;Go swag comments           ← @Router / @Param / @Success
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;三份檔案本身不是問題，同一個概念散在多個檔案是常態。&lt;/p&gt;
&lt;p&gt;真正的問題是，它們彼此不知道對方存在。&lt;/p&gt;
&lt;p&gt;改 path，可能只改到 Go route，忘了 infra；改 response shape，可能只改 handler struct，忘了註解；授權設定只活在 infra 層，Go 跟 OpenAPI 都不知道有這回事。而 swaggo 的註解在 repo 裡形同空轉，它是「程式碼的產物」，而我們沒有任何機制拿它回頭驗證程式碼。&lt;/p&gt;</description>
    </item>
    <item>
      <title>Golang 1.27 新特性整理：泛型補完與轉正的實驗性功能</title>
      <link>https://www.omegaatt.com/blogs/develop/2026/golang_1_27_new_features/</link>
      <pubDate>Thu, 06 Aug 2026 00:00:00 +0000</pubDate><author>raiven.kao@gmail.com (Raiven Kao)</author>
      <guid>https://www.omegaatt.com/blogs/develop/2026/golang_1_27_new_features/</guid>
      <description>&lt;p&gt;半年前寫完 &lt;a href=&#34;https://www.omegaatt.com/blogs/develop/2026/golang_1_26_huge_features/&#34;&gt;Go 1.26 新特性&lt;/a&gt; 那篇，文末問了一句「準備好更新你的 &lt;code&gt;go.mod&lt;/code&gt; 到 &lt;code&gt;go 1.26&lt;/code&gt; 了嗎？」，沒想到一轉眼 Go 1.27 也要來了。撰文的當下 Go 1.27 即將正式發布，以下內容是根據 &lt;a href=&#34;https://go.dev/doc/go1.27&#34;&gt;release candidate&lt;/a&gt; 整理，如果正式版有微調，還是以官方 release notes 為準。&lt;/p&gt;
&lt;p&gt;如果說 Go 1.26 給我的感覺是數量多、廣度大，那 Go 1.27 更像是把之前欠的債還一還。generic method 從 issue 一路等到現在終於落地，&lt;code&gt;goroutineleak&lt;/code&gt; profile 從需要 &lt;code&gt;GOEXPERIMENT&lt;/code&gt; 才能開的實驗功能轉正成預設行為，&lt;code&gt;encoding/json/v2&lt;/code&gt; 也從實驗畢業成為預設實作。這篇就挑幾個我覺得值得記錄的改動來聊聊。&lt;/p&gt;
&lt;h2 id=&#34;語言層面泛型設計的最後一塊拼圖&#34;&gt;語言層面：泛型設計的最後一塊拼圖&lt;/h2&gt;
&lt;h3 id=&#34;generic-method-真的來了&#34;&gt;Generic Method 真的來了&lt;/h3&gt;
&lt;p&gt;這件事我在 &lt;a href=&#34;https://www.omegaatt.com/blogs/develop/2026/golang_generic_method/&#34;&gt;Go 1.27 即將支援 Generic Method&lt;/a&gt; 那篇已經寫得很詳細了，這裡就不重複展開，只放結論：method 現在可以宣告自己的 type parameter，不用再繞道用 package-level function 把 receiver 當參數傳進去。&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;-webkit-text-size-adjust:none;&#34;&gt;&lt;code class=&#34;language-go&#34; data-lang=&#34;go&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#66d9ef&#34;&gt;type&lt;/span&gt; &lt;span style=&#34;color:#a6e22e&#34;&gt;List&lt;/span&gt;[&lt;span style=&#34;color:#a6e22e&#34;&gt;T&lt;/span&gt; &lt;span style=&#34;color:#66d9ef&#34;&gt;any&lt;/span&gt;] &lt;span style=&#34;color:#66d9ef&#34;&gt;struct&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;items&lt;/span&gt; []&lt;span style=&#34;color:#a6e22e&#34;&gt;T&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;func&lt;/span&gt; &lt;span style=&#34;color:#a6e22e&#34;&gt;NewList&lt;/span&gt;[&lt;span style=&#34;color:#a6e22e&#34;&gt;T&lt;/span&gt; &lt;span style=&#34;color:#66d9ef&#34;&gt;any&lt;/span&gt;](&lt;span style=&#34;color:#a6e22e&#34;&gt;items&lt;/span&gt; &lt;span style=&#34;color:#f92672&#34;&gt;...&lt;/span&gt;&lt;span style=&#34;color:#a6e22e&#34;&gt;T&lt;/span&gt;) &lt;span style=&#34;color:#a6e22e&#34;&gt;List&lt;/span&gt;[&lt;span style=&#34;color:#a6e22e&#34;&gt;T&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:#a6e22e&#34;&gt;List&lt;/span&gt;[&lt;span style=&#34;color:#a6e22e&#34;&gt;T&lt;/span&gt;]{&lt;span style=&#34;color:#a6e22e&#34;&gt;items&lt;/span&gt;: &lt;span style=&#34;color:#a6e22e&#34;&gt;items&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:#75715e&#34;&gt;// MapTo 是 generic method，帶著自己的型別參數 U，&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:#75715e&#34;&gt;// 跟 receiver 的型別參數 T 是兩回事。&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;func&lt;/span&gt; (&lt;span style=&#34;color:#a6e22e&#34;&gt;l&lt;/span&gt; &lt;span style=&#34;color:#a6e22e&#34;&gt;List&lt;/span&gt;[&lt;span style=&#34;color:#a6e22e&#34;&gt;T&lt;/span&gt;]) &lt;span style=&#34;color:#a6e22e&#34;&gt;MapTo&lt;/span&gt;[&lt;span style=&#34;color:#a6e22e&#34;&gt;U&lt;/span&gt; &lt;span style=&#34;color:#66d9ef&#34;&gt;any&lt;/span&gt;](&lt;span style=&#34;color:#a6e22e&#34;&gt;f&lt;/span&gt; &lt;span style=&#34;color:#66d9ef&#34;&gt;func&lt;/span&gt;(&lt;span style=&#34;color:#a6e22e&#34;&gt;T&lt;/span&gt;) &lt;span style=&#34;color:#a6e22e&#34;&gt;U&lt;/span&gt;) &lt;span style=&#34;color:#a6e22e&#34;&gt;List&lt;/span&gt;[&lt;span style=&#34;color:#a6e22e&#34;&gt;U&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;out&lt;/span&gt; &lt;span style=&#34;color:#f92672&#34;&gt;:=&lt;/span&gt; make([]&lt;span style=&#34;color:#a6e22e&#34;&gt;U&lt;/span&gt;, len(&lt;span style=&#34;color:#a6e22e&#34;&gt;l&lt;/span&gt;.&lt;span style=&#34;color:#a6e22e&#34;&gt;items&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;for&lt;/span&gt; &lt;span style=&#34;color:#a6e22e&#34;&gt;i&lt;/span&gt;, &lt;span style=&#34;color:#a6e22e&#34;&gt;v&lt;/span&gt; &lt;span style=&#34;color:#f92672&#34;&gt;:=&lt;/span&gt; &lt;span style=&#34;color:#66d9ef&#34;&gt;range&lt;/span&gt; &lt;span style=&#34;color:#a6e22e&#34;&gt;l&lt;/span&gt;.&lt;span style=&#34;color:#a6e22e&#34;&gt;items&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;out&lt;/span&gt;[&lt;span style=&#34;color:#a6e22e&#34;&gt;i&lt;/span&gt;] = &lt;span style=&#34;color:#a6e22e&#34;&gt;f&lt;/span&gt;(&lt;span style=&#34;color:#a6e22e&#34;&gt;v&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:#a6e22e&#34;&gt;List&lt;/span&gt;[&lt;span style=&#34;color:#a6e22e&#34;&gt;U&lt;/span&gt;]{&lt;span style=&#34;color:#a6e22e&#34;&gt;items&lt;/span&gt;: &lt;span style=&#34;color:#a6e22e&#34;&gt;out&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;type&lt;/span&gt; &lt;span style=&#34;color:#a6e22e&#34;&gt;Cat&lt;/span&gt; &lt;span style=&#34;color:#66d9ef&#34;&gt;struct&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;Name&lt;/span&gt; &lt;span style=&#34;color:#66d9ef&#34;&gt;string&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;Age&lt;/span&gt;  &lt;span style=&#34;color:#66d9ef&#34;&gt;int&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:#a6e22e&#34;&gt;cats&lt;/span&gt; &lt;span style=&#34;color:#f92672&#34;&gt;:=&lt;/span&gt; &lt;span style=&#34;color:#a6e22e&#34;&gt;NewList&lt;/span&gt;(&lt;span style=&#34;color:#a6e22e&#34;&gt;Cat&lt;/span&gt;{&lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;Mittens&amp;#34;&lt;/span&gt;, &lt;span style=&#34;color:#ae81ff&#34;&gt;3&lt;/span&gt;}, &lt;span style=&#34;color:#a6e22e&#34;&gt;Cat&lt;/span&gt;{&lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;Tama&amp;#34;&lt;/span&gt;, &lt;span style=&#34;color:#ae81ff&#34;&gt;5&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;names&lt;/span&gt; &lt;span style=&#34;color:#f92672&#34;&gt;:=&lt;/span&gt; &lt;span style=&#34;color:#a6e22e&#34;&gt;cats&lt;/span&gt;.&lt;span style=&#34;color:#a6e22e&#34;&gt;MapTo&lt;/span&gt;(&lt;span style=&#34;color:#66d9ef&#34;&gt;func&lt;/span&gt;(&lt;span style=&#34;color:#a6e22e&#34;&gt;c&lt;/span&gt; &lt;span style=&#34;color:#a6e22e&#34;&gt;Cat&lt;/span&gt;) &lt;span style=&#34;color:#66d9ef&#34;&gt;string&lt;/span&gt; { &lt;span style=&#34;color:#66d9ef&#34;&gt;return&lt;/span&gt; &lt;span style=&#34;color:#a6e22e&#34;&gt;c&lt;/span&gt;.&lt;span style=&#34;color:#a6e22e&#34;&gt;Name&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;fmt&lt;/span&gt;.&lt;span style=&#34;color:#a6e22e&#34;&gt;Println&lt;/span&gt;(&lt;span style=&#34;color:#a6e22e&#34;&gt;names&lt;/span&gt;.&lt;span style=&#34;color:#a6e22e&#34;&gt;items&lt;/span&gt;) &lt;span style=&#34;color:#75715e&#34;&gt;// [Mittens Tama]&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;老話一句：interface 目前還是無法宣告帶 type parameter 的 method，這是 dynamic dispatch 架構性的限制，不是漏掉沒做。想看完整的來龍去脈跟 workaround 的比較，去讀前面連結的那篇。&lt;/p&gt;</description>
    </item>
    <item>
      <title>Go 1.27 即將支援 Generic Method：告別 package-level 函式的 workaround</title>
      <link>https://www.omegaatt.com/blogs/develop/2026/golang_generic_method/</link>
      <pubDate>Wed, 27 May 2026 00:00:00 +0000</pubDate><author>raiven.kao@gmail.com (Raiven Kao)</author>
      <guid>https://www.omegaatt.com/blogs/develop/2026/golang_generic_method/</guid>
      <description>&lt;h2 id=&#34;前言&#34;&gt;前言&lt;/h2&gt;
&lt;p&gt;Go 1.18 帶來泛型之後，我們終於可以寫出型別安全的通用函式，不再需要到處 &lt;code&gt;interface{}&lt;/code&gt; 加型別斷言。但一個眾所周知的限制一直存在到現在，method 沒辦法有自己的 type parameter。&lt;/p&gt;
&lt;p&gt;換句話說，這段程式碼在目前的 Go 版本中是非法的：&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;-webkit-text-size-adjust:none;&#34;&gt;&lt;code class=&#34;language-go&#34; data-lang=&#34;go&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#66d9ef&#34;&gt;func&lt;/span&gt; (&lt;span style=&#34;color:#a6e22e&#34;&gt;c&lt;/span&gt; &lt;span style=&#34;color:#f92672&#34;&gt;*&lt;/span&gt;&lt;span style=&#34;color:#a6e22e&#34;&gt;Client&lt;/span&gt;) &lt;span style=&#34;color:#a6e22e&#34;&gt;doRequest&lt;/span&gt;[&lt;span style=&#34;color:#a6e22e&#34;&gt;T&lt;/span&gt; &lt;span style=&#34;color:#66d9ef&#34;&gt;any&lt;/span&gt;](&lt;span style=&#34;color:#a6e22e&#34;&gt;ctx&lt;/span&gt; &lt;span style=&#34;color:#a6e22e&#34;&gt;context&lt;/span&gt;.&lt;span style=&#34;color:#a6e22e&#34;&gt;Context&lt;/span&gt;, &lt;span style=&#34;color:#a6e22e&#34;&gt;method&lt;/span&gt;, &lt;span style=&#34;color:#a6e22e&#34;&gt;path&lt;/span&gt; &lt;span style=&#34;color:#66d9ef&#34;&gt;string&lt;/span&gt;, &lt;span style=&#34;color:#a6e22e&#34;&gt;body&lt;/span&gt; &lt;span style=&#34;color:#66d9ef&#34;&gt;any&lt;/span&gt;) (&lt;span style=&#34;color:#f92672&#34;&gt;*&lt;/span&gt;&lt;span style=&#34;color:#a6e22e&#34;&gt;T&lt;/span&gt;, &lt;span style=&#34;color:#66d9ef&#34;&gt;error&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:#75715e&#34;&gt;// 編譯錯誤：cannot use generic method doRequest without instantiation&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;為了繞過這個限制，大家只能退而求其次，把 receiver 當作普通參數傳進去，改用 package-level 的 generic function。&lt;/p&gt;
&lt;p&gt;最近 &lt;a href=&#34;https://github.com/golang/go/issues/77273&#34;&gt;Go issue #77273&lt;/a&gt; 的進展讓這件事有了轉機，generic method 的設計已被接受，預計在 Go 1.27 正式釋出。&lt;/p&gt;
&lt;h2 id=&#34;為什麼現在沒有-generic-method&#34;&gt;為什麼現在沒有 Generic Method？&lt;/h2&gt;
&lt;p&gt;這不是 Go team 沒想到，而是刻意的設計決策。&lt;/p&gt;
&lt;p&gt;Go 的 interface 是核心功能，method 必須能夠被 dispatch 到具體的實作上。如果 method 自身帶有 type parameter，編譯器在 dispatch 時需要知道 &lt;code&gt;T&lt;/code&gt; 是什麼，這在動態 dispatch（interface dispatch）的情境下是個難題，runtime 無法事先知道要生成哪個型別的實例化版本。&lt;/p&gt;
&lt;p&gt;所以泛型剛推出時，generic method 被刻意排除在外，等待一個比較完整的設計方案。這個等待從 Go 1.18 一路等到了 1.27（暫定）。&lt;/p&gt;</description>
    </item>
    <item>
      <title>把 k3s 從 Ubuntu 搬到 OpenSUSE MicroOS，順便換上 FluxCD GitOps</title>
      <link>https://www.omegaatt.com/blogs/develop/2026/migrate_k3s_lab_from_ubuntu_to_opensuse_micro_os/</link>
      <pubDate>Sun, 24 May 2026 00:00:00 +0000</pubDate><author>raiven.kao@gmail.com (Raiven Kao)</author>
      <guid>https://www.omegaatt.com/blogs/develop/2026/migrate_k3s_lab_from_ubuntu_to_opensuse_micro_os/</guid>
      <description>&lt;h2 id=&#34;前言&#34;&gt;前言&lt;/h2&gt;
&lt;p&gt;我的 Homelab 跑著一台 Proxmox VE，底下有一個 Ubuntu Server 24.04 的 VM 負責跑 k3s cluster，這個 cluster 接管了家裡所有的自架服務：從 &lt;a href=&#34;https://www.omegaatt.com/blogs/develop/2023/bitwarden_with_self_hosted_password_backend/&#34;&gt;Vaultwarden 密碼管理&lt;/a&gt;、&lt;a href=&#34;https://karakeep.app/&#34;&gt;Karakeep&lt;/a&gt; 書籤、&lt;a href=&#34;https://github.com/filebrowser/filebrowser&#34;&gt;Filebrowser&lt;/a&gt; 文件管理，到 &lt;a href=&#34;https://www.wakapi.dev/&#34;&gt;Wakapi&lt;/a&gt; 開發時間追蹤，大大小小跑了十幾個服務。&lt;/p&gt;
&lt;p&gt;這個架構一直跑得很穩。直到 Linux kernel 7.0 發布，我才開始動搖。&lt;/p&gt;
&lt;p&gt;kernel 7.0 的各種新特性讓我心癢，Ubuntu 26.04 LTS 剛好以 kernel 7.0 作為預設核心，理論上從 24.04 升上去就能一步到位。但在一個跑著十幾個服務的生產 VM 上執行 &lt;code&gt;do-release-upgrade&lt;/code&gt;，那種心臟發抖的感覺，即便有做好備份，還是讓我遲遲下不了手。&lt;/p&gt;
&lt;p&gt;就在猶豫要不要升級的當下，我問了自己一個問題：「我要繼續用 Ubuntu 嗎？」&lt;/p&gt;
&lt;p&gt;這個問題打開了一扇門。我的 Linux desktop 是 openSUSE Tumbleweed + KDE Plasma，用了三年、換過兩台裝置（筆電和桌電），沒有遇到太多問題。Tumbleweed 的滾動更新讓我一直跑著最新的 kernel，升級這件事從來不是一個需要另外排行程的壓力事件。&lt;/p&gt;
&lt;p&gt;既然 desktop 側已經對 openSUSE 生態有信心，server 側為什麼不試試看？&lt;/p&gt;
&lt;p&gt;這讓我開始認真考慮：有沒有一種 OS，讓 server 的升級這件事也變成「預設安全」而不是「需要勇氣」？&lt;/p&gt;
&lt;h2 id=&#34;為什麼選-opensuse-microos&#34;&gt;為什麼選 OpenSUSE MicroOS&lt;/h2&gt;
&lt;p&gt;&lt;a href=&#34;https://microos.opensuse.org/&#34;&gt;OpenSUSE MicroOS&lt;/a&gt; 是一個 Immutable OS（不可變作業系統），核心特性有幾個：&lt;/p&gt;</description>
    </item>
    <item>
      <title>Pragmatic Clean Architecture in Go</title>
      <link>https://www.omegaatt.com/blogs/develop/2026/pragmatic_clean_architecture_in_go/</link>
      <pubDate>Thu, 23 Apr 2026 00:00:00 +0000</pubDate><author>raiven.kao@gmail.com (Raiven Kao)</author>
      <guid>https://www.omegaatt.com/blogs/develop/2026/pragmatic_clean_architecture_in_go/</guid>
      <description>&lt;h2 id=&#34;前言&#34;&gt;前言&lt;/h2&gt;
&lt;p&gt;在大型系統或需要長期維護的產品中，導入 DDD（Domain-Driven Design）或 Clean Architecture 幾乎是業界的標準答案，分層清晰、職責明確、可測試性高，這些優點毋庸置疑。&lt;/p&gt;
&lt;p&gt;但問題是，不是每個專案都是大型系統。&lt;/p&gt;
&lt;p&gt;當你面對的是一個內部工具、side project、或是剛起步的新產品，Clean Architecture 的大全套 Use Case、Port、Adapter、Aggregate、Repository Interface 全放 domain，往往會讓你在還沒寫第一行商業邏輯之前，就先在資料夾結構裡迷路了三個小時，或是讓不熟悉的貢獻者花費大量時間在閱讀架構。&lt;/p&gt;
&lt;p&gt;這篇文章想討論的是：在不犧牲可測試性與可維護性的前提下，哪些抽象可以捨棄、哪些值得保留，以及我自己踩過的一些坑。&lt;/p&gt;
&lt;h2 id=&#34;捨棄什麼&#34;&gt;捨棄什麼&lt;/h2&gt;
&lt;h3 id=&#34;獨立的-portadapter-層&#34;&gt;獨立的 Port/Adapter 層&lt;/h3&gt;
&lt;p&gt;Clean Architecture 中，Use Case 透過明確定義的 input/output port 與外界溝通，搭配 Adapter 負責格式轉換。這在大型系統中確實有其價值，但它帶來的代價是：為了讓每一層都能獨立替換，你需要維護大量的介面與轉換邏輯，而這些轉換邏輯往往只是把 A struct 的欄位複製到 B struct。&lt;/p&gt;
&lt;p&gt;在小專案中，這條轉換鏈可以大幅縮短。HTTP handler 本身就可以負責 DTO 的轉換，不需要再多一層 Adapter。&lt;/p&gt;
&lt;h3 id=&#34;ddd-的-aggregate&#34;&gt;DDD 的 Aggregate&lt;/h3&gt;
&lt;p&gt;Aggregate 是 DDD 中保護業務不變式（invariant）的邊界，透過 Aggregate Root 統一管理相關物件的存取。這個概念本身沒有問題，但在小專案中，如果業務規則還不夠複雜到需要嚴格的不變式保護，過早引入 Aggregate 反而會讓簡單的 CRUD 操作變得冗長。&lt;/p&gt;
&lt;h2 id=&#34;保留什麼&#34;&gt;保留什麼&lt;/h2&gt;
&lt;p&gt;捨棄了部分複雜度之後，剩下的四層架構已經足以應付絕大多數的小專案需求。&lt;/p&gt;
&lt;h3 id=&#34;package-domain&#34;&gt;&lt;code&gt;package domain&lt;/code&gt;&lt;/h3&gt;
&lt;p&gt;這裡只放 &lt;strong&gt;Domain Model&lt;/strong&gt; 與 &lt;strong&gt;Value Object&lt;/strong&gt;。&lt;/p&gt;
&lt;p&gt;值得特別說明 Domain Model 的定義。它不等同於 DDD 裡的 Domain，不是什麼深奧的設計概念，Domain Model 只是在描述「這個應用程式如何跟外部世界互動、邊界在哪」，也就是你的核心資料結構與它們身上的行為。有些「賣課」的人喜歡把 Domain Model 直接等同於 DDD 的 Domain，實際上它是個更基礎、更普遍的概念，可以參考這支&lt;a href=&#34;https://www.youtube.com/watch?v=aSPu0WhVPew&#34;&gt;影片&lt;/a&gt;的解釋。Domain Model 中不需要也不該出現任何技術細節，例如回傳是否是 JSON 等等，這些 Domain Model 甚至要能讓 PM 與非技術人員也「聽的懂」。&lt;/p&gt;</description>
    </item>
    <item>
      <title>k3s Ingress Controller 從 Nginx 遷移到 Traefik 同時整合 CrowdSec</title>
      <link>https://www.omegaatt.com/blogs/develop/2026/migrate_ingress_controller_from_nginx_to_traefik/</link>
      <pubDate>Sat, 11 Apr 2026 00:00:00 +0000</pubDate><author>raiven.kao@gmail.com (Raiven Kao)</author>
      <guid>https://www.omegaatt.com/blogs/develop/2026/migrate_ingress_controller_from_nginx_to_traefik/</guid>
      <description>&lt;blockquote&gt;
&lt;p&gt;TL;DR 馬上就看到有小王八蛋想壞壞&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;&lt;img loading=&#34;lazy&#34; src=&#34;https://www.omegaatt.com/blogs/develop/2026/migrate_ingress_controller_from_nginx_to_traefik/images/SCR-20260411-nmjr.webp&#34;&gt;&lt;/p&gt;
&lt;h2 id=&#34;前言&#34;&gt;前言&lt;/h2&gt;
&lt;p&gt;2025 年 11 月，Kubernetes SIG Network 發布了一篇公告：&lt;a href=&#34;https://kubernetes.io/blog/2025/11/11/ingress-nginx-retirement/&#34;&gt;ingress-nginx 將正式退役&lt;/a&gt;。&lt;/p&gt;
&lt;p&gt;這個消息讓不少人震驚，但嚴格來說有個細節需要釐清。目前市場上有兩個長得很像的 nginx ingress controller：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href=&#34;https://github.com/kubernetes/ingress-NGINX&#34;&gt;kubernetes/ingress-nginx&lt;/a&gt;：由 Kubernetes SIG Network 維護，這次宣布 EOL 的就是它。&lt;/li&gt;
&lt;li&gt;&lt;a href=&#34;https://github.com/nginx/kubernetes-ingress&#34;&gt;nginx/kubernetes-ingress&lt;/a&gt;（又稱 NKS）：由 F5 / NGINX Inc. 維護，目前仍持續開發中。&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;我的 homelab k3s cluster 一直以來用的是 F5 維護的版本，嚴格來說不在這次 EOL 的範圍內。但這個公告還是給了我一個動力，與其繼續追蹤兩個版本之間的差異、擔心哪天又有什麼變化，不如趁這個機會把遷移一次做完。&lt;/p&gt;
&lt;p&gt;遷移目標選定 Traefik v3，原因有兩個：&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;k3s 內建 Traefik 作為預設 Ingress Controller，&lt;a href=&#34;https://docs.k3s.io/networking/networking-services&#34;&gt;1.32 版本後升級為 Traefik v3&lt;/a&gt;，搭配 &lt;code&gt;HelmChartConfig&lt;/code&gt; 管理，和 cluster 的整合度最高。&lt;/li&gt;
&lt;li&gt;順手引入 CrowdSec 主動防護——長期以來我的服務靠著 Cloudflare proxy 當唯一守門人，但我一直想要在 Ingress 層就能即時判斷並封鎖攻擊者 IP，而不只是依賴前端的 CDN 過濾。&lt;/li&gt;
&lt;/ol&gt;
&lt;h2 id=&#34;為什麼不選-cilium-gateway-api&#34;&gt;為什麼不選 Cilium Gateway API&lt;/h2&gt;
&lt;p&gt;在決定遷移到 Traefik 之前，我也閱讀了 Gene 的&lt;a href=&#34;https://igene.tw/cilium-gateway-api-migration&#34;&gt;告別 ingress-nginx：Cilium Gateway API 遷移筆記&lt;/a&gt;，評估過 Cilium Gateway API 方案。Cilium 在 CNI 層整合了 Gateway API，理論上能提供更底層、效能更好的流量控制，且完全符合 Kubernetes Gateway API 的標準規範。&lt;/p&gt;</description>
    </item>
    <item>
      <title>用 Hetzner &#43; Proxmox Backup Server 實踐 3-2-1 備份原則的異地備援</title>
      <link>https://www.omegaatt.com/blogs/develop/2026/proxmox_backup_server_on_hetzner/</link>
      <pubDate>Tue, 31 Mar 2026 00:00:00 +0000</pubDate><author>raiven.kao@gmail.com (Raiven Kao)</author>
      <guid>https://www.omegaatt.com/blogs/develop/2026/proxmox_backup_server_on_hetzner/</guid>
      <description>&lt;h2 id=&#34;前言&#34;&gt;前言&lt;/h2&gt;
&lt;p&gt;如果你有認真對待自己的資料，應該聽過 &lt;strong&gt;3-2-1 備份原則&lt;/strong&gt;：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;3&lt;/strong&gt; 份備份&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;2&lt;/strong&gt; 種不同的儲存媒介&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;1&lt;/strong&gt; 份異地備援&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;前兩項在家中的 Homelab 環境相對容易做到，但「異地」這個字往往是大多數人的盲點。硬碟壞掉？用 Proxmox Backup Server (PBS) 還原。系統崩潰？從 PBS 拉一份回來。但若是家裡發生火災、淹水，或是更平凡的情況——你不小心把整台 Proxmox VE 的 NVMe 連資料一起刪掉——這時候同一個屋簷下的所有備份都會跟著陪葬。&lt;/p&gt;
&lt;p&gt;我的 Homelab 環境是一台 Proxmox VE 搭配一台 Proxmox Backup Server，跑著包含 k3s、Vaultwarden 等各種服務。在研究了很久的異地備援方案後，我決定在歐洲架設一台 PBS，讓它透過 WireGuard 隧道主動把家裡的備份「拉」過去，讓 321 中的那個「1」真正落地。&lt;/p&gt;
&lt;p&gt;這篇文章會記錄完整的架構決策過程，從為什麼選 Hetzner、選哪個方案、到實際的設定步驟，以及中間踩過的坑。&lt;/p&gt;
&lt;h2 id=&#34;為什麼選-hetzner&#34;&gt;為什麼選 Hetzner&lt;/h2&gt;
&lt;p&gt;&lt;a href=&#34;https://www.hetzner.com/&#34;&gt;Hetzner&lt;/a&gt; 是一間位於德國的基礎設施供應商，在評估異地備援的雲端供應商時，我的優先考量是：&lt;strong&gt;便宜&lt;/strong&gt;、&lt;strong&gt;隱私安全&lt;/strong&gt;、&lt;strong&gt;技術友善&lt;/strong&gt;。&lt;/p&gt;
&lt;h3 id=&#34;gcp--aws-的問題&#34;&gt;GCP / AWS 的問題&lt;/h3&gt;
&lt;p&gt;第一個念頭當然是最熟悉的 GCP 或 AWS，畢竟都是主流雲端。但把兩者丟進計算機後，答案很快就出來了：&lt;/p&gt;
&lt;p&gt;以我「最終」需要的規格：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;2 vCPU / 4GB RAM VM&lt;/li&gt;
&lt;li&gt;80GB 高速 Storage 用於 PBS 備份&lt;/li&gt;
&lt;li&gt;500GB 慢速 Storage 用於 NAS 備份&lt;/li&gt;
&lt;/ul&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;Hetzner&lt;br/&gt;- CX23&lt;br/&gt;- 80GB Volume&lt;br/&gt;- BX11 Storage Box&lt;/td&gt;
					&lt;td&gt;約 $15.73 USD&lt;/td&gt;
			&lt;/tr&gt;
			&lt;tr&gt;
					&lt;td&gt;AWS 同級配置&lt;br/&gt;- t3.medium&lt;br/&gt;- gp3 80GB&lt;br/&gt;- S3 500GB&lt;/td&gt;
					&lt;td&gt;約 $50 USD&lt;/td&gt;
			&lt;/tr&gt;
			&lt;tr&gt;
					&lt;td&gt;GCP 同級配置&lt;/td&gt;
					&lt;td&gt;相近&lt;/td&gt;
			&lt;/tr&gt;
	&lt;/tbody&gt;
&lt;/table&gt;
&lt;p&gt;光是這個差距就已經結案了。AWS 和 GCP 的小型運算實例本身就已經比 Hetzner 貴好幾倍，Block Storage 的單價更是幾乎翻倍，再加上對外流量費用（下載資料還要另外收），災難復原時光是把 500GB 拉回來就可能要多付幾十美元。&lt;/p&gt;</description>
    </item>
    <item>
      <title>放棄 Electron 與 Tauri? 我用 Wails &#43; HTMX 打造 3MB 的跨平台桌面應用</title>
      <link>https://www.omegaatt.com/blogs/develop/2026/golang_desktop_application_with_wails_and_htmx/</link>
      <pubDate>Mon, 09 Mar 2026 00:00:00 +0000</pubDate><author>raiven.kao@gmail.com (Raiven Kao)</author>
      <guid>https://www.omegaatt.com/blogs/develop/2026/golang_desktop_application_with_wails_and_htmx/</guid>
      <description>&lt;blockquote&gt;
&lt;p&gt;Web 仔要有 Web 仔的自覺&lt;/p&gt;
&lt;/blockquote&gt;
&lt;h2 id=&#34;前言&#34;&gt;前言&lt;/h2&gt;
&lt;p&gt;前陣子收到了 Murphy 的需求，嘗試使用 &lt;a href=&#34;https://www.omegaatt.com/blogs/develop/2025/tauri_first_look&#34;&gt;Tauri 寫了一個桌面 App&lt;/a&gt;。Tauri 結合 Rust 與 React 的體驗相當不錯，打包出來的體積也十分理想。&lt;/p&gt;
&lt;p&gt;但身為一個主要撰寫 Golang 的後端工程師，內心總有一個聲音：「如果能用 Go 來寫桌面應用，那該有多好？」&lt;/p&gt;
&lt;p&gt;眾所周知，Golang 在原生的 Desktop GUI 框架上一直沒有特別強勢的殺手級專案（過去使用 Fyne 的經驗不算太好）。在環顧了一眾基於 Chromium 的 Electron 或是基於系統 WebView 的 Tauri 後，決定走一條稍微不一樣的「老路」：使用 &lt;a href=&#34;https://wails.io/&#34;&gt;Wails&lt;/a&gt; 搭配 &lt;a href=&#34;https://htmx.org/&#34;&gt;HTMX&lt;/a&gt; 與 &lt;a href=&#34;https://templ.guide/&#34;&gt;Templ&lt;/a&gt;。&lt;/p&gt;
&lt;p&gt;最終的產物就是 &lt;a href=&#34;https://github.com/omegaatt36/dub&#34;&gt;Dub&lt;/a&gt; 一個跨平台的批次檔案重新命名工具。更甚，在 Linux 上打包出來的可執行檔只有約 3MB，並且完美支援 macOS 與 Linux，且幾乎沒有複雜的前端狀態管理負擔。&lt;/p&gt;
&lt;h2 id=&#34;為什麼是-wails--htmx&#34;&gt;為什麼是 Wails + HTMX？&lt;/h2&gt;
&lt;p&gt;在選擇桌面應用程式的技術方案時，我們通常有幾個考量&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;Electron: 開發體驗極佳，生態系最豐富，但代價是動輒上百 MB 的體積與高昂的記憶體佔用。對於一個小工具來說，太過沉重。（這也是我優先選擇 Zed.dev 作為主要編輯器的原因）&lt;/li&gt;
&lt;li&gt;Tauri: 效能極佳，體積小，但後端需要寫 Rust。雖然 Rust 很香，但有時候只想用最熟悉的 Go 快速把邏輯刻出來。&lt;/li&gt;
&lt;li&gt;Wails: 與 Tauri 類似，採用前端網頁技術結合系統原生 WebView，但後端語言是 Golang。&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;既然選了 Wails，前端該用什麼？
現代前端框架（React, Vue, Svelte）通常需要建置複雜的 SPA（Single Page Application），並透過 JSON API 與後端溝通。但對於一個狀態並不複雜的工具軟體，不想在前端再維護一份狀態。&lt;/p&gt;</description>
    </item>
    <item>
      <title>旅遊札記 - 2026 新年的關西遊</title>
      <link>https://www.omegaatt.com/blogs/develop/2026/travel_kansai_kyoto_and_osaka/</link>
      <pubDate>Fri, 20 Feb 2026 00:00:00 +0000</pubDate><author>raiven.kao@gmail.com (Raiven Kao)</author>
      <guid>https://www.omegaatt.com/blogs/develop/2026/travel_kansai_kyoto_and_osaka/</guid>
      <description>&lt;p&gt;在 &lt;a href=&#34;https://www.omegaatt.com/blogs/develop/2025/2025_recap&#34;&gt;2025 Recap&lt;/a&gt; 的展望中寫下了「與 +1 首次踏足國外」，沒想到兌現的速度比預期快得多，2026 年的第二天，我跟 Murphy 就站在了關西國際機場。&lt;/p&gt;
&lt;p&gt;會選擇日本關西作為首次出國的目的地，原因很單純：我們都各自來過，作為初次日本自由行，熟悉的地方還是比較安心。加上 2025 年 10 月才從五十音開始學日文，正好拿這趟旅程來驗收 N87 的日文實力（？&lt;/p&gt;
&lt;p&gt;五天的行程，從京都走到大阪，從零度的細雪走到通天閣的暖陽。這篇拖了快兩個月才動筆的遊記，就當作是對這趟旅程的交代吧。&lt;/p&gt;
&lt;h2 id=&#34;day-112初雪與初売り&#34;&gt;Day 1（1/2）：初雪與初売り&lt;/h2&gt;
&lt;p&gt;搭乘星宇航空 JX820 班次，Airbux A330-900neo 機型，這次旅行選擇星宇也是為了體驗 Airbus 的飛機。11:10 分落地後，11:20 分就開始排隊出關（這邊禁止拍照），11:40 順利出關拿完行李！原本預計出關會要約莫一個半小時，訂了 12:44 分的 JR Haruka 由於關西國際機場極高的效率讓我們在機場空等了一小時 XD。&lt;/p&gt;
&lt;p&gt;&lt;img loading=&#34;lazy&#34; src=&#34;https://www.omegaatt.com/blogs/develop/2026/travel_kansai_kyoto_and_osaka/images/day_1/airport/PXL_20260101_224549230.webp&#34;&gt;&lt;/p&gt;
&lt;p&gt;由於吃了暈機藥，在 Haruka 時仍持續處於昏迷狀態，抵達京都時已經下午兩點，隨即轉乘烏丸線到烏丸御池站，出站走了一小段路就到了這兩晚的住所，&lt;a href=&#34;https://maps.app.goo.gl/1axMGJEhVGY187iC9&#34;&gt;Hotel Resol Trinity Kyoto（京都RESOL TRINITY酒店）&lt;/a&gt;。&lt;/p&gt;
&lt;p&gt;我與 Murphy 在這次旅行的分工是，我負責京都部份，Murphy 負責大阪部份，而京都的住宿，我只花了半小時搜尋「京都 住宿 ptt」就搞定了，我並沒有過多研究 Hotel Resol 整體的歷史等等，單純是因為飯店的窗戶有一個紙門設計，我就被迷惑了。好在運氣始終不錯，當從京都的大馬路上轉進小巷子，一間外裝不太起眼的飯店映入眼前，進入飯店後裝潢與外裝截然不同，雖然知道他是ホテル但濃濃的りょかん味，特別是一樓還有大浴場，這兩天我都在這邊洗澡，所以並沒有感受到評價所說的「浴室很小」的感覺。由於是新年期間，附近的學校也跟著放假，大廳還提供了原先供應給學校的鮮乳，屬實幸運。&lt;/p&gt;
&lt;div class=&#34;gallery scroll&#34; data-pswp-gallery id=&#34;gallery-0&#34;&gt;&lt;a href=&#34;https://www.omegaatt.com/blogs/develop/2026/travel_kansai_kyoto_and_osaka/images/day_1/hotel_resol_trinity_kyoto/00_PXL_20260102_054746529.webp&#34;
           data-pswp-width=&#34;1506&#34;
           data-pswp-height=&#34;2000&#34;&gt;
          &lt;img src=&#34;https://www.omegaatt.com/blogs/develop/2026/travel_kansai_kyoto_and_osaka/images/day_1/hotel_resol_trinity_kyoto/00_PXL_20260102_054746529.webp&#34;
               alt=&#34;Gallery image 1&#34;
               class=&#34;gallery-image&#34;
               width=&#34;1506&#34;
               height=&#34;2000&#34;
               loading=&#34;lazy&#34;
               decoding=&#34;async&#34; /&gt;
        &lt;/a&gt;&lt;a href=&#34;https://www.omegaatt.com/blogs/develop/2026/travel_kansai_kyoto_and_osaka/images/day_1/hotel_resol_trinity_kyoto/01_PXL_20260102_055113849.webp&#34;
           data-pswp-width=&#34;2000&#34;
           data-pswp-height=&#34;1506&#34;&gt;
          &lt;img src=&#34;https://www.omegaatt.com/blogs/develop/2026/travel_kansai_kyoto_and_osaka/images/day_1/hotel_resol_trinity_kyoto/01_PXL_20260102_055113849.webp&#34;
               alt=&#34;Gallery image 2&#34;
               class=&#34;gallery-image&#34;
               width=&#34;2000&#34;
               height=&#34;1506&#34;
               loading=&#34;lazy&#34;
               decoding=&#34;async&#34; /&gt;
        &lt;/a&gt;&lt;a href=&#34;https://www.omegaatt.com/blogs/develop/2026/travel_kansai_kyoto_and_osaka/images/day_1/hotel_resol_trinity_kyoto/02_PANA6707.webp&#34;
           data-pswp-width=&#34;2000&#34;
           data-pswp-height=&#34;1333&#34;&gt;
          &lt;img src=&#34;https://www.omegaatt.com/blogs/develop/2026/travel_kansai_kyoto_and_osaka/images/day_1/hotel_resol_trinity_kyoto/02_PANA6707.webp&#34;
               alt=&#34;Gallery image 3&#34;
               class=&#34;gallery-image&#34;
               width=&#34;2000&#34;
               height=&#34;1333&#34;
               loading=&#34;lazy&#34;
               decoding=&#34;async&#34; /&gt;
        &lt;/a&gt;&lt;a href=&#34;https://www.omegaatt.com/blogs/develop/2026/travel_kansai_kyoto_and_osaka/images/day_1/hotel_resol_trinity_kyoto/PANA6704.webp&#34;
           data-pswp-width=&#34;2000&#34;
           data-pswp-height=&#34;1333&#34;&gt;
          &lt;img src=&#34;https://www.omegaatt.com/blogs/develop/2026/travel_kansai_kyoto_and_osaka/images/day_1/hotel_resol_trinity_kyoto/PANA6704.webp&#34;
               alt=&#34;Gallery image 4&#34;
               class=&#34;gallery-image&#34;
               width=&#34;2000&#34;
               height=&#34;1333&#34;
               loading=&#34;lazy&#34;
               decoding=&#34;async&#34; /&gt;
        &lt;/a&gt;&lt;a href=&#34;https://www.omegaatt.com/blogs/develop/2026/travel_kansai_kyoto_and_osaka/images/day_1/hotel_resol_trinity_kyoto/PXL_20260102_055831685.webp&#34;
           data-pswp-width=&#34;2000&#34;
           data-pswp-height=&#34;1506&#34;&gt;
          &lt;img src=&#34;https://www.omegaatt.com/blogs/develop/2026/travel_kansai_kyoto_and_osaka/images/day_1/hotel_resol_trinity_kyoto/PXL_20260102_055831685.webp&#34;
               alt=&#34;Gallery image 5&#34;
               class=&#34;gallery-image&#34;
               width=&#34;2000&#34;
               height=&#34;1506&#34;
               loading=&#34;lazy&#34;
               decoding=&#34;async&#34; /&gt;
        &lt;/a&gt;&lt;a href=&#34;https://www.omegaatt.com/blogs/develop/2026/travel_kansai_kyoto_and_osaka/images/day_1/hotel_resol_trinity_kyoto/PXL_20260102_055856737.webp&#34;
           data-pswp-width=&#34;1506&#34;
           data-pswp-height=&#34;2000&#34;&gt;
          &lt;img src=&#34;https://www.omegaatt.com/blogs/develop/2026/travel_kansai_kyoto_and_osaka/images/day_1/hotel_resol_trinity_kyoto/PXL_20260102_055856737.webp&#34;
               alt=&#34;Gallery image 6&#34;
               class=&#34;gallery-image&#34;
               width=&#34;1506&#34;
               height=&#34;2000&#34;
               loading=&#34;lazy&#34;
               decoding=&#34;async&#34; /&gt;
        &lt;/a&gt;&lt;a href=&#34;https://www.omegaatt.com/blogs/develop/2026/travel_kansai_kyoto_and_osaka/images/day_1/hotel_resol_trinity_kyoto/PXL_20260102_055917398.webp&#34;
           data-pswp-width=&#34;2000&#34;
           data-pswp-height=&#34;1506&#34;&gt;
          &lt;img src=&#34;https://www.omegaatt.com/blogs/develop/2026/travel_kansai_kyoto_and_osaka/images/day_1/hotel_resol_trinity_kyoto/PXL_20260102_055917398.webp&#34;
               alt=&#34;Gallery image 7&#34;
               class=&#34;gallery-image&#34;
               width=&#34;2000&#34;
               height=&#34;1506&#34;
               loading=&#34;lazy&#34;
               decoding=&#34;async&#34; /&gt;
        &lt;/a&gt;&lt;a href=&#34;https://www.omegaatt.com/blogs/develop/2026/travel_kansai_kyoto_and_osaka/images/day_1/hotel_resol_trinity_kyoto/PXL_20260102_124540263.webp&#34;
           data-pswp-width=&#34;2000&#34;
           data-pswp-height=&#34;1506&#34;&gt;
          &lt;img src=&#34;https://www.omegaatt.com/blogs/develop/2026/travel_kansai_kyoto_and_osaka/images/day_1/hotel_resol_trinity_kyoto/PXL_20260102_124540263.webp&#34;
               alt=&#34;Gallery image 8&#34;
               class=&#34;gallery-image&#34;
               width=&#34;2000&#34;
               height=&#34;1506&#34;
               loading=&#34;lazy&#34;
               decoding=&#34;async&#34; /&gt;
        &lt;/a&gt;&lt;/div&gt;
&lt;p&gt;下榻的飯店距離錦市場只有十分鐘的步行距離，放完行李後，緊接著步行前往&lt;a href=&#34;https://maps.app.goo.gl/xvMWqZqoSiTH7s4F8&#34;&gt;藤井大丸&lt;/a&gt;，體驗日本新年的「初売り」，此時室外的溫度零度，天空飄下了細雪，即便十分興奮，還是會擔心外套濕掉。我高估了我耐寒的能力，只穿了短袖、中層與棉質的薄外套就來了這零度的京都，Murphy 在大丸買了件新的羽絨外套後，我就把他身上的搶過來穿，撐過了這幾天。買完戰利品後先回到飯店放下，畢竟我們接下來還有行程，這就是飯店距離商圈近的好處。&lt;/p&gt;</description>
    </item>
    <item>
      <title>Clean Craftsmanship：在 LLM 時代重拾軟體工匠精神</title>
      <link>https://www.omegaatt.com/blogs/develop/2026/clean_craftsmanship/</link>
      <pubDate>Thu, 19 Feb 2026 00:00:00 +0000</pubDate><author>raiven.kao@gmail.com (Raiven Kao)</author>
      <guid>https://www.omegaatt.com/blogs/develop/2026/clean_craftsmanship/</guid>
      <description>&lt;h2 id=&#34;為什麼在這個時間點讀這本書&#34;&gt;為什麼在這個時間點讀這本書&lt;/h2&gt;
&lt;p&gt;最近對於如何在 LLM 時代帶領團隊一起提昇生產力感到困惑。當 AI 能在幾秒鐘內生成幾百行程式碼，「寫程式」這件事的門檻似乎降到了前所未有的低點。但門檻低了，品質呢？&lt;/p&gt;
&lt;p&gt;當「每個人」都在養龍蝦，有了生產力焦慮，TypeScript 寫的 &lt;a href=&#34;https://github.com/openclaw/openclaw&#34;&gt;OpenClaw&lt;/a&gt; 才剛出現，Python 的 &lt;a href=&#34;https://github.com/HKUDS/nanobot&#34;&gt;NanoBot&lt;/a&gt; 馬上跟上，接著是 Golang 的 &lt;a href=&#34;https://github.com/sipeed/picoclaw&#34;&gt;picoclaw&lt;/a&gt;，然後 Rust 的 &lt;a href=&#34;https://github.com/zeroclaw-labs/zeroclaw&#34;&gt;ZeroClaw&lt;/a&gt; 也來了。&lt;/p&gt;
&lt;p&gt;根據 ZeroClaw 做的 &lt;a href=&#34;https://github.com/zeroclaw-labs/zeroclaw?tab=readme-ov-file#benchmark-snapshot-zeroclaw-vs-openclaw-reproducible&#34;&gt;benchmark&lt;/a&gt;，這些 Agent 已經降到小於 5MB 的記憶體佔用與 10ms 的啟動時間，「種族」為人類的我們對於產出軟體來說還剩什麼？&lt;/p&gt;
&lt;p&gt;帶著這個問題，我決定先從自身出發，找出在 LLM 時代還能保持軟體工程「工匠精神」的誘因。於是翻開了 Robert C. Martin 的 &lt;a href=&#34;https://www.tenlong.com.tw/products/9786263339941&#34;&gt;&lt;em&gt;Clean Craftsmanship&lt;/em&gt;&lt;/a&gt;。&lt;/p&gt;
&lt;p&gt;這本書分為三個部份：&lt;strong&gt;紀律、標準、道德&lt;/strong&gt;。一半以上的篇幅在講述 TDD 這個老生常談的開發方式，但透過 TDD，我們更能知道何謂軟體的「品質」。以下是我特別書籤的幾個段落。&lt;/p&gt;
&lt;h2 id=&#34;紀律&#34;&gt;紀律&lt;/h2&gt;
&lt;h3 id=&#34;童子軍規則&#34;&gt;童子軍規則&lt;/h3&gt;
&lt;blockquote&gt;
&lt;p&gt;離開營地時，要比你來時更乾淨。&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;這也是在 &lt;em&gt;Clean Code&lt;/em&gt; 一書就提到的概念。每次微小的重構，都能小程度的減少技術債的產生。不需要一次大刀闊斧，只要每次經過一段程式碼時，順手讓它變得更好一點。&lt;/p&gt;
&lt;p&gt;讓我想到 &lt;a href=&#34;https://www.approachwithalacrity.com/claude-ne/&#34;&gt;Claude is not a senior engineer (yet)&lt;/a&gt; 這篇文章中提到的 Sweeks——一位被稱為「園丁」的 distinguished engineer，他不斷地重寫、收緊抽象，讓經過他手的程式碼都變得更乾淨。我們都想成為 Sweeks，對吧？&lt;/p&gt;
&lt;p&gt;在 LLM 時代，AI 擅長的是「組裝」現有解決方案，但它缺乏 Sweeks 那種「看到可以更好的地方就會忍不住動手」的靈魂。童子軍規則提醒我們：這份靈魂不能丟。&lt;/p&gt;
&lt;h3 id=&#34;test-doubles-的正名&#34;&gt;Test Doubles 的正名&lt;/h3&gt;
&lt;p&gt;書中透過實戰的例子講述了所有 test double：&lt;strong&gt;Dummy、Stub、Spy、Mock、Fake&lt;/strong&gt;。&lt;/p&gt;
&lt;p&gt;坦白說，我曾經在諸多 repo 中看到這些名詞卻沒有實際使用它們。頂多在 DI 時製作了一個「用於模擬 database repository 的 implement」，或是使用了 &lt;code&gt;gomock&lt;/code&gt; 這種套件來產生 mock，然後把所有替身都統稱為 mock。&lt;/p&gt;</description>
    </item>
    <item>
      <title>Golang 1.25 testing/synctest 初體驗：告別在測試中寫 time.Sleep 的日子</title>
      <link>https://www.omegaatt.com/blogs/develop/2026/golang_synctest/</link>
      <pubDate>Sun, 18 Jan 2026 00:00:00 +0000</pubDate><author>raiven.kao@gmail.com (Raiven Kao)</author>
      <guid>https://www.omegaatt.com/blogs/develop/2026/golang_synctest/</guid>
      <description>&lt;h2 id=&#34;前言&#34;&gt;前言&lt;/h2&gt;
&lt;p&gt;在我們目前的應用程式架構中，由於高度與 Kubernetes 耦合，服務啟動與運作期間需要頻繁地去讀取 K8s 中的 ConfigMap。為了達成配置熱更新（Hot Reload），我們引入了 Kubernetes client-go 中的 &lt;a href=&#34;https://pkg.go.dev/k8s.io/client-go/informers&#34;&gt;informers&lt;/a&gt; 機制來監聽 ConfigMap 的 CRUD 事件。&lt;/p&gt;
&lt;p&gt;雖然 K8s 官方提供了 fake client 讓我們能測試 informers 的邏輯，但在 Service Code 的層級，我們往往需要封裝一層更適合業務邏輯的 &lt;code&gt;ConfigWatcher&lt;/code&gt;。Golang 引以為傲的輕量級 Goroutine 與 Channel 搭配非常適合用來處理這種非同步的事件傳遞。&lt;/p&gt;
&lt;p&gt;然而，一旦涉及到 Goroutine 的非同步測試，「時間」往往就成了最大的敵人。&lt;/p&gt;
&lt;h2 id=&#34;遇到的問題不穩定的測試與魔法數字&#34;&gt;遇到的問題：不穩定的測試與魔法數字&lt;/h2&gt;
&lt;p&gt;為了模擬 ConfigMap 的變更通知，我們定義了一個 &lt;code&gt;ConfigMapWatcher&lt;/code&gt; 介面與對應的 Event 結構：&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;-webkit-text-size-adjust:none;&#34;&gt;&lt;code class=&#34;language-go&#34; data-lang=&#34;go&#34;&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&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;	&lt;span style=&#34;color:#a6e22e&#34;&gt;ConfigMapUpdateEventTypeAdded&lt;/span&gt; &lt;span style=&#34;color:#a6e22e&#34;&gt;ConfigMapUpdateEventType&lt;/span&gt; = &lt;span style=&#34;color:#66d9ef&#34;&gt;iota&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;ConfigMapUpdateEventTypeModified&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;ConfigMapUpdateEventTypeDeleted&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;type&lt;/span&gt; &lt;span style=&#34;color:#a6e22e&#34;&gt;ConfigMapUpdateEvent&lt;/span&gt; &lt;span style=&#34;color:#66d9ef&#34;&gt;struct&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;Name&lt;/span&gt;  &lt;span style=&#34;color:#66d9ef&#34;&gt;string&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;Type&lt;/span&gt;  &lt;span style=&#34;color:#a6e22e&#34;&gt;ConfigMapUpdateEventType&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;Value&lt;/span&gt; &lt;span style=&#34;color:#66d9ef&#34;&gt;map&lt;/span&gt;[&lt;span style=&#34;color:#66d9ef&#34;&gt;string&lt;/span&gt;]&lt;span style=&#34;color:#66d9ef&#34;&gt;string&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;type&lt;/span&gt; &lt;span style=&#34;color:#a6e22e&#34;&gt;ConfigMapWatcher&lt;/span&gt; &lt;span style=&#34;color:#66d9ef&#34;&gt;interface&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;Watch&lt;/span&gt;(&lt;span style=&#34;color:#a6e22e&#34;&gt;ctx&lt;/span&gt; &lt;span style=&#34;color:#a6e22e&#34;&gt;context&lt;/span&gt;.&lt;span style=&#34;color:#a6e22e&#34;&gt;Context&lt;/span&gt;, &lt;span style=&#34;color:#a6e22e&#34;&gt;eventCh&lt;/span&gt; &lt;span style=&#34;color:#66d9ef&#34;&gt;chan&lt;/span&gt;&lt;span style=&#34;color:#f92672&#34;&gt;&amp;lt;-&lt;/span&gt; &lt;span style=&#34;color:#a6e22e&#34;&gt;ConfigMapUpdateEvent&lt;/span&gt;) &lt;span style=&#34;color:#66d9ef&#34;&gt;error&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;接著，我們很自然地在 testing 中實作了一個 fake 物件來模擬事件發送：&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;-webkit-text-size-adjust:none;&#34;&gt;&lt;code class=&#34;language-go&#34; data-lang=&#34;go&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#66d9ef&#34;&gt;type&lt;/span&gt; &lt;span style=&#34;color:#a6e22e&#34;&gt;fakeConfigMapWatcher&lt;/span&gt; &lt;span style=&#34;color:#66d9ef&#34;&gt;struct&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;injectCh&lt;/span&gt;  &lt;span style=&#34;color:#66d9ef&#34;&gt;chan&lt;/span&gt; &lt;span style=&#34;color:#a6e22e&#34;&gt;ConfigMapUpdateEvent&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;watchErr&lt;/span&gt;  &lt;span style=&#34;color:#66d9ef&#34;&gt;error&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;watchOnce&lt;/span&gt; &lt;span style=&#34;color:#a6e22e&#34;&gt;sync&lt;/span&gt;.&lt;span style=&#34;color:#a6e22e&#34;&gt;Once&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;func&lt;/span&gt; &lt;span style=&#34;color:#a6e22e&#34;&gt;newFakeConfigMapWatcher&lt;/span&gt;() &lt;span style=&#34;color:#f92672&#34;&gt;*&lt;/span&gt;&lt;span style=&#34;color:#a6e22e&#34;&gt;fakeConfigMapWatcher&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:#f92672&#34;&gt;&amp;amp;&lt;/span&gt;&lt;span style=&#34;color:#a6e22e&#34;&gt;fakeConfigMapWatcher&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;injectCh&lt;/span&gt;: make(&lt;span style=&#34;color:#66d9ef&#34;&gt;chan&lt;/span&gt; &lt;span style=&#34;color:#a6e22e&#34;&gt;ConfigMapUpdateEvent&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;func&lt;/span&gt; (&lt;span style=&#34;color:#a6e22e&#34;&gt;f&lt;/span&gt; &lt;span style=&#34;color:#f92672&#34;&gt;*&lt;/span&gt;&lt;span style=&#34;color:#a6e22e&#34;&gt;fakeConfigMapWatcher&lt;/span&gt;) &lt;span style=&#34;color:#a6e22e&#34;&gt;sendEvent&lt;/span&gt;(&lt;span style=&#34;color:#a6e22e&#34;&gt;event&lt;/span&gt; &lt;span style=&#34;color:#a6e22e&#34;&gt;ConfigMapUpdateEvent&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;f&lt;/span&gt;.&lt;span style=&#34;color:#a6e22e&#34;&gt;injectCh&lt;/span&gt; &lt;span style=&#34;color:#f92672&#34;&gt;&amp;lt;-&lt;/span&gt; &lt;span style=&#34;color:#a6e22e&#34;&gt;event&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;func&lt;/span&gt; (&lt;span style=&#34;color:#a6e22e&#34;&gt;f&lt;/span&gt; &lt;span style=&#34;color:#f92672&#34;&gt;*&lt;/span&gt;&lt;span style=&#34;color:#a6e22e&#34;&gt;fakeConfigMapWatcher&lt;/span&gt;) &lt;span style=&#34;color:#a6e22e&#34;&gt;Watch&lt;/span&gt;(&lt;span style=&#34;color:#a6e22e&#34;&gt;ctx&lt;/span&gt; &lt;span style=&#34;color:#a6e22e&#34;&gt;context&lt;/span&gt;.&lt;span style=&#34;color:#a6e22e&#34;&gt;Context&lt;/span&gt;, &lt;span style=&#34;color:#a6e22e&#34;&gt;eventCh&lt;/span&gt; &lt;span style=&#34;color:#66d9ef&#34;&gt;chan&lt;/span&gt;&lt;span style=&#34;color:#f92672&#34;&gt;&amp;lt;-&lt;/span&gt; &lt;span style=&#34;color:#a6e22e&#34;&gt;ConfigMapUpdateEvent&lt;/span&gt;) &lt;span style=&#34;color:#66d9ef&#34;&gt;error&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;f&lt;/span&gt;.&lt;span style=&#34;color:#a6e22e&#34;&gt;watchErr&lt;/span&gt; &lt;span style=&#34;color:#f92672&#34;&gt;!=&lt;/span&gt; &lt;span style=&#34;color:#66d9ef&#34;&gt;nil&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:#a6e22e&#34;&gt;f&lt;/span&gt;.&lt;span style=&#34;color:#a6e22e&#34;&gt;watchErr&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:#a6e22e&#34;&gt;f&lt;/span&gt;.&lt;span style=&#34;color:#a6e22e&#34;&gt;watchOnce&lt;/span&gt;.&lt;span style=&#34;color:#a6e22e&#34;&gt;Do&lt;/span&gt;(&lt;span style=&#34;color:#66d9ef&#34;&gt;func&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;go&lt;/span&gt; &lt;span style=&#34;color:#66d9ef&#34;&gt;func&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;for&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;select&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;case&lt;/span&gt; &lt;span style=&#34;color:#f92672&#34;&gt;&amp;lt;-&lt;/span&gt;&lt;span style=&#34;color:#a6e22e&#34;&gt;ctx&lt;/span&gt;.&lt;span style=&#34;color:#a6e22e&#34;&gt;Done&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&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;				&lt;span style=&#34;color:#66d9ef&#34;&gt;case&lt;/span&gt; &lt;span style=&#34;color:#a6e22e&#34;&gt;e&lt;/span&gt; &lt;span style=&#34;color:#f92672&#34;&gt;:=&lt;/span&gt; &lt;span style=&#34;color:#f92672&#34;&gt;&amp;lt;-&lt;/span&gt;&lt;span style=&#34;color:#a6e22e&#34;&gt;f&lt;/span&gt;.&lt;span style=&#34;color:#a6e22e&#34;&gt;injectCh&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;eventCh&lt;/span&gt; &lt;span style=&#34;color:#f92672&#34;&gt;&amp;lt;-&lt;/span&gt; &lt;span style=&#34;color:#a6e22e&#34;&gt;e&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&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;nil&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;問題來了，當我們在寫單元測試時，呼叫 &lt;code&gt;sendEvent&lt;/code&gt; 將事件送入 channel 後，消費者端（也就是我們的業務邏輯 Goroutine）並不會「立刻」收到並處理完成。為了確保 &lt;code&gt;assert&lt;/code&gt; 斷言執行時，業務邏輯已經跑完了，我們被迫在測試中加入 &lt;code&gt;time.Sleep&lt;/code&gt;：&lt;/p&gt;</description>
    </item>
    <item>
      <title>RouterOS v7.21 container &#43; App 組合拳，你的下一台 Router 也可以是 All In One Homelab</title>
      <link>https://www.omegaatt.com/blogs/develop/2026/routeros_v7_21_container_and_app/</link>
      <pubDate>Sat, 17 Jan 2026 00:00:00 +0000</pubDate><author>raiven.kao@gmail.com (Raiven Kao)</author>
      <guid>https://www.omegaatt.com/blogs/develop/2026/routeros_v7_21_container_and_app/</guid>
      <description>&lt;h2 id=&#34;前言&#34;&gt;前言&lt;/h2&gt;
&lt;p&gt;自從 RouterOS 在 v7.13 引入 container 功能後，這為我們這些 Homelab 愛好者開啟了一扇新的大門。我們終於可以在路由器上原生執行 container，這意味著一些網路相關的基礎設施服務（Infrastructure Services）可以從原本的伺服器中遷移出來。&lt;/p&gt;
&lt;p&gt;以我為例，過去我的 Adguard Home 是 deploy 在我的 Homelab 伺服器群中。然而，作為一個喜歡「折騰」的工程師，Homelab 難免會因為硬體更換、系統升級或是實驗新架構而需要重啟。每當這時候，家裡的網路 DNS 解析就會中斷，隨之而來的就是家人的抱怨。&lt;/p&gt;
&lt;p&gt;為了解決這個單點故障（SPOF）的問題，我決定將 Adguard Home 改為 deploy 在 Router (RB5009UG) 上。這樣做不僅減少了物理上的 latency，更重要的是將「網路服務」與「應用服務」解耦，避免因為 Homelab 的維護而導致全家斷網。&lt;/p&gt;
&lt;p&gt;這是遷移後的狀態：&lt;/p&gt;
&lt;pre tabindex=&#34;0&#34;&gt;&lt;code&gt;[user@RB5009UG] &amp;gt; container print
Flags: R - RUNNING
Columns: NAME, ROOT-DIR, INTERFACE, MOUNTLISTS, ENTRYPOINT
#   NAME                                  ROOT-DIR                     INTERFACE    MOUNTLISTS         ENTRYPOINT
0 R b51e0424-8fce-4c3d-b4bb-6493f76aa24b  /usb1/container/adguardhome  adguardhome  adguardhomeConfig  /opt/adguardhome/AdGuardHome
&lt;/code&gt;&lt;/pre&gt;&lt;h2 id=&#34;routeros-v721-的新變革app&#34;&gt;RouterOS v7.21 的新變革：App&lt;/h2&gt;
&lt;p&gt;&lt;img loading=&#34;lazy&#34; src=&#34;https://www.omegaatt.com/blogs/develop/2026/routeros_v7_21_container_and_app/images/packages.webp&#34;&gt;&lt;/p&gt;
&lt;p&gt;而在最近發布的 &lt;a href=&#34;https://forum.mikrotik.com/t/v7-21-stable-is-released/267773&#34;&gt;RouterOS v7.21&lt;/a&gt; 中，Mikrotik 給我們帶來了更方便的 container 部署方式，他們稱呼為 App。&lt;/p&gt;</description>
    </item>
    <item>
      <title>Container Image Optimization 那些年我們寫錯的 Dockerfile</title>
      <link>https://www.omegaatt.com/blogs/develop/2026/container_image_optimization/</link>
      <pubDate>Wed, 14 Jan 2026 00:00:00 +0000</pubDate><author>raiven.kao@gmail.com (Raiven Kao)</author>
      <guid>https://www.omegaatt.com/blogs/develop/2026/container_image_optimization/</guid>
      <description>&lt;p&gt;最近在檢視公司內部的專案時，針對其中一個 container image 進行了優化。在一個簡單的 commit 後，我們的 image size 從 &lt;strong&gt;1.82GB&lt;/strong&gt; 修正到了 &lt;strong&gt;1.18GB&lt;/strong&gt;。&lt;/p&gt;
&lt;p&gt;透過 &lt;a href=&#34;https://github.com/wagoodman/dive&#34;&gt;dive&lt;/a&gt; 查看，Image Efficiency 更是從不及格的 &lt;strong&gt;69%&lt;/strong&gt; 飆升到了 &lt;strong&gt;99%&lt;/strong&gt;。&lt;/p&gt;
&lt;p&gt;這讓我回想起過去寫 Dockerfile 時，常常因為不了解 Docker Layer 的機制，或是為了寫起來「方便」，而踩到了許多效能與安全的地雷。&lt;/p&gt;
&lt;h2 id=&#34;致命的-chown&#34;&gt;致命的 &lt;code&gt;chown&lt;/code&gt;&lt;/h2&gt;
&lt;p&gt;這次優化的核心，其實源自於一個非常常見的操作：修改檔案權限。&lt;/p&gt;
&lt;p&gt;在我們的案例中，Dockerfile 原本是這樣寫的：&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;-webkit-text-size-adjust:none;&#34;&gt;&lt;code class=&#34;language-dockerfile&#34; data-lang=&#34;dockerfile&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#75715e&#34;&gt;# Bad Practice: recursive chown after copy&lt;/span&gt;&lt;span style=&#34;color:#960050;background-color:#1e0010&#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;FROM&lt;/span&gt; &lt;span style=&#34;color:#e6db74&#34;&gt;ubuntu:22.04&lt;/span&gt;&lt;span style=&#34;color:#960050;background-color:#1e0010&#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:#960050;background-color:#1e0010&#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;WORKDIR&lt;/span&gt; &lt;span style=&#34;color:#e6db74&#34;&gt;/app&lt;/span&gt;&lt;span style=&#34;color:#960050;background-color:#1e0010&#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;COPY&lt;/span&gt; . .&lt;span style=&#34;color:#960050;background-color:#1e0010&#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:#960050;background-color:#1e0010&#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:#75715e&#34;&gt;# ... install dependencies ...&lt;/span&gt;&lt;span style=&#34;color:#960050;background-color:#1e0010&#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;RUN&lt;/span&gt; dpkg -i packages/*.deb&lt;span style=&#34;color:#960050;background-color:#1e0010&#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:#960050;background-color:#1e0010&#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:#75715e&#34;&gt;# Change ownership for security reasons&lt;/span&gt;&lt;span style=&#34;color:#960050;background-color:#1e0010&#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;RUN&lt;/span&gt; groupadd -r appuser &lt;span style=&#34;color:#f92672&#34;&gt;&amp;amp;&amp;amp;&lt;/span&gt; useradd -r -g appuser appuser&lt;span style=&#34;color:#960050;background-color:#1e0010&#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;RUN&lt;/span&gt; chown -R appuser:appuser /app&lt;span style=&#34;color:#960050;background-color:#1e0010&#34;&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;看起來邏輯很正確：把檔案複製進去，安裝套件，最後為了安全性將檔案權限交給非 root 使用者。&lt;/p&gt;</description>
    </item>
    <item>
      <title>Golang 1.26 新特性在數量上史無前例的多</title>
      <link>https://www.omegaatt.com/blogs/develop/2026/golang_1_26_huge_features/</link>
      <pubDate>Tue, 13 Jan 2026 00:00:00 +0000</pubDate><author>raiven.kao@gmail.com (Raiven Kao)</author>
      <guid>https://www.omegaatt.com/blogs/develop/2026/golang_1_26_huge_features/</guid>
      <description>&lt;p&gt;隨著時間來到 2026 年初，Go 語言迎來了 1.26 版本的更新。如果說 Go 1.18 的泛型是語言層面的重大變革，那麼 Go 1.26 則是在「數量」與「廣度」上讓人感到驚艷的一次釋出。從語言特性的語法糖、標準庫的實用擴充，到 Runtime 效能的顯著提升（Green Tea GC），甚至是實驗性的 SIMD 支援，這次的更新內容豐富到讓人目不暇給。&lt;/p&gt;
&lt;p&gt;本文將挑選其中幾個我認為對日常開發最重要、或最有趣的改動來進行介紹。&lt;/p&gt;
&lt;h2 id=&#34;語言層面的改動&#34;&gt;語言層面的改動&lt;/h2&gt;
&lt;h3 id=&#34;newexpr終於不用再寫輔助函數了&#34;&gt;&lt;code&gt;new(expr)&lt;/code&gt;：終於不用再寫輔助函數了&lt;/h3&gt;
&lt;p&gt;在 Go 1.26 之前，如果我們想要取得一個基本型別（如 &lt;code&gt;int&lt;/code&gt;, &lt;code&gt;bool&lt;/code&gt;, &lt;code&gt;string&lt;/code&gt;）的 pointer，通常需要宣告一個變數或者寫一個輔助函數。這在定義 struct 的 literal 時特別煩人，尤其是當 struct 欄位是 &lt;code&gt;*bool&lt;/code&gt; 或 &lt;code&gt;*int&lt;/code&gt; 用來區分「零值」與「未設定」的時候。&lt;/p&gt;
&lt;p&gt;回顧過去，我們為了這個小需求付出了不少努力：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;在 Go 1.18 泛型出現之前：我們經常需要定義一堆如 &lt;code&gt;Int64Ptr(v int64) *int64&lt;/code&gt; 或 &lt;code&gt;Float64Ptr(v float64) *float64&lt;/code&gt; 的輔助函數（AWS SDK 的使用者應該對此非常熟悉）。&lt;/li&gt;
&lt;li&gt;匿名函數大法：如果不想要定義全域的輔助函數，有時甚至會看到像 &lt;code&gt;enabled := func(b bool) *bool { return &amp;amp;b }(true)&lt;/code&gt; 這種冗長且難讀的寫法。&lt;/li&gt;
&lt;li&gt;泛型時代：雖然可以用一個通用的 &lt;code&gt;ptr[T]&lt;/code&gt; 解決，但還是需要額外的程式碼。&lt;/li&gt;
&lt;/ul&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;-webkit-text-size-adjust:none;&#34;&gt;&lt;code class=&#34;language-go&#34; data-lang=&#34;go&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#66d9ef&#34;&gt;func&lt;/span&gt; &lt;span style=&#34;color:#a6e22e&#34;&gt;ptr&lt;/span&gt;[&lt;span style=&#34;color:#a6e22e&#34;&gt;T&lt;/span&gt; &lt;span style=&#34;color:#66d9ef&#34;&gt;any&lt;/span&gt;](&lt;span style=&#34;color:#a6e22e&#34;&gt;v&lt;/span&gt; &lt;span style=&#34;color:#a6e22e&#34;&gt;T&lt;/span&gt;) &lt;span style=&#34;color:#f92672&#34;&gt;*&lt;/span&gt;&lt;span style=&#34;color:#a6e22e&#34;&gt;T&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:#f92672&#34;&gt;&amp;amp;&lt;/span&gt;&lt;span style=&#34;color:#a6e22e&#34;&gt;v&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;type&lt;/span&gt; &lt;span style=&#34;color:#a6e22e&#34;&gt;Config&lt;/span&gt; &lt;span style=&#34;color:#66d9ef&#34;&gt;struct&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;Enabled&lt;/span&gt; &lt;span style=&#34;color:#f92672&#34;&gt;*&lt;/span&gt;&lt;span style=&#34;color:#66d9ef&#34;&gt;bool&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:#a6e22e&#34;&gt;conf&lt;/span&gt; &lt;span style=&#34;color:#f92672&#34;&gt;:=&lt;/span&gt; &lt;span style=&#34;color:#a6e22e&#34;&gt;Config&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;Enabled&lt;/span&gt;: &lt;span style=&#34;color:#a6e22e&#34;&gt;ptr&lt;/span&gt;(&lt;span style=&#34;color:#66d9ef&#34;&gt;true&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;在 Go 1.26 中，內建的 &lt;code&gt;new&lt;/code&gt; 函數得到了增強，現在它不僅接受型別，還可以直接接受表達式（Expression）。&lt;/p&gt;</description>
    </item>
    <item>
      <title>從 GrapheneOS 回歸原廠：Google Pixel 5 刷機紀錄</title>
      <link>https://www.omegaatt.com/blogs/develop/2026/recovery_pixel_to_factory_firmware/</link>
      <pubDate>Sat, 10 Jan 2026 00:00:00 +0000</pubDate><author>raiven.kao@gmail.com (Raiven Kao)</author>
      <guid>https://www.omegaatt.com/blogs/develop/2026/recovery_pixel_to_factory_firmware/</guid>
      <description>&lt;p&gt;最近整理抽屜時，翻出了這台陪伴我一段時間的 Google Pixel 5。&lt;/p&gt;
&lt;p&gt;身為一個喜歡折騰的開發者，早在它退役成為備用機時，我就第一時間把它刷成了 &lt;a href=&#34;https://grapheneos.org/&#34;&gt;GrapheneOS&lt;/a&gt;。不得不說，GrapheneOS 在隱私保護和安全性上做得真的很好，沒有 Google Play Services 的純淨體驗也別有一番風味（雖然依賴 Sandboxed Google Play 還是能解決大部分 App 的問題）。&lt;/p&gt;
&lt;p&gt;但就在前幾天，或許單純是想念 Pixel 原生系統的那些獨家功能（原生相機等等），我決定讓它「回歸原廠」。&lt;/p&gt;
&lt;p&gt;原本以為又要經歷一番 &lt;code&gt;adb&lt;/code&gt; 和 &lt;code&gt;fastboot&lt;/code&gt; 的指令轟炸，還要自己去 Google 官網下載好幾 GB 的 Factory Image 壓縮檔，解壓後再祈禱 &lt;code&gt;flash-all.sh&lt;/code&gt; 不要噴錯。&lt;/p&gt;
&lt;p&gt;沒想到現在 Google 官方提供了一個方便的網頁工具：&lt;a href=&#34;https://flash.android.com/&#34;&gt;Android Flash Tool&lt;/a&gt;。&lt;/p&gt;
&lt;p&gt;這篇文章簡單紀錄一下如何從 GrapheneOS 這種 Custom ROM，透過官方工具刷回 Stock Firmware 的過程。&lt;/p&gt;
&lt;h2 id=&#34;為什麼選擇-android-flash-tool&#34;&gt;為什麼選擇 Android Flash Tool？&lt;/h2&gt;
&lt;p&gt;以前刷機的標準作業流程（SOP）：&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;搜尋正確的 Factory Image（還要對型號，買到電信商鎖定版就哭哭）。&lt;/li&gt;
&lt;li&gt;下載 Android SDK Platform-Tools。&lt;/li&gt;
&lt;li&gt;解鎖 Bootloader。&lt;/li&gt;
&lt;li&gt;執行 &lt;code&gt;flash-all&lt;/code&gt; script。&lt;/li&gt;
&lt;li&gt;遇到驅動程式問題、傳輸線問題、路徑問題&amp;hellip;&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;現在的 Android Flash Tool 直接把這些步驟簡化了：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;WebUSB: 直接透過瀏覽器與手機溝通，不用煩惱太底層的驅動設定（至少在 macOS/Linux 上是這樣）。&lt;/li&gt;
&lt;li&gt;Auto Detect: 自動偵測裝置型號，自動下載對應的最新版韌體。&lt;/li&gt;
&lt;li&gt;Fool-proof: 圖形化介面引導你開啟 USB Debugging 和 OEM Unlocking。&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id=&#34;事前準備&#34;&gt;事前準備&lt;/h2&gt;
&lt;p&gt;雖然工具很強大，但基本的準備還是要有的：&lt;/p&gt;</description>
    </item>
    <item>
      <title>2025 Recap</title>
      <link>https://www.omegaatt.com/blogs/develop/2025/2025_recap/</link>
      <pubDate>Sun, 28 Dec 2025 00:00:00 +0000</pubDate><author>raiven.kao@gmail.com (Raiven Kao)</author>
      <guid>https://www.omegaatt.com/blogs/develop/2025/2025_recap/</guid>
      <description>&lt;blockquote&gt;
&lt;p&gt;當兩個人意見不一致，可能其中一個人是錯的。如錯錯的人是你，難道你不想知道嗎？&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;出自 Ray Dalio 的《原則》一書，是我 2025 年對自己的提問。&lt;/p&gt;
&lt;p&gt;接續  &lt;a href=&#34;https://www.omegaatt.com/blogs/develop/2023/2023_outlook&#34;&gt;2023 年展望&lt;/a&gt;，與 &lt;a href=&#34;https://www.omegaatt.com/blogs/develop/2024/2024_recap&#34;&gt;2024 Recap&lt;/a&gt;，用這篇文章來紀錄今年的自己。&lt;/p&gt;
&lt;p&gt;可以搭配今年度最常聽的音樂 Again (Yui Acoustic Version)&lt;/p&gt;
&lt;iframe width=&#34;560&#34; height=&#34;315&#34; src=&#34;https://www.youtube.com/embed/R99TGOr-Iec?si=B03XZM-AMXg9qbZ5&#34; title=&#34;YouTube video player&#34; frameborder=&#34;0&#34; allow=&#34;accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture; web-share&#34; referrerpolicy=&#34;strict-origin-when-cross-origin&#34; allowfullscreen&gt;&lt;/iframe&gt;
&lt;h2 id=&#34;工作&#34;&gt;工作&lt;/h2&gt;
&lt;p&gt;去年把工作放到最後，今年則是放到第一位。今年換到了新的公司、新的工作崗位，從十幾位 RD 的新創公司，換到了有一千多位 RD 的大公司。即便已經沒有過去擔憂的「過度官僚」文化，但仍在職也不好多說什麼。&lt;/p&gt;
&lt;p&gt;整體來說目前待的團隊，是夾雜著無趣卻也有趣的產品，無趣的點在於產品本身並不性感，目標客群是企業客戶，幾乎沒有什麼創新的概念。有趣的點在於，如何在有限的時間內拓展產品線，並且以 UX 的角度來思考企業客戶如何使用產品，並花費更多時間在前期的 design，而不是單就功能進行開發，時常會需要花費八成的時間在前期規劃，真正的開發時間就是靠 AI(LLM) 解決。&lt;/p&gt;
&lt;p&gt;隨著團隊拓展，我也承擔了 mentor 的責任，第一次帶新人多少有些徬徨與無助。隨著團隊擴展，漸漸的開始反思現代軟體開發，一件一個人能完成的事情被拆成四五個人都要有貢獻，導致交付速度不升反降，這樣真的是正確的嗎？我想還需要更多的時間積累才能找到答案。&lt;/p&gt;
&lt;p&gt;不得不提的就是，目前的公司有「近乎無限」的 AI 資源（特別指 LLM），讓手速不再成為寫程式的瓶頸。與前東家不同的是，這間公司是在今年才開始轉型 AI First，並且透過 sharing &amp;amp; AI Hackathon 來推廣 AI 在內部開發與產品上的使用。不過觀察下來，年初的 AI 使用率應該是不高的，就連我這個 AI 質疑論者，都能被邀請去 All Hands 進行 AI 使用的 session。&lt;/p&gt;
&lt;h2 id=&#34;健康&#34;&gt;健康&lt;/h2&gt;
&lt;p&gt;今年在健康上只能說完全荒廢，與去年底期許的目標，不僅完全沒有維持，更糟糕的是因為通勤距離變遠了，導致運動時間大幅下降。&lt;/p&gt;
&lt;p&gt;但&amp;hellip;&lt;/p&gt;
&lt;p&gt;我想這一切都只是藉口，單純只是變懶惰、沒了動力。只能把希望寄託在 2026 年，能透過「12周做完一年工作」這本書內提到的方法，不設定目標，而是設定行動，先把行動給完成再看看怎麼推進了。&lt;/p&gt;
&lt;h2 id=&#34;旅遊&#34;&gt;旅遊&lt;/h2&gt;
&lt;p&gt;隨著離開新創公司，又回到了下班不認識的那個自己，今年多數旅遊都是跟 Murphy 兩人，連爬山也變少了，希望 2026 年能投入更多時間在旅遊上。&lt;/p&gt;</description>
    </item>
    <item>
      <title>Helm Smart Resource：讓你的 Chart 學會與既有資源和平共處</title>
      <link>https://www.omegaatt.com/blogs/develop/2025/helm_smart_resource/</link>
      <pubDate>Sat, 22 Nov 2025 00:00:00 +0000</pubDate><author>raiven.kao@gmail.com (Raiven Kao)</author>
      <guid>https://www.omegaatt.com/blogs/develop/2025/helm_smart_resource/</guid>
      <description>&lt;h2 id=&#34;前言&#34;&gt;前言&lt;/h2&gt;
&lt;p&gt;在 Kubernetes 的世界裡，Helm 無疑是管理應用程式部署的霸主。它標準化了資源的定義，讓我們可以用宣告式的方式管理整套系統。然而，在真實世界的維運場景中，事情往往沒那麼單純。&lt;/p&gt;
&lt;p&gt;我們常遇到一種尷尬的情況：某些資源（例如 Database 的 Secret、外部系統的 ConfigMap）可能在 Helm Chart 安裝之前就已經由維運人員手動建立，或是由另一個流程（如 Terraform）預先準備好了。&lt;/p&gt;
&lt;p&gt;這時候，如果直接執行 &lt;code&gt;helm install&lt;/code&gt;，往往會收到 &amp;ldquo;resource already exists&amp;rdquo; 的錯誤；如果使用 &lt;code&gt;helm upgrade --install&lt;/code&gt;，又擔心 Helm 會覆蓋掉這些既有設定。&lt;/p&gt;
&lt;p&gt;這篇文章將分享一種「Smart Resource」的設計模式，透過 Helm 的 &lt;code&gt;lookup&lt;/code&gt; 函數與樣板邏輯，讓你的 Chart 能夠聰明地判斷：「這東西是我管的嗎？如果是，我才動它；如果不是，我就尊重現狀。」&lt;/p&gt;
&lt;h2 id=&#34;核心難題ownership&#34;&gt;核心難題：Ownership&lt;/h2&gt;
&lt;p&gt;在 Kubernetes 中，資源的「所有權」觀念至關重要。Helm 預設認為它 release 中的所有資源都應該由它全權管理。但當我們需要與外部資源協作時，我們需要更細緻的控制。&lt;/p&gt;
&lt;p&gt;我們的目標很明確：&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;若資源不存在：建立它，並標記為 Helm 管理。&lt;/li&gt;
&lt;li&gt;若資源已存在且由 Helm 管理：更新它（Patch/Merge）。&lt;/li&gt;
&lt;li&gt;若資源已存在但由外部管理：保持原狀，不進行覆蓋或刪除。&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;為了達成這個目標，我們需要一個輔助函數來判斷資源的歸屬權。&lt;/p&gt;
&lt;h2 id=&#34;實作細節&#34;&gt;實作細節&lt;/h2&gt;
&lt;h3 id=&#34;1-定義所有權檢查&#34;&gt;1. 定義所有權檢查&lt;/h3&gt;
&lt;p&gt;首先，我們在 &lt;code&gt;_helpers.tpl&lt;/code&gt; 中定義一個檢查函數。Helm 會在它建立的資源上打上特定的 Annotations（&lt;code&gt;meta.helm.sh/release-name&lt;/code&gt; 和 &lt;code&gt;meta.helm.sh/release-namespace&lt;/code&gt;）。我們可以利用這一點來判斷資源是否屬於當前的 Release。&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;-webkit-text-size-adjust:none;&#34;&gt;&lt;code class=&#34;language-yaml&#34; data-lang=&#34;yaml&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;{{&lt;span style=&#34;color:#ae81ff&#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:#ae81ff&#34;&gt;Check if a resource is owned by this Helm release&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:#ae81ff&#34;&gt;Returns &amp;#34;true&amp;#34; or &amp;#34;false&amp;#34; as string for stable piping&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:#75715e&#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:#ae81ff&#34;&gt;define &amp;#34;visionone-filesecurity.isOwnedByRelease&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:#ae81ff&#34;&gt;$resource := .resource -}}&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:#ae81ff&#34;&gt;$releaseName := .releaseName -}}&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:#ae81ff&#34;&gt;$releaseNamespace := .releaseNamespace -}}&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:#ae81ff&#34;&gt;$owned := and $resource&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:#ae81ff&#34;&gt;(hasKey $resource.metadata &amp;#34;annotations&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:#ae81ff&#34;&gt;(eq (get $resource.metadata.annotations &amp;#34;meta.helm.sh/release-name&amp;#34;) $releaseName)&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:#ae81ff&#34;&gt;(eq (get $resource.metadata.annotations &amp;#34;meta.helm.sh/release-namespace&amp;#34;) $releaseNamespace)&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:#ae81ff&#34;&gt;printf &amp;#34;%t&amp;#34; $owned }}&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:#ae81ff&#34;&gt;end -}}&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;這段程式碼邏輯很簡單：只有當資源存在，且其 Annotations 中的 Release Name 與 Namespace 都與當前 Release 相符時，才視為「Owned」。&lt;/p&gt;</description>
    </item>
    <item>
      <title>Interface 不是有開就好：從一個 PR 來看抽象化的重要性</title>
      <link>https://www.omegaatt.com/blogs/develop/2025/interface-is-not-just-about-creating-one/</link>
      <pubDate>Sat, 04 Oct 2025 00:00:00 +0000</pubDate><author>raiven.kao@gmail.com (Raiven Kao)</author>
      <guid>https://www.omegaatt.com/blogs/develop/2025/interface-is-not-just-about-creating-one/</guid>
      <description>&lt;h2 id=&#34;前言&#34;&gt;前言&lt;/h2&gt;
&lt;p&gt;最近團隊正在開發一個新產品，其中一個核心功能需要 client 與 server 之間進行即時、雙向的溝通。經過一番技術評估，我們決定採用 WebSocket 來實現這個需求。&lt;/p&gt;
&lt;p&gt;身為一個良好習慣的開發團隊，我們在開發初期就導入了依賴注入（Dependency Injection），希望透過界面（Interface）來解耦商業邏輯與具體的實作，這樣不僅能提高程式碼的可測試性，未來在更換底層實作時也能更加輕鬆。&lt;/p&gt;
&lt;p&gt;一切聽起來都很美好，直到我在一次 Code Review 中，看到了一段熟悉的程式碼。&lt;/p&gt;
&lt;h2 id=&#34;一個-pr-的故事&#34;&gt;一個 PR 的故事&lt;/h2&gt;
&lt;p&gt;在我們的 Domain Layer，也就是處理核心商業邏輯的地方，我看到同事定義了下面這個 interface：&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;-webkit-text-size-adjust:none;&#34;&gt;&lt;code class=&#34;language-go&#34; data-lang=&#34;go&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#75715e&#34;&gt;// package/to/domain/service.go&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:#75715e&#34;&gt;// WebSocketService defines the interface for websocket communication.&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;type&lt;/span&gt; &lt;span style=&#34;color:#a6e22e&#34;&gt;WebSocketService&lt;/span&gt; &lt;span style=&#34;color:#66d9ef&#34;&gt;interface&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:#75715e&#34;&gt;// StartAndLinsten starts the service and listens for incoming messages.&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;StartAndLinsten&lt;/span&gt;(&lt;span style=&#34;color:#a6e22e&#34;&gt;ctx&lt;/span&gt; &lt;span style=&#34;color:#a6e22e&#34;&gt;context&lt;/span&gt;.&lt;span style=&#34;color:#a6e22e&#34;&gt;Context&lt;/span&gt;) &lt;span style=&#34;color:#66d9ef&#34;&gt;error&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:#75715e&#34;&gt;// Send sends a message to the client.&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;Send&lt;/span&gt;(&lt;span style=&#34;color:#a6e22e&#34;&gt;ctx&lt;/span&gt; &lt;span style=&#34;color:#a6e22e&#34;&gt;context&lt;/span&gt;.&lt;span style=&#34;color:#a6e22e&#34;&gt;Context&lt;/span&gt;, &lt;span style=&#34;color:#a6e22e&#34;&gt;message&lt;/span&gt; &lt;span style=&#34;color:#66d9ef&#34;&gt;any&lt;/span&gt;) &lt;span style=&#34;color:#66d9ef&#34;&gt;error&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:#75715e&#34;&gt;// ... other methods&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;第一眼看過去，好像沒什麼大問題。有名稱、有方法、也確實是個 interface。然而，當我細看 &lt;code&gt;WebSocketService&lt;/code&gt; 這個命名時，總覺得哪裡怪怪的。&lt;/p&gt;
&lt;p&gt;於是我在 PR 上留下了這樣的 comment：&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;這個界面主要是抽象化 client 與 server 間的互動，不應該侷限於 WebSocket 這個 Protocol。假如我們未來要換成使用 socket.io 或是 gRPC stream，是不是連 domain 層的 interface 也要跟著改動？&lt;/p&gt;</description>
    </item>
    <item>
      <title>監控你的執行檔：初探 watchexec</title>
      <link>https://www.omegaatt.com/blogs/develop/2025/watch_your_exec_watchexec/</link>
      <pubDate>Sat, 06 Sep 2025 00:00:00 +0000</pubDate><author>raiven.kao@gmail.com (Raiven Kao)</author>
      <guid>https://www.omegaatt.com/blogs/develop/2025/watch_your_exec_watchexec/</guid>
      <description>前端開發有 liveserver，後端開發有 air，那 TUI 開發呢？本文記錄了我在開發 Bubbletea 應用時，從 air 轉向 watchexec 的心路歷程，以及如何使用這個通用工具來優雅地實現終端機應用的熱重載。</description>
    </item>
    <item>
      <title>用 Golang Bubbletea 打造終端機應用：從 Hello World 到多頁面架構</title>
      <link>https://www.omegaatt.com/blogs/develop/2025/golang_bubbletea_experience/</link>
      <pubDate>Fri, 05 Sep 2025 00:00:00 +0000</pubDate><author>raiven.kao@gmail.com (Raiven Kao)</author>
      <guid>https://www.omegaatt.com/blogs/develop/2025/golang_bubbletea_experience/</guid>
      <description>探索如何使用 Golang 的 Bubbletea 函式庫，基於 Elm 架構，從零開始打造一個互動式終端機應用（TUI）。本文將從一個簡單的計數器範例，逐步引導你建構出一個類似 Web 應用的多頁面架構，並分享整個生命週期中的關鍵概念與注意事項。</description>
    </item>
    <item>
      <title>初探 Tauri：為了解決朋友的繁瑣任務，寫了個桌面 App</title>
      <link>https://www.omegaatt.com/blogs/develop/2025/tauri_first_look/</link>
      <pubDate>Wed, 27 Aug 2025 00:00:00 +0000</pubDate><author>raiven.kao@gmail.com (Raiven Kao)</author>
      <guid>https://www.omegaatt.com/blogs/develop/2025/tauri_first_look/</guid>
      <description>一個非工程師朋友的日常繁瑣任務，成為我初探 Tauri 的契機。本文記錄如何使用 Rust 的強大後端結合 React 的靈活前端，打造一個輕量、跨平台的桌面應用，來解決真實世界的問題。</description>
    </item>
    <item>
      <title>Prompt to Product：AI 時代開發者的範式轉移</title>
      <link>https://www.omegaatt.com/blogs/develop/2025/prompt_to_product/</link>
      <pubDate>Sun, 10 Aug 2025 00:00:00 +0000</pubDate><author>raiven.kao@gmail.com (Raiven Kao)</author>
      <guid>https://www.omegaatt.com/blogs/develop/2025/prompt_to_product/</guid>
      <description>&lt;h2 id=&#34;那些年我們追逐的開發效率&#34;&gt;那些年我們追逐的開發效率&lt;/h2&gt;
&lt;p&gt;&lt;img alt=&#34;bg fit right&#34; loading=&#34;lazy&#34; src=&#34;https://nowankybollocks.com/wp-content/uploads/2020/01/10x.jpg&#34;&gt;&lt;/p&gt;
&lt;p&gt;還記得第一次聽到「十倍工程師」這個詞的時候，心中總是充滿憧憬。想像著有一天也能寫出十倍的程式碼、處理十倍的任務、創造十倍的價值。每當看到某個同事在短時間內完成複雜的功能，總是會想：他們是不是真的擁有某種神秘的超能力？&lt;/p&gt;
&lt;p&gt;隨著經驗的累積，漸漸發現這更像是一個美麗的神話。真正的效率並不在於敲擊鍵盤的速度，也不在於記住多少語法細節。那些看似神奇的十倍效率，往往來自於對問題本質的深刻理解，以及選擇正確工具的智慧。&lt;/p&gt;
&lt;p&gt;直到 ChatGPT 橫空出世，Agentic AI 開始進入我們的開發流程，我開始思考：AI 能為我們帶來什麼？又有什麼是它永遠無法取代的？&lt;/p&gt;
&lt;h2 id=&#34;從懷疑到接受的心路歷程&#34;&gt;從懷疑到接受的心路歷程&lt;/h2&gt;
&lt;h3 id=&#34;最初的抗拒與恐懼&#34;&gt;最初的抗拒與恐懼&lt;/h3&gt;
&lt;p&gt;坦白說，剛開始接觸這些 AI 工具時，我的內心是抗拒的。作為一個有著多年開發經驗的工程師，看著這些「會寫程式的機器」，心中五味雜陳。那種感覺就像是突然有人告訴你，你引以為傲的專業技能可能會被一個沒有情感的演算法取代。&lt;/p&gt;
&lt;p&gt;第一次使用 AI code assistant 時，我提出一個簡單的需求：「幫我寫一個排序函數」。當它瞬間給出了完整且正確的程式碼時，我的第一反應不是驚喜，而是擔憂。這些工具真的實用嗎？會不會最終取代我們這些工程師？依賴 AI 是否會讓我們的技能退化？&lt;/p&gt;
&lt;p&gt;這些疑慮很真實，也很合理。畢竟，我們這個行業向來對「銀彈」保持謹慎態度。太多次，我們看到某個新技術被吹捧為「革命性的」，最終卻發現它只是解決了某個特定問題，而創造了十個新問題。&lt;/p&gt;
&lt;h3 id=&#34;重新定義效率的數學陷阱&#34;&gt;重新定義效率的數學陷阱&lt;/h3&gt;
&lt;p&gt;然而，在實際使用 Zed AI、GitHub Copilot Agent 等工具幾個月後，我發現自己的思維框架需要調整。「十倍工程師」的概念確實存在一個隱藏的數學陷阱，而理解這個陷阱是擁抱 AI 輔助開發的第一步。&lt;/p&gt;
&lt;p&gt;我開始仔細觀察 AI 在哪些環節能夠提供幫助。當我需要寫一個複雜的正則表達式時，AI 能在幾秒鐘內給出準確的答案，而我可能需要查資料和測試十幾分鐘。當我需要重構一個大型函數時，AI 能夠快速理解程式碼邏輯並提供多種重構方案。當我需要撰寫技術文件時，AI 能夠幫我整理思路，甚至提供不同角度的表達方式。&lt;/p&gt;
&lt;p&gt;但同時，我也清楚地感受到 AI 的局限。當系統編譯時，AI 無法讓 CPU 運算得更快。當測試套件在 CI 環境中執行時，AI 無法讓網路延遲消失。當我需要與產品經理討論需求細節時，AI 無法代替我進行那些微妙的人際溝通。當面對複雜的架構選擇時，AI 可以提供選項，但最終的決策仍需要結合業務上下文和長期策略考量。&lt;/p&gt;
&lt;p&gt;有一天，一位同事興奮地告訴我：「用了 AI 工具後，我覺得自己的開發速度提升了十倍！」我問他：「你的專案交付時間真的縮短了十倍嗎？」他想了想，搖搖頭說：「沒有，可能只快了兩三倍。」這就是數學陷阱的本質：AI 能夠大幅加速某些特定環節，但整個開發流程的速度受限於最慢的那個環節。&lt;/p&gt;
&lt;h3 id=&#34;從工具使用者到架構思考者&#34;&gt;從工具使用者到架構思考者&lt;/h3&gt;
&lt;p&gt;這種認知的轉變讓我重新審視自己作為開發者的角色定位。AI 的出現並沒有降低我們的價值，反而讓我們從機械性的程式碼編寫中解放出來，有更多時間專注於真正重要的事情：理解業務需求的本質、設計優雅的系統架構、做出明智的技術選型決策。&lt;/p&gt;
&lt;p&gt;我開始意識到，真正的「十倍效率」不是來自於打字速度的提升，而是來自於思維方式的轉變。當 AI 幫我們處理了大量重複性工作後，我們有機會成為更好的架構師、更好的問題解決者、更好的技術領導者。&lt;/p&gt;
&lt;h2 id=&#34;機器可讀設計的哲學革命&#34;&gt;機器可讀設計的哲學革命&lt;/h2&gt;
&lt;p&gt;經過這段時間的實踐，我得出了一個看似簡單但意義深遠的核心理念：「只有機器可讀的設計，才能被 LLM 加速」。&lt;/p&gt;
&lt;h3 id=&#34;重新審視我們的設計習慣&#34;&gt;重新審視我們的設計習慣&lt;/h3&gt;
&lt;p&gt;這個理念讓我開始重新審視過去的工作習慣。回想一下我們是如何管理專案的：每次設計評審，我們會花費大量時間製作精美的 PowerPoint，其中包含各種流程圖、架構圖、時序圖。這些圖表在投影幕上看起來很專業，但會議結束後，它們就被儲存為 PNG 或 PDF 格式，靜靜地躺在某個共享資料夾中，再也沒有人會去更新或維護。&lt;/p&gt;</description>
    </item>
    <item>
      <title>如何利用 Golang AST 助攻 LLM 省 token 又高效</title>
      <link>https://www.omegaatt.com/blogs/develop/2025/golang_ast_llm_coding/</link>
      <pubDate>Mon, 16 Jun 2025 00:00:00 +0000</pubDate><author>raiven.kao@gmail.com (Raiven Kao)</author>
      <guid>https://www.omegaatt.com/blogs/develop/2025/golang_ast_llm_coding/</guid>
      <description>&lt;h2 id=&#34;前言&#34;&gt;前言&lt;/h2&gt;
&lt;p&gt;近來大型語言模型（LLM）的發展可謂一日千里，特別是在程式碼理解、生成與輔助開發方面，展現出了驚人的潛力。許多開發者開始嘗試將 LLM 融入到日常工作中，期望能提昇開發效率，甚至實現所謂的「vibe coding」——讓 LLM 理解程式碼的整體風格與意圖，並在此基礎上進行協作。&lt;/p&gt;
&lt;p&gt;然而，當我們試圖讓 LLM 直接「閱讀」整個大型專案的程式碼庫時，往往會碰到一些現實的挑戰。上下文長度限制、高昂的 token 消耗以及潛在的雜訊干擾，都可能讓 LLM 的表現不盡如人意。這時候，我們就需要更聰明的方法來為 LLM「提煉」程式碼的精華。&lt;/p&gt;
&lt;p&gt;在這篇文章中，我想分享一個在 Golang 專案中可能被忽略的利器：抽象語法樹（Abstract Syntax Tree, AST）。透過 Golang AST，我們可以更精準地提取程式碼的結構資訊，為 LLM 提供一份濃縮且高效的上下文，既能節省寶貴的 token，又能幫助 LLM 更好地把握「Code Vibe」。&lt;/p&gt;
&lt;h3 id=&#34;llm-直接消化大型-code-base-的痛&#34;&gt;LLM 直接消化大型 Code Base 的「痛」&lt;/h3&gt;
&lt;p&gt;想像一下，你正在開發一個頗具規模的 Golang 後端服務，裡面包含了數十個套件、數百個檔案。現在，你想讓 LLM 幫你新增一個功能，或者重構某個模組。如果直接把所有相關的程式碼一股腦地丟給 LLM，可能會遇到以下這些令人頭痛的問題：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;token 消耗「爆表」：LLM 的使用成本與輸入輸出的 token 數量直接相關。將大量原始碼作為輸入，無疑會產生巨額的 token 費用，對於個人開發者或小型團隊來說，這可能難以承受。&lt;/li&gt;
&lt;li&gt;「腦容量」不足的上下文限制：即使是目前頂尖的 LLM，其能夠處理的上下文長度也是有限的。面對龐大的程式碼庫，LLM 可能無法一次「看」全所有必要的資訊，導致理解片面或生成結果不佳。&lt;/li&gt;
&lt;li&gt;資訊過載與雜訊干擾：完整的程式碼中，充斥著各種細節——註解、空行、詳細的錯誤處理邏輯、暫時用不到的私有函式等等。這些資訊對於 LLM 理解程式碼的「vibe」或執行特定高層次任務（例如「模仿現有風格新增一個 API 端點」）來說，有時反而會成為雜訊，影響其判斷。&lt;/li&gt;
&lt;li&gt;龜速的回應：通常情況下，輸入給 LLM 的資訊越多，它處理並生成回應所需的時間就越長。在追求高效開發的今天，漫長的等待顯然不是我們想要的。&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;面對這些挑戰，我們不禁要問：有沒有一種方法，可以只給 LLM「剛剛好」的資訊，讓它既能理解我們的意圖，又能高效地完成任務呢？Golang AST 或許就是答案的一部分。&lt;/p&gt;
&lt;h3 id=&#34;golang-ast-如何助攻&#34;&gt;Golang AST 如何「助攻」&lt;/h3&gt;
&lt;p&gt;在我們深入探討 AST 如何幫助 LLM 之前，先快速回顧一下什麼是 Golang AST。&lt;/p&gt;</description>
    </item>
    <item>
      <title>一次核心模組的重構經驗</title>
      <link>https://www.omegaatt.com/blogs/develop/2025/experience_of_refine_core_module/</link>
      <pubDate>Sat, 14 Jun 2025 00:00:00 +0000</pubDate><author>raiven.kao@gmail.com (Raiven Kao)</author>
      <guid>https://www.omegaatt.com/blogs/develop/2025/experience_of_refine_core_module/</guid>
      <description>&lt;h2 id=&#34;前言&#34;&gt;前言&lt;/h2&gt;
&lt;p&gt;在軟體開發的漫漫長路中，我們時常會接手一些充滿「歷史印記」的專案。這些專案的核心模組，往往因為業務的快速迭代與時間的無情沖刷，逐漸演化成難以觸碰的「史前巨獸」。近期，我便有幸（或許該說是不幸地）參與了一次這樣核心模組的重構之旅，其核心是我們產品線廣泛使用的 Golang gRPC 認證攔截器 (Interceptor)。這段經歷充滿挑戰，但也收穫良多，希望能藉此分享一些心得。&lt;/p&gt;
&lt;h2 id=&#34;歷史的塵埃核心模組的演進悲歌&#34;&gt;歷史的塵埃：核心模組的演進悲歌&lt;/h2&gt;
&lt;p&gt;我接手的這個核心認證模組，在專案初期或許設計簡潔明瞭，但隨著產品線的不斷擴展和需求的堆疊，其複雜度已然失控。追溯其演進的脈絡，彷彿能看到一部小型技術債的形成史。&lt;/p&gt;
&lt;h3 id=&#34;最初的起點單純的-grpc-interceptor&#34;&gt;最初的起點：單純的 gRPC Interceptor&lt;/h3&gt;
&lt;p&gt;可以想見，專案伊始，對於 gRPC 服務的認證需求相對單純。一個通用的攔截器或許就能滿足所有需求，程式碼結構清晰可見：&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;-webkit-text-size-adjust:none;&#34;&gt;&lt;code class=&#34;language-go&#34; data-lang=&#34;go&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#f92672&#34;&gt;package&lt;/span&gt; &lt;span style=&#34;color:#a6e22e&#34;&gt;main&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;func&lt;/span&gt; &lt;span style=&#34;color:#a6e22e&#34;&gt;SimpleAuthInterceptor&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;log&lt;/span&gt;.&lt;span style=&#34;color:#a6e22e&#34;&gt;Println&lt;/span&gt;(&lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;Performing basic authentication via SimpleAuthInterceptor&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:#a6e22e&#34;&gt;handler&lt;/span&gt;(&lt;span style=&#34;color:#a6e22e&#34;&gt;ctx&lt;/span&gt;, &lt;span style=&#34;color:#a6e22e&#34;&gt;req&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;func&lt;/span&gt; &lt;span style=&#34;color:#a6e22e&#34;&gt;SimpleStreamAuthInterceptor&lt;/span&gt;(&lt;span style=&#34;color:#f92672&#34;&gt;...&lt;/span&gt;) &lt;span style=&#34;color:#66d9ef&#34;&gt;error&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;log&lt;/span&gt;.&lt;span style=&#34;color:#a6e22e&#34;&gt;Println&lt;/span&gt;(&lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;Performing basic stream authentication via SimpleStreamAuthInterceptor&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:#a6e22e&#34;&gt;handler&lt;/span&gt;(&lt;span style=&#34;color:#a6e22e&#34;&gt;srv&lt;/span&gt;, &lt;span style=&#34;color:#a6e22e&#34;&gt;ss&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;func&lt;/span&gt; &lt;span style=&#34;color:#a6e22e&#34;&gt;main&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;lis&lt;/span&gt;, &lt;span style=&#34;color:#a6e22e&#34;&gt;err&lt;/span&gt; &lt;span style=&#34;color:#f92672&#34;&gt;:=&lt;/span&gt; &lt;span style=&#34;color:#a6e22e&#34;&gt;net&lt;/span&gt;.&lt;span style=&#34;color:#a6e22e&#34;&gt;Listen&lt;/span&gt;(&lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;tcp&amp;#34;&lt;/span&gt;, &lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;:50051&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;if&lt;/span&gt; &lt;span style=&#34;color:#a6e22e&#34;&gt;err&lt;/span&gt; &lt;span style=&#34;color:#f92672&#34;&gt;!=&lt;/span&gt; &lt;span style=&#34;color:#66d9ef&#34;&gt;nil&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;log&lt;/span&gt;.&lt;span style=&#34;color:#a6e22e&#34;&gt;Fatalf&lt;/span&gt;(&lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;failed to listen: %v&amp;#34;&lt;/span&gt;, &lt;span style=&#34;color:#a6e22e&#34;&gt;err&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:#a6e22e&#34;&gt;s&lt;/span&gt; &lt;span style=&#34;color:#f92672&#34;&gt;:=&lt;/span&gt; &lt;span style=&#34;color:#a6e22e&#34;&gt;grpc&lt;/span&gt;.&lt;span style=&#34;color:#a6e22e&#34;&gt;NewServer&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;grpc&lt;/span&gt;.&lt;span style=&#34;color:#a6e22e&#34;&gt;UnaryInterceptor&lt;/span&gt;(&lt;span style=&#34;color:#a6e22e&#34;&gt;SimpleAuthInterceptor&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;grpc&lt;/span&gt;.&lt;span style=&#34;color:#a6e22e&#34;&gt;StreamInterceptor&lt;/span&gt;(&lt;span style=&#34;color:#a6e22e&#34;&gt;SimpleStreamAuthInterceptor&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:#a6e22e&#34;&gt;log&lt;/span&gt;.&lt;span style=&#34;color:#a6e22e&#34;&gt;Println&lt;/span&gt;(&lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;gRPC server listening on :50051&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;if&lt;/span&gt; &lt;span style=&#34;color:#a6e22e&#34;&gt;err&lt;/span&gt; &lt;span style=&#34;color:#f92672&#34;&gt;:=&lt;/span&gt; &lt;span style=&#34;color:#a6e22e&#34;&gt;s&lt;/span&gt;.&lt;span style=&#34;color:#a6e22e&#34;&gt;Serve&lt;/span&gt;(&lt;span style=&#34;color:#a6e22e&#34;&gt;lis&lt;/span&gt;); &lt;span style=&#34;color:#a6e22e&#34;&gt;err&lt;/span&gt; &lt;span style=&#34;color:#f92672&#34;&gt;!=&lt;/span&gt; &lt;span style=&#34;color:#66d9ef&#34;&gt;nil&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;log&lt;/span&gt;.&lt;span style=&#34;color:#a6e22e&#34;&gt;Fatalf&lt;/span&gt;(&lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;failed to serve: %v&amp;#34;&lt;/span&gt;, &lt;span style=&#34;color:#a6e22e&#34;&gt;err&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;在這個階段，一切看起來是那麼的美好與純粹。&lt;/p&gt;</description>
    </item>
    <item>
      <title>Golang Iterator 簡介與 samber/lo 比較</title>
      <link>https://www.omegaatt.com/blogs/develop/2025/golang_iterator/</link>
      <pubDate>Sat, 31 May 2025 00:00:00 +0000</pubDate><author>raiven.kao@gmail.com (Raiven Kao)</author>
      <guid>https://www.omegaatt.com/blogs/develop/2025/golang_iterator/</guid>
      <description>&lt;p&gt;自從 Golang 1.18 版本引入泛型（Generics）後，Go 語言的生態系統迎來了許多令人興奮的變化。其中，Golang 1.23 版本對 Iterator（迭代器）的標準化，以及 &lt;code&gt;iter&lt;/code&gt; 套件的加入，無疑是近期改動中相當重要的一環。本文將淺談 Golang Iterator 的基本概念，深入探討 Pure Iterator 與 Impure Iterator 之間的區別與設計考量，並與社群中流行的 &lt;code&gt;samber/lo&lt;/code&gt; 工具庫進行比較。&lt;/p&gt;
&lt;h2 id=&#34;什麼是-iterator&#34;&gt;什麼是 Iterator？&lt;/h2&gt;
&lt;p&gt;Iterator Pattern（迭代器模式）是一種常見的設計模式，它提供了一種循序存取集合物件中各個元素的方法，而又無需暴露該物件的內部表示。簡單來說，Iterator 就像一個指針，可以依序指向集合中的下一個元素，直到遍歷完所有元素為止。&lt;/p&gt;
&lt;h2 id=&#34;golang-中的-iterator&#34;&gt;Golang 中的 Iterator&lt;/h2&gt;
&lt;p&gt;在 Golang 1.23 之前，我們通常透過 &lt;code&gt;for-range&lt;/code&gt; 迴圈來迭代 array、slice、string、map、channel 等內建資料結構。然而，對於自訂的資料結構或複雜的序列生成邏輯，缺乏一個統一的迭代標準。&lt;/p&gt;
&lt;p&gt;Golang 1.23 版本正式將 Iterator 標準化，並在標準庫中加入了 &lt;code&gt;iter&lt;/code&gt; 套件。同時，&lt;code&gt;slices&lt;/code&gt; 和 &lt;code&gt;maps&lt;/code&gt; 套件也增加了一些回傳 Iterator 的工廠函數（Iterator Factories）。到了 Golang 1.24，更有如 &lt;code&gt;strings.SplitSeq&lt;/code&gt; 等函數加入，進一步豐富了 Iterator 的應用場景。&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;-webkit-text-size-adjust:none;&#34;&gt;&lt;code class=&#34;language-go&#34; data-lang=&#34;go&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#75715e&#34;&gt;// strings.SplitSeq 回傳一個迭代器，用於遍歷由 sep 分隔的 s 子字串。&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:#75715e&#34;&gt;// 此迭代器產生的字串與 Split(s, sep) 回傳的相同，但不會建構整個 slice。&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:#75715e&#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;func&lt;/span&gt; &lt;span style=&#34;color:#a6e22e&#34;&gt;SplitSeq&lt;/span&gt;(&lt;span style=&#34;color:#a6e22e&#34;&gt;s&lt;/span&gt;, &lt;span style=&#34;color:#a6e22e&#34;&gt;sep&lt;/span&gt; &lt;span style=&#34;color:#66d9ef&#34;&gt;string&lt;/span&gt;) &lt;span style=&#34;color:#a6e22e&#34;&gt;iter&lt;/span&gt;.&lt;span style=&#34;color:#a6e22e&#34;&gt;Seq&lt;/span&gt;[&lt;span style=&#34;color:#66d9ef&#34;&gt;string&lt;/span&gt;]
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;如果對 Golang 1.23+ 中 Iterator 的語法和語義還不熟悉，建議可以閱讀 Ian Lance Taylor 在 Go 官方部落格發表的&lt;a href=&#34;https://go.dev/blog/range-functions&#34;&gt;介紹文章&lt;/a&gt;&lt;/p&gt;</description>
    </item>
    <item>
      <title>告別 Docker Desktop 束縛！macOS 容器實戰：colima &#43; k8s &#43; containerd 踩坑遷移全記錄</title>
      <link>https://www.omegaatt.com/blogs/develop/2025/colima_docker_alternative_on_macos/</link>
      <pubDate>Sat, 19 Apr 2025 00:00:00 +0000</pubDate><author>raiven.kao@gmail.com (Raiven Kao)</author>
      <guid>https://www.omegaatt.com/blogs/develop/2025/colima_docker_alternative_on_macos/</guid>
      <description>&lt;h2 id=&#34;前言&#34;&gt;前言&lt;/h2&gt;
&lt;p&gt;身為一個開發者，特別是在 macOS 環境下，Docker Desktop 幾乎是容器化開發的標配。然而，自從 Docker Desktop 開始針對大型企業調整其&lt;a href=&#34;https://www.ithome.com.tw/news/146537&#34;&gt;授權模式&lt;/a&gt;後，許多開發者開始尋找替代方案。&lt;/p&gt;
&lt;p&gt;市面上確實出現了一些選擇，例如閉源但功能強大的 &lt;a href=&#34;https://orbstack.dev/&#34;&gt;OrbStack&lt;/a&gt;。但對於熱愛開源的我來說，目光自然投向了社群。這時，&lt;a href=&#34;https://github.com/abiosoft/colima&#34;&gt;colima&lt;/a&gt; 這個開源專案進入了我的視野。它不僅提供了一個在 macOS 上運行 Linux 容器的輕量級方式，還內建了 Kubernetes 支援，引起了我極大的興趣。&lt;/p&gt;
&lt;p&gt;這篇文章，想記錄一下我從 Docker Desktop 轉換到 colima，並且在 colima 的 Kubernetes 環境中，從原本依賴 Docker Engine 逐步遷移到使用 containerd 作為容器執行時（Container Runtime）的心路歷程與踩坑經驗。&lt;/p&gt;
&lt;h2 id=&#34;lima-與-colima-簡介&#34;&gt;lima 與 colima 簡介&lt;/h2&gt;
&lt;p&gt;在深入 colima 之前，得先提一下 &lt;a href=&#34;https://github.com/lima-vm/lima&#34;&gt;lima (Linux virtual machines on macOS)&lt;/a&gt;。lima 是一個旨在於 macOS 上輕鬆運行 Linux 虛擬機的開源專案。它底層利用了 macOS 的虛擬化框架（如 QEMU 或更高效的 &lt;code&gt;vz&lt;/code&gt;），提供了一個相對輕量的 Linux VM 環境。&lt;/p&gt;
&lt;p&gt;而 colima 則可以看作是建立在 lima 之上的「使用者友善層」。它簡化了 lima 的配置，並專注於提供容器執行時環境。colima 可以讓你輕鬆地啟動一個配置好 Docker 或 containerd 的 Linux VM，並且可以選擇性地啟用 Kubernetes (K3s) 支援。簡單來說，colima 幫你處理了建立 VM、安裝 Runtime 等繁瑣步驟，讓你專注在容器本身。&lt;/p&gt;</description>
    </item>
    <item>
      <title>建構多平台的 container image</title>
      <link>https://www.omegaatt.com/blogs/develop/2025/building_multiple_platform_container_image/</link>
      <pubDate>Sun, 30 Mar 2025 00:00:00 +0000</pubDate><author>raiven.kao@gmail.com (Raiven Kao)</author>
      <guid>https://www.omegaatt.com/blogs/develop/2025/building_multiple_platform_container_image/</guid>
      <description>&lt;h2 id=&#34;前言&#34;&gt;前言&lt;/h2&gt;
&lt;p&gt;最近在搞 side project 時，常常需要在不同的 CPU 架構上跑我的應用程式。例如，開發用的 MacBook 是 ARM 架構 (Apple Silicon)，開發用的 Desktop 是 x86 架構（Ryzen 5900X），而部署的伺服器可能是 x86 (AMD64)，有時候甚至想在 Raspberry Pi (ARM) 上跑些小東西。每次都要為不同平台分別建構 image 實在有點麻煩，而且 Registry 上一堆 &lt;code&gt;xxxapp-amd64&lt;/code&gt;, &lt;code&gt;xxxapp-arm64&lt;/code&gt; 的 tag 看了也很礙眼。經過一番研究與嘗試，是時候接觸 Docker Buildx 了。&lt;/p&gt;
&lt;h2 id=&#34;為什麼需要多平台映像檔&#34;&gt;為什麼需要多平台映像檔&lt;/h2&gt;
&lt;p&gt;在 wintel 的商業策略下，以及大家對高性能伺服器的普遍認知，主要用 x86/amd64，但現在 ARM 架構越來越普及，從 Apple Silicon 的 Mac、AWS Graviton 處理器、各種 IoT 設備到你的 Raspberry Pi，ARM 無所不在。如果你的 container image 只支援 amd64，那它就無法在這些 ARM 設備上原生運行 (需要模擬，效能差)。為了&lt;a href=&#34;https://en.wikipedia.org/wiki/Write_once,_run_anywhere&#34;&gt;Build Once, Run Anywhere&lt;/a&gt;，多平台映像檔 (Multi-platform images) 就是 meta。&lt;/p&gt;
&lt;h2 id=&#34;oci-多平台映像檔架構簡述&#34;&gt;OCI 多平台映像檔架構簡述&lt;/h2&gt;
&lt;p&gt;其實不複雜。傳統的單一平台 image，它的 manifest 指向一組設定檔和一堆 layer。而多平台 image 則是透過一個 manifest list (索引) 指向多個特定平台的 manifest。每個特定平台的 manifest 才各自指向該平台的設定檔和 layer。&lt;/p&gt;</description>
    </item>
    <item>
      <title>Never Install Locally？試試 Dev Container</title>
      <link>https://www.omegaatt.com/blogs/develop/2025/dev_container/</link>
      <pubDate>Sat, 01 Mar 2025 00:00:00 +0000</pubDate><author>raiven.kao@gmail.com (Raiven Kao)</author>
      <guid>https://www.omegaatt.com/blogs/develop/2025/dev_container/</guid>
      <description>&lt;p&gt;最近換了新工作，到了一間資訊安全公司，讓我更加重視開發環境的安全。&lt;/p&gt;
&lt;p&gt;還記得之前分享過&lt;a href=&#34;https://www.omegaatt.com/blogs/develop/2024/two_distrobox_use_case&#34;&gt;透過 Distrobox 解決 Linux 環境依賴問題&lt;/a&gt;，用他來解決不同 Linux distribution 的依賴關係，背後即是讓程式跑在 container 內。&lt;/p&gt;
&lt;p&gt;既然能夠將應用程式一來透過 container，與 Host OS 本身做出區隔，那麼我們也能透過 container 來對開發的依賴做出隔離。於是，開始擁抱 Dev Container，一個能讓我更安心、更有效率（？）的開發環境。&lt;/p&gt;
&lt;h2 id=&#34;什麼是-dev-container&#34;&gt;什麼是 Dev Container？&lt;/h2&gt;
&lt;p&gt;簡單來說，Dev Container 就是把開發環境「容器化」。我們可以把所有需要的工具、函式庫、設定檔都放在一個 Docker Image 裡，然後用這個 Image 啟動一個 Container 作為你的開發環境。&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Consistency (一致性)： 不管是在哪台機器上開發，只要有 Docker，就能保證開發環境完全一致。再也不用擔心「在我這台電腦上可以跑啊！」這種崩潰的狀況發生。&lt;/li&gt;
&lt;li&gt;Isolation (隔離性)： Dev Container 與本機系統完全隔離，可以避免各種依賴衝突，也能保護系統安全。&lt;/li&gt;
&lt;li&gt;Reproducibility (可重現性)： 透過 Dockerfile，你可以完整記錄你的開發環境設定，方便團隊協作和版本控制。&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id=&#34;為什麼需要-dev-container&#34;&gt;為什麼需要 Dev Container？&lt;/h2&gt;
&lt;p&gt;身為一個&lt;strong&gt;資安從業人員&lt;/strong&gt;，Dev Container 解決了以下痛點：&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;不同版本的 Node 環境，告別 &lt;code&gt;nvm&lt;/code&gt;！需要 node14, node16, node18 或是 stable 版本，隨時產生開發環境。&lt;/li&gt;
&lt;li&gt;需要下載 malware 到本地進行 e2e 測試，透過 container 進行蛤蜊（🦪意象），盡可能避免破壞系統安全性。&lt;/li&gt;
&lt;li&gt;在 macOS 上解決一些只支援 linux 的 binary，或是在 arm64 host 上透過 rosetta2 模擬 x86_64 環境，進而執行 amd64 執行檔。&lt;/li&gt;
&lt;li&gt;使用 rootless 模式，在危機四伏的 npm 環境中，確保開發環境的安全性。&lt;/li&gt;
&lt;/ol&gt;
&lt;h2 id=&#34;我的-dev-container-工作流&#34;&gt;我的 Dev Container 工作流&lt;/h2&gt;
&lt;p&gt;我將 dev-container 放在 github &lt;a href=&#34;https://github.com/omegaatt36/lab/tree/main/dev-container&#34;&gt;omegaatt36/lab/dev-container&lt;/a&gt; 中，以下 demo 僅「目前版本」，會依據使用情境進行迭代。&lt;/p&gt;</description>
    </item>
    <item>
      <title>使用 go work 在本地開發解決同時開發 module 的問題</title>
      <link>https://www.omegaatt.com/blogs/develop/2025/go_module_and_go_work/</link>
      <pubDate>Thu, 20 Feb 2025 00:00:00 +0000</pubDate><author>raiven.kao@gmail.com (Raiven Kao)</author>
      <guid>https://www.omegaatt.com/blogs/develop/2025/go_module_and_go_work/</guid>
      <description>&lt;p&gt;在 &lt;a href=&#34;https://go.dev/blog/get-familiar-with-workspaces&#34;&gt;Golang 1.18 中 go workspace 的提案&lt;/a&gt;釋出後，golang 的官方文件或多或少也提到應該要怎麼做 multi module 的開發。相較於過去需要不斷的替換 &lt;code&gt;go.mod&lt;/code&gt; 內的 &lt;code&gt;replace&lt;/code&gt; 指令，&lt;code&gt;go work&lt;/code&gt; 大幅改善了 multi module 的開發體驗。&lt;/p&gt;
&lt;h2 id=&#34;為什麼需要-go-work&#34;&gt;為什麼需要 go work&lt;/h2&gt;
&lt;h3 id=&#34;專案逐漸變大&#34;&gt;專案逐漸變大&lt;/h3&gt;
&lt;p&gt;當你在維護一個小工具 side project 時，單一的 module 就能夠滿足所有需求，但當專案逐漸變大，會需要將專案拆分成多個 module。
可以透過一個例子來理解：
假設我們有一個專案，拆分成以下兩個 module：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;common-lib-golang&lt;/code&gt;: 存放所有專案都會用到的 function，例如 retry, logger, tracer 等等&lt;/li&gt;
&lt;li&gt;&lt;code&gt;backend&lt;/code&gt;: 實際提供 http api 的程式碼&lt;/li&gt;
&lt;/ul&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;-webkit-text-size-adjust:none;&#34;&gt;&lt;code class=&#34;language-text&#34; data-lang=&#34;text&#34;&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;├── backend
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;│   ├── go.mod
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;│   ├── go.sum
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;│   └── main.go
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;└── common-lib-golang
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    ├── go.mod
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    ├── go.sum
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    └── util.go
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;隨著專案變大，我們在開發 &lt;code&gt;backend&lt;/code&gt; 時，時常會需要修改 &lt;code&gt;common-lib-golang&lt;/code&gt; 內的程式碼，這時候就需要 &lt;code&gt;go work&lt;/code&gt; 來協助我們。&lt;/p&gt;</description>
    </item>
    <item>
      <title>在習慣 go mod 後重新學習 git submodule</title>
      <link>https://www.omegaatt.com/blogs/develop/2025/git_submodule_turorial/</link>
      <pubDate>Fri, 24 Jan 2025 00:00:00 +0000</pubDate><author>raiven.kao@gmail.com (Raiven Kao)</author>
      <guid>https://www.omegaatt.com/blogs/develop/2025/git_submodule_turorial/</guid>
      <description>&lt;h2 id=&#34;前言&#34;&gt;前言&lt;/h2&gt;
&lt;p&gt;使用 Golang 作為主要開發已經有五年的時間。最近因工作需要接觸到 Python 專案，並且該專案使用 git submodule 的方式來引用共同函式庫 &lt;code&gt;common-lib-python&lt;/code&gt;。對於長久使用 Golang 的 &lt;code&gt;go mod&lt;/code&gt; 的我來說，submodule 是一個相對陌生的概念。藉這個機會撰寫一篇文章，整理並紀錄一下 git submodule 的用法，也作為未來的參考。&lt;/p&gt;
&lt;p&gt;這篇文章會假設已經對 git 的基本操作有一定程度的了解，並著重在 submodule 的概念、使用情境以及與 Golang 的 &lt;code&gt;go mod&lt;/code&gt; 的差異比較。文章內容會以下列流程來呈現：&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;建立一個新的 Python 專案 &lt;code&gt;my-python-repo&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;建立一個 Python 模組 &lt;code&gt;my-python-module&lt;/code&gt; 作為 submodule&lt;/li&gt;
&lt;li&gt;在 &lt;code&gt;my-python-repo&lt;/code&gt; 中使用 &lt;code&gt;my-python-module&lt;/code&gt; 作為 submodule&lt;/li&gt;
&lt;li&gt;模擬需求變更，同時修改 &lt;code&gt;my-python-repo&lt;/code&gt; 與 &lt;code&gt;my-python-module&lt;/code&gt;，並分別發送 PR&lt;/li&gt;
&lt;li&gt;與 Golang 的 &lt;code&gt;go mod&lt;/code&gt; 進行比較&lt;/li&gt;
&lt;/ol&gt;
&lt;h2 id=&#34;建立-python-專案與模組&#34;&gt;建立 Python 專案與模組&lt;/h2&gt;
&lt;p&gt;先建立兩個新的 git repo，分別是 &lt;code&gt;my-python-repo&lt;/code&gt; 與 &lt;code&gt;my-python-module&lt;/code&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;-webkit-text-size-adjust:none;&#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;&lt;span style=&#34;color:#75715e&#34;&gt;# 建立 my-python-repo&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;mkdir my-python-repo
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;cd my-python-repo
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;git init
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;touch main.py
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;git add .
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;git commit -m &lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;Initial commit&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 style=&#34;color:#75715e&#34;&gt;# 建立 my-python-module&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;cd ..
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;mkdir my-python-module
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;cd my-python-module
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;git init
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;touch my_module.py
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;git add .
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;git commit -m &lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;Initial commit&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 style=&#34;color:#75715e&#34;&gt;# 將兩個專案 push 到 Github/Gitlab 上&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;&lt;code&gt;my-python-repo&lt;/code&gt; 與 &lt;code&gt;my-python-module&lt;/code&gt; 都已經是一個獨立的 git repo。&lt;/p&gt;</description>
    </item>
    <item>
      <title>Golang Composition over Inheritance</title>
      <link>https://www.omegaatt.com/blogs/develop/2025/golang_composition_over_inheritance/</link>
      <pubDate>Sat, 11 Jan 2025 00:00:00 +0000</pubDate><author>raiven.kao@gmail.com (Raiven Kao)</author>
      <guid>https://www.omegaatt.com/blogs/develop/2025/golang_composition_over_inheritance/</guid>
      <description>&lt;p&gt;Golang 是一門簡潔有力的程式語言，相較於其他程式語言，更傾向於使用組合（composition）而不是繼承（inheritance），語言設計之初更是沒有提供繼承的關鍵字，這種設計哲學讓 Golang 在現代軟體開發中脫穎而出。&lt;/p&gt;
&lt;p&gt;繼承固然有其優點，但在建構複雜的物件關係時，容易產生過於龐大的繼承層級結構。這使得程式碼難以閱讀和維護，就像是一棵盤根錯節的大樹，牽一髮而動全身。&lt;/p&gt;
&lt;p&gt;過深的繼承層級會導致知名的&lt;a href=&#34;https://github.com/Dobiasd/articles/blob/master/implementation_inheritance_is_bad_-_the_fragile_base_class_problem.md&#34;&gt;「脆弱基類問題」(fragile base class problem)&lt;/a&gt;，使得程式碼難以修改和擴展。&lt;/p&gt;
&lt;p&gt;組合則不同，它鼓勵建立小型、專注的 struct，然後像樂高積木一樣，將這些 struct 組合成更大的結構。這種方式讓程式碼模組化，更容易理解和修改。&lt;/p&gt;
&lt;h2 id=&#34;彈性&#34;&gt;彈性&lt;/h2&gt;
&lt;p&gt;Golang 的 type system 支援我們靈活地組合各種 struct。可以建立一個新的 struct，並在其中「嵌入」其他 struct 作為其欄位。&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;-webkit-text-size-adjust:none;&#34;&gt;&lt;code class=&#34;language-go&#34; data-lang=&#34;go&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#66d9ef&#34;&gt;type&lt;/span&gt; &lt;span style=&#34;color:#a6e22e&#34;&gt;Car&lt;/span&gt; &lt;span style=&#34;color:#66d9ef&#34;&gt;struct&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;make&lt;/span&gt;  &lt;span style=&#34;color:#66d9ef&#34;&gt;string&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;model&lt;/span&gt; &lt;span style=&#34;color:#66d9ef&#34;&gt;string&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;year&lt;/span&gt;  &lt;span style=&#34;color:#66d9ef&#34;&gt;int&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;type&lt;/span&gt; &lt;span style=&#34;color:#a6e22e&#34;&gt;Driver&lt;/span&gt; &lt;span style=&#34;color:#66d9ef&#34;&gt;struct&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;name&lt;/span&gt; &lt;span style=&#34;color:#66d9ef&#34;&gt;string&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;car&lt;/span&gt;  &lt;span style=&#34;color:#a6e22e&#34;&gt;Car&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;func&lt;/span&gt; &lt;span style=&#34;color:#a6e22e&#34;&gt;main&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;myCar&lt;/span&gt; &lt;span style=&#34;color:#f92672&#34;&gt;:=&lt;/span&gt; &lt;span style=&#34;color:#a6e22e&#34;&gt;Car&lt;/span&gt;{&lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;Toyota&amp;#34;&lt;/span&gt;, &lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;Camry&amp;#34;&lt;/span&gt;, &lt;span style=&#34;color:#ae81ff&#34;&gt;2020&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;driver&lt;/span&gt; &lt;span style=&#34;color:#f92672&#34;&gt;:=&lt;/span&gt; &lt;span style=&#34;color:#a6e22e&#34;&gt;Driver&lt;/span&gt;{&lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;John&amp;#34;&lt;/span&gt;, &lt;span style=&#34;color:#a6e22e&#34;&gt;myCar&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;fmt&lt;/span&gt;.&lt;span style=&#34;color:#a6e22e&#34;&gt;Println&lt;/span&gt;(&lt;span style=&#34;color:#a6e22e&#34;&gt;driver&lt;/span&gt;.&lt;span style=&#34;color:#a6e22e&#34;&gt;name&lt;/span&gt;)         &lt;span style=&#34;color:#75715e&#34;&gt;// 輸出: John&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;fmt&lt;/span&gt;.&lt;span style=&#34;color:#a6e22e&#34;&gt;Println&lt;/span&gt;(&lt;span style=&#34;color:#a6e22e&#34;&gt;driver&lt;/span&gt;.&lt;span style=&#34;color:#a6e22e&#34;&gt;car&lt;/span&gt;.&lt;span style=&#34;color:#a6e22e&#34;&gt;make&lt;/span&gt;)    &lt;span style=&#34;color:#75715e&#34;&gt;// 輸出: Toyota&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;在這個例子中，&lt;code&gt;Driver&lt;/code&gt; 透過組合 &lt;code&gt;Car&lt;/code&gt; 來建立更豐富的資料結構。&lt;code&gt;Driver&lt;/code&gt; 「has-a」 &lt;code&gt;Car&lt;/code&gt;，而不是 「is-a」 &lt;code&gt;Car&lt;/code&gt;，這提供了更高的彈性，讓 &lt;code&gt;Driver&lt;/code&gt; 可以更專注在自身的邏輯。&lt;/p&gt;
&lt;h2 id=&#34;程式碼複用&#34;&gt;程式碼複用&lt;/h2&gt;
&lt;p&gt;組合促進了程式碼的複用。可以建立許多小型、可複用的 struct，然後將它們組合成各種不同的結構。&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;-webkit-text-size-adjust:none;&#34;&gt;&lt;code class=&#34;language-go&#34; data-lang=&#34;go&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#66d9ef&#34;&gt;type&lt;/span&gt; &lt;span style=&#34;color:#a6e22e&#34;&gt;Engine&lt;/span&gt; &lt;span style=&#34;color:#66d9ef&#34;&gt;struct&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;power&lt;/span&gt;      &lt;span style=&#34;color:#66d9ef&#34;&gt;int&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;fuelType&lt;/span&gt;   &lt;span style=&#34;color:#66d9ef&#34;&gt;string&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;type&lt;/span&gt; &lt;span style=&#34;color:#a6e22e&#34;&gt;Wheels&lt;/span&gt; &lt;span style=&#34;color:#66d9ef&#34;&gt;struct&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;count&lt;/span&gt;      &lt;span style=&#34;color:#66d9ef&#34;&gt;int&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;material&lt;/span&gt;   &lt;span style=&#34;color:#66d9ef&#34;&gt;string&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;type&lt;/span&gt; &lt;span style=&#34;color:#a6e22e&#34;&gt;Vehicle&lt;/span&gt; &lt;span style=&#34;color:#66d9ef&#34;&gt;struct&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;engine&lt;/span&gt; &lt;span style=&#34;color:#a6e22e&#34;&gt;Engine&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;wheels&lt;/span&gt; &lt;span style=&#34;color:#a6e22e&#34;&gt;Wheels&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;brand&lt;/span&gt;  &lt;span style=&#34;color:#66d9ef&#34;&gt;string&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;func&lt;/span&gt; (&lt;span style=&#34;color:#a6e22e&#34;&gt;v&lt;/span&gt; &lt;span style=&#34;color:#a6e22e&#34;&gt;Vehicle&lt;/span&gt;) &lt;span style=&#34;color:#a6e22e&#34;&gt;getBrand&lt;/span&gt;() &lt;span style=&#34;color:#66d9ef&#34;&gt;string&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:#a6e22e&#34;&gt;v&lt;/span&gt;.&lt;span style=&#34;color:#a6e22e&#34;&gt;brand&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;func&lt;/span&gt; (&lt;span style=&#34;color:#a6e22e&#34;&gt;v&lt;/span&gt; &lt;span style=&#34;color:#a6e22e&#34;&gt;Vehicle&lt;/span&gt;) &lt;span style=&#34;color:#a6e22e&#34;&gt;getEnginePower&lt;/span&gt;() &lt;span style=&#34;color:#66d9ef&#34;&gt;int&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:#a6e22e&#34;&gt;v&lt;/span&gt;.&lt;span style=&#34;color:#a6e22e&#34;&gt;engine&lt;/span&gt;.&lt;span style=&#34;color:#a6e22e&#34;&gt;power&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;在這個例子中，&lt;code&gt;Vehicle&lt;/code&gt; 透過組合 &lt;code&gt;Engine&lt;/code&gt; 和 &lt;code&gt;Wheels&lt;/code&gt; 來複用這兩個 struct 的欄位和功能。&lt;code&gt;Vehicle&lt;/code&gt; 「has-a」 &lt;code&gt;Engine&lt;/code&gt; and 「has-a」 &lt;code&gt;Wheels&lt;/code&gt;，並可以新增自己的欄位和方法，例如 &lt;code&gt;brand&lt;/code&gt;、&lt;code&gt;getBrand()&lt;/code&gt; 和 &lt;code&gt;getEnginePower()&lt;/code&gt;。這種方式讓 &lt;code&gt;Vehicle&lt;/code&gt; 可以專注於自身的邏輯，同時又能複用 &lt;code&gt;Engine&lt;/code&gt; 和 &lt;code&gt;Wheels&lt;/code&gt; 的功能。&lt;/p&gt;</description>
    </item>
    <item>
      <title>使用 Templater 來提昇 Obsidian 的生產力</title>
      <link>https://www.omegaatt.com/blogs/develop/2025/use_obsidian_templater_to_get_more_productivity/</link>
      <pubDate>Thu, 02 Jan 2025 00:00:00 +0000</pubDate><author>raiven.kao@gmail.com (Raiven Kao)</author>
      <guid>https://www.omegaatt.com/blogs/develop/2025/use_obsidian_templater_to_get_more_productivity/</guid>
      <description>&lt;blockquote&gt;
&lt;p&gt;這篇文章不涉及 &lt;a href=&#34;https://obsidian.md/&#34;&gt;Obsidian&lt;/a&gt; 完整使用說明以及 &lt;a href=&#34;https://silentvoid13.github.io/Templater/&#34;&gt;Templater&lt;/a&gt; 完整使用教學，僅以一個 use case 來展現如何使用 Templater 來提昇 Obsidian 的使用效率。&lt;/p&gt;
&lt;/blockquote&gt;
&lt;h2 id=&#34;前言&#34;&gt;前言：&lt;/h2&gt;
&lt;p&gt;工作日誌，是一個甜蜜的負擔，有些人是因為公司要求，開始寫工作日誌，逐漸流於形式。&lt;/p&gt;
&lt;p&gt;最初我也是因為工作需要「彙報」，才開始寫工作日誌。直到開始跑 scrum，開始明白 standup meeting 的意義，開始在意如何讓 stackholder 放心把事情交給我做，於是&lt;/p&gt;
&lt;p&gt;參考了《&lt;a href=&#34;https://www.books.com.tw/products/0010785434&#34;&gt;SCRUM：用一半的時間做兩倍的事&lt;/a&gt;》以及實際執行的經驗，以下是我認為良好的彙報需要注意的關鍵因素：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;昨天的進度：簡要說明昨天完成的具體工作，重點在成果而非過程。&lt;/li&gt;
&lt;li&gt;今天的計劃：說明今天的工作重點，聚焦在需要達成的具體目標。如果是要跨超過一天的工作，也需要設定一個預計能完成的時間。&lt;/li&gt;
&lt;li&gt;遇到的障礙：明確指出當前的問題或挑戰，並說明需要的幫助。當然也沒有必要什麼事都等到 standup meeting 才提出問題，有障礙應當即時反應。&lt;/li&gt;
&lt;li&gt;與其他團隊或成員的依賴：說明需要其他人或團隊支持的地方，確保協作順暢，例如後端需要寄送的 email 內容，需要前端協助產生 html 等等。&lt;/li&gt;
&lt;li&gt;以終為始、以成果為導向：聚焦於交付成果的進展，而非細節，例如：目前已完成 80% 的登入模組開發，預計明天可以驗收。&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;詳細的舉例不是這篇文章的重點，有空可以再寫成其他文章 D:&lt;/p&gt;
&lt;p&gt;上述幾個要點，在工作日誌中被我濃縮成 markdown 的格式：&lt;/p&gt;
&lt;pre tabindex=&#34;0&#34;&gt;&lt;code&gt;XXXX-XX-XX

昨日：
- 昨日1
- 昨日2
瓶頸：
- 瓶頸
今日：
- 今日1
- 今日2
&lt;/code&gt;&lt;/pre&gt;&lt;h2 id=&#34;obsidian&#34;&gt;Obsidian&lt;/h2&gt;
&lt;p&gt;高中時接觸了 markdown，大學接觸了 Hackpad，畢業後延續這個習慣，使用 Hackmd 來管理自己的工作日誌。&lt;/p&gt;
&lt;p&gt;隨著時間的增長，開始發現 Hackmd 無論是官方 hosting 的免費版與企業版，或是 self-hosted 的 CodeMD，在單一大檔案編輯文件時，都會有明顯的延遲。若我將工作日誌拆成多個月份，又不好管理。&lt;/p&gt;
&lt;p&gt;在 2023 年轉換工作之際，開始尋找其他 markdown 文字編輯器。&lt;/p&gt;</description>
    </item>
    <item>
      <title>2024 Recap</title>
      <link>https://www.omegaatt.com/blogs/develop/2024/2024_recap/</link>
      <pubDate>Tue, 31 Dec 2024 00:00:00 +0000</pubDate><author>raiven.kao@gmail.com (Raiven Kao)</author>
      <guid>https://www.omegaatt.com/blogs/develop/2024/2024_recap/</guid>
      <description>&lt;blockquote&gt;
&lt;p&gt;假如你的答案更好，書本上的答案一點也不重要。&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;2024 年 12 月 13 日過世的查爾斯．韓第，在《你拿什麼定義自己？》中如此寫到，這句話也在 2024 年改變了我做決策的方式。&lt;/p&gt;
&lt;p&gt;在 2022 年底寫了  &lt;a href=&#34;https://www.omegaatt.com/blogs/develop/2023/2023_outlook&#34;&gt;2023 年展望&lt;/a&gt;，轉眼間兩年就要過去了，在 2024 年底紀錄一下這一年的回顧，以及未來的展望。&lt;/p&gt;
&lt;h2 id=&#34;健康&#34;&gt;健康&lt;/h2&gt;
&lt;p&gt;今年是特別注重健康的一年，無論是身體健康或是心靈健康。&lt;/p&gt;
&lt;p&gt;年初的健康檢查，發現陪伴我四年的輕度脂肪肝消失了，於是開始不忌口的胡亂飲食。八月底看不下去體重的增長，開始戒零食與珍珠，也開始跑健身房有氧與無氧，從高點的 75 公斤掉到 68 公斤。明年的期望是體脂肪降到 15% 並且同時能夠增肌到體重 70 公斤。
&lt;div class=&#34;gallery scroll&#34; data-pswp-gallery id=&#34;gallery-0&#34;&gt;&lt;a href=&#34;https://www.omegaatt.com/blogs/develop/2024/2024_recap/images/photo_2024-12-30_18-18-29.webp&#34;
           data-pswp-width=&#34;974&#34;
           data-pswp-height=&#34;1113&#34;&gt;
          &lt;img src=&#34;https://www.omegaatt.com/blogs/develop/2024/2024_recap/images/photo_2024-12-30_18-18-29.webp&#34;
               alt=&#34;Gallery image 1&#34;
               class=&#34;gallery-image&#34;
               width=&#34;974&#34;
               height=&#34;1113&#34;
               loading=&#34;lazy&#34;
               decoding=&#34;async&#34; /&gt;
        &lt;/a&gt;&lt;a href=&#34;https://www.omegaatt.com/blogs/develop/2024/2024_recap/images/Screenshot_1.webp&#34;
           data-pswp-width=&#34;1400&#34;
           data-pswp-height=&#34;600&#34;&gt;
          &lt;img src=&#34;https://www.omegaatt.com/blogs/develop/2024/2024_recap/images/Screenshot_1.webp&#34;
               alt=&#34;Gallery image 2&#34;
               class=&#34;gallery-image&#34;
               width=&#34;1400&#34;
               height=&#34;600&#34;
               loading=&#34;lazy&#34;
               decoding=&#34;async&#34; /&gt;
        &lt;/a&gt;&lt;a href=&#34;https://www.omegaatt.com/blogs/develop/2024/2024_recap/images/Screenshot_2.webp&#34;
           data-pswp-width=&#34;1400&#34;
           data-pswp-height=&#34;600&#34;&gt;
          &lt;img src=&#34;https://www.omegaatt.com/blogs/develop/2024/2024_recap/images/Screenshot_2.webp&#34;
               alt=&#34;Gallery image 3&#34;
               class=&#34;gallery-image&#34;
               width=&#34;1400&#34;
               height=&#34;600&#34;
               loading=&#34;lazy&#34;
               decoding=&#34;async&#34; /&gt;
        &lt;/a&gt;&lt;a href=&#34;https://www.omegaatt.com/blogs/develop/2024/2024_recap/images/Screenshot_3.webp&#34;
           data-pswp-width=&#34;1400&#34;
           data-pswp-height=&#34;602&#34;&gt;
          &lt;img src=&#34;https://www.omegaatt.com/blogs/develop/2024/2024_recap/images/Screenshot_3.webp&#34;
               alt=&#34;Gallery image 4&#34;
               class=&#34;gallery-image&#34;
               width=&#34;1400&#34;
               height=&#34;602&#34;
               loading=&#34;lazy&#34;
               decoding=&#34;async&#34; /&gt;
        &lt;/a&gt;&lt;/div&gt;&lt;/p&gt;
&lt;p&gt;在三月也開始牙齒矯正，好在不需要拔牙，約莫一年到一年半即可以完成矯正。傳統矯正的缺點是鐵線會一直刮到嘴皮、需要專門的牙籤才好剔牙齒，但感到最不方便的是，需要完成矯正才能捐血 QAQ。&lt;/p&gt;
&lt;p&gt;在年中旬，發現有些 Burn Out，好在身邊的同事與朋友們，願意花時間開導、給與職涯上的建議，才能順利的調整過來，今年感受到滿滿的溫暖。&lt;/p&gt;
&lt;h2 id=&#34;旅遊&#34;&gt;旅遊&lt;/h2&gt;
&lt;p&gt;今年原本是預計要去高島縱走、合歡山、大霸尖山等等，但因為突如其來的地震、颱風而全數取消，不過透過照片回顧，今年還是去了不少地方的。&lt;/p&gt;
&lt;h3 id=&#34;跨年&#34;&gt;跨年&lt;/h3&gt;
&lt;p&gt;Murphy 考到了駕照，跨年去到了台東，沒有駕照的我就當個優良的副駕，較特別的是，我們在紅烏龍的故鄉鹿野，誤打誤撞進入了&lt;a href=&#34;https://www.facebook.com/beeloutea/&#34;&gt;碧蘿園&lt;/a&gt;。&lt;/p&gt;
&lt;div class=&#34;gallery scroll&#34; data-pswp-gallery id=&#34;gallery-1&#34;&gt;&lt;a href=&#34;https://www.omegaatt.com/blogs/develop/2024/2024_recap/images/P1072324.webp&#34;
           data-pswp-width=&#34;1500&#34;
           data-pswp-height=&#34;1000&#34;&gt;
          &lt;img src=&#34;https://www.omegaatt.com/blogs/develop/2024/2024_recap/images/P1072324.webp&#34;
               alt=&#34;Gallery image 1&#34;
               class=&#34;gallery-image&#34;
               width=&#34;1500&#34;
               height=&#34;1000&#34;
               loading=&#34;lazy&#34;
               decoding=&#34;async&#34; /&gt;
        &lt;/a&gt;&lt;a href=&#34;https://www.omegaatt.com/blogs/develop/2024/2024_recap/images/PXL_20231230_071154126.webp&#34;
           data-pswp-width=&#34;1020&#34;
           data-pswp-height=&#34;768&#34;&gt;
          &lt;img src=&#34;https://www.omegaatt.com/blogs/develop/2024/2024_recap/images/PXL_20231230_071154126.webp&#34;
               alt=&#34;Gallery image 2&#34;
               class=&#34;gallery-image&#34;
               width=&#34;1020&#34;
               height=&#34;768&#34;
               loading=&#34;lazy&#34;
               decoding=&#34;async&#34; /&gt;
        &lt;/a&gt;&lt;/div&gt;
&lt;h3 id=&#34;台北大縱走第四段&#34;&gt;台北大縱走第四段&lt;/h3&gt;
&lt;p&gt;去年開始找同事爬山，今年終於成團出發。(照片合併到下面)&lt;/p&gt;</description>
    </item>
    <item>
      <title>開源軟體的雙面刃：免費背後的隱藏成本</title>
      <link>https://www.omegaatt.com/blogs/develop/2024/the_double_edged_sword_of_open_source_software.md/</link>
      <pubDate>Fri, 01 Nov 2024 00:00:00 +0000</pubDate><author>raiven.kao@gmail.com (Raiven Kao)</author>
      <guid>https://www.omegaatt.com/blogs/develop/2024/the_double_edged_sword_of_open_source_software.md/</guid>
      <description>&lt;p&gt;&lt;img alt=&#34;setup-0&#34; loading=&#34;lazy&#34; src=&#34;https://www.omegaatt.com/blogs/develop/2024/the_double_edged_sword_of_open_source_software.md/images/20241102_142051.webp&#34;&gt;&lt;/p&gt;
&lt;p&gt;每日例行地開啟最愛的 Save for later，準備觀看有哪些部落格更新、產品釋出新版本時，一行醒目的提示告訴你「&lt;a href=&#34;https://blog.omnivore.app/p/details-on-omnivore-shutting-down&#34;&gt;我們即將關閉伺服器&lt;/a&gt;」。&lt;/p&gt;
&lt;p&gt;錯愕，這是 2024 年十月底每一位 Omnivore App 難民的心情。&lt;/p&gt;
&lt;h2 id=&#34;一開源軟體的普及與迷思&#34;&gt;一、開源軟體的普及與迷思&lt;/h2&gt;
&lt;p&gt;說到開源軟體，不外乎就是 free 或是 freedom 的軟體，可以說是免費的軟體，也可以說是自由的軟體。&lt;/p&gt;
&lt;p&gt;早期的 &lt;a href=&#34;https://en.wikipedia.org/wiki/LAMP_(software_bundle)&#34;&gt;LAMP Stack&lt;/a&gt; 相較於 Microsoft 的 Windows Server、IBM 或是 Oracle 付費的服務，每個組件都是免費的，廣泛受到 Geek 到大中小型企業的使用&lt;/p&gt;
&lt;p&gt;同時，開源軟體顧名思義是開放原始碼，在授權許可下可以自由使用、修改、分享，許多人將開源與安全劃上等號。&lt;/p&gt;
&lt;h2 id=&#34;二開源軟體的安全現況&#34;&gt;二、開源軟體的安全現況&lt;/h2&gt;
&lt;h3 id=&#34;多雙眼睛的侷限&#34;&gt;多雙眼睛的侷限&lt;/h3&gt;
&lt;p&gt;即便開放原始碼，所有人都能查看開放原始碼，如果有任何 bug 或是安全漏洞，都「應該」會被發現。
我使用「應該」一詞，正因為許多人（包含我）在安裝開源軟體時，就如同安裝閉源軟體一樣，並不會查看全部的程式碼，確認安全後才進行安裝。而是做一個「大家都看過了，應該很安全」的假設。
例如當你想用 Nginx 取代 Apache 時，可能僅會比較兩者的 feature 與 benchmark，並不會查看內部 tcp socket 是如何管理等等，或是 buffer overflow 時的錯誤處理機制。&lt;/p&gt;
&lt;h3 id=&#34;開源是否暴露更多漏洞&#34;&gt;開源是否暴露更多漏洞？&lt;/h3&gt;
&lt;p&gt;####安全問題的挑戰&lt;/p&gt;
&lt;p&gt;當我們將軟體開源，等於是將所有安全問題也給暴露了出來。&lt;/p&gt;
&lt;p&gt;舉例來說，假如我們 self-hosted 一個 NAS 服務，這個 NAS 使用指定的 port 進行 web 與 API 的存取，例如 &lt;code&gt;:8787&lt;/code&gt;。
當一個 Hacker 看到該 NAS 的 Auth 並沒有任何 rate limit，就可以直接在 NAS 內進行 port scan 或是 brute force 攻擊，取得這個 NAS 服務的控制權。&lt;/p&gt;</description>
    </item>
    <item>
      <title>Golang 1.22 中 http routing 的改進</title>
      <link>https://www.omegaatt.com/blogs/develop/2024/golang_http_routing_122/</link>
      <pubDate>Wed, 30 Oct 2024 00:00:00 +0000</pubDate><author>raiven.kao@gmail.com (Raiven Kao)</author>
      <guid>https://www.omegaatt.com/blogs/develop/2024/golang_http_routing_122/</guid>
      <description>&lt;p&gt;Golang 作為一個偏向 server 應用的程式語言，一般的 web server 並不會直接使用原生的 package &lt;code&gt;net/http&lt;/code&gt;，而更多的使用 &lt;code&gt;gin-gonic/gin&lt;/code&gt; 或是 &lt;code&gt;gorilla/mux&lt;/code&gt;，後來也有 &lt;code&gt;labstack/echo&lt;/code&gt; 以及 &lt;code&gt;go-chi/chi&lt;/code&gt; 等等選擇，在效能、輕量、好維護、好擴充中，都能找到對應的 third party package，其中的原因不外乎是原生的 package 提供的功能過於簡潔。&lt;/p&gt;
&lt;p&gt;好在 1.22 中，官方改進了 &lt;code&gt;net/http&lt;/code&gt; 中對於多工器、路由，甚至出了一篇&lt;a href=&#34;https://go.dev/blog/routing-enhancements&#34;&gt;部落格&lt;/a&gt;，現在更可以「大膽的」直接使用 standard library。&lt;/p&gt;
&lt;h2 id=&#34;path-parameter&#34;&gt;Path Parameter&lt;/h2&gt;
&lt;p&gt;若要將應用的 Web API 定義成 RESTful，我們會使用 &lt;code&gt;/資源/{資源唯一識別符}/子資源/{子資源唯一識別符}&lt;/code&gt; 來定義路徑。假如要獲取一個使用者的訂單，則會使用 &lt;code&gt;GET /users/1/orders&lt;/code&gt; 來獲取。在 1.22 以前，我們只能定義到 &lt;code&gt;/users&lt;/code&gt;，再自行解析往後的 path：&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;-webkit-text-size-adjust:none;&#34;&gt;&lt;code class=&#34;language-go&#34; data-lang=&#34;go&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#a6e22e&#34;&gt;http&lt;/span&gt;.&lt;span style=&#34;color:#a6e22e&#34;&gt;HandleFunc&lt;/span&gt;(&lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;/users&amp;#34;&lt;/span&gt;, &lt;span style=&#34;color:#66d9ef&#34;&gt;func&lt;/span&gt;(&lt;span style=&#34;color:#a6e22e&#34;&gt;w&lt;/span&gt; &lt;span style=&#34;color:#a6e22e&#34;&gt;http&lt;/span&gt;.&lt;span style=&#34;color:#a6e22e&#34;&gt;ResponseWriter&lt;/span&gt;, &lt;span style=&#34;color:#a6e22e&#34;&gt;r&lt;/span&gt; &lt;span style=&#34;color:#f92672&#34;&gt;*&lt;/span&gt;&lt;span style=&#34;color:#a6e22e&#34;&gt;http&lt;/span&gt;.&lt;span style=&#34;color:#a6e22e&#34;&gt;Request&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;subPath&lt;/span&gt; &lt;span style=&#34;color:#f92672&#34;&gt;:=&lt;/span&gt; &lt;span style=&#34;color:#a6e22e&#34;&gt;strings&lt;/span&gt;.&lt;span style=&#34;color:#a6e22e&#34;&gt;TrimPrefix&lt;/span&gt;(&lt;span style=&#34;color:#a6e22e&#34;&gt;req&lt;/span&gt;.&lt;span style=&#34;color:#a6e22e&#34;&gt;URL&lt;/span&gt;.&lt;span style=&#34;color:#a6e22e&#34;&gt;Path&lt;/span&gt;, &lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;/users/&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;if&lt;/span&gt; len(&lt;span style=&#34;color:#a6e22e&#34;&gt;subPath&lt;/span&gt;) &lt;span style=&#34;color:#f92672&#34;&gt;==&lt;/span&gt; &lt;span style=&#34;color:#ae81ff&#34;&gt;0&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;xxx&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;else&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;ooo&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:#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&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;而在 1.22 中新增了 &lt;code&gt;net.http&lt;/code&gt; 對 path parameter 的支持，我們可以直接使用 &lt;code&gt;(*http.Request).PathValue(&amp;quot;xxx&amp;quot;)&lt;/code&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;-webkit-text-size-adjust:none;&#34;&gt;&lt;code class=&#34;language-go&#34; data-lang=&#34;go&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#a6e22e&#34;&gt;http&lt;/span&gt;.&lt;span style=&#34;color:#a6e22e&#34;&gt;HandleFunc&lt;/span&gt;(&lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;/users/{user_id}&amp;#34;&lt;/span&gt;, &lt;span style=&#34;color:#66d9ef&#34;&gt;func&lt;/span&gt;(&lt;span style=&#34;color:#a6e22e&#34;&gt;w&lt;/span&gt; &lt;span style=&#34;color:#a6e22e&#34;&gt;http&lt;/span&gt;.&lt;span style=&#34;color:#a6e22e&#34;&gt;ResponseWriter&lt;/span&gt;, &lt;span style=&#34;color:#a6e22e&#34;&gt;r&lt;/span&gt; &lt;span style=&#34;color:#f92672&#34;&gt;*&lt;/span&gt;&lt;span style=&#34;color:#a6e22e&#34;&gt;http&lt;/span&gt;.&lt;span style=&#34;color:#a6e22e&#34;&gt;Request&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;userID&lt;/span&gt; &lt;span style=&#34;color:#f92672&#34;&gt;:=&lt;/span&gt; &lt;span style=&#34;color:#a6e22e&#34;&gt;r&lt;/span&gt;.&lt;span style=&#34;color:#a6e22e&#34;&gt;PathValue&lt;/span&gt;(&lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;user_id&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:#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&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;不過也帶來一些限制&lt;/p&gt;</description>
    </item>
    <item>
      <title>使用 TinyGo 與 Raspberry pi pico 實現溫濕度感測器</title>
      <link>https://www.omegaatt.com/blogs/develop/2024/tinygo_temperature_humidity_sensor/</link>
      <pubDate>Wed, 02 Oct 2024 00:00:00 +0000</pubDate><author>raiven.kao@gmail.com (Raiven Kao)</author>
      <guid>https://www.omegaatt.com/blogs/develop/2024/tinygo_temperature_humidity_sensor/</guid>
      <description>&lt;h2 id=&#34;前言&#34;&gt;前言&lt;/h2&gt;
&lt;p&gt;作為嵌入式系統學習的微小專案，我決定使用 &lt;a href=&#34;https://tinygo.org/&#34;&gt;TinyGo&lt;/a&gt; 來實現一個簡單的溫濕度感測器。過去我大多使用 Arduino 作為微控制器（MCU）開發平台，但這次想嘗試使用 TinyGo 來進行單片機的學習。
TinyGo 是 Go 語言的一個子集，專門針對小型設備和微控制器進行了優化，使得我們可以在資源受限的硬體上運行 Go 程式。&lt;/p&gt;
&lt;p&gt;全部的程式碼都可以到 &lt;a href=&#34;https://github.com/omegaatt36/pico-bme280&#34;&gt;omegaatt36/pico-bme280&lt;/a&gt; 中找到。&lt;/p&gt;
&lt;h2 id=&#34;硬體選擇&#34;&gt;硬體選擇&lt;/h2&gt;
&lt;h3 id=&#34;raspberry-pi-pico&#34;&gt;Raspberry Pi Pico&lt;/h3&gt;
&lt;p&gt;我選擇了 Raspberry Pi Pico 作為本專案的主控板。Pico 是一款基於 RP2040 晶片的微控制器開發板，具有以下特點：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;雙核 ARM Cortex-M0+ 處理器，時脈可達 133 MHz&lt;/li&gt;
&lt;li&gt;264KB 的 SRAM 和 2MB 的板載閃存&lt;/li&gt;
&lt;li&gt;支援 USB 1.1 主機和設備功能&lt;/li&gt;
&lt;li&gt;低功耗睡眠和休眠模式&lt;/li&gt;
&lt;li&gt;可編程 I/O（PIO）狀態機&lt;/li&gt;
&lt;li&gt;30 個 GPIO 引腳&lt;/li&gt;
&lt;li&gt;便宜&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;價格僅需要 5 美元，後繼款的 &lt;a href=&#34;https://makerpro.cc/2024/08/the-second-generation-raspberry-pi-pico-is-here/&#34;&gt;Pico 2&lt;/a&gt; 仍然維持 5 美元，還額外增加了 RISC-V 架構的支援，可以一次玩到兩種處理器架構。
Pico 的這些特性使其非常適合用於各種嵌入式專案，包括我們的溫濕度感測器。&lt;/p&gt;
&lt;h3 id=&#34;bme280-感測器&#34;&gt;BME280 感測器&lt;/h3&gt;
&lt;p&gt;最初我購買了 &lt;a href=&#34;https://www.bosch-sensortec.com/products/environmental-sensors/pressure-sensors/bmp280/&#34;&gt;BMP280&lt;/a&gt; 感測器，但後來發現它只能測量溫度和氣壓，無法測量濕度。因此，我轉而選擇了 &lt;a href=&#34;https://www.bosch-sensortec.com/products/environmental-sensors/humidity-sensors-bme280/&#34;&gt;BME280&lt;/a&gt; 感測器，它可以同時測量溫度、濕度和氣壓。&lt;/p&gt;
&lt;p&gt;BME280 是一款精確度高、體積小、待機功耗低的環境感測器。但當連續偵測時，通過的電流會導致 sensor 功耗上升，進而導致測量到過高的溫度。&lt;/p&gt;</description>
    </item>
    <item>
      <title>A smarter way to change directory: zoxide</title>
      <link>https://www.omegaatt.com/blogs/develop/2024/zoxide_a_smarter_way_to_change_directory/</link>
      <pubDate>Tue, 24 Sep 2024 00:00:00 +0000</pubDate><author>raiven.kao@gmail.com (Raiven Kao)</author>
      <guid>https://www.omegaatt.com/blogs/develop/2024/zoxide_a_smarter_way_to_change_directory/</guid>
      <description>&lt;p&gt;在日常的開發工作中，我們經常需要在不同的目錄間切換。雖然 &lt;code&gt;cd&lt;/code&gt; 命令已經足夠好用，但如果有一個更聰明的工具能記住我們最常用的目錄，並讓我們用最少的按鍵就能快速跳轉，那豈不是更棒?&lt;/p&gt;
&lt;p&gt;這就是 zoxide 的用武之地。zoxide 是一個由 Rust 編寫的「更聰明的 cd 命令」，靈感來自 z 和 autojump。它會記住你最常使用的目錄，讓你只需輸入幾個字符就能快速跳轉。&lt;/p&gt;
&lt;h2 id=&#34;zoxide-的主要特性&#34;&gt;zoxide 的主要特性&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;自動匹配: 不需要輸入完整路徑，zoxide 會根據輸入自動匹配最相關的目錄：&lt;/li&gt;
&lt;li&gt;自動紀錄過去的目錄: zoxide 會記住你最常使用的目錄，讓你只需輸入幾個字符就能快速跳轉。這些資料可以使用 &lt;code&gt;zoxide query&lt;/code&gt; 來查詢，或是 &lt;code&gt;zoxide edit&lt;/code&gt; 來管理。&lt;/li&gt;
&lt;li&gt;互動式選擇: 結合 fzf，可以互動式地選擇目標目錄&lt;/li&gt;
&lt;li&gt;輕量快速: 用 Rust 編寫，啟動迅速，幾乎不會影響 shell 的啟動時間&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id=&#34;安裝與配置&#34;&gt;安裝與配置&lt;/h2&gt;
&lt;p&gt;zoxide 的安裝可以參考 &lt;a href=&#34;https://github.com/ajeetdsouza/zoxide?tab=readme-ov-file#installation&#34;&gt;官方文件&lt;/a&gt;，我們就不多贅述了。&lt;/p&gt;
&lt;p&gt;安裝完成後，我們需要在 shell 的配置文件中添加初始化命令。以 zsh 為例，在 &lt;code&gt;~/.zshrc&lt;/code&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;-webkit-text-size-adjust:none;&#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;eval &lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;&lt;/span&gt;&lt;span style=&#34;color:#66d9ef&#34;&gt;$(&lt;/span&gt;zoxide init zsh&lt;span style=&#34;color:#66d9ef&#34;&gt;)&lt;/span&gt;&lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;重新打開終端或執行 &lt;code&gt;source ~/.zshrc&lt;/code&gt; 後，zoxide 就可以使用了。&lt;/p&gt;
&lt;h2 id=&#34;使用方法&#34;&gt;使用方法&lt;/h2&gt;
&lt;p&gt;zoxide 的基本用法非常直觀，經過自動執行 &lt;code&gt;zoxide init zsh&lt;/code&gt; 後，我們可以使用 &lt;code&gt;z&lt;/code&gt; 和 &lt;code&gt;zi&lt;/code&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&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;-webkit-text-size-adjust:none;&#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;❯ ls
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  boo bar baz
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;❯ z bo
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;❯ pwd
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  /home/raiven/boo
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/li&gt;
&lt;li&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;-webkit-text-size-adjust:none;&#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;❯ pwd
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  /home/raiven/dev/omegaatt-blog
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;❯ z
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;❯ pwd
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  /home/raiven
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;❯ z blog
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;❯ pwd
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  /home/raiven/dev/omegaatt-blog
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/li&gt;
&lt;li&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;-webkit-text-size-adjust:none;&#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;❯ zi
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;~ &amp;lt; 98/98&lt;span style=&#34;color:#f92672&#34;&gt;(&lt;/span&gt;0&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;296.0 /home/raiven/dev/omegaatt-blog
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;124.0 /home/raiven/dev/bookly
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt; 50.0 /home/raiven/dev
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt; 24.0 /home/raiven/dev/test
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt; 12.0 /home/raiven/
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;這裡的 &lt;code&gt;z&lt;/code&gt; 命令就像一個更聰明的 &lt;code&gt;cd&lt;/code&gt;，而 &lt;code&gt;zi&lt;/code&gt; 則是互動式版本。&lt;/p&gt;</description>
    </item>
    <item>
      <title>使用 Bitwarden 與自架後端 Vaultwarden 來管理密碼與 2FA Authenticator</title>
      <link>https://www.omegaatt.com/blogs/develop/2023/bitwarden_with_self_hosted_password_backend/</link>
      <pubDate>Sun, 01 Sep 2024 00:00:00 +0000</pubDate><author>raiven.kao@gmail.com (Raiven Kao)</author>
      <guid>https://www.omegaatt.com/blogs/develop/2023/bitwarden_with_self_hosted_password_backend/</guid>
      <description>&lt;p&gt;在尋覓有哪些 self-hosted 專案好玩時，偶然發現了 1password、LastPass 的開源替代方案，甚至後端資料庫能自架，決定架來用用看。&lt;/p&gt;
&lt;p&gt;為什麼要自架？完全控制資料、隱私考量、或者純粹是學習和折騰 (符合 self-hosted 精神)。但控制權也意味著責任，特別是安全和備份。&lt;/p&gt;
&lt;p&gt;這篇會專注在使用 Bitwarden 客戶端搭配輕量級的 Vaultwarden 後端。&lt;/p&gt;
&lt;h1 id=&#34;使用-bitwarden-來管理密碼&#34;&gt;使用 Bitwarden 來管理密碼&lt;/h1&gt;
&lt;p&gt;&lt;a href=&#34;https://bitwarden.com/products/personal/&#34;&gt;&lt;img alt=&#34;Bitwarden_logo&#34; loading=&#34;lazy&#34; src=&#34;https://upload.wikimedia.org/wikipedia/commons/thumb/c/cc/Bitwarden_logo.svg/1200px-Bitwarden_logo.svg.png&#34;&gt;&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;Bitwarden 是一款流行且功能強大的密碼管理工具，它提供了一個安全的方法來存儲和管理所有密碼。作為一個開源產品，Bitwarden 允許用戶選擇自行托管其服務，這意味著用戶可以在自己的服務器上運行 Bitwarden，從而更好地控制自己的數據安全。&lt;/p&gt;
&lt;h2 id=&#34;bitwarden-的特點&#34;&gt;Bitwarden 的特點&lt;/h2&gt;
&lt;p&gt;&lt;img alt=&#34;bitwarden-demo&#34; loading=&#34;lazy&#34; src=&#34;https://www.omegaatt.com/blogs/develop/2023/bitwarden_with_self_hosted_password_backend/images/Screenshot_20240901_101852.webp&#34;&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;安全性&lt;/strong&gt;: Bitwarden 使用端到端加密，確保只有您可以訪問您的密碼。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;跨平台支持&lt;/strong&gt;: 支持 Windows、macOS、Linux、Android 和 iOS。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;易於使用&lt;/strong&gt;: 提供直觀的用戶界面和簡單的操作流程。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;開源&lt;/strong&gt;: 開源，增加了透明度和安全性。&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Bitwarden 同時&lt;a href=&#34;https://bitwarden.com/help/authenticator-keys/&#34;&gt;支援基於時間的一次性密碼&lt;/a&gt;，讓 TOTP 也能自動填入。也能透過 &lt;a href=&#34;https://github.com/scito/extract_otp_secrets&#34;&gt;github.com/scito/extract_otp_secrets&lt;/a&gt; 來提取 Google Authenticator 內的 2FA 資訊，儲存進 Bitwarden 中。&lt;/p&gt;
&lt;p&gt;&lt;a href=&#34;https://github.com/bitwarden&#34;&gt;Bitwarden 開源了 client 與 server&lt;/a&gt;，在 server 端的選擇有以下：&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;使用 Bitwarden 提供的官方服務，又分為免費跟付費，但這個選擇就跟 1p 沒太多區別。&lt;/li&gt;
&lt;li&gt;自架 Bitwarden 提供的 &lt;a href=&#34;https://github.com/bitwarden/server&#34;&gt;open source server&lt;/a&gt;，由於是使用 C# 與 mssql，吃的記憶體著實太多。&lt;/li&gt;
&lt;li&gt;自架 Bitwarden 相容的後端，我採用的是 rust 實做的 &lt;a href=&#34;https://github.com/dani-garcia/vaultwarden&#34;&gt;Vaultwarden&lt;/a&gt;，搭配 sqlite，記憶體使用量與官方的 C# 不是一個量級的。&lt;/li&gt;
&lt;/ol&gt;
&lt;h2 id=&#34;bitwarden-架構&#34;&gt;Bitwarden 架構&lt;/h2&gt;
&lt;p&gt;bitwarden 的 local storage 都是儲存加密後的密碼資料，不會使用明碼儲存，故上傳到 server 上的也僅僅是加密後的密碼資料。&lt;/p&gt;</description>
    </item>
    <item>
      <title>在 Firefox 上使用 PWA 將網頁應用安裝成 Desktop App</title>
      <link>https://www.omegaatt.com/blogs/develop/2024/pwa_on_firefox/</link>
      <pubDate>Sun, 25 Aug 2024 00:00:00 +0000</pubDate><author>raiven.kao@gmail.com (Raiven Kao)</author>
      <guid>https://www.omegaatt.com/blogs/develop/2024/pwa_on_firefox/</guid>
      <description>&lt;h2 id=&#34;前言&#34;&gt;前言&lt;/h2&gt;
&lt;p&gt;開應用時，跨平台的桌面解決方案時常困擾開發者，在 Linux Desktop 上要支援多種桌面協議與桌面管理系統，更是複雜許多。時常會看到 Electron 等等基於 chromium 的技術。&lt;/p&gt;
&lt;p&gt;當然也可以像是 zed.dev 這樣的 geek 精神，自己寫了一套跨平台的 GUI 框架，但多數新創公司可能沒有這些資源來實現。例如已經非常成熟的 Notion.so、Obsidian 等等都是基於 Electron 實現的。&lt;/p&gt;
&lt;p&gt;我並非一個 &lt;a href=&#34;https://github.com/sudhakar3697/awesome-electron-alternatives&#34;&gt;anti-Electron&lt;/a&gt; 或是 &lt;a href=&#34;https://www.reddit.com/r/degoogle/&#34;&gt;deGoogle&lt;/a&gt; 的人，但試圖找到其他解決方案正式樂趣之所在。&lt;/p&gt;
&lt;h2 id=&#34;pwaprogressive-web-app&#34;&gt;PWA(Progressive Web App）&lt;/h2&gt;
&lt;p&gt;詳盡的 PWA 技術可以到 mozilla 的&lt;a href=&#34;https://developer.mozilla.org/zh-TW/docs/Web/Progressive_web_apps&#34;&gt;文件&lt;/a&gt;中查看，對我來說只要可以達到目的：可以在 Windows、Mac、Linux 上封裝成 Desktop Aplication 即可。&lt;/p&gt;
&lt;h2 id=&#34;官方支援&#34;&gt;官方支援&lt;/h2&gt;
&lt;p&gt;mozilla 官方有一個文件針對「安裝網頁應用到桌面環境」的歷史講解，可以參考&lt;a href=&#34;https://developer.mozilla.org/en-US/docs/Web/Progressive_web_apps/Guides/Installing&#34;&gt;官方文件&lt;/a&gt;。&lt;/p&gt;
&lt;p&gt;文中提到過去將網址「儲存」在桌面上的類似「書籤」的方法(SSB)，以及與 PWA 的不同之處：PWA 的使用者資料是儲存在桌面環境中。&lt;/p&gt;
&lt;h2 id=&#34;pwa-add-on&#34;&gt;PWA Add-on&lt;/h2&gt;
&lt;p&gt;上述文件內有提到需要使用 Fan-maded 的 PWA Add-on 來安裝 PWA：&lt;a href=&#34;https://addons.mozilla.org/en-US/firefox/addon/pwas-for-firefox/&#34;&gt;Progressive Web Apps for Firefox by Filip Štamcar&lt;/a&gt;。&lt;/p&gt;
&lt;p&gt;他是一個開源的 Firefox 擴充套件(&lt;a href=&#34;https://github.com/filips123/PWAsForFirefox&#34;&gt;filips123/PWAsForFirefox&lt;/a&gt;)，具有&lt;a href=&#34;https://pwasforfirefox.filips.si/&#34;&gt;完整的文件&lt;/a&gt;。&lt;/p&gt;
&lt;h3 id=&#34;安裝過程&#34;&gt;安裝過程&lt;/h3&gt;
&lt;ol&gt;
&lt;li&gt;在 Firefox 瀏覽氣上安裝 &lt;a href=&#34;https://addons.mozilla.org/en-US/firefox/addon/pwas-for-firefox/&#34;&gt;PWA Add-on&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;瀏覽器會自動跳到設定頁面，首先會需要同意 EULA
&lt;img alt=&#34;setup-0&#34; loading=&#34;lazy&#34; src=&#34;https://www.omegaatt.com/blogs/develop/2024/pwa_on_firefox/images/setup-0.webp&#34;&gt;&lt;/li&gt;
&lt;li&gt;接著會需要安裝 connector，需要透過該 connector 才能連結瀏覽器本身與 extension。
connector 由 Rust 編寫，使該 extension 可以在跨平台的桌面環境中管理 firefox 設定檔、runtime 等等未開放給 extension 存取的資源。&lt;/li&gt;
&lt;/ol&gt;
&lt;ul&gt;
&lt;li&gt;Windows:
Windows 的 connector 除了手動安裝外，也推薦使用 &lt;a href=&#34;https://github.com/microsoft/winget-cli&#34;&gt;winget&lt;/a&gt; 來安裝，類似於 apt/yum 的套件管理工具。
&lt;img alt=&#34;connector installation windows&#34; loading=&#34;lazy&#34; src=&#34;https://www.omegaatt.com/blogs/develop/2024/pwa_on_firefox/images/setup-1.webp&#34;&gt;&lt;/li&gt;
&lt;li&gt;Linux
Linux 的 connector 可以到 &lt;a href=&#34;https://github.com/filips123/PWAsForFirefox/releases&#34;&gt;github release&lt;/a&gt; 上找到，例如我的作業系統是 OpenSUSE Tumbleweed，則可以下載 .rpm 檔案。
&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;-webkit-text-size-adjust:none;&#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 zypper in ./firefoxpwa-2.12.1-1.x86_64.rpm
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;ul&gt;
&lt;li&gt;Debian/Ubuntu(DEB):
&lt;img alt=&#34;connector installation deb&#34; loading=&#34;lazy&#34; src=&#34;https://www.omegaatt.com/blogs/develop/2024/pwa_on_firefox/images/setup-2.webp&#34;&gt;&lt;/li&gt;
&lt;li&gt;Redhat/Fedora/OpenSUSE(RPM):
&lt;img alt=&#34;connector installation rpm&#34; loading=&#34;lazy&#34; src=&#34;https://www.omegaatt.com/blogs/develop/2024/pwa_on_firefox/images/setup-3.webp&#34;&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;ol&gt;
&lt;li&gt;接著會需要安裝不同於原生 Firefox 的 runtime，根據描述，會下載並由 connector 自動修改成支援 PWA 的 runtime。
&lt;img alt=&#34;runtime installation&#34; loading=&#34;lazy&#34; src=&#34;https://www.omegaatt.com/blogs/develop/2024/pwa_on_firefox/images/setup-4.webp&#34;&gt;&lt;/li&gt;
&lt;li&gt;到這邊已經完成 PWA extension 的安裝，可以嘗試使用 PWA 的方式來安裝 web app to desktop app。&lt;/li&gt;
&lt;li&gt;首先需要打開你想要安裝成 Desktop App 的網頁，如果在不同的網頁嘗試設定，extension 會自動判斷該網頁 CORS Error，即便是 &lt;code&gt;https://www.notion.so&lt;/code&gt; 與 &lt;code&gt;https://notion.so&lt;/code&gt; 也屬於不同的 Origin。
接著點擊 PWAForFirefox 的圖示，可以看到已經安裝好的 extension。
&lt;img alt=&#34;extension icon&#34; loading=&#34;lazy&#34; src=&#34;https://www.omegaatt.com/blogs/develop/2024/pwa_on_firefox/images/setup-5.webp&#34;&gt;&lt;/li&gt;
&lt;li&gt;接著一步一步的跟著指引設定好該 PWA 的設定。
&lt;img alt=&#34;add pwa&#34; loading=&#34;lazy&#34; src=&#34;https://www.omegaatt.com/blogs/develop/2024/pwa_on_firefox/images/setup-6.webp&#34;&gt;
&lt;img alt=&#34;add pwa&#34; loading=&#34;lazy&#34; src=&#34;https://www.omegaatt.com/blogs/develop/2024/pwa_on_firefox/images/setup-7.webp&#34;&gt;
&lt;img alt=&#34;add pwa&#34; loading=&#34;lazy&#34; src=&#34;https://www.omegaatt.com/blogs/develop/2024/pwa_on_firefox/images/setup-8.webp&#34;&gt;
&lt;img alt=&#34;add pwa&#34; loading=&#34;lazy&#34; src=&#34;https://www.omegaatt.com/blogs/develop/2024/pwa_on_firefox/images/setup-9.webp&#34;&gt;&lt;/li&gt;
&lt;li&gt;完成，可以在應用程式選單內找到剛剛建立的 PWA 應用程式。
&lt;img alt=&#34;pwa at menu&#34; loading=&#34;lazy&#34; src=&#34;https://www.omegaatt.com/blogs/develop/2024/pwa_on_firefox/images/setup-10.webp&#34;&gt;&lt;/li&gt;
&lt;li&gt;開啟後可以自動開啟網頁應用程式，並具有獨立的設定檔。
&lt;img alt=&#34;open pwa&#34; loading=&#34;lazy&#34; src=&#34;https://www.omegaatt.com/blogs/develop/2024/pwa_on_firefox/images/setup-11.webp&#34;&gt;&lt;/li&gt;
&lt;/ol&gt;
&lt;h2 id=&#34;使用感受&#34;&gt;使用感受&lt;/h2&gt;
&lt;p&gt;三四年前在 Windows 上習慣使用 Notion Desktop 版本，在 Linux 上反而已經習慣要用網頁來打開，這次嘗試使用 PWA 後倒是經常忘記有將它安裝成 Desktop Application。&lt;/p&gt;</description>
    </item>
    <item>
      <title>透過內建的 pprof 工具來分析 Golang 發生的 memory leak</title>
      <link>https://www.omegaatt.com/blogs/develop/2024/golang_memory_leak_detection/</link>
      <pubDate>Sat, 17 Aug 2024 00:00:00 +0000</pubDate><author>raiven.kao@gmail.com (Raiven Kao)</author>
      <guid>https://www.omegaatt.com/blogs/develop/2024/golang_memory_leak_detection/</guid>
      <description>&lt;h2 id=&#34;前言&#34;&gt;前言&lt;/h2&gt;
&lt;p&gt;某天下午，公司的 cronjob daemon 無預警的被 GCP OOM Kill 了，且程式碼沒有看出明顯的原因。&lt;/p&gt;
&lt;p&gt;根據過去的經驗，local 開發時會使用 &lt;code&gt;go tool pprof&lt;/code&gt; 來分析 CPU profile 或是 memory 與 trace 的問題，詳細可以參考 &lt;a href=&#34;https://go.dev/blog/pprof&#34;&gt;Go 官方文件&lt;/a&gt;。&lt;/p&gt;
&lt;p&gt;由於我們的程式碼是一個基於 gin 的 http service，因此可以使用 &lt;a href=&#34;https://github.com/gin-contrib/pprof&#34;&gt;gin 提供的 pprof&lt;/a&gt; 來快速建立 endpoint。&lt;/p&gt;
&lt;h2 id=&#34;gin-pprof&#34;&gt;gin pprof&lt;/h2&gt;
&lt;p&gt;gin 的 pprof package 提供了數個基於 &lt;code&gt;net/http/pprof&lt;/code&gt; 的 endpoint，可以分別為：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;/&lt;/code&gt;: 基本的 pprof 的 static page，可以分析 CPU 與 memory 的問題。&lt;/li&gt;
&lt;li&gt;&lt;code&gt;/cmdline&lt;/code&gt;: 分析 command line 的問題。&lt;/li&gt;
&lt;li&gt;&lt;code&gt;/profile&lt;/code&gt;: 分析 CPU profile 的問題，可以透過 query string 來指定 CPU profile 的 duration。&lt;/li&gt;
&lt;li&gt;&lt;code&gt;/symbol&lt;/code&gt;: 分析 symbol table 的問題。&lt;/li&gt;
&lt;li&gt;&lt;code&gt;/trace&lt;/code&gt;: 分析 trace 的問題。&lt;/li&gt;
&lt;li&gt;&lt;code&gt;/goroutine&lt;/code&gt;: 分析 goroutine 的問題，這也是本文中重點查看的 endpoint。&lt;/li&gt;
&lt;li&gt;&lt;code&gt;/heap&lt;/code&gt;: 分析 heap 的問題。&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;可以在 http server 中加入以下程式碼來啟用 pprof：&lt;/p&gt;</description>
    </item>
    <item>
      <title>從實務經驗重新認識 TLS</title>
      <link>https://www.omegaatt.com/blogs/develop/2024/tls_tutorial_with_two_practice/</link>
      <pubDate>Sat, 22 Jun 2024 00:00:00 +0000</pubDate><author>raiven.kao@gmail.com (Raiven Kao)</author>
      <guid>https://www.omegaatt.com/blogs/develop/2024/tls_tutorial_with_two_practice/</guid>
      <description>&lt;p&gt;因為&lt;a href=&#34;./../grafana_embed_example&#34;&gt;工作需要部署 grafana&lt;/a&gt;，重新認識了 TLS 的流程，藉此機會把學習過程紀錄下來。&lt;/p&gt;
&lt;p&gt;主要分為理論與案例分享兩個大章節，&lt;a href=&#34;#%E7%90%86%E8%AB%96&#34;&gt;理論章節&lt;/a&gt;主要是在講「應該要知道但學過就會忘」的內行，&lt;a href=&#34;#%E6%A1%88%E4%BE%8B%E5%88%86%E4%BA%AB&#34;&gt;案例分享&lt;/a&gt;則為這一次部署 grafana 時的經驗分享。&lt;/p&gt;
&lt;h2 id=&#34;理論&#34;&gt;理論&lt;/h2&gt;
&lt;h3 id=&#34;介紹&#34;&gt;介紹&lt;/h3&gt;
&lt;h4 id=&#34;tls-是什麼&#34;&gt;TLS 是什麼&lt;/h4&gt;
&lt;p&gt;TLS（Transport Layer Security，傳輸層安全協議）是用來保護互聯網通訊安全的協議。它能夠確保數據在互聯網上傳輸過程中的機密性、完整性和真實性。TLS 是從 SSL（Secure Sockets Layer）發展而來的，因此我們可能也可以把它稱為 SSL/TLS。&lt;/p&gt;
&lt;h4 id=&#34;為什麼需要-tls&#34;&gt;為什麼需要 TLS&lt;/h4&gt;
&lt;p&gt;在現代互聯網環境中，數據安全性變得至關重要。無論是電商、網路銀行或是社交平台，都需要確保用戶的機敏資訊不會被攔截或篡改。TLS 可以加密通訊內容，防止第三方竊聽，並且能驗證通訊雙方的身份，防止中間人攻擊（MITM）。&lt;/p&gt;
&lt;h4 id=&#34;tls-的應用場景&#34;&gt;TLS 的應用場景&lt;/h4&gt;
&lt;p&gt;TLS 被廣泛應用於各種需要保護數據傳輸的場景，包括但不限於：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;網站和應用程式的 HTTPS 通訊&lt;/li&gt;
&lt;li&gt;電子郵件的加密傳輸（如 SMTPS、IMAPS、POP3S）&lt;/li&gt;
&lt;li&gt;VPN 通訊&lt;/li&gt;
&lt;li&gt;即時消息應用程式&lt;/li&gt;
&lt;li&gt;各類雲服務和 API&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 id=&#34;tls-基本概念&#34;&gt;TLS 基本概念&lt;/h3&gt;
&lt;div class=&#34;svg-container&#34;&gt;
&lt;img alt=&#34;symmetric-encryption&#34; loading=&#34;lazy&#34; src=&#34;https://cf-assets.www.cloudflare.com/slt3lc6tev37/1PYEAgdkoII5tQ5yzweHEX/a025977d2cb6a74df020ceb6273ae6d5/symmetric-encryption.svg&#34;&gt;
&lt;/div&gt;
&lt;h4 id=&#34;公鑰加密和私鑰加密&#34;&gt;公鑰加密和私鑰加密&lt;/h4&gt;
&lt;p&gt;公鑰加密和私鑰加密是 TLS 的基礎。每個參與通訊的實體都擁有一對密鑰：公鑰和私鑰。公鑰是公開的，任何人都可以用來加密訊息，而私鑰是保密的，只有擁有者可以用來解密訊息。這樣的機制確保了即使加密訊息被攔截，也只有擁有私鑰的人能夠解密，細節可以回顧密碼學或計算機概論。&lt;/p&gt;
&lt;h4 id=&#34;對稱加密和非對稱加密&#34;&gt;對稱加密和非對稱加密&lt;/h4&gt;
&lt;p&gt;對稱加密使用相同的密鑰來加密和解密數據，而非對稱加密則使用一對密鑰（公鑰和私鑰）。TLS 結合了這兩種加密方式：在握手階段使用非對稱加密來安全地交換對稱加密的密鑰，之後的通訊則使用對稱加密來提高效率。&lt;/p&gt;
&lt;h4 id=&#34;數位證書和證書授權機構ca&#34;&gt;數位證書和證書授權機構（CA）&lt;/h4&gt;
&lt;p&gt;數位證書是用來證明公鑰擁有者身份的電子文件，通常由證書授權機構（CA）簽發。證書包含公鑰、擁有者訊息以及 CA 的數字簽名。瀏覽器和其他應用程式可以驗證證書的真實性，確保通訊對象的身份。&lt;/p&gt;
&lt;p&gt;數位證書的主要類型有：&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;&lt;strong&gt;域名驗證（DV）證書&lt;/strong&gt;：僅驗證域名的所有權。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;組織驗證（OV）證書&lt;/strong&gt;：除了驗證域名，還驗證組織的合法性。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;擴展驗證（EV）證書&lt;/strong&gt;：提供最高級別的驗證，包括嚴格的身份檢查，瀏覽器地址欄顯示綠色的公司名稱。&lt;/li&gt;
&lt;/ol&gt;
&lt;h3 id=&#34;tls-的工作原理&#34;&gt;TLS 的工作原理&lt;/h3&gt;
&lt;h4 id=&#34;握手過程handshake-process&#34;&gt;握手過程（Handshake Process）&lt;/h4&gt;
&lt;p&gt;TLS 握手過程是建立安全連接的第一步，確保通訊雙方能夠安全地交換加密訊息。握手過程大致分為以下幾個步驟：&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;&lt;strong&gt;客戶端問候（Client Hello）&lt;/strong&gt;：客戶端發送一個問候消息給伺服器，包含支持的 TLS 版本、加密算法、隨機數和其他必要訊息。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;伺服器問候（Server Hello）&lt;/strong&gt;：伺服器回應客戶端的問候消息，選擇一個加密算法，並發送伺服器的隨機數。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;伺服器證書（Server Certificate）&lt;/strong&gt;：伺服器發送其數位證書給客戶端，用於驗證伺服器的身份。證書包含伺服器的公鑰和由 CA 簽名的證書。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;密鑰交換（Key Exchange）&lt;/strong&gt;：伺服器和客戶端交換密鑰訊息，使用非對稱加密算法（如 RSA、ECDHE）安全地生成會話密鑰。這個會話密鑰將用於之後的對稱加密通訊。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;加密通訊（Encrypted Communication）&lt;/strong&gt;：客戶端和伺服器使用協商好的會話密鑰進行加密通訊。這確保了後續的數據傳輸是安全的。&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;以 TLS 1.3 的工作方式為例：&lt;/p&gt;</description>
    </item>
    <item>
      <title>透過 throughput 與 latency 的關係解決高延遲問題</title>
      <link>https://www.omegaatt.com/blogs/develop/2024/throughput_and_latency/</link>
      <pubDate>Sun, 16 Jun 2024 00:00:00 +0000</pubDate><author>raiven.kao@gmail.com (Raiven Kao)</author>
      <guid>https://www.omegaatt.com/blogs/develop/2024/throughput_and_latency/</guid>
      <description>&lt;p&gt;在軟體工程中，我們常會面臨服務 high latency 的問題，而要解決這個問題，理解 throughput 與 latency 的關係是關鍵。&lt;/p&gt;
&lt;h2 id=&#34;名詞定義&#34;&gt;名詞定義&lt;/h2&gt;
&lt;p&gt;什麼是 throughput 與 latency？&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;latency：處理一個請求需要花多少時間。從請求進來到回應出去的時間差。通常用毫秒 (ms) 來量測。越低越好。&lt;/li&gt;
&lt;li&gt;throughput：單位時間內能處理多少請求。通常用 RPS (Requests Per Second) 或 QPS (Queries Per Second) 來表示。越高越好。&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id=&#34;server-如何工作&#34;&gt;server 如何工作&lt;/h2&gt;
&lt;p&gt;server 接收請求後，由一個或多執行緒（threads）處理請求並生成回應。單執行緒 server 一次只能處理一個請求，而多執行緒 server 可以同時處理多個請求。&lt;/p&gt;
&lt;p&gt;想像一下你的伺服器就是一家自助洗衣店。請求就像是帶衣服來洗的客人。&lt;/p&gt;
&lt;p&gt;單執行緒的伺服器就像店裡只有一台洗衣機，一次只能服務一個客人。客人得排隊。
多執行緒/多進程/異步 I/O 的伺服器就像有多台洗衣機，可以同時服務多個客人。&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;latency：從顧客開始操作洗衣機到洗衣完成的時間。這包括顧客等候空閒洗衣機的時間，以及實際洗衣的時間。等候時間越長，latency 越高。&lt;/li&gt;
&lt;li&gt;throughput：洗衣店每小時完成的洗衣次數。洗衣機數量越多，洗衣店能處理的顧客數量越多，throughput 越高。&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 id=&#34;latency-與-throughput-的關係&#34;&gt;latency 與 throughput 的關係&lt;/h3&gt;
&lt;p&gt;很多人以為 latency 和 throughput 的關係是簡單的線性反比，或者覺得加機器就能解決一切。但現實更殘酷一點。&lt;/p&gt;
&lt;p&gt;起初，當客人不多（請求量低）的時候，洗衣機（伺服器資源）很空閒，客人一來就能馬上洗，這時候 latency 很低，主要就是洗衣機本身運作的時間（我們稱之為 service time 或 true latency）。&lt;/p&gt;
&lt;p&gt;隨著客人變多（請求量增加），洗衣機開始被佔滿。當所有洗衣機都在運作時，新來的客人就得排隊等了。這段等待時間就是 queueing latency。&lt;/p&gt;
&lt;p&gt;Total Latency = Queuing Latency + Service Time&lt;/p&gt;</description>
    </item>
    <item>
      <title>基於 Golang 的 Grafana Dashboard 與 JWT 認證的前後端實作</title>
      <link>https://www.omegaatt.com/blogs/develop/2024/grafana_embed_example/</link>
      <pubDate>Mon, 10 Jun 2024 00:00:00 +0000</pubDate><author>raiven.kao@gmail.com (Raiven Kao)</author>
      <guid>https://www.omegaatt.com/blogs/develop/2024/grafana_embed_example/</guid>
      <description>&lt;p&gt;在工作上有一個需求是需要做一些 &lt;a href=&#34;https://aws.amazon.com/tw/what-is/olap/&#34;&gt;OLAP&lt;/a&gt;，原訂計畫是使用 Google Looker(ver. Google Cloud Core)，礙於量小不符合經濟效益，決定用 Grafana 這個較熟悉的開源套件來幫助我們做視覺化的處理。&lt;/p&gt;
&lt;p&gt;這篇文章的範例可以在 &lt;a href=&#34;https://github.com/omegaatt36/grafana-embed-example&#34;&gt;omegaatt36/grafana-embed-example&lt;/a&gt; 中找到所有的 source code&lt;/p&gt;
&lt;p&gt;我的 Use Case 為已經有一組 SHA512 產生的 Key，以下的內容為使用 HS512 進行簽名與認證。&lt;/p&gt;
&lt;h2 id=&#34;流程&#34;&gt;流程&lt;/h2&gt;
&lt;pre class=&#34;mermaid&#34;&gt;sequenceDiagram
    autonumber
    participant U as User
    participant B as Browser
    participant I as iframe
    participant S as Server
    participant G as Grafana

    rect rgb(236,239,244)
    U-&gt;&gt;B: Open Web Page
    B-&gt;&gt;S: Request JWT Token
    S-&gt;&gt;B: Return JWT Token
    B-&gt;&gt;I: Load iframe
    I-&gt;&gt;G: Request Dashboard with JWT Token
    G-&gt;&gt;I: Return Dashboard
    I-&gt;&gt;B: Display Dashboard in iframe
    B-&gt;&gt;U: Show Dashboard
    end
&lt;/pre&gt;

&lt;h2 id=&#34;grafana&#34;&gt;Grafana&lt;/h2&gt;
&lt;h3 id=&#34;配置&#34;&gt;配置&lt;/h3&gt;
&lt;p&gt;主要是針對 &lt;code&gt;grafana.ini&lt;/code&gt; 做修改&lt;/p&gt;</description>
    </item>
    <item>
      <title>從 vuepress 遷移到 hugo 的心得</title>
      <link>https://www.omegaatt.com/blogs/develop/2024/migrate_from_vuepress_to_hugo/</link>
      <pubDate>Mon, 10 Jun 2024 00:00:00 +0000</pubDate><author>raiven.kao@gmail.com (Raiven Kao)</author>
      <guid>https://www.omegaatt.com/blogs/develop/2024/migrate_from_vuepress_to_hugo/</guid>
      <description>&lt;h2 id=&#34;前言&#34;&gt;前言&lt;/h2&gt;
&lt;p&gt;在網頁開發中，選擇合適的靜態網站生成器非常重要。過去我們使用 VuePress 來建立部落格網站，但在使用過程中遇到了一些痛點，最終決定遷移到 Hugo。本篇文章分享遷移過程中的一些心得。&lt;/p&gt;
&lt;h2 id=&#34;當初選擇-vuepress-而非-hugo-的原因&#34;&gt;當初選擇 VuePress 而非 Hugo 的原因&lt;/h2&gt;
&lt;p&gt;最初想要撰寫部落格時，已經有再 hugo 與 vuepress 間做選擇，當時覺得 hexo 與 hugo 實在是爛大街了，想要一些與眾不同的體驗。&lt;/p&gt;
&lt;p&gt;選擇 VuePress 是基於以下幾點考量：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;朋友推薦，他是一名&lt;a href=&#34;https://www.linkedin.com/in/billy-chin/&#34;&gt;資深前端工程師&lt;/a&gt;，是 vuepress core-team 成員。&lt;/li&gt;
&lt;li&gt;僅需要撰寫 markdown，便能產生靜態的部落格。&lt;/li&gt;
&lt;li&gt;使用 theme plugin，可以方便的美化部落格，僅需要專注在內容的撰寫，不需要操心部落格是不是白底黑字而已。&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id=&#34;使用-vuepress-的痛點&#34;&gt;使用 VuePress 的痛點&lt;/h2&gt;
&lt;p&gt;起初我所使用的 theme plugin 為 &lt;a href=&#34;https://github.com/vuepress-reco/vuepress-theme-reco-1.x&#34;&gt;vuepress-theme-reco(1.x)&lt;/a&gt;，並且搭配 vuepress 1.x 來進行建構。或是 vitepress，然而，在使用 VuePress 的過程中，我們也遇到了一些問題：&lt;/p&gt;
&lt;h3 id=&#34;構建速度&#34;&gt;構建速度&lt;/h3&gt;
&lt;p&gt;我的部落格是無付費的使用 Netlify 進行託管，使用 vuepress 進行建構總是會花超過三分鐘，當進行 pr 的 preview build 時將會耗費大量的執行時間。&lt;/p&gt;
&lt;h3 id=&#34;版本更新&#34;&gt;版本更新&lt;/h3&gt;
&lt;p&gt;過了一兩年，vuepress 官方開始推動 vuepress next(2.x)，vuepress-theme-reco 的作者也著手更新 &lt;a href=&#34;https://github.com/vuepress-reco/vuepress-theme-reco&#34;&gt;next(2.x)&lt;/a&gt; 版本。期間從 alpha 版本到 beta 版本，當 vuepress 與 theme plugin 有 API 不同不的問題，就容易導致 build failed，這個情況到了 rc 版本也沒有改善。&lt;/p&gt;</description>
    </item>
    <item>
      <title>使用內建的 rsync 備份 Truenas Scale 到 Proxmox Backup Server</title>
      <link>https://www.omegaatt.com/blogs/develop/2024/backup_from_truenas_scale_to_pbs/</link>
      <pubDate>Sun, 19 May 2024 00:00:00 +0000</pubDate><author>raiven.kao@gmail.com (Raiven Kao)</author>
      <guid>https://www.omegaatt.com/blogs/develop/2024/backup_from_truenas_scale_to_pbs/</guid>
      <description>&lt;h2 id=&#34;前言&#34;&gt;前言&lt;/h2&gt;
&lt;p&gt;過去我會使用 backup script 配合 crontab 來定期的備份 nas 的資料，這次更換了 Proxmox Backup Server 的物理機後，多了一個硬碟的空間好讓我實驗 Truenas Scale 的備份機制。&lt;/p&gt;
&lt;h3 id=&#34;差異&#34;&gt;差異&lt;/h3&gt;
&lt;p&gt;rsync 本身並沒有 server/client 的概念，只有 source 與 destination。
過去我會在&lt;strong&gt;備份主機&lt;/strong&gt;上透過 samba 來 mount Nas 到資料夾內，檢查有沒有 mount 成功才在&lt;strong&gt;備份主機&lt;/strong&gt;上使用 rsync。
而 Truenas Scale 提供的 Data Protection 功能中，內建了 Rsync Tasks 模組，透過預先建立好的 ssh credential 來呼叫&lt;strong&gt;備份主機&lt;/strong&gt;進行 rsync。
同樣都是由&lt;strong&gt;備份主機&lt;/strong&gt;來進行 rsync，主要是任務的執行呼叫是 Truenas Scale 本身還是&lt;strong&gt;備份主基本身&lt;/strong&gt;。&lt;/p&gt;
&lt;h2 id=&#34;在-truenas-scale-上建立備份任務&#34;&gt;在 Truenas Scale 上建立備份任務&lt;/h2&gt;
&lt;h3 id=&#34;建立-ssh-pair&#34;&gt;建立 SSH Pair&lt;/h3&gt;
&lt;p&gt;首先到 Credentials -&amp;gt; Backup Credentials 的頁面&lt;/p&gt;
&lt;p&gt;&lt;img loading=&#34;lazy&#34; src=&#34;https://www.omegaatt.com/blogs/develop/2024/backup_from_truenas_scale_to_pbs/images/20240519_163627.webp&#34;&gt;&lt;/p&gt;
&lt;p&gt;點擊 SSH Configurations 中的 Add&lt;/p&gt;</description>
    </item>
    <item>
      <title>用 testcontainers 在本地開發 Go 應用程式</title>
      <link>https://www.omegaatt.com/blogs/develop/2024/testcontainers_with_go/</link>
      <pubDate>Sun, 19 May 2024 00:00:00 +0000</pubDate><author>raiven.kao@gmail.com (Raiven Kao)</author>
      <guid>https://www.omegaatt.com/blogs/develop/2024/testcontainers_with_go/</guid>
      <description>&lt;h2 id=&#34;介紹&#34;&gt;介紹&lt;/h2&gt;
&lt;p&gt;使用 &lt;a href=&#34;https://testcontainers.com/&#34;&gt;testcontainers&lt;/a&gt; 是在本地開發 Golang 應用程式的一個高效方式。這可以讓我們在不需要依賴外部環境的情況下，模擬應用程式在實際生產環境中的運行狀況。&lt;/p&gt;
&lt;h3 id=&#34;安裝-testcontainers&#34;&gt;安裝 testcontainers&lt;/h3&gt;
&lt;p&gt;在 Go 專案中，我們可以通過以下指令來導入 testcontainers&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;-webkit-text-size-adjust:none;&#34;&gt;&lt;code class=&#34;language-shell&#34; data-lang=&#34;shell&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;go get github.com/testcontainers/testcontainers-go
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;h3 id=&#34;透過-redis-實踐一個-rate-limiter&#34;&gt;透過 Redis 實踐一個 rate limiter&lt;/h3&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;-webkit-text-size-adjust:none;&#34;&gt;&lt;code class=&#34;language-go&#34; data-lang=&#34;go&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#f92672&#34;&gt;package&lt;/span&gt; &lt;span style=&#34;color:#a6e22e&#34;&gt;user&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;type&lt;/span&gt; &lt;span style=&#34;color:#a6e22e&#34;&gt;Limiter&lt;/span&gt; &lt;span style=&#34;color:#66d9ef&#34;&gt;struct&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;client&lt;/span&gt; &lt;span style=&#34;color:#f92672&#34;&gt;*&lt;/span&gt;&lt;span style=&#34;color:#a6e22e&#34;&gt;redis&lt;/span&gt;.&lt;span style=&#34;color:#a6e22e&#34;&gt;Client&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:#a6e22e&#34;&gt;limit&lt;/span&gt;         &lt;span style=&#34;color:#66d9ef&#34;&gt;int&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;limitPeriod&lt;/span&gt;   &lt;span style=&#34;color:#a6e22e&#34;&gt;time&lt;/span&gt;.&lt;span style=&#34;color:#a6e22e&#34;&gt;Duration&lt;/span&gt; &lt;span style=&#34;color:#75715e&#34;&gt;// 1 hour for limitPeriod&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;counterWindow&lt;/span&gt; &lt;span style=&#34;color:#a6e22e&#34;&gt;time&lt;/span&gt;.&lt;span style=&#34;color:#a6e22e&#34;&gt;Duration&lt;/span&gt; &lt;span style=&#34;color:#75715e&#34;&gt;// 1 minute for example, 1/60 of the period&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;func&lt;/span&gt; &lt;span style=&#34;color:#a6e22e&#34;&gt;NewLimiter&lt;/span&gt;(&lt;span style=&#34;color:#a6e22e&#34;&gt;client&lt;/span&gt; &lt;span style=&#34;color:#f92672&#34;&gt;*&lt;/span&gt;&lt;span style=&#34;color:#a6e22e&#34;&gt;redis&lt;/span&gt;.&lt;span style=&#34;color:#a6e22e&#34;&gt;Client&lt;/span&gt;, &lt;span style=&#34;color:#a6e22e&#34;&gt;limit&lt;/span&gt; &lt;span style=&#34;color:#66d9ef&#34;&gt;int&lt;/span&gt;, &lt;span style=&#34;color:#a6e22e&#34;&gt;period&lt;/span&gt;, &lt;span style=&#34;color:#a6e22e&#34;&gt;expiry&lt;/span&gt; &lt;span style=&#34;color:#a6e22e&#34;&gt;time&lt;/span&gt;.&lt;span style=&#34;color:#a6e22e&#34;&gt;Duration&lt;/span&gt;) &lt;span style=&#34;color:#f92672&#34;&gt;*&lt;/span&gt;&lt;span style=&#34;color:#a6e22e&#34;&gt;Limiter&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:#f92672&#34;&gt;&amp;amp;&lt;/span&gt;&lt;span style=&#34;color:#a6e22e&#34;&gt;Limiter&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;client&lt;/span&gt;: &lt;span style=&#34;color:#a6e22e&#34;&gt;client&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:#a6e22e&#34;&gt;limit&lt;/span&gt;:         &lt;span style=&#34;color:#a6e22e&#34;&gt;limit&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;limitPeriod&lt;/span&gt;:   &lt;span style=&#34;color:#a6e22e&#34;&gt;period&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;counterWindow&lt;/span&gt;: &lt;span style=&#34;color:#a6e22e&#34;&gt;expiry&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;func&lt;/span&gt; (&lt;span style=&#34;color:#a6e22e&#34;&gt;r&lt;/span&gt; &lt;span style=&#34;color:#f92672&#34;&gt;*&lt;/span&gt;&lt;span style=&#34;color:#a6e22e&#34;&gt;Limiter&lt;/span&gt;) &lt;span style=&#34;color:#a6e22e&#34;&gt;AllowRequest&lt;/span&gt;(&lt;span style=&#34;color:#a6e22e&#34;&gt;ctx&lt;/span&gt; &lt;span style=&#34;color:#a6e22e&#34;&gt;context&lt;/span&gt;.&lt;span style=&#34;color:#a6e22e&#34;&gt;Context&lt;/span&gt;, &lt;span style=&#34;color:#a6e22e&#34;&gt;key&lt;/span&gt; &lt;span style=&#34;color:#66d9ef&#34;&gt;string&lt;/span&gt;, &lt;span style=&#34;color:#a6e22e&#34;&gt;incr&lt;/span&gt; &lt;span style=&#34;color:#66d9ef&#34;&gt;int&lt;/span&gt;) &lt;span style=&#34;color:#66d9ef&#34;&gt;error&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;now&lt;/span&gt; &lt;span style=&#34;color:#f92672&#34;&gt;:=&lt;/span&gt; &lt;span style=&#34;color:#a6e22e&#34;&gt;time&lt;/span&gt;.&lt;span style=&#34;color:#a6e22e&#34;&gt;Now&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:#a6e22e&#34;&gt;fmt&lt;/span&gt;.&lt;span style=&#34;color:#a6e22e&#34;&gt;Sprint&lt;/span&gt;(&lt;span style=&#34;color:#a6e22e&#34;&gt;now&lt;/span&gt;.&lt;span style=&#34;color:#a6e22e&#34;&gt;Truncate&lt;/span&gt;(&lt;span style=&#34;color:#a6e22e&#34;&gt;r&lt;/span&gt;.&lt;span style=&#34;color:#a6e22e&#34;&gt;counterWindow&lt;/span&gt;).&lt;span style=&#34;color:#a6e22e&#34;&gt;Unix&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:#a6e22e&#34;&gt;val&lt;/span&gt;, &lt;span style=&#34;color:#a6e22e&#34;&gt;err&lt;/span&gt; &lt;span style=&#34;color:#f92672&#34;&gt;:=&lt;/span&gt; &lt;span style=&#34;color:#a6e22e&#34;&gt;r&lt;/span&gt;.&lt;span style=&#34;color:#a6e22e&#34;&gt;client&lt;/span&gt;.&lt;span style=&#34;color:#a6e22e&#34;&gt;HIncrBy&lt;/span&gt;(&lt;span style=&#34;color:#a6e22e&#34;&gt;ctx&lt;/span&gt;, &lt;span style=&#34;color:#a6e22e&#34;&gt;key&lt;/span&gt;, &lt;span style=&#34;color:#a6e22e&#34;&gt;timestamp&lt;/span&gt;, int64(&lt;span style=&#34;color:#a6e22e&#34;&gt;incr&lt;/span&gt;)).&lt;span style=&#34;color:#a6e22e&#34;&gt;Result&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;err&lt;/span&gt; &lt;span style=&#34;color:#f92672&#34;&gt;!=&lt;/span&gt; &lt;span style=&#34;color:#66d9ef&#34;&gt;nil&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:#a6e22e&#34;&gt;err&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;if&lt;/span&gt; &lt;span style=&#34;color:#a6e22e&#34;&gt;val&lt;/span&gt; &lt;span style=&#34;color:#f92672&#34;&gt;&amp;gt;=&lt;/span&gt; int64(&lt;span style=&#34;color:#a6e22e&#34;&gt;r&lt;/span&gt;.&lt;span style=&#34;color:#a6e22e&#34;&gt;limit&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:#a6e22e&#34;&gt;ErrRateLimitExceeded&lt;/span&gt;(&lt;span style=&#34;color:#ae81ff&#34;&gt;0&lt;/span&gt;, &lt;span style=&#34;color:#a6e22e&#34;&gt;r&lt;/span&gt;.&lt;span style=&#34;color:#a6e22e&#34;&gt;limit&lt;/span&gt;, &lt;span style=&#34;color:#a6e22e&#34;&gt;r&lt;/span&gt;.&lt;span style=&#34;color:#a6e22e&#34;&gt;limitPeriod&lt;/span&gt;, &lt;span style=&#34;color:#a6e22e&#34;&gt;now&lt;/span&gt;.&lt;span style=&#34;color:#a6e22e&#34;&gt;Add&lt;/span&gt;(&lt;span style=&#34;color:#a6e22e&#34;&gt;r&lt;/span&gt;.&lt;span style=&#34;color:#a6e22e&#34;&gt;limitPeriod&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:#a6e22e&#34;&gt;r&lt;/span&gt;.&lt;span style=&#34;color:#a6e22e&#34;&gt;client&lt;/span&gt;.&lt;span style=&#34;color:#a6e22e&#34;&gt;Expire&lt;/span&gt;(&lt;span style=&#34;color:#a6e22e&#34;&gt;ctx&lt;/span&gt;, &lt;span style=&#34;color:#a6e22e&#34;&gt;key&lt;/span&gt;, &lt;span style=&#34;color:#a6e22e&#34;&gt;r&lt;/span&gt;.&lt;span style=&#34;color:#a6e22e&#34;&gt;limitPeriod&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:#a6e22e&#34;&gt;result&lt;/span&gt;, &lt;span style=&#34;color:#a6e22e&#34;&gt;err&lt;/span&gt; &lt;span style=&#34;color:#f92672&#34;&gt;:=&lt;/span&gt; &lt;span style=&#34;color:#a6e22e&#34;&gt;r&lt;/span&gt;.&lt;span style=&#34;color:#a6e22e&#34;&gt;client&lt;/span&gt;.&lt;span style=&#34;color:#a6e22e&#34;&gt;HGetAll&lt;/span&gt;(&lt;span style=&#34;color:#a6e22e&#34;&gt;ctx&lt;/span&gt;, &lt;span style=&#34;color:#a6e22e&#34;&gt;key&lt;/span&gt;).&lt;span style=&#34;color:#a6e22e&#34;&gt;Result&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;err&lt;/span&gt; &lt;span style=&#34;color:#f92672&#34;&gt;!=&lt;/span&gt; &lt;span style=&#34;color:#66d9ef&#34;&gt;nil&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:#a6e22e&#34;&gt;err&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:#a6e22e&#34;&gt;threshold&lt;/span&gt; &lt;span style=&#34;color:#f92672&#34;&gt;:=&lt;/span&gt; &lt;span style=&#34;color:#a6e22e&#34;&gt;fmt&lt;/span&gt;.&lt;span style=&#34;color:#a6e22e&#34;&gt;Sprint&lt;/span&gt;(&lt;span style=&#34;color:#a6e22e&#34;&gt;now&lt;/span&gt;.&lt;span style=&#34;color:#a6e22e&#34;&gt;Add&lt;/span&gt;(&lt;span style=&#34;color:#f92672&#34;&gt;-&lt;/span&gt;&lt;span style=&#34;color:#a6e22e&#34;&gt;r&lt;/span&gt;.&lt;span style=&#34;color:#a6e22e&#34;&gt;limitPeriod&lt;/span&gt;).&lt;span style=&#34;color:#a6e22e&#34;&gt;Unix&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:#a6e22e&#34;&gt;total&lt;/span&gt; &lt;span style=&#34;color:#f92672&#34;&gt;:=&lt;/span&gt; &lt;span style=&#34;color:#ae81ff&#34;&gt;0&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;for&lt;/span&gt; &lt;span style=&#34;color:#a6e22e&#34;&gt;k&lt;/span&gt;, &lt;span style=&#34;color:#a6e22e&#34;&gt;v&lt;/span&gt; &lt;span style=&#34;color:#f92672&#34;&gt;:=&lt;/span&gt; &lt;span style=&#34;color:#66d9ef&#34;&gt;range&lt;/span&gt; &lt;span style=&#34;color:#a6e22e&#34;&gt;result&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;k&lt;/span&gt; &amp;gt; &lt;span style=&#34;color:#a6e22e&#34;&gt;threshold&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;i&lt;/span&gt;, &lt;span style=&#34;color:#a6e22e&#34;&gt;_&lt;/span&gt; &lt;span style=&#34;color:#f92672&#34;&gt;:=&lt;/span&gt; &lt;span style=&#34;color:#a6e22e&#34;&gt;strconv&lt;/span&gt;.&lt;span style=&#34;color:#a6e22e&#34;&gt;Atoi&lt;/span&gt;(&lt;span style=&#34;color:#a6e22e&#34;&gt;v&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;total&lt;/span&gt; &lt;span style=&#34;color:#f92672&#34;&gt;+=&lt;/span&gt; &lt;span style=&#34;color:#a6e22e&#34;&gt;i&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;else&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;r&lt;/span&gt;.&lt;span style=&#34;color:#a6e22e&#34;&gt;client&lt;/span&gt;.&lt;span style=&#34;color:#a6e22e&#34;&gt;HDel&lt;/span&gt;(&lt;span style=&#34;color:#a6e22e&#34;&gt;ctx&lt;/span&gt;, &lt;span style=&#34;color:#a6e22e&#34;&gt;key&lt;/span&gt;, &lt;span style=&#34;color:#a6e22e&#34;&gt;k&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;if&lt;/span&gt; &lt;span style=&#34;color:#a6e22e&#34;&gt;total&lt;/span&gt; &lt;span style=&#34;color:#f92672&#34;&gt;&amp;gt;=&lt;/span&gt; int(&lt;span style=&#34;color:#a6e22e&#34;&gt;r&lt;/span&gt;.&lt;span style=&#34;color:#a6e22e&#34;&gt;limit&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:#a6e22e&#34;&gt;ErrRateLimitExceeded&lt;/span&gt;(&lt;span style=&#34;color:#ae81ff&#34;&gt;0&lt;/span&gt;, &lt;span style=&#34;color:#a6e22e&#34;&gt;r&lt;/span&gt;.&lt;span style=&#34;color:#a6e22e&#34;&gt;limit&lt;/span&gt;, &lt;span style=&#34;color:#a6e22e&#34;&gt;r&lt;/span&gt;.&lt;span style=&#34;color:#a6e22e&#34;&gt;limitPeriod&lt;/span&gt;, &lt;span style=&#34;color:#a6e22e&#34;&gt;now&lt;/span&gt;.&lt;span style=&#34;color:#a6e22e&#34;&gt;Add&lt;/span&gt;(&lt;span style=&#34;color:#a6e22e&#34;&gt;r&lt;/span&gt;.&lt;span style=&#34;color:#a6e22e&#34;&gt;limitPeriod&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;nil&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;type&lt;/span&gt; &lt;span style=&#34;color:#a6e22e&#34;&gt;RateLimitExceeded&lt;/span&gt; &lt;span style=&#34;color:#66d9ef&#34;&gt;struct&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;Remaining&lt;/span&gt; &lt;span style=&#34;color:#66d9ef&#34;&gt;int&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;Limit&lt;/span&gt;     &lt;span style=&#34;color:#66d9ef&#34;&gt;int&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;Period&lt;/span&gt;    &lt;span style=&#34;color:#a6e22e&#34;&gt;time&lt;/span&gt;.&lt;span style=&#34;color:#a6e22e&#34;&gt;Duration&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;Reset&lt;/span&gt;     &lt;span style=&#34;color:#a6e22e&#34;&gt;time&lt;/span&gt;.&lt;span style=&#34;color:#a6e22e&#34;&gt;Time&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;func&lt;/span&gt; &lt;span style=&#34;color:#a6e22e&#34;&gt;ErrRateLimitExceeded&lt;/span&gt;(&lt;span style=&#34;color:#a6e22e&#34;&gt;remaining&lt;/span&gt; &lt;span style=&#34;color:#66d9ef&#34;&gt;int&lt;/span&gt;, &lt;span style=&#34;color:#a6e22e&#34;&gt;limit&lt;/span&gt; &lt;span style=&#34;color:#66d9ef&#34;&gt;int&lt;/span&gt;, &lt;span style=&#34;color:#a6e22e&#34;&gt;period&lt;/span&gt; &lt;span style=&#34;color:#a6e22e&#34;&gt;time&lt;/span&gt;.&lt;span style=&#34;color:#a6e22e&#34;&gt;Duration&lt;/span&gt;, &lt;span style=&#34;color:#a6e22e&#34;&gt;reset&lt;/span&gt; &lt;span style=&#34;color:#a6e22e&#34;&gt;time&lt;/span&gt;.&lt;span style=&#34;color:#a6e22e&#34;&gt;Time&lt;/span&gt;) &lt;span style=&#34;color:#66d9ef&#34;&gt;error&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:#a6e22e&#34;&gt;RateLimitExceeded&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;Remaining&lt;/span&gt;: &lt;span style=&#34;color:#a6e22e&#34;&gt;remaining&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;Limit&lt;/span&gt;:     &lt;span style=&#34;color:#a6e22e&#34;&gt;limit&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;Period&lt;/span&gt;:    &lt;span style=&#34;color:#a6e22e&#34;&gt;period&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;Reset&lt;/span&gt;:     &lt;span style=&#34;color:#a6e22e&#34;&gt;reset&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;func&lt;/span&gt; (&lt;span style=&#34;color:#a6e22e&#34;&gt;e&lt;/span&gt; &lt;span style=&#34;color:#a6e22e&#34;&gt;RateLimitExceeded&lt;/span&gt;) &lt;span style=&#34;color:#a6e22e&#34;&gt;Error&lt;/span&gt;() &lt;span style=&#34;color:#66d9ef&#34;&gt;string&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:#a6e22e&#34;&gt;fmt&lt;/span&gt;.&lt;span style=&#34;color:#a6e22e&#34;&gt;Sprintf&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:#e6db74&#34;&gt;&amp;#34;rate limit of %d per %v has been exceeded and resets at %v&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;e&lt;/span&gt;.&lt;span style=&#34;color:#a6e22e&#34;&gt;Limit&lt;/span&gt;, &lt;span style=&#34;color:#a6e22e&#34;&gt;e&lt;/span&gt;.&lt;span style=&#34;color:#a6e22e&#34;&gt;Period&lt;/span&gt;, &lt;span style=&#34;color:#a6e22e&#34;&gt;e&lt;/span&gt;.&lt;span style=&#34;color:#a6e22e&#34;&gt;Reset&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;h3 id=&#34;創建和啟動-redis-容器&#34;&gt;創建和啟動 Redis 容器&lt;/h3&gt;
&lt;p&gt;正式的產品通常會使用 config 來管理 Redis 位置，這個 demo 中直接使用 &lt;code&gt;localhost:6379&lt;/code&gt; 來展示。&lt;/p&gt;</description>
    </item>
    <item>
      <title>透過 Distrobox 解決 Linux 環境依賴問題</title>
      <link>https://www.omegaatt.com/blogs/develop/2024/two_distrobox_use_case/</link>
      <pubDate>Sat, 18 May 2024 00:00:00 +0000</pubDate><author>raiven.kao@gmail.com (Raiven Kao)</author>
      <guid>https://www.omegaatt.com/blogs/develop/2024/two_distrobox_use_case/</guid>
      <description>&lt;h2 id=&#34;前言&#34;&gt;前言&lt;/h2&gt;
&lt;p&gt;自從一年前嘗試使用 &lt;a href=&#34;https://www.omegaatt.com/blogs/develop/2023/kde_neon_experience&#34;&gt;KDE Neon&lt;/a&gt; 後，主要的桌面開發環境就從 wsl 整個遷移到 Linux 上。這一年來感受到，KDE Neon 可能正如 reddit 上的留言闡述的那樣，只是 KDE 團隊的「實驗場」，可以享受到「最」新的 Plasma 與「較」新的 Kernel，卻始終是基於 Ubuntu 22.04 LTS，即便 Ubuntu 24.04 LTS 推出之際，也不再讓我感受性感。&lt;/p&gt;
&lt;p&gt;因緣際會，在 Ubuntu 24.04 LTS 發布之際，我決定從 KDE Neon distro hopping 到 OpenSUSE Tumbleweed，這是一個由 SUSE 公司推出的滾動式發行版，軟體包在 SUSE 測試過後便會推送到 OpenSUSE 的 Repo（其他 OpenSUSE 的功能省略）。這次的轉換主要是為了獲得最前沿的 Linux 體驗。OpenSUSE Tumbleweed 的滾動更新模式能確保我始終使用最新的軟體和技術，這對我來說是一個有趣的選擇，即便有可能收到有問題的 xz 更新 XD。&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;-webkit-text-size-adjust:none;&#34;&gt;&lt;code class=&#34;language-shell&#34; data-lang=&#34;shell&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;❯ fastfetch
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;                                     ......             raiven@raiven-suse
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;     .,cdxxxoc,.               .:kKMMMNWMMMNk:.         ------------------
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    cKMMN0OOOKWMMXo. A        ;0MWk:&lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#39;      &amp;#39;&lt;/span&gt;:OMMk.      OS: openSUSE Tumbleweed &lt;span style=&#34;color:#ae81ff&#34;&gt;20240517&lt;/span&gt; x86_64
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  ;WMK;&lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#39;       &amp;#39;&lt;/span&gt;lKMMNM,     :NMK&lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#39;             &amp;#39;&lt;/span&gt;OMW;     Host: FMV UH-X
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt; cMW;             WMMMN   ,XMK&lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#39;                 oMM.    Kernel: 6.8.9-1-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:#e6db74&#34;&gt;.MMc             &amp;#39;&amp;#39;^*~l. xMN:                    KM0    Uptime: 1 hour, 14 mins
&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:#e6db74&#34;&gt;&amp;#39;&lt;/span&gt;MM.                   .NMO                      oMM    Packages: &lt;span style=&#34;color:#ae81ff&#34;&gt;3211&lt;/span&gt; &lt;span style=&#34;color:#f92672&#34;&gt;(&lt;/span&gt;rpm&lt;span style=&#34;color:#f92672&#34;&gt;)&lt;/span&gt;, &lt;span style=&#34;color:#ae81ff&#34;&gt;37&lt;/span&gt; &lt;span style=&#34;color:#f92672&#34;&gt;(&lt;/span&gt;flatpak&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;.MM,                 .kMMl                       xMN    Shell: zsh 5.9
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt; KM0               .kMM0&lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#39; .dl&amp;gt;~,.               .WMd    DE: KDE Plasma 6.0.4
&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:#e6db74&#34;&gt; &amp;#39;&lt;/span&gt;XM0.           ,OMMK&lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#39;    OMMM7&amp;#39;&lt;/span&gt;              .XMK     WM: KWin &lt;span style=&#34;color:#f92672&#34;&gt;(&lt;/span&gt;Wayland&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;   *WMO:.    .;xNMMk&lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#39;       NNNMKl.          .xWMx      WM Theme: Nordic
&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:#e6db74&#34;&gt;     ^ONMMNXMMMKx;          V  &amp;#39;&lt;/span&gt;xNMWKkxllox0NMWk&lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#39;       Theme: Breeze (NordicDarker) [QT], Breeze-Dark [GTK2], Breeze [GTK3]
&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:#e6db74&#34;&gt;         &amp;#39;&amp;#39;&amp;#39;&amp;#39;&amp;#39;&lt;/span&gt;                    &lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#39;:dOOXXKOxl&amp;#39;&lt;/span&gt;          Icons: Zafiro-Nord-Dark-Black &lt;span style=&#34;color:#f92672&#34;&gt;[&lt;/span&gt;QT&lt;span style=&#34;color:#f92672&#34;&gt;]&lt;/span&gt;, Zafiro-Nord-Dark-Black &lt;span style=&#34;color:#f92672&#34;&gt;[&lt;/span&gt;GTK2/3/4&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;                                                        Font: Noto Sans &lt;span style=&#34;color:#f92672&#34;&gt;(&lt;/span&gt;10pt&lt;span style=&#34;color:#f92672&#34;&gt;)&lt;/span&gt; &lt;span style=&#34;color:#f92672&#34;&gt;[&lt;/span&gt;QT&lt;span style=&#34;color:#f92672&#34;&gt;]&lt;/span&gt;, Noto Sans &lt;span style=&#34;color:#f92672&#34;&gt;(&lt;/span&gt;10pt&lt;span style=&#34;color:#f92672&#34;&gt;)&lt;/span&gt; &lt;span style=&#34;color:#f92672&#34;&gt;[&lt;/span&gt;GTK2/3/4&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;                                                        Cursor: Nordic &lt;span style=&#34;color:#f92672&#34;&gt;(&lt;/span&gt;24px&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;                                                        Terminal: tmux 3.4
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;                                                        CPU: 13th Gen Intel&lt;span style=&#34;color:#f92672&#34;&gt;(&lt;/span&gt;R&lt;span style=&#34;color:#f92672&#34;&gt;)&lt;/span&gt; Core&lt;span style=&#34;color:#f92672&#34;&gt;(&lt;/span&gt;TM&lt;span style=&#34;color:#f92672&#34;&gt;)&lt;/span&gt; i5-1335U &lt;span style=&#34;color:#f92672&#34;&gt;(&lt;/span&gt;12&lt;span style=&#34;color:#f92672&#34;&gt;)&lt;/span&gt; @ 4.60 GHz
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;                                                        GPU: Intel Iris Xe Graphics @ 1.25 GHz &lt;span style=&#34;color:#f92672&#34;&gt;[&lt;/span&gt;Integrated&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;                                                        Memory: 9.86 GiB / 15.27 GiB &lt;span style=&#34;color:#f92672&#34;&gt;(&lt;/span&gt;65%&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;                                                        Swap: 677.00 MiB / 2.00 GiB &lt;span style=&#34;color:#f92672&#34;&gt;(&lt;/span&gt;33%&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;                                                        Disk &lt;span style=&#34;color:#f92672&#34;&gt;(&lt;/span&gt;/&lt;span style=&#34;color:#f92672&#34;&gt;)&lt;/span&gt;: 88.81 GiB / 1.82 TiB &lt;span style=&#34;color:#f92672&#34;&gt;(&lt;/span&gt;5%&lt;span style=&#34;color:#f92672&#34;&gt;)&lt;/span&gt; - btrfs
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;即便是已經足夠穩定的 OpenSUSE，也是可以很輕易的被搞的不穩定，正如 debian 在 &lt;a href=&#34;https://wiki.debian.org/DontBreakDebian&#34;&gt;DontBreakDebian&lt;/a&gt; 一文中提到的：&lt;/p&gt;</description>
    </item>
    <item>
      <title>如何不啟動 container 從 image 中提取可執行檔</title>
      <link>https://www.omegaatt.com/blogs/develop/2024/extract_executable_from_docker_image/</link>
      <pubDate>Sun, 14 Apr 2024 00:00:00 +0000</pubDate><author>raiven.kao@gmail.com (Raiven Kao)</author>
      <guid>https://www.omegaatt.com/blogs/develop/2024/extract_executable_from_docker_image/</guid>
      <description>&lt;p&gt;::: info
雖然文章內大多指令都是使用 docker，但由於是標準的 OCI image，使用 Podman 也是一樣的效果。
:::&lt;/p&gt;
&lt;p&gt;某段時間內，我會將所有需要執行的 binary 使用 containerize 給打包起來執行，例如：&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;
&lt;p&gt;需要將前端環境給跑起來，我會啟動一個 node 環境的 container:&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;-webkit-text-size-adjust:none;&#34;&gt;&lt;code class=&#34;language-shell&#34; data-lang=&#34;shell&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;docker run --rm -it --name f2e --net&lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt;host -v &lt;span style=&#34;color:#66d9ef&#34;&gt;$(&lt;/span&gt;pwd&lt;span style=&#34;color:#66d9ef&#34;&gt;)&lt;/span&gt;:/app &lt;span style=&#34;color:#ae81ff&#34;&gt;\
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    docker.io/library/node:20-bookworm &lt;span style=&#34;color:#ae81ff&#34;&gt;\
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    bash
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;需要安裝某個基於 golang 的 cli 工具，會使用&lt;a href=&#34;https://gist.github.com/omegaatt36/487d643aca6443f9524eed2975cbd746&#34;&gt;自己寫的腳本&lt;/a&gt;，在 container 內進行建構。&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;需要透過 Liquibase 來進行 db migration，並不會選擇在本地安裝 maven 環境，一樣是啟動一個 container 來執行。&lt;/p&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;這麼做的好處是，「目前」大多數的 server/cli tools 至少會編譯 x86 架構下的可執行檔，我只需要確保 container 環境內可以工作，就可以在不同硬體的開發環境下游走。極度偏激的來說，無法確保安裝的 binary 有沒有受過污染，無論開源或閉源與否，也可以透過 Podman 來啟動 rootless 的 container 確保本機的安全。&lt;/p&gt;
&lt;p&gt;但有時候會遇到別人打包的 image 十分肥大，即便程式可執行檔只需要幾 MB，編譯後的 image 確有幾百 MB（大多數是腳本語言），蒙生了從 image 中提取 executable 的想法，藉此也學習 docker layer 間的關係。&lt;/p&gt;</description>
    </item>
    <item>
      <title>如何利用 Open Policy Agent 配合 Golang 建構彈性的 RBAC 模組</title>
      <link>https://www.omegaatt.com/blogs/develop/2024/golang_rbac_with_open_policy_agent/</link>
      <pubDate>Thu, 04 Apr 2024 00:00:00 +0000</pubDate><author>raiven.kao@gmail.com (Raiven Kao)</author>
      <guid>https://www.omegaatt.com/blogs/develop/2024/golang_rbac_with_open_policy_agent/</guid>
      <description>&lt;h2 id=&#34;rbac-概念簡介&#34;&gt;RBAC 概念簡介&lt;/h2&gt;
&lt;p&gt;在我們探討如何利用 Open Policy Agent (以下簡稱 OPA) 和 Golang 建立一個彈性的 RBAC 模組之前，先讓我們來了解一下 RBAC的基本概念。&lt;/p&gt;
&lt;p&gt;RBAC（Role-Based Access Control，基於角色的訪問控制）是一種廣泛應用的訪問控制策略，在軟體安全性領域尤為重要。其核心思想是將系統訪問權限與用戶的角色（職位、責任或職務）關聯起來，而不是直接與個別用戶關聯。這意味著訪問權限被捆綁到角色上，然後將用戶分配給這些角色。舉個例子，一個「管理員」角色可能有權訪問系統的所有資源，而「員工」角色則只能訪問特定部分的資源。&lt;/p&gt;
&lt;p&gt;RBAC 的主要優勢在於其靈活性和簡化的權限管理。當需要變更權限時，只需修改角色的訪問權限，而不需要為每個用戶單獨設定。這不僅使權限管理更為高效，也減少了錯誤配置的可能性，提高了整體的系統安全性。&lt;/p&gt;
&lt;p&gt;在實踐中，RBAC 允許創建精細且靈活的策略，以滿足複雜的商業和安全需求。無論是大型企業還是小型團隊，RBAC 都提供了一個可靠的框架，來確保正確的用戶擁有適當的訪問權限，從而保護關鍵資源免受未授權訪問。&lt;/p&gt;
&lt;p&gt;可以參考 &lt;a href=&#34;https://www.cloudflare.com/zh-tw/learning/access-management/role-based-access-control-rbac/&#34;&gt;Cloudflare 的文章&lt;/a&gt;，簡單來說就是&lt;strong&gt;什麼「角色」能夠對什麼「資源」做什麼「操作」&lt;/strong&gt;。&lt;/p&gt;
&lt;h2 id=&#34;open-policy-agent-opa-介紹&#34;&gt;Open Policy Agent (OPA) 介紹&lt;/h2&gt;
&lt;p&gt;&lt;a href=&#34;https://www.openpolicyagent.org/&#34;&gt;OPA&lt;/a&gt; 是一個「Strategy as Code」的開源專案，專門設計用於統一地管理和執行跨不同系統的策略。它不僅提供了一個高級的策略語言——Rego，還支援將策略作為代碼與應用程式的其他部分一同存儲、版本控制和部署。OPA 的這種設計使其能夠輕鬆集成到微服務、Kubernetes、CI/CD 管道、API 網關等多種環境中。顯著特點是其策略的編寫方式。Rego 是一種專門為策略和規則定制的查詢語言，它使開發者能夠以聲明式方式描述策略和規則，從而確保這些策略既容易理解又易於維護。這對於建立複雜的 RBAC 系統尤為重要，因為它允許策略的靈活性和可擴展性，同時又保持了清晰和易於審查的結構。&lt;/p&gt;
&lt;p&gt;我們可以利用 OPA 提供的 API 來評估和執行這些策略。這意味著開發者可以在 Golang 程式碼中直接嵌入策略判斷的邏輯，從而實現動態、細粒度的訪問控制。這種方法的一個優點是，它支援在 runtime 動態更新策略，或是編譯進 binary，從而提供更大的靈活性和即時性。&lt;/p&gt;
&lt;h2 id=&#34;整合-opa-與-golang&#34;&gt;整合 OPA 與 Golang&lt;/h2&gt;
&lt;h3 id=&#34;透過官方案例來了解如何使用&#34;&gt;透過官方案例來了解如何使用&lt;/h3&gt;
&lt;p&gt;參考了 &lt;a href=&#34;https://www.openpolicyagent.org/docs/latest/comparison-to-other-systems/&#34;&gt;OPA 官方的 rbac 章節&lt;/a&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;-webkit-text-size-adjust:none;&#34;&gt;&lt;code class=&#34;language-plain&#34; data-lang=&#34;plain&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;admin can read user
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;bob is admin
&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;bob can read user
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;轉化成 RBAC 模型即為：&lt;/p&gt;</description>
    </item>
    <item>
      <title>Agile Testing 閱讀筆記</title>
      <link>https://www.omegaatt.com/blogs/develop/2024/agile_testing/</link>
      <pubDate>Sun, 24 Mar 2024 00:00:00 +0000</pubDate><author>raiven.kao@gmail.com (Raiven Kao)</author>
      <guid>https://www.omegaatt.com/blogs/develop/2024/agile_testing/</guid>
      <description>&lt;iframe src=&#34;https://gamma.app/embed/8r0jqfc8k78lads&#34; style=&#34;width: 100%; max-width: 100%; height: 450px&#34; &gt;&lt;/iframe&gt;
&lt;h2 id=&#34;鋪墊敏捷開發價值觀原則與實踐&#34;&gt;鋪墊：敏捷開發價值觀、原則與實踐&lt;/h2&gt;
&lt;p&gt;有什麼開發，就有什麼測試，傳統開發就有傳統測試，敏捷開發就應該要推行敏捷測試。在討論敏捷測試前，應該先理解敏捷開發模式，否則理解敏捷測試會很困難。&lt;/p&gt;
&lt;p&gt;敏捷開發是一種思想或稱作方法論，通過不斷迭代與增量發布，最終交付符合用戶價值的產品。&lt;/p&gt;
&lt;p&gt;書中提到一些敏捷開發的歷史、演進與框架&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;PDCA 循環&lt;/li&gt;
&lt;li&gt;輕量級軟體開發
減少複雜的文件，強調人員的互動&lt;/li&gt;
&lt;li&gt;敏捷宣言&lt;/li&gt;
&lt;li&gt;XP（eXtreme Programing）：較多是著重在軟體開發，例如 TDD、pair programing、CI 等等。&lt;/li&gt;
&lt;li&gt;BDD（行為驅動開發）：使用「通用語言」來描述測試案例，將 User Story 的細節進行完整地描述。
&lt;strong&gt;Feature（特性）&lt;/strong&gt;: 購物車功能
&lt;strong&gt;Scenario（情境）&lt;/strong&gt;: 添加商品到購物車
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Given&lt;/strong&gt;（假設）: 用戶已經登錄到購物平台並且正在瀏覽商品&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;When&lt;/strong&gt;（當）: 用戶點擊某個商品的「添加到購物車」按鈕&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Then&lt;/strong&gt;（那麼）: 該商品應該被添加到用戶的購物車中&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;And&lt;/strong&gt;（並且）: 購物車中的商品總數應該更新&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;FDD（特性驅動開發）：使用制式結構來建構特性列表&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;code&gt;&amp;lt;action&amp;gt; the &amp;lt;result&amp;gt; &amp;lt;by|for|of|to&amp;gt; a(n) &amp;lt;object&amp;gt;&lt;/code&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Scrum：確保每天、每個階段都向著目標明確進行的一種「方法」。
推薦看 Scrum 提倡者自己寫的 &lt;code&gt;SCRUM：用一半的時間做兩倍的事&lt;/code&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 id=&#34;devops-與敏捷的關係&#34;&gt;DevOps 與敏捷的關係&lt;/h3&gt;
&lt;p&gt;DevOps 可以看作是敏捷的延伸，打通軟體開發、測試、交付、維護中的層層牆壁。&lt;/p&gt;
&lt;h3 id=&#34;敏捷宣言&#34;&gt;&lt;a href=&#34;https://agilemanifesto.org/iso/zhcht/manifesto.html&#34;&gt;敏捷宣言&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;藉著親自並協助他人進行軟體開發，我們正致力於發掘更優良的軟體開發方法。透過這樣的努力，我們已建立以下價值觀：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;個人與互動 重於 流程與工具&lt;/li&gt;
&lt;li&gt;可用的軟體 重於 詳盡的文件&lt;/li&gt;
&lt;li&gt;與客戶合作 重於 合約協商&lt;/li&gt;
&lt;li&gt;回應變化 重於 遵循計劃&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;也就是說，雖然右側項目有其價值，但我們更重視左側項目。&lt;/p&gt;
&lt;hr&gt;
&lt;h2 id=&#34;敏捷測試之道&#34;&gt;敏捷測試之道&lt;/h2&gt;
&lt;p&gt;敏決測試不是一種測試方法，而是為了適應敏捷開發而設計的一套軟體測試解決方案。&lt;/p&gt;
&lt;h3 id=&#34;敏捷測試宣言&#34;&gt;敏捷測試宣言&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;Full Lifecycle Testing &lt;strong&gt;OVER&lt;/strong&gt; Isolated Testing Phase&lt;/li&gt;
&lt;li&gt;Team Shared Responsibility &lt;strong&gt;OVER&lt;/strong&gt; Testers Ensure Quality&lt;/li&gt;
&lt;li&gt;Continuous Targeted Automation &lt;strong&gt;OVER&lt;/strong&gt; Widespread Regression Testing&lt;/li&gt;
&lt;li&gt;Quality Built-in &lt;strong&gt;OVER&lt;/strong&gt; Defect Detection&lt;/li&gt;
&lt;/ul&gt;
&lt;h4 id=&#34;full-lifecycle-testing&#34;&gt;Full Lifecycle Testing&lt;/h4&gt;
&lt;p&gt;強調測試左移與右移，並非將測試「移動」到兩個端點，而是全程測試的介入。&lt;/p&gt;</description>
    </item>
    <item>
      <title>那些在 Backend Sharing 中出現的人事物</title>
      <link>https://www.omegaatt.com/blogs/develop/2024/backend_sharing_note/</link>
      <pubDate>Sun, 24 Mar 2024 00:00:00 +0000</pubDate><author>raiven.kao@gmail.com (Raiven Kao)</author>
      <guid>https://www.omegaatt.com/blogs/develop/2024/backend_sharing_note/</guid>
      <description>&lt;p&gt;《最高學以致用法》、《最高學習法》這兩本書是我在 2023 年上半年讀了覺得挺有意思的書，核心概念就是「產出」，例如唸書時能夠回答同學問題的，肯定都已經精通該知識點了。&lt;/p&gt;
&lt;p&gt;加入 KryptoGO 後，因為團隊的成長，Leader 開始嘗試舉行兩週一次的 Backend Sharing，不僅是分享工作上遇到的疑難雜症，更可以交流不同的知識點。&lt;/p&gt;
&lt;p&gt;起初，我可以我分享了一些過去用過的工具與知識點，隨著時間的流逝，開始感受到黔驢技窮，於是我也仿效了《刻意練習》，不斷的為了能有更好的分享品質而學習。&lt;/p&gt;
&lt;p&gt;以下是這半年來我在 Backend Sharing 中或多或少提及或是討論到的，又分為解決方案、小工具、方法論。&lt;/p&gt;
&lt;h3 id=&#34;小工具&#34;&gt;小工具&lt;/h3&gt;
&lt;p&gt;由於喜歡折騰 Homelab，時不時會到 &lt;a href=&#34;https://www.reddit.com/r/selfhosted/&#34;&gt;r/selfhosted&lt;/a&gt;、&lt;a href=&#34;https://www.reddit.com/r/opensource/&#34;&gt;r/opensource/&lt;/a&gt; 尋找一些開源的自架方案或小工具，有一些大幅度的改善了我的開發流程，而有一些則漸漸的不再使用&lt;/p&gt;
&lt;h4 id=&#34;exa--eza--bat&#34;&gt;exa &amp;amp; eza &amp;amp; bat&lt;/h4&gt;
&lt;p&gt;&lt;a href=&#34;https://github.com/eza-community/eza&#34;&gt;eza&lt;/a&gt;、&lt;a href=&#34;https://github.com/ogham/exa&#34;&gt;exa&lt;/a&gt;、&lt;a href=&#34;https://github.com/sharkdp/bat&#34;&gt;bat&lt;/a&gt; 都是基於 rust 寫成的 cli 替代品，&lt;code&gt;exa&lt;/code&gt;、&lt;code&gt;eza&lt;/code&gt; 對標 &lt;code&gt;cd&lt;/code&gt;，而 &lt;code&gt;bat&lt;/code&gt; 對標 &lt;code&gt;cat&lt;/code&gt;，我會在 &lt;code&gt;.zshrc&lt;/code&gt; 中寫上 alias。&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;-webkit-text-size-adjust:none;&#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;&lt;span style=&#34;color:#66d9ef&#34;&gt;if&lt;/span&gt; command -v bat &amp;amp;&amp;gt; /dev/null; &lt;span style=&#34;color:#66d9ef&#34;&gt;then&lt;/span&gt; alias cat&lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt;bat; &lt;span style=&#34;color:#66d9ef&#34;&gt;fi&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; command -v eza &amp;amp;&amp;gt; /dev/null;
&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;then&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  alias ls&lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt;&lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;eza --icons&amp;#34;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  alias ll&lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt;&lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;eza --icons -lh&amp;#34;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  alias tree&lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt;&lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;eza --icons --tree&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;fi&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;還有其他性質相同的 rust 寫的小工具諸如： &lt;a href=&#34;https://github.com/ajeetdsouza/zoxide&#34;&gt;zoxide&lt;/a&gt;、&lt;a href=&#34;https://github.com/topgrade-rs/topgrade&#34;&gt;topgrade&lt;/a&gt;、&lt;a href=&#34;https://github.com/alacritty/alacritty&#34;&gt;alacritty&lt;/a&gt;、&lt;a href=&#34;https://github.com/ClementTsang/bottom&#34;&gt;bottom&lt;/a&gt;&lt;/p&gt;</description>
    </item>
    <item>
      <title>透過 CLI 調整藍牙耳機音訊設定</title>
      <link>https://www.omegaatt.com/blogs/develop/2024/change_bluetooth_audio_profile_by_using_pactl/</link>
      <pubDate>Sun, 28 Jan 2024 00:00:00 +0000</pubDate><author>raiven.kao@gmail.com (Raiven Kao)</author>
      <guid>https://www.omegaatt.com/blogs/develop/2024/change_bluetooth_audio_profile_by_using_pactl/</guid>
      <description>&lt;p&gt;接續著 &lt;a href=&#34;https://www.omegaatt.com/blogs/develop/2023/kde_neon_pipewire_and_more_bluetooth_kde_neon_pipe_wire_and_more_bluetooth_codec&#34;&gt;KDE neon / Ubuntu 啟用 PipeWire 與 LDAC/AAC/AptX 藍芽編碼&lt;/a&gt;，每當連線到藍芽耳機時，語音設定檔在使用 LDAC 後，就無法同時使用麥克風，這樣一來在開會時，就需要手動到設定裡面去調整語音設定檔。&lt;/p&gt;
&lt;p&gt;身為一個懶惰鬼，可以用鍵盤解決的就不會用滑鼠去點，發現可以通過 pactl（PulseAudio）在 cli 直接設定 audio profile。&lt;/p&gt;
&lt;p&gt;首先，我們通過 &lt;code&gt;bluetoothctl&lt;/code&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;-webkit-text-size-adjust:none;&#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;$ bluetoothctl
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&amp;gt; &lt;span style=&#34;color:#f92672&#34;&gt;[&lt;/span&gt;EAH-A800&lt;span style=&#34;color:#f92672&#34;&gt;]&lt;/span&gt;&lt;span style=&#34;color:#75715e&#34;&gt;# devices&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&amp;gt; Device 88:C9:E8:B1:5D:AE WF-1000XM4
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&amp;gt; Device DC:22:D2:85:85:15 MX Master 3S
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&amp;gt; Device B8:20:8E:35:CB:D0 EAH-A800
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;文章內舉例的目標為 EAH-A800，也就是 &lt;code&gt;B8:20:8E:35:CB:D0&lt;/code&gt;
接著需要確認目前藍牙裝置支持的音訊設定檔。可以透過 &lt;code&gt;pactl list cards short&lt;/code&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;-webkit-text-size-adjust:none;&#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;$ pactl list cards short
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#ae81ff&#34;&gt;47&lt;/span&gt;      alsa_card.usb-Shure_Inc_Shure_MV7-00    alsa
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#ae81ff&#34;&gt;48&lt;/span&gt;      alsa_card.pci-0000_00_1f.3-platform-skl_hda_dsp_generic alsa
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#ae81ff&#34;&gt;49&lt;/span&gt;      alsa_card.usb-NuPrime_NuPrime_DAC-9H-00 alsa
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#ae81ff&#34;&gt;1002&lt;/span&gt;    bluez_card.B8_20_8E_35_CB_D0    module-bluez5-device.c
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;發現目標為 &lt;code&gt;1002    bluez_card.B8_20_8E_35_CB_D0&lt;/code&gt;
再來通過 &lt;code&gt;pactl list cards&lt;/code&gt; 來獲取所有音訊設定檔&lt;/p&gt;</description>
    </item>
    <item>
      <title>Golang 隱式記憶體別名 Implicit Memory Aliasing 與其檢測方法</title>
      <link>https://www.omegaatt.com/blogs/develop/2023/golang_implicit_memory_aliasing/</link>
      <pubDate>Sun, 12 Nov 2023 00:00:00 +0000</pubDate><author>raiven.kao@gmail.com (Raiven Kao)</author>
      <guid>https://www.omegaatt.com/blogs/develop/2023/golang_implicit_memory_aliasing/</guid>
      <description>&lt;p&gt;在使用 Golang 時，我們可能會遇到一種稱為隱式記憶體別名（Implicit Memory Aliasing）的問題。這篇文章將探討這個問題以及如何使用工具和語言特性來解決它。&lt;/p&gt;
&lt;h2 id=&#34;隱式記憶體別名的問題&#34;&gt;隱式記憶體別名的問題&lt;/h2&gt;
&lt;p&gt;隱式記憶體別名主要發生在 &lt;code&gt;range&lt;/code&gt; 語句中。當使用 &lt;code&gt;range&lt;/code&gt; 對一個切片 slice 或映射 map 進行迭代時，Iterator 在每次迭代中並不是唯一的實例。這可能導致意外的行為，特別是在並發環境或當迭代變量被指針引用時。&lt;/p&gt;
&lt;h3 id=&#34;slice-with-implicit-memory-aliasing&#34;&gt;&lt;a href=&#34;https://go.dev/play/p/E_0aBWZcsmZ&#34;&gt;slice with Implicit Memory Aliasing&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;下面示例可能是基本的 golang 面試題，會問你迭代後的 pointers 內的 Name 為何
答案是 Joe Joe Joe&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;-webkit-text-size-adjust:none;&#34;&gt;&lt;code class=&#34;language-go&#34; data-lang=&#34;go&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#f92672&#34;&gt;package&lt;/span&gt; &lt;span style=&#34;color:#a6e22e&#34;&gt;main&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:#f92672&#34;&gt;import&lt;/span&gt; &lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;fmt&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 style=&#34;color:#66d9ef&#34;&gt;type&lt;/span&gt; &lt;span style=&#34;color:#a6e22e&#34;&gt;Person&lt;/span&gt; &lt;span style=&#34;color:#66d9ef&#34;&gt;struct&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;Name&lt;/span&gt;   &lt;span style=&#34;color:#66d9ef&#34;&gt;string&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;Gender&lt;/span&gt; &lt;span style=&#34;color:#66d9ef&#34;&gt;string&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;func&lt;/span&gt; &lt;span style=&#34;color:#a6e22e&#34;&gt;main&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;persons&lt;/span&gt; &lt;span style=&#34;color:#f92672&#34;&gt;:=&lt;/span&gt; []&lt;span style=&#34;color:#a6e22e&#34;&gt;Person&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;Name&lt;/span&gt;: &lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;John&amp;#34;&lt;/span&gt;, &lt;span style=&#34;color:#a6e22e&#34;&gt;Gender&lt;/span&gt;: &lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;M&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;Name&lt;/span&gt;: &lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;Jane&amp;#34;&lt;/span&gt;, &lt;span style=&#34;color:#a6e22e&#34;&gt;Gender&lt;/span&gt;: &lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;F&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;Name&lt;/span&gt;: &lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;Joe&amp;#34;&lt;/span&gt;, &lt;span style=&#34;color:#a6e22e&#34;&gt;Gender&lt;/span&gt;: &lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;X&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:#a6e22e&#34;&gt;pointers&lt;/span&gt; &lt;span style=&#34;color:#f92672&#34;&gt;:=&lt;/span&gt; make([]&lt;span style=&#34;color:#f92672&#34;&gt;*&lt;/span&gt;&lt;span style=&#34;color:#66d9ef&#34;&gt;string&lt;/span&gt;, len(&lt;span style=&#34;color:#a6e22e&#34;&gt;persons&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;for&lt;/span&gt; &lt;span style=&#34;color:#a6e22e&#34;&gt;index&lt;/span&gt;, &lt;span style=&#34;color:#a6e22e&#34;&gt;person&lt;/span&gt; &lt;span style=&#34;color:#f92672&#34;&gt;:=&lt;/span&gt; &lt;span style=&#34;color:#66d9ef&#34;&gt;range&lt;/span&gt; &lt;span style=&#34;color:#a6e22e&#34;&gt;persons&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;fmt&lt;/span&gt;.&lt;span style=&#34;color:#a6e22e&#34;&gt;Printf&lt;/span&gt;(&lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;%p, %p, %p, %v\n&amp;#34;&lt;/span&gt;, &lt;span style=&#34;color:#f92672&#34;&gt;&amp;amp;&lt;/span&gt;&lt;span style=&#34;color:#a6e22e&#34;&gt;person&lt;/span&gt;, &lt;span style=&#34;color:#f92672&#34;&gt;&amp;amp;&lt;/span&gt;&lt;span style=&#34;color:#a6e22e&#34;&gt;person&lt;/span&gt;.&lt;span style=&#34;color:#a6e22e&#34;&gt;Name&lt;/span&gt;, &lt;span style=&#34;color:#f92672&#34;&gt;&amp;amp;&lt;/span&gt;&lt;span style=&#34;color:#a6e22e&#34;&gt;person&lt;/span&gt;.&lt;span style=&#34;color:#a6e22e&#34;&gt;Gender&lt;/span&gt;, &lt;span style=&#34;color:#f92672&#34;&gt;&amp;amp;&lt;/span&gt;&lt;span style=&#34;color:#a6e22e&#34;&gt;persons&lt;/span&gt;[&lt;span style=&#34;color:#a6e22e&#34;&gt;index&lt;/span&gt;].&lt;span style=&#34;color:#a6e22e&#34;&gt;Name&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;pointers&lt;/span&gt;[&lt;span style=&#34;color:#a6e22e&#34;&gt;index&lt;/span&gt;] = &lt;span style=&#34;color:#f92672&#34;&gt;&amp;amp;&lt;/span&gt;&lt;span style=&#34;color:#a6e22e&#34;&gt;person&lt;/span&gt;.&lt;span style=&#34;color:#a6e22e&#34;&gt;Name&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;for&lt;/span&gt; &lt;span style=&#34;color:#a6e22e&#34;&gt;index&lt;/span&gt; &lt;span style=&#34;color:#f92672&#34;&gt;:=&lt;/span&gt; &lt;span style=&#34;color:#66d9ef&#34;&gt;range&lt;/span&gt; &lt;span style=&#34;color:#a6e22e&#34;&gt;pointers&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;fmt&lt;/span&gt;.&lt;span style=&#34;color:#a6e22e&#34;&gt;Println&lt;/span&gt;(&lt;span style=&#34;color:#a6e22e&#34;&gt;pointers&lt;/span&gt;[&lt;span style=&#34;color:#a6e22e&#34;&gt;index&lt;/span&gt;], &lt;span style=&#34;color:#f92672&#34;&gt;*&lt;/span&gt;&lt;span style=&#34;color:#a6e22e&#34;&gt;pointers&lt;/span&gt;[&lt;span style=&#34;color:#a6e22e&#34;&gt;index&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;h3 id=&#34;channel-with-implicit-memory-aliasing&#34;&gt;&lt;a href=&#34;https://go.dev/play/p/N21dlpTr_9G&#34;&gt;channel with Implicit Memory Aliasing&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;同樣的情況也會發生在 channel 的迭代。&lt;/p&gt;</description>
    </item>
    <item>
      <title>透過 frp 與 GCP 打通家用 kubernetes cluster 的對外連線</title>
      <link>https://www.omegaatt.com/blogs/develop/2023/kubernetes_with_frp_tunnel_and_cert_manager/</link>
      <pubDate>Sun, 12 Nov 2023 00:00:00 +0000</pubDate><author>raiven.kao@gmail.com (Raiven Kao)</author>
      <guid>https://www.omegaatt.com/blogs/develop/2023/kubernetes_with_frp_tunnel_and_cert_manager/</guid>
      <description>&lt;h2 id=&#34;概述&#34;&gt;概述&lt;/h2&gt;
&lt;p&gt;在家裡面架著一台 lab，使用 proxmox ve 作為 vm server
透過 qemu 虛擬化一台 ubuntu server vm
在這個 vm 上使用 k3s，啟動 kubernetes 的服務
在 kubernetes 內跑著眾多 dockerize 的 containers。&lt;/p&gt;
&lt;p&gt;雖然我不是資深維運專家，但我知道這可能很搞笑。&lt;/p&gt;
&lt;p&gt;&lt;a href=&#34;https://www.linkedin.com/pulse/running-vm-container-ramesh-kumar&#34;&gt;&lt;img loading=&#34;lazy&#34; src=&#34;https://www.omegaatt.com/blogs/develop/2023/kubernetes_with_frp_tunnel_and_cert_manager/images/1596892531951.webp&#34;&gt;&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;會需要這麼麻煩還不是為了省一點點 GKE 的成本（即便可能沒省到），但在自家建一個 lab 環境而不用計算由時間計價的 infra 成本，還是挺省腦袋的。&lt;/p&gt;
&lt;p&gt;過去僅是將 service 使用 nodePort 來讓家裡面的其他裝置可以連線，或是使用 &lt;a href=&#34;https://www.omegaatt.com/blogs/develop/2023/wireguard_easy&#34;&gt;wireguard 作為 VPN&lt;/a&gt; 來從遠端連線，就是沒有動力處理好 http service 與 TLS，趁這個假日剛好需要把 &lt;a href=&#34;https://www.omegaatt.com/blogs/develop/2023/bitwarden_with_self_hosted_password_backend&#34;&gt;自架 vaultwarden 後端&lt;/a&gt; 給 expose 出來，便誕生了這篇。&lt;/p&gt;
&lt;p&gt;在當前的雲端運算時代，許多開發者和 IT 專業人員都面臨著一個共同的挑戰：如何有效地將位於不同網絡環境中的服務整合並暴露給公眾。特別是對於那些希望將家用網絡中的服務有效連接到公共雲端（如 GCP,AWS,Azure,OCI）的用戶來說，這一挑戰尤為突出。文內主要講述通過在 GCP VM 上部署 FRP（Fast Reverse Proxy）服務器，結合家中的 kubernetes cluster 上配置的 FRP 客戶端，來實現一個高效且安全的服務部署流程。&lt;/p&gt;
&lt;p&gt;透過此方式，我們不僅可以利用 GCP VM 作為公共 IP 的代理，實現從互聯網到家庭網絡的無縫連接，還能藉助 kubernetes 的強大功能進行快速的服務部署和管理。同時，利用 ingress-nginx 作為 ingress 控制器和 cert-manager 進行 TLS 設定，我們能夠確保服務的安全性和可靠性。&lt;/p&gt;</description>
    </item>
    <item>
      <title>KDE neon / Ubuntu 啟用 PipeWire 與 LDAC/AAC/AptX 藍芽編碼</title>
      <link>https://www.omegaatt.com/blogs/develop/2023/kde_neon_pipewire_and_more_bluetooth_kde_neon_pipe_wire_and_more_bluetooth_codec/</link>
      <pubDate>Sun, 01 Oct 2023 00:00:00 +0000</pubDate><author>raiven.kao@gmail.com (Raiven Kao)</author>
      <guid>https://www.omegaatt.com/blogs/develop/2023/kde_neon_pipewire_and_more_bluetooth_kde_neon_pipe_wire_and_more_bluetooth_codec/</guid>
      <description>&lt;p&gt;&lt;a href=&#34;https://www.ghacks.net/2022/05/23/ubuntu-22-10-dropping-pulseaudio/&#34;&gt;Ubuntu 22.10 將 Audio Server 從 PulseAudio 切換成 PipeWire&lt;/a&gt;&lt;/p&gt;
&lt;h2 id=&#34;遇到了什麼問題&#34;&gt;遇到了什麼問題&lt;/h2&gt;
&lt;p&gt;明明使用著有 LDAC 或是 AptX 解碼能力的藍芽耳機，卻只能被迫接受 sbc 的低傳輸率音質嗎，身為規格黨怎麼可以忍受這件事（即便大部份時間都只使用 Youtube Music 的 128K bps opus）&lt;/p&gt;
&lt;h2 id=&#34;解決方法&#34;&gt;解決方法&lt;/h2&gt;
&lt;p&gt;我們可以在 KDE neon 上啟用 PipeWire，並安裝更多藍芽轉碼器&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;安裝 WirePlumber（PipeWire Manager）
&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;-webkit-text-size-adjust:none;&#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 pipewire-media-session- wireplumber
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/li&gt;
&lt;li&gt;透過 systemd 管理 WirePlumber 的開機自啟 daemon
&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;-webkit-text-size-adjust:none;&#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;systemctl --user --now enable wireplumber.service
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/li&gt;
&lt;li&gt;安裝 ALSA
&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;-webkit-text-size-adjust:none;&#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 pipewire-audio-client-libraries
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/li&gt;
&lt;li&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;-webkit-text-size-adjust:none;&#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 &lt;span style=&#34;color:#ae81ff&#34;&gt;\
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    libfdk-aac2 &lt;span style=&#34;color:#ae81ff&#34;&gt;\
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    libldacbt-&lt;span style=&#34;color:#f92672&#34;&gt;{&lt;/span&gt;abr,enc&lt;span style=&#34;color:#f92672&#34;&gt;}&lt;/span&gt;&lt;span style=&#34;color:#ae81ff&#34;&gt;2&lt;/span&gt; &lt;span style=&#34;color:#ae81ff&#34;&gt;\
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    libopenaptx0
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;sudo apt install -y &lt;span style=&#34;color:#ae81ff&#34;&gt;\
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    libspa-0.2-bluetooth &lt;span style=&#34;color:#ae81ff&#34;&gt;\
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    pipewire-audio-client-libraries &lt;span style=&#34;color:#ae81ff&#34;&gt;\
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    pipewire-pulse
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/li&gt;
&lt;li&gt;解除安裝 PulseAudio
&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;-webkit-text-size-adjust:none;&#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 remove -y pulseaudio-module-bluetooth
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/li&gt;
&lt;li&gt;登出或重新啟動&lt;/li&gt;
&lt;li&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;-webkit-text-size-adjust:none;&#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;❯ LANG&lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt;C pactl info | grep &lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#39;^Server Name&amp;#39;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;Server Name: PulseAudio &lt;span style=&#34;color:#f92672&#34;&gt;(&lt;/span&gt;on PipeWire 0.3.48&lt;span style=&#34;color:#f92672&#34;&gt;)&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id=&#34;成效&#34;&gt;成效&lt;/h2&gt;
&lt;p&gt;讓耳機連線至電腦後，選擇設定檔會出現更多轉碼器，諸如 SBC 與 LDAC，但若訊源只是普通串流，那其實聽起來不太可能會有差異。&lt;/p&gt;</description>
    </item>
    <item>
      <title>使用 wireguard 與 aws 搭建虛擬內網</title>
      <link>https://www.omegaatt.com/blogs/develop/2023/wireguard_easy/</link>
      <pubDate>Sun, 01 Oct 2023 00:00:00 +0000</pubDate><author>raiven.kao@gmail.com (Raiven Kao)</author>
      <guid>https://www.omegaatt.com/blogs/develop/2023/wireguard_easy/</guid>
      <description>&lt;p&gt;先前在「&lt;a href=&#34;https://www.omegaatt.com/blogs/develop/2022/frp-tunnel&#34;&gt;在 AWS 上使用 EC2 建立 FRP 玩玩內網穿透&lt;/a&gt;」一文中敘述了如何透過 AWS 實現虛擬穿透，也在內文中提到或許可以使用 wireguard 搭建內網，其原因也很簡單：每更新一個 port 都要重新設定 server side 實在是太麻煩了，拖更了進一年終於要開始寫 wireguard 的使用了。（AWS 免費也快到期了）&lt;/p&gt;
&lt;h2 id=&#34;遇到了什麼問題&#34;&gt;遇到了什麼問題&lt;/h2&gt;
&lt;p&gt;在使用 wireguard 來搭建 VPN 前，我是使用 &lt;a href=&#34;https://www.zerotier.com/&#34;&gt;zerotier&lt;/a&gt; 搭配 Mikrotik RB5009 所執行的 RouterOS 7.x 版本，讓外網可以連回家裡的網路環境&lt;/p&gt;
&lt;p&gt;但 zerotier 的網路拓樸是存在他們官方伺服氣上，與其相信 zerotier，我想在 vps 上建立虛擬機，並只許特定 ip 登入，可能更加可靠（？&lt;/p&gt;
&lt;h2 id=&#34;解決方法&#34;&gt;解決方法&lt;/h2&gt;
&lt;p&gt;於是我在 frp tunnel VM 上建立了 wireguard 節點，作為外網與內網溝通的橋樑。&lt;/p&gt;
&lt;p&gt;&lt;a href=&#34;https://www.wireguard.com/&#34;&gt;wireguard&lt;/a&gt; 是一個高效的現代化 VPN，目標是比 IPsec 更快。在 2020 年時正式推出 1.0.0 版本。&lt;/p&gt;
&lt;p&gt;wireguard 的拓樸實際上是 peer2peer，甚至可以達到 full mesh，但礙於錢錢不夠，單一個節點作為 server side 就足夠了。&lt;/p&gt;
&lt;h3 id=&#34;server&#34;&gt;server&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;使用 docker-compose 能快速部署 wireguard 服務，我們使用的是 &lt;a href=&#34;https://github.com/wg-easy/wg-easy&#34;&gt;wg-easy&lt;/a&gt;
簡單換掉一些參數:&lt;/p&gt;</description>
    </item>
    <item>
      <title>透過 WakaTime 幫助來紀錄自己做了哪些事，並製作獨特的 GitHub Profile</title>
      <link>https://www.omegaatt.com/blogs/develop/2023/wakatime_experience/</link>
      <pubDate>Thu, 03 Aug 2023 00:00:00 +0000</pubDate><author>raiven.kao@gmail.com (Raiven Kao)</author>
      <guid>https://www.omegaatt.com/blogs/develop/2023/wakatime_experience/</guid>
      <description>&lt;h2 id=&#34;wakatime-介紹&#34;&gt;WakaTime 介紹&lt;/h2&gt;
&lt;p&gt;&lt;a href=&#34;https://wakatime.com&#34;&gt;WakaTime&lt;/a&gt; 是一款紀錄自己生產力的工具，透過客戶端插件、集成工具紀錄「行徑」並發送到官方｜非官方伺服器，可以分析花了多久時間在哪個專案、哪個程式語言、會議、code review。
&lt;img loading=&#34;lazy&#34; src=&#34;https://www.omegaatt.com/blogs/develop/2023/wakatime_experience/images/wakatime_dashboard_official-1.webp&#34;&gt;&lt;/p&gt;
&lt;p&gt;記憶力不佳，過去常常無法想起某段時間做了哪些專案，甚至 daily standup 前忘記昨天做了什麼，發現了 wakatime 可以透過 vscode 插件、terminal 插件來查看自己在哪些時間變更了哪些專案、檔案，甚至可以紀錄下過得指令（僅 binary 的部份，不含參數不會洩漏資料）。&lt;/p&gt;
&lt;p&gt;在使用 WakaTime 前，先到 &lt;a href=&#34;https://wakatime.com&#34;&gt;WakaTime 官網&lt;/a&gt;上註冊並登入，取得最重要的 API Key(API Token)：&lt;/p&gt;
&lt;p&gt;&lt;img loading=&#34;lazy&#34; src=&#34;https://www.omegaatt.com/blogs/develop/2023/wakatime_experience/images/wakatime_dashboard_official-2.webp&#34;&gt;&lt;/p&gt;
&lt;p&gt;安裝相應的客戶端插件。&lt;a href=&#34;https://wakatime.com/plugins&#34;&gt;wakatime 支援的插件、編輯器&lt;/a&gt;包括但不限於：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Visual Studio Code&lt;/li&gt;
&lt;li&gt;Vim&lt;/li&gt;
&lt;li&gt;Excel&lt;/li&gt;
&lt;li&gt;Terminal
&lt;ul&gt;
&lt;li&gt;zsh&lt;/li&gt;
&lt;li&gt;bash&lt;/li&gt;
&lt;li&gt;fish&lt;/li&gt;
&lt;li&gt;iTerm2&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;舉例來說我們可以安裝 &lt;a href=&#34;https://marketplace.visualstudio.com/items?itemName=WakaTime.vscode-wakatime&#34;&gt;vscode 插件&lt;/a&gt;，並跟著 Installation 輸入完 API Key 就可以開始寫點東西了。&lt;/p&gt;
&lt;h2 id=&#34;查看報告&#34;&gt;查看報告&lt;/h2&gt;
&lt;p&gt;使用了一段時間後，可以回到官網查看 &lt;a href=&#34;https://wakatime.com/dashboard&#34;&gt;dashboard&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;查看自己上週、前兩週、前一個月每天花了（浪費）了多少時間，以前三十天的圖為例：
&lt;img loading=&#34;lazy&#34; src=&#34;https://www.omegaatt.com/blogs/develop/2023/wakatime_experience/images/wakatime_dashboard_official-3.webp&#34;&gt;&lt;/p&gt;
&lt;p&gt;或是 YoY 的活動圖
&lt;img loading=&#34;lazy&#34; src=&#34;https://www.omegaatt.com/blogs/develop/2023/wakatime_experience/images/wakatime_dashboard_official-6.webp&#34;&gt;&lt;/p&gt;
&lt;p&gt;以此 blog 的 repo 來作為舉例，可以查看某個 project 的細節&lt;/p&gt;
&lt;p&gt;在該專案花了多少時間、什麼語言：
&lt;img loading=&#34;lazy&#34; src=&#34;https://www.omegaatt.com/blogs/develop/2023/wakatime_experience/images/wakatime_dashboard_official-4.webp&#34;&gt;&lt;/p&gt;
&lt;p&gt;檔案、分支的時間分配：
&lt;img loading=&#34;lazy&#34; src=&#34;https://www.omegaatt.com/blogs/develop/2023/wakatime_experience/images/wakatime_dashboard_official-5.webp&#34;&gt;&lt;/p&gt;
&lt;h2 id=&#34;費用&#34;&gt;費用&lt;/h2&gt;
&lt;p&gt;免費版本的功能已經十分完整了，&lt;a href=&#34;https://wakatime.com/pricing?utm_source=magic-panda-engineer&#34;&gt;完整費用方案在此&lt;/a&gt;。
透過學生帳號可以申請教育折扣，年度付費 Premium 方案的話一年只要 59 美元，就可以享有完整的 WakaTime 服務。
雖然付費方案的功能幾乎都可以透過免費版本就有的 API 來完成，但仍可以花點小錢支持團隊，或是開啟公司計畫。&lt;/p&gt;</description>
    </item>
    <item>
      <title>KDE Neon 使用心得與踩雷紀錄</title>
      <link>https://www.omegaatt.com/blogs/develop/2023/kde_neon_experience/</link>
      <pubDate>Sun, 04 Jun 2023 00:00:00 +0000</pubDate><author>raiven.kao@gmail.com (Raiven Kao)</author>
      <guid>https://www.omegaatt.com/blogs/develop/2023/kde_neon_experience/</guid>
      <description>&lt;h2 id=&#34;前言&#34;&gt;前言&lt;/h2&gt;
&lt;p&gt;年初為了 &lt;a href=&#34;https://arc.net/&#34;&gt;arc browser&lt;/a&gt; 裝了一台 Hackintosh，因為原因又花了點時間裝了 &lt;a href=&#34;https://neon.kde.org/&#34;&gt;KDE Neon&lt;/a&gt;，這邊紀錄一下使用心得與踩雷紀錄。時間線大概是&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;快快樂樂使用 wsl2&lt;/li&gt;
&lt;li&gt;wsl 中踩了不少坑，想要嘗試 unix liked 的系統&lt;/li&gt;
&lt;li&gt;好心人提供 arc browser 的邀請，萌生了裝 Hackintosh 的念頭
&lt;ul&gt;
&lt;li&gt;在 HP Z2 G4 上裝了 Hackintosh，但是沒有事前做足功課，內顯圖形記憶體(P630)始終是 7mb&lt;/li&gt;
&lt;li&gt;買了張 amd WX4100 督上去，大幅提昇圖形性能，卻也沒再花時間繼續深入&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;嘗試在 windows 主機上雙系統 (windows + linux):
&lt;ul&gt;
&lt;li&gt;一開始直接選擇熟悉的 ubuntu，安裝了 ubuntu desktop 版本，由於是 nvidia 顯示卡，很快的遇到了硬體解碼問題&lt;/li&gt;
&lt;li&gt;開始嘗試 &lt;a href=&#34;https://github.com/elFarto/nvidia-vaapi-driver&#34;&gt;nvidia-vaapi-driver&lt;/a&gt;，但是遇到了很多問題，最後放棄&lt;/li&gt;
&lt;li&gt;想換到 amd 顯示卡，但是又不想花錢，於是想說看看其他發布版是否能解決&lt;/li&gt;
&lt;li&gt;從 Linux Mint, Manjaro, KEO Neon 中選擇了同樣是 ubuntu 底，卻是 KDE 團隊維護團隊的 KDE Neon&lt;/li&gt;
&lt;li&gt;不出意外的一樣遇到了 nvidia 驅動問題，雖然 firefox 已經可以順利硬體加速，但是 chromium 等仍然無法硬體加速&lt;/li&gt;
&lt;li&gt;這時候已經花了不少時間在這個問題上，於是決定換到 amd 顯示卡(內顯)&lt;/li&gt;
&lt;li&gt;在 Minisforum UM560 上安裝了 KDE Neon，一切順利，驅動毫無問題，硬體加速也正常&lt;/li&gt;
&lt;li&gt;嘗試裝了 ventura theme，讓他稍微好看一點&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;持續與 wayland 相處，走一步是一步&amp;hellip;&lt;/li&gt;
&lt;/ul&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;-webkit-text-size-adjust:none;&#34;&gt;&lt;code class=&#34;language-shell&#34; data-lang=&#34;shell&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;❯ neofetch
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;             &lt;span style=&#34;color:#e6db74&#34;&gt;`&lt;/span&gt;..---+/---..&lt;span style=&#34;color:#e6db74&#34;&gt;`&lt;/span&gt;                raiven@um560
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;         &lt;span style=&#34;color:#e6db74&#34;&gt;`&lt;/span&gt;---.&lt;span style=&#34;color:#e6db74&#34;&gt;``&lt;/span&gt;   &lt;span style=&#34;color:#e6db74&#34;&gt;``&lt;/span&gt;   &lt;span style=&#34;color:#e6db74&#34;&gt;`&lt;/span&gt;.---.&lt;span style=&#34;color:#e6db74&#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:#e6db74&#34;&gt;`&lt;/span&gt;        &lt;span style=&#34;color:#e6db74&#34;&gt;``&lt;/span&gt;        &lt;span style=&#34;color:#e6db74&#34;&gt;`&lt;/span&gt;-:-.         OS: KDE neon 5.27 x86_64
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#e6db74&#34;&gt;`&lt;/span&gt;:/:     &lt;span style=&#34;color:#e6db74&#34;&gt;`&lt;/span&gt;.----//----.&lt;span style=&#34;color:#e6db74&#34;&gt;`&lt;/span&gt;     :/-        Host: UM560
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;   .:.    &lt;span style=&#34;color:#e6db74&#34;&gt;`&lt;/span&gt;---&lt;span style=&#34;color:#e6db74&#34;&gt;`&lt;/span&gt;          &lt;span style=&#34;color:#e6db74&#34;&gt;`&lt;/span&gt;--.&lt;span style=&#34;color:#e6db74&#34;&gt;`&lt;/span&gt;    .:&lt;span style=&#34;color:#e6db74&#34;&gt;`&lt;/span&gt;      Kernel: 5.19.0-43-generic
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  .:&lt;span style=&#34;color:#e6db74&#34;&gt;`&lt;/span&gt;   &lt;span style=&#34;color:#e6db74&#34;&gt;`&lt;/span&gt;--&lt;span style=&#34;color:#e6db74&#34;&gt;`&lt;/span&gt;                .:-    &lt;span style=&#34;color:#e6db74&#34;&gt;`&lt;/span&gt;:.     Uptime: &lt;span style=&#34;color:#ae81ff&#34;&gt;1&lt;/span&gt; hour, &lt;span style=&#34;color:#ae81ff&#34;&gt;2&lt;/span&gt; mins
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt; &lt;span style=&#34;color:#e6db74&#34;&gt;`&lt;/span&gt;/    &lt;span style=&#34;color:#e6db74&#34;&gt;`&lt;/span&gt;:.      &lt;span style=&#34;color:#e6db74&#34;&gt;`&lt;/span&gt;.-::-.&lt;span style=&#34;color:#e6db74&#34;&gt;`&lt;/span&gt;      -:&lt;span style=&#34;color:#e6db74&#34;&gt;`&lt;/span&gt;   &lt;span style=&#34;color:#e6db74&#34;&gt;`&lt;/span&gt;/&lt;span style=&#34;color:#e6db74&#34;&gt;`&lt;/span&gt;    Packages: &lt;span style=&#34;color:#ae81ff&#34;&gt;1924&lt;/span&gt; &lt;span style=&#34;color:#f92672&#34;&gt;(&lt;/span&gt;dpkg&lt;span style=&#34;color:#f92672&#34;&gt;)&lt;/span&gt;, &lt;span style=&#34;color:#ae81ff&#34;&gt;4&lt;/span&gt; &lt;span style=&#34;color:#f92672&#34;&gt;(&lt;/span&gt;snap&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:#e6db74&#34;&gt;`&lt;/span&gt;:++++++++:&lt;span style=&#34;color:#e6db74&#34;&gt;`&lt;/span&gt;     .:    .:    Shell: zsh 5.8.1
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#e6db74&#34;&gt;`&lt;/span&gt;/    .:     &lt;span style=&#34;color:#e6db74&#34;&gt;`&lt;/span&gt;+++++++++++/      /&lt;span style=&#34;color:#e6db74&#34;&gt;`&lt;/span&gt;   &lt;span style=&#34;color:#e6db74&#34;&gt;`&lt;/span&gt;+&lt;span style=&#34;color:#e6db74&#34;&gt;`&lt;/span&gt;   Resolution: 2560x1440
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;/+&lt;span style=&#34;color:#e6db74&#34;&gt;`&lt;/span&gt;   --     .++++++++++++&lt;span style=&#34;color:#e6db74&#34;&gt;`&lt;/span&gt;     :.   .+:   DE: Plasma 5.27.5
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#e6db74&#34;&gt;`&lt;/span&gt;/    .:     &lt;span style=&#34;color:#e6db74&#34;&gt;`&lt;/span&gt;+++++++++++/      /&lt;span style=&#34;color:#e6db74&#34;&gt;`&lt;/span&gt;   &lt;span style=&#34;color:#e6db74&#34;&gt;`&lt;/span&gt;+&lt;span style=&#34;color:#e6db74&#34;&gt;`&lt;/span&gt;   WM: kwin
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt; /&lt;span style=&#34;color:#e6db74&#34;&gt;`&lt;/span&gt;    /.     &lt;span style=&#34;color:#e6db74&#34;&gt;`&lt;/span&gt;:++++++++:&lt;span style=&#34;color:#e6db74&#34;&gt;`&lt;/span&gt;     .:    .:    Theme: &lt;span style=&#34;color:#f92672&#34;&gt;[&lt;/span&gt;Plasma&lt;span style=&#34;color:#f92672&#34;&gt;]&lt;/span&gt;, Colloid-Dark &lt;span style=&#34;color:#f92672&#34;&gt;[&lt;/span&gt;GTK2/3&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:#e6db74&#34;&gt;`&lt;/span&gt;:.      &lt;span style=&#34;color:#e6db74&#34;&gt;`&lt;/span&gt;.:::-.&lt;span style=&#34;color:#e6db74&#34;&gt;`&lt;/span&gt;      -:&lt;span style=&#34;color:#e6db74&#34;&gt;`&lt;/span&gt;   &lt;span style=&#34;color:#e6db74&#34;&gt;`&lt;/span&gt;/&lt;span style=&#34;color:#e6db74&#34;&gt;`&lt;/span&gt;    Icons: Cupertino-Ventura &lt;span style=&#34;color:#f92672&#34;&gt;[&lt;/span&gt;Plasma&lt;span style=&#34;color:#f92672&#34;&gt;]&lt;/span&gt;, Cupertino-Ventura &lt;span style=&#34;color:#f92672&#34;&gt;[&lt;/span&gt;GTK2/3&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:#e6db74&#34;&gt;`&lt;/span&gt;   &lt;span style=&#34;color:#e6db74&#34;&gt;`&lt;/span&gt;--&lt;span style=&#34;color:#e6db74&#34;&gt;`&lt;/span&gt;                .:-    &lt;span style=&#34;color:#e6db74&#34;&gt;`&lt;/span&gt;:.     Terminal: konsole
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;   .:.    &lt;span style=&#34;color:#e6db74&#34;&gt;`&lt;/span&gt;---&lt;span style=&#34;color:#e6db74&#34;&gt;`&lt;/span&gt;          &lt;span style=&#34;color:#e6db74&#34;&gt;`&lt;/span&gt;--.&lt;span style=&#34;color:#e6db74&#34;&gt;`&lt;/span&gt;    .:&lt;span style=&#34;color:#e6db74&#34;&gt;`&lt;/span&gt;      CPU: AMD Ryzen &lt;span style=&#34;color:#ae81ff&#34;&gt;5&lt;/span&gt; 5625U with Radeon Graphics &lt;span style=&#34;color:#f92672&#34;&gt;(&lt;/span&gt;12&lt;span style=&#34;color:#f92672&#34;&gt;)&lt;/span&gt; @ 2.300GHz
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#e6db74&#34;&gt;`&lt;/span&gt;:/:     &lt;span style=&#34;color:#e6db74&#34;&gt;`&lt;/span&gt;.----//----.&lt;span style=&#34;color:#e6db74&#34;&gt;`&lt;/span&gt;     :/-        GPU: AMD ATI 04:00.0 Barcelo
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;      .-:.&lt;span style=&#34;color:#e6db74&#34;&gt;`&lt;/span&gt;        &lt;span style=&#34;color:#e6db74&#34;&gt;``&lt;/span&gt;        &lt;span style=&#34;color:#e6db74&#34;&gt;`&lt;/span&gt;-:-.         Memory: 6265MiB / 31519MiB
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;         &lt;span style=&#34;color:#e6db74&#34;&gt;`&lt;/span&gt;---.&lt;span style=&#34;color:#e6db74&#34;&gt;``&lt;/span&gt;   &lt;span style=&#34;color:#e6db74&#34;&gt;``&lt;/span&gt;   &lt;span style=&#34;color:#e6db74&#34;&gt;`&lt;/span&gt;.---.&lt;span style=&#34;color:#e6db74&#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:#e6db74&#34;&gt;`&lt;/span&gt;..---+/---..&lt;span style=&#34;color:#e6db74&#34;&gt;`&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;h2 id=&#34;post-install-kde-neon&#34;&gt;Post Install KDE Neon&lt;/h2&gt;
&lt;h3 id=&#34;解決-key-過舊&#34;&gt;解決 key 過舊&lt;/h3&gt;
&lt;p&gt;當使用 &lt;code&gt;sudo apt update&lt;/code&gt; 時可能會發生&lt;/p&gt;</description>
    </item>
    <item>
      <title>golang 樂觀鎖、悲觀鎖 學習筆記與實驗</title>
      <link>https://www.omegaatt.com/blogs/develop/2023/golang_lock_practice/</link>
      <pubDate>Sun, 14 May 2023 00:00:00 +0000</pubDate><author>raiven.kao@gmail.com (Raiven Kao)</author>
      <guid>https://www.omegaatt.com/blogs/develop/2023/golang_lock_practice/</guid>
      <description>&lt;h2 id=&#34;介紹&#34;&gt;介紹&lt;/h2&gt;
&lt;p&gt;改變數值的三個步驟&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;取出&lt;/li&gt;
&lt;li&gt;修改&lt;/li&gt;
&lt;li&gt;保存&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;但這三者間的時間差在不同 process、不同 thread、不同 corutine/goroutine 中會造成競爭危害(race condition)。&lt;/p&gt;
&lt;p&gt;可以使用多種發法確保並行(concurrency)處理時保持資料的一致性，這裡介紹的是最常使用的悲觀鎖與樂觀鎖。&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;悲觀鎖: 總可能發生問題
&lt;pre tabindex=&#34;0&#34;&gt;&lt;code&gt;lock
(1) 取值
(2) 修改
(3) 保存
unlock
&lt;/code&gt;&lt;/pre&gt;&lt;/li&gt;
&lt;li&gt;樂觀鎖: 不會總是發生問題
&lt;pre tabindex=&#34;0&#34;&gt;&lt;code&gt;(1) 取值
if *addr == old {
    (2) 修改
    (3) 保存
    return true
}
return false
&lt;/code&gt;&lt;/pre&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id=&#34;悲觀鎖&#34;&gt;悲觀鎖&lt;/h2&gt;
&lt;p&gt;golang 中主要使用 &lt;code&gt;sync.Mutex&lt;/code&gt; 作為悲觀鎖，看似會阻塞住其他 goroutines，但其實 &lt;code&gt;sync.Mutex&lt;/code&gt; 中也使用到了 CAS。&lt;/p&gt;
&lt;p&gt;&lt;code&gt;sync.Mutex&lt;/code&gt; 中有一個 &lt;code&gt;int32&lt;/code&gt; 的 &lt;code&gt;state&lt;/code&gt; 與 &lt;code&gt;uint32&lt;/code&gt; 的 &lt;code&gt;sema&lt;/code&gt;(semaphore)&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;-webkit-text-size-adjust:none;&#34;&gt;&lt;code class=&#34;language-go&#34; data-lang=&#34;go&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#66d9ef&#34;&gt;type&lt;/span&gt; &lt;span style=&#34;color:#a6e22e&#34;&gt;Mutex&lt;/span&gt; &lt;span style=&#34;color:#66d9ef&#34;&gt;struct&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;state&lt;/span&gt; &lt;span style=&#34;color:#66d9ef&#34;&gt;int32&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;sema&lt;/span&gt;  &lt;span style=&#34;color:#66d9ef&#34;&gt;uint32&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&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;	&lt;span style=&#34;color:#a6e22e&#34;&gt;mutexLocked&lt;/span&gt; = &lt;span style=&#34;color:#ae81ff&#34;&gt;1&lt;/span&gt; &lt;span style=&#34;color:#f92672&#34;&gt;&amp;lt;&amp;lt;&lt;/span&gt; &lt;span style=&#34;color:#66d9ef&#34;&gt;iota&lt;/span&gt; &lt;span style=&#34;color:#75715e&#34;&gt;// mutex is locked&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;mutexWoken&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;mutexStarving&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;mutexWaiterShift&lt;/span&gt; = &lt;span style=&#34;color:#66d9ef&#34;&gt;iota&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;starvationThresholdNs&lt;/span&gt; = &lt;span style=&#34;color:#ae81ff&#34;&gt;1e6&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;func&lt;/span&gt; (&lt;span style=&#34;color:#a6e22e&#34;&gt;m&lt;/span&gt; &lt;span style=&#34;color:#f92672&#34;&gt;*&lt;/span&gt;&lt;span style=&#34;color:#a6e22e&#34;&gt;Mutex&lt;/span&gt;) &lt;span style=&#34;color:#a6e22e&#34;&gt;Lock&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;atomic&lt;/span&gt;.&lt;span style=&#34;color:#a6e22e&#34;&gt;CompareAndSwapInt32&lt;/span&gt;(&lt;span style=&#34;color:#f92672&#34;&gt;&amp;amp;&lt;/span&gt;&lt;span style=&#34;color:#a6e22e&#34;&gt;m&lt;/span&gt;.&lt;span style=&#34;color:#a6e22e&#34;&gt;state&lt;/span&gt;, &lt;span style=&#34;color:#ae81ff&#34;&gt;0&lt;/span&gt;, &lt;span style=&#34;color:#a6e22e&#34;&gt;mutexLocked&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;race&lt;/span&gt;.&lt;span style=&#34;color:#a6e22e&#34;&gt;Enabled&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;race&lt;/span&gt;.&lt;span style=&#34;color:#a6e22e&#34;&gt;Acquire&lt;/span&gt;(&lt;span style=&#34;color:#a6e22e&#34;&gt;unsafe&lt;/span&gt;.&lt;span style=&#34;color:#a6e22e&#34;&gt;Pointer&lt;/span&gt;(&lt;span style=&#34;color:#a6e22e&#34;&gt;m&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&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:#a6e22e&#34;&gt;m&lt;/span&gt;.&lt;span style=&#34;color:#a6e22e&#34;&gt;lockSlow&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;ul&gt;
&lt;li&gt;
&lt;p&gt;&lt;code&gt;state&lt;/code&gt;:&lt;/p&gt;</description>
    </item>
    <item>
      <title>讓 BOOX 也使用繁體中文辭典</title>
      <link>https://www.omegaatt.com/blogs/develop/2023/mdx_dictionary_sc_to_tc/</link>
      <pubDate>Thu, 04 May 2023 00:00:00 +0000</pubDate><author>raiven.kao@gmail.com (Raiven Kao)</author>
      <guid>https://www.omegaatt.com/blogs/develop/2023/mdx_dictionary_sc_to_tc/</guid>
      <description>&lt;p&gt;由於最近讀原文書讀得挺辛苦，邊用電子閱讀器看書，騰不出手用手機查字典，內建的辭典又只有英文翻譯簡體中文，在網上衝浪也找不到繁體中文的 mdx 英漢辭典可以直接使用。&lt;/p&gt;
&lt;h2 id=&#34;所需依賴&#34;&gt;所需依賴&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;任何英漢辭典
&lt;blockquote&gt;
&lt;p&gt;文內以 BOOX 內下載的英漢辭典，參考&lt;a href=&#34;https://mul.iqrator.com/2021/07/19/boox%E6%99%BA%E6%85%A7%E6%82%85%E8%AE%80-%E5%85%A7%E5%BB%BA%E5%AD%97%E5%85%B8%E6%87%89%E7%94%A8/&#34;&gt;教學&lt;/a&gt;。&lt;/p&gt;
&lt;/blockquote&gt;
&lt;/li&gt;
&lt;li&gt;python 環境，用以安裝 &lt;a href=&#34;https://github.com/liuyug/mdict-utils&#34;&gt;mdict-utils&lt;/a&gt;
&lt;blockquote&gt;
&lt;p&gt;經測試後 windows 與 wsl 中均能完成，文內 python 環境以 wsl 為主。&lt;/p&gt;
&lt;/blockquote&gt;
&lt;/li&gt;
&lt;li&gt;任何繁簡轉換軟體
&lt;blockquote&gt;
&lt;p&gt;文內以新發現的 &lt;a href=&#34;https://github.com/flier268/ConvertZZ&#34;&gt;ConvertZZ&lt;/a&gt; 最為 GUI 簡轉繁示例。
文末有使用 &lt;a href=&#34;https://github.com/BYVoid/OpenCC&#34;&gt;OpenCC&lt;/a&gt; 作為 CLI 示例。&lt;/p&gt;
&lt;/blockquote&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id=&#34;主要流程&#34;&gt;主要流程&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;將 mdx 拆成 txt&lt;/li&gt;
&lt;li&gt;簡轉繁&lt;/li&gt;
&lt;li&gt;將 txt 包成 mdx&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 id=&#34;安裝&#34;&gt;安裝&lt;/h3&gt;
&lt;p&gt;安裝 mdict-utils&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;-webkit-text-size-adjust:none;&#34;&gt;&lt;code class=&#34;language-shell&#34; data-lang=&#34;shell&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;❯ pip install mdict-utils
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;Defaulting to user installation because normal site-packages is not writeable
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;Collecting mdict-utils
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  Downloading mdict_utils-1.3.12-py3-none-any.whl &lt;span style=&#34;color:#f92672&#34;&gt;(&lt;/span&gt;&lt;span style=&#34;color:#ae81ff&#34;&gt;36&lt;/span&gt; kB&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;Collecting xxhash
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  Downloading xxhash-3.2.0-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl &lt;span style=&#34;color:#f92672&#34;&gt;(&lt;/span&gt;&lt;span style=&#34;color:#ae81ff&#34;&gt;212&lt;/span&gt; kB&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;     ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ 212.5/212.5 KB 1.6 MB/s eta 0:00:00
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;Collecting tqdm
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  Downloading tqdm-4.65.0-py3-none-any.whl &lt;span style=&#34;color:#f92672&#34;&gt;(&lt;/span&gt;&lt;span style=&#34;color:#ae81ff&#34;&gt;77&lt;/span&gt; kB&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;     ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ 77.1/77.1 KB 7.4 MB/s eta 0:00:00
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;Installing collected packages: xxhash, tqdm, mdict-utils
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;Successfully installed mdict-utils-1.3.12 tqdm-4.65.0 xxhash-3.2.0
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;h3 id=&#34;unpack&#34;&gt;unpack&lt;/h3&gt;
&lt;p&gt;將英漢辭典重新命名為 &lt;code&gt;dict.mdx&lt;/code&gt;&lt;/p&gt;</description>
    </item>
    <item>
      <title>如何優雅地批次安裝 Nerd font 字型</title>
      <link>https://www.omegaatt.com/blogs/develop/2023/install_fonts/</link>
      <pubDate>Wed, 03 May 2023 00:00:00 +0000</pubDate><author>raiven.kao@gmail.com (Raiven Kao)</author>
      <guid>https://www.omegaatt.com/blogs/develop/2023/install_fonts/</guid>
      <description>&lt;p&gt;&lt;a href=&#34;https://github.com/ryanoasis/nerd-fonts&#34;&gt;fonts repo&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;接觸 wsl/ubuntu 的這幾年，經常會需要安裝 zsh 以及字型，因此誕生了 &lt;a href=&#34;https://www.omegaatt.com/blogs/develop/2022/zsh-cheat-sheet&#34;&gt;cheat sheet&lt;/a&gt;，久而久之連下載-&amp;gt;解壓縮-&amp;gt;安裝這個過程，都懶了。&lt;/p&gt;
&lt;p&gt;於是寫了一個批次下載並安裝最新版本的 Nerd Fond 字型的 shell script，讓 wsl 可以安裝 windows 的字型，linux 可以安裝 linux 字型。&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;-webkit-text-size-adjust:none;&#34;&gt;&lt;code class=&#34;language-shell&#34; data-lang=&#34;shell&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#75715e&#34;&gt;#!/bin/bash
&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;declare target&lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt;&lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;linux&amp;#34;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;declare repo&lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt;&lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;ryanoasis/nerd-fonts&amp;#34;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;declare -a fonts&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;  BitstreamVeraSansMono
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  CodeNewRoman
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  DroidSansMono
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  FiraCode
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  FiraMono
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  Go-Mono
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  Hack
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  Hermit
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  JetBrainsMono
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  Meslo
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  Noto
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  Overpass
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  ProggyClean
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  RobotoMono
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  SourceCodePro
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  SpaceMono
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  Ubuntu
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  UbuntuMono
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&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&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;ARGS&lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt;&lt;span style=&#34;color:#e6db74&#34;&gt;`&lt;/span&gt;getopt -o p --long windows -- &lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;&lt;/span&gt;$@&lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;&lt;/span&gt;&lt;span style=&#34;color:#e6db74&#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;if&lt;/span&gt; &lt;span style=&#34;color:#f92672&#34;&gt;[&lt;/span&gt; $? -ne &lt;span style=&#34;color:#ae81ff&#34;&gt;0&lt;/span&gt; &lt;span style=&#34;color:#f92672&#34;&gt;]&lt;/span&gt;; &lt;span style=&#34;color:#66d9ef&#34;&gt;then&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  echo &lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;getopt failed: &amp;#34;&lt;/span&gt; $ARGS
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  exit &lt;span style=&#34;color:#ae81ff&#34;&gt;1&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;fi&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;eval set -- &lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;&lt;/span&gt;&lt;span style=&#34;color:#e6db74&#34;&gt;${&lt;/span&gt;ARGS&lt;span style=&#34;color:#e6db74&#34;&gt;}&lt;/span&gt;&lt;span style=&#34;color:#e6db74&#34;&gt;&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 style=&#34;color:#66d9ef&#34;&gt;while&lt;/span&gt; true
&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;do&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;case&lt;/span&gt; &lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;&lt;/span&gt;$1&lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;&lt;/span&gt; in
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  -p|--windows&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;    target&lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt;windows
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    shift
&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:#f92672&#34;&gt;)&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    shift
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    break
&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;esac&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;done&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;get_latest_release&lt;span style=&#34;color:#f92672&#34;&gt;()&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;  echo &lt;span style=&#34;color:#66d9ef&#34;&gt;$(&lt;/span&gt;curl --silent &lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;https://api.github.com/repos/&lt;/span&gt;$@&lt;span style=&#34;color:#e6db74&#34;&gt;/releases/latest&amp;#34;&lt;/span&gt; |
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    grep &lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#39;&amp;#34;tag_name&amp;#34;:&amp;#39;&lt;/span&gt; |
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    sed -E &lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#39;s/.*&amp;#34;([^&amp;#34;]+)&amp;#34;.*/\1/&amp;#39;&lt;/span&gt;&lt;span style=&#34;color:#66d9ef&#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:#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&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#66d9ef&#34;&gt;function&lt;/span&gt; install_linux_fonts&lt;span style=&#34;color:#f92672&#34;&gt;()&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;  local fonts_dir
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  fonts_dir&lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt;&lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;&lt;/span&gt;&lt;span style=&#34;color:#e6db74&#34;&gt;${&lt;/span&gt;HOME&lt;span style=&#34;color:#e6db74&#34;&gt;}&lt;/span&gt;&lt;span style=&#34;color:#e6db74&#34;&gt;/.local/share/fonts&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;if&lt;/span&gt; &lt;span style=&#34;color:#f92672&#34;&gt;[[&lt;/span&gt; ! -d &lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;&lt;/span&gt;$fonts_dir&lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;&lt;/span&gt; &lt;span style=&#34;color:#f92672&#34;&gt;]]&lt;/span&gt;; &lt;span style=&#34;color:#66d9ef&#34;&gt;then&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    mkdir -p &lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;&lt;/span&gt;$fonts_dir&lt;span style=&#34;color:#e6db74&#34;&gt;&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;fi&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  mv $@/*.ttf $fonts_dir/
&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:#75715e&#34;&gt;# fc-cache -fv&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:#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&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#75715e&#34;&gt;# Install font for the current user. It&amp;#39;ll appear in &amp;#34;Font settings&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;function&lt;/span&gt; install_windows_fonts&lt;span style=&#34;color:#f92672&#34;&gt;()&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;  local dst_dir
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  dst_dir&lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt;&lt;span style=&#34;color:#66d9ef&#34;&gt;$(&lt;/span&gt;wslpath &lt;span style=&#34;color:#66d9ef&#34;&gt;$(&lt;/span&gt;cmd.exe /c &lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;echo %LOCALAPPDATA%\Microsoft\\Windows\\Fonts&amp;#34;&lt;/span&gt; 2&amp;gt;/dev/null | sed &lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#39;s/\r$//&amp;#39;&lt;/span&gt;&lt;span style=&#34;color:#66d9ef&#34;&gt;))&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  mkdir -p &lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;&lt;/span&gt;$dst_dir&lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  local src
&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;for&lt;/span&gt; src in &lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;&lt;/span&gt;$@&lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;&lt;/span&gt;; &lt;span style=&#34;color:#66d9ef&#34;&gt;do&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    local file&lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt;&lt;span style=&#34;color:#66d9ef&#34;&gt;$(&lt;/span&gt;basename &lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;&lt;/span&gt;$src&lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;&lt;/span&gt;&lt;span style=&#34;color:#66d9ef&#34;&gt;)&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    test -f &lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;&lt;/span&gt;$dst_dir&lt;span style=&#34;color:#e6db74&#34;&gt;/&lt;/span&gt;$file&lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;&lt;/span&gt; &lt;span style=&#34;color:#f92672&#34;&gt;||&lt;/span&gt; cp -f &lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;&lt;/span&gt;$src&lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;&lt;/span&gt; &lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;&lt;/span&gt;$dst_dir&lt;span style=&#34;color:#e6db74&#34;&gt;/&amp;#34;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    local win_path
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    win_path&lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt;&lt;span style=&#34;color:#66d9ef&#34;&gt;$(&lt;/span&gt;wslpath -w &lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;&lt;/span&gt;$dst_dir&lt;span style=&#34;color:#e6db74&#34;&gt;/&lt;/span&gt;$file&lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;&lt;/span&gt;&lt;span style=&#34;color:#66d9ef&#34;&gt;)&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    echo $win_path
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    reg.exe add                                                      &lt;span style=&#34;color:#ae81ff&#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:#e6db74&#34;&gt;&amp;#34;HKCU\\SOFTWARE\\Microsoft\\Windows NT\\CurrentVersion\\Fonts&amp;#34;&lt;/span&gt; &lt;span style=&#34;color:#ae81ff&#34;&gt;\
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;      /v &lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;&lt;/span&gt;&lt;span style=&#34;color:#e6db74&#34;&gt;${&lt;/span&gt;file%.*&lt;span style=&#34;color:#e6db74&#34;&gt;}&lt;/span&gt;&lt;span style=&#34;color:#e6db74&#34;&gt; (TrueType)&amp;#34;&lt;/span&gt;  /t REG_SZ /d &lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;&lt;/span&gt;$win_path&lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;&lt;/span&gt; /f 2&amp;gt;/dev/null
&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;done&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:#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&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#66d9ef&#34;&gt;function&lt;/span&gt; main&lt;span style=&#34;color:#f92672&#34;&gt;()&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;  local version
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  version&lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt;&lt;span style=&#34;color:#66d9ef&#34;&gt;$(&lt;/span&gt; get_latest_release $repo &lt;span style=&#34;color:#66d9ef&#34;&gt;)&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  local tmp_dir
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  tmp_dir&lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt;&lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;&lt;/span&gt;&lt;span style=&#34;color:#66d9ef&#34;&gt;$(&lt;/span&gt;mktemp -d&lt;span style=&#34;color:#66d9ef&#34;&gt;)&lt;/span&gt;&lt;span style=&#34;color:#e6db74&#34;&gt;&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;  trap &lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;rm -rf &lt;/span&gt;&lt;span style=&#34;color:#e6db74&#34;&gt;${&lt;/span&gt;tmp_dir@Q&lt;span style=&#34;color:#e6db74&#34;&gt;}&lt;/span&gt;&lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;&lt;/span&gt; INT TERM EXIT
&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;for&lt;/span&gt; font in &lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;&lt;/span&gt;&lt;span style=&#34;color:#e6db74&#34;&gt;${&lt;/span&gt;fonts[@]&lt;span style=&#34;color:#e6db74&#34;&gt;}&lt;/span&gt;&lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;&lt;/span&gt;; &lt;span style=&#34;color:#66d9ef&#34;&gt;do&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    zip_file&lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt;&lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;&lt;/span&gt;&lt;span style=&#34;color:#e6db74&#34;&gt;${&lt;/span&gt;font&lt;span style=&#34;color:#e6db74&#34;&gt;}&lt;/span&gt;&lt;span style=&#34;color:#e6db74&#34;&gt;.zip&amp;#34;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    download_url&lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt;&lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;https://github.com/&lt;/span&gt;$repo&lt;span style=&#34;color:#e6db74&#34;&gt;/releases/download/&lt;/span&gt;&lt;span style=&#34;color:#e6db74&#34;&gt;${&lt;/span&gt;version&lt;span style=&#34;color:#e6db74&#34;&gt;}&lt;/span&gt;&lt;span style=&#34;color:#e6db74&#34;&gt;/&lt;/span&gt;&lt;span style=&#34;color:#e6db74&#34;&gt;${&lt;/span&gt;zip_file&lt;span style=&#34;color:#e6db74&#34;&gt;}&lt;/span&gt;&lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    echo &lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;Downloading &lt;/span&gt;$download_url&lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    wget -q --show-progress -P &lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;&lt;/span&gt;$tmp_dir&lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;&lt;/span&gt; &lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;&lt;/span&gt;$download_url&lt;span style=&#34;color:#e6db74&#34;&gt;&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:#75715e&#34;&gt;# unzip -o means replace file without asking.&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    unzip -o &lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;&lt;/span&gt;$tmp_dir&lt;span style=&#34;color:#e6db74&#34;&gt;/&lt;/span&gt;$zip_file&lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;&lt;/span&gt; -d &lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;&lt;/span&gt;$tmp_dir&lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    rm &lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;&lt;/span&gt;$tmp_dir&lt;span style=&#34;color:#e6db74&#34;&gt;/&lt;/span&gt;$zip_file&lt;span style=&#34;color:#e6db74&#34;&gt;&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;done&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  find &lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;&lt;/span&gt;$tmp_dir&lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;&lt;/span&gt; -name &lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#39;*Windows Compatible*&amp;#39;&lt;/span&gt; -delete
&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;if&lt;/span&gt; &lt;span style=&#34;color:#f92672&#34;&gt;[&lt;/span&gt; &lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;&lt;/span&gt;$target&lt;span style=&#34;color:#e6db74&#34;&gt;&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;linux&amp;#34;&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:#66d9ef&#34;&gt;then&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    install_linux_fonts $tmp_dir
&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;elif&lt;/span&gt; &lt;span style=&#34;color:#f92672&#34;&gt;[&lt;/span&gt; &lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;&lt;/span&gt;$target&lt;span style=&#34;color:#e6db74&#34;&gt;&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;windows&amp;#34;&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:#66d9ef&#34;&gt;then&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    install_windows_fonts $tmp_dir/*.ttf
&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;fi&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:#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&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;main
&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;echo -e &lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#39;\033[0;32m&amp;#39;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;echo &lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#39;Fonts successfully installed.&amp;#39;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;echo -e &lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#39;\033[0m&amp;#39;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;若沒有要改目標字型也可以直接透過 github 上的檔案來安裝&lt;/p&gt;</description>
    </item>
    <item>
      <title>golang 中使用 Line LIFF 實作 Single Sign-On</title>
      <link>https://www.omegaatt.com/blogs/develop/2023/line_verify/</link>
      <pubDate>Sat, 22 Apr 2023 00:00:00 +0000</pubDate><author>raiven.kao@gmail.com (Raiven Kao)</author>
      <guid>https://www.omegaatt.com/blogs/develop/2023/line_verify/</guid>
      <description>&lt;p&gt;文章中的程式碼放在&lt;a href=&#34;https://github.com/omegaatt36/line-verify-id-token&#34;&gt;https://github.com/omegaatt36/line-verify-id-token&lt;/a&gt;中。&lt;/p&gt;
&lt;h3 id=&#34;requirements&#34;&gt;Requirements&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href=&#34;https://developers.line.biz/zh-hant/&#34;&gt;Line Login Channel&lt;/a&gt;
&lt;ul&gt;
&lt;li&gt;channel id&lt;/li&gt;
&lt;li&gt;channel secret&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&lt;a href=&#34;https://github.com/line/line-liff-v2-starter&#34;&gt;Line LIFF APP&lt;/a&gt;
&lt;ul&gt;
&lt;li&gt;用 liff.getIDToken() 獲取 &lt;a href=&#34;https://developers.line.biz/en/docs/line-login/verify-id-token/#get-an-id-token&#34;&gt;ID Token&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;在現代網站中，單一登錄 (Single Sign-On, SSO) 已經成為了一個普遍存在的功能，它能夠讓使用者在不同的應用程式和服務之間自動地登錄，而不需要再輸入帳號和密碼。這樣可以方便使用者的使用，並且也能夠增加安全性，減少帳號密碼被盜用的風險。&lt;/p&gt;
&lt;p&gt;我們將使用 Golang 語言來實作單一登錄功能，並且使用 Line LIFF 來進行身份驗證。在此之前，我們需要先了解幾個概念。&lt;/p&gt;
&lt;p&gt;&lt;a href=&#34;https://developers.line.biz/en/docs/liff/overview/&#34;&gt;Line LIFF (Line Front-end Framework)&lt;/a&gt; 是一個由 Line 提供的 Web 應用程式框架，開發者可以使用它來建立 Line 的客戶端應用程式。使用 Line LIFF 開發的應用程式可以在 Line 客戶端中被直接執行，而不需要額外安裝或下載。此外，Line LIFF 還提供了一些功能，例如使用者的身份驗證、分享資料等等。&lt;/p&gt;
&lt;p&gt;JWT (JSON Web Token) 則是一種開放標準，用於在不同系統之間安全地傳輸訊息。它通常用於認證和授權，因為它可以確保傳輸的訊息是可信的，而且在傳輸過程中不會被竄改。&lt;/p&gt;
&lt;p&gt;我們可以使用 Line 的 Verify API，同時也可以選擇後端&lt;a href=&#34;https://developers.line.biz/en/docs/line-login/verify-id-token/#write-original-code&#34;&gt;自己驗證&lt;/a&gt;，解出 jwt 中的資訊。&lt;/p&gt;
&lt;p&gt;我們的目的是拿到每個使用者在 Line 的 UserID，做為身分識別。於是根據 &lt;a href=&#34;https://developers.line.biz/en/docs/line-login/verify-id-token/#payload&#34;&gt;Line 提供的 JWT 欄位對應&lt;/a&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;-webkit-text-size-adjust:none;&#34;&gt;&lt;code class=&#34;language-go&#34; data-lang=&#34;go&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#75715e&#34;&gt;// DecodedIDToken defines decoded payload by id token.&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;type&lt;/span&gt; &lt;span style=&#34;color:#a6e22e&#34;&gt;DecodedIDToken&lt;/span&gt; &lt;span style=&#34;color:#66d9ef&#34;&gt;struct&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;Amr&lt;/span&gt;       []&lt;span style=&#34;color:#66d9ef&#34;&gt;string&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;ChannelID&lt;/span&gt; &lt;span style=&#34;color:#66d9ef&#34;&gt;string&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;Email&lt;/span&gt;     &lt;span style=&#34;color:#66d9ef&#34;&gt;string&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;ExpiredAt&lt;/span&gt; &lt;span style=&#34;color:#66d9ef&#34;&gt;int64&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;IssuedAt&lt;/span&gt;  &lt;span style=&#34;color:#66d9ef&#34;&gt;int64&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;Issuer&lt;/span&gt;    &lt;span style=&#34;color:#66d9ef&#34;&gt;string&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;Name&lt;/span&gt;      &lt;span style=&#34;color:#66d9ef&#34;&gt;string&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;Picture&lt;/span&gt;   &lt;span style=&#34;color:#66d9ef&#34;&gt;string&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;UserID&lt;/span&gt;    &lt;span style=&#34;color:#66d9ef&#34;&gt;string&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;接著撰寫一個驗證 ID Token 的 function 用來驗證&lt;/p&gt;</description>
    </item>
    <item>
      <title>跟風寫了個使用 OpenAI API 的 Telegram Chat Bot</title>
      <link>https://www.omegaatt.com/blogs/develop/2023/openai_telegram_chat_bot/</link>
      <pubDate>Fri, 17 Mar 2023 00:00:00 +0000</pubDate><author>raiven.kao@gmail.com (Raiven Kao)</author>
      <guid>https://www.omegaatt.com/blogs/develop/2023/openai_telegram_chat_bot/</guid>
      <description>&lt;h1 id=&#34;前言&#34;&gt;前言&lt;/h1&gt;
&lt;p&gt;放在最前面 &lt;a href=&#34;https://github.com/omegaatt36/chatelegram&#34;&gt;Github repo&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;原本打算使用別人寫好的 bot，免費的版本均是透過儲存 cookies 的方式，直接透過 ChatGPT 網頁建立新的 「Chat」。好景不常，OpenAI 馬上把登入頁面到聊天頁面中間安插了類似 reCaptcha，來驗證是否為真人。看到有好心人提供了 go 版本的 OpenAi API，就打算自己寫一寫，順便當作 &lt;a href=&#34;https://www.tenlong.com.tw/products/9789864342945&#34;&gt;《Clean Architecture》&lt;/a&gt; 的讀後作業。&lt;/p&gt;
&lt;h1 id=&#34;依賴注入&#34;&gt;依賴注入&lt;/h1&gt;
&lt;p&gt;&lt;del&gt;架構圖待補&lt;/del&gt;&lt;/p&gt;
&lt;p&gt;我需要對 OpenAI 詢問，並用 stream(逐字)的方式回給我，也需要對 Telegram 逐字回答。&lt;/p&gt;
&lt;p&gt;在&lt;a href=&#34;https://github.com/omegaatt36/chatelegram/tree/8931bfbf9e0e00891c07aaaf0c12aa730e34dd39&#34;&gt;這個 8931bf 版本&lt;/a&gt;中，很明確定義對於 OpenAI 與 Telegram 的 usecase&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;chatgpt_usecase.go&lt;/code&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;-webkit-text-size-adjust:none;&#34;&gt;&lt;code class=&#34;language-go&#34; data-lang=&#34;go&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#75715e&#34;&gt;// ChatGPTUseCase defines ChatGPT send question use case.&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;type&lt;/span&gt; &lt;span style=&#34;color:#a6e22e&#34;&gt;ChatGPTUseCase&lt;/span&gt; &lt;span style=&#34;color:#66d9ef&#34;&gt;interface&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;Stream&lt;/span&gt;(&lt;span style=&#34;color:#a6e22e&#34;&gt;ctx&lt;/span&gt; &lt;span style=&#34;color:#a6e22e&#34;&gt;context&lt;/span&gt;.&lt;span style=&#34;color:#a6e22e&#34;&gt;Context&lt;/span&gt;, &lt;span style=&#34;color:#a6e22e&#34;&gt;question&lt;/span&gt; &lt;span style=&#34;color:#66d9ef&#34;&gt;string&lt;/span&gt;) (&lt;span style=&#34;color:#f92672&#34;&gt;&amp;lt;-&lt;/span&gt;&lt;span style=&#34;color:#66d9ef&#34;&gt;chan&lt;/span&gt; &lt;span style=&#34;color:#66d9ef&#34;&gt;string&lt;/span&gt;, &lt;span style=&#34;color:#f92672&#34;&gt;&amp;lt;-&lt;/span&gt;&lt;span style=&#34;color:#66d9ef&#34;&gt;chan&lt;/span&gt; &lt;span style=&#34;color:#66d9ef&#34;&gt;error&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;/li&gt;
&lt;li&gt;&lt;code&gt;telegram_usecase.go&lt;/code&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;-webkit-text-size-adjust:none;&#34;&gt;&lt;code class=&#34;language-go&#34; data-lang=&#34;go&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#75715e&#34;&gt;// TelegramUseCase defines telegram send message use case.&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;type&lt;/span&gt; &lt;span style=&#34;color:#a6e22e&#34;&gt;TelegramUseCase&lt;/span&gt; &lt;span style=&#34;color:#66d9ef&#34;&gt;interface&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;SendAsLiveOutput&lt;/span&gt;(&lt;span style=&#34;color:#a6e22e&#34;&gt;chatID&lt;/span&gt; &lt;span style=&#34;color:#66d9ef&#34;&gt;int64&lt;/span&gt;, &lt;span style=&#34;color:#a6e22e&#34;&gt;feed&lt;/span&gt; &lt;span style=&#34;color:#f92672&#34;&gt;&amp;lt;-&lt;/span&gt;&lt;span style=&#34;color:#66d9ef&#34;&gt;chan&lt;/span&gt; &lt;span style=&#34;color:#66d9ef&#34;&gt;string&lt;/span&gt;) &lt;span style=&#34;color:#66d9ef&#34;&gt;error&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;/li&gt;
&lt;/ul&gt;
&lt;p&gt;在 &lt;code&gt;*_repository.go&lt;/code&gt; 進行實作。並在 &lt;code&gt;main.go&lt;/code&gt; 中依賴反轉，將實作注入近 bot service。&lt;/p&gt;</description>
    </item>
    <item>
      <title>Logitech Media Server 遷移紀錄：從 Bare Metal 到 Docker 再到 Podman</title>
      <link>https://www.omegaatt.com/blogs/develop/2023/logitech_media_server_migration/</link>
      <pubDate>Fri, 24 Feb 2023 00:00:00 +0000</pubDate><author>raiven.kao@gmail.com (Raiven Kao)</author>
      <guid>https://www.omegaatt.com/blogs/develop/2023/logitech_media_server_migration/</guid>
      <description>&lt;p&gt;2021 年公司開始實施 322 WFH，少了通勤的時間，在家的時間也多了，就想在上班時也能好好對待自己，雖然是木耳但始終會想知道，網路上說的獨立訊源減少雜訊是有多重要，亦或只是玄學?於是誕生了&lt;a href=&#34;https://www.omegaatt.com/blogs/develop/2021/piCorePlayer&#34;&gt;Raspberry pi 4 + piCorePlayer 7.0.0 折騰筆記&lt;/a&gt;這篇筆記，到現在這個部落格的累積曝光最高的也是因為這篇吧。
&lt;img loading=&#34;lazy&#34; src=&#34;https://www.omegaatt.com/blogs/develop/2023/logitech_media_server_migration/images/bb4cgPLT2m.webp&#34;&gt;&lt;/p&gt;
&lt;p&gt;雖然說現在已經出到 &lt;a href=&#34;https://docs.picoreplayer.org/downloads/&#34;&gt;8.2.0&lt;/a&gt;，但也已經是 2022 年六月的版本了，起初因為 raspberry pi 4b 便宜而使用 piCorePlayer，後來也因 raspberry pi 4b 漲價(漲幅超過 100%)進而不使用 piCorePlayer，不知道官方後來沒更新了是因為樹莓派太穩定，還是真的太貴了&amp;hellip;。&lt;/p&gt;
&lt;h2 id=&#34;從-bare-metal-到-docker&#34;&gt;從 Bare Metal 到 Docker&lt;/h2&gt;
&lt;p&gt;當時在學習 HomeLab，首先是從 &lt;a href=&#34;https://www.portainer.io/&#34;&gt;portainer&lt;/a&gt; 開始玩，也因此誕生了 &lt;a href=&#34;https://www.omegaatt.com/blogs/develop/2021/logitech_media_server_with_docker&#34;&gt;logitech media server 搭配 docker 實現雙機分離&lt;/a&gt;。後來也用著同樣的 &lt;a href=&#34;https://hub.docker.com/r/lmscommunity/logitechmediaserver&#34;&gt;image&lt;/a&gt; 將 lms 建在 k8s cluster 內，遇到的比較髒的問題是 nginx 的 port &lt;a href=&#34;https://github.com/omegaatt36/lab/blob/main/k8s/ingress-nginx/tcp-services-config-map.yaml&#34;&gt;用非正式的方式解決&lt;/a&gt;。&lt;/p&gt;
&lt;p&gt;這時候其實已經不在乎 logitech media server 是否帶來更好的音質了，也沒有連動 NAS 的音樂，主要是使用 &lt;a href=&#34;https://github.com/philippe44/LMS-YouTube&#34;&gt;Youtube Plugin&lt;/a&gt; 播放 Youtube 上的內容(即便有訂閱 Youtube Premium)。以及使用 &lt;a href=&#34;https://mysqueezebox.com/appgallery/Podcasts&#34;&gt;Podcast Plugin&lt;/a&gt; 收聽 podcast。&lt;/p&gt;
&lt;p&gt;使用 docker 的好處不外乎一個字，省，於是我把 raspberry pi 4b 也給賣了。&lt;/p&gt;
&lt;h2 id=&#34;從-docker-到-podman-與-lxc&#34;&gt;從 Docker 到 Podman 與 LXC&lt;/h2&gt;
&lt;p&gt;2022 年台灣疫情大爆發，公司也從 322 變成全遠端，但也在 2022 下半年恢復 322，於 2023 年正式恢復正常進辦公室。在這期間發現 logitech media server 使用 docker 做為執行環境，除了省物理機的錢(相較 bare metal)，更省顯示卡資源。&lt;/p&gt;</description>
    </item>
    <item>
      <title>WSL2 中使用 systemd 管理 podman 的 container</title>
      <link>https://www.omegaatt.com/blogs/develop/2023/wsl2_systemd_podman/</link>
      <pubDate>Mon, 13 Feb 2023 00:00:00 +0000</pubDate><author>raiven.kao@gmail.com (Raiven Kao)</author>
      <guid>https://www.omegaatt.com/blogs/develop/2023/wsl2_systemd_podman/</guid>
      <description>&lt;p&gt;原本使用 &lt;a href=&#34;https://github.com/nullpo-head/wsl-distrod&#34;&gt;wsl-distrod&lt;/a&gt; 來作為 wsl 中 systemd 的實現方式，後來 &lt;a href=&#34;https://devblogs.microsoft.com/commandline/systemd-support-is-now-available-in-wsl/&#34;&gt;microsoft 宣布 wsl 支持 systemd&lt;/a&gt; 後，distrod 等等 repo 就沒有在更新了呢，是巧合嗎?我不這麼認為。&lt;/p&gt;
&lt;p&gt;這篇簡略紀錄 wsl2 中啟用 systemd 作為 process 管理工具以及配合紅帽推出的 &lt;a href=&#34;https://podman.io/&#34;&gt;podman&lt;/a&gt; 來取代(斷捨離)docker desktop。&lt;/p&gt;
&lt;h2 id=&#34;環境&#34;&gt;環境&lt;/h2&gt;
&lt;p&gt;wsl2 + ubuntu 22.04&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;-webkit-text-size-adjust:none;&#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;❯ neofetch
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;            .-/+oossssoo+/-.               raiven@raiven
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        &lt;span style=&#34;color:#e6db74&#34;&gt;`&lt;/span&gt;:+ssssssssssssssssss+:&lt;span style=&#34;color:#e6db74&#34;&gt;`&lt;/span&gt;           -------------
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;      -+ssssssssssssssssssyyssss+-         OS: Ubuntu 22.04.1 LTS on Windows &lt;span style=&#34;color:#ae81ff&#34;&gt;10&lt;/span&gt; x86_64
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    .ossssssssssssssssssdMMMNysssso.       Kernel: 5.10.43.3-microsoft-standard-WSL2
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;   /ssssssssssshdmmNNmmyNMMMMhssssss/      Uptime: &lt;span style=&#34;color:#ae81ff&#34;&gt;3&lt;/span&gt; hours, &lt;span style=&#34;color:#ae81ff&#34;&gt;10&lt;/span&gt; mins
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  +ssssssssshmydMMMMMMMNddddyssssssss+     Packages: &lt;span style=&#34;color:#ae81ff&#34;&gt;2005&lt;/span&gt; &lt;span style=&#34;color:#f92672&#34;&gt;(&lt;/span&gt;dpkg&lt;span style=&#34;color:#f92672&#34;&gt;)&lt;/span&gt;, &lt;span style=&#34;color:#ae81ff&#34;&gt;4&lt;/span&gt; &lt;span style=&#34;color:#f92672&#34;&gt;(&lt;/span&gt;snap&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; /sssssssshNMMMyhhyyyyhmNMMMNhssssssss/    Shell: zsh 5.8.1
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;.ssssssssdMMMNhsssssssssshNMMMdssssssss.   Theme: Adwaita &lt;span style=&#34;color:#f92672&#34;&gt;[&lt;/span&gt;GTK3&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;+sssshhhyNMMNyssssssssssssyNMMMysssssss+   Icons: Adwaita &lt;span style=&#34;color:#f92672&#34;&gt;[&lt;/span&gt;GTK3&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;ossyNMMMNyMMhsssssssssssssshmmmhssssssso   Terminal: Windows Terminal
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;ossyNMMMNyMMhsssssssssssssshmmmhssssssso   CPU: AMD Ryzen &lt;span style=&#34;color:#ae81ff&#34;&gt;9&lt;/span&gt; 5900X &lt;span style=&#34;color:#f92672&#34;&gt;(&lt;/span&gt;24&lt;span style=&#34;color:#f92672&#34;&gt;)&lt;/span&gt; @ 3.700GHz
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;+sssshhhyNMMNyssssssssssssyNMMMysssssss+   GPU: dbac:00:00.0 Microsoft Corporation Device 008e
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;.ssssssssdMMMNhsssssssssshNMMMdssssssss.   Memory: 1427MiB / 15966MiB
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt; /sssssssshNMMMyhhyyyyhdNMMMNhssssssss/
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  +sssssssssdmydMMMMMMMMddddyssssssss+
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;   /ssssssssssshdmNNNNmyNMMMMhssssss/
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    .ossssssssssssssssssdMMMNysssso.
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;      -+sssssssssssssssssyyyssss+-
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        &lt;span style=&#34;color:#e6db74&#34;&gt;`&lt;/span&gt;:+ssssssssssssssssss+:&lt;span style=&#34;color:#e6db74&#34;&gt;`&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;            .-/+oossssoo+/-.
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;h2 id=&#34;wsl2-中啟用-systemd&#34;&gt;wsl2 中啟用 systemd&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;請確保 wsl2 的版本是 version 2，可以在 cmd/powershell 中查看&lt;/p&gt;</description>
    </item>
    <item>
      <title>gin 搭配 html/template 包實現動態生成 HTML 文件</title>
      <link>https://www.omegaatt.com/blogs/develop/2023/gin_embed_practice/</link>
      <pubDate>Thu, 26 Jan 2023 00:00:00 +0000</pubDate><author>raiven.kao@gmail.com (Raiven Kao)</author>
      <guid>https://www.omegaatt.com/blogs/develop/2023/gin_embed_practice/</guid>
      <description>&lt;h2 id=&#34;起因&#34;&gt;起因&lt;/h2&gt;
&lt;p&gt;在網站註冊流程中，若是用信箱驗證，網站會寄送一封驗證信到指定的電子信箱。信中可能含有 verify token 或是直接是寫好的 verify URL。至於實作方面我們可以透過 Go 1.16 推出的 Embedding Files 搭配 html/template，實現動態生成 HTML 信件，用以寄送至指定信箱。&lt;/p&gt;
&lt;p&gt;本篇內容的詳細程式碼可以到 &lt;a href=&#34;https://github.com/omegaatt36/gin-embed-template&#34;&gt;github&lt;/a&gt; 頁面查看。&lt;/p&gt;
&lt;h2 id=&#34;實作&#34;&gt;實作&lt;/h2&gt;
&lt;p&gt;資料夾結構:&lt;/p&gt;
&lt;pre tabindex=&#34;0&#34;&gt;&lt;code&gt;project/
├─ templates/
│  ├─ template.go
│  ├─ success.tmpl
│  ├─ verify.tmpl
├─ main.go
&lt;/code&gt;&lt;/pre&gt;&lt;h3 id=&#34;templates&#34;&gt;templates&lt;/h3&gt;
&lt;blockquote&gt;
&lt;p&gt;project/templates/template.go&lt;/p&gt;
&lt;/blockquote&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;在包內宣告私有變數，透過 Embedding Files 讀出目錄內的所有檔案。&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;-webkit-text-size-adjust:none;&#34;&gt;&lt;code class=&#34;language-go&#34; data-lang=&#34;go&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#f92672&#34;&gt;package&lt;/span&gt; &lt;span style=&#34;color:#a6e22e&#34;&gt;template&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:#75715e&#34;&gt;//go:embed *&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;var&lt;/span&gt; &lt;span style=&#34;color:#a6e22e&#34;&gt;f&lt;/span&gt; &lt;span style=&#34;color:#a6e22e&#34;&gt;embed&lt;/span&gt;.&lt;span style=&#34;color:#a6e22e&#34;&gt;FS&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;宣告需要被轉換成 HTML 模板的檔案名 &lt;code&gt;TemplateName&lt;/code&gt;，可以用 &lt;a href=&#34;https://github.com/abice/go-enum&#34;&gt;go-enum&lt;/a&gt; 來自動生成變數。並將其註冊進陣列 &lt;code&gt;_TemplateNameNames&lt;/code&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;-webkit-text-size-adjust:none;&#34;&gt;&lt;code class=&#34;language-go&#34; data-lang=&#34;go&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#f92672&#34;&gt;package&lt;/span&gt; &lt;span style=&#34;color:#a6e22e&#34;&gt;template&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:#75715e&#34;&gt;// ENUM(&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:#75715e&#34;&gt;// success.tmpl&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:#75715e&#34;&gt;// verify.tmpl&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:#75715e&#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;type&lt;/span&gt; &lt;span style=&#34;color:#a6e22e&#34;&gt;TemplateName&lt;/span&gt; &lt;span style=&#34;color:#66d9ef&#34;&gt;string&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;func&lt;/span&gt; (&lt;span style=&#34;color:#a6e22e&#34;&gt;x&lt;/span&gt; &lt;span style=&#34;color:#a6e22e&#34;&gt;TemplateName&lt;/span&gt;) &lt;span style=&#34;color:#a6e22e&#34;&gt;String&lt;/span&gt;() &lt;span style=&#34;color:#66d9ef&#34;&gt;string&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; string(&lt;span style=&#34;color:#a6e22e&#34;&gt;x&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;var&lt;/span&gt; &lt;span style=&#34;color:#a6e22e&#34;&gt;_TemplateNameNames&lt;/span&gt; = []&lt;span style=&#34;color:#66d9ef&#34;&gt;string&lt;/span&gt;{
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;	string(&lt;span style=&#34;color:#a6e22e&#34;&gt;TemplateNameVerifyTmpl&lt;/span&gt;),
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;	string(&lt;span style=&#34;color:#a6e22e&#34;&gt;TemplateNameSuccessTmpl&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:#75715e&#34;&gt;// TemplateNameNames returns a list of possible string values of TemplateName.&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;func&lt;/span&gt; &lt;span style=&#34;color:#a6e22e&#34;&gt;TemplateNameNames&lt;/span&gt;() []&lt;span style=&#34;color:#66d9ef&#34;&gt;string&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;tmp&lt;/span&gt; &lt;span style=&#34;color:#f92672&#34;&gt;:=&lt;/span&gt; make([]&lt;span style=&#34;color:#66d9ef&#34;&gt;string&lt;/span&gt;, len(&lt;span style=&#34;color:#a6e22e&#34;&gt;_TemplateNameNames&lt;/span&gt;))
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;	copy(&lt;span style=&#34;color:#a6e22e&#34;&gt;tmp&lt;/span&gt;, &lt;span style=&#34;color:#a6e22e&#34;&gt;_TemplateNameNames&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:#a6e22e&#34;&gt;tmp&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&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;	&lt;span style=&#34;color:#a6e22e&#34;&gt;TemplateNameVerifyTmpl&lt;/span&gt;  &lt;span style=&#34;color:#a6e22e&#34;&gt;TemplateName&lt;/span&gt; = &lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;verify.tmpl&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;TemplateNameSuccessTmpl&lt;/span&gt; &lt;span style=&#34;color:#a6e22e&#34;&gt;TemplateName&lt;/span&gt; = &lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;success.tmpl&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;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;最終將 embed files 與檔案名丟給 &lt;code&gt;html/template&lt;/code&gt; 包請他們我們轉換成 template。&lt;/p&gt;</description>
    </item>
    <item>
      <title>後端工程師用 Golang 在 WSL 中寫 GUI 是不是搞錯了甚麼 - 安裝篇</title>
      <link>https://www.omegaatt.com/blogs/develop/2023/golang_gui_fyne_bme280/</link>
      <pubDate>Thu, 26 Jan 2023 00:00:00 +0000</pubDate><author>raiven.kao@gmail.com (Raiven Kao)</author>
      <guid>https://www.omegaatt.com/blogs/develop/2023/golang_gui_fyne_bme280/</guid>
      <description>&lt;p&gt;由於先前將 raspberry pi 4b 給賣掉了，&lt;a href=&#34;https://github.com/omegaatt36/bmx280/tree/main/raspberrypi&#34;&gt;bmx280&lt;/a&gt; 中基於 bme280 而產生的 exporter 因此失去戰鬥能力，藉此玩玩 &lt;a href=&#34;https://tinygo.org/&#34;&gt;tinygo&lt;/a&gt;，透過 serial port 透過 raspberry pi pico 對 bme280 讀取溫溼度。在開發過程中發現，WSL 中，command line 輸入:&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;-webkit-text-size-adjust:none;&#34;&gt;&lt;code class=&#34;language-shell&#34; data-lang=&#34;shell&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;GOOS&lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt;windows go run main.go
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;這樣會吃 windows 的環境，比如讀 serial port 需要 COM port，這樣就不是去讀 /dev/ttyS4 而是讀 COM4，這 cross compile 也太方便了吧&amp;hellip;。&lt;/p&gt;
&lt;p&gt;後續在工作上接收了需要寫一個 websocket server/client，而 client 需要:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;有圖形化最好 (TUI or GUI)&lt;/li&gt;
&lt;li&gt;跑在 windows 桌面上&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;，開發過程中再次感受到 cross compile 的方便，有點慶幸是在這個一個方便的時代學習敲鍵盤(?&lt;/p&gt;
&lt;h2 id=&#34;requirement&#34;&gt;Requirement&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;Golang 1.16+ ()&lt;/li&gt;
&lt;li&gt;Docker or Podman&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id=&#34;在-wsl-中建立-x-window-system-環境&#34;&gt;在 WSL 中建立 X Window System 環境&lt;/h2&gt;
&lt;p&gt;因為我的 Golang 環境是基於 WSL，於是我需要先準備在 WSL 中能跑的 GUI 環境。
在 Unix like 使用的 GUI 被稱之為 &lt;a href=&#34;https://en.wikipedia.org/wiki/X_Window_System&#34;&gt;X Window System 或 X11 或 X&lt;/a&gt;，以所需的情形來說即是需要在 Windows 中啟動 X server，來與 WSL 中的 X client 連線。&lt;/p&gt;</description>
    </item>
    <item>
      <title>解決 VM 關機時等待容器關閉</title>
      <link>https://www.omegaatt.com/blogs/develop/2023/k3s_kill_all/</link>
      <pubDate>Wed, 11 Jan 2023 00:00:00 +0000</pubDate><author>raiven.kao@gmail.com (Raiven Kao)</author>
      <guid>https://www.omegaatt.com/blogs/develop/2023/k3s_kill_all/</guid>
      <description>&lt;pre tabindex=&#34;0&#34;&gt;&lt;code&gt;[     *] A stop job is running for libcontainer container xxxxxxxx (10s / 1m30s)_
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;過去 kubernetes 還跟 docker-shim 手牽手時，在關閉 k8s node vm 時常常出現等待 &lt;code&gt;docker-shim&lt;/code&gt; 關閉，直到一分三十秒被 time out 才正確關機。若是開發機就當作去尿尿的時間就好，但若是重要的環境停機備份，或是斷電時靠 UPS 提供電源等待系統關機，那浪費時間就不好了。&lt;/p&gt;
&lt;p&gt;這次在 home lab 中採取省電措施，不用 &lt;a href=&#34;https://www.omegaatt.com/blogs/develop/2022/centos-7-kubernetes-install.html&#34;&gt;1 master + 1 node&lt;/a&gt; 的組合，直接使用 &lt;a href=&#34;https://k3s.io/&#34;&gt;k3s&lt;/a&gt; 作為家用 kubernetes 的實驗環境。但依然在關機時會需要等待 containerd-shim 等等被 time out kill 才會完成關機。&lt;/p&gt;
&lt;p&gt;若是沒有做特別的設定，或是沒有過分的 stateful pod，找到了 &lt;a href=&#34;https://github.com/k3s-io/k3s/issues/2400#issuecomment-1312621468&#34;&gt;k3s issue #2400&lt;/a&gt; 中的解決方法&lt;/p&gt;
&lt;p&gt;&lt;a href=&#34;https://docs.k3s.io/upgrades/killall&#34;&gt;Stopping K3s&lt;/a&gt;&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;To allow high availability during upgrades, the K3s containers continue running when the K3s service is stopped.&lt;/p&gt;</description>
    </item>
    <item>
      <title>2023 展望</title>
      <link>https://www.omegaatt.com/blogs/develop/2023/2023_outlook/</link>
      <pubDate>Sun, 01 Jan 2023 00:00:00 +0000</pubDate><author>raiven.kao@gmail.com (Raiven Kao)</author>
      <guid>https://www.omegaatt.com/blogs/develop/2023/2023_outlook/</guid>
      <description>&lt;p&gt;TL;DR&lt;/p&gt;
&lt;p&gt;今年還沒躺平。&lt;/p&gt;
&lt;hr&gt;
&lt;blockquote&gt;
&lt;p&gt;你不是&lt;strong&gt;喜歡&lt;/strong&gt;寫程式的人，是用程式吃飯的。&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;年前 CEO 這麼對我說，後面接著一段話:&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;像我跟他(指主管)是喜歡寫程式的，沒人打擾的話我們可以在家一直玩這些。&lt;/p&gt;
&lt;/blockquote&gt;
&lt;hr&gt;
&lt;p&gt;跟主管借了&lt;a href=&#34;https://www.tenlong.com.tw/products/9789864342945&#34;&gt;《Clean Architecture》&lt;/a&gt;來看，每次在看軟體「工程」的書時，都備感挫折。&lt;a href=&#34;https://www.tenlong.com.tw/products/9789861818283&#34;&gt;《程式設計師的自我修養－連結、載入、程式庫》&lt;/a&gt; 中提到的編譯器、連結器，我壓根就沒學過，這種滿滿的挫折感，在 Clean Architecture 中得到了解答。&lt;/p&gt;
&lt;p&gt;台灣的&lt;strong&gt;資訊系&lt;/strong&gt;在國外的最正統名稱是 &lt;strong&gt;Computer Science&lt;/strong&gt; a.k.a. &lt;strong&gt;C.S.&lt;/strong&gt;，而我的學士學位是資訊管理 &lt;strong&gt;Department of Information Management&lt;/strong&gt;，我們接受的教育是怎麼去快速理解 domain，並在運用該領域的領域知識開發輔助用的資訊系統，課程著重在 ERP、CRM、KMS 等等資訊管理系統(MIS)。作業系統課程不會教你減少分支預測帶來的好處，資料庫管理課程不會教 Lock Level 把整張表鎖起來會被罵，資料結構課程不會教你怎麼用程式實作 linked list、tree，程式設計課程更不會去看你的時間/空間複雜度。&lt;/p&gt;
&lt;p&gt;或許也只能怪自己在念書時得過且過，讓現在的自己需要花時間去補其他人早就知道的&lt;strong&gt;知識&lt;/strong&gt;。&lt;/p&gt;
&lt;hr&gt;
&lt;p&gt;似乎也是因為這種挫折感給了自己莫大的動力，不想輸給「本科系」的學生，雖然已經 25 歲了，但及早發現及早治療吧?過去總是喜歡把不理解的部分都弄懂才往下一步走，也是慢慢地改變自己且戰且走、以戰養戰。&lt;/p&gt;
&lt;p&gt;舉例來說，如果要把整個公司專案的程式碼、技術債的原因、神秘數字的由來、歷史共業等等，全部都摸熟才能開始寫 code，那可能就會失去許多成長的機會。&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;工作不像在學校，不會要求你每次都要一百分，應該是先做出 60 分，甚至 40 分，再慢慢迭代成 100 分。&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;CEO 如此說著，也符合軟體開發流程迭代機制，先拿 prototype 去撈錢再來修(?)。&lt;/p&gt;
&lt;p&gt;除了有些設計原則是需要最初就先講好比較好(e.g. Cloud Native, API First)，似乎沒有甚麼是「非這麼做不可」，更多的是「不要這麼做比較好」。&lt;/p&gt;
&lt;hr&gt;
&lt;p&gt;言歸正傳，對 2023 年的展望呢，希望身體健康，也希望能拉近與同儕間的距離，多少不想輸給碩畢的同學XD。&lt;/p&gt;</description>
    </item>
    <item>
      <title>在 AWS 上使用 EC2 建立 FRP 玩玩內網穿透</title>
      <link>https://www.omegaatt.com/blogs/develop/2022/frp-tunnel/</link>
      <pubDate>Sat, 19 Nov 2022 00:00:00 +0000</pubDate><author>raiven.kao@gmail.com (Raiven Kao)</author>
      <guid>https://www.omegaatt.com/blogs/develop/2022/frp-tunnel/</guid>
      <description>&lt;h1 id=&#34;前言&#34;&gt;前言&lt;/h1&gt;
&lt;p&gt;TL;DR&lt;/p&gt;
&lt;p&gt;2022 年上旬熱衷於軟路由，在 Raspberry Pi 4b 上裝了 openWRT 來玩，使用 USB 網卡作為 LAN NIC，雖然使用上完全沒有問題，但透過 USB 始終有個芥蒂，「就不能多個 pci-e 來接網卡嗎」，於是衝動順勢買了 Mikrotik RB5009UG+S+IN。&lt;/p&gt;
&lt;p&gt;&lt;a href=&#34;https://mikrotik.com/product/rb5009ug_s_in&#34;&gt;RB5009G&lt;/a&gt; 為 arm64(ARMv8)，系統為 RouterOS v7，透過 &lt;a href=&#34;https://help.mikrotik.com/docs/display/ROS/ZeroTier&#34;&gt;ZeroTier&lt;/a&gt; 插件可以實現內網穿透以及 VPN，一直以來可在外連回家裡做事，連 Jellyfin 使用起來都很順。&lt;/p&gt;
&lt;p&gt;事出必有因，ZeroTier 的服務器在未知的地方，VPN 的設定也是在網頁上設定，即便不需要太過擔心，但若是 ZeroTier 被攻擊，那自己的內網也危險了。後來又遇到一次，在某處透過 Windows rdp 連回家開 VScode 做事，但敲鍵盤會有一到兩秒的延遲，加劇了需要替換掉 ZeroTier 的想法。&lt;/p&gt;
&lt;p&gt;&lt;a href=&#34;https://github.com/fatedier/frp&#34;&gt;frp&lt;/a&gt; 是一個由 golang 寫出來的 reverse proxy&amp;hellip;，其他的到 github 看比較清楚 XD。&lt;/p&gt;
&lt;p&gt;這篇文章主要是透過 frp 建立內網穿透，雖然大部分都在建立 AWS EC2 instance，frp 的建立實在是非常簡單，VPN (maybe WireGuard?)的實現會另外再寫一篇。&lt;/p&gt;
&lt;h1 id=&#34;事前準備&#34;&gt;事前準備&lt;/h1&gt;
&lt;ul&gt;
&lt;li&gt;一個 AWS 帳號&lt;/li&gt;
&lt;li&gt;一台 linux 主機作為 frp client&lt;/li&gt;
&lt;/ul&gt;
&lt;h1 id=&#34;建立-amazon-ec2&#34;&gt;建立 Amazon EC2&lt;/h1&gt;
&lt;h2 id=&#34;建立執行個體&#34;&gt;建立執行個體&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;建立好帳號後，進入 &lt;a href=&#34;https://console.aws.amazon.com/console/&#34;&gt;AWS console&lt;/a&gt;，點擊 EC2。
&lt;img loading=&#34;lazy&#34; src=&#34;https://www.omegaatt.com/blogs/develop/2022/frp-tunnel/images/chrome_lk5fqYZhYL.webp&#34;&gt;&lt;/li&gt;
&lt;li&gt;點擊啟動執行個體
&lt;img loading=&#34;lazy&#34; src=&#34;https://www.omegaatt.com/blogs/develop/2022/frp-tunnel/images/chrome_H0uFz5vH3y.webp&#34;&gt;&lt;/li&gt;
&lt;li&gt;取名為 frp-tunnel
&lt;img loading=&#34;lazy&#34; src=&#34;https://www.omegaatt.com/blogs/develop/2022/frp-tunnel/images/chrome_I3wkK7askV.webp&#34;&gt;&lt;/li&gt;
&lt;li&gt;選用自己習慣的 linux distro，這邊我習慣使用 ubuntu，故選 ubuntu 22.04。
&lt;img loading=&#34;lazy&#34; src=&#34;https://www.omegaatt.com/blogs/develop/2022/frp-tunnel/images/chrome_z5KPS18q6J.webp&#34;&gt;&lt;/li&gt;
&lt;li&gt;執行個體類型選 t2.micro，金鑰由於安全因素，請建立新的金鑰對(key pair)
&lt;img loading=&#34;lazy&#34; src=&#34;https://www.omegaatt.com/blogs/develop/2022/frp-tunnel/images/chrome_8Cfa1EOR9c.webp&#34;&gt;&lt;/li&gt;
&lt;li&gt;由於我使用 ubuntu 22.04，故使用 ED25519 來加密(RSA 已被淘汰)。下載下來的 pem or ppk 需要好好保存，未來不能再下載。
&lt;img loading=&#34;lazy&#34; src=&#34;https://www.omegaatt.com/blogs/develop/2022/frp-tunnel/images/chrome_VzVIcfHUg0.webp&#34;&gt;&lt;/li&gt;
&lt;li&gt;網路設定的部分，ssh 的 source 使用&lt;code&gt;我的IP&lt;/code&gt;，不要一開機就被陌生人打穿。
&lt;img loading=&#34;lazy&#34; src=&#34;https://www.omegaatt.com/blogs/develop/2022/frp-tunnel/images/chrome_Qx4K4tK1jT.webp&#34;&gt;&lt;/li&gt;
&lt;li&gt;設定存儲的部分，若是使用 AWS Linux 的話可以預設 8GB 沒問題，我自己的 ubuntu vm 習慣 20GB，所以設成 20GB。
&lt;img loading=&#34;lazy&#34; src=&#34;https://www.omegaatt.com/blogs/develop/2022/frp-tunnel/images/chrome_Pu7Weq1NXU.webp&#34;&gt;&lt;/li&gt;
&lt;li&gt;最後確認一下右邊的摘要，就可以點擊啟動執行個體。
&lt;img loading=&#34;lazy&#34; src=&#34;https://www.omegaatt.com/blogs/develop/2022/frp-tunnel/images/chrome_dJxs8LK4lG.webp&#34;&gt;&lt;/li&gt;
&lt;li&gt;建立完成後就會轉跳至完成頁面。
&lt;img loading=&#34;lazy&#34; src=&#34;https://www.omegaatt.com/blogs/develop/2022/frp-tunnel/images/chrome_TRIPSPPEva.webp&#34;&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id=&#34;ssh-連線至-ec2&#34;&gt;ssh 連線至 EC2&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;在 console 頁可以看到剛剛建立的 EC2 instance，點擊可以進入 instance 頁面。
&lt;img loading=&#34;lazy&#34; src=&#34;https://www.omegaatt.com/blogs/develop/2022/frp-tunnel/images/chrome_JuQsMOCLAR.webp&#34;&gt;&lt;/p&gt;</description>
    </item>
    <item>
      <title>zsh &amp; oh-my-zsh &amp; tmux install cheat sheet on ubuntu 22.04</title>
      <link>https://www.omegaatt.com/blogs/develop/2022/zsh-cheat-sheet/</link>
      <pubDate>Sun, 30 Oct 2022 00:00:00 +0000</pubDate><author>raiven.kao@gmail.com (Raiven Kao)</author>
      <guid>https://www.omegaatt.com/blogs/develop/2022/zsh-cheat-sheet/</guid>
      <description>&lt;h2 id=&#34;oh-my-zsh--power-lever-10k&#34;&gt;oh my zsh &amp;amp; power lever 10k&lt;/h2&gt;
&lt;ol start=&#34;0&#34;&gt;
&lt;li&gt;
&lt;p&gt;install deps&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;-webkit-text-size-adjust:none;&#34;&gt;&lt;code class=&#34;language-shell&#34; data-lang=&#34;shell&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;sudo apt install git curl -y
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;install zsh&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;-webkit-text-size-adjust:none;&#34;&gt;&lt;code class=&#34;language-shell&#34; data-lang=&#34;shell&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;sudo apt install zsh -y
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;check is installed&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;-webkit-text-size-adjust:none;&#34;&gt;&lt;code class=&#34;language-shell&#34; data-lang=&#34;shell&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;cat /etc/shells | grep zsh
&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:#75715e&#34;&gt;# raiven@k3s:~$ cat /etc/shells | grep zsh&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:#75715e&#34;&gt;# /bin/zsh&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:#75715e&#34;&gt;# /usr/bin/zsh&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;install &lt;a href=&#34;https://github.com/ohmyzsh/ohmyzsh#basic-installation&#34;&gt;oh-my-zsh&lt;/a&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;-webkit-text-size-adjust:none;&#34;&gt;&lt;code class=&#34;language-shell&#34; data-lang=&#34;shell&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;sh -c &lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;&lt;/span&gt;&lt;span style=&#34;color:#66d9ef&#34;&gt;$(&lt;/span&gt;curl -fsSL https://raw.githubusercontent.com/ohmyzsh/ohmyzsh/master/tools/install.sh&lt;span style=&#34;color:#66d9ef&#34;&gt;)&lt;/span&gt;&lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;install &lt;a href=&#34;https://github.com/romkatv/powerlevel10k#oh-my-zsh&#34;&gt;powerlevel10k&lt;/a&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;-webkit-text-size-adjust:none;&#34;&gt;&lt;code class=&#34;language-shell&#34; data-lang=&#34;shell&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;git clone --depth&lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt;&lt;span style=&#34;color:#ae81ff&#34;&gt;1&lt;/span&gt; https://github.com/romkatv/powerlevel10k.git &lt;span style=&#34;color:#e6db74&#34;&gt;${&lt;/span&gt;ZSH_CUSTOM&lt;span style=&#34;color:#66d9ef&#34;&gt;:-&lt;/span&gt;$HOME/.oh-my-zsh/custom&lt;span style=&#34;color:#e6db74&#34;&gt;}&lt;/span&gt;/themes/powerlevel10k
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;install fonts:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;wsl:
&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;-webkit-text-size-adjust:none;&#34;&gt;&lt;code class=&#34;language-sh&#34; data-lang=&#34;sh&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;bash -c &lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;&lt;/span&gt;&lt;span style=&#34;color:#66d9ef&#34;&gt;$(&lt;/span&gt;curl -fsSL https://raw.githubusercontent.com/omegaatt36/dotfiles/main/install_fonts.sh&lt;span style=&#34;color:#66d9ef&#34;&gt;)&lt;/span&gt;&lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;&lt;/span&gt; --windows
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/li&gt;
&lt;li&gt;linux:
&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;-webkit-text-size-adjust:none;&#34;&gt;&lt;code class=&#34;language-sh&#34; data-lang=&#34;sh&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;bash -c &lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;&lt;/span&gt;&lt;span style=&#34;color:#66d9ef&#34;&gt;$(&lt;/span&gt;curl -fsSL https://raw.githubusercontent.com/omegaatt36/dotfiles/main/install_fonts.sh&lt;span style=&#34;color:#66d9ef&#34;&gt;)&lt;/span&gt;&lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;change login shell(must logout and login again)&lt;/p&gt;</description>
    </item>
    <item>
      <title>proxmox ve shrink vm disk size</title>
      <link>https://www.omegaatt.com/blogs/develop/2022/pve-shrink-vm-disk/</link>
      <pubDate>Sun, 04 Sep 2022 00:00:00 +0000</pubDate><author>raiven.kao@gmail.com (Raiven Kao)</author>
      <guid>https://www.omegaatt.com/blogs/develop/2022/pve-shrink-vm-disk/</guid>
      <description>&lt;p&gt;由於 zpool 吃超過 80%，故將其中一個 VM(k8s-worker) 的硬碟縮小(200GB=&amp;gt;100GB)&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;pve: 7.2.7&lt;/li&gt;
&lt;li&gt;zfs:
&lt;ul&gt;
&lt;li&gt;zfs-2.1.5-pve1&lt;/li&gt;
&lt;li&gt;zfs-kmod-2.1.5-pve1&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;ol&gt;
&lt;li&gt;
&lt;p&gt;確認 vm disk 剩餘空間&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;-webkit-text-size-adjust:none;&#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;$ df -h
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;Filesystem      Size  Used Avail Use% Mounted on
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;/dev/vda1        197G   14G   178G  7% /
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;vm 關機&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;下載 &lt;a href=&#34;https://sourceforge.net/projects/gparted/&#34;&gt;gparded iso&lt;/a&gt; 到 pve&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;vm 從 gparded iso 開機並&lt;a href=&#34;https://www.howtogeek.com/114503/how-to-resize-your-ubuntu-partitions/&#34;&gt;縮減 disk 大小&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;pve server 針對 vm 的 hard disk 調整占用大小
switch(allocate storage type)&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;case LV:
&lt;code&gt;lvreduce -L 5G /dev/pve/disk-name (縮小到只剩 5G)&lt;/code&gt;
or
&lt;code&gt;lvreduce -L -5G /dev/pve/disk-name (縮小 5G)&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;case qcow2:
&lt;code&gt;qemu-img resize --shrink &amp;lt;vmfile.qcow2&amp;gt; [+-] or size&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;case ZFS:
&lt;code&gt;zfs set volsize=&amp;lt;new size&amp;gt;G rpool/vm-&amp;lt;vm id&amp;gt;-disk-&amp;lt;disk number&amp;gt;&lt;/code&gt;
舉例:
vm(&lt;code&gt;205&lt;/code&gt;) 的虛擬硬碟(&lt;code&gt;vm-205-disk-0&lt;/code&gt;)放在 zpool &lt;code&gt;land&lt;/code&gt; 上面，已經將 vm 縮小至 100GB
&lt;pre tabindex=&#34;0&#34;&gt;&lt;code&gt;❯ zfs list
NAME USED AVAIL REFER MOUNTPOINT
land/vm-205-disk-0 236G 354G 119.3G -

    ❯ zfs set volsize=100G land/vm-205-disk-0

    ❯ zfs list
    NAME                     USED  AVAIL     REFER  MOUNTPOINT
    land/vm-205-disk-0       136G   354G     19.3G  -
    ```
&lt;/code&gt;&lt;/pre&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;在網頁 GUI 中修改強制刷新 disk。修改 disk 的設定，再調整回來
&lt;img loading=&#34;lazy&#34; src=&#34;https://md.stranity.org/uploads/upload_d7fbc8d92382438b1b485a23f3e008e9.png&#34;&gt;&lt;/p&gt;</description>
    </item>
    <item>
      <title>CentOS 7 kubernetes &#43; containerd &#43; calico basic installation tutorial</title>
      <link>https://www.omegaatt.com/blogs/develop/2022/centos-7-kubernetes-install/</link>
      <pubDate>Sun, 20 Feb 2022 00:00:00 +0000</pubDate><author>raiven.kao@gmail.com (Raiven Kao)</author>
      <guid>https://www.omegaatt.com/blogs/develop/2022/centos-7-kubernetes-install/</guid>
      <description>&lt;h1 id=&#34;前言&#34;&gt;前言&lt;/h1&gt;
&lt;p&gt;鑒於最近接到 1 CentOS master + 2 Gentoo node k8s cluster 建置雜事，被自己不熟系統雷到，做個筆記紀錄一下，未來敲敲指令就可以了&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;-webkit-text-size-adjust:none;&#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;                 ..                    root@master
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;               .PLTJ.                  -----------
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;              &amp;lt;&amp;gt;&amp;lt;&amp;gt;&amp;lt;&amp;gt;&amp;lt;&amp;gt;                 OS: CentOS Linux &lt;span style=&#34;color:#ae81ff&#34;&gt;7&lt;/span&gt; &lt;span style=&#34;color:#f92672&#34;&gt;(&lt;/span&gt;Core&lt;span style=&#34;color:#f92672&#34;&gt;)&lt;/span&gt; x86_64
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;     KKSSV&lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#39; 4KKK LJ KKKL.&amp;#39;&lt;/span&gt;VSSKK        Host: KVM/QEMU &lt;span style=&#34;color:#f92672&#34;&gt;(&lt;/span&gt;Standard PC &lt;span style=&#34;color:#f92672&#34;&gt;(&lt;/span&gt;i440FX + PIIX, 1996&lt;span style=&#34;color:#f92672&#34;&gt;)&lt;/span&gt; pc-i440fx-6.1&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;     KKV&lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#39; 4KKKKK LJ KKKKAL &amp;#39;&lt;/span&gt;VKK        Kernel: 5.4.180-1.el7.elrepo.x86_64
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;     V&lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#39; &amp;#39;&lt;/span&gt; &lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#39;VKKKK LJ KKKKV&amp;#39;&lt;/span&gt; &lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#39; &amp;#39;&lt;/span&gt;V        Uptime: &lt;span style=&#34;color:#ae81ff&#34;&gt;2&lt;/span&gt; mins
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;     .4MA.&lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#39; &amp;#39;&lt;/span&gt;VKK LJ KKV&lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#39; &amp;#39;&lt;/span&gt;.4Mb.        Packages: &lt;span style=&#34;color:#ae81ff&#34;&gt;359&lt;/span&gt; &lt;span style=&#34;color:#f92672&#34;&gt;(&lt;/span&gt;rpm&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;   . KKKKKA.&lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#39; &amp;#39;&lt;/span&gt;V LJ V&lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#39; &amp;#39;&lt;/span&gt;.4KKKKK .      Shell: bash 4.2.46
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt; .4D KKKKKKKA.&lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#39;&amp;#39;&lt;/span&gt; LJ &lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#39;&amp;#39;&lt;/span&gt;.4KKKKKKK FA.    Terminal: /dev/pts/0
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&amp;lt;QDD ++++++++++++  ++++++++++++ GFD&amp;gt;   CPU: Common KVM processor &lt;span style=&#34;color:#f92672&#34;&gt;(&lt;/span&gt;4&lt;span style=&#34;color:#f92672&#34;&gt;)&lt;/span&gt; @ 3.493GHz
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt; &lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#39;VD KKKKKKKK&amp;#39;&lt;/span&gt;.. LJ ..&lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#39;KKKKKKKK FV     Memory: 97MiB / 16015MiB
&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:#e6db74&#34;&gt;   &amp;#39;&lt;/span&gt; VKKKKK&lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#39;. .4 LJ K. .&amp;#39;&lt;/span&gt;KKKKKV &lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#39;
&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:#e6db74&#34;&gt;      &amp;#39;&lt;/span&gt;VK&lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#39;. .4KK LJ KKA. .&amp;#39;&lt;/span&gt;KV&lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#39;
&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:#e6db74&#34;&gt;     A. . .4KKKK LJ KKKKA. . .4
&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:#e6db74&#34;&gt;     KKA. &amp;#39;&lt;/span&gt;KKKKK LJ KKKKK&lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#39; .4KK
&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:#e6db74&#34;&gt;     KKSSA. VKKK LJ KKKV .4SSKK
&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:#e6db74&#34;&gt;              &amp;lt;&amp;gt;&amp;lt;&amp;gt;&amp;lt;&amp;gt;&amp;lt;&amp;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:#e6db74&#34;&gt;               &amp;#39;&lt;/span&gt;MKKM&lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#39;
&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:#e6db74&#34;&gt;                 &amp;#39;&lt;/span&gt;&lt;span style=&#34;color:#960050;background-color:#1e0010&#34;&gt;&amp;#39;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;此篇 CentOS 寄宿於 PVE 下，kernel 已升級為 &lt;code&gt;5.4.180-1.el7.elrepo.x86_64&lt;/code&gt;
使用 containerd 作為 cri
使用 calico 作為 cni，並使用 host 唯一的網卡，不做其他進階設定。&lt;/p&gt;</description>
    </item>
    <item>
      <title>CentOS 7 upgrade kernel</title>
      <link>https://www.omegaatt.com/blogs/develop/2022/centos-7-upgrade-kernel/</link>
      <pubDate>Sun, 20 Feb 2022 00:00:00 +0000</pubDate><author>raiven.kao@gmail.com (Raiven Kao)</author>
      <guid>https://www.omegaatt.com/blogs/develop/2022/centos-7-upgrade-kernel/</guid>
      <description>&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;查看目前 kernel 版本&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;-webkit-text-size-adjust:none;&#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;&amp;gt; uname -a
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;Linux R350-1 3.10.0-1160.53.1.el7.x86_64 &lt;span style=&#34;color:#75715e&#34;&gt;#1 SMP Fri Jan 14 13:59:45 UTC 2022 x86_64 x86_64 x86_64 GNU/Linux&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;添加 ELRepo 公鑰&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;-webkit-text-size-adjust:none;&#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;rpm --import https://www.elrepo.org/RPM-GPG-KEY-elrepo.org
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;安裝 ELRepo yum 來源&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;-webkit-text-size-adjust:none;&#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;rpm -Uvh https://www.elrepo.org/elrepo-release-7.0-4.el7.elrepo.noarch.rpm
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;查看可用的 kernel&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;-webkit-text-size-adjust:none;&#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;yum --disablerepo&lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt;&lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;*&amp;#34;&lt;/span&gt; --enablerepo&lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt;&lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;elrepo-kernel&amp;#34;&lt;/span&gt; list available
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;安裝最新的 kernel，挑一個安裝，這邊選擇 kernel-lt&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;kernel-lt 為長期支援版&lt;/li&gt;
&lt;li&gt;kernel-mt 為 linus 個人維護版&lt;/li&gt;
&lt;/ul&gt;
&lt;pre tabindex=&#34;0&#34;&gt;&lt;code&gt;yum --enablerepo=elrepo-kernel install kernel-lt
&lt;/code&gt;&lt;/pre&gt;&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;查看目前已安裝的 kernel&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;-webkit-text-size-adjust:none;&#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 awk -F&lt;span style=&#34;color:#ae81ff&#34;&gt;\&amp;#39;&lt;/span&gt; &lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#39;$1==&amp;#34;menuentry &amp;#34; {print i++ &amp;#34; : &amp;#34; $2}&amp;#39;&lt;/span&gt; /etc/grub2.cfg
&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:#ae81ff&#34;&gt;0&lt;/span&gt; : CentOS Linux &lt;span style=&#34;color:#f92672&#34;&gt;(&lt;/span&gt;5.4.180-1.el7.elrepo.x86_64&lt;span style=&#34;color:#f92672&#34;&gt;)&lt;/span&gt; &lt;span style=&#34;color:#ae81ff&#34;&gt;7&lt;/span&gt; &lt;span style=&#34;color:#f92672&#34;&gt;(&lt;/span&gt;Core&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:#ae81ff&#34;&gt;1&lt;/span&gt; : CentOS Linux &lt;span style=&#34;color:#f92672&#34;&gt;(&lt;/span&gt;3.10.0-1160.53.1.el7.x86_64&lt;span style=&#34;color:#f92672&#34;&gt;)&lt;/span&gt; &lt;span style=&#34;color:#ae81ff&#34;&gt;7&lt;/span&gt; &lt;span style=&#34;color:#f92672&#34;&gt;(&lt;/span&gt;Core&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:#ae81ff&#34;&gt;2&lt;/span&gt; : CentOS Linux &lt;span style=&#34;color:#f92672&#34;&gt;(&lt;/span&gt;3.10.0-1160.el7.x86_64&lt;span style=&#34;color:#f92672&#34;&gt;)&lt;/span&gt; &lt;span style=&#34;color:#ae81ff&#34;&gt;7&lt;/span&gt; &lt;span style=&#34;color:#f92672&#34;&gt;(&lt;/span&gt;Core&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:#ae81ff&#34;&gt;3&lt;/span&gt; : CentOS Linux &lt;span style=&#34;color:#f92672&#34;&gt;(&lt;/span&gt;0-rescue-b804ce66fb404eb7a5dd04547e3e972e&lt;span style=&#34;color:#f92672&#34;&gt;)&lt;/span&gt; &lt;span style=&#34;color:#ae81ff&#34;&gt;7&lt;/span&gt; &lt;span style=&#34;color:#f92672&#34;&gt;(&lt;/span&gt;Core&lt;span style=&#34;color:#f92672&#34;&gt;)&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;如果結果為空，或是沒有這個文件，可以先進行 &lt;code&gt;grub2-mkconfig -o /boot/grub2/grub.cfg&lt;/code&gt;&lt;/p&gt;</description>
    </item>
    <item>
      <title>改善 Proxmox VE/Debian 始終跑在最高頻率</title>
      <link>https://www.omegaatt.com/blogs/develop/2022/pve_power-save/</link>
      <pubDate>Sun, 09 Jan 2022 00:00:00 +0000</pubDate><author>raiven.kao@gmail.com (Raiven Kao)</author>
      <guid>https://www.omegaatt.com/blogs/develop/2022/pve_power-save/</guid>
      <description>&lt;p&gt;這幾天把便宜撿到的 Threadripper 2950X 平台也上 Proxmox VE 玩玩了，裝完系統後才發現自己太習慣於 windows 下的電源管理，一直都沒發現 linux 下 CPU 頻率都是拉滿的狀態，找了&lt;a href=&#34;https://forum.proxmox.com/threads/fix-always-high-cpu-frequency-in-proxmox-host.84270/&#34;&gt;debian 下進行電源管理的電源計畫設定教學&lt;/a&gt;達到降溫省電，順便做做紀錄。&lt;/p&gt;
&lt;p&gt;此篇文章的硬體基於&lt;/p&gt;
&lt;pre tabindex=&#34;0&#34;&gt;&lt;code&gt;root@raiven:~# neofetch
       _,met$$$$$gg.          root@raiven
    ,g$$$$$$$$$$$$$$$P.       -----------
  ,g$$P&amp;#34;     &amp;#34;&amp;#34;&amp;#34;Y$$.&amp;#34;.        OS: Debian GNU/Linux 10 (buster) x86_64
 ,$$P&amp;#39;              `$$$.     Host: HP Z2 SFF G4 Workstation
&amp;#39;,$$P       ,ggs.     `$$b:   Kernel: 5.4.106-1-pve
`d$$&amp;#39;     ,$P&amp;#34;&amp;#39;   .    $$$    Uptime: 276 days, 9 hours, 51 mins
 $$P      d$&amp;#39;     ,    $$P    Packages: 719 (dpkg)
 $$:      $$.   -    ,d$$&amp;#39;    Shell: bash 5.0.3
 $$;      Y$b._   _,d$P&amp;#39;      Terminal: /dev/pts/1
 Y$$.    `.`&amp;#34;Y$$$$P&amp;#34;&amp;#39;         CPU: Intel Xeon E-2278G (16) @ 5.000GHz
 `$$b      &amp;#34;-.__              GPU: Intel Device 3e9a
  `Y$$                        Memory: 48763MiB / 64099MiB
   `Y$$.
     `$$b.
       `Y$$b.
          `&amp;#34;Y$b._
              `&amp;#34;&amp;#34;&amp;#34;

root@raiven:~# ^C
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;用 &lt;code&gt;watch -n 1 &amp;quot;cat /proc/cpuinfo | grep MHz&amp;quot;&lt;/code&gt; 可以查看當下的 cpu 頻率狀態。&lt;/p&gt;</description>
    </item>
    <item>
      <title>logitech media server 搭配 docker 實現雙機分離</title>
      <link>https://www.omegaatt.com/blogs/develop/2021/logitech_media_server_with_docker/</link>
      <pubDate>Sat, 04 Dec 2021 00:00:00 +0000</pubDate><author>raiven.kao@gmail.com (Raiven Kao)</author>
      <guid>https://www.omegaatt.com/blogs/develop/2021/logitech_media_server_with_docker/</guid>
      <description>&lt;h1 id=&#34;前言&#34;&gt;前言&lt;/h1&gt;
&lt;p&gt;雖然&lt;a href=&#34;https://docs.picoreplayer.org/releases/&#34;&gt;官方 releases 頁面&lt;/a&gt;似乎還沒發布，不過前一陣子把 piCorePlayer 升級到 8.1.0，看起來似乎還是 &lt;a href=&#34;https://forums.slimdevices.com/showthread.php?113353-Players-don-t-play-!-After-upgrading-8-0-1-to-8-1-0&#34;&gt;unstable&lt;/a&gt;，懶得倒版回去 8.0.0，趁這個機會把一直想玩的雙機留兜起來。&lt;/p&gt;
&lt;p&gt;雙機顧名思義為 client 與 server 分開在兩台機器上，piCorePlayer 的 client 為 Squeezelite，server 則為 Logitech Media Server，事實上在 windows 上也有 client &lt;a href=&#34;https://www.microsoft.com/zh-tw/p/squeezelite-x/9pbhmtnp9037&#34;&gt;Squeezelite-X&lt;/a&gt;可以玩，預期中的最終成品將會是「三機流」吧 XD&lt;/p&gt;
&lt;h1 id=&#34;lms-解決方案&#34;&gt;LMS 解決方案&lt;/h1&gt;
&lt;p&gt;採用 &lt;a href=&#34;https://hub.docker.com/u/lmscommunity&#34;&gt;lms community&lt;/a&gt; 的 docker image &lt;a href=&#34;https://hub.docker.com/r/lmscommunity/logitechmediaserver&#34;&gt;lmscommunity/logitechmediaserver&lt;/a&gt; 作為這次 server 端的 service。&lt;/p&gt;
&lt;h1 id=&#34;安裝流程&#34;&gt;安裝流程&lt;/h1&gt;
&lt;p&gt;此篇文章使用 portainer 作為 GUI 方便演示，若熟悉 docker 與 k8s 的可以自行下指令安裝。&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;首先我把我的音樂都存放在 NAS 上，先將 music 資料夾給共享出來
&lt;img loading=&#34;lazy&#34; src=&#34;https://www.omegaatt.com/blogs/develop/2021/logitech_media_server_with_docker/images/chrome_eeCPLxdAc7.webp&#34;&gt;&lt;/li&gt;
&lt;li&gt;接著到 portainer 中建立三個 volume
a. lms-music
輸入在 NAS 分享出來的目錄與使用者、密碼，建立一個 cifs mounted volume
&lt;img loading=&#34;lazy&#34; src=&#34;https://www.omegaatt.com/blogs/develop/2021/logitech_media_server_with_docker/images/chrome_ldBeP6Fxh9.webp&#34;&gt;
b. lms-config
&lt;img loading=&#34;lazy&#34; src=&#34;https://www.omegaatt.com/blogs/develop/2021/logitech_media_server_with_docker/images/chrome_OWW2NmHnVl.webp&#34;&gt;
c. lms-playlist
&lt;img loading=&#34;lazy&#34; src=&#34;https://www.omegaatt.com/blogs/develop/2021/logitech_media_server_with_docker/images/chrome_yfoLywNeFT.webp&#34;&gt;&lt;/li&gt;
&lt;li&gt;接著到 stack 中，複製貼上官方的 &lt;a href=&#34;https://hub.docker.com/r/lmscommunity/logitechmediaserver&#34;&gt;docker-compose&lt;/a&gt;，若 portainer 已經佔用了 port &lt;code&gt;9000&lt;/code&gt;，則需要更改暴露的 port &lt;code&gt;port:port/tcp&lt;/code&gt; 並追加環境變數 &lt;code&gt;HTTP_PORT=port&lt;/code&gt;，我最終的 stack 設定為:&lt;/li&gt;
&lt;/ol&gt;
&lt;pre tabindex=&#34;0&#34;&gt;&lt;code&gt;lms:
    image: lmscommunity/logitechmediaserver
    volumes:
      - lms-config:/config:rw
      - lms-music:/music:ro
      - lms-playlist:/playlist:rw
      - /etc/localtime:/etc/localtime:ro
      - /etc/timezone:/etc/timezone:ro
    ports:
      - 9002:9002/tcp
      - 9090:9090/tcp
      - 3483:3483/tcp
      - 3483:3483/udp
    environment:
      - HTTP_PORT=9002
    restart: always
&lt;/code&gt;&lt;/pre&gt;&lt;ol start=&#34;4&#34;&gt;
&lt;li&gt;點下 &lt;code&gt;Deploy the stack 後就完成了&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;可以到 container 下看是否已經啟動完成了，並稍微記一下 container 的 hash，在稍後 windows 的設定中會出現。
&lt;img loading=&#34;lazy&#34; src=&#34;https://www.omegaatt.com/blogs/develop/2021/logitech_media_server_with_docker/images/chrome_mGOfrPb43k.webp&#34;&gt;&lt;/li&gt;
&lt;/ol&gt;
&lt;h1 id=&#34;picoreplayer-設定&#34;&gt;piCorePlayer 設定&lt;/h1&gt;
&lt;p&gt;進入到 piCorePlayer web 中的 &lt;code&gt;Squeezelite Settings&lt;/code&gt; 設定頁面，並在 &lt;code&gt;LMS IP&lt;/code&gt; 中輸入 portainer 的 ip 位置後點擊 Save，大功告成。
&lt;img loading=&#34;lazy&#34; src=&#34;https://www.omegaatt.com/blogs/develop/2021/logitech_media_server_with_docker/images/chrome_YFvgpVIpxs.webp&#34;&gt;&lt;/p&gt;</description>
    </item>
    <item>
      <title>A=B 遊完心得、解法</title>
      <link>https://www.omegaatt.com/blogs/develop/2021/a_equals_to_b/</link>
      <pubDate>Wed, 13 Oct 2021 00:00:00 +0000</pubDate><author>raiven.kao@gmail.com (Raiven Kao)</author>
      <guid>https://www.omegaatt.com/blogs/develop/2021/a_equals_to_b/</guid>
      <description>&lt;p&gt;Steam 上推出了一款 &lt;a href=&#34;https://store.steampowered.com/app/1720850/AB/&#34;&gt;A=B&lt;/a&gt;，稍微玩了一下，下面提供自己的解法。&lt;/p&gt;
&lt;p&gt;小試了一下，這個迷你語言每一行由左側、等號&lt;code&gt;=&lt;/code&gt;、右側組成，左側會被取代成右側，並且每次取代完都會從第一行重新開始，有點像在寫遞迴。&lt;/p&gt;
&lt;h1 id=&#34;第一章-ab&#34;&gt;第一章 A=B&lt;/h1&gt;
&lt;ul&gt;
&lt;li&gt;1-1
a取代成b，限定一行。
&lt;pre tabindex=&#34;0&#34;&gt;&lt;code&gt;a=b
&lt;/code&gt;&lt;/pre&gt;&lt;/li&gt;
&lt;li&gt;1-2
變成大寫，限定三行。
&lt;pre tabindex=&#34;0&#34;&gt;&lt;code&gt;a=A
b=B
c=C
&lt;/code&gt;&lt;/pre&gt;&lt;/li&gt;
&lt;li&gt;1-3
去掉重複的，限定三行。
&lt;pre tabindex=&#34;0&#34;&gt;&lt;code&gt;aa=a
bb=b
cc=c
&lt;/code&gt;&lt;/pre&gt;&lt;/li&gt;
&lt;li&gt;1-4
去掉連續的a，限定兩行。
&lt;pre tabindex=&#34;0&#34;&gt;&lt;code&gt;aaa=aa
aa=
&lt;/code&gt;&lt;/pre&gt;&lt;/li&gt;
&lt;li&gt;1-5
只會給 a 與 b，回答最多的是 a 還是 b，最多四行。
&lt;pre tabindex=&#34;0&#34;&gt;&lt;code&gt;ab=
ba=
aa=a
bb=b
&lt;/code&gt;&lt;/pre&gt;&lt;/li&gt;
&lt;li&gt;1-6
排序，最多三行。
&lt;pre tabindex=&#34;0&#34;&gt;&lt;code&gt;ca=ac
cb=bc
ba=ab
&lt;/code&gt;&lt;/pre&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;h1 id=&#34;第二章-新的關鍵字&#34;&gt;第二章 新的關鍵字&lt;/h1&gt;
&lt;p&gt;多了一個新的關鍵字 &lt;code&gt;return&lt;/code&gt;，僅能修飾右側語法，並且要用&lt;code&gt;()&lt;/code&gt;包起來&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;2-1
回答 helloword，最多一行。
&lt;pre tabindex=&#34;0&#34;&gt;&lt;code&gt;=(return)helloworld
&lt;/code&gt;&lt;/pre&gt;&lt;/li&gt;
&lt;li&gt;2-2
是否至少包含三個 a，最多四行。
&lt;pre tabindex=&#34;0&#34;&gt;&lt;code&gt;b=
c=
aaa=(return)true
=(return)false
&lt;/code&gt;&lt;/pre&gt;&lt;/li&gt;
&lt;li&gt;2-3
字數除以 3 的餘數，最多六行。
&lt;pre tabindex=&#34;0&#34;&gt;&lt;code&gt;c=a
b=a
aaa=
aa=(return)2
a=(return)1
=(return)0
&lt;/code&gt;&lt;/pre&gt;&lt;/li&gt;
&lt;li&gt;2-4
&lt;pre tabindex=&#34;0&#34;&gt;&lt;code&gt;ca=ac
ba=ab
cb=bc
aaa=a
bbb=b
ccc=c
aa=(return)false
bb=(return)false
cc=(return)false
=(return)true
&lt;/code&gt;&lt;/pre&gt;&lt;/li&gt;
&lt;li&gt;2-5
&lt;pre tabindex=&#34;0&#34;&gt;&lt;code&gt;aaa=aa
aa=d
bbb=bb
bb=d
ccc=cc
cc=d
ab=(return)false
ac=(return)false
ba=(return)false
bc=(return)false
ca=(return)false
cb=(return)false
d=
a=(return)true
b=(return)true
c=(return)true
=(return)false
&lt;/code&gt;&lt;/pre&gt;&lt;/li&gt;
&lt;li&gt;2-6
&lt;pre tabindex=&#34;0&#34;&gt;&lt;code&gt;ba=ab
ca=ac
cb=dc
bc=d
bd=db
ad=
a=(return)false
b=(return)false
c=(return)true
=(return)false
&lt;/code&gt;&lt;/pre&gt;&lt;/li&gt;
&lt;li&gt;2-7
&lt;pre tabindex=&#34;0&#34;&gt;&lt;code&gt;ba=ab
ca=ac
cb=bc
ab=d
ad=da
da=aa
ac=
bc=
d=b
cc=c
bb=b
aa=a
&lt;/code&gt;&lt;/pre&gt;&lt;/li&gt;
&lt;/ul&gt;</description>
    </item>
    <item>
      <title>Kubernetes 學習筆記 - Ubuntu kubeamd 建置 cluster</title>
      <link>https://www.omegaatt.com/blogs/develop/2021/kubeadm-init/</link>
      <pubDate>Sun, 19 Sep 2021 00:00:00 +0000</pubDate><author>raiven.kao@gmail.com (Raiven Kao)</author>
      <guid>https://www.omegaatt.com/blogs/develop/2021/kubeadm-init/</guid>
      <description>&lt;h2 id=&#34;環境&#34;&gt;環境&lt;/h2&gt;
&lt;p&gt;物理機: 2278G/16G DDR4 ECC*4/1T MX500
OS: - master: Ubuntu server 20.04 - node: Ubuntu server 20.04&lt;/p&gt;
&lt;h2 id=&#34;安裝-dockerkubeadmkubeletkubectl&#34;&gt;安裝 docker、kubeadm、kubelet、kubectl&lt;/h2&gt;
&lt;h3 id=&#34;安裝-docker&#34;&gt;安裝 docker&lt;/h3&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;-webkit-text-size-adjust:none;&#34;&gt;&lt;code class=&#34;language-shell&#34; data-lang=&#34;shell&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;sudo apt install apt-transport-https ca-certificates curl gnupg-agent software-properties-common -y
&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;curl -fsSL https://download.docker.com/linux/ubuntu/gpg | sudo apt-key add -
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;sudo add-apt-repository &lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;deb [arch=amd64] https://download.docker.com/linux/ubuntu &lt;/span&gt;&lt;span style=&#34;color:#66d9ef&#34;&gt;$(&lt;/span&gt;lsb_release -cs&lt;span style=&#34;color:#66d9ef&#34;&gt;)&lt;/span&gt;&lt;span style=&#34;color:#e6db74&#34;&gt; stable&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;sudo apt-get update
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;sudo apt-get install docker-ce docker-ce-cli containerd.io -y
&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;sudo usermod -aG docker $USER
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;登出再登入&lt;/p&gt;
&lt;h3 id=&#34;安裝-kubeadm-kubelet-kubectl&#34;&gt;安裝 kubeadm kubelet kubectl&lt;/h3&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;-webkit-text-size-adjust:none;&#34;&gt;&lt;code class=&#34;language-shell&#34; data-lang=&#34;shell&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;sudo apt-get install -y apt-transport-https curl
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;sudo su
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;curl -s https://packages.cloud.google.com/apt/doc/apt-key.gpg | apt-key add -
&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;sudo cat &lt;span style=&#34;color:#e6db74&#34;&gt;&amp;lt;&amp;lt;EOF &amp;gt;/etc/apt/sources.list.d/kubernetes.list
&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:#e6db74&#34;&gt;deb https://apt.kubernetes.io/ kubernetes-xenial main
&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:#e6db74&#34;&gt;EOF&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;exit
&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;sudo apt-get update
&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:#75715e&#34;&gt;# apt-cache madison kubeadm&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:#75715e&#34;&gt;# K_VER=&amp;#34;1.20.5-00&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:#75715e&#34;&gt;# apt install -y kubelet=${K_VER} kubectl=${K_VER} kubeadm=${K_VER}&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:#75715e&#34;&gt;# 不指定版本的話直接安裝即可&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;sudo apt install -y kubelet kubeadm kubectl
&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;# 若需要鎖定版本可以使用 apt-mark hold&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;sudo apt-mark hold kubelet kubeadm kubectl
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;h3 id=&#34;設定-kubeadm&#34;&gt;設定 kubeadm&lt;/h3&gt;
&lt;h4 id=&#34;master&#34;&gt;master&lt;/h4&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;-webkit-text-size-adjust:none;&#34;&gt;&lt;code class=&#34;language-shell&#34; data-lang=&#34;shell&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;sudo kubeadm init &lt;span style=&#34;color:#ae81ff&#34;&gt;\
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    --pod-network-cidr 網路區段 &lt;span style=&#34;color:#ae81ff&#34;&gt;\
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    --apiserver-advertise-address 本機IP &lt;span style=&#34;color:#ae81ff&#34;&gt;\
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    --apiserver-cert-extra-sans gcp IP
&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:#75715e&#34;&gt;# kubeadm init --pod-network-cidr 172.100.0.0/16 --apiserver-advertise-address 10.140.0.2 --apiserver-cert-extra-sans 130.211.253.131&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;結束後會看到&lt;/p&gt;</description>
    </item>
    <item>
      <title>Prometheus 學習筆記 - 基本架構與資料結構</title>
      <link>https://www.omegaatt.com/blogs/develop/2021/promethues-overview/</link>
      <pubDate>Sat, 18 Sep 2021 00:00:00 +0000</pubDate><author>raiven.kao@gmail.com (Raiven Kao)</author>
      <guid>https://www.omegaatt.com/blogs/develop/2021/promethues-overview/</guid>
      <description>&lt;h1 id=&#34;prometheus-overview&#34;&gt;Prometheus Overview&lt;/h1&gt;
&lt;iframe width=&#34;100%&#34; height=&#34;512&#34; src=&#34;https://www.youtube.com/embed/h4Sl21AKiDg&#34; title=&#34;YouTube video player&#34; frameborder=&#34;0&#34; allow=&#34;accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture&#34; allowfullscreen&gt;&lt;/iframe&gt;
&lt;h2 id=&#34;架構&#34;&gt;架構&lt;/h2&gt;
&lt;p&gt;&lt;img loading=&#34;lazy&#34; src=&#34;https://prometheus.io/assets/architecture.png&#34;&gt;&lt;/p&gt;
&lt;ol start=&#34;0&#34;&gt;
&lt;li&gt;Exporter: 要被採集的目標 job 中曝露 metrics 的組件，負責轉換資訊成 prometheus 可解讀的格式。由於某些早於 prometheus 的 service 因為某些原因，不想主動在服務中曝露 metrics，故可以用一些額外的 exporter 將這些 service 中的 metrics 轉成 prometheus 格式，並曝露埠。
&lt;ul&gt;
&lt;li&gt;node exporter&lt;/li&gt;
&lt;li&gt;mysql exporter&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;Push Gateway: 由於有些任務時間太短，可能不到 prometheus pull 的週期，可讓這些 jobs 主動把 metrics push 到這個 push gateway，好讓 premetheus server 可以 pull。&lt;/li&gt;
&lt;li&gt;Prometheus Server
&lt;ul&gt;
&lt;li&gt;Retrieval(retrieval system): 主要負責 pull 指定的 target
&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;-webkit-text-size-adjust:none;&#34;&gt;&lt;code class=&#34;language-yaml&#34; data-lang=&#34;yaml&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#75715e&#34;&gt;# /etc/prometheus/prometheus.yml&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:#f92672&#34;&gt;scrape_configs&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:#f92672&#34;&gt;job_name&lt;/span&gt;: &lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;prometheus&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:#f92672&#34;&gt;static_configs&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:#f92672&#34;&gt;targets&lt;/span&gt;: [&lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;localhost:9090&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:#f92672&#34;&gt;job_name&lt;/span&gt;: &lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;node_exporter&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:#f92672&#34;&gt;static_configs&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:#f92672&#34;&gt;targets&lt;/span&gt;: [&lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;localhost:9100&amp;#34;&lt;/span&gt;]
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/li&gt;
&lt;li&gt;TSDB: 時間序列資料庫，存放 pull 到的 metrics&lt;/li&gt;
&lt;li&gt;HTTP Server: 為 PromeQL 查詢提供接口&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;Service Discovery:
&lt;ul&gt;
&lt;li&gt;kubernetes: 自動發現 k8s 中 pod 增加減少以及 probe 異動等等&lt;/li&gt;
&lt;li&gt;file_sd: 通過 config 實現服務的自動發現&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;Alert Manager: 當 prometheus server 發現某某某 job 已經達到設定好的通知閾值進行通知的 service。&lt;/li&gt;
&lt;li&gt;PromeQL: 無論是自帶 web UI 或是串接 Grafana，都是通過 http 進行 PromeQL 查詢後做視覺化處理 TSDB 中的時間序列資料。&lt;/li&gt;
&lt;/ol&gt;
&lt;h2 id=&#34;資料結構&#34;&gt;資料結構&lt;/h2&gt;
&lt;p&gt;下面範例中會有更詳細說明&lt;/p&gt;</description>
    </item>
    <item>
      <title>Raspberry pi 4 &#43; piCorePlayer 7.0.0 折騰筆記</title>
      <link>https://www.omegaatt.com/blogs/develop/2021/picoreplayer/</link>
      <pubDate>Sun, 02 May 2021 00:00:00 +0000</pubDate><author>raiven.kao@gmail.com (Raiven Kao)</author>
      <guid>https://www.omegaatt.com/blogs/develop/2021/picoreplayer/</guid>
      <description>&lt;h1 id=&#34;前言&#34;&gt;前言&lt;/h1&gt;
&lt;p&gt;鑒於想要剛好逛到 Raspberry pi 4 出了(2019)，剛好手邊又有兩顆閒置的喇叭，於是產生了做一套&lt;strong&gt;網路串流音樂撥放器&lt;/strong&gt;。用到的硬體如下：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href=&#34;https://www.raspberrypi.org/products/raspberry-pi-4-model-b/&#34;&gt;Raspberry pi 4b 4G ver 1.2&lt;/a&gt;
樹莓派的部分選用 4G 版本，由於 4G 跟 8G 版本差價有點大，僅作為多媒體撥放器，應該不需要那麼大量的記憶體，故降梯成本僅選用 4G 版本。
至於 1.2 版本差在哪裡可以參考&lt;a href=&#34;https://www.raspberrypi.com.tw/30359/news-raspberry-pi4-new-version-fixes-usb-type-c-problem&#34;&gt;這篇文章&lt;/a&gt;，主要修正了 type-C 供電問題。&lt;/li&gt;
&lt;li&gt;&lt;a href=&#34;https://www.argon40.com/argon-one-m-2-case-for-raspberry-pi-4.html&#34;&gt;Argon ONE m.2&lt;/a&gt;
由於 pi 4b 的 cpu 較 3b+ 升級很多，同時&lt;a href=&#34;https://www.reddit.com/r/raspberry_pi/comments/cbqt19/raspberry_pi_4_heatsink_testing/&#34;&gt;發熱量也提升&lt;/a&gt;了，故得選用一個同時具有散熱、美觀、擴充的外殼，剛好手邊有汰換下來的 m.2 SATA SSD，於是選擇了 Argon ONE m.2。&lt;/li&gt;
&lt;li&gt;直流電源供應器
儘管線性電源如 Keces P3 或便宜一點 ifi iPower X 或是老虎魚都是很好的穩定 5V 直流電源供應器，但由於使用樹莓派就是不想花大錢，用比系統本身還高價如此多的周邊，似乎有點本末倒置，魚是只用了手機的 type-C 5V/3A 變壓器作為電源供應器。&lt;/li&gt;
&lt;/ul&gt;
&lt;h1 id=&#34;硬體安裝&#34;&gt;硬體安裝&lt;/h1&gt;
&lt;p&gt;硬體安裝部分不在這次探討範圍，由於每個人使用的外殼均不相同，文章中就假設是裸機設定。&lt;/p&gt;
&lt;h1 id=&#34;系統選擇&#34;&gt;系統選擇&lt;/h1&gt;
&lt;p&gt;由於是第一次接觸 Raspberry pi as audio player，一開始是看到 Volumio 與 Moode 等等，也實際安裝了一次 Volumio，但由於慧根不夠(X)，或是不知道撞到了什麼，只要不小心設定錯音訊，便會導致系統無法撥音樂，也懶得 debug 了，於是想說乾脆來挑戰看看 piCorePlayer。&lt;/p&gt;</description>
    </item>
    <item>
      <title>Golang 學習筆記 Day 0 - Why Golang &amp; 安裝篇</title>
      <link>https://www.omegaatt.com/blogs/develop/2020/golang_learning_day0/</link>
      <pubDate>Tue, 25 Aug 2020 00:00:00 +0000</pubDate><author>raiven.kao@gmail.com (Raiven Kao)</author>
      <guid>https://www.omegaatt.com/blogs/develop/2020/golang_learning_day0/</guid>
      <description>&lt;pre tabindex=&#34;0&#34;&gt;&lt;code&gt;免責聲明，此篇為個人學習筆記，非正式教學文，僅供參考感謝指教，有任何內容錯誤歡迎發 issue。
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;&lt;img alt=&#34;image source: https://medium.com/nordcloud-engineering/how-to-build-pluggable-golang-application-and-benefit-from-aws-lambda-layers-154c8117df9b&#34; loading=&#34;lazy&#34; src=&#34;https://miro.medium.com/max/2946/1*TArmNwMaoXjR1MegmucwyQ.png&#34;&gt;&lt;/p&gt;
&lt;h2 id=&#34;why-golang&#34;&gt;Why Golang&lt;/h2&gt;
&lt;p&gt;    在高中時期，除了一年級上學期是用 C 打開程式大門之外，大家都知道的，畫金字塔、 &lt;code&gt;cin&lt;/code&gt; 、 &lt;code&gt;cout&lt;/code&gt; 等等基本的邏輯觀念，一年級下學期開始就是 C#、HTML、PHP，也玩轉在 CPLD、Arduino、焊三角錐、兩根同軸焊接，總之就是全部吃掉就對了，雖然都是皮毛但不至於完全沒印象。&lt;/p&gt;
&lt;p&gt;    然而到了大學，敝人不才沒有考上一般大學，也沒考上工科第一二志願科大，在整個大學過程碰了最多的反而是 python、machine learning。然而也都是碰皮毛&lt;del&gt;因為都在玩社團、學攝影&lt;/del&gt;，事實上在大學時期程式功力已荒廢大半。&lt;/p&gt;
&lt;p&gt;    所幸在實習期間，公司願意提供環境讓我邊學習邊成長後做出貢獻，而使用的後端語言是 PHP，然而就一年以來的使用 PHP 有以下優缺點：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;學習曲線&lt;del&gt;極&lt;/del&gt;低，但坑很多&lt;/li&gt;
&lt;li&gt;解釋型腳本語言部署極方便&lt;/li&gt;
&lt;li&gt;非靜態、非強行別、萬用 array 導致容易出現很多偷懶的 code&lt;/li&gt;
&lt;li&gt;窮人聖經 LAMP、LNMP&lt;/li&gt;
&lt;li&gt;歷史悠久 framework 眾多、工作好找，甚至有許多 C 底層的高校框架可以使用。&lt;/li&gt;
&lt;li&gt;可以很 OO 也可以不 OO，寫 code 彈性極大&lt;/li&gt;
&lt;li&gt;在 php 7.0 改用 zend 引擎後效能改頭換面，「原來 PHP 也可以寫遊戲」。搭配一些 C 底層框架，甚至可以跟 nodeJS 一較高下，而期望中 8.0 效能將再更高。&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;img loading=&#34;lazy&#34; src=&#34;https://i.imgur.com/CkKvVrE.jpg&#34;&gt;&lt;/p&gt;
&lt;p&gt;    但是，再快也只是 AE86，腳本語言終究與「高效」存在矛盾，同時，與其在 PHP 中配合 IDE 使用 type hinting 自我約束，何不直接寫強行別語言呢?? 然而在看過許多主流 web service 語言後，最後決定來學學 Golang，而關於 golang 的優勢，可以點擊下方圖片看更多詳細。&lt;/p&gt;</description>
    </item>
    <item>
      <title>Golang 學習筆記 Day 1 - Getting started</title>
      <link>https://www.omegaatt.com/blogs/develop/2020/golang_learning_day1/</link>
      <pubDate>Tue, 25 Aug 2020 00:00:00 +0000</pubDate><author>raiven.kao@gmail.com (Raiven Kao)</author>
      <guid>https://www.omegaatt.com/blogs/develop/2020/golang_learning_day1/</guid>
      <description>&lt;pre tabindex=&#34;0&#34;&gt;&lt;code&gt;免責聲明，此篇為個人學習筆記，非正式教學文，僅供參考感謝指教，有任何內容錯誤歡迎發 issue。
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;在前一天介紹了為何要使用 golang、安裝過程以及基本的 hello world，這天將重點放在最基本的 golang 程式碼特徵。&lt;/p&gt;
&lt;h2 id=&#34;packages&#34;&gt;packages&lt;/h2&gt;
&lt;p&gt;正如昨天關於 hello world 的解析中提到的，每個程式都是由一到多個 packages 組成的，也就是 &lt;code&gt;.go&lt;/code&gt; 中必須宣告該檔案是哪個 package，且同一個資料夾中僅接受 &lt;code&gt;xxx&lt;/code&gt; 以及 &lt;code&gt;xxx_test&lt;/code&gt; 兩種 package名稱，也就是該 package 的名稱以及測試檔的「測試包」。&lt;/p&gt;
&lt;p&gt;至於 package 要怎麼命名呢，首先要提到檔案該如何命名，雖然 &lt;code&gt;go format&lt;/code&gt; 是對於程式碼風格十分嚴格的，但是對於檔案名稱似乎就沒那麼嚴謹，但整個社群中普遍會用 &lt;code&gt;snake_case&lt;/code&gt; 或是 &lt;code&gt;dash-case&lt;/code&gt; 作為檔案 (甚至是 package) 的命名。&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;aaabbbccc.go&lt;/li&gt;
&lt;li&gt;time-machine.go&lt;/li&gt;
&lt;li&gt;battery_charger.go&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;但是需要注意的是 go build 會忽略所有以 &lt;code&gt;_&lt;/code&gt; 或是以 &lt;code&gt;.&lt;/code&gt; 作為結尾的後綴， &lt;a href=&#34;https://dave.cheney.net/2013/10/12/how-to-use-conditional-compilation-with-the-go-build-tool&#34;&gt; &lt;code&gt;underline&lt;/code&gt; 可能會導致在建構時被忽略&lt;/a&gt;。&lt;/p&gt;
&lt;p&gt;再來提到 package 到底要如何命名呢，&lt;a href=&#34;https://blog.golang.org/package-names&#34;&gt;官方文檔&lt;/a&gt;中明確的提到不要用 &lt;code&gt;camelCase&lt;/code&gt; 以及 &lt;code&gt;snake_case&lt;/code&gt; 。&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;The style of names typical of another language might not be idiomatic in a Go program. Here are two examples of names that might be good style in other languages but do not fit well in Go:&lt;/p&gt;</description>
    </item>
    <item>
      <title>RESTful server based on golang</title>
      <link>https://www.omegaatt.com/blogs/develop/2020/restful-api-with-go/</link>
      <pubDate>Thu, 19 Mar 2020 00:00:00 +0000</pubDate><author>raiven.kao@gmail.com (Raiven Kao)</author>
      <guid>https://www.omegaatt.com/blogs/develop/2020/restful-api-with-go/</guid>
      <description>&lt;p&gt;很高興有個機會製作面試前的測試作業，也就趁這個機會做個筆記以及學習紀錄。&lt;/p&gt;
&lt;h2 id=&#34;前置作業&#34;&gt;前置作業&lt;/h2&gt;
&lt;p&gt;先點列一下項目需求：&lt;/p&gt;
&lt;h3 id=&#34;基本需求&#34;&gt;基本需求&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;based on golnag&lt;/li&gt;
&lt;li&gt;The API interface you provide can be any of the following：RESTful、json rpc、gRPC&lt;/li&gt;
&lt;li&gt;At least two sources&lt;/li&gt;
&lt;li&gt;When a source is unavailable, the result of its last successful ask is returned&lt;/li&gt;
&lt;li&gt;Use git to manage source code&lt;/li&gt;
&lt;li&gt;Write readme.md and describe what features, features, and TODO which have been implemented&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 id=&#34;額外可選&#34;&gt;額外可選&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;Traffic limits, including the number of times your server queries the source, and the number of times the user queries you&lt;/li&gt;
&lt;li&gt;Good testing, annotations, and git commit&lt;/li&gt;
&lt;li&gt;An additional websocket interface is provided to automatically send the latest information whenever the market changes&lt;/li&gt;
&lt;li&gt;Users can choose to use an automatic source determination, the latest data source, or manually specify the source of the data&lt;/li&gt;
&lt;li&gt;Package it as a Dockerfile, docker-compose file, or kubernetes yaml&lt;/li&gt;
&lt;li&gt;There is a simple front-end or cli program that displays the results&lt;/li&gt;
&lt;li&gt;The API you provide has a corresponding file, such as a swagger, or simply a markdown file&lt;/li&gt;
&lt;li&gt;Other features not listed but that you thought would be cool to implement&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;這邊就必須要先打預防針了，&lt;strong&gt;這篇僅為學習筆記&lt;/strong&gt;，並非正式教學。且，我只是 Golang 初學者，所以這篇內也不會出現什麼高等級的解法，甚至也是第一次實作介接 API 呢 (笑)。而 DB 以及 redis 都是直接以方便使用去寫 dbi 而已並無用抽象曾去封裝，也進而導致 unit test 十分困難先擱置了，有空才會再針對性重構，而此次就先將目標專注在 API 介接。&lt;/p&gt;</description>
    </item>
    <item>
      <title>Codewars Find X II in golang</title>
      <link>https://www.omegaatt.com/blogs/develop/2020/codewars_find_x_ii/</link>
      <pubDate>Tue, 03 Mar 2020 00:00:00 +0000</pubDate><author>raiven.kao@gmail.com (Raiven Kao)</author>
      <guid>https://www.omegaatt.com/blogs/develop/2020/codewars_find_x_ii/</guid>
      <description>&lt;p&gt;想不到一眨眼半個月過去了，自從面試趨勢之後整個人彷彿被電到失去信心，在離職與等待面試消息期間，也只好振作補足不足的地方繼續向前。&lt;/p&gt;
&lt;p&gt;而 docker 學習心得何時會產出呢？在學習過程中發現除了一般操作(day1 內容)外，底層或多 container 溝通都是大量的網路觀念與知識，需要更多知識量才能整理成筆記，不急不急。&lt;/p&gt;
&lt;p&gt;由於學習過程發現而這段時間在 coding 能力上主要還是靠 codewars 上的題目，畢竟解完後能看別人的 code 來檢視自己的 code 能不能更好。其中不免遇到一些覺得很有趣的題目，好比這題 &lt;a href=&#34;https://www.codewars.com/kata/5d339b01496f8d001054887f&#34;&gt;Find X II&lt;/a&gt;，他並不是什麼過分難的題目，邏輯也不是很難通，畢竟只是一個 6 kyu 的題目，但為何快一年了只有 44 個人解完呢，這就牽扯到下面要說的。&lt;/p&gt;
&lt;h1 id=&#34;題目概要&#34;&gt;題目概要&lt;/h1&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;-webkit-text-size-adjust:none;&#34;&gt;&lt;code class=&#34;language-go&#34; data-lang=&#34;go&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#66d9ef&#34;&gt;func&lt;/span&gt; &lt;span style=&#34;color:#a6e22e&#34;&gt;FindX&lt;/span&gt;(&lt;span style=&#34;color:#a6e22e&#34;&gt;n&lt;/span&gt; &lt;span style=&#34;color:#66d9ef&#34;&gt;int&lt;/span&gt;) &lt;span style=&#34;color:#66d9ef&#34;&gt;int&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;n&lt;/span&gt; &lt;span style=&#34;color:#f92672&#34;&gt;==&lt;/span&gt; &lt;span style=&#34;color:#ae81ff&#34;&gt;0&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:#ae81ff&#34;&gt;0&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:#a6e22e&#34;&gt;x&lt;/span&gt; &lt;span style=&#34;color:#f92672&#34;&gt;:=&lt;/span&gt; &lt;span style=&#34;color:#ae81ff&#34;&gt;0&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;for&lt;/span&gt; &lt;span style=&#34;color:#a6e22e&#34;&gt;i&lt;/span&gt;&lt;span style=&#34;color:#f92672&#34;&gt;:=&lt;/span&gt;&lt;span style=&#34;color:#ae81ff&#34;&gt;1&lt;/span&gt;; &lt;span style=&#34;color:#a6e22e&#34;&gt;i&lt;/span&gt;&lt;span style=&#34;color:#f92672&#34;&gt;&amp;lt;=&lt;/span&gt;&lt;span style=&#34;color:#a6e22e&#34;&gt;n&lt;/span&gt;; &lt;span style=&#34;color:#a6e22e&#34;&gt;i&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;x&lt;/span&gt; &lt;span style=&#34;color:#f92672&#34;&gt;+=&lt;/span&gt; &lt;span style=&#34;color:#a6e22e&#34;&gt;FindX&lt;/span&gt;(&lt;span style=&#34;color:#a6e22e&#34;&gt;i&lt;/span&gt;&lt;span style=&#34;color:#f92672&#34;&gt;-&lt;/span&gt;&lt;span style=&#34;color:#ae81ff&#34;&gt;1&lt;/span&gt;) &lt;span style=&#34;color:#f92672&#34;&gt;+&lt;/span&gt; &lt;span style=&#34;color:#ae81ff&#34;&gt;3&lt;/span&gt;&lt;span style=&#34;color:#f92672&#34;&gt;*&lt;/span&gt;&lt;span style=&#34;color:#a6e22e&#34;&gt;i&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:#a6e22e&#34;&gt;x&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;但 x 範圍為 1 &amp;lt;= n &amp;lt;= &lt;code&gt;10**6(1e6)&lt;/code&gt; ，請試著重構。且當 n 愈來試大時，可能會超過 int64 表達，故需要將結果對 &lt;code&gt;10**9 + 7&lt;/code&gt; 取模&lt;/p&gt;
&lt;p&gt;標記 &lt;code&gt;FUNDAMENTALS&lt;/code&gt; &lt;code&gt;OPTIMIZATION&lt;/code&gt;&lt;/p&gt;
&lt;h1 id=&#34;解題邏輯&#34;&gt;解題邏輯&lt;/h1&gt;
&lt;p&gt;若沒有取模限制的話這題可以化簡為 O(1)，但很明顯不行。於是先將原題目化簡為 O(n):&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;-webkit-text-size-adjust:none;&#34;&gt;&lt;code class=&#34;language-go&#34; data-lang=&#34;go&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#66d9ef&#34;&gt;func&lt;/span&gt; &lt;span style=&#34;color:#a6e22e&#34;&gt;FindX&lt;/span&gt;(&lt;span style=&#34;color:#a6e22e&#34;&gt;n&lt;/span&gt; &lt;span style=&#34;color:#66d9ef&#34;&gt;int&lt;/span&gt;) &lt;span style=&#34;color:#66d9ef&#34;&gt;int&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;mod&lt;/span&gt; &lt;span style=&#34;color:#f92672&#34;&gt;:=&lt;/span&gt; int(&lt;span style=&#34;color:#a6e22e&#34;&gt;math&lt;/span&gt;.&lt;span style=&#34;color:#a6e22e&#34;&gt;Pow10&lt;/span&gt;(&lt;span style=&#34;color:#ae81ff&#34;&gt;9&lt;/span&gt;)) &lt;span style=&#34;color:#f92672&#34;&gt;+&lt;/span&gt; &lt;span style=&#34;color:#ae81ff&#34;&gt;7&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;m&lt;/span&gt; &lt;span style=&#34;color:#f92672&#34;&gt;:=&lt;/span&gt; &lt;span style=&#34;color:#ae81ff&#34;&gt;0&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;for&lt;/span&gt; &lt;span style=&#34;color:#a6e22e&#34;&gt;i&lt;/span&gt; &lt;span style=&#34;color:#f92672&#34;&gt;:=&lt;/span&gt; &lt;span style=&#34;color:#ae81ff&#34;&gt;0&lt;/span&gt;; &lt;span style=&#34;color:#a6e22e&#34;&gt;i&lt;/span&gt; &lt;span style=&#34;color:#f92672&#34;&gt;&amp;lt;=&lt;/span&gt; &lt;span style=&#34;color:#a6e22e&#34;&gt;n&lt;/span&gt;; &lt;span style=&#34;color:#a6e22e&#34;&gt;i&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;m&lt;/span&gt; = (&lt;span style=&#34;color:#a6e22e&#34;&gt;m&lt;/span&gt;&lt;span style=&#34;color:#f92672&#34;&gt;*&lt;/span&gt;&lt;span style=&#34;color:#ae81ff&#34;&gt;2&lt;/span&gt; &lt;span style=&#34;color:#f92672&#34;&gt;+&lt;/span&gt; &lt;span style=&#34;color:#ae81ff&#34;&gt;3&lt;/span&gt;&lt;span style=&#34;color:#f92672&#34;&gt;*&lt;/span&gt;&lt;span style=&#34;color:#a6e22e&#34;&gt;i&lt;/span&gt;) &lt;span style=&#34;color:#f92672&#34;&gt;%&lt;/span&gt; &lt;span style=&#34;color:#a6e22e&#34;&gt;mod&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:#a6e22e&#34;&gt;m&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;好測試過，就算是輸入 1e6 進去也很快就算出來了，於是提交答案&lt;/p&gt;</description>
    </item>
    <item>
      <title>威康生命遊戲 in PHP</title>
      <link>https://www.omegaatt.com/blogs/develop/2020/codewars_game_of_life/</link>
      <pubDate>Thu, 23 Jan 2020 00:00:00 +0000</pubDate><author>raiven.kao@gmail.com (Raiven Kao)</author>
      <guid>https://www.omegaatt.com/blogs/develop/2020/codewars_game_of_life/</guid>
      <description>&lt;h1 id=&#34;威康生命遊戲-in-php&#34;&gt;威康生命遊戲 in PHP&lt;/h1&gt;
&lt;p&gt;&lt;a href=&#34;https://www.youtube.com/embed/GQNREcMVPHY&#34; title=&#34;生命遊戲：另一種計算機 |混亂博物館&#34;&gt;&lt;img alt=&#34;生命遊戲：另一種計算機 |混亂博物館&#34; loading=&#34;lazy&#34; src=&#34;https://i.ytimg.com/vi/GQNREcMVPHY/hqdefault.jpg&#34;&gt;&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;生命遊戲，無論在程式、哲學、生命探討上都十分有趣，在生命遊戲中甚至出現永動機。而在 Codewars 中也有生命遊戲的 &lt;a href=&#34;https://www.codewars.com/kata/52423db9add6f6fc39000354&#34;&gt;題目&lt;/a&gt;，規則也十分簡單，以下為維基百科的內容：&lt;/p&gt;
&lt;pre tabindex=&#34;0&#34;&gt;&lt;code&gt;1. 每個細胞有兩種狀態 - 存活或死亡，每個細胞與以自身為中心的周圍八格細胞產生互動（如圖，黑色為存活，白色為死亡）
2. 當前細胞為存活狀態時，當周圍的存活細胞低於2個時（不包含2個），該細胞變成死亡狀態。（模擬生命數量稀少）
3. 當前細胞為存活狀態時，當周圍有2個或3個存活細胞時，該細胞保持原樣。
4. 當前細胞為存活狀態時，當周圍有超過3個存活細胞時，該細胞變成死亡狀態。（模擬生命數量過多）
5. 當前細胞為死亡狀態時，當周圍有3個存活細胞時，該細胞變成存活狀態。（模擬繁殖）
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;若想知道關於更多可以觀看上方由&lt;strong&gt;混亂博物館&lt;/strong&gt;出品的影片，此篇就針對這 4kyu 的題目進行探討解法。首先題目中指出這個宇宙的 x,y 軸都是無限大的，所以生成高斯帕機槍且迭代過多次可能造成記憶體使用過量。&lt;/p&gt;
&lt;h1 id=&#34;迭代&#34;&gt;迭代&lt;/h1&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;-webkit-text-size-adjust:none;&#34;&gt;&lt;code class=&#34;language-PHP&#34; data-lang=&#34;PHP&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#66d9ef&#34;&gt;function&lt;/span&gt; &lt;span style=&#34;color:#a6e22e&#34;&gt;get_generation&lt;/span&gt;(&lt;span style=&#34;color:#66d9ef&#34;&gt;array&lt;/span&gt; $cells, &lt;span style=&#34;color:#a6e22e&#34;&gt;int&lt;/span&gt; $generations)&lt;span style=&#34;color:#f92672&#34;&gt;:&lt;/span&gt; &lt;span style=&#34;color:#66d9ef&#34;&gt;array&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;if&lt;/span&gt;($cells &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:#66d9ef&#34;&gt;return&lt;/span&gt; [[]];
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    $w &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; &lt;span style=&#34;color:#a6e22e&#34;&gt;count&lt;/span&gt;($cells);
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    $h &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; &lt;span style=&#34;color:#a6e22e&#34;&gt;count&lt;/span&gt;($cells[&lt;span style=&#34;color:#ae81ff&#34;&gt;0&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;for&lt;/span&gt; ($index &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; &lt;span style=&#34;color:#ae81ff&#34;&gt;0&lt;/span&gt;; $index &lt;span style=&#34;color:#f92672&#34;&gt;&amp;lt;&lt;/span&gt; $generations; $index&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;        $cells &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; &lt;span style=&#34;color:#a6e22e&#34;&gt;getNextGen&lt;/span&gt;($cells, $w, $h);
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        $sum &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; &lt;span style=&#34;color:#ae81ff&#34;&gt;0&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;foreach&lt;/span&gt; ($cells &lt;span style=&#34;color:#66d9ef&#34;&gt;as&lt;/span&gt; $row) {
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;            $sum &lt;span style=&#34;color:#f92672&#34;&gt;+=&lt;/span&gt; &lt;span style=&#34;color:#a6e22e&#34;&gt;array_sum&lt;/span&gt;($row);
&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;if&lt;/span&gt; ($sum &lt;span style=&#34;color:#f92672&#34;&gt;==&lt;/span&gt; &lt;span style=&#34;color:#ae81ff&#34;&gt;0&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&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;return&lt;/span&gt; $cells;
&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;於是可以建立一個方法 getNextGen 來獲取下個世代的結果&lt;/p&gt;</description>
    </item>
    <item>
      <title>VScode SFTP 快速同步文件到 Server</title>
      <link>https://www.omegaatt.com/blogs/develop/2020/vscode_sftp/</link>
      <pubDate>Mon, 20 Jan 2020 00:00:00 +0000</pubDate><author>raiven.kao@gmail.com (Raiven Kao)</author>
      <guid>https://www.omegaatt.com/blogs/develop/2020/vscode_sftp/</guid>
      <description>&lt;p&gt;當 Server 環境使用白名單搭配 Reverce-Proxy 時，或是 dev、test 環境不供外部使用時，或許會將部分 code 存在只能靠內網連線的主機上，並透過 OpenSSH 供內部遠端操作。&lt;/p&gt;
&lt;p&gt;而 PHP 快速部屬的便捷在於替換單一檔案不須重啟 Server (前提是並非把檔案載進 Memory 或轉化守護進程)，此時可以依靠 FTP 上傳單一檔案即好。但每次改個 code 還要想辦法上傳程式碼實在是太浪費時間了，如標題所述，如何快速同步 code 到 Server 上呢。&lt;/p&gt;
&lt;p&gt;&lt;a href=&#34;https://github.com/liximomo/vscode-sftp&#34;&gt;vscode-sftp&lt;/a&gt; 是一個在 VScode 中非常方便的擴充套件，上傳/下載/同步、單一/多伺服器、ftp/sftp，僅需設置一份或多份的設定檔便能(偷懶不切視窗)上傳檔案到 Server。然而正是環境 (production) 還是走正常 CI/CD 比較好。&lt;/p&gt;
&lt;h1 id=&#34;安裝&#34;&gt;安裝&lt;/h1&gt;
&lt;p&gt;首先在 VScode 中的市集，或是快捷鍵 ctrl+shift+P (或 F1) 打開指令窗口後輸入 extension:install。並搜尋 SFTP，認名作者 liximomo 點一下玩一年，上傳不花一分錢(支持者可以拉到下面點選 donate)。&lt;/p&gt;
&lt;p&gt;&lt;img loading=&#34;lazy&#34; src=&#34;https://i.imgur.com/p0YZBxa.png&#34;&gt;&lt;/p&gt;
&lt;h1 id=&#34;設定上傳環境參數&#34;&gt;設定上傳環境/參數&lt;/h1&gt;
&lt;p&gt;接著同樣打開指令窗口並輸入 SFTP: Config&lt;/p&gt;
&lt;p&gt;&lt;img loading=&#34;lazy&#34; src=&#34;https://i.imgur.com/ozI524w.png&#34;&gt;&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;注意由於是 JSON 格式，不能註解&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;主要配置好host, port, username, privateKeyPath, remotePath, ignore这参数即可：
&lt;img loading=&#34;lazy&#34; src=&#34;https://i.imgur.com/m03U2cm.png&#34;&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;name : 檔案暱稱&lt;/li&gt;
&lt;li&gt;host : server IP/URL&lt;/li&gt;
&lt;li&gt;port : 若 server 建置好 ssh 環境，可將 port 端口改為 22&lt;/li&gt;
&lt;li&gt;username : 在 server 中的使用者名稱&lt;/li&gt;
&lt;li&gt;remotePath : 欲上傳的目錄位置&lt;/li&gt;
&lt;li&gt;privateKeyPath : 私鑰&lt;/li&gt;
&lt;li&gt;password : 若有設私鑰則可有(null)可無&lt;/li&gt;
&lt;li&gt;uploadOnSave : 自動上傳，個人是沒有開啟(經常性按 ctrl + S)&lt;/li&gt;
&lt;li&gt;ignore :&lt;/li&gt;
&lt;/ul&gt;
&lt;pre tabindex=&#34;0&#34;&gt;&lt;code&gt;{
    &amp;#34;name&amp;#34;: &amp;#34;my_work_space&amp;#34;,
    &amp;#34;host&amp;#34;: &amp;#34;192.168.X.X&amp;#34;,
    &amp;#34;port&amp;#34;: 22,
    &amp;#34;protocol&amp;#34;: &amp;#34;sftp&amp;#34;,
    &amp;#34;username&amp;#34;: &amp;#34;centos&amp;#34;,
    &amp;#34;remotePath&amp;#34;: &amp;#34;\\home\\centos\\***\\&amp;#34;,
    &amp;#34;privateKeyPath&amp;#34;: &amp;#34;C:\\***.pem&amp;#34;,
    &amp;#34;uploadOnSave&amp;#34;: false,
    &amp;#34;syncMode&amp;#34;: &amp;#34;update&amp;#34;,
    &amp;#34;ignore&amp;#34;: [
        &amp;#34;**/.vscode/**&amp;#34;,
        &amp;#34;**/.git/**&amp;#34;,
        &amp;#34;**/.svn/**&amp;#34;,
        &amp;#34;**/.DS_Store&amp;#34;
        ]
}
&lt;/code&gt;&lt;/pre&gt;&lt;h1 id=&#34;同步所有檔案&#34;&gt;同步所有檔案&lt;/h1&gt;
&lt;p&gt;儲存後按下同樣打開指令窗口並輸入「SFTP: Sync Local -&amp;gt; Remote」，再選取剛剛設定中的 name(ex.my_work_space) 便可以重部整份資料夾/工作區到遠端伺服器上囉。&lt;/p&gt;</description>
    </item>
    <item>
      <title>Evaluate mathematical expression in PHP</title>
      <link>https://www.omegaatt.com/blogs/develop/2020/codewars_evaluate_mathematical_expression/</link>
      <pubDate>Thu, 16 Jan 2020 00:00:00 +0000</pubDate><author>raiven.kao@gmail.com (Raiven Kao)</author>
      <guid>https://www.omegaatt.com/blogs/develop/2020/codewars_evaluate_mathematical_expression/</guid>
      <description>&lt;p&gt;四則運算，不多，不就是加減乘除而已嗎，怎麼會在 &lt;a href=&#34;https://www.codewars.com/kata/52a78825cdfc2cfc87000005&#34;&gt;Codewars 中被標記為 2 kyu 的題目&lt;/a&gt;呢?&lt;/p&gt;
&lt;p&gt;&lt;img loading=&#34;lazy&#34; src=&#34;https://i.ytimg.com/vi/KTpd-CEJahw/maxresdefault.jpg&#34;&gt;&lt;/p&gt;
&lt;p&gt;總覺得這篇標題應該改為「Codewars 從入坑道棄坑，只需要一題就搞定」。首先我是在 2019 年底才接觸 Codewars，這種打怪練功的感覺讓我一下就上癮了，每天都在想今天能不能再拚一階，或是這題用另外一個語言會不會比較省時間。直到遇到了這題，足足卡了一整天。下面就來介紹一下四則運算在 PHP 中的解法吧。&lt;/p&gt;
&lt;h1 id=&#34;先乘除後加減看到括號先做&#34;&gt;先乘除後加減，看到括號先做&lt;/h1&gt;
&lt;p&gt;先舉一個簡單的例子 a+b 該怎麼做呢，有學過一點資料結構的應該很快就會回想起「中序轉後序」。&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;-webkit-text-size-adjust:none;&#34;&gt;&lt;code class=&#34;language-php&#34; data-lang=&#34;php&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#75715e&#34;&gt;// 123 + 456 * -789
&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:#75715e&#34;&gt;// =&amp;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:#75715e&#34;&gt;// [&amp;#39;123&amp;#39;, &amp;#39;+&amp;#39;, &amp;#39;456, &amp;#39;*&amp;#39;, &amp;#39;-&amp;#39;, &amp;#39;789&amp;#39;]
&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;function&lt;/span&gt; &lt;span style=&#34;color:#a6e22e&#34;&gt;isOp&lt;/span&gt;(&lt;span style=&#34;color:#a6e22e&#34;&gt;string&lt;/span&gt; $s)&lt;span style=&#34;color:#f92672&#34;&gt;:&lt;/span&gt; &lt;span style=&#34;color:#a6e22e&#34;&gt;bool&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:#a6e22e&#34;&gt;strpos&lt;/span&gt;(&lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;+-*/&amp;#34;&lt;/span&gt;, $s) &lt;span style=&#34;color:#f92672&#34;&gt;!==&lt;/span&gt; &lt;span style=&#34;color:#66d9ef&#34;&gt;false&lt;/span&gt; &lt;span style=&#34;color:#f92672&#34;&gt;?&lt;/span&gt; &lt;span style=&#34;color:#66d9ef&#34;&gt;true&lt;/span&gt; &lt;span style=&#34;color:#f92672&#34;&gt;:&lt;/span&gt; &lt;span style=&#34;color:#66d9ef&#34;&gt;false&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;function&lt;/span&gt; &lt;span style=&#34;color:#a6e22e&#34;&gt;changeStringToInfix&lt;/span&gt;(&lt;span style=&#34;color:#a6e22e&#34;&gt;string&lt;/span&gt; $expression)&lt;span style=&#34;color:#f92672&#34;&gt;:&lt;/span&gt; &lt;span style=&#34;color:#66d9ef&#34;&gt;array&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;    $infix &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;    $count &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; &lt;span style=&#34;color:#a6e22e&#34;&gt;strlen&lt;/span&gt;($expression);
&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;for&lt;/span&gt; ($i &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; &lt;span style=&#34;color:#ae81ff&#34;&gt;0&lt;/span&gt;; $i &lt;span style=&#34;color:#f92672&#34;&gt;&amp;lt;&lt;/span&gt; $count; $i&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:#66d9ef&#34;&gt;if&lt;/span&gt; (&lt;span style=&#34;color:#a6e22e&#34;&gt;isOp&lt;/span&gt;($expression[$i]) &lt;span style=&#34;color:#f92672&#34;&gt;===&lt;/span&gt; &lt;span style=&#34;color:#66d9ef&#34;&gt;false&lt;/span&gt;) {
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;            $j &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; $i;
&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;while&lt;/span&gt; ($i &lt;span style=&#34;color:#f92672&#34;&gt;&amp;lt;&lt;/span&gt; $count &lt;span style=&#34;color:#f92672&#34;&gt;&amp;amp;&amp;amp;&lt;/span&gt; (&lt;span style=&#34;color:#a6e22e&#34;&gt;isOp&lt;/span&gt;($expression[$i]) &lt;span style=&#34;color:#f92672&#34;&gt;===&lt;/span&gt; &lt;span style=&#34;color:#66d9ef&#34;&gt;false&lt;/span&gt;)) {
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;                $i &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; $i &lt;span style=&#34;color:#f92672&#34;&gt;+&lt;/span&gt; &lt;span style=&#34;color:#ae81ff&#34;&gt;1&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;            $infix[] &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; &lt;span style=&#34;color:#a6e22e&#34;&gt;substr&lt;/span&gt;($expression, $j, $i &lt;span style=&#34;color:#f92672&#34;&gt;-&lt;/span&gt; $j);
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;            $i &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; $i &lt;span style=&#34;color:#f92672&#34;&gt;-&lt;/span&gt; &lt;span style=&#34;color:#ae81ff&#34;&gt;1&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;else&lt;/span&gt; {
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;            $infix[] &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; &lt;span style=&#34;color:#a6e22e&#34;&gt;substr&lt;/span&gt;($expression, $i, &lt;span style=&#34;color:#ae81ff&#34;&gt;1&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; $infix;
&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;這時候可能有眼尖的會發現，不對阿中序怎麼可以兩個運算子(&amp;rsquo;*&amp;rsquo;, &amp;lsquo;-&amp;rsquo;)連在一起，恭喜你踩進四則運算第一個坑：負號可能是負號也可能是減號。於是可以使用一個偷吃步的方式，將運算子後的負號一率改成另一個代表負號的特殊符號，比如「@」。而這步可以使用正規表達式輕鬆辦到。&lt;/p&gt;</description>
    </item>
    <item>
      <title>PHP ENUM 偽實現</title>
      <link>https://www.omegaatt.com/blogs/develop/2020/php_enum/</link>
      <pubDate>Wed, 15 Jan 2020 00:00:00 +0000</pubDate><author>raiven.kao@gmail.com (Raiven Kao)</author>
      <guid>https://www.omegaatt.com/blogs/develop/2020/php_enum/</guid>
      <description>&lt;p&gt;&lt;code&gt;Enumerations&lt;/code&gt; 又可簡稱為 &lt;code&gt;Enum&lt;/code&gt; ，在眾多語言中都可以讓程式碼更高效簡潔，例如我們可以在 python 中這麼宣告，並且用 &lt;code&gt;requests&lt;/code&gt; 模組打一個 request，即可透過 &lt;code&gt;Enum&lt;/code&gt; 來模組化 Response&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;-webkit-text-size-adjust:none;&#34;&gt;&lt;code class=&#34;language-python&#34; data-lang=&#34;python&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#f92672&#34;&gt;import&lt;/span&gt; enum
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#f92672&#34;&gt;import&lt;/span&gt; request
&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;class&lt;/span&gt; &lt;span style=&#34;color:#a6e22e&#34;&gt;HTTPResponsecode&lt;/span&gt;(enum&lt;span style=&#34;color:#f92672&#34;&gt;.&lt;/span&gt;Enum)
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    OK &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; &lt;span style=&#34;color:#ae81ff&#34;&gt;200&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    BAD_REQUEST &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;    NOT_FOUND &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; &lt;span style=&#34;color:#ae81ff&#34;&gt;404&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;r &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; requests&lt;span style=&#34;color:#f92672&#34;&gt;.&lt;/span&gt;get(&lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#39;https://omegaatt.com/&amp;#39;&lt;/span&gt;)
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;print(r&lt;span style=&#34;color:#f92672&#34;&gt;.&lt;/span&gt;status_code &lt;span style=&#34;color:#f92672&#34;&gt;==&lt;/span&gt; HTTPResponsecode&lt;span style=&#34;color:#f92672&#34;&gt;.&lt;/span&gt;OK)
&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;# True&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;但是在 PHP 中原生是沒有內建 &lt;code&gt;Enum&lt;/code&gt; 的，必須安裝 &lt;code&gt;perl&lt;/code&gt; 的 &lt;a href=&#34;https://stackoverflow.com/questions/57885011/error-class-splenum-not-found-in-php-7&#34;&gt; &lt;code&gt;SplEnum&lt;/code&gt; &lt;/a&gt; 類套件庫，方能直接使用 &lt;code&gt;Enum&lt;/code&gt; 的功能。或是可以透過以下的方式達到接近的效果。&lt;/p&gt;
&lt;h2 id=&#34;極簡易版-enum&#34;&gt;極簡易版 Enum&lt;/h2&gt;
&lt;p&gt;首先，建立一個 &lt;code&gt;Enum&lt;/code&gt; 類，一樣舉例為 HTTP 的 response code&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;-webkit-text-size-adjust:none;&#34;&gt;&lt;code class=&#34;language-php&#34; data-lang=&#34;php&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#66d9ef&#34;&gt;abstract&lt;/span&gt; &lt;span style=&#34;color:#66d9ef&#34;&gt;class&lt;/span&gt; &lt;span style=&#34;color:#a6e22e&#34;&gt;HTTPResponsecodeEnum&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:#66d9ef&#34;&gt;OK&lt;/span&gt; &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; &lt;span style=&#34;color:#ae81ff&#34;&gt;200&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:#66d9ef&#34;&gt;BAD_REQUEST&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 style=&#34;color:#66d9ef&#34;&gt;const&lt;/span&gt; &lt;span style=&#34;color:#66d9ef&#34;&gt;NOT_FOUND&lt;/span&gt; &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; &lt;span style=&#34;color:#ae81ff&#34;&gt;404&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;這樣就完成最基本的 &lt;code&gt;Enum&lt;/code&gt; 拉，只要透過簡單的 &lt;code&gt;if&lt;/code&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;-webkit-text-size-adjust:none;&#34;&gt;&lt;code class=&#34;language-PHP&#34; data-lang=&#34;PHP&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#66d9ef&#34;&gt;if&lt;/span&gt;($status_code &lt;span style=&#34;color:#f92672&#34;&gt;==&lt;/span&gt; &lt;span style=&#34;color:#a6e22e&#34;&gt;HTTPResponsecodeEnum&lt;/span&gt;&lt;span style=&#34;color:#f92672&#34;&gt;::&lt;/span&gt;&lt;span style=&#34;color:#a6e22e&#34;&gt;OK&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:#75715e&#34;&gt;// foo();
&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;h2 id=&#34;具有驗證功能的-enum&#34;&gt;具有驗證功能的 Enum&lt;/h2&gt;
&lt;p&gt;根據防禦性程式寫法，我們知道千萬不要相信使用者傳過來的東西，必須去驗證資料是否正確時，極簡版本已經無法勝任。這時候可以使用 &lt;a href=&#34;https://www.php.net/manual/en/class.reflectionclass.php&#34;&gt;ReflectionClass&lt;/a&gt; 這個這個類別協助，由這個類來取得 Class 中的常數。&lt;/p&gt;</description>
    </item>
    <item>
      <title>A piece of me</title>
      <link>https://www.omegaatt.com/about/</link>
      <pubDate>Mon, 01 Jan 0001 00:00:00 +0000</pubDate><author>raiven.kao@gmail.com (Raiven Kao)</author>
      <guid>https://www.omegaatt.com/about/</guid>
      <description>&lt;p&gt;我是個熱衷於技術分享和學習的軟體工程師，專注於各種開源技術和工具的應用與實踐。在這裡，你將找到關於 Golang、Kubernetes、FRP、Prometheus 等多種技術的深入探討和實踐經驗。&lt;/p&gt;
&lt;h3 id=&#34;我的專長&#34;&gt;我的專長&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;Golang: 我對 Golang 有深入的研究和豐富的實踐經驗，從 HTTP 路由到記憶體管理，無所不包。&lt;/li&gt;
&lt;li&gt;Kubernetes: 熟悉 Kubernetes 的安裝和配置，包括使用 K3s 和 Cert Manager 進行集群管理。&lt;/li&gt;
&lt;li&gt;DevOps 工具: 擅長使用各種 DevOps 工具，如 WireGuard、FRP、Prometheus，來優化和監控系統。&lt;/li&gt;
&lt;li&gt;自動化和腳本: 使用 Golang 和其他腳本語言自動化各種任務，提高工作效率。&lt;/li&gt;
&lt;li&gt;系統配置: 對 Linux 系統有深入了解，包括在 WSL2 上配置 Systemd 和 Podman。&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 id=&#34;部落格內容&#34;&gt;部落格內容&lt;/h3&gt;
&lt;p&gt;在這個部落格中，我會分享我的工作筆記和學習心得，涵蓋以下主題：&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;軟體開發: 包括 Golang 的最佳實踐、HTTP 路由和鎖定機制等。
系統和網絡: 例如使用 WireGuard 建立安全的 VPN 連接，通過 FRP 設置內網穿透。
測試與自動化: 分享如何在敏捷開發中進行高效測試，以及使用 WakaTime 進行編碼時間跟蹤的經驗。
容器化和編排: 探討 Kubernetes 和 Docker 容器化技術，從安裝配置到實際應用。
&lt;/code&gt;&lt;/pre&gt;
&lt;h3 id=&#34;聯絡我&#34;&gt;聯絡我&lt;/h3&gt;
&lt;p&gt;如果你對我的內容有任何問題或建議，歡迎隨時聯絡我：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Email: &lt;a href=&#34;mailto:raiven.kao@gmail.com&#34;&gt;raiven.kao@gmail.com&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;GitHub: github.omegaatt.com&lt;/li&gt;
&lt;li&gt;LinkedIn: cv.omegaatt.com&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;謝謝你的訪問，希望你能在這裡找到有用的資訊並一起成長進步！&lt;/p&gt;</description>
    </item>
    <item>
      <title>Resume</title>
      <link>https://www.omegaatt.com/resume/</link>
      <pubDate>Mon, 01 Jan 0001 00:00:00 +0000</pubDate><author>raiven.kao@gmail.com (Raiven Kao)</author>
      <guid>https://www.omegaatt.com/resume/</guid>
      <description>&lt;object data=&#34;https://resume.omegaatt.com/resume.pdf&#34; type=&#34;application/pdf&#34; width=&#34;100%&#34; height=&#34;800px&#34;&gt;
    &lt;p&gt;can&#39;t display PDF？&lt;a href=&#34;https://resume.omegaatt.com/resume.pdf&#34;&gt;Download&lt;/a&gt;&lt;/p&gt;
&lt;/object&gt;</description>
    </item>
  </channel>
</rss>
