# embeded linux post install
[linux mass config](/wCXyj5uWTWmBode5vdr8Gw)
## 目的
post install on linux 一般是在 system core, user own app 外,需要額外的 middleware, set up 工具 去幫助 user app 達成更好的效果,甚至是必要的環境設定。
1. system core: e.g. glibc
2. middleware
1. mosquitto, x windows, WM, x11vnc, avahi(mDNS)...
2. 一般不會是 libs for user app。
3. user app
1. linux flutter apps 理論上只要 libgtk3,但 沒有 windows manager 可能會有各種奇怪反應。
post install 集中在 layer 2 middleware,也是接下來的討論方向。
## design aspect of buildroot
buildroot deliberately usually ship without pkg manager(PM). 這牽涉到 buildroot 的設計理念。
* Image-based design: Buildroot cross-compiles everything and emits a complete, immutable rootfs image. No in-field dependency resolution.
* Reproducibility: Exact versions, configs, and toolchain are fixed at build time. A runtime PM would break determinism.
* ABI/toolchain coupling: Third‑party binary feeds (Debian/Alpine/OpenWrt) are built with different toolchains/ABIs and often break on Buildroot systems.
* Footprint and robustness: Embedded systems often use read-only rootfs, limited storage/flash write cycles, and need a small attack surface.
* 根目錄 用 read only e.g. `/bin`, `/usr/bin` --> 無法更新。
* [`squashfs`](https://www.toradex.com/blog/shi-yongsquashfs-heoverlayfs-ti-gao-qian-ru-shilinux-wen-jian-xi-tong-ke-kao-xing)+`overlayfs`
* Maintenance model: Updates are done by rebuilding a new image, not by partial upgrades.
所以對於 buildroot 產生的系統來說,任何些微的安裝,實際上都是 重新 copy 整個系統。即使 image 可以用 OTA update。
> Buildroot 属于“通用”工具,它可以跨系统广泛使用。这种理念的另一个有趣点就是:Buildroot 会生成根文件系统镜像,而非完整的发行版。也就是说,每当需要更新时,您都需要重新生成整个镜像。嵌入式系统默认不同于桌面或服务器发行版。正是在这样的理念下,项目团队专门设计成如此机制。故障或部分更新都有可能造成灾难性的影响,而从头开始重建镜像,就可以完全避免这种问题。
> [ref](https://zhuanlan.zhihu.com/p/671536247)
> buildroot 可增量編譯 (by make 天性),但不能分享 builded package
相比之下,yocto 把選擇權給 user,他是 general purpose distribution build tool。
## incompatible to apt
dpkg/apt, rpm/dnf 的想法不同。
`dpkg -i` 不會自動 install deps,`apt install` 才會。
pkg 間的依賴關係就是一張網,一切的 root 就是 glibc (libc6.so)。系統出廠時就要帶有 PM,且 mark 好所有 default installed pkgs。
而且 PM 的期待是盡量大的 tree graph,如此,更新 一個靠近 root 的節點,大量 leaf 節點就已更新。
當 這種 PM 放在沒有 PM 的 embeded OS 時,它自然就會想重裝 glibc,好讓所有 pkgs 都在他的控制下。
而重裝 glibc 就會導致 glibc API version break,導致系統預裝的軟體全部失效 --> system break。
唯一可能事後重用 PM 的方法,就是將目前系統裝的 libs,以 PM 的 pkgs 表達 (read only),使 PM 了解系統所處的狀態。
或是兩個 glibc(獨立系統)-->虛擬機,"graph in scope"。
### characteristic
To mark a package as installed with dpkg, you can use the apt-mark command, which is a front-end for dpkg selections.
```
sudo apt-mark manual <package-name>
```
dpkg 好像沒有 直接的 command,會變成需要手動寫file。
---
I just manual install a lib through `make install`. How can I use dpkg to mark this libs is installed?
Short answer: you can’t. dpkg only tracks .deb packages. A plain make install is invisible to it.
Practical options:
* Reinstall via a .deb (recommended, tracked and removable)
* Use checkinstall to wrap make install and produce a .deb:
*
```
# build
make
# build and install a .deb tracked by dpkg
sudo checkinstall --pkgname=your-lib-name --pkgversion=1.0 --pkgrelease=1 --install=yes
```
* Satisfy dependencies with a dummy package (no files, just marks “installed”)
* Use `equivs` to create a fake package that Provides the library:
*
```
sudo apt-get update
sudo apt-get install -y equivs
mkdir -p ~/pkg && cd ~/pkg
equivs-control your-lib
# Edit the generated 'your-lib' control file:
# Package: libfoo1
# Version: 1.0
# Architecture: amd64
# Provides: libfoo1 (= 1.0)
# Conflicts: libfoo1
# Replaces: libfoo1
# Description: Dummy package to satisfy libfoo1 (installed from source)
equivs-build your-lib
sudo dpkg -i ./libfoo1_1.0_amd64.deb
```
* <-- only option for my use case
* 沒看過有人實際使用。
還要先想辦法弄出 equivs。
好處是**維持在單一的 PM系統**。
### force install
如果放棄完整的deps 解析,是否也還能用?
如果 glibc 週圍一圈 pkgs force install ,後續 pkgs 是否還會提醒 deps break。
```bash
alex@alex-Lenovo-Y520-15IKBN:~$ apt-rdepends zstd
Reading package lists... Done
Building dependency tree... Done
Reading state information... Done
zstd
Depends: libc6 (>= 2.34)
Depends: libgcc-s1 (>= 3.3.1)
Depends: liblz4-1 (>= 0.0~r127)
Depends: liblzma5 (>= 5.1.1alpha+20120614)
Depends: libstdc++6 (>= 12)
Depends: zlib1g (>= 1:1.1.4)
libc6
Depends: libcrypt1 (>= 1:4.4.10-10ubuntu4)
Depends: libgcc-s1
libcrypt1
Depends: libc6 (>= 2.25)
libgcc-s1
Depends: gcc-13-base (= 13.1.0-8ubuntu1~22.04)
Depends: libc6 (>= 2.35)
gcc-13-base
liblz4-1
Depends: libc6 (>= 2.14)
liblzma5
Depends: libc6 (>= 2.34)
libstdc++6
Depends: gcc-13-base (= 13.1.0-8ubuntu1~22.04)
Depends: libc6 (>= 2.34)
Depends: libgcc-s1 (>= 4.3)
zlib1g
Depends: libc6 (>= 2.14)
debtree zstd | dot -Tpng -o dependencies.png #prefer
```
```bash
mkdir -p /var/lib/dpkg/info/
touch /var/lib/dpkg/status
```
verify
```bash
dpkg --force-depends -i lz4_1.9.2-2ubuntu0.20.04.1_armhf.deb liblz4-tool_1.9.2-2ubuntu0.20.04.1_all.deb #only left libc6.so
lz4 -
root@buildroot-tx7:~/armv7_out# dpkg -i zstd_1.4.4+dfsg-3ubuntu0.1_armhf.deb
dpkg: package zstd depends on libc6, which is not installed or flagged to be installed
dpkg --force-depends -i zstd_1.4.4+dfsg-3ubuntu0.1_armhf.deb liblzma5_5.2.4-1ubuntu1.1_armhf.deb liblz4-1_1.9.2-2ubuntu0.20.04
.1_armhf.deb
cd / && find / -name liblzma*
ln -s /lib/arm-linux-gnueabihf/* /lib/
cd / && find -name liblz4
ln -s /usr/lib/arm-linux-gnueabihf/* /usr/lib/
# cp -r -u /media/sda1/armv7_out/ /root/
```
所以 dpkg 一樣 會去找 deps,後續的install 也會跳出 liblzma5 depends on libc6,很難說是正常用。
小 program e.g. lz4, zstd, nano 還算能正常安裝。
試大一點的 e.g. mosquitto。
首先是 deps 很多,很難區分 mosquitto 的deps。直接force install 之後
```
root@buildroot-tx7:~/armv7_out# which mosquitto
/usr/sbin/mosquitto
root@buildroot-tx7:~/armv7_out# ldd /usr/sbin/mosquitto
libwrap.so.0 => not found
libsystemd.so.0 => not found
libdlt.so.2 => not found
libpthread.so.0 => /lib/libpthread.so.0 (0xa6ecd000)
libssl.so.1.1 => /usr/lib/libssl.so.1.1 (0xa6e63000)
libcrypto.so.1.1 => /usr/lib/libcrypto.so.1.1 (0xa6c70000)
libanl.so.1 => /lib/libanl.so.1 (0xa6c6d000)
libdl.so.2 => /lib/libdl.so.2 (0xa6c6a000)
libm.so.6 => /lib/libm.so.6 (0xa6c24000)
libwebsockets.so.15 => not found
libc.so.6 => /lib/libc.so.6 (0xa6aa2000)
/lib/ld-linux-armhf.so.3 (0xa6f0d000)
libatomic.so.1 => /lib/libatomic.so.1 (0xa6a96000)
```
裝 libwrap 有遇到 post install error,看起來是有copy \*.so,但沒有成功(mark in dpkg)。
重覆上面的過程,把所有 `.so` 安裝,最後是能夠啟動 mosquitto(沒有測全部功能)。
理論上 apt 會下載所有deps,但實際上,大部份都要手動指定pkg name並下載,只能當作 default ubuntu OS 真的有安裝這麼多東西。
一個方式是先 generate pre-installed pkgs list,重下載一次。
其它用到 gpu 的,e.g. `glxinfo` need `libGL.so.1` 還沒有試,如果 pkgs 碰到驅動可能會有問題。
有 preinstall script 的也很容易fail(裝不了)。
```bash
root@buildroot-tx7:~/armv7_out# dpkg -i --force-depends firejail_0.9.62-3ubuntu0.1_armhf.deb
Unpacking firejail (from firejail_0.9.62-3ubuntu0.1_armhf.deb)...
/var/lib/dpkg/info/firejail.preinst: line 4: dpkg-maintscript-helper: not found
dpkg: preinst failed, exit code 32512
```
我們目前用的 dpkg 並非完整的版本。
總之,勉強算是能 work,但真的很難算可靠。
### opkg
opkg 是 buildroot 唯一有整合的 package manager。
就算想啟用,第一版的出廠系統就要啟動 opkg。從這個角度看,其實和需要預裝的 apt, dnf 在應用上也沒有大差別。
1. In Buildroot menuconfig:
* System configuration → Package manager → select opkg
* Enable “install package manager in target” and “generate packages”
* Use a writable rootfs (e.g., ext4) or an overlay for /usr and /var
2. Build your image. Buildroot will produce a feed directory containing .ipk and Packages.gz.
3. Host the feed (on your dev PC):
* ```bash
# from the feed directory Buildroot generated (e.g., output/feeds/<arch>/)
python3 -m http.server 8000
```
4. Point the target’s opkg to your feed:
* ```bash
src/gz br-feed http://<your-host-ip>:8000
dest root /
lists_dir /var/opkg-lists
option overlay_root / # if using overlayfs
```
5. Use it on target:
* ```bash
opkg update
opkg install <your-package-name>
```
Notes
* > Only install packages built by the same Buildroot config/toolchain. Do not mix with OpenWrt, Debian, Alpine, etc., repos.
* 實際上,opkg + buildroot **還是以 單一的 build 為核心idea**,而非嘗試將不同 build env 的 binary 共用。
* > For robust updates, consider image/OTA solutions (RAUC, SWUpdate, Mender, OSTree) instead of a package manager.
## alpine apk
如果和 預裝的 libs系統 完全分隔且平行,可能就能使用預裝系統。
alpine apk 基本是 static link 所以和一般 glibc 完全分開。
先 cross compile apk,理論上就能裝了。
alpine apk 還是會裝到 lib (libxxx.deb),有部份仍舊是 dynamic link。
像 flutter 這樣,直接在release page 要求裝[特定 lib](https://docs.flutter.dev/platform-integration/linux/building#prepare-linux-apps-for-distribution) 的也有,有安裝lib 的能力可能是可以run flutter 的。
要注意的是 用 apk 的原因始終是 standalone executable,libs 問題始終會是不得已才需要解決的部份。
[在 Linux 作業系統上使用 musl libc 來編譯出靜態可攜的程式,擺脫對於 glibc 的依賴](https://magiclen.org/musl-libc/)
### installed musl lib linked by glibc app?
應該是不會 有 link 錯誤 libs 的問題,因為 glibc 和 musl 的 linker 完全不同,`ld-linux-.so.`, `ld-musl-<arch>.so`。
但安裝時,看起來是可能會把 glibc ver libs 覆蓋掉。
如果在 glic OS 裡用 apk,一般會建議完全和 glibc 分離。
```
sudo mkdir -p /opt/alpine
sudo apk --root /opt/alpine --initdb add alpine-base
# run an Alpine binary with the musl loader (examples)
# x86_64
/opt/alpine/lib/ld-musl-x86_64.so.1 --library-path /opt/alpine/lib:/opt/alpine/usr/lib /opt/alpine/bin/busybox
# aarch64
/opt/alpine/lib/ld-musl-aarch64.so.1 --library-path /opt/alpine/lib:/opt/alpine/usr/lib /opt/alpine/bin/busybox
```
可是 linker 和 path 可能會找不到路徑 (可以用一堆 `ln -s` 解決),還是先試試混在一起。
## standalone (self) build
對於自己 build by conan 的 app。
或是 dart, go 這類大部份只有 link 核心 lib,e.g. libc, libdl, libpthread ... 的語言。
這類 executable app,基本上是 deps graph 的 leaf ,app 可以完全獨立控管自己 deps。受限 lgpl 所以 沒有 static link 的 dynamic libs 也可以copy 一份。
如果不好 cross compile,docker emulated compile 一般都能 work。
我們要的是 middleware executable。從這個角度來說,如果所有 app 的編譯都是 standalone focus,則根本不需要考慮 package manager 相關的問題。
## parallel image (impractical)
直接建立一個非常類似的 image ,每次更新就 copy all folder `/usr/*`, `/bin`, `/lib`, `/opt`, `/etc`, `/var`...。
`sys`?
一般情況是既然建立了 image,直接走正常的燒錄流程就好,change whole iamge 也是最正規的。
我們的問題是沒有燒錄的方法,或是關鍵的 firmware,所以才用這種奇怪的方案。
有燒錄了話,level 1, level 2(middleware) 都直接 壓進 image 裡。只留 user app 事後加入。
但是 只要差一點點,copy 進去就可能整個crash OS。
## 反推最佳 OS creation
其實,就算不想要 end user 動態更新,也應該裝套件管理器。
因為完全可以用套件管理器裝完後,disable PM,再重新基於裝完的結果產生 image ,就像 docker。也沒有人說 docker 不穩。
重點是**有套件管理器才能快速做客制化的動作**。
PM 只是一個工具,不會限制最終目標。相反,只有提供的功能太少才會導致限制。buildroot 強制全新的image,就導致只有唯一的方法能解決問題。如果唯一的方法門檻又很高,本質上就是不好的設計。
另外理論上,linux 是 GPL,所以拿到開發板就能要求拿source code。但實際上,有各種推託方式。而且,採取法律行動,也需要相當的資源基礎。
與其跟廠商扯皮,不如廠商搭好一個台階。
## solution selection
基本上可以分三種 solution
1. 要求 system 提供者加入PM e.g. apt or opkg
2. 用上述的奇怪方式,在 target system 重用 PM 下載的 pkg
3. 要求 整個 build system code e.g. buildroot
以 scope 來說 3>1>2,所以有 3 就等於可以採取任何方案。但 如果 3 是可能的選項,一般在最開始就會提供。所以更多的是有不想擺在檯面上的理由。
對雙方(提供方和用戶)最好的選擇是 1,提供方免於提供support,並維持控制。user 則可以達成可靠的部份定制。
只對提供方有利的選擇是2,因為不用出力。
## other
* [全志T507如何在Ubuntu定制文件系统](https://www.forlinx.com/article-new-c22/1044.html)
### buildroot
* [ 如何定制Buildroot文件系统-基于TQT507开发板](https://www.bilibili.com/opus/798380596052099138)
* 如果需要的 pkgs 有寫好的腳本 e.g. zstd,只要選選項就好 (Target packages -> Compression and decompression -> (select) zstd)
* 如果沒有 e.g. FRP(Fast Reverse Proxy),就要寫一堆腳本 (custom package)。
* 下載 prebuild binary
* or 編譯命令 e.g. `(cd $(@D) && $(GO_ENV) $(GOLANG) build -o $(@D)/bin/frpc ./cmd/frpc)`
* 缺乏標準的 build packages 方法,很難應對很多deps 的 3rd party app。
### yocto
[Set Up Yocto for Raspberry Pi](https://medium.com/@boussettaachraf26/set-up-yocto-for-raspberry-pi-31b4a1ec4b10)