Skip to main content

3 posts tagged with "homelab"

View All Tags

· 7 min read
Wei Ji

前情提要​

info

前情提要有點長,摘要如下:

  • 我的開源專案口袋名單有很多專案等著我去探索,LLM (Large language model) 是一種潛在能協助我的工具。
  • 我不傾向使用 LLM 來寫程式。1
  • 就算使用 LLM 產生文件也應該處於某種程度的控制。2
  • 文件本身應該是 RAG 體系兼容或是 RAG 體系友善的。3

閃亮事物​

作為一個重度閃亮事物症候群患者,我的口袋名單裡有一堆「我想理解、我想修改」的開源專案,諸如:

info

「Classic Minecraft」是指非常早期的 Minecraft 版本,不包含血量、怪物...等生存要素,事實是網路上流傳很多工程師練手的開源「Minecraft Clone」 都是 Classic 版。

LLM​

LLM 橫空出世,衝擊了產業生態,甚至公司內也開始推廣使用 Agent Coding,具體地說是使用 Cursor 寫程式。親自使用一段時間之後,加上公司內其他人的使用情況,我認為這不是正確的 LLM 使用方式。

用 LLM 來「寫文件」​

同事使用 LLM 生成的數千行的垃圾文件讓我有感而發寫了一篇「LLM 與機械手臂的類比」,簡單來說:忽略「人類大腦處理資料的能力是有限的」而讓大量生成式資訊灌入專案反而是一種精神傷害而沒有任何幫助。

用 LLM 來「寫程式」​

LLM 用來產生「零到一」的範例程式碼確實方便,不過 LLM 有「自己造輪子」的傾向,在複雜的專案中它的表現不盡理想。從職涯規劃的角度,我也有感而發寫了一篇「從賣油翁談『人寫程式』與『人寫提示詞讓 LLM 寫程式』的抉擇」,簡單來說:手寫磨練自己的肌肉記憶或是用提示詞來逃避程式碼,我個人傾向選擇前者。

llms.txt 的缺點​

為了將資訊傳遞給 LLM,出現了像是 repomix 或 gitingest 這樣的工具,用來將其他型態的資料轉換成一個巨大的純文字檔案。這種檔案不只不適合人類閱讀,而且很容易超出 LLM 的上下文窗口,實際上依然需要透過切塊 (Chunk)、嵌入 (Embedding)、向量資料庫...等流程建立的 RAG 系統才能有效的使用這些資訊。

在嘗試使用了 AnythingLLM、Repomix 和 Gitingest 等工具之後,有感而發寫了一篇關於 llms.txt 的廢文「回到一切的原點、LLM 系統的基石 - LLMs.txt、向量資料庫與 RAG」。

TiddlyWiki - 人類與機器通用的可攜格式​

TiddlyWiki 的幾個特點剛好可以用來解決上述問題,TiddlyWiki 有兩種儲存資料的型態:

  • 前端的 SPA 模式:單一 html 包含了資料以及 Tiddlywiki 本身的程式。
  • 後端的 server 模式:每個條目(Tiddlers)可以獨立以檔案的形式存在。

在 SPA 模式下,它提供一種人類友善的可攜體驗,只要有網頁瀏覽器就能閱讀或編輯,無須安裝其他軟體。並且其「非線性閱讀」的特性可以大幅降低認知負荷,避免人類在上千行的文件穿梭。

在 server 模式下,條目(Tiddlers) 可以放在個別的檔案內,提供一個版本控制 (git) 友善的環境。更可以透過 HTTP API 對條目進行新增、修改、移除。

TiddlyWiki 的設計上,鼓勵不要在一個 Tiddler 放入過多的資訊,每個 Tiddler 都應該像便條紙一樣傳遞一個單一主題的資訊,如此設計除了有效的降低人類的認知負荷以外,更讓 Tiddler 成為 RAG 切塊絕佳的單元,大型單一文件進行切塊的風險就是可能切割在不理想的地方造成語意被破壞。

因此 TiddlyWiki 提供了一種可攜、適合人類而且同時適合機械的資料型態。

