從 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....

2024-05-19 · 3 min · 474 words

透過 Distrobox 解決 Linux 環境依賴問題

前言 自從一年前嘗試使用 KDE Neon 後,主要的桌面開發環境就從 wsl 整個遷移到 Linux 上。這一年來感受到,KDE Neon 可能正如 reddit 上的留言闡述的那樣,只是 KDE 團隊的「實驗場」,可以享受到「最」新的 Plasma 與「較」新的 Kernel,卻始終是基於 Ubuntu 22.04 LTS,即便 Ubuntu 24.04 LTS 推出之際,也不再讓我感受性感。 因緣際會,在 Ubuntu 24.04 LTS 發布之際,我決定從 KDE Neon distro hopping 到 OpenSUSE Tumbleweed,這是一個由 SUSE 公司推出的滾動式發行版,軟體包在 SUSE 測試過後便會推送到 OpenSUSE 的 Repo(其他 OpenSUSE 的功能省略)。這次的轉換主要是為了獲得最前沿的 Linux 體驗。OpenSUSE Tumbleweed 的滾動更新模式能確保我始終使用最新的軟體和技術,這對我來說是一個有趣的選擇,即便有可能收到有問題的 xz 更新 XD。 ❯ fastfetch ...... raiven@raiven-suse .,cdxxxoc,. .:kKMMMNWMMMNk:. ------------------ cKMMN0OOOKWMMXo. A ;0MWk:' ':OMMk. OS: openSUSE Tumbleweed 20240517 x86_64 ;WMK;' 'lKMMNM, :NMK' 'OMW; Host: FMV UH-X cMW; WMMMN ,XMK' oMM....

2024-05-18 · 6 min · 1213 words

如何不啟動 container 從 image 中提取可執行檔

::: info 雖然文章內大多指令都是使用 docker,但由於是標準的 OCI image,使用 Podman 也是一樣的效果。 ::: 某段時間內,我會將所有需要執行的 binary 使用 containerize 給打包起來執行,例如: 需要將前端環境給跑起來,我會啟動一個 node 環境的 container: docker run --rm -it --name f2e --net=host -v $(pwd):/app \ docker.io/library/node:20-bookworm \ bash 需要安裝某個基於 golang 的 cli 工具,會使用自己寫的腳本,在 container 內進行建構。 需要透過 Liquibase 來進行 db migration,並不會選擇在本地安裝 maven 環境,一樣是啟動一個 container 來執行。 這麼做的好處是,「目前」大多數的 server/cli tools 至少會編譯 x86 架構下的可執行檔,我只需要確保 container 環境內可以工作,就可以在不同硬體的開發環境下游走。極度偏激的來說,無法確保安裝的 binary 有沒有受過污染,無論開源或閉源與否,也可以透過 Podman 來啟動 rootless 的 container 確保本機的安全。 但有時候會遇到別人打包的 image 十分肥大,即便程式可執行檔只需要幾 MB,編譯後的 image 確有幾百 MB(大多數是腳本語言),蒙生了從 image 中提取 executable 的想法,藉此也學習 docker layer 間的關係。...

2024-04-14 · 4 min · 806 words

如何利用 Open Policy Agent 配合 Golang 建構彈性的 RBAC 模組

