---
# System prepended metadata

title: embeded linux post install
tags: [linux]

---

# 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)

