A smarter way to change directory: zoxide

在日常的開發工作中,我們經常需要在不同的目錄間切換。雖然 cd 命令已經足夠好用,但如果有一個更聰明的工具能記住我們最常用的目錄,並讓我們用最少的按鍵就能快速跳轉,那豈不是更棒? 這就是 zoxide 的用武之地。zoxide 是一個由 Rust 編寫的「更聰明的 cd 命令」,靈感來自 z 和 autojump。它會記住你最常使用的目錄,讓你只需輸入幾個字符就能快速跳轉。 zoxide 的主要特性 自動匹配: 不需要輸入完整路徑,zoxide 會根據輸入自動匹配最相關的目錄: 自動紀錄過去的目錄: zoxide 會記住你最常使用的目錄,讓你只需輸入幾個字符就能快速跳轉。這些資料可以使用 zoxide query 來查詢,或是 zoxide edit 來管理。 互動式選擇: 結合 fzf,可以互動式地選擇目標目錄 輕量快速: 用 Rust 編寫,啟動迅速,幾乎不會影響 shell 的啟動時間 安裝與配置 zoxide 的安裝可以參考 官方文件,我們就不多贅述了。 安裝完成後,我們需要在 shell 的配置文件中添加初始化命令。以 zsh 為例,在 ~/.zshrc 的末尾添加: eval "$(zoxide init zsh)" 重新打開終端或執行 source ~/.zshrc 後,zoxide 就可以使用了。 使用方法 zoxide 的基本用法非常直觀,經過自動執行 zoxide init zsh 後,我們可以使用 z 和 zi 自動匹配: ❯ ls boo bar baz ❯ z bo ❯ pwd /home/raiven/boo 即便沒有輸入完整路徑,也能夠自動匹配最相關的目錄 ❯ pwd /home/raiven/dev/omegaatt-blog ❯ z ❯ pwd /home/raiven ❯ z blog ❯ pwd /home/raiven/dev/omegaatt-blog 互動式選擇: ❯ zi ~ < 98/98(0) 296.0 /home/raiven/dev/omegaatt-blog 124.0 /home/raiven/dev/bookly 50.0 /home/raiven/dev 24.0 /home/raiven/dev/test 12.0 /home/raiven/ 這裡的 z 命令就像一個更聰明的 cd,而 zi 則是互動式版本。 ...

2024-09-24 · 1 min · 130 words
使用 Bitwarden 與自架後端 Vaultwarden 來管理密碼與 2FA Authenticator

使用 Bitwarden 與自架後端 Vaultwarden 來管理密碼與 2FA Authenticator

在尋覓有哪些 self-hosted 專案好玩時,偶然發現了 1password、LastPass 的開源替代方案,甚至後端資料庫能自架,決定架來用用看。 為什麼要自架?完全控制資料、隱私考量、或者純粹是學習和折騰 (符合 self-hosted 精神)。但控制權也意味著責任,特別是安全和備份。 這篇會專注在使用 Bitwarden 客戶端搭配輕量級的 Vaultwarden 後端。 使用 Bitwarden 來管理密碼 Bitwarden 是一款流行且功能強大的密碼管理工具,它提供了一個安全的方法來存儲和管理所有密碼。作為一個開源產品,Bitwarden 允許用戶選擇自行托管其服務,這意味著用戶可以在自己的服務器上運行 Bitwarden,從而更好地控制自己的數據安全。 Bitwarden 的特點 安全性: Bitwarden 使用端到端加密,確保只有您可以訪問您的密碼。 跨平台支持: 支持 Windows、macOS、Linux、Android 和 iOS。 易於使用: 提供直觀的用戶界面和簡單的操作流程。 開源: 開源,增加了透明度和安全性。 Bitwarden 同時支援基於時間的一次性密碼,讓 TOTP 也能自動填入。也能透過 github.com/scito/extract_otp_secrets 來提取 Google Authenticator 內的 2FA 資訊,儲存進 Bitwarden 中。 Bitwarden 開源了 client 與 server,在 server 端的選擇有以下: 使用 Bitwarden 提供的官方服務,又分為免費跟付費,但這個選擇就跟 1p 沒太多區別。 自架 Bitwarden 提供的 open source server,由於是使用 C# 與 mssql,吃的記憶體著實太多。 自架 Bitwarden 相容的後端,我採用的是 rust 實做的 Vaultwarden,搭配 sqlite,記憶體使用量與官方的 C# 不是一個量級的。 Bitwarden 架構 bitwarden 的 local storage 都是儲存加密後的密碼資料,不會使用明碼儲存,故上傳到 server 上的也僅僅是加密後的密碼資料。 ...

