---
# System prepended metadata

title: Gentoo Maintenance
tags: [Gentoo]

---

###### tags: `Gentoo`
# Gentoo Maintenance
[TOC]

## 更新 kernel

### 從原先自訂的 kernel 升級到新版本

* 參閱 [Gentoo 官方的 Kernel/Upgrade 文件](https://wiki.gentoo.org/wiki/Kernel/Upgrade)，大致上是多了一步 `make olddefconfig`。
* 其實降級也能使用相同步驟完成，但並不建議。

### 從 distribution kernel 換成自訂 kernel

1. 確定不要繼續使用 distribution kernel 的話，用 `emerge --unmerge gentoo-kernel` 或 `emerge --unmerge gentoo-kernel-bin` 移除原本的 kernel。一般建議移除，以免在更新系統時意外將 kernel 更新為 distribution kernel。
    :::info
    * 要清理得更乾淨的話，可以手動移除 /boot 裡主檔名以 `gentoo-dist` 結尾的檔案，還有 `/lib/modules` 裡名稱以 `gentoo-dist` 結尾的目錄。
    :::
2. 執行 `emerge sys-kernel/gentoo-sources` 將 linux 原始碼安裝到 `/usr/src` 裡。
3. 確認 `/usr/src/linux` 符號連結指向上一步安裝的 linux 原始碼目錄後，執行 `zcat /proc/config.gz > /usr/src/linux/.config` 複製當前運行中系統的 kernel 設定檔，並按照[自訂 kernel 的流程進行](https://hackmd.io/@tinlans/gentoo-installation#Option-2-%E6%89%8B%E5%8B%95%E8%A8%AD%E5%AE%9A%E5%8F%8A%E5%AE%89%E8%A3%9D-kernel)操作，大致上是多了一步 `make olddefconfig`。如果跟 `uname -a` 顯示的當前 kernel 版本不同，則要按照[升級流程](https://wiki.gentoo.org/wiki/Kernel/Upgrade)操作。
    :::info
    * 從 distribution kernel 複製過來的設定檔在這裡也會加上後綴，可以把它刪除：
        ```
        General setup  --->
          (-gentoo-dist) Local version - append to kernel release
        ```
    :::
4. 執行 `grub-mkconfig -o /boot/grub/grub.cfg` 重新產生 `grub.cfg` 後即可重新開機。

## 系統更新後無法開機的解決方式

### 共通處理步驟
1. 嘗試在 boot manager 的選單裡選擇舊版 kernel 開機。
2. 如果前一步驟無效，則需要仿照安裝系統的流程，先[使用光碟或 USB 隨身碟開機](https://hackmd.io/@tinlans/gentoo-installation#%E9%96%8B%E6%A9%9F%E5%8F%8A%E5%95%9F%E7%94%A8-sshd)，[掛上必要的磁碟分割區](https://hackmd.io/@tinlans/gentoo-installation#%E7%A3%81%E7%A2%9F%E5%88%86%E5%89%B2%E5%8F%8A%E6%A0%BC%E5%BC%8F%E5%8C%96) (不需重新分割硬碟及格式化分割區)，再[進入 chroot 的環境](https://hackmd.io/@tinlans/gentoo-installation#%E5%AE%89%E8%A3%9D-stage3-%E4%B8%A6%E9%80%B2%E5%85%A5-chroot-%E7%92%B0%E5%A2%83)以執行修復工作。

### 解決 boot manager 沒有出現的問題

如果原本有安裝如 `grub` 之類的 boot manager，但是 POST 畫面結束後卻看不見它，可能的原因有：
* 相關的 partition flags 沒有被正確設定，參閱[磁碟分割及格式化的說明](https://hackmd.io/@tinlans/gentoo-installation#%E7%A3%81%E7%A2%9F%E5%88%86%E5%89%B2%E5%8F%8A%E6%A0%BC%E5%BC%8F%E5%8C%96)使用 `parted` 加以檢查和修正。
* bootloader 沒有被正確安裝或因不明原因遭到覆蓋，此時只要[重新設定及安裝 bootloader]( https://hackmd.io/@tinlans/gentoo-installation#%E8%A8%AD%E5%AE%9A-bootloader) 就能解決。

### 解決無法掛載 rootfs 的問題

遇到這個問題時，通常會看到 linux 開機流程已經進行了一小段後突然停止。如果有使用 initramfs，通常會在提示無法掛載 rootfs 後停留在 initramfs 內的 shell 下，否則會在提示後發生 kernel panic。這個問題的可能原因有：
* `grub.cfg` 的內容不正確或忘記使用 `grub-mkconfig` 產生與當前系統相符的設定內容，執行 `grub-mkconfig -o /boot/grub/grub.cfg` 重新產生一遍。
* 沒有將相關驅動程式編譯到 kernel 裡或編譯成 kernel modules，利用 `lshw | grep 'driver='` 確認 driver 名稱後，按照[手動設定及安裝 kernel](https://hackmd.io/@tinlans/gentoo-installation#Option-2-%E6%89%8B%E5%8B%95%E8%A8%AD%E5%AE%9A%E5%8F%8A%E5%AE%89%E8%A3%9D-kernel) 的說明重新編譯 kernel 及安裝 kernel modules。如果沒有使用 `initramfs`，必須將驅動程式直接編譯到 kernel 裡。
* 相關裝置的驅動程式沒有被包進 `initramfs` 裡，按照[手動設定及安裝 kernel](https://hackmd.io/@tinlans/gentoo-installation#Option-2-%E6%89%8B%E5%8B%95%E8%A8%AD%E5%AE%9A%E5%8F%8A%E5%AE%89%E8%A3%9D-kernel) 中關於 `dracut` 的說明產生對應的 `initramfs`。在特定情況下，需要修改 `/etc/dracut.conf` [自行指定要加入的驅動程式]((https://wiki.gentoo.org/wiki/Dracut#Kernel_modules))再使用 `dracut` 產生 `initramfs`。

### 還原到上一版 kernel

通常會選擇這種解決方式是因為能從 boot manager 裡選擇舊版 kernel 並成功開機，因此可能的做法有：
* 手動編輯 `/boot/grub/grub.cfg`，刪除不想要的部分。
* 刪除 `/boot` 裡不需要的 kernel 版本及相關檔案，再執行 `grub-mkconfig -o /boot/grub/grub.cfg` 重新產生設定檔，也建議順手刪除 `/lib/modules` 裡對應的目錄；這些清除特定版本 kernel 的操作也可以使用 [`eclean-kernel`](https://wiki.gentoo.org/wiki/Kernel/Removal#Using_eclean-kernel) 這個套件自動完成。

## 開機流程相關問題

### log 中出現 kernel: Error: Driver 'pcspkr' is already registered, aborting...

編輯或建立 `/etc/modprobe.d/blacklist.conf` 這個檔案，然後加入一行 `blacklist pcspkr` 即可解決。

## 編譯 kernel 常見問題

### 在 /usr/src/linux 打 make 出現錯誤訊息 gold linker 'ld' not supported

在安裝 binutils 時使用了 default-gold 這個 use flag 會遇到這個問題，因為 build kernel 時無法使用 gold linker。將環境變數 `LD=ld.bfd` pass 給 `make` 指令即可暫時將 linker 指定為傳統的 BFD linker，譬如 `make LD=ld.bfd -j8`。

## GRUB 常見問題

### 執行 grub-install 時遇到 error: efibootmgr failed to register the boot entry: Input/output error.

使用了不是安裝系統用的 ISO 開機來安裝時會遇到，執行 `mount -o remount,rw /sys/firmware/efi/efivars` 重新掛載成 read-write 模式即可解決。

## Docker 常見問題

### OpenVPN 啟動的環境下無法執行 docker create network，且在 /etc/docker/daemon.json 指定 default-address-pools 時會無法啟動 docker engine

OpenVPN server 端以 def1 模式推送 gateway redirection 資訊時，OpenVPN client 會在 routing table 裡加入 `0.0.0.0/1` 和 `128.0.0.0/1` 兩條路由，這兩條路由會讓 docker engine 找不到任何可用網段做為 address pool。必須刪除兩條路由，再將 OpenVPN server 指定為 default gateway 來解決。

#### 解決步驟
1. 新增一個 `/etc/openvpn/fix-routes.sh` 檔，內容為
    ```
    ip route add default via 10.0.27.1
    ip route del 0.0.0.0/1 via 10.0.27.1
    ip route del 128.0.0.0/1 via 10.0.27.1
    ```
    其中 10.0.27.1 是 OpenVPN server 給的 gateway IP。
2. 執行 `chmod +x /etc/openvpn/fix-routes.sh` 新增執行權限。
3. 打開 OpenVPN client config 檔，加入下面兩行後重新啟動 OpenVPN client：
    ```
    script-security 2
    route-up /etc/openvpn/fix-routes.sh
    ```

### iptables-save 會儲存大量 docker 自動產生的 rules

#### 解決步驟
1. 新增一個 `/lib/systemd/system/restore-iptables.service` 檔案如下：
    ```
    [Unit]
    Description=Iptables rules import
    After=docker.service

    [Service]
    Type=oneshot
    ExecStart=/root/bin/iptables-custom.sh
    Restart=no

    [Install]
    WantedBy=multi-user.target
    ```
2. 在 `/root/bin/iptables-custom.sh` 內編寫完整 iptables 指令。
3. ```
   chmod +x /root/bin/iptables-custom.sh
   systemctl enable restore-iptables
   ```

### 讓特定 container 走指定的 IP 出去

#### 解決步驟
1. 在 docker-compose.yml 裡把這個 container 放在特定的 subnet 下。
2. `/sbin/iptables -t nat -I POSTROUTING -p all -s <subnet> -j SNAT --to-source <WAN IP>`

## Systemd 常見問題

### 在 my.cnf 指定 datadir 為 /home/mysql 時，使用 systemd 來啟動 mysqld 時會出現 permission denied 錯誤

mysqld 預設的 unit file 會防止 /home 被寫入，需要修改 unit file 的內容才能把 datadir 放在 /home 下。

#### 解決步驟
1. 用編輯器打開 `/lib/systemd/system/mysqld.service`，將 `ProtectHome` 改成 `false`。
2. 執行 `systemctl daemon-reload` 後再執行 `systemctl start mysqld`。

### 執行 systemctl start mysqld 時會卡住，過一陣子 mysqld 會被自動關閉

在 systemd 環境下，MySQL 的 unit file 會指定以 `/usr/libexec/mysqld-wait-ready` 這個 script 監視 MySQL server 是否 ready，原理是檢查 socket file 是否存在，因此 my.cnf 中需要寫一行 `socket = <path>` 指定 socket file 路徑。如果指定在 `/var` 以外的路徑，應檢視 `/lib/systemd/system/mysqld.service` 內的路徑保護參數，譬如 `ProtectSystem` 和 `PrivateTmp`，以確保 socket file 可以被正常寫入且 `mysqld-wait-ready` 可以看見它。

### 執行 systemctl start slapd 時會卡住，過一陣子 slapd 會被自動關閉
此時 log 應會顯示類似這樣的訊息：`slapd.service: Got notification message from PID 1155492, but reception only permitted for main PID 1155491`

#### 解決步驟
1. 用編輯器打開 `/lib/systemd/system/slapd.service`，在 [Service] 裡加上一行 `NotifyAccess=all`。
2. 執行 `systemctl daemon-reload` 後再執行 `systemctl start slapd`。


### dmesg 中頻繁出現 dhcp4 (enp42s0f3u5u3c2): request timed out，但 enp42s0f3u5u3c2 是沒使用到的網路裝置

使用 NetworkManager 時會遇到這類問題，因為 NetworkManager 會在背景反覆進行一些嘗試。

#### 解決步驟
1. 編輯 `/etc/NetworkManager/NetworkManager.conf` 這個檔案，如果不存在的話新增一個。內容如下：
    ```
    [main]
    plugins=keyfile

    [keyfile]
    unmanaged-devices=interface-name:enp42s0f3u5u3c2
    ```
2. 執行 `systemctl restart NetworkManager` 重新起動 NetworkManager。

### 每次重開機後 nginx 會啟動失敗，看見錯誤訊息 bind() to xxx.xxx.xxx.xxx:80 failed (99: Cannot assign requested address)

在從 DHCP server 取得 IP 之前，各 daemon 如果嘗試根據其設定檔 bind 某個固定 IP，都會遭遇類似問題，不只有 nginx 會遇到。網路上查到的解法有很多，如 systemctl enable systemd-networkd-wait-online (使用 systemd-networkd 的情況)，還有 sysemctl enable NetworkManager-wait-online.service (使用 NetworkManager 的情況)，或者在 unit file 裡寫上 `After=network-online.target` 和 `Wants=network-online.target`，經實測都沒有作用。

#### 解決步驟

1. 編輯 `/etc/sysctl.conf`，加入一行 `net.ipv4.ip_nonlocal_bind = 1`。

## 套件系統常見問題

### 使用 emerge 安裝套件途中遇到編譯錯誤

可能的解決方法如下：
* 選擇安裝其它版本，用 `equery keywords <name>` 或 `ls /usr/portage/<category>/<name>` 找到可安裝版本後，使用 `emerge =<name>-<version>` 或 `emerge =<category>/<name>-<version>` 來安裝，譬如 `emerge =polkit-0.116-r1` 或 `emerge =sys-auth/polkit-0.116-r1`。
    :::info
    * 根據 `/etc/portage/make.conf` 中 `PORTDIR` 的設定不同，portage tree 不一定被存放在 `/usr/portage` 裡面。
    * 也可以利用 [`package.mask`](https://wiki.gentoo.org/wiki//etc/portage/package.mask) 手動封鎖某個版本或某個版本以上的套件，再重新執行 `emerge` 就會自動安裝符合條件的舊版本。
    :::
* 去官方的 [bugzilla](https://bugs.gentoo.org/) 查詢是否有人回報過同樣的問題，以及是否有人提出過解決問題的有效方法卻還沒被採用。如果都沒有，可以考慮註冊帳號自己發一個上去，但問題被解決的速度通常不樂觀。
* 如果打算自行解決編譯錯誤，需要手動操作 [ebuild](https://dev.gentoo.org/~zmedico/portage/doc/man/ebuild.1.html) 逐步完成原本 emerge 會自動跑完的[流程](https://devmanual.gentoo.org/ebuild-writing/functions/)，即 `fetch`、`unpack`、`prepare`、`configure`、`compile`、`install`、`qmerge` 這幾步，編譯錯誤一般會發生在 `compile` 這一步，因此可以直接指定 `compile` 讓流程先一路跑到這裡，確認自己做的修改可以通過編譯後，可以直接跳到 `qmerge` 完成安裝。
    :::info
    如果不幸需要修改到 ebuild 檔或 patch 檔才能解決問題，則需要使用 `ebuild digest` 更新 Manifest 檔裡的 checksums，否則會因為校驗失敗而拒絕操作。
    :::

### 更新 portage tree 後安裝任何套件都會看到 !!! The following installed packages are masked:
使用 `emerge --sync` 更新 portage tree 後安裝任何套件時看到類似這樣的訊息：
![](https://i.imgur.com/gELlFpA.png)

#### 解決步驟
1. 使用 `emerge -vp opentmpfiles` 確認該套件是否還能安裝，如果不行，可以使用 `emerge --unmerge opentmpfiles` 解除安裝，要保留該套件的話也可以忽略這個訊息。
2. 如果上一步確認還能安裝，通常只是需要升級或降級該套件版本，使用 `emerge opentmpfiles` 再裝一遍就行了。

### 安裝完套件後看見 CONFIG_XXX: is not set when is should be. 的警告
![](https://i.imgur.com/wlm5OGg.png)

這是因為 kernel 的設定裡缺少了特定選項，可能使該套件無法正常運作或使用其完整功能，需要[自行編譯 kernel](https://hackmd.io/@tinlans/gentoo-installation#Option-2-%E6%89%8B%E5%8B%95%E8%A8%AD%E5%AE%9A%E5%8F%8A%E5%AE%89%E8%A3%9D-kernel)。

#### 解決步驟
1. 在 kernel 設定選單裡使用 `/` 搜尋訊息中的大寫字串，不含 `CONFIG_` 前綴，以上圖為例是 `NF_CT_NETLINK_HELPER`。
2. 按照搜尋結果裡的 `Location:` 路徑找到選項，將該項目切換成 `*` 或 `M` 符號。
3. 如果按照 `Location:` 的路徑找不到該選項，查看 `Depends on:` 列出的各選項後方是否有 `[=n]` 的標記，如果有的話，回到第一步搜尋該項目，並將它切換成 `*` 或 `M` 符號。
4. 儲存 kernel 設定離開選單，按照[自行編譯 kernel 的說明](https://hackmd.io/@tinlans/gentoo-installation#Option-2-%E6%89%8B%E5%8B%95%E8%A8%AD%E5%AE%9A%E5%8F%8A%E5%AE%89%E8%A3%9D-kernel)將 kernel 編譯並安裝。
5. 執行 `grub-mkconfig -o /boot/grub/grub.cfg` 重新產生 `grub.cfg` 後重新開機。

### 很久沒執行更新 portage tree，執行 emerge --sync 後不管用 emerge 裝什麼都會出現錯誤
![](https://i.imgur.com/OVVhgvr.png)

太久沒更新 world 的系統，一般不建議魯莽地執行 emerge --sync 更新 portage tree，因為可能同時跨越 python 預設版本變動以及 EAPI 版本變動的時間點。這時需要設法將 portage tree 退回特定日期，以使用系統目前已安裝的 portage 套件來安裝新版本的 python 並更新 portage 套件。

#### 解決步驟
1. 執行 `eselect news list`，查看 python 預設版本的變動日期，如果目前系統預設的版本是 python 3.6，可能會看到 2020-04-22 有預設版本變成 3.7 以及 2021-05-05 預設版本變成 3.9 的 news，如果中間有 EAPI 版本變動的 news 也可以記一下日期。
2. 執行 `cd /usr; mv portage portage.latest` 將原本的 portage tree 更名。
3. 參閱 [HOWTO Update Old Gentoo](https://wiki.gentoo.org/wiki/User:NeddySeagoon/HOWTO_Update_Old_Gentoo)，執行 `git clone https://github.com/gentoo/gentoo.git portage`。
4. 執行 `cd /usr/portage` 進入 git clone 下來的 portage tree，然後執行下面的指令 checkout 到指定日期：
    ```
    git checkout `git rev-list -n 1 --first-parent --before="2020-04-21" master`
    ```
    通常在 python 預設版本發生變動之前，portage tree 早就已經有新的預設版本可以安裝，因此切換到變動發生之前的時間點，再使用 `emerge` 或 `emerge --nodeps` 安裝指定版本的 python 並更新 portage 到當下的最新版本。以這邊的例子來說，2020-04-22 改變了系統預設的 python 版本到 3.7，那麼回到 2020-04-21 這一天通常能順利使用系統已安裝的 python 3.6 及當下的 portage 套件來安裝 python 3.7，這時間點最新版本的 portage 套件通常也做好了相應的準備。
5. 反覆進行上個步驟切換日期，安裝各時期預設的 python 版本以及當下最新版本的 portage 套件，emerge 指令就能恢復成正常狀態。過程中如果執行 emerge 遇到 python-exec 相關的問題，直接使用 `/usr/lib/python-exec/python<version>` 目錄下的 emerge 執行檔來更新套件即可解決。
