Snapcraft 打包
Snapcraft 是製作 snap 的工具,讀取專案中的 snapcraft.yaml,依序拉取、建置、安裝各個 part,最後打包成 .snap。
概念請先參考 Snap 概念,建置完成後可依 上架 Snap Store 發佈。
安裝 snapcraft
sudo snap install snapcraft --classic
# 預設用 LXD 建置,確保環境乾淨
sudo snap install lxd
sudo lxd init --auto
# 把自己加入 lxd 群組,才能不用 sudo 操作 LXD
sudo usermod -aG lxd "$USER"
群組變更要登出整個桌面 session 再登入(或重新開機)才會生效,可用 id 確認輸出已包含 lxd。
專案結構
hello.sh 的內容:
dump plugin 會保留原檔的權限,所以腳本一定要先設為可執行,否則 snapcraft pack 會以 should be world-readable and executable 失敗:
撰寫 snapcraft.yaml
# snap 名稱,上架時必須先在 Store 註冊
name: hello-shell
# 執行環境,core26 對應 Ubuntu 26.04
base: core26
# 給人看的名稱,顯示在 Store 與圖形介面,缺少時 snapcraft 會顯示 lint 警告
title: Hello Shell
# 應用程式自己的版本,顯示用,與 revision 無關
version: '0.1'
summary: Hello from a shell script
description: |
A minimal snap that prints a message.
# 以下也是 metadata,缺少時同樣只會顯示 lint 警告(不影響打包)
# license 使用 SPDX 識別碼
license: MIT
# contact、issues、source-code、website 可填 email 或網址
contact: you@example.com
issues: https://example.com/hello-shell/issues
source-code: https://example.com/hello-shell
website: https://example.com/hello-shell
# strict 是預設的完整 sandbox;開發途中想放寬可改 devmode
confinement: strict
# stable 表示可發佈到 stable channel;測試中用 devel,Store 不允許 devel 進 stable/candidate
grade: stable
apps:
hello-shell:
# 執行檔路徑相對於 $SNAP
command: bin/hello.sh
# 宣告需要的 interface,network 之類的 plug 會在安裝時自動連接
plugs:
- network
parts:
hello:
# dump plugin 只把 source 的檔案複製進 snap,不編譯
plugin: dump
source: .
# 只留下需要的檔案,避免把 snap/ 資料夾也打包進去
organize:
hello.sh: bin/hello.sh
stage:
- bin/hello.sh
建置
- 第一次建置會下載 base 並建立 container,需要一些時間
snapcraft pack與直接輸入snapcraft相同- 要建置其他架構可使用
snapcraft remote-build,由 Launchpad 代為建置
不想使用 container 時,可以直接在主機建置,但主機的 Ubuntu 版本必須與 base 相同(core26 對應 Ubuntu 26.04),且會直接動到主機環境。
本機安裝測試
# 未上架的 snap 沒有簽章,所以要加 --dangerous
sudo snap install ./hello-shell_0.1_amd64.snap --dangerous
hello-shell
# 查看 snap 內有哪些 connection
snap connections hello-shell
測試完成後移除:
# --purge 連同資料一起刪除,並且不留下移除前的資料快照
sudo snap remove --purge hello-shell
# 確認已移除,應該顯示 error: no matching snaps installed
snap list hello-shell
建置產生的檔案不會隨 snap remove 消失,需要時另外清理:
# 刪除 LXD build container 與 part 狀態
# 需在專案資料夾執行,清除的是 lxc --project snapcraft list 看到的對應 container
snapcraft clean
# 刪除打包出來的 snap 檔
rm hello-shell_0.1_amd64.snap
Parts 與 Plugin
一個 snap 由多個 part 組成,每個 part 有自己的 plugin、source 與生命週期。
| 階段 | 動作 | 控制的 key |
|---|---|---|
| pull | 取得 source | source、source-type、source-checksum、source-branch、source-tag、source-commit、source-depth、source-submodules、source-subdir |
| build | 編譯,結果放在 $CRAFT_PART_INSTALL |
plugin、after、build-packages、build-snaps、build-environment、build-attributes、organize |
| stage | 把各 part 的結果合併到 stage 目錄 | stage、stage-packages、stage-snaps |
| prime | 挑出最終進入 snap 的檔案 | prime、permissions |
常用 plugin
| plugin | 用途 |
|---|---|
| dump | 直接複製檔案 |
| nil | 不做事,搭配 override-build 自訂流程 |
| python | 以 pip 建置 Python 專案 |
| cmake、make、autotools | 一般 C/C++ 專案 |
| rust、go、npm | 對應語言的專案 |
parts:
my-app:
plugin: make
source: https://github.com/example/my-app.git
source-tag: v1.0
# 建置時需要的 apt 套件
build-packages:
- gcc
# 執行時需要、會被打包進 snap 的 apt 套件
stage-packages:
- libssl3t64
參考其他 snap 的 snapcraft.yaml
snap 檔案裡通常會附上建置時的 snapcraft.yaml,可以下載別人的 snap 來學習寫法。
# 下載 snap,不安裝
snap download hello
# 讀取 snap 內的 snapcraft.yaml,需要 squashfs-tools
unsquashfs -cat hello_*.snap snap/snapcraft.yaml
已安裝的 snap 可以直接讀取:
meta/snap.yaml 是 snapcraft 展開後、snapd 實際讀取的版本,與原始的 snapcraft.yaml 不同。
除錯
# 只執行到某個階段,不繼續往下
snapcraft pull
snapcraft build
# 失敗或完成後,直接進入 container 的 shell
snapcraft pack --shell-after
# 建置失敗時進入 shell
snapcraft pack --debug
# 清除建置結果與 container
snapcraft clean
建置完成後,用 snap run --shell <name>.<app> 進入 sandbox 檢查,詳見 Snap 基本操作。
LXD 無法連網
建置時出現 A network related operation failed in a context of no network access,代表 LXD container 連不到外網。
主機同時安裝 Docker 時最常見,因為 Docker 會把 iptables 的 FORWARD 預設政策改成 DROP,擋掉 lxdbr0 的轉發流量。
在 Docker 的 DOCKER-USER chain 放行 lxdbr0,重新開機後規則會消失:
sudo iptables -I DOCKER-USER -i lxdbr0 -j ACCEPT
sudo iptables -I DOCKER-USER -o lxdbr0 -m conntrack --ctstate RELATED,ESTABLISHED -j ACCEPT
設定完成後清除失敗留下的 container 並重新建置:
宣告 plug 與 slot
apps:
my-daemon:
command: bin/my-daemon
# service 會以 systemd unit 在背景執行
daemon: simple
plugs:
- network
- network-bind
# 需要手動連接的 interface 也要在這裡宣告
- removable-media
# 以 content interface 與其他 snap 分享檔案時,由提供者宣告 slot
slots:
shared-data:
interface: content
content: shared-data
read:
- $SNAP/share
- 沒有宣告的 interface,snap 就無法使用
- 找不到該用哪個 interface 時,先以
confinement: devmode安裝,再從journalctl -k | grep DENIED找出缺少的項目