9797 字
49 分钟
新年要用新系统
2026-03-08
2026-03-28

这个blog两年没更新过了啊!靠着AI打气又拾起来了!

起因是节前Fusion 360警告我win10不受支持了,Gemini也建议我换个系统,并且疯狂安利Win11,安抚我说Win11已经不是几年前那种费拉不堪的状态了。

再信AI一次。

0 备份#

尤其是wsl2!

Terminal window
mkdir -p ~/wsl_backup/manifests
cd ~/wsl_backup/manifests
#######
# apt #
#######
# 导出所有手动安装的包(排除依赖包)
comm -23 <(apt-mark showmanual | sort -u) <(gzip -dc /var/log/installer/initial-status.gz | sed -n 's/^Package: //p' | sort -u) > apt-manual-packages.txt
# 如果上面命令报错或太复杂,用这个简单的版本(会包含一些基础包):
apt-mark showmanual > apt-manual-list.txt
###################
# python 和 conda #
###################
# 激活主环境,比如 base 或 pytorch
conda env export --no-builds > conda_environment.yml
pip freeze > requirements.txt
########
# rust #
########
cargo install --list > cargo-crates.txt
########
# node #
########
npm list -g --depth=0 > npm-global-packages.txt
##############
# shell conf #
##############
cd ~
tar -czvf ~/wsl_backup/dotfiles.tar.gz \
.zshrc \
.bashrc \
.profile \
.zsh_history \
.bash_history
.ssh \
.tmux.conf \
.fzf.zsh \
.gitconfig \
.vimrc \
.config/nvim \
.gnupg \
.condarc \
.pip \
.cargo/config.toml \

1 装系统#

装专业版即可,需要注意,分区别在跟Linux似的一块/用到底,2T盘可以先给C盘384G(384×1024=393216 MB384 \times 1024 = 393216 \text{ MB}),剩下的给D盘。使用的时候:

  • wsl2放D盘
  • 尽量用protable软件,全放D盘,那些死皮赖脸要待在C盘的软件再放C盘。

如此,重装的时候不会一锅端。

另外,装好了第一次见到Windows初始化配置的时候,微软强制登录Microsoft账号。此时Shift + F10,输入OOBE\BYPASSNRO自动重启,就可以选“我没有 Internet 连接”跳过账号登录。好处是可以完全自己决定user文件夹的名字。

进了系统先装驱动,尤其是显卡驱动,先装完驱动再装wsl2,但是主板带的什么MSI Center装完驱动可以卸了。

NVIDIA APP 选 NVIDIA studio 驱动,对跑CUDA的场景来说,Game Ready 驱动更新过于频繁。

2 清理微软的垃圾#

第一时间防火防盗防微软。

  1. 彻底禁止 Windows 自动更新驱动: Win + R 输入 gpedit.msc计算机配置 -> 管理模板 -> Windows 组件 -> Windows 更新 -> 管理从 Windows 更新提供的更新,右侧的 “Windows 更新不包括驱动程序”,选择 “已启用”。如此,让 Windows Update 只推安全补丁和功能更新,绝不会动显卡、网卡、声卡驱动。

  2. 保留 Defender,配置“码农豁免区” 打开 Windows 安全中心 -> 病毒和威胁防护 -> 管理设置,下拉找到 “排除项” -> 添加或删除排除项,酌情加入:

    • D:\WSL (放 WSL 镜像,防止扫描 vhdx 读写);
    • D:\DEV (放代码);
    • D:\MOEDL (放模型);
    • etc.
  3. 左下角“天气新闻”等等是 小组件” (Widgets)Win + R 输入 gpedit.msc计算机配置 -> 管理模板 -> Windows 组件 -> 小组件,右侧的 “允许小组件”,选择 “已禁用”

  4. 锁屏时“天气新闻”等待是 Windows 聚焦 (Windows Spotlight)

    • 右键桌面 -> “个性化” -> 选择 “锁屏界面”,在个 性化锁屏界面 中选择 “图片” (Picture)
    • 再往下看,如果有 “在锁屏界面上获取花絮、提示、技巧和更多内容”,选择 关闭
    • 如果有 “锁屏界面状态”,改为 无 (None)
  5. 开始菜单里的垃圾,右键桌面 -> “个性化” -> “开始”

    • 显示最近添加的应用 ->
    • 显示最常用的应用 ->
    • 在“开始”菜单中……显示最近打开的项目 ->
  6. 开始菜单的搜索 强行把本地搜索和 Bing 搜索混合

    • Win + R 输入 gpedit.msc用户配置 (User Configuration) -> 管理模板 (Administrative Templates) -> Windows 组件 (Windows Components) -> 文件资源管理器 (File Explorer),右侧找:“关闭文件资源管理器搜索框中的最近搜索条目显示” (Turn off display of recent search entries in the Windows Search box) ,选择 “已启用” (Enabled) 。重启资源管理器即可,taskkill /f /im explorer.exe; start explorer.exe注意:微软经常改这个策略的名字,比如也可能叫“关闭搜索框建议”。
    • 也可以执行 reg add "HKCU\Software\Policies\Microsoft\Windows\Explorer" /v "DisableSearchBoxSuggestions" /t REG_DWORD /d 1 /f 修改。
    • 关闭 “搜索要点” (Search Highlights)设置 -> 隐私和安全性 -> 搜索权限,往下找到 “更多设置”,关掉 “显示搜索要点” (Show search highlights)
  7. 任务栏的搜索框,任务栏空白处 右键 -> “任务栏设置”,第一项就是 “搜索”,选择 “隐藏”

  8. Steam/Epic党可以把XBox相关都端了:

    • 关闭 Xbox Game Bar:在 设置 -> 游戏 -> Xbox Game Bar,关闭 “允许控制器打开 Game Bar”
    • 开始菜单搜 “Xbox”,右键 -> 卸载
    • 唯一例外:游戏模式 (Game Mode) ,留着,在 设置 -> 游戏 -> 游戏模式 里,保持 开 (ON)。
  9. 关闭 OneDrive

    • 解除“文件夹备份”,任务栏右下角的那个“云朵”图标,右上角齿轮进入 设置,找到 “同步并备份” (Sync and backup) -> “管理备份” (Manage backup),把“文档”、“图片”、“桌面”全部关掉(Stop backup)。,此时系统会把这些文件夹的路径从 C:\Users\<username>\OneDrive\Desktop 恢复为 C:\Users\<username>\Desktop
    • 取消链接:设置 里 -> “账户” (Account),点击 “取消链接此电脑” (Unlink this PC)。
    • 卸载:设置 -> 应用 -> 安装的应用,搜 “OneDrive” -> 卸载
  10. 至于 基于虚拟化的安全性 (VBS)” / HVCI,模型和网上的说法都没定论。

    • Win 键搜索 “内核隔离” -> “内存完整性”,收益有限,关不关无所谓。
    • 网上流传的 bcdedit /set hypervisorlaunchtype off 不能设置, wsl2 要用。
  11. 找回Win10右键菜单:

    Terminal window
    reg add "HKCU\Software\Classes\CLSID\{86ca1aa0-34aa-4e8b-a509-50c905bae2a2}\InprocServer32" /f /ve

    重启资源管理器即可。后悔的话:

    Terminal window
    reg delete "HKCU\Software\Classes\CLSID\{86ca1aa0-34aa-4e8b-a509-50c905bae2a2}" /f

3 清理其他垃圾,装Windows下的常用工具#

  • 搜狗自带桌面壁纸和汪仔,记得删掉和关闭。
  • 取消WPS的云盘资源管理器图标、取消传输助手自启。
  • cherrystudio
  • 装no machine。
  • Snipaste用portable。
  • Motrix用portable。

装Git:

Terminal window
winget install --id Git.Git

装微信QQ:

Terminal window
winget install Tencent.WeChat
winget install Tencent.QQ

PotPlayer:

Terminal window
winget install Daum.PotPlayer

装powertoys:

Terminal window
winget install Microsoft.PowerToys

装Everything和Listary:

Terminal window
winget install voidtools.Everything
winget install -e --id Bopsoft.Listary

4 安装wsl2#