RBAC 概念簡介 在我們探討如何利用 Open Policy Agent (以下簡稱 OPA) 和 Golang 建立一個彈性的 RBAC 模組之前,先讓我們來了解一下 RBAC的基本概念。 RBAC(Role-Based Access Control,基於角色的訪問控制)是一種廣泛應用的訪問控制策略,在軟體安全性領域尤為重要。其核心思想是將系統訪問權限與用戶的角色(職位、責任或職務)關聯起來,而不是直接與個別用戶關聯。這意味著訪問權限被捆綁到角色上,然後將用戶分配給這些角色。舉個例子,一個「管理員」角色可能有權訪問系統的所有資源,而「員工」角色則只能訪問特定部分的資源。 RBAC 的主要優勢在於其靈活性和簡化的權限管理。當需要變更權限時,只需修改角色的訪問權限,而不需要為每個用戶單獨設定。這不僅使權限管理更為高效,也減少了錯誤配置的可能性,提高了整體的系統安全性。 在實踐中,RBAC 允許創建精細且靈活的策略,以滿足複雜的商業和安全需求。無論是大型企業還是小型團隊,RBAC 都提供了一個可靠的框架,來確保正確的用戶擁有適當的訪問權限,從而保護關鍵資源免受未授權訪問。 可以參考 Cloudflare 的文章,簡單來說就是什麼「角色」能夠對什麼「資源」做什麼「操作」。 Open Policy Agent (OPA) 介紹 OPA 是一個「Strategy as Code」的開源專案,專門設計用於統一地管理和執行跨不同系統的策略。它不僅提供了一個高級的策略語言——Rego,還支援將策略作為代碼與應用程式的其他部分一同存儲、版本控制和部署。OPA 的這種設計使其能夠輕鬆集成到微服務、Kubernetes、CI/CD 管道、API 網關等多種環境中。顯著特點是其策略的編寫方式。Rego 是一種專門為策略和規則定制的查詢語言,它使開發者能夠以聲明式方式描述策略和規則,從而確保這些策略既容易理解又易於維護。這對於建立複雜的 RBAC 系統尤為重要,因為它允許策略的靈活性和可擴展性,同時又保持了清晰和易於審查的結構。 我們可以利用 OPA 提供的 API 來評估和執行這些策略。這意味著開發者可以在 Golang 程式碼中直接嵌入策略判斷的邏輯,從而實現動態、細粒度的訪問控制。這種方法的一個優點是,它支援在 runtime 動態更新策略,或是編譯進 binary,從而提供更大的靈活性和即時性。 整合 OPA 與 Golang 透過官方案例來了解如何使用 參考了 OPA 官方的 rbac 章節,並加以修改。使用最簡單的例子: admin can read user bob is admin ------------------- bob can read user 轉化成 RBAC 模型即為:...

2024-04-04 · 4 min · 722 words

Agile Testing 閱讀筆記

鋪墊:敏捷開發價值觀、原則與實踐 有什麼開發,就有什麼測試,傳統開發就有傳統測試,敏捷開發就應該要推行敏捷測試。在討論敏捷測試前,應該先理解敏捷開發模式,否則理解敏捷測試會很困難。 敏捷開發是一種思想或稱作方法論,通過不斷迭代與增量發布,最終交付符合用戶價值的產品。 書中提到一些敏捷開發的歷史、演進與框架 PDCA 循環 輕量級軟體開發 減少複雜的文件,強調人員的互動 敏捷宣言 XP(eXtreme Programing):較多是著重在軟體開發,例如 TDD、pair programing、CI 等等。 BDD(行為驅動開發):使用「通用語言」來描述測試案例,將 User Story 的細節進行完整地描述。 Feature(特性): 購物車功能 Scenario(情境): 添加商品到購物車 Given(假設): 用戶已經登錄到購物平台並且正在瀏覽商品 When(當): 用戶點擊某個商品的「添加到購物車」按鈕 Then(那麼): 該商品應該被添加到用戶的購物車中 And(並且): 購物車中的商品總數應該更新 FDD(特性驅動開發):使用制式結構來建構特性列表 <action> the <result> <by|for|of|to> a(n) <object> Scrum:確保每天、每個階段都向著目標明確進行的一種「方法」。 推薦看 Scrum 提倡者自己寫的 SCRUM:用一半的時間做兩倍的事 DevOps 與敏捷的關係 DevOps 可以看作是敏捷的延伸,打通軟體開發、測試、交付、維護中的層層牆壁。 敏捷宣言 藉著親自並協助他人進行軟體開發,我們正致力於發掘更優良的軟體開發方法。透過這樣的努力,我們已建立以下價值觀: 個人與互動 重於 流程與工具 可用的軟體 重於 詳盡的文件 與客戶合作 重於 合約協商 回應變化 重於 遵循計劃 也就是說,雖然右側項目有其價值,但我們更重視左側項目。 敏捷測試之道 敏決測試不是一種測試方法,而是為了適應敏捷開發而設計的一套軟體測試解決方案。 敏捷測試宣言 Full Lifecycle Testing OVER Isolated Testing Phase Team Shared Responsibility OVER Testers Ensure Quality Continuous Targeted Automation OVER Widespread Regression Testing Quality Built-in OVER Defect Detection Full Lifecycle Testing 強調測試左移與右移,並非將測試「移動」到兩個端點,而是全程測試的介入。...

2024-03-24 · 3 min · 456 words

那些在 Backend Sharing 中出現的人事物