2024-09-01 · 3 min · 602 words

在 Firefox 上使用 PWA 將網頁應用安裝成 Desktop App

前言 開應用時,跨平台的桌面解決方案時常困擾開發者,在 Linux Desktop 上要支援多種桌面協議與桌面管理系統,更是複雜許多。時常會看到 Electron 等等基於 chromium 的技術。 當然也可以像是 zed.dev 這樣的 geek 精神,自己寫了一套跨平台的 GUI 框架,但多數新創公司可能沒有這些資源來實現。例如已經非常成熟的 Notion.so、Obsidian 等等都是基於 Electron 實現的。 我並非一個 anti-Electron 或是 deGoogle 的人,但試圖找到其他解決方案正式樂趣之所在。 PWA(Progressive Web App) 詳盡的 PWA 技術可以到 mozilla 的文件中查看,對我來說只要可以達到目的:可以在 Windows、Mac、Linux 上封裝成 Desktop Aplication 即可。 官方支援 mozilla 官方有一個文件針對「安裝網頁應用到桌面環境」的歷史講解,可以參考官方文件。 文中提到過去將網址「儲存」在桌面上的類似「書籤」的方法(SSB),以及與 PWA 的不同之處:PWA 的使用者資料是儲存在桌面環境中。 PWA Add-on 上述文件內有提到需要使用 Fan-maded 的 PWA Add-on 來安裝 PWA:Progressive Web Apps for Firefox by Filip Štamcar。 他是一個開源的 Firefox 擴充套件(filips123/PWAsForFirefox),具有完整的文件。 安裝過程 在 Firefox 瀏覽氣上安裝 PWA Add-on 瀏覽器會自動跳到設定頁面,首先會需要同意 EULA 接著會需要安裝 connector,需要透過該 connector 才能連結瀏覽器本身與 extension。 connector 由 Rust 編寫,使該 extension 可以在跨平台的桌面環境中管理 firefox 設定檔、runtime 等等未開放給 extension 存取的資源。 Windows: Windows 的 connector 除了手動安裝外,也推薦使用 winget 來安裝,類似於 apt/yum 的套件管理工具。 Linux Linux 的 connector 可以到 github release 上找到,例如我的作業系統是 OpenSUSE Tumbleweed,則可以下載 .rpm 檔案。 sudo zypper in ./firefoxpwa-2.12.1-1.x86_64.rpm Debian/Ubuntu(DEB): Redhat/Fedora/OpenSUSE(RPM): 接著會需要安裝不同於原生 Firefox 的 runtime,根據描述,會下載並由 connector 自動修改成支援 PWA 的 runtime。 到這邊已經完成 PWA extension 的安裝,可以嘗試使用 PWA 的方式來安裝 web app to desktop app。 首先需要打開你想要安裝成 Desktop App 的網頁,如果在不同的網頁嘗試設定,extension 會自動判斷該網頁 CORS Error,即便是 https://www.notion.so 與 https://notion.so 也屬於不同的 Origin。 接著點擊 PWAForFirefox 的圖示,可以看到已經安裝好的 extension。 接著一步一步的跟著指引設定好該 PWA 的設定。 完成,可以在應用程式選單內找到剛剛建立的 PWA 應用程式。 開啟後可以自動開啟網頁應用程式,並具有獨立的設定檔。 使用感受 三四年前在 Windows 上習慣使用 Notion Desktop 版本,在 Linux 上反而已經習慣要用網頁來打開,這次嘗試使用 PWA 後倒是經常忘記有將它安裝成 Desktop Application。 ...

2024-08-25 · 2 min · 226 words

透過內建的 pprof 工具來分析 Golang 發生的 memory leak