Footnotes​

  1. 從賣油翁談「人寫程式」與「人寫提示詞讓 LLM 寫程式」的抉擇. Retrieved 2025-10-06 from https://flyskypie.github.io/posts/2025-10-06_oil-seller ↩

  2. LLM 與機械手臂的類比. Retrieved 2025-10-06 from https://flyskypie.github.io/posts/2025-09-09_llm-and-robot-arm ↩

  3. 回到一切的原點、LLM 系統的基石 - LLMs.txt、向量資料庫與 RAG. Retrieved 2025-10-06 from https://flyskypie.github.io/posts/2025-10-06_the-cornerstone-of-llm-system-rag ↩

· 8 min read
Wei Ji
info

OCI Image 實際上不是單一檔案,而是一層一層的 layer 構成的,但是為了簡化描述,本文使用 image 一詞時可能是在描述 layer(s)。

背景​

我在 Homelab 中架設了簡易的 Docker Registry 作為鏡像/代理,來節省我工作筆電的硬碟空間和下載時間,更具體的細節請見另外一篇文章:我的 Homelab。

起因​

一切都源自於上班的時候需要用到這個 image: quay.io/keycloak/keycloak,拉了幾次卻發現我的鏡像站沒有快取這個 image。

下班之後在 homelab 開了另外一個 service:

registry-server-2:
image: registry:2.8.2
environment:
- REGISTRY_PROXY_REMOTEURL="https://quay.io/v2"
volumes:
- quay-data:/var/lib/registry
networks:
- default
- traefik-public
deploy:
labels:
- traefik.enable=true
- traefik.docker.network=traefik-public
- traefik.http.routers.registry-quay-mirror.entrypoints=web
- traefik.http.routers.registry-quay-mirror.rule=Host(`mirror.quay.oci-image.arachne`)
- "traefik.http.services.registry-quay-mirror.loadbalancer.server.port=5000"

發現依然不會快取。

No, this is a limitation of the docker engine, registry-mirrors is only for Docker Hub. You can follow issue 18818 for more details.1

Unfortunately and infuriatingly, it is not possible. This is a most desired feature, by me, and by many others. However, Docker, on purpose, does not implement this feature.2

這似乎是 Docker 本身的設計所致,不過在我發現這個限制以前,以為是 distribution/distribution 的限制因此做了一些嘗試。

Sonatype Nexus Repository​

我先是嘗試 sonatype/nexus3,然後看到了這個:

蛤?為什麼我用自架的開源軟體還要有額度限制?下一個。下一個。

接著試了 sonatype/nexus:oss:

不支援 Docker?好吧。

Harbor​

當我在考慮自架 OCI Registry 時,Harbor 不在考慮範圍的原因是我在官方網站只找得到兩種安裝方式:

我目前不是使用 K8s,Helm Chart 自然是不考慮。安裝精靈又可能導向裸機 (baremetal) 安裝,讓我沒辦法用 Docker 管理叢集。

後來經過調查才發現安裝精靈最後是產生 Docker Compose 的組態來執行的,可以在原始碼中發現 Docker Compose YAML 的樣板:harbor/make/photon/prepare/templates/docker_compose/docker-compose.yml.jinja。

不過很遺憾,我使用 Docker Swarm 來編排 homelab 的服務,Compose 的 YAML 沒辦法直接拿來使用。

分而治之​

Harbor 由八個微服務構成:

  • proxy (goharbor/nginx-photon:v2.13.1)
  • core (goharbor/harbor-core:v2.13.1)
  • registry (goharbor/registry-photon:v2.13.1)
  • redis (goharbor/redis-photon:v2.13.1)
  • postgresql (goharbor/harbor-db:v2.13.1)
  • portal (goharbor/harbor-portal:v2.13.1)
  • jobservice (goharbor/harbor-jobservice:v2.13.1)
  • registryctl (goharbor/harbor-registryctl:v2.13.1)

我試著以最小仰賴的方式,一個一個服務跑起來試試看,然後用自己寫的腳本完成必要的配置,而不是使用 Harbor 安裝精靈。細節就不在本文贅述,最後建起來 Docker Compose YAML 在這裡:

https://github.com/FlySkyPie/harbor-docker-compose