《最高學以致用法》、《最高學習法》這兩本書是我在 2023 年上半年讀了覺得挺有意思的書,核心概念就是「產出」,例如唸書時能夠回答同學問題的,肯定都已經精通該知識點了。 加入 KryptoGO 後,因為團隊的成長,Leader 開始嘗試舉行兩週一次的 Backend Sharing,不僅是分享工作上遇到的疑難雜症,更可以交流不同的知識點。 起初,我可以我分享了一些過去用過的工具與知識點,隨著時間的流逝,開始感受到黔驢技窮,於是我也仿效了《刻意練習》,不斷的為了能有更好的分享品質而學習。 以下是這半年來我在 Backend Sharing 中或多或少提及或是討論到的,又分為解決方案、小工具、方法論。 小工具 由於喜歡折騰 Homelab,時不時會到 r/selfhosted、r/opensource/ 尋找一些開源的自架方案或小工具,有一些大幅度的改善了我的開發流程,而有一些則漸漸的不再使用 exa & eza & bat eza、exa、bat 都是基於 rust 寫成的 cli 替代品,exa、eza 對標 cd,而 bat 對標 cat,我會在 .zshrc 中寫上 alias。 if command -v bat &> /dev/null; then alias cat=bat; fi if command -v eza &> /dev/null; then alias ls="eza --icons" alias ll="eza --icons -lh" alias tree="eza --icons --tree" fi 還有其他性質相同的 rust 寫的小工具諸如: zoxide、topgrade、alacritty、bottom...

2024-03-24 · 2 min · 321 words

透過 CLI 調整藍牙耳機音訊設定

接續著 KDE neon / Ubuntu 啟用 PipeWire 與 LDAC/AAC/AptX 藍芽編碼,每當連線到藍芽耳機時,語音設定檔在使用 LDAC 後,就無法同時使用麥克風,這樣一來在開會時,就需要手動到設定裡面去調整語音設定檔。 身為一個懶惰鬼,可以用鍵盤解決的就不會用滑鼠去點,發現可以通過 pactl(PulseAudio)在 cli 直接設定 audio profile。 首先,我們通過 bluetoothctl 列出藍芽設備的實體位置 $ bluetoothctl > [EAH-A800]# devices > Device 88:C9:E8:B1:5D:AE WF-1000XM4 > Device DC:22:D2:85:85:15 MX Master 3S > Device B8:20:8E:35:CB:D0 EAH-A800 文章內舉例的目標為 EAH-A800,也就是 B8:20:8E:35:CB:D0 接著需要確認目前藍牙裝置支持的音訊設定檔。可以透過 pactl list cards short 來列出目前啟用的設備 $ pactl list cards short 47 alsa_card.usb-Shure_Inc_Shure_MV7-00 alsa 48 alsa_card.pci-0000_00_1f.3-platform-skl_hda_dsp_generic alsa 49 alsa_card.usb-NuPrime_NuPrime_DAC-9H-00 alsa 1002 bluez_card.B8_20_8E_35_CB_D0 module-bluez5-device.c 發現目標為 1002 bluez_card.B8_20_8E_35_CB_D0 再來通過 pactl list cards 來獲取所有音訊設定檔...

2024-01-28 · 2 min · 346 words

Golang 隱式記憶體別名 Implicit Memory Aliasing 與其檢測方法

在使用 Golang 時,我們可能會遇到一種稱為隱式記憶體別名(Implicit Memory Aliasing)的問題。這篇文章將探討這個問題以及如何使用工具和語言特性來解決它。 隱式記憶體別名的問題 隱式記憶體別名主要發生在 range 語句中。當使用 range 對一個切片 slice 或映射 map 進行迭代時,Iterator 在每次迭代中並不是唯一的實例。這可能導致意外的行為,特別是在並發環境或當迭代變量被指針引用時。 slice with Implicit Memory Aliasing 下面示例可能是基本的 golang 面試題,會問你迭代後的 pointers 內的 Name 為何 答案是 Joe Joe Joe package main import "fmt" type Person struct { Name string Gender string } func main() { persons := []Person{ {Name: "John", Gender: "M"}, {Name: "Jane", Gender: "F"}, {Name: "Joe", Gender: "X"}, } pointers := make([]*string, len(persons)) for index, person := range persons { fmt....

2023-11-12 · 2 min · 368 words