前言 某天下午,公司的 cronjob daemon 無預警的被 GCP OOM Kill 了,且程式碼沒有看出明顯的原因。 根據過去的經驗,local 開發時會使用 go tool pprof 來分析 CPU profile 或是 memory 與 trace 的問題,詳細可以參考 Go 官方文件。 由於我們的程式碼是一個基於 gin 的 http service,因此可以使用 gin 提供的 pprof 來快速建立 endpoint。 gin pprof gin 的 pprof package 提供了數個基於 net/http/pprof 的 endpoint,可以分別為: /: 基本的 pprof 的 static page,可以分析 CPU 與 memory 的問題。 /cmdline: 分析 command line 的問題。 /profile: 分析 CPU profile 的問題,可以透過 query string 來指定 CPU profile 的 duration。 /symbol: 分析 symbol table 的問題。 /trace: 分析 trace 的問題。 /goroutine: 分析 goroutine 的問題,這也是本文中重點查看的 endpoint。 /heap: 分析 heap 的問題。 可以在 http server 中加入以下程式碼來啟用 pprof: ...

2024-08-17 · 3 min · 441 words

從實務經驗重新認識 TLS

因為工作需要部署 grafana,重新認識了 TLS 的流程,藉此機會把學習過程紀錄下來。 主要分為理論與案例分享兩個大章節,理論章節主要是在講「應該要知道但學過就會忘」的內行,案例分享則為這一次部署 grafana 時的經驗分享。 理論 介紹 TLS 是什麼 TLS(Transport Layer Security,傳輸層安全協議)是用來保護互聯網通訊安全的協議。它能夠確保數據在互聯網上傳輸過程中的機密性、完整性和真實性。TLS 是從 SSL(Secure Sockets Layer)發展而來的,因此我們可能也可以把它稱為 SSL/TLS。 為什麼需要 TLS 在現代互聯網環境中,數據安全性變得至關重要。無論是電商、網路銀行或是社交平台,都需要確保用戶的機敏資訊不會被攔截或篡改。TLS 可以加密通訊內容,防止第三方竊聽,並且能驗證通訊雙方的身份,防止中間人攻擊(MITM)。 TLS 的應用場景 TLS 被廣泛應用於各種需要保護數據傳輸的場景,包括但不限於: 網站和應用程式的 HTTPS 通訊 電子郵件的加密傳輸(如 SMTPS、IMAPS、POP3S) VPN 通訊 即時消息應用程式 各類雲服務和 API TLS 基本概念 公鑰加密和私鑰加密 公鑰加密和私鑰加密是 TLS 的基礎。每個參與通訊的實體都擁有一對密鑰:公鑰和私鑰。公鑰是公開的,任何人都可以用來加密訊息,而私鑰是保密的,只有擁有者可以用來解密訊息。這樣的機制確保了即使加密訊息被攔截,也只有擁有私鑰的人能夠解密,細節可以回顧密碼學或計算機概論。 對稱加密和非對稱加密 對稱加密使用相同的密鑰來加密和解密數據,而非對稱加密則使用一對密鑰(公鑰和私鑰)。TLS 結合了這兩種加密方式:在握手階段使用非對稱加密來安全地交換對稱加密的密鑰,之後的通訊則使用對稱加密來提高效率。 數位證書和證書授權機構(CA) 數位證書是用來證明公鑰擁有者身份的電子文件,通常由證書授權機構(CA)簽發。證書包含公鑰、擁有者訊息以及 CA 的數字簽名。瀏覽器和其他應用程式可以驗證證書的真實性,確保通訊對象的身份。 數位證書的主要類型有: 域名驗證(DV)證書:僅驗證域名的所有權。 組織驗證(OV)證書:除了驗證域名,還驗證組織的合法性。 擴展驗證(EV)證書:提供最高級別的驗證,包括嚴格的身份檢查,瀏覽器地址欄顯示綠色的公司名稱。 TLS 的工作原理 握手過程(Handshake Process) TLS 握手過程是建立安全連接的第一步,確保通訊雙方能夠安全地交換加密訊息。握手過程大致分為以下幾個步驟: 客戶端問候(Client Hello):客戶端發送一個問候消息給伺服器,包含支持的 TLS 版本、加密算法、隨機數和其他必要訊息。 伺服器問候(Server Hello):伺服器回應客戶端的問候消息,選擇一個加密算法,並發送伺服器的隨機數。 伺服器證書(Server Certificate):伺服器發送其數位證書給客戶端,用於驗證伺服器的身份。證書包含伺服器的公鑰和由 CA 簽名的證書。 密鑰交換(Key Exchange):伺服器和客戶端交換密鑰訊息,使用非對稱加密算法(如 RSA、ECDHE)安全地生成會話密鑰。這個會話密鑰將用於之後的對稱加密通訊。 加密通訊(Encrypted Communication):客戶端和伺服器使用協商好的會話密鑰進行加密通訊。這確保了後續的數據傳輸是安全的。 以 TLS 1.3 的工作方式為例: ...

