安裝那些沒人幫你打包的小工具。
安裝過程不執行任何一行指令碼。
沒有 install、沒有 postinstall、沒有 build、沒有 hook,一個都沒有。toy 只會下載、驗雜湊、解壓縮、建立符號連結。
$ toy install github:Derrity/tinyserve
$ tinyserve
toy.toml,打個 tag,就這樣name = "tinyserve"
desc = "一個很小的靜態檔案伺服器"
license = "MIT"
[bin]
tinyserve = "tinyserve"
[artifact]
"linux/amd64" = "tinyserve_{version}_linux_amd64.tar.gz"
"darwin/arm64" = "tinyserve_{version}_darwin_arm64.tar.gz"
沒有 URL、沒有版本號、沒有代管方名稱 —— 所以下次發版這個檔案一個字都不用改。已經在用 goreleaser 的話連寫都不用寫:toy init 讀 ./dist 裡的檔名反推出來。
三件事
-
一、安裝時不執行任何程式碼
這不是「預設關閉」,也不是「在沙箱裡執行」,而是這些概念在程式碼裡根本沒有對應物。它擋掉的是一整類攻擊,不是某一個 CVE:吃掉 700 多個 npm 套件的 Shai-Hulud 蠕蟲靠的就是
preinstall;Homebrew 的 formula 是可執行的 Ruby,6.0 為此不得不補上一道 Tap Trust。邊界也說清楚:「安裝時不執行」不等於「這個套件永遠拿不到執行機會」。一個套件宣告
[bin] git = "payload",裝完~/.toy/bin/git就在 PATH 上,而 toy 自己靠git ls-remote找版本。所以git是保留名稱,無條件拒絕,連--force也不放行 —— 全專案唯一一條這樣的檢查。 -
二、8 行 toml,零審批
不必註冊、不必等審核、不必求任何人。打個 tag 發個 release,別人就能
toy install github:你的帳號/你的工具。想被搜尋到再把一行條目提進索引,跑一次toy keygen就能替自己的條目簽章 —— 這兩步都是可選的。強制簽章 = 強制註冊 = 又一道門檻。 -
三、下架無害,索引可自建
索引裡預設只存一個指向儲存庫的指標,真相永遠是那個 git 位址。從索引裡刪掉一條只代表「不再被搜尋到」,
toy install github:owner/repo照樣裝得起來 —— 沒有人能真的封殺一個套件,包括我們。任何人 fork 一份那個 JSON,就是一個完整的平行索引。所以維護者要求下架就下架,不問理由:既然代價這麼低,就沒有任何理由跟人爭。
索引裡現在有什麼
這份索引就掛在 /index.json,用戶端整份下載後在本機搜尋。toy 裝好就帶著它(名稱是 default),toy search 直接就能搜。toy 0.1.0 寫出來的設定裡沒有它,補上:
toy index add default https://toy.cirraya.com/index.json
這裡每一條都是內嵌條目。裝出來是第三檔信任等級(index-toyfile):二進位檔取自作者自己的 release(下載位址由代管方樣板 + tag 拼出來,索引說了不算),但要裝哪個檔案、符號連結取什麼指令名稱是由索引提供 —— 作者本人沒寫過 toy.toml,多半也不知道我們收錄了他。收錄只代表「這個套件裝得起來」,不代表我們稽核過它的程式碼,也不代表作者和我們有任何關係。安裝時用戶端會如實印出這一檔。
你在信任誰,toy 會告訴你
安裝完成那一行會印出下面四種之一。讀的時候要讀它證明不了的那一部分。
| 安裝完成那一行 | 你信的是 | 它證明不了什麼 |
|---|---|---|
✓ signed · key a3f9… · registered in index X (this is not a claim about github.com/Y themselves) |
索引裡登記的那把 ed25519 公開金鑰 | 不證明是儲存庫主人簽的。簽章訊息是 id + source + toy.toml 原文,三樣都公開可讀,裡面沒有一個位元組把這把公開金鑰和儲存庫的 owner 綁在一起。索引誰都能 fork、誰都能提 PR,抄一把公開金鑰零成本 —— 有人完全可以 fork 一份、用自己的金鑰替同一個 id 重簽一條、把 source 指向自己的儲存庫。它保證的是連續性(公開金鑰首次安裝即釘住,換了就拒絕安裝),不是身分。第一次安裝該看的是 source 指向哪裡,不是有沒有那個勾。 |
· from github.com/Y · unsigned |
代管方的帳號體系:GitHub 說這個儲存庫屬於這個帳號,TLS 說你連到的是 GitHub | 沒有作者簽章,也不需要 —— 這是絕大多數套件的預設狀態,而且它是一個誠實的狀態,不是一種降級。 |
⚠ packaging instructions come from the index · … |
位元組是作者的,打包說明不是 | 作者沒寫過 toy.toml,索引條目裡內嵌了一份。索引能改的是「裝哪個檔案、符號連結取什麼指令名稱」,改不了下載位址。內嵌的 toyfile 永遠到不了那個勾 —— 簽章的人是提交索引紀錄的人,不是作者。 |
⚠ unsigned · first install, sha256 pinned (trust on first use) |
裸 URL:你什麼都沒信 | 但內容變了會被攔下來。TOFU 的第一次就是無條件信任 —— 第一次裝到的如果本來就是壞的,toy 會忠實地把它釘住。 |
有 [sha256] 就強制驗證,沒有則首次安裝釘住,之後變了就拒絕安裝(模型抄的是 SSH 的 known_hosts)。--reinstall 和 --force 是兩回事:前者只是「我知道已經裝過這一版,再裝一次」,不放行任何安全檢查;後者才是「我看過了,接受這些變化」,而且會逐條印出自己放行了什麼。完整的威脅模型 —— 包括 toy 不防的那一欄 —— 在 SECURITY.md。上表左欄是用戶端實際印出的原文(英文);右邊兩欄是說明,不是輸出。
安裝 toy 本身
先把話說在前面:第一個套件管理器總得靠別的方式安裝。toy 存在的理由是讓你不必 curl | bash,而它自己沒辦法用 toy 安裝 —— 這個循環沒有出路,凡是宣稱自己解決了的,都只是把它搬了個地方。所以下面兩種方式都不完美,差別只在於你在信誰。
方式一:go install(有 Go 工具鏈的話,這是最省事的)
go install github.com/Cirraya-LLC/toy/cmd/toy@latest
你信的是 Go 的模組代理與 checksum database(sum.golang.org)—— 一套公開、可稽核的透明日誌。它比我們自己的任何承諾都強,所以有 Go 就用這個。
方式二:下載 release 二進位檔,自己核對雜湊
# 換成你的平台:linux_amd64 / linux_arm64 / darwin_amd64 / darwin_arm64
V=0.1.0; P=linux_amd64
BASE=https://github.com/Cirraya-LLC/toy/releases/download/v$V
curl -fLO $BASE/toy_${V}_${P}.tar.gz
curl -fLO $BASE/checksums.txt
# 核對雜湊。這一步不是可選的 —— 跳過它,你就是在無條件信任那條下載鏈路。
sha256sum --ignore-missing -c checksums.txt # macOS: shasum -a 256 --ignore-missing -c
tar -xzf toy_${V}_${P}.tar.gz
install -m 0755 toy ~/.local/bin/toy
checksums.txt 和二進位檔來自同一個 release,所以它擋的是「傳輸過程中被改掉」,不是「發布的人本身有問題」。這就是啟動悖論的實際形狀。 在 Mac 上請照上面用 curl 下載,別用瀏覽器:瀏覽器會替檔案加上隔離標記,tar 會把它帶到解出來的 toy 上,Gatekeeper 就不讓這個臨時簽署的二進位檔執行。已經這樣了的話:xattr -d com.apple.quarantine toy。
這裡不會有 curl | bash 安裝指令碼。那正是這個專案要反對的東西 —— 把整台機器交給一份沒人看過的指令碼。
裝完之後
export PATH="$HOME/.toy/bin:$PATH" # 寫進 ~/.bashrc / ~/.zshrc
toy doctor
toy 裝好就帶著本站的索引,所以 toy search 馬上能用,toy doctor 也會檢查它連不連得上。要是 doctor 說「no index configured」(toy 0.1.0 寫的設定就是這樣),沒有壞掉,它會給出加上的那一行。沒有索引也從來不影響安裝:toy install github:owner/repo 那條路本來就不經過索引。
底下的架構
索引是去中心化的,這是架構不是賣點。它就是一份 JSON 檔案,用戶端整份下載後在本機搜尋;裡面只存指標,source 那個 git 位址永遠是真相。刪一條只是不再被搜尋到,fork 一份就是一個平行索引。(把「去中心化」當宣傳語的 Entropic —— npm 前 CTO 於 2019 年在 JSConf EU 發表 —— 10 個月後就封存了。所以我們只講上面那兩件事。)
版本探索用的是 git ls-remote --tags,一條指令、所有 git 代管方行為一致,不用任何一家的 REST API —— 也就不會被任何一家的流量限制卡住。代管方是一張設定表而不是三份程式碼,加三行設定就多支援一家。
toy serve 把你本機的快取變成一個鏡像 —— 因為內容是雜湊定址的,鏡像不需要被信任,誰都可以當,五分鐘的事。上游哪天沒了,套件還活著。但「不需要信任」不等於「不需要知道」:真的走了鏡像,安裝時會明說是從哪裡拿的。
不做的事:不從原始碼建置、不做套件相依、不做 P2P/DHT、不做 Windows(暫時)。套件與套件之間沒有相依關係 —— 每個 toy 都是一個自包含的二進位檔或單一指令碼檔,所以不存在「我審了直接相依,但它的第 47 個間接相依被下毒了」這回事。