在 Harbor 官方的版本中,所有組態都是仰賴 volumes 或是 env_file 完成的,這在 Swarm 模式是行不通得。在 Compose 模式下可以用本地的相對路徑來組織檔案或是資料夾,而 Swarm 模式必須抱持著「一切都發生在遠端」的思路:機敏資訊由 secrets 實例儲存、設定檔以 configs 實例儲存...。完成 Compose 版本後再轉成 Swarm 的版本:

https://github.com/FlySkyPie/harbor-docker-swarm

最後有些密碼還是用 environment 傳遞,因為服務本身可能沒有設計支援 Secret File,我也不想花太多時間在這件事上折騰,就先這樣將就著用吧。

另外,安裝精靈建立的當中其實還有一個 log 容器用來從其他微服務蒐集 log,我的版本並沒有包含它,一來是它不是執行服務必要的功能,二來是如果我有蒐集 log 的需求會再搭配 Promtail 和 Grafana Loki 之類專門用來做這檔事的工具。

附上成果圖:

資料轉移​

為了把舊的鏡像資料遷移到 Harbor,我做了一些措施,雖然整體而言稱不上完美。

建立 image 清單​

首先要建立鏡像站快取過得清單:

  1. GET /v2/_catalog 取得 N 個 repositories 的資料。
  2. GET/v2/${repository}/tags/list 取得每一個 repository 的 tag 資料,要注意的是 distribution/distribution 鏡像站可能快取了 tag 但是還沒有快取映像檔本身。
  3. GET /v2/${repo}/manifests/${tag} 取得 manifest 資訊
  4. HEAD /v2/${repository}/blobs/${digest} 根據 manifest 判斷 layers 有沒有被快取。
    • 這裡要注意,必須暫時關閉 REGISTRY_PROXY_REMOTEURL 的設定,不然會直接觸發代理鏡像機制,關閉代理之後,沒有被快取過得檔案會直接 404 作為判斷有沒有被快取過得機制。

關於更多 API 操作的細節可以閱讀官方的文件3。

整個過程完成會得到類似這樣的清單:

goharbor/nginx-photon:v2.13.1
goharbor/redis-photon:v2.11.0
goharbor/registry-photon:v2.13.1
grafana/loki:latest
grafana/loki:2.7.1
...

設定 Harbor​

接著在 Harbor 設定 registries 和 projects 指向舊的鏡像站。

要注意的是,設定 Proxy Cache 之後既不能關閉也不能修改。

從 Harbor 鏡像本地鏡像站​

接著使用腳本從 Harbor 新設定的 Project 依照清單拉取 image

docker pull harbor.arachne/docker-hub-proxy/getpinry/pinry:latest

這樣就可以把一部分 image 從舊的鏡像站轉移到 Harbor 去了,雖然在其他 Project 不會顯示 image 的存在,但是 image 的檔案在 Harbor 是跨 Project 儲存的,之後如果從其他 Project 拉取 image 時有已經快取過的檔案,Harbor 會直接使用已經存在的檔案。

限制​

我這個作法是 「不完美」而且只能遷移「一部分」的原因在於,像 docker.io/library/mysql 這種 image,因為安全性的問題,同一個 tag 其實會一直更新,因此有些 image 在我的鏡像站上是過期的,所以當腳本拿著最新的 manifest 去檢查快取的時候,是有可能全部 404 的。

Podman​

還記得我前面提到的「Docker 本身的設計就不允許設定 docker.io 以外的鏡像站」嗎?於是我試著使用 Podman,發現很輕易的就設定好本地鏡像站了(/etc/containers/registries.conf):

[[registry]]
location = "quay.io"
[[registry.mirror]]
location = "harbor.arachne/quay-proxy"
insecure = false

[[registry]]
location = "docker.io"
[[registry.mirror]]
location = "harbor.arachne/docker-hub-proxy"
insecure = false

當然,「使用 Podman 解決非 docker.io 本地鏡像站問題」與「使用 Harbor 取代 distribution/distribution」變成兩個獨立事件了,不過既然我都已經折騰完 Harbor 了,那就乾脆直接當作非預期的升級計畫換過去吧。