2024-06-22 · 10 min · 2085 words

透過 throughput 與 latency 的關係解決高延遲問題

在軟體工程中,我們常會面臨服務 high latency 的問題,而要解決這個問題,理解 throughput 與 latency 的關係是關鍵。 名詞定義 什麼是 throughput 與 latency? latency:處理一個請求需要花多少時間。從請求進來到回應出去的時間差。通常用毫秒 (ms) 來量測。越低越好。 throughput:單位時間內能處理多少請求。通常用 RPS (Requests Per Second) 或 QPS (Queries Per Second) 來表示。越高越好。 server 如何工作 server 接收請求後,由一個或多執行緒(threads)處理請求並生成回應。單執行緒 server 一次只能處理一個請求,而多執行緒 server 可以同時處理多個請求。 想像一下你的伺服器就是一家自助洗衣店。請求就像是帶衣服來洗的客人。 單執行緒的伺服器就像店裡只有一台洗衣機,一次只能服務一個客人。客人得排隊。 多執行緒/多進程/異步 I/O 的伺服器就像有多台洗衣機,可以同時服務多個客人。 latency:從顧客開始操作洗衣機到洗衣完成的時間。這包括顧客等候空閒洗衣機的時間,以及實際洗衣的時間。等候時間越長,latency 越高。 throughput:洗衣店每小時完成的洗衣次數。洗衣機數量越多,洗衣店能處理的顧客數量越多,throughput 越高。 latency 與 throughput 的關係 很多人以為 latency 和 throughput 的關係是簡單的線性反比,或者覺得加機器就能解決一切。但現實更殘酷一點。 起初,當客人不多(請求量低)的時候,洗衣機(伺服器資源)很空閒,客人一來就能馬上洗,這時候 latency 很低,主要就是洗衣機本身運作的時間(我們稱之為 service time 或 true latency)。 隨著客人變多(請求量增加),洗衣機開始被佔滿。當所有洗衣機都在運作時,新來的客人就得排隊等了。這段等待時間就是 queueing latency。 Total Latency = Queuing Latency + Service Time ...

2024-06-16 · 3 min · 631 words

基於 Golang 的 Grafana Dashboard 與 JWT 認證的前後端實作

在工作上有一個需求是需要做一些 OLAP,原訂計畫是使用 Google Looker(ver. Google Cloud Core),礙於量小不符合經濟效益,決定用 Grafana 這個較熟悉的開源套件來幫助我們做視覺化的處理。 這篇文章的範例可以在 omegaatt36/grafana-embed-example 中找到所有的 source code 我的 Use Case 為已經有一組 SHA512 產生的 Key,以下的內容為使用 HS512 進行簽名與認證。 流程 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->>B: Open Web Page B->>S: Request JWT Token S->>B: Return JWT Token B->>I: Load iframe I->>G: Request Dashboard with JWT Token G->>I: Return Dashboard I->>B: Display Dashboard in iframe B->>U: Show Dashboard end Grafana 配置 主要是針對 grafana.ini 做修改 ...

2024-06-10 · 5 min · 964 words

從 vuepress 遷移到 hugo 的心得

前言 在網頁開發中,選擇合適的靜態網站生成器非常重要。過去我們使用 VuePress 來建立部落格網站,但在使用過程中遇到了一些痛點,最終決定遷移到 Hugo。本篇文章分享遷移過程中的一些心得。 當初選擇 VuePress 而非 Hugo 的原因 最初想要撰寫部落格時,已經有再 hugo 與 vuepress 間做選擇,當時覺得 hexo 與 hugo 實在是爛大街了,想要一些與眾不同的體驗。 選擇 VuePress 是基於以下幾點考量: 朋友推薦,他是一名資深前端工程師,是 vuepress core-team 成員。 僅需要撰寫 markdown,便能產生靜態的部落格。 使用 theme plugin,可以方便的美化部落格,僅需要專注在內容的撰寫,不需要操心部落格是不是白底黑字而已。 使用 VuePress 的痛點 起初我所使用的 theme plugin 為 vuepress-theme-reco(1.x),並且搭配 vuepress 1.x 來進行建構。或是 vitepress,然而,在使用 VuePress 的過程中,我們也遇到了一些問題: 構建速度 我的部落格是無付費的使用 Netlify 進行託管,使用 vuepress 進行建構總是會花超過三分鐘,當進行 pr 的 preview build 時將會耗費大量的執行時間。 版本更新 過了一兩年,vuepress 官方開始推動 vuepress next(2.x),vuepress-theme-reco 的作者也著手更新 next(2.x) 版本。期間從 alpha 版本到 beta 版本,當 vuepress 與 theme plugin 有 API 不同不的問題,就容易導致 build failed,這個情況到了 rc 版本也沒有改善。 ...