有显卡驱动了再装wsl2。

  1. 先装 WSL 本体,管理员 PowerShell:

    Terminal window
    wsl --install
  2. 装完建议重启,确认 WSL 版本:

    Terminal window
    wsl --version
  3. 在 D 盘准备一个专门目录 D:\WSL,安装一个临时 Ubuntu(会默认在 C 盘),启动一次 Ubuntu,完成用户名/密码初始化后退出。

    Terminal window
    wsl --install -d Ubuntu
  4. 把迁移到 D 盘:

    • 先关掉 WSL:
    Terminal window
    wsl --shutdown
    • 导出为 tar(临时文件建议也放 D 盘):
    Terminal window
    wsl --export Ubuntu D:\WSL\ubuntu.tar
    • 注销原来的 Ubuntu(这一步会删除 C 盘那个实例的注册与磁盘):
    Terminal window
    wsl --unregister Ubuntu
    • 导入到 D 盘(强制 WSL2):
    Terminal window
    wsl --import Ubuntu D:\WSL D:\WSL\ubuntu.tar --version 2

    现在 Ubuntu 的 ext4.vhdx 就在 D:\WSL\ 下面了。

  5. wsl2网络配置:建议按老方法,默认NAT,不要配自动代理、DNS隧道等配置。装个clash-verge,用自己写的订阅发布程序让Windows、wsl、PVE、rock5b、o6n等设备同步订阅配置,虽然heavy但是最稳妥:

    [wsl2]
    networkingMode=nat
    autoProxy=false
    dnsTunneling=false
    dnsProxy=false

    新wsl2支持mirror,直接走Windows的网卡,别的没什么问题,但是gemini-cli或codex可能会用不了:

    [wsl2]
    networkingMode=mirrored
    dnsTunneling=true
    autoProxy=true
    firewall=true

    此时wget下载chrome会自动拿到autoproxy配置走clash,但是apt不行,还得手动指定proxy

    Terminal window
    sudo tee /etc/apt/apt.conf.d/99proxy <<'EOF'
    Acquire::http::Proxy "http://127.0.0.1:7897";
    Acquire::https::Proxy "http://127.0.0.1:7897";
    EOF

5 初始化wsl2#

建议先改主机名:

Terminal window
sudo hostnamectl set-hostname new_name

用gemini和chatgpt写了个一键安装脚本,也可以一步一步执行。