Footnotes​

  1. mirroring - docker registry-mirrors is not used for fqdn images - Server Fault. Retrieved 2025-07-05 from https://serverfault.com/a/1125577 ↩

  2. Configure dockerd to use a regsitry mirror for a private registry - General - Docker Community Forums. Retrieved 2025-07-05 from https://forums.docker.com/t/configure-dockerd-to-use-a-regsitry-mirror-for-a-private-registry/135180/2 ↩

  3. HTTP API V2 | CNCF Distribution. Retrieved 2025-07-05 from https://distribution.github.io/distribution/spec/api/ ↩

· 15 min read
Wei Ji

從我開始計畫建一個 Homelab 至今已經超過半年了,如今它的規模也算是有點東西可以拿出來講了。

什麼是 Homelab?​

Homelab 就是資訊工程師平常在公司管伺服器管得不夠爽,所以在自己家裡蓋一個資料中心繼續玩。

好吧,對某些人來說或許是這樣,不過要稱得上是 Homelab 的門檻其實很低:只要在家中放一台伺服器,它可以是一般的桌機、NAS、筆電或是樹莓派。

硬體架構​

黑線是乙太網路、藍線是其他(主要是 USB):

目前我的 Homelab 硬體配置大概如下:

  • 4G 路由器
    • TOTOLINK LR350 4G LTE
  • 交換器
    • Zyxel GS-108B v3
  • 樹莓派
    • Raspberry Pi 3B
  • 筆電(伺服器)
    • Lenovo Yoga Slim 7 Carbon 13ITL5
  • DAS (Direct-Attached Storage)
    • QNAP TR-004U 4-bay
  • USB Dock
    • j5create JCD533
  • USB 網路卡
    • j5create JCE145
  • 筆電
    • Framework Laptop 13 (13th Intel i7-1360P)

入口網站​

當自架的服務越來越多,一個簡單的入口網站來尋找需要的服務是十分必要的。目前我是使用 Dashy,關於其他最後沒使用的候選方案我補充在另外一篇貼文。

目前我把所有服務大致分成四類:

  • 數位基礎設施
    • 一些情況不會直接面對終端使用者 (End User),而是在背景默默工作的東西會放在這裡。
  • 專案 (Side Project) 基礎設施
    • 「管理」相關的東西會放在這裡。
  • 生活
    • 比較屬於個人會使用到的服務會放在這裡。
  • 開發工具
    • 「研發工具」之類的東西會放在這裡。

經濟學裡裡的生產要素被分成四類:自然資源、人力資源、資本、企業才能。如果用經濟學裡生產要素的概念來比喻,「數位基礎設施」就像是自然資源的陽光、空氣、水;「專案基礎設施」則是資本裡的機器、設備、廠房。

「生活」是不直接涉及生產東西,比較像是個人消耗品。「開發工具」則是尺度比較小的工具,像是電鑽、板手...之類的。

數位基礎設施​

Pi Hole 與 DNS​

架設的服務一多,單靠埠號去分辨服務其實不理想,而為了實現這個需求就需要 DNS 和反向代理 (Reverse Proxy),反向代理的部份稍後再回來談,我使用 Pi-hole 來擔任我的 DNS 伺服器。

Pi-hole 除了 DNS 另外一個功能是封鎖黑名單內的域名,如此一來就可以在 DNS 層級封鎖掉一些廣告。

DHCP​

因為我會帶著主力筆電出門,如果每次都還要重新設定 DNS 同樣不夠理想,因此 DNS 能夠直接由 DHCP 發送完成設定是十分必要的,最後是成功完成了,然而過程中有遇到一些小阻礙,在此紀錄一下。

容器內的 Pi-hole DHCP​

Pi-hole 本身就支援 DHCP,但是我是把它跑在 Docker 容器內,因此需要一些額外的處理1,然而在這方面我沒有成功讓容器內的 Pi-hole DHCP 正常運作。

Host 上的 isc-dhcp-server​

無奈之下我只好在主機 (Host) 上直接安裝 DHCP 伺服器,然而這讓我碰到另外一個問題:

