OPKG包管理器

[Opkg package manager](https://openwrt.org/docs/guide-user/additional-software/opkg)
是OpenWrt系統內置的包管理器。

# opkg軟件源
opkg包管理器的軟件源配置文件為`/etc/opkg/distfeeds.conf`。

官方OpenWRT軟件源配置結構如下：

```
src/gz openwrt_core https://downloads.openwrt.org/openwrt/releases/openwrt版本號/targets/芯片廠家/芯片架構/packages
src/gz openwrt_base https://downloads.openwrt.org/openwrt/releases/openwrt版本號/packages/cpu架構/base
src/gz openwrt_luci https://downloads.openwrt.org/openwrt/releases/openwrt版本號/packages/cpu架構/luci
src/gz openwrt_packages https://downloads.openwrt.org/openwrt/releases/openwrt版本號/packages/cpu架構/packages
src/gz openwrt_routing https://downloads.openwrt.org/openwrt/releases/openwrt版本號/packages/cpu架構/routing
src/gz openwrt_telephony https://downloads.openwrt.org/openwrt/releases/openwrt版本號/packages/cpu架構/telephony
```

其中`openwrt_core`倉庫提供內核模塊，與Linux內核版本以及特定芯片型號相關，
而其它倉庫中的軟件包則不依賴Linux內核版本以及芯片型號，通常僅需CPU架構相同即可使用。

本地源配置：

```
src/gz xxx_local_mirror file:///xxx/xxx...
```

替換軟件源為牆國軟件源：

```html
<!-- 替換為牆國USTC源 -->
# sed -i 's/downloads.openwrt.org/mirrors.ustc.edu.cn\/openwrt/g' /etc/opkg/distfeeds.conf

<!-- 替換為牆國TUNA源 -->
# sed -i 's_downloads.openwrt.org_mirrors.tuna.tsinghua.edu.cn/openwrt_' /etc/opkg/distfeeds.conf
```

USTC源通常更新較慢，TUNA源更新更及時。

對於部分非官方倉庫（如GL.iNET的廠家倉庫），默認配置下更新源會得到證書校驗失敗的錯誤：

```
root@OpenWrt:~# opkg update
Downloading https://fw.gl-inet.cn/releases/v21.02-SNAPSHOT/kmod-4.0/arm_cortex-a7/ip60xx/Packages.gz
Updated list of available packages in /var/opkg-lists/glinet_core
Downloading https://fw.gl-inet.cn/releases/v21.02-SNAPSHOT/kmod-4.0/arm_cortex-a7/ip60xx/Packages.sig
Signature check failed.
Remove wrong Signature file.
...
```

對於非官方倉庫，應關閉簽名校驗，編輯`/etc/opkg.conf`，註釋證書校驗相關內容：

```sh
# option check_signature
```

# 基本包管理操作
OpenWRT提供的opkg包管理器被設計運行在嵌入式環境中，
因此遠比傳統的包管理器更加輕量級，功能也更加簡陋。

基本操作：

```html
<!-- 查詢軟件包 -->
# opkg list "*軟件包關鍵字*"
<!-- 查詢已安裝的軟件包 -->
# opkg list-installed "*軟件包關鍵字*"

<!-- 安裝軟件包 -->
# opkg install 軟件包名稱
# opkg install --force-depends 軟件包名稱 <!-- 強制安裝軟件包 -->
# opkg install --noaction 軟件包名稱 <!-- 模擬安裝過程 -->

<!-- 移除軟件包 -->
# opkg remove 軟件包名稱
# opkg remove --autoremove 軟件包名稱 <!-- 移除軟件包（同時移除軟件包的孤兒依賴） -->

<!-- 列出軟件包內容 -->
# opkg files 軟件包名稱
```

opkg並未直接提供升級所有軟件包功能，可利用管道操作組合指令實現：

```
# opkg list-upgradable | cut -f 1 -d ' ' | xargs -r opkg upgrade --force-overwrite
```

在OpenWRT中，升級所有軟件包的操作有一定危險性，
當當使用自編譯固件時，應儘量避免使用該功能，
自行編譯的固件可能與官方源的軟件包不兼容（例如部分軟件包的動態鏈接庫不匹配）。

# 常用軟件包
記錄常用的軟件包：

```html
# opkg install
<!-- 常用程序，所有設備均安裝 -->
fish file lsblk btop iperf3 tcpdump screen luci-app-adblock luci-app-ddns luci-app-nlbwmon luci-app-ttyd
<!-- 帶有USB接口的設備可作為下載服務器 -->
luci-app-aria2 ariang kmod-fs-exfat kmod-usb-storage-uas usbutils rsync block-mount parted
<!--
帶有USB接口的設備可作爲文件共享服務器
（luci-app-samba4軟件包較大，會占用大量存儲空間，不建議ROM空間較小的機器安裝）
-->
luci-app-samba4

<!--
VIM編輯器，OpenWRT中内置的vi編輯器為busybox提供，功能簡陋，多數VIM常用指令都未實現，
OpenWRT中VIM分為三個版本vim/vim-full/vim-fuller，
其中vim為tiny-build，關閉了大多數VIM特性（如部分文件的語法高亮以及vimdiff等功能），
建議使用vim-full(normal-build)或更完整的vim-fuller(big-build)
-->
vim-full
<!-- 存儲空間足夠的設備可安裝全功能版本的VIM -->
vim-fuller

<!--
與VIM類似，nmap/ncat等常用網絡工具亦存在普通版和完整功能版，
存儲首先的設備僅安裝ncat即可，nmap普通版本亦有數M大小，開銷較大
-->
ncat
<!-- 存儲空間充足的設備可安裝full版本 -->
ncat-full nmap-full

<!-- OpenWRT2020 主題 -->
luci-theme-openwrt-2020

<!-- ARM64/X86架構的設備可安裝 Docker -->
luci-app-dockerman dockerd

<!-- ImmortalWRT 以及部分國產固件可直接從軟件源中安裝 OpenClash -->
luci-app-openclash
```

MT762x系列芯片若未識別出SD卡，則可嘗試安裝SD卡驅動（SD卡正常識別則無需安裝）：

```
# opkg install kmod-sdhci-mt7620
```

若存儲使用BTRFS文件系統，則需要額外安裝內核模塊：

```
# opkg install kmod-fs-btrfs
```

# 禁止/恢復軟件包升級
可通過設置軟件包flag為`hold`禁止軟件包升級：

```
# opkg flag hold 軟件包名稱
```

設置flag為`user`可恢復軟件包升級：

```
# opkg flag user 軟件包名稱
```

# 軟件包安裝狀態
系統中已安裝的軟件包信息存儲在`/rom/usr/lib/opkg/status`文件中，
軟件包信息示例：

```
# cat /rom/usr/lib/opkg/status
...
Package: luci-app-firewall
Version: git-22.089.67741-3856d50
Depends: libc, firewall
Status: install user installed
Architecture: all
Installed-Time: 1650113974
Auto-Installed: yes
...
```

查找所有手動安裝的軟件包（ash語法）：

```ash
$ for i in `opkg list-installed | sed 's/ - .*//'`; do if !(cat /rom/usr/lib/opkg/status | grep -q "Package: $i") && !(opkg whatdepends $i | grep -q "depends on $i"); then echo $i; fi; done
```

查找所有手動安裝的軟件包（fish語法，添加自動修改安裝原因功能）：

```fish
#! /usr/bin/fish

set package_status /usr/lib/opkg/status
set rom_package_status /rom$package_status
if [ -e $rom_package_status ]
	set rom_packages (cat $rom_package_status)
end

# Transform the package status contents, and save package info to a list.
for package_info in (cat $package_status | tr '\n' ';' | sed 's/;;/\n/g')
	if not string match -q "*Auto-Installed: yes*" $package_info
		# Find packages which are not auto installed.
		set package_name (string match -r "(?<=Package: )[\w\.-]+" $package_info)
		# Check if package not in rom (pre-installed).
		if not string match -q "*Package: $package_name*" $rom_packages
			if not opkg whatdepends $package_name | grep -q "depends on $package_name" # Check the package depends result code.
				set package_used_info "clean"
			else
				set package_used_info "used by others"
				# Add the current package info to a package list.
				set dependency_packages $dependency_packages $package_name
			end
			echo "$package_name($package_used_info)"
		end
	end
end

# Check if packages which need change the installed reason exist.
if set -q dependency_packages
	echo -e "=====================================================================\n"
	echo -e "Packages which can change the installed resaon:\n$dependency_packages\n"
	read -p 'echo "Do you need to change packge install reason? (yes/NO) "' need_change_install_resaon
	# Check the user input.
	if [ $need_change_install_resaon = yes ]; or [ $need_change_install_resaon = y ]
		for dependency_package in $dependency_packages
			# Find the packge line number in the file "/usr/lib/opkg/status".
			set line_number (cat /usr/lib/opkg/status | grep -nE "^Package: $dependency_package\$" | awk -F':' '{print $1}')
			set insert_line_number (math $line_number + 1)
			# Check if the line number is valid.
			if string match -qr '^[0-9]+$' $insert_line_number
				echo "Change package [$dependency_package](line $insert_line_number) install resaon to Auto-Installed."
				sed "$insert_line_number i Auto-Installed: yes" -i /usr/lib/opkg/status
			else
				echo Find invalid line number, skip the install reason change operation!
			end
		end
	end
end
```

# 強制安裝軟件包
當使用自編譯鏡像時，安裝內核模塊會出現依賴不滿足的錯誤，
因為即使源碼、配置無任何改動，自編譯鏡像的內核版本與倉庫中內核模塊依賴信息中的小版本也號不相符，
具體參見[OpenWRT官方文檔](https://openwrt.org/faq/cannot_satisfy_dependencies)。

錯誤示例：

```html
<!-- 安裝內核模塊，提示內核版本不滿足依賴要求 -->
# opkg install kmod-fs-exfat
Installing kmod-fs-exfat (4.14.90+2017-06-20-de4c760b-1) to root...
Downloading http://fw.gl-inet.cn/releases/v18.06.5/kmod-3.8/siflower/sf19a28_nand/kmod-fs-exfat_4.14.90%2b2017-06-20-de4c760b-1_mips_siflower.ipk
Collected errors:
 * satisfy_dependencies_for: Cannot satisfy the following dependencies for kmod-fs-exfat:
 * 	kernel (= 4.14.90-1-8e7f05250007b3b96947722cb34fb82f) *
 * opkg_install_cmd: Cannot install package kmod-fs-exfat.

<!-- 查看內核版本信息，可知內核小版本號不滿足要求 -->
# opkg info kernel
Package: kernel
Version: 4.14.90-1-36a80e72295637cf4e383d8ee24054dd
Depends: libc
Status: install user installed
Architecture: mips_siflower
Installed-Time: 1661108424
```

解決方案是直接編譯需要的模塊，或安裝時使用`--force-depends`參數強制安裝：

```
# opkg install --force-depends kmod-fs-exfat
Installing kmod-fs-exfat (4.14.90+2017-06-20-de4c760b-1) to root...
Downloading http://fw.gl-inet.cn/releases/v18.06.5/kmod-3.8/siflower/sf19a28_nand/kmod-fs-exfat_4.14.90%2b2017-06-20-de4c760b-1_mips_siflower.ipk
Configuring kmod-fs-exfat.
Collected errors:
 * satisfy_dependencies_for: Cannot satisfy the following dependencies for kmod-fs-exfat:
 * 	kernel (= 4.14.90-1-8e7f05250007b3b96947722cb34fb82f) *

# opkg info kmod-fs-exfat
Package: kmod-fs-exfat
Version: 4.14.90+2017-06-20-de4c760b-1
Depends: kernel (= 4.14.90-1-8e7f05250007b3b96947722cb34fb82f), kmod-nls-base
Status: install user installed
Section: kernel
Architecture: mips_siflower
Size: 51043
Filename: kmod-fs-exfat_4.14.90+2017-06-20-de4c760b-1_mips_siflower.ipk
Description: Kernel module for ExFAT Filesytems
Installed-Time: 1661238557
```

強制安裝依舊會輸出依賴不滿足的告警信息，但軟件包已安裝成功。

儘管內核模塊版本不匹配可使用強制安裝解決依賴不滿足的問題，
但若存在大量內核模塊需要安裝時操作較為繁瑣，應考慮使用修改內核軟件包版本信息的方式。

# 強制覆蓋文件
部分軟件包會出現文件衝突，例如：

```
# opkg info libnl-tiny1
Package: libnl-tiny1
Version: 2020-08-05-c291088f-2
Depends: libc
Provides: libnl-tiny
Status: install user installed
Architecture: aarch64_cortex-a53
Installed-Time: 1657334765

# opkg info libnl-tiny2022-05-17
Package: libnl-tiny2022-05-17
Version: 2022-05-17-b5b2ba09-1
Depends: libc
Provides: libnl-tiny
Status: install ok not-installed
Section: libs
Architecture: aarch64_cortex-a53
Size: 17681
Filename: libnl-tiny2022-05-17_2022-05-17-b5b2ba09-1_aarch64_cortex-a53.ipk
Description: This package contains a stripped down version of libnl

# opkg upgrade libnl-tiny2022-05-17
Installing libnl-tiny2022-05-17 (2022-05-17-b5b2ba09-1) to root...
Collected errors:
 * check_data_file_clashes: Package libnl-tiny2022-05-17 wants to install file /usr/lib/libnl-tiny.so
	But that file is already provided by package  * libnl-tiny1
 * check_data_file_clashes: Package libnl-tiny2022-05-17 wants to install file /usr/lib/libnl-tiny.so
	But that file is already provided by package  * libnl-tiny1
 * check_data_file_clashes: Package libnl-tiny2022-05-17 wants to install file /usr/lib/libnl-tiny.so
	But that file is already provided by package  * libnl-tiny1
 * check_data_file_clashes: Package libnl-tiny2022-05-17 wants to install file /usr/lib/libnl-tiny.so
	But that file is already provided by package  * libnl-tiny1
opkg: exited with status 255; aborting
```

系統內存在兩個不同軟件包均提供`/usr/lib/libnl-tiny.so`，會導致另一個軟件包安裝/升級失敗，
通常可採用刪除其中一個軟件包的方式解決（如`dnsmasq`和`dnsmasq-full`、`tcpdump`和`tcpdump-full`等），
若產生文件衝突的軟件包同時被依賴，則需要使用`--force-overwrite`參數強制覆蓋文件，繞過依賴錯誤。

# 未配置的安裝包
當安裝流程中出現錯誤，會導致部分軟件包處於未配置（部分安裝）狀態：

```html
<!-- 狀態為 install ok not-installed，無Installed-Time屬性，僅標記而未實際安裝 -->
# opkg info kmod-ikconfig
Package: kmod-ikconfig
Version: 5.4.203-1
Depends: kernel (= 5.4.203-1-5379bb746f374ca43bbbef0b8a9c7bab)
Status: install ok not-installed
Section: kernel
Architecture: aarch64_cortex-a53
Size: 41972
Filename: kmod-ikconfig_5.4.203-1_aarch64_cortex-a53.ipk
Description: Kernel configuration via /proc/config.gz

# opkg install kmod-ikconfig
Installing kmod-ikconfig (5.4.203-1) to root...
Downloading https://mirrors.vsean.net/openwrt/releases/21.02.1/targets/sunxi/cortexa53/packages/kmod-ikconfig_5.4.203-1_aarch64_cortex-a53.ipk
Configuring kmod-ikconfig.

<!-- 重新安裝後狀態為 install user installed，出現Installed-Time屬性，已完成安裝 -->
# opkg info kmod-ikconfig
Package: kmod-ikconfig
Version: 5.4.203-1
Depends: kernel (= 5.4.203-1-5379bb746f374ca43bbbef0b8a9c7bab)
Status: install user installed
Section: kernel
Architecture: aarch64_cortex-a53
Size: 41972
Filename: kmod-ikconfig_5.4.203-1_aarch64_cortex-a53.ipk
Description: Kernel configuration via /proc/config.gz
Installed-Time: 1664909591
```