#!/usr/bin/env bash
set -e
# ==========================================
# WSL2 Development Environment Bootstrap
# Target: Debian/Ubuntu
# Role: System, Shell, Rust, Node, Python, Git
# ==========================================
# 颜色定义,方便看日志
GREEN='\033[0;32m'
BLUE='\033[0;34m'
RED='\033[0;31m'
NC='\033[0m'
log() { echo -e "${GREEN}[SETUP] $1${NC}"; }
info() { echo -e "${BLUE}[INFO] $1${NC}"; }
error() { echo -e "${RED}[ERROR] $1${NC}"; }
# 0. 基础检查
# ------------------------------------------
if [ "$EUID" -eq 0 ]; then
error "请不要使用 root 运行此脚本,请使用普通用户运行!"
error "脚本内部会在需要时请求 sudo 权限。"
exit 1
fi
# 保持 sudo 权限活跃,防止脚本运行中途因为超时等待输入密码
sudo -v
while true; do sudo -n true; sleep 60; kill -0 "$$" || exit; done 2>/dev/null &
# 先把文件建好,后面的 Rust/NVM/Conda 就会以为你已经在使用 zsh 了,
# 它们会自动把 export PATH... 写入这两个文件。
touch "$HOME/.bashrc"
touch "$HOME/.zshrc"
### ========================================
### 1. 系统基础 (System Base)
### ========================================
log "📦 1. 更新系统并安装基础依赖..."
sudo apt update && sudo apt upgrade -y
# 安装必备工具
# build-essential: 编译环境
# software-properties-common:以此支持 add-apt-repository
sudo apt install -y \
git git-lfs curl wget ca-certificates \
build-essential pkg-config libssl-dev \
zip unzip btop tmux zsh vim \
net-tools iproute2 dnsutils \
jq tree procps make \
direnv rsync shellcheck \
software-properties-common ffmpeg \
fonts-noto-cjk fonts-wqy-microhei \
language-pack-zh-hans
# 注:如果要装yq,直接装go版二进制文件。
# 获取并转换系统架构名称 (匹配 mikefarah/yq 的命名规则)
ARCH=$(uname -m)
case $ARCH in
x86_64) YQ_ARCH="amd64" ;;
aarch64) YQ_ARCH="arm64" ;;
*) echo "不支持的架构"; exit 1 ;;
esac
# 下载最新版的二进制文件并安装到 /usr/local/bin
wget "https://github.com/mikefarah/yq/releases/latest/download/yq_linux_${YQ_ARCH}"
sudo mv "yq_linux_${YQ_ARCH}" /usr/local/bin/yq
sudo chmod +x /usr/local/bin/yq
# 安装fastfetch,因为sudo后常用 gpg/dirmngr 去连 keyserver,
# gpg/dirmngr 不读 apt 的代理配置,
# 而且 sudo 默认还会把 shell 里的 http_proxy/https_proxy 环境变量“清掉”,
# 导致直连 keyserver,然后被墙阻断,表现为“没反应”。
export http_proxy="http://127.0.0.1:7897"
export https_proxy="http://127.0.0.1:7897"
# 或永久添加代理
echo 'export http_proxy="http://127.0.0.1:7897"' >> ~/.bashrc
echo 'export https_proxy="http://127.0.0.1:7897"' >> ~/.bashrc
echo 'export http_proxy="http://127.0.0.1:7897"' >> ~/.zshrc
echo 'export https_proxy="http://127.0.0.1:7897"' >> ~/.zshrc
sudo --preserve-env=http_proxy,https_proxy add-apt-repository ppa:zhangsongcui3371/fastfetch -y
sudo apt update
sudo apt install fastfetch -y
### ========================================
### 2. Rust 工具链 (Rust & Modern CLI)
### ========================================
log "🦀 2. 安装 Rust 环境..."
if ! command -v rustup >/dev/null 2>&1; then
# 安装 Rustup
curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs | sh -s -- -y
else
info "Rustup 已安装,跳过."
fi
# 【关键】加载环境变量,确保 cargo 命令可用
# 如果你分步运行,请务必执行这一行
source "$HOME/.cargo/env"
log "🦀 安装 Cargo Binstall (加速工具安装)..."
if ! command -v cargo-binstall >/dev/null 2>&1; then
curl -L --proto '=https' --tlsv1.2 -sSf https://raw.githubusercontent.com/cargo-bins/cargo-binstall/main/install-from-binstall-release.sh | bash
fi
log "🦀 安装现代命令行工具 (Rust编写)..."
# 以用户token登录方式,解除 GitHub API 速率限制 (Rate Limiting)
# cargo-binstall 匿名查询API每小时仅60次,
# 不登录超过将报403,登录将提升到5000次
# Settings -> Developer settings -> Personal access tokens -> Tokens (classic),
# Expiration 选 "No expiration",Scopes (权限) 一个都不用勾
export GITHUB_TOKEN="ghp_xxx"
# bob-nvim: Neovim 版本管理器
# starship: 跨 Shell 提示符
# eza(ls), bat(cat), fd(find), ripgrep(grep), zoxide(cd)
cargo binstall -y \
eza bat fd-find ripgrep du-dust procs zoxide hyperfine \
cargo-watch cargo-nextest cargo-update cargo-audit \
starship bob-nvim topgrade tealdeer zellij
# atuin本地编译更不容易报错glibc的错误
cargo install atuin
# 装了tealdeer可以卸载 ubuntu 自带的 haskell tldr-hs
sudo apt remove -y tldr
### ========================================
### 3. Neovim (via Bob)
### ========================================
log "📝 3. 使用 Bob 安装最新 Neovim..."
# 确保 bob 在路径中
export PATH="$HOME/.cargo/bin:$PATH"
if command -v bob >/dev/null 2>&1; then
# 安装最新的稳定版
bob install stable
bob use stable
# 这一步是为了确保 nvim 能被系统找到
# Bob 默认把 nvim 放在 ~/.local/share/bob/nvim-bin
if [[ ":$PATH:" != *":$HOME/.local/share/bob/nvim-bin:"* ]]; then
info "提示: 请将 $HOME/.local/share/bob/nvim-bin 添加到你的 PATH 中"
fi
else
error "Bob 安装失败,跳过 Neovim 安装。"
fi
# 1. 让当前终端立刻生效(临时)
export PATH="$HOME/.local/share/bob/nvim-bin:$PATH"
# 2. 写入 .bashrc(永久生效,防止下次进 bash 找不到 nvim)
echo 'export PATH="$HOME/.local/share/bob/nvim-bin:$PATH"' >> ~/.bashrc
echo 'export PATH="$HOME/.local/share/bob/nvim-bin:$PATH"' >> ~/.zshrc
# 如果我觉得 neovim 过于毛坯,可以安装 lazyvim: https://www.lazyvim.org/installation
# 备份
for dir in ~/.config/nvim ~/.local/share/nvim ~/.local/state/nvim ~/.cache/nvim; do
[ -d "$dir" ] && mv "$dir" "${dir}.bak"
done
git clone https://github.com/LazyVim/starter ~/.config/nvim
rm -rf ~/.config/nvim/.git
# 第一次打开 nvim 要装东西,会比较慢,记得梯子
### ========================================
### 4. Node.js (NVM)
### ========================================
log "🟢 4. 安装 Node.js 环境..."
export NVM_DIR="$HOME/.nvm"
if [ ! -d "$NVM_DIR" ]; then
curl -o- https://raw.githubusercontent.com/nvm-sh/nvm/v0.40.4/install.sh | bash
fi
# 【关键】加载 NVM 环境
[ -s "$NVM_DIR/nvm.sh" ] && . "$NVM_DIR/nvm.sh"
log "🟢 安装 Node LTS..."
nvm install --lts
nvm use --lts
nvm alias default lts/*
log "🟢 安装 pnpm 和常用全局包..."
if ! command -v pnpm >/dev/null 2>&1; then
curl -fsSL https://get.pnpm.io/install.sh | sh -
fi
pnpm setup
# 临时加载 pnpm 路径
export PATH="$HOME/.local/share/pnpm:$PATH"
# 安装常用开发工具
pnpm add -g typescript tsx eslint prettier @google/gemini-cli @openai/codex
# pnpm 交互式构建批准
pnpm approve-builds -g
curl -fsSL https://claude.ai/install.sh | bash
echo 'export PATH="$HOME/.local/bin:$PATH"' >> ~/.bashrc && source ~/.bashrc
### ========================================
### 5. Python uv and miniconda
### ========================================
log "🐍 5. 安装 Python 环境 (uv)..."
if ! command -v uv >/dev/null 2>&1; then
curl -Ls https://astral.sh/uv/install.sh | sh
fi
# 临时加载 uv 路径
export PATH="$HOME/.local/bin:$PATH"
log "🐍 安装 Python 工具链..."
# uv tool install 类似于 pipx,安装到隔离环境
for tool in ruff mypy black poetry httpie; do
uv tool install "$tool" || true
done
log "🐍 5.5 安装 Miniconda..."
# 定义安装路径 (通常在 ~/miniconda3 或 ~/.miniconda3)
MINICONDA_PATH="$HOME/miniconda3"
if [ ! -d "$MINICONDA_PATH" ]; then
# 1. 下载最新的 Linux 安装脚本
wget "https://repo.anaconda.com/miniconda/Miniconda3-latest-Linux-$(uname -m).sh" -O miniconda.sh
# 2. 静默安装 (-b: batch mode, -p: prefix path)
bash miniconda.sh -b -p "$MINICONDA_PATH"
# 3. 删除安装包
rm miniconda.sh
# 4. 【关键配置】初始化 Shell (bash 和 zsh)
# 这步会自动把 conda 的路径写入 .bashrc 和 .zshrc
"$MINICONDA_PATH/bin/conda" init bash
"$MINICONDA_PATH/bin/conda" init zsh
# 5. 【强力推荐】禁止 Conda 默认激活 base 环境
# 否则每次打开终端前面都有个 (base),会干扰 uv 和系统 Python
"$MINICONDA_PATH/bin/conda" config --set auto_activate_base false
log "✅ Miniconda 安装完成 (Base 环境已禁用自动激活)"
else
info "Miniconda 已存在,跳过安装。"
fi
### ========================================
### 6. Git & GitHub
### ========================================
log "🐙 6. 安装 Git 工具..."
# 6.1 GitHub CLI (gh)
if ! command -v gh >/dev/null 2>&1; then
info "安装 gh cli..."
sudo mkdir -p -m 755 /etc/apt/keyrings
wget -qO- https://cli.github.com/packages/githubcli-archive-keyring.gpg | sudo tee /etc/apt/keyrings/githubcli-archive-keyring.gpg > /dev/null
sudo chmod go+r /etc/apt/keyrings/githubcli-archive-keyring.gpg
echo "deb [arch=$(dpkg --print-architecture) signed-by=/etc/apt/keyrings/githubcli-archive-keyring.gpg] https://cli.github.com/packages stable main" | sudo tee /etc/apt/sources.list.d/github-cli.list > /dev/null
sudo apt update
sudo apt install -y gh
fi
# 6.2 Lazygit (自动架构检测)
if ! command -v lazygit >/dev/null 2>&1; then
info "安装 lazygit..."
LAZYGIT_VERSION=$(curl -s "https://api.github.com/repos/jesseduffield/lazygit/releases/latest" | grep -Po '"tag_name": "v\K[^"]*')
ARCH=$(uname -m)
case $ARCH in
x86_64) LG_ARCH="x86_64" ;;
aarch64) LG_ARCH="arm64" ;;
*) error "不支持的架构: $ARCH"; exit 1 ;;
esac
curl -Lo lazygit.tar.gz "https://github.com/jesseduffield/lazygit/releases/download/v${LAZYGIT_VERSION}/lazygit_${LAZYGIT_VERSION}_Linux_${LG_ARCH}.tar.gz"
tar xf lazygit.tar.gz lazygit
sudo install lazygit /usr/local/bin
rm -f lazygit lazygit.tar.gz
fi
### ========================================
### 7. Shell (Oh-My-Zsh + Starship)
### ========================================
log "💻 7. 配置 Shell 环境..."
# 安装 OMZ
if [ ! -d "$HOME/.oh-my-zsh" ]; then
info "安装 Oh-My-Zsh..."
# --unattended: 不自动进入 zsh
# --keep-zshrc: 如果你已经有配置文件,不覆盖
sh -c "$(curl -fsSL https://raw.githubusercontent.com/ohmyzsh/ohmyzsh/master/tools/install.sh)" "" --unattended --keep-zshrc
fi
# 安装 fzf
if [ ! -d "$HOME/.fzf" ]; then
info "安装 FZF (命令行模糊搜索)..."
git clone --depth 1 https://github.com/junegunn/fzf.git "$HOME/.fzf"
# --all: 自动启用 key-bindings (Ctrl+R/T) 和 fuzzy-completion
# --no-bash/--no-fish: 我们只配 zsh,不让它乱改其他 shell
"$HOME/.fzf/install" --all
else
info "FZF 已安装,跳过."
fi
ZSHRC="$HOME/.zshrc"
# # 3.1 禁用 OMZ 主题 (让位给 Starship)
# # 我们用 sed 查找并替换 ZSH_THEME="..." 为 ZSH_THEME=""
# if grep -q 'ZSH_THEME=' "$ZSHRC"; then
# sed -i 's/^ZSH_THEME=".*"/ZSH_THEME=""/' "$ZSHRC"
# fi
# A. 备份现有的 .zshrc (里面有 Conda 和 FZF 的配置,不能丢!)
if [ -f "$ZSHRC" ]; then
cp "$ZSHRC" "$ZSHRC.pre-bootstrap"
info "已备份现有配置到 .zshrc.pre-bootstrap"
fi
# B. 获取 OMZ 官方模板 (作为地基)
# 我们把模板下载下来,追加到现有的 .zshrc 后面,这样既保留了 Conda/FZF,又有了 OMZ 的功能
if [ ! -f "$HOME/.oh-my-zsh/templates/zshrc.zsh-template" ]; then
# 万一模板没下载下来,手动抓取
curl -fsSL https://raw.githubusercontent.com/ohmyzsh/ohmyzsh/master/templates/zshrc.zsh-template > temp_zshrc
else
cp "$HOME/.oh-my-zsh/templates/zshrc.zsh-template" temp_zshrc
fi
# C. 合并配置 (关键一步!)
# 1. 保留 Conda/FZF (在文件头部)
# 2. 插入 OMZ 模板 (在中间)
# 3. 插入我们自定义的 PATH 和 Tool (在底部)
# 读取现有内容 (Conda + FZF)
CURRENT_CONTENT=$(cat "$ZSHRC")
# 覆写 .zshrc:先放 OMZ 模板,再放 原内容
cat temp_zshrc > "$ZSHRC"
echo "" >> "$ZSHRC"
echo "$CURRENT_CONTENT" >> "$ZSHRC"
rm temp_zshrc
# D. 修改配置 (现在里面有东西了,可以改了)
# 1. 禁用 OMZ 主题 (让 Starship 接管)
# 使用 sed 将 ZSH_THEME="robbyrussell" 替换为 ZSH_THEME=""
sed -i 's/^ZSH_THEME=".*"/ZSH_THEME=""/' "$ZSHRC"
# 2. 启用常用插件 (git, extract, sudo)
# 这是 OMZ 的精华,必须开
sed -i 's/^plugins=(git)/plugins=(git git-extras debian tmux screen history extract colorize web-search docker sudo)/' "$ZSHRC"
# 3. 注入环境变量 (把之前漏在 bashrc 里的补回来)
cat <<EOT >> "$ZSHRC"
# --- Bootstrap Generated Config (PATHs & Tools) ---
# 1. PATH 补全 (Rust, Node, Bob, Local Bin)
# 注意:Miniconda 已经在文件头部配好了,这里不需要重复
export PATH="\$HOME/.local/bin:\$HOME/.cargo/bin:\$HOME/.local/share/bob/nvim-bin:\$HOME/.local/share/pnpm:\$PATH"
. "\$HOME/.cargo/env"
# 2. 工具启动
eval "\$(atuin init zsh)" # 启动 atuin (需要在 fzf 初始化之后)
eval "\$(starship init zsh)" # 启动 Starship
eval "\$(zoxide init zsh)" # 启动 Zoxide (z 命令)
# 3. 别名 (Aliases)
alias cat='bat'
alias ls='eza --icons' # 基础 ls 变彩色+图标
alias ll='eza -lah --icons --git ' # ll 显示详情、Git状态、隐藏文件
alias lt='eza -lah --tree --level=2 --icons' # lt 显示目录树(2层)
alias vi='nvim'
alias vim='nvim'
alias zj='zellij'
alias ra='ranger' # 如果装了 ranger
# 4. NVM
export NVM_DIR="\$HOME/.nvm"
[ -s "\$NVM_DIR/nvm.sh" ] && . "\$NVM_DIR/nvm.sh" # This loads nvm
[ -s "\$NVM_DIR/bash_completion" ] && \. "\$NVM_DIR/bash_completion" # This loads nvm bash_completion
# 5. pnpm
export PNPM_HOME="\$HOME/.local/share/pnpm"
case ":\$PATH:" in
*":\$PNPM_HOME:"*) ;;
*) export PATH="\$PNPM_HOME:\$PATH" ;;
esac
# pnpm end
EOT
# 1. 删掉顶部的那行 FZF 配置 (使用 sed)
#sed -i '/\[ -f ~\/.fzf.zsh \] && source ~\/.fzf.zsh/d' "$ZSHRC"
# 2. 把它追加到文件最末尾
#echo '[ -f ~/.fzf.zsh ] && source ~/.fzf.zsh' >> "$ZSHRC"
# 修改系统shell
chsh -s $(which zsh)
log "✅ .zshrc 修复完成!(Conda + FZF + OMZ + Tools 全部合体)"
log "👉 现在输入 'zsh' 进入新环境吧!"
# 安装 ohmytmux
curl -fsSL "https://github.com/gpakosz/.tmux/raw/refs/heads/master/install.sh#$(date +%s)" | bash
# 安装docker
curl -fsSL https://get.docker.com -o get-docker.sh
sh get-docker.sh

5.1 关于yt-dlp#

Terminal window
# 建议使用default安装
# 直接`uv tool install yt-dlp --with curl_cffi --with pycryptodomex --upgrade --force`会导致curl_cffi变成最新版
# 但yt-dlp可能不支持这么高版本的curl_cffi,而default的extra会自动补齐requests、urllib3、websockets、mutagen、brotli、pycryptodomex
uv tool install 'yt-dlp[default,curl-cffi]' --upgrade --force
# 记得装ffmpeg和nvm node,配置node变量
mkdir -p ~/.config/yt-dlp && cat <<EOF >> ~/.config/yt-dlp/config
--js-runtimes node
--remote-components ejs:github
EOF

arm设备可能会遇到curl_cffi安装了也没有伪装的问题,转回x86平台吧,暂时无解,像这样:

Terminal window
yt-dlp --list-impersonate-targets
[info] Available impersonate targets
Client OS Source
--------------------------------------------
Tor - curl_cffi>=0.11 (unavailable)
Edge - curl_cffi (unavailable)
Firefox - curl_cffi>=0.10 (unavailable)
Safari - curl_cffi (unavailable)
Chrome - curl_cffi (unavailable)

5.2 关于恢复备份的.ssh文件夹#

拷过去权限得改对

Terminal window
chmod 700 ~/.ssh
chmod 600 ~/.ssh/id_*
chmod 644 ~/.ssh/*.pub
# 如果有
chmod 600 ~/.ssh/config

5.3 关于nomachine#

安装时如果遇到60%卡住终端没响应,关掉重开一个终端修复一下安装即可,其实是已经装好,之是post install脚本可能有问题挂起了:

Terminal window
sudo killall apt apt-get dpkg
sudo dpkg --configure -a

arm上有时会遇到wayland桌面环境nomachine连上是黑底各种乱七八糟的竖条纹的情况,改回x11就会好:

Terminal window
sudo vim /etc/gdm3/custom.conf
WaylandEnable=false

重启gdm3,或直接重启系统更稳妥。

5.4 关于中文环境,上面装了字体和中文编码,需要配置语言环境:#

Terminal window
sudo dpkg-reconfigure locales

zh_CN.UTF-8 UTF-8即可,echo $LANG还不行的话:

Terminal window
echo "export LANGUAGE=zh_CN.zh" >> ~/.bashrc
echo "export LC_ALL=zh_CN.UTF-8" >> ~/.bashrc
echo "export LANG=zh_CN.UTF-8" >> ~/.bashrc
echo "export LANGUAGE=zh_CN.zh" >> ~/.zshrc
echo "export LC_ALL=zh_CN.UTF-8" >> ~/.zshrc
echo "export LANG=zh_CN.UTF-8" >> ~/.zshrc

关于输入法,就用 Fcitx5 默认,省事:

Terminal window
# 安装 Fcitx5 核心、中文拼音插件及图形化配置工具
sudo apt update
sudo apt install fcitx5 fcitx5-chinese-addons fcitx5-frontend-gtk3 fcitx5-frontend-gtk2 fcitx5-frontend-qt5 kde-config-fcitx5 fcitx5-config-qt
# 将环境变量写入 ~/.xprofile 或 /etc/profile
echo 'export GTK_IM_MODULE=fcitx' >> ~/.xprofile
echo 'export QT_IM_MODULE=fcitx' >> ~/.xprofile
echo 'export XMODIFIERS=@im=fcitx' >> ~/.xprofile

运行im-config,选择fcitx5,重启,右上角出现 Fcitx5 的小键盘图标,右键点击选择 配置 (Configure),在右侧列表中找到 拼音 (Pinyin) 并添加到左侧。

现在使用topgrade自动感知并更新安装工具。

5.5 关于starship#

配置文件可以改好看点:

Terminal window
cat > ~/.config/starship.toml << 'EOF'
# ==========================
# 1. 机器身份识别 (防错核心)
# ==========================
[hostname]
ssh_only = false # 即使是本地终端也显示主机名
format = '在 [$hostname]($style) '
style = "bold purple" # 建议:不同机器改成不同颜色,比如测试板用红色,生产环境用紫色
# IP 显示 (仅限 SSH)
[localip]
ssh_only = true # 性能优化:只有在你通过 SSH 连入时才去查 IP,本地直连时不查
disabled = false
format = '🌐 [$localipv4]($style) '
style = "bold blue"
# ==========================
# 2. 资源报警 (阈值触发,零延迟感知)
# ==========================
# 内存使用率
[memory_usage]
disabled = false
threshold = 80 # 性能优化:内存占用没超过 80% 时完全不渲染此模块,节省算力
format = '🐏 [$ram]($style) '
style = "bold red"
# ==========================
# 3. 运维排障辅助
# ==========================
# 命令执行耗时 (仅当命令执行超过 2 秒时显示)
[cmd_duration]
min_time = 2000
format = '耗时 [$duration]($style) '
style = "bold yellow"
# 退出码状态 (命令报错时变红)
[status]
disabled = false
format = '[$symbol$status]($style) '
success_symbol = "🟢 "
symbol = "🔴 "
style = "bold red"
EOF

6 tailscale 与 ipv6#

节后移动给的GPON坏了,运营商换了一个后发现有ipv6了。家里的rock5b、PVE工作站都可以用ipv6直连了。

PVE装tailscale#

需要注意的是,PVE默认没打开ipv6支持,所以需要手动设置:

Terminal window
vi /etc/sysctl.conf
net.ipv6.conf.all.disable_ipv6 = 0
net.ipv6.conf.default.disable_ipv6 = 0
net.ipv6.conf.lo.disable_ipv6 = 0
net.ipv6.conf.all.forwarding = 1
vi /etc/sysctl.d/99-tailscale.conf
net.ipv4.ip_forward = 1
net.ipv6.conf.all.forwarding = 1
# 或:
echo 'net.ipv4.ip_forward = 1' | tee -a /etc/sysctl.d/99-tailscale.conf
echo 'net.ipv6.conf.all.forwarding = 1' | tee -a /etc/sysctl.d/99-tailscale.conf
# 应用网络配置
ifreload -a

此时用ip -6 addr show会发现vmbr0上多出来一个2409(移动)开头的地址,再用手机等公网设备打开tailscale去ping这台PVE就会从relay转到direct了。

Terminal window
5: vmbr0: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 state UP qlen 1000
inet6 2409:xxxx:xxx:xxxx:xxxx:xxxx:xxxx:xxxx/64 scope global dynamic mngtmpaddr proto kernel_ra
valid_lft 86181sec preferred_lft 14181sec

全家搬到tailscale#

通过rock5b转发流量,可以把家里的192.168.1.x/24都放在tailscale上。在rock5b上配置系统转发:

Terminal window
echo 'net.ipv4.ip_forward = 1' | sudo tee -a /etc/sysctl.conf
echo 'net.ipv6.conf.all.forwarding = 1' | sudo tee -a /etc/sysctl.conf
sudo sysctl -p
sudo tailscale up --advertise-routes=192.168.1.0/24 --accept-dns=false

出于安全考虑,被“广播”的网段 Tailscale 默认不信任,需要手动确认:

  1. 登录 Tailscale Admin Console
  2. Rock 5B 点击右侧的 三个点 (…)
  3. 选择 Edit route settings
  4. 在弹出的窗口中,勾选广播的网段 192.168.1.0/24
  5. 点击 Save

此时,手机用运营商的网,打开tailscale,也可以使用192.168.1.0/24的IP控制家里的设备了。

6.1 tailscale可能存在的坑1 - 外带设备o6n回家后只能单向ping#

6.1.1 现象#

在家里的局域网 (192.168.1.0/24) 有:

  • 开发板 A (o6n): 192.168.1.140
  • 开发板 B (Rock5B): 192.168.1.120
  • Windows 11 (含 WSL2): 192.168.1.125

症状: 开发板 A (140) 可以正常 Ping 通局域网内的 B (120) 和 Windows (125),但是 B 和 Windows 无法 Ping 通 A。呈现典型的 “单向失联” 状态。

6.1.2 排查#

遇到单向不通,通常的排查链路是:物理连接 -> 防火墙 -> ARP 解析 -> 路由表。

  1. 排除防火墙与物理隔离: 检查了 ufwiptables,未发现拦截规则。
  2. 检查 ARP 缓存 (ip neigh show): 开发板 A 上能成功获取到 B 和 Windows 的 MAC 地址,说明物理层和数据链路层通信正常。
  3. 排查代理软件: 怀疑是 Windows 端的 Clash TUN 模式劫持了局域网流量。关闭 TUN 模式并使用 ping -S 192.168.1.125 192.168.1.140 强制指定源 IP,依然不通,排除发件端代理问题。
  4. 定位核心问题——策略路由 (ip rule): 在开发板 A (140) 上执行 ip rule 发现异常:
    Terminal window
    5270: from all lookup 52
    32766: from all lookup main
    执行 ip route show table 52 发现:
    Terminal window
    192.168.1.0/24 dev tailscale0

6.1.3 原因#

网络中存在另一台设备——开发板 B (Rock5B, 192.168.1.120) 通过 Tailscale 执行了 --advertise-routes=192.168.1.0/24,广播了该局域网的 Subnet Route(子网路由),而开发板 A (140) 默认接受了该路由。

Tailscale 会接管系统的策略路由,创建一个高优先级(5270)的路由表 52。 当开发板 A (140) 收到局域网(比如来自 Windows 或 Rock5B 本身)发来的 Ping 请求时,它需要发送 ICMP Echo Reply(回包)。在查找路由时,它优先命中了表 52 的规则,导致本该通过物理网卡(wlan0/eth0)发回本地局域网的数据包,被错误地塞进了 Tailscale 的虚拟隧道 (tailscale0) 中,造成了“有去无回”的路由黑洞。

6.1.4 解决#

由于既需要在家时局域网直连,又需要带开发板外出时能通过 Tailscale 访问家里的内网,直接关闭 accept-routes 并不符合需求。

最优解:基于源 IP 的智能分流 (Smart Routing) 在开发板 A (140) 上添加一条比 Tailscale 优先级更高(例如 2500)的策略路由规则:

Terminal window
sudo ip rule add from 192.168.1.140 to 192.168.1.0/24 priority 2500 lookup main

原理解析:

  • 在家时: 开发板 A 分配到的物理 IP 是 192.168.1.140。当局域网设备 Ping 它时,它使用该 IP 作为源 IP 回包,精确命中此规则,流量直接走 main 表(物理网卡),瞬间连通。
  • 外出时: 开发板 A 连接其他网络,物理 IP 改变(不再是 192.168.1.140)。该规则失效,系统继续往下匹配,命中 Tailscale 的表 52,从而实现正常的异地组网访问。

避坑 尽量避免将家中的主路由网段设置为 192.168.1.0/24192.168.0.0/24 这种极其常见的默认网段。如果携带开启了异地组网的设备去酒店或单位,一旦公共 Wi-Fi 也是这个网段,就会引发严重的子网冲突(Overlapping Subnets)。建议改成类似 10.88.99.0/24 的冷门私有网段。

6.1.5 进阶固化:使用 systemd 实现路由规则开机自启#

为了在 Armbian/Debian 12 等现代 Linux 环境下一劳永逸地解决此问题,最稳妥的方案是创建一个标准的 systemd 服务,确保在网络和 Tailscale 均就绪后,自动注入我们的高优先级本地路由规则。

Step 1: 编写路由修复脚本#

创建一个专门的 Shell 脚本,采用“先清理后添加”的逻辑,防止因网络服务重启导致规则重复堆叠。

Terminal window
sudo vim /usr/local/bin/fix-lan-route.sh

内容为:

#!/bin/bash
# 忽略报错删除可能存在的旧规则,避免重复
ip rule del from 192.168.1.140 to 192.168.1.0/24 priority 2500 lookup main 2>/dev/null
# 添加本地局域网高优先级规则
ip rule add from 192.168.1.140 to 192.168.1.0/24 priority 2500 lookup main

赋予执行权限:

Terminal window
sudo chmod +x /usr/local/bin/fix-lan-route.sh

Step 2: 编写 systemd 服务文件#

配置服务依赖,确保脚本在网络连通 (network-online.target) 且 Tailscale 服务启动 (tailscaled.service) 之后再执行。

Terminal window
sudo vim /etc/systemd/system/fix-lan-route.service

内容为:

[Unit]
Description=Fix LAN routing conflict with Tailscale
After=network-online.target tailscaled.service
Wants=network-online.target
[Service]
Type=oneshot
ExecStart=/usr/local/bin/fix-lan-route.sh
RemainAfterExit=yes
[Install]
WantedBy=multi-user.target

Step 3: 激活服务并验证#

重载 systemd 守护进程,并将该服务设为开机自启:

Terminal window
# 重载系统配置
sudo systemctl daemon-reload
# 设置为开机自启
sudo systemctl enable fix-lan-route.service
# 立即启动该服务进行验证
sudo systemctl start fix-lan-route.service

最终验证: 在终端执行 ip rule 命令。如果列表中清晰地出现了 2500: from 192.168.1.140 to 192.168.1.0/24 lookup main,即代表配置大功告成。从此,无论设备重启还是随时启停 Tailscale,本地局域网的通信策略都将稳如泰山。

6.2 tailscale可能存在的坑2 - 子网发布网关rock5b开启TUN导致tailscale无法链接#

6.2.1 现象:#

Clash开TUN以后,tailscale启动报错。

  1. 开启TUN时的异常日志:

    Terminal window
    ➜  ~ sudo systemctl status tailscaled.service
    tailscaled.service - Tailscale node agent
         Loaded: loaded (/lib/systemd/system/tailscaled.service; enabled; vendor preset: enabled)
         Active: active (running) since Sat 2026-03-28 10:00:48 CST; 28s ago
           Docs: https://tailscale.com/docs/
       Main PID: 578694 (tailscaled)
          Tasks: 15 (limit: 18526)
         Memory: 54.6M
            CPU: 878ms
         CGroup: /system.slice/tailscaled.service
                 └─578694 /usr/sbin/tailscaled --state=/var/lib/tailscale/tailscaled.state --socket=/run/tailscale/tailscaled.sock --port=41641
    Mar 28 10:00:48 rock-5b tailscaled[578694]: logpolicy: using $STATE_DIRECTORY, "/var/lib/tailscale"
    Mar 28 10:00:48 rock-5b tailscaled[578694]: linkChange: in state NoState; PAC or proxyConfig changed; updating routes
    Mar 28 10:00:48 rock-5b tailscaled[578694]: got LocalBackend in 56ms
    Mar 28 10:00:48 rock-5b tailscaled[578694]: Start
    Mar 28 10:00:48 rock-5b tailscaled[578694]: ipnext: active extensions: conn25, portlist, posture, clientupdate, relayserver, taildrop
    Mar 28 10:00:48 rock-5b tailscaled[578694]: Backend: logs: be:de27a4d50030bb2fa9eba85172884903b5e6894feb434ee6ece1a4b0f407b618 fe:
    Mar 28 10:00:48 rock-5b tailscaled[578694]: control: client.Login(0)
    Mar 28 10:00:48 rock-5b tailscaled[578694]: health(warnable=warming-up): error: Tailscale is starting. Please wait.
    Mar 28 10:00:48 rock-5b tailscaled[578694]: control: doLogin(regen=false, hasUrl=false)
    Mar 28 10:00:53 rock-5b tailscaled[578694]: health(warnable=warming-up): ok

    此时:

    Terminal window
    ➜  ~ tailscale status
    unexpected state: NoState
  2. 关闭tun后的正常日志:

    Terminal window
    ➜  ~ sudo systemctl status tailscaled.service
    tailscaled.service - Tailscale node agent
         Loaded: loaded (/lib/systemd/system/tailscaled.service; enabled; vendor preset: enabled)
         Active: active (running) since Sat 2026-03-28 10:01:28 CST; 3s ago
           Docs: https://tailscale.com/docs/
       Main PID: 579582 (tailscaled)
          Tasks: 15 (limit: 18526)
         Memory: 42.3M
            CPU: 708ms
         CGroup: /system.slice/tailscaled.service
                 └─579582 /usr/sbin/tailscaled --state=/var/lib/tailscale/tailscaled.state --socket=/run/tailscale/tailscaled.sock --port=41641
    Mar 28 10:01:31 rock-5b tailscaled[579582]: monitor: RTM_DELROUTE: src=198.18.0.1/0, dst=198.18.0.1/32, gw=, outif=16, table=255
    Mar 28 10:01:31 rock-5b tailscaled[579582]: LinkChange: major, rebinding: old: interfaces.State{defaultRoute=enP4p65s0 ifs={Meta:[198.18.0.>
    Mar 28 10:01:31 rock-5b tailscaled[579582]: dns: Set: {DefaultResolvers:[] Routes:{} SearchDomains:[] Hosts:6}
    Mar 28 10:01:31 rock-5b tailscaled[579582]: dns: Resolvercfg: {Routes:{} Hosts:6 LocalDomains:[]}
    Mar 28 10:01:31 rock-5b tailscaled[579582]: dns: OScfg: {}
    Mar 28 10:01:31 rock-5b tailscaled[579582]: wgengine: set DNS config again after major link change
    Mar 28 10:01:31 rock-5b tailscaled[579582]: router: portUpdate(port=41641, network=udp6)
    Mar 28 10:01:31 rock-5b tailscaled[579582]: Rebind; defIf="enP4p65s0", ips=[192.168.1.120/24 2409:8a70:b61:b91::1005/128 2409:8a70:b61:b91:>
    Mar 28 10:01:31 rock-5b tailscaled[579582]: magicsock: 0 active derp conns
    Mar 28 10:01:31 rock-5b tailscaled[579582]: router: portUpdate(port=41641, network=udp4)

    此时:

    Terminal window
    ➜  ~ tailscale status
    100.xx.xx.xx    rock-5b              xxx@  linux    -
    100.xx.xx.xx     server            xxx@  linux    offline, last seen xxd ago
    100.xx.xx.xx phone1               xxx@  android  offline, last seen xxd ago
    100.xx.xx.xx   orion-o6             xxx@  linux    offline, last seen xxh ago
    100.xx.xx.xx   phone2   xxx@  android  offline, last seen xxh ago
    100.xx.xx.xx     macbook-pro   xxx@  macOS    offline, last seen xxd ago

6.2.2 原因#

简单来说,Clash 的 TUN 模式把 Tailscale 的建连流量给“劫持”了。

  1. Clash TUN 的霸道机制:当开启 Clash TUN 模式时,它会在系统里建一个虚拟网卡(比如 Mihomo),并通过修改系统路由表(下发 0.0.0.0/1 这种默认路由)或者用 fwmark(防火墙标记)把机器上所有的流量都强行吸进自己的核心里。同时,它通常还会开启 Fake-IP 来劫持 DNS 请求(日志里关闭 TUN 时出现的 198.18.0.1 就是 Clash 的 Fake-IP 网段)。
  2. Tailscale 的迷失tailscaled 服务启动时,需要向它自己的控制面板(login.tailscale.com)和分布在全球的 DERP 中继服务器发送 UDP 握手包来建立状态。
  3. 死锁形成:开启 TUN 后,Tailscale 刚发出建连请求,就被 Clash 一把抓走。如果这些流量被 Clash 丢到了错误的代理节点,或者由于 Fake-IP 导致 Tailscale 拿不到中继服务器的真实 IP,它的握手包就永远发不出去,一直卡在 NoState 等待响应。

6.2.3 解决#

思路很简单:给 Clash 设个规矩,让它对 Tailscale 相关的 IP、域名和进程“高抬贵手”,直接放行。

添加 Merge (合并) 路由规则#

为了万无一失,我们需要让 Mihomo 核心彻底放行 tailscaled 进程和相关域名的流量。

将以下规则添加到直连规则(Direct)中,并且要放在规则列表的 最前面

rules:
# 直连 Tailscale 后台守护进程
- PROCESS-NAME,tailscaled,DIRECT
# 直连 Tailscale 的内网网段
- IP-CIDR,100.64.0.0/10,DIRECT
# 直连 Tailscale 的控制面域名
- DOMAIN-SUFFIX,tailscale.com,DIRECT
- DOMAIN-SUFFIX,tailscale.io,DIRECT

修复 Fake-IP 污染(关键):如果 Clash 配置里开启了 Fake-IP,必须把 Tailscale 的域名加入 fake-ip-filter,否则它连不上中继。在配置(实际配置位于dns_config.yaml)中补充:

dns:
fake-ip-filter:
- '*.tailscale.com'
- '*.tailscale.io'

应用这些设置并重启 Clash 的 TUN 开关后,Clash 就会把 Tailscale 的建连流量和 100.x.x.x 的内网流量原封不动地交还给系统路由表,两边就能和平共处了。

注:关于如何查找配置文件位置#

在 Linux 中,要查看一个进程在启动时到底读取了哪些文件,最强大且标准的工具是 strace。它可以追踪进程调用的所有系统 API,包括打开文件的操作。

因为 clash-verge 是基于 Tauri 的应用,它会启动多个子进程(前端渲染、后端服务等),所以需要加上特定参数来追踪所有子进程,并过滤出跟配置文件相关的操作。

Step1: 进程追踪#

安装 strace ,用 strace 运行组合命令并输出日志,将 strace 加在 clash 环境变量之后、 clash-verge 之前。为了防止终端输出被海量的系统调用淹没,把日志重定向到一个文本文件中:

Terminal window
sudo apt install strace
WEBKIT_DISABLE_COMPOSITING_MODE=1 WEBKIT_DISABLE_DMABUF_RENDERER=1 WEBKIT_DISABLE_SANDBOX_THIS_IS_DANGEROUS=1 GDK_BACKEND=x11 strace -f -e trace=open,openat clash-verge 2> startup_trace.log

参数解释:

  • -f:告诉 strace 追踪所有由 clash-verge 派生出来的子进程(关键,因为配置通常是核心子进程读取的)。
  • -e trace=open,openat:只监听“打开文件”相关的系统调用,过滤掉网络、内存等无关操作。
  • 2> startup_trace.log:将 strace 的标准错误输出(它的追踪日志默认输出到这里)保存到 startup_trace.log 文件中。

运行上面这条命令后,等终端停留在之前的状态(说明应用已经启动并转入后台监听),按 Ctrl + C 结束它。

Step2: 分析追踪日志#

现在当前目录下生成了一个 startup_trace.log 文件。可以用 grep 过滤出所有常见的配置文件后缀(如 .yaml, .yml, .json):

Terminal window
grep -i -E '\.yaml|\.yml|\.json' startup_trace.log | grep -v "ENOENT"

参数解释:

  • -i -E '\.yaml|\.yml|\.json':忽略大小写,匹配这三种常见的配置文件格式。
  • grep -v "ENOENT":过滤掉“文件不存在(Error No Entry)”的记录。程序在启动时通常会按照一定顺序去多个默认目录“试探性”地寻找配置文件,过滤掉找不到的记录,剩下的就是它真正成功读取到的文件路径:
    ➜ ~ grep -i -E '\.yaml|\.yml|\.json' startup_trace.log | grep -v "ENOENT"
    [pid 568056] openat(AT_FDCWD, "/home/pi/.local/share/io.github.clash-verge-rev.clash-verge-rev/verge.yaml", O_RDONLY|O_CLOEXEC) = 19
    [pid 568057] openat(AT_FDCWD, "/home/pi/.local/share/io.github.clash-verge-rev.clash-verge-rev/config.yaml", O_RDONLY|O_CLOEXEC) = 19
    [pid 568057] openat(AT_FDCWD, "/home/pi/.local/share/io.github.clash-verge-rev.clash-verge-rev/verge.yaml", O_RDONLY|O_CLOEXEC) = 19
    [pid 568056] openat(AT_FDCWD, "/home/pi/.local/share/io.github.clash-verge-rev.clash-verge-rev/profiles.yaml", O_RDONLY|O_CLOEXEC) = 19
    [pid 568036] openat(AT_FDCWD, "/home/pi/.config/io.github.clash-verge-rev.clash-verge-rev/window_state.json", O_RDONLY|O_CLOEXEC) = 20
    [pid 568057] openat(AT_FDCWD, "/home/pi/.local/share/io.github.clash-verge-rev.clash-verge-rev/profiles/RrCXXb9eVReP.yaml", O_RDONLY|O_CLOEXEC) = 30
    [pid 568058] openat(AT_FDCWD, "/home/pi/.local/share/io.github.clash-verge-rev.clash-verge-rev/profiles/Merge.yaml", O_RDONLY|O_CLOEXEC <unfinished ...>
    [pid 568058] openat(AT_FDCWD, "/home/pi/.local/share/io.github.clash-verge-rev.clash-verge-rev/dns_config.yaml", O_RDONLY|O_CLOEXEC) = 21
    [pid 568057] openat(AT_FDCWD, "/home/pi/.local/share/io.github.clash-verge-rev.clash-verge-rev/clash-verge.yaml", O_WRONLY|O_CREAT|O_TRUNC|O_CLOEXEC, 0666) = 21
    [pid 568058] openat(AT_FDCWD, "/home/pi/.local/share/io.github.clash-verge-rev.clash-verge-rev/clash-verge-check.yaml", O_WRONLY|O_CREAT|O_TRUNC|O_CLOEXEC, 0666) = 21
    [pid 568095] openat(AT_FDCWD, "/home/pi/.local/share/io.github.clash-verge-rev.clash-verge-rev/clash-verge-check.yaml", O_RDONLY|O_CLOEXEC) = 3
    [pid 568057] openat(AT_FDCWD, "/home/pi/.local/share/io.github.clash-verge-rev.clash-verge-rev/clash-verge.yaml", O_WRONLY|O_CREAT|O_TRUNC|O_CLOEXEC, 0666) = 21```

7 self host atuin#

atuin 的主机间 sync 命令行历史记录非常有用,按照官方说明 Self Hosting - Docker Compose 用 Docker Compose 的方式可以很容易的拉起来一个私有服务。之后,在任意需要 atuin sync 的主机上,修改 atuin 的配置文件 ~/.config/atuin/config.tomlsync_address = "http://<server-ip:port>"即可。

将本地的shell命令导入 atuin 管理,运行atuin import autoatuin import zsh。注:如果要 atuin 接管 ctrl-r,则要保证.zshrc中 atuin 在 fzf 之后初始化。

接下来,在第一台要同步的主机命令行里,运行首次用户注册:atuin register -u <username> -e <email>,此时 atuin 会让你运行atuin key拿到助记词,用来在之后的登录时使用。注册完毕后运行atuin sync向服务器同步历史记录。完成注册后,再在其他主机上只需要执行登录即可:atuin login -u <username>再输入助记词即可,成功后atuin sync

此时ctrl-r打开 atuin ,多次按ctrl-r就会在 全局、本机、本次会话、此目录 等环境中切换。在全局环境,选中命令按ctrl-o进入检视页会看到此命令所属的主机。

最后设置系统服务,新建sudo vim /etc/systemd/system/atuin.service,把官方说明中systemd那段脚本贴进去,然后将WorkingDirectory换成刚才docker-compose.yml所在的绝对路径即可,再sudo systemctl daemon-reloadsudo systemctl enable --now atuin.servicesudo systemctl status atuin.service即可看到开机自启的 atuin 服务。

firefox esr apt安装说明

8 启用 rock 5b 的 pwm#

风扇的pwm线插在pin 27上,sudo armbian-config进入TUI界面 -> System -> Kernel -> DTO01 - Manage device tree overlays -> 选 rk3588-pwm7-m3。或者在/boot/armbianEnv.txtoverlays后面续上字符串rk3588-pwm7-m3

重启。

PWM 子系统的 pwmchipN 编号是按 probe 顺序生成的,每次启动可能不一样,必须看一下:

Terminal window
sudo cat /sys/kernel/debug/pwm
platform/febd0030.pwm, 1 PWM device
pwm-0 ((null) ): period: 0 ns duty: 0 ns polarity: normal
platform/fd8b0010.pwm, 1 PWM device
pwm-0 (pwm-fan ): requested period: 60000 ns duty: 0 ns polarity: normal

febd0030 这个地址就是 PWM7(RK3588 PWM7 寄存器基址)。然后看哪个 pwmchip 链接到它:

Terminal window
ls -l /sys/class/pwm/
lrwxrwxrwx - root 29 4月 18:51 pwmchip0 -> ../../devices/platform/fd8b0010.pwm/pwm/pwmchip0
lrwxrwxrwx - root 29 4月 18:51 pwmchip1 -> ../../devices/platform/febd0030.pwm/pwm/pwmchip1

记住这个pwmchip1

先写个脚本测试:

Terminal window
uv init pwm7-m3
cd pwm7-m3
uv add python-periphery

写一个python脚本 fan_pwm.py 测试:

"""Hardware PWM fan control on Rock 5B PIN_27 (GPIO4_C6 / PWM7_IR_M3).
需先启用 rk3588-pwm7-m3 overlay 并重启。
通过 /sys/kernel/debug/pwm 找到 febd0030.pwm 对应的 pwmchip 号。
"""
import signal
import sys
import time
from periphery import PWM
# === 改成你机器上实际看到的 pwmchip 号 ===
PWM_CHIP = 1 # pwmchipN 中的 N
PWM_CHANNEL = 0 # Rockchip 每个 chip 只有一个 channel,固定为 0
# =====================================
FREQ_HZ = 25000 # 4 线 PC 风扇标准 PWM 频率
DUTY = 0.30 # 30% 占空比
def main() -> None:
pwm = PWM(PWM_CHIP, PWM_CHANNEL)
try:
pwm.frequency = FREQ_HZ # 25 kHz
pwm.duty_cycle = DUTY # 0.0 ~ 1.0
pwm.polarity = "normal"
pwm.enable()
print(f"PWM7 on PIN_27: {DUTY * 100:.0f}% @ {FREQ_HZ} Hz "
f"(pwmchip{PWM_CHIP})")
print("Ctrl+C 退出")
def shutdown(_sig, _frame):
print("\n停止 PWM...")
pwm.disable()
pwm.close()
sys.exit(0)
signal.signal(signal.SIGINT, shutdown)
signal.signal(signal.SIGTERM, shutdown)
while True:
time.sleep(1)
except Exception:
pwm.close()
raise
if __name__ == "__main__":
main()

运行脚本测试 sudo $(which uv) run python fan_pwm.py,此时风扇应该降速了。

成功后封装为系统服务,服务运行时不能依赖家目录(特别是如果以 root 跑),放到 /opt

Terminal window
sudo mv ~/pwm7-m3 /opt/pwm7-m3
sudo chown -R root:root /opt/pwm7-m3
cd /opt/pwm7-m3

确认依赖装好(uv 会在 /opt/pwm7-m3/.venv 建虚拟环境):

Terminal window
sudo $(which uv) sync

记下 uv 的绝对路径,service 里要用:

Terminal window
which uv
/home/radxa/.local/bin/uv

由于 uv 装在家目录下(~/.local/bin/uv),root 跑服务时找不到,把 uv 放到系统路径:

Terminal window
sudo cp ~/.local/bin/uv /usr/local/bin/uv

微调一下脚本,让它对 SIGTERM(systemd stop 发的信号)也能干净退出,并且把输出冲到日志里(journald 会自动收):

"""Hardware PWM fan control on Rock 5B PIN_27 (PWM7_IR_M3)."""
import signal
import sys
import time
from periphery import PWM
PWM_CHIP = 1 # 改成你机器上 febd0030.pwm 对应的 pwmchipN
PWM_CHANNEL = 0
FREQ_HZ = 25000
DUTY = 0.30
def main() -> None:
pwm = PWM(PWM_CHIP, PWM_CHANNEL)
pwm.frequency = FREQ_HZ
pwm.duty_cycle = DUTY
pwm.polarity = "normal"
pwm.enable()
print(f"pwm7-m3: {DUTY * 100:.0f}% @ {FREQ_HZ} Hz on pwmchip{PWM_CHIP}",
flush=True)
stopping = False
def shutdown(_sig, _frame):
nonlocal stopping
stopping = True
signal.signal(signal.SIGINT, shutdown)
signal.signal(signal.SIGTERM, shutdown)
try:
while not stopping:
time.sleep(1)
finally:
print("pwm7-m3: stopping", flush=True)
try:
pwm.disable()
finally:
pwm.close()
if __name__ == "__main__":
sys.exit(main())

flush=True 是为了让 print 立刻被 systemd 抓到日志里,不然 Python 默认会做行缓冲/块缓冲,停服务前可能看不到输出。

创建 systemd service 文件:

Terminal window
sudo vim /etc/systemd/system/pwm7-m3.service
[Unit]
Description=Rock 5B PWM fan control (PIN_27 / PWM7)
After=multi-user.target
[Service]
Type=simple
WorkingDirectory=/opt/fan-pwm
ExecStart=/usr/local/bin/uv run python fan_pwm.py
Restart=on-failure
RestartSec=3
User=root
StandardOutput=journal
StandardError=journal
[Install]
WantedBy=multi-user.target

几个要点:

  • Type=simple:脚本本身就是常驻进程,不 fork,所以 simple 即可。
  • ExecStart 里 uv 必须用绝对路径——systemd 不读的 shell PATH
  • WorkingDirectory 让 uv 找得到项目里的 pyproject.toml.venv
  • Restart=on-failure:脚本异常退出就重启,但 systemctl stop 主动停的时候不会重启。
  • User=root:因为 PWM sysfs 默认只有 root 能写。

启用 + 启动,enable --now 等于 enable(开机自启)+ start(立刻启动)一起做。

Terminal window
sudo systemctl daemon-reload
sudo systemctl enable --now pwm7-m3.service

常用操作:

Terminal window
# 看状态
sudo systemctl status pwm7-m3
# 看实时日志
sudo journalctl -u pwm7-m3 -f
# 停止 / 重启
sudo systemctl stop pwm7-m3
sudo systemctl restart pwm7-m3
# 改完占空比想生效,重启服务即可
sudo systemctl restart pwm7-m3
# 不要开机自启了
sudo systemctl disable pwm7-m3

成功后也可以把脚本改成自动控温的模式:

"""Temperature-based PWM fan control for Rock 5B (PIN_27 / PWM7_IR_M3)."""
import signal
import sys
import time
from periphery import PWM
# === 硬件配置 ===
PWM_CHIP = 1 # febd0030.pwm 对应的 pwmchipN
PWM_CHANNEL = 0
FREQ_HZ = 25000
THERMAL_ZONE = "/sys/class/thermal/thermal_zone0/temp" # CPU 温度
POLL_INTERVAL = 3.0 # 秒,温度采样间隔
# === 风扇曲线 ===
# (温度 °C, 占空比 0.0~1.0)
# 大风扇没必要跑全速,最高也就 60%
FAN_CURVE = [
(45.0, 0.30), # 45°C 以下30%
(50.0, 0.35), # 起转点
(60.0, 0.40),
(70.0, 0.45),
(80.0, 0.60), # 80°C 以上也只到 60%
]
# 滞回,避免在阈值附近反复启停
HYSTERESIS_C = 2.0
# 起转辅助:从 0% 跳到目标值时,先全力踹一下让风扇转起来
KICKSTART_DUTY = 0.50
KICKSTART_SEC = 1.0
def read_temp_c() -> float:
with open(THERMAL_ZONE) as f:
return int(f.read().strip()) / 1000.0
def duty_for_temp(temp_c: float, curve: list[tuple[float, float]]) -> float:
"""在曲线上做线性插值。"""
if temp_c <= curve[0][0]:
return curve[0][1]
if temp_c >= curve[-1][0]:
return curve[-1][1]
for (t0, d0), (t1, d1) in zip(curve, curve[1:]):
if t0 <= temp_c <= t1:
ratio = (temp_c - t0) / (t1 - t0)
return d0 + (d1 - d0) * ratio
return curve[-1][1]
def main() -> int:
pwm = PWM(PWM_CHIP, PWM_CHANNEL)
pwm.frequency = FREQ_HZ
pwm.duty_cycle = 0.0
pwm.polarity = "normal"
pwm.enable()
print(f"fan-pwm: started, curve={FAN_CURVE}", flush=True)
stopping = False
def shutdown(_sig, _frame):
nonlocal stopping
stopping = True
signal.signal(signal.SIGINT, shutdown)
signal.signal(signal.SIGTERM, shutdown)
last_temp = read_temp_c()
current_duty = 0.0
try:
while not stopping:
temp = read_temp_c()
# 滞回:温度变化小于 HYSTERESIS_C 时不动
if abs(temp - last_temp) < HYSTERESIS_C:
target = current_duty
else:
target = duty_for_temp(temp, FAN_CURVE)
last_temp = temp
if target != current_duty:
# 从停转到要起转 → 先 kickstart
if current_duty == 0.0 and target > 0.0:
pwm.duty_cycle = max(target, KICKSTART_DUTY)
print(f"fan-pwm: {temp:.1f}°C kickstart "
f"-> {pwm.duty_cycle * 100:.0f}%", flush=True)
time.sleep(KICKSTART_SEC)
pwm.duty_cycle = target
current_duty = target
print(f"fan-pwm: {temp:.1f}°C duty -> {target * 100:.0f}%",
flush=True)
time.sleep(POLL_INTERVAL)
finally:
print("fan-pwm: stopping", flush=True)
try:
pwm.duty_cycle = 0.0
pwm.disable()
finally:
pwm.close()
return 0
if __name__ == "__main__":
sys.exit(main())
新年要用新系统
https://zlotus.github.io/2026/03/08/init-2026-win11/
作者
Z
发布于
2026-03-08
许可协议
CC BY-NC-SA 4.0