2024-06-10 · 2 min · 239 words

使用內建的 rsync 備份 Truenas Scale 到 Proxmox Backup Server

前言 過去我會使用 backup script 配合 crontab 來定期的備份 nas 的資料,這次更換了 Proxmox Backup Server 的物理機後,多了一個硬碟的空間好讓我實驗 Truenas Scale 的備份機制。 差異 rsync 本身並沒有 server/client 的概念,只有 source 與 destination。 過去我會在備份主機上透過 samba 來 mount Nas 到資料夾內,檢查有沒有 mount 成功才在備份主機上使用 rsync。 而 Truenas Scale 提供的 Data Protection 功能中,內建了 Rsync Tasks 模組,透過預先建立好的 ssh credential 來呼叫備份主機進行 rsync。 同樣都是由備份主機來進行 rsync,主要是任務的執行呼叫是 Truenas Scale 本身還是備份主基本身。 在 Truenas Scale 上建立備份任務 建立 SSH Pair 首先到 Credentials -> Backup Credentials 的頁面 點擊 SSH Configurations 中的 Add ...

2024-05-19 · 1 min · 174 words

用 testcontainers 在本地開發 Go 應用程式

介紹 使用 testcontainers 是在本地開發 Golang 應用程式的一個高效方式。這可以讓我們在不需要依賴外部環境的情況下,模擬應用程式在實際生產環境中的運行狀況。 安裝 testcontainers 在 Go 專案中,我們可以通過以下指令來導入 testcontainers go get github.com/testcontainers/testcontainers-go 透過 Redis 實踐一個 rate limiter package user type Limiter struct { client *redis.Client limit int limitPeriod time.Duration // 1 hour for limitPeriod counterWindow time.Duration // 1 minute for example, 1/60 of the period } func NewLimiter(client *redis.Client, limit int, period, expiry time.Duration) *Limiter { return &Limiter{ client: client, limit: limit, limitPeriod: period, counterWindow: expiry, } } func (r *Limiter) AllowRequest(ctx context.Context, key string, incr int) error { now := time.Now() timestamp := fmt.Sprint(now.Truncate(r.counterWindow).Unix()) val, err := r.client.HIncrBy(ctx, key, timestamp, int64(incr)).Result() if err != nil { return err } if val >= int64(r.limit) { return ErrRateLimitExceeded(0, r.limit, r.limitPeriod, now.Add(r.limitPeriod)) } r.client.Expire(ctx, key, r.limitPeriod) result, err := r.client.HGetAll(ctx, key).Result() if err != nil { return err } threshold := fmt.Sprint(now.Add(-r.limitPeriod).Unix()) total := 0 for k, v := range result { if k > threshold { i, _ := strconv.Atoi(v) total += i } else { r.client.HDel(ctx, key, k) } } if total >= int(r.limit) { return ErrRateLimitExceeded(0, r.limit, r.limitPeriod, now.Add(r.limitPeriod)) } return nil } type RateLimitExceeded struct { Remaining int Limit int Period time.Duration Reset time.Time } func ErrRateLimitExceeded(remaining int, limit int, period time.Duration, reset time.Time) error { return RateLimitExceeded{ Remaining: remaining, Limit: limit, Period: period, Reset: reset, } } func (e RateLimitExceeded) Error() string { return fmt.Sprintf( "rate limit of %d per %v has been exceeded and resets at %v", e.Limit, e.Period, e.Reset) } 創建和啟動 Redis 容器 正式的產品通常會使用 config 來管理 Redis 位置,這個 demo 中直接使用 localhost:6379 來展示。 ...

2024-05-19 · 3 min · 474 words