receive_packet failed on enx00051b52140f: Network is down

要排除問題必須先切到固定 IP 配置遠端進 homelab (或是直接物理訪問伺服器),把 DHCP 的 Daemon 重新啟動:

sudo service isc-dhcp-server restart

問題發生的時候 Daemon 本身顯示正常,所以我也找不到其他方法偵測問題來自動重起 Daemon。

最後懷疑是 USB 網路卡 j5create JCE145 不夠可靠、掉封包造成 DHCP 故障。

至於為什麼需要掛一個 USB 網路卡是因為擔任伺服器的筆電是薄型筆電,只有三個 USB Type-C。

樹莓派上的 isc-dhcp-server​

剛好之前借給親戚當文書電腦的樹莓派被反還了,索性試著把 DHCP 伺服器轉移到樹莓派上,然後那個討厭的問題就再也沒有出現過了。

DHCP 伺服器,結案。

Traefik 與反向代理​

用 Apache 或 Nginx 做反向代理的組態檔我都寫過,因此當我第一次接觸到 Traefik 的時候是十分的驚豔,確實感受到來自開發團隊所謂的:

We forgot what was impossible so we could build it!2

整個流程大概是這樣的,在 Docker 新增一個網路:

docker network create --driver=overlay --attachable traefik-public

開一個容器跑 Traefik 的服務然後接到剛剛開網路:

---
services:
proxy:
image: traefik:latest
command:
- --api.insecure=true
- --providers.docker
- --entrypoints.ep1.address=:80
- --entrypoints.ep2.address=:9090
ports:
- "80:80"
- "127.0.0.1:8080:8080"
- "9090:9090"
volumes:
- /var/run/docker.sock:/var/run/docker.sock
networks:
- traefik-public

networks:
traefik-public:
external: true

對於想要反向代理的服務則是在它的組態上加上反向代理的資訊像這樣:

app1:
image: docker.io/alpinelinux/darkhttpd:latest
labels:
- traefik.http.routers.app1.entrypoints=ep1
- traefik.http.routers.app1.rule=Host(`example.com`)

如此一來就不用每次更改規則的時候都要去修改或重新啟動反向代理的配置與伺服器,又或是目標服務因為某些原因掉線時候讓反向代理伺服器找不目標而出錯,使用上十分方便。

OCI Registry​

OCI (Open Container Initiative) Registry,或是通俗的講 Docker 庫,用來存放 Docker 映像檔 (Image) 的地方。

我使用一個庫搭配遺一個 Web UI 的方案:

https://github.com/distribution/distribution

即使短期內沒有自己的映像檔要佈署,當成鏡像站也很好用,在 client 端的 /etc/docker/daemon.json 加入3:

{
"registry-mirrors": ["https://<my-docker-mirror-host>"]
}

這樣 pull 的時候就會留一份副本在鏡像站,下次拉取一樣的東西就可以直接從 homelab 下載。

這對我而言有用是因為一些條件:

  1. 我對外網路是無線網路,速度本來就十分有限,能夠快取映像檔,之後重複拉取可以節省我的時間。
  2. 我偶爾還會處理像 GitLab 或是 SideFX Houdini 那種一個 1 GB 起跳的映像檔。
  3. 我使用 Docker 的時候經常會接觸不同的映像檔,會不定期清理筆電上的映像檔,不然硬碟空間會被吃光。

NPM Registry​

作為一個 (Web) 前端仔,自架一個私有 NPM 庫是再合理不過的事,跟 Docker 的情況相同,就算沒有要佈署東西,作為鏡像站依然有它的作用,並且同樣使用一個庫一個 Web UI 的搭配:

Verdaccio 雖然有 Web UI 能夠管理私人 (private) 套件,卻不會顯示快取的套件。filebrowser 是一個簡單的服務,只要把 Volume 掛給它就能透過瀏覽器查看檔案系統的檔案以及簡單的管理,包含上傳、下載、新增資料夾、刪除、重新命名...等。

在網路不穩定的情況 Verdaccio 有機會快取失敗留下損壞的組態檔,這時就能從瀏覽器刪除損壞的組態讓它重新下載。

S3 (Simple Storage Service) 物件儲存​

info

S3 原本是 AWS 的雲端服務,更準確的用詞應該是 S3 兼容 (S3 compatible) 服務,只是因為它在雲端世界已經實質上是一種通用標準了,以下我直接簡稱 S3。

對我而言輕量級的東西總是有特別的吸引力,因此一開始我試著使用 Garage,但是也因為它的精簡,因此並沒有內建 Web UI,加上我對 S3 的使用也稱不上熟悉,相對複雜的安裝文件最後讓我卻步而改用比較普通的 MinIO:

正如我說的,我對 S3 的使用仍然缺乏經驗,自架一個 S3 實例就是為了長經驗,另一方面是目前不少網路服務會把儲存資料或檔案的部份抽象化並且把職責交給外部的 S3 服務,例如 DVC 是我預計想玩的東西;前端使用 Git 風格的 CLI、後端則是把版控的大型檔案儲存在 S3 (或其他支援的儲存形式)。

專案基礎設施​

專案管理​

專案管理的部份在前一篇文章有稍微提過,那個時候使用 WeKan。

目前我選擇使用 Vikunja,雖然跟其他候選相比星星少而且發展時間也比較短,但是算是一個後起之秀,兩個我很看中的功能都有支援,連續流水號:

卡片之間的關聯:

並且整體的 UI/UX 也更貼近現代的風格,更具體地說,跟我平時工作使用的 Jira 有一定程度的相似度:

曬一下目前在 Homelab 裡紀錄的 side project:

版本控制​

我使用 Gitea,因為它比較簡單。另外一個常見的自架選擇是 GitLab,也是俗稱的全家桶,不過對我而言過於笨重了,我有接觸過它的 Docker Image 不論是官方的還是第三方的,總而言之就是笨重。

物料管理​

我使用 Part-DB 來管理我的零件與物品,在前一篇文章也有稍微提過。

實際上運作起來的樣子我在 FB 和 Twitter 都有貼文分享過。

Pinry​

Pinry 基本上是開源自架版本的 Pinterest。

生活​

個人影音​

我使用 Jellyfin 搭配 Filebrowser 的方式:

其實這是我第一個使用 filebrowser 的組合,之後才套用到其他服務去的,這方法也不是我原創的,是在網路上找到的4。

資訊訂閱器​

RSS (Really Simple Syndication) 是一種把網站內容建立摘要的協定,像這樣,接著其他人就可以定期的去撈 (Creep) 這個文件,有更新的話就會知道,達到類似訂閱的效果,不過定期撈資料這個動作一般交由特定的軟體完成。

我目前是使用 Tiny Tiny RSS,關於其他沒使用到的候選我放在另外一個貼文。

網頁備份器​

不知道你有沒有使用過 Wayback Machine 呢?這是一個快取網站,讓人可以調閱那些已經不存在的網頁的網站。ArchiveBox 就是它的自架版本。

電子書​

其實我沒有看電子書的習慣,出於方便歸檔跟調閱的目的架架看的,目前使用 Kavita。跟 Jellyfin 一樣我搭配了一個 Filebrowser 使用。

開發工具​

目前就是放一些上網也能用的平台,但是還是自己架一個以防萬一。

虛擬白板​

Excalidraw是一個開源的協作虛擬白板,包含讓遠端工作者進行線上腦力激盪 (Brainstorming) 之類的用例。

Open API/AsynAPI 編輯器​

Swagger Editor

Footnotes​

  1. Docker DHCP and Network Modes - Pi-hole documentation. Retrieved 2025-04-26 from https://docs.pi-hole.net/docker/dhcp/ ↩

  2. Traefik 2.0 - The Wait is Over! Retrieved 2025-04-26 from https://traefik.io/blog/traefik-2-0-6531ec5196c2/ ↩

  3. Mirror | Docker Docs. Retrieved 2025-04-26 from https://docs.docker.com/docker-hub/image-library/mirror/ ↩

  4. Upload file from the app or site · Jellyfin Feature Requests. Retrieved 2025-04-27 from https://features.jellyfin.org/posts/543/upload-file-from-the-app-or-site#comment-5160 ↩