Ubuntu 日常运维最佳实践

> 经过生产验证的 Ubuntu 开发工作站管理规范:软件安装、安全、备份、监控及环境配置。


目录


软件安装规范

规则:两级安装策略

类型 安装位置 安装方式 示例
系统工具 系统路径(/usr/bin/usr/local/bin apt install git、vim、curl、gcc、make、systemd 服务
开发工具 /home/user/tools/ 手动安装、压缩包解压、源码编译 Vivado、ModelSim、Verible、Veridian、node、go

系统工具(apt)


# 核心开发软件包
sudo apt update && sudo apt install -y \
  build-essential \
  git \
  curl \
  wget \
  vim \
  htop \
  tmux \
  net-tools \
  openssh-server \
  python3 \
  python3-pip \
  python3-venv

# FPGA 专用系统工具
sudo apt install -y \
gtkwave \
iverilog \
verilator \
minicom \
cutecom \
putty \
tftpd-hpa

开发工具(手动安装)

规范: 所有手动安装的开发工具统一放在 /home/user/tools/ 目录下,采用带版本号的目录,并使用符号链接指向当前版本。

# 目录结构
/home/user/tools/
├── verilator -> verilator-5.026/
├── verilator-5.026/
├── verible -> verible-v0.0-3724/
├── verible-v0.0-3724/
├── vivado -> /opt/Xilinx/Vivado/2024.1/   # (指向系统安装位置的符号链接)
├── modelsim -> /opt/ModelSim/modeltech/    # (指向系统安装位置的符号链接)
├── node -> node-v20.15.0/
├── node-v20.15.0/
├── go -> go1.22.5/
├── go1.22.5/
├── veridian/
│   └── target/release/veridian
└── fpga_scripts/
    ├── pull-all.sh
    ├── push-all.sh
    └── tag-all.sh

标准安装模板:

#!/bin/bash
# install-tool.sh -- 标准工具安装模式

TOOL_NAME="$1"
TOOL_VERSION="$2"
ARCHIVE_URL="$3"

INSTALL_BASE="/home/user/tools"
VERSION_DIR="${INSTALL_BASE}/${TOOL_NAME}-${TOOL_VERSION}"
SYMLINK="${INSTALL_BASE}/${TOOL_NAME}"

# 下载并解压
cd /tmp
wget "$ARCHIVE_URL" -O "${TOOL_NAME}.tar.gz"
mkdir -p "$VERSION_DIR"
tar xzf "${TOOL_NAME}.tar.gz" -C "$VERSION_DIR" --strip-components=1

# 创建/更新符号链接
rm -f "$SYMLINK"
ln -s "$VERSION_DIR" "$SYMLINK"

# 通过 profile.d 添加到 PATH
cat > "/etc/profile.d/${TOOL_NAME}.sh" << EOF
export PATH="${SYMLINK}/bin:\$PATH"
EOF

chmod +x "/etc/profile.d/${TOOL_NAME}.sh"
echo "[正常] 已安装 ${TOOL_NAME} ${TOOL_VERSION} → ${SYMLINK}"

PATH 管理


# ~/.bashrc 追加内容
export PATH="/home/user/tools/verible/bin:$PATH"
export PATH="/home/user/tools/veridian/target/release:$PATH"
export PATH="/home/user/tools/node/bin:$PATH"
export PATH="/home/user/tools/go/bin:$PATH"
export PATH="/home/user/go/bin:$PATH"
export PATH="/home/user/.local/bin:$PATH"
export PATH="/home/user/.cargo/bin:$PATH"

# Vivado/ModelSim(如不在标准 PATH 中)
if [ -d "/opt/Xilinx/Vivado/2024.1" ]; then
source /opt/Xilinx/Vivado/2024.1/settings64.sh
fi
if [ -d "/opt/ModelSim/modeltech" ]; then
export PATH="/opt/ModelSim/modeltech/bin:$PATH"
fi

安装日志

记录安装了哪些工具及安装方式:


# /home/user/tools/INSTALL_LOG.md
# ================================

# 2026-07-15:Verible v0.0-3724
# wget https://github.com/chipsalliance/verible/releases/download/...
# 解压至 /home/user/tools/verible-v0.0-3724/

# 2026-07-16:Node.js v20.15.0
# wget https://nodejs.org/dist/v20.15.0/node-v20.15.0-linux-x64.tar.xz
# 解压至 /home/user/tools/node-v20.15.0/

# 2026-07-18:Verilator 5.026
# git clone https://github.com/verilator/verilator.git
# autoconf && ./configure --prefix=/home/user/tools/verilator-5.026
# make -j$(nproc) && make install


Sudo 密码管理与安全

Sudo 配置


# 安全地编辑 sudoers 配置文件(务必使用 visudo!)
sudo visudo

# 推荐:延长开发会话的 sudo 超时时间
# 添加到 /etc/sudoers.d/99-devtools:
Defaults timestamp_timeout=30 # 30 分钟,替代默认的 5 分钟
Defaults env_keep += "PATH HOME DISPLAY"
Defaults !tty_tickets # 每个 tty 使用同一张票据

# 对特定命令允许免密 sudo
# 用户名 ALL=(ALL) NOPASSWD: /usr/bin/apt, /usr/bin/systemctl

安全的脚本密码存储方案

绝不要在脚本中硬编码密码。 请使用下列方法之一: 方案 A:环境变量文件(推荐)

# 创建受保护的密码文件
sudo mkdir -p /home/user/.local/share/secrets
echo "export SUDO_PASSWORD='your-password'" > /home/user/.local/share/secrets/sudo.env
chmod 600 /home/user/.local/share/secrets/sudo.env

# 在脚本中使用
source /home/user/.local/share/secrets/sudo.env
echo "$SUDO_PASSWORD" | sudo -S apt update

方案 B:GNOME Keyring

# 将密码存储在密钥环中
secret-tool store --label="Sudo 密码" application sudo-password
echo "$(secret-tool lookup application sudo-password)" | sudo -S apt update

方案 C:sudoers NOPASSWD(最便捷)

# 允许特定用户无需密码运行所有命令
sudo bash -c 'echo "用户名 ALL=(ALL) NOPASSWD: ALL" > /etc/sudoers.d/99-用户名-nopasswd'
sudo chmod 440 /etc/sudoers.d/99-用户名-nopasswd

密码轮换


# 修改自己的密码
passwd

# 强制密码在 90 天后过期
sudo chage -M 90 用户名

# 检查密码过期状态
sudo chage -l 用户名

SSH 密钥认证


# 生成 ED25519 密钥(优先于 RSA)
ssh-keygen -t ed25519 -C "[email protected]" -f /home/user/.ssh/id_ed25519

# 启动 ssh-agent 并添加密钥
eval "$(ssh-agent -s)"
ssh-add /home/user/.ssh/id_ed25519

# 将公钥复制到远程服务器
ssh-copy-id -i /home/user/.ssh/id_ed25519.pub user@remote-host

# 配置 ~/.ssh/config
cat >> /home/user/.ssh/config << 'EOF'
Host github.com
HostName github.com
User git
IdentityFile /home/user/.ssh/id_ed25519

Host dev-server
HostName 192.168.1.100
User developer
IdentityFile /home/user/.ssh/id_ed25519
ForwardAgent yes
EOF

chmod 600 /home/user/.ssh/config


系统备份策略

策略 1:Timeshift(系统快照)

Timeshift 非常适合系统级快照(OS、软件包、配置)-- 类似于 Windows 系统还原。


# 安装
sudo apt install -y timeshift

# 手动创建快照
sudo timeshift --create --comments "升级前快照 $(date +%Y%m%d)"

# 列出快照
sudo timeshift --list

# 恢复快照
sudo timeshift --restore --snapshot '2026-07-24_14-00-00'

# 计划任务:通过 GUI 界面配置
# timeshift-gtk
# 推荐:每天一份快照,保留 5 份日快照 + 3 份周快照 + 2 份月快照

# 命令行配置
sudo timeshift --schedule

推荐排除项(通过 Timeshift GUI 或 /etc/timeshift/timeshift.json 配置):

{
  "exclude": [
    "/home/user/**",
    "/home/user/work/**",
    "/home/user/tools/**",
    "/root/**",
    "/tmp/**",
    "/var/cache/**",
    "/var/tmp/**"
  ]
}

注意: Timeshift 备份的是系统文件,而非用户数据。用户数据请使用 rsync 备份。

策略 2:rsync(用户数据备份)


#!/bin/bash
# backup-userdata.sh -- 通过 rsync 进行增量用户数据备份

BACKUP_DEST="/mnt/backup/userdata" # 外部磁盘或 NAS 路径
SOURCE_DIRS=(
"/home/user/work"
"/home/user/tools"
"/home/user/.config"
"/home/user/.local"
"/home/user/.ssh"
"/home/user/.gitconfig"
"/home/user/.bashrc"
"/home/user/.profile"
)

TIMESTAMP=$(date +%Y%m%d_%H%M%S)
LOG_FILE="/home/user/.local/log/backup-${TIMESTAMP}.log"

mkdir -p "$(dirname "$LOG_FILE")"

echo "=== 备份开始:$(date) ===" | tee -a "$LOG_FILE"

for dir in "${SOURCE_DIRS[@]}"; do
if [ -e "$dir" ]; then
echo "[$(date +%H:%M:%S)] 正在备份:$dir" | tee -a "$LOG_FILE"
rsync -avh --delete \
--exclude='.git/objects/' \
--exclude='node_modules/' \
--exclude='*.dcp' \
--exclude='*.bit' \
--exclude='vivado.*' \
--exclude='*.cache/' \
--exclude='*.runs/' \
"$dir/" "$BACKUP_DEST/${dir#/home/user/}/" \
2>&1 | tee -a "$LOG_FILE"
fi
done

echo "=== 备份完成:$(date) ===" | tee -a "$LOG_FILE"

# 计算备份大小
BACKUP_SIZE=$(du -sh "$BACKUP_DEST" | cut -f1)
echo "备份总大小:$BACKUP_SIZE" | tee -a "$LOG_FILE"

策略 3:tar + 压缩(存档快照)


#!/bin/bash
# archive-snapshot.sh -- 创建重要目录的压缩归档

ARCHIVE_DIR="/mnt/backup/archives"
SNAPSHOT_NAME="work-snapshot-$(date +%Y%m%d)"
ARCHIVE_PATH="${ARCHIVE_DIR}/${SNAPSHOT_NAME}.tar.zst"

mkdir -p "$ARCHIVE_DIR"

echo "正在创建归档:$ARCHIVE_PATH"

# 使用 zstd 实现快速压缩,同时保持较优的压缩比
tar -I 'zstd -T0 -3' \
--exclude='.git' \
--exclude='node_modules' \
--exclude='*.dcp' \
--exclude='*.bit' \
--exclude='vivado*' \
-cf "$ARCHIVE_PATH" \
-C /home/user work tools .config .local .ssh .gitconfig .bashrc

ARCHIVE_SIZE=$(du -h "$ARCHIVE_PATH" | cut -f1)
echo "归档已创建:$ARCHIVE_PATH($ARCHIVE_SIZE)"

# 仅保留最近 10 份归档
ls -1t "${ARCHIVE_DIR}"/work-snapshot-*.tar.zst | tail -n +11 | xargs -r rm -f

策略 4:基于 Git 的配置备份


#!/bin/bash
# backup-dotfiles.sh -- 用 Git 追踪 ~/.config 和 dotfiles

DOTFILES_DIR="/home/user/.dotfiles"

# 初始化裸 git 仓库(不在 $HOME 中创建工作树)
git init --bare "$DOTFILES_DIR"

# 创建管理 dotfiles 的别名
alias dotfiles='/usr/bin/git --git-dir=/home/user/.dotfiles --work-tree=/home/user'

# 添加要追踪的文件
dotfiles add ~/.bashrc ~/.profile ~/.gitconfig ~/.config/Code/User/settings.json
dotfiles commit -m "dotfiles:初始提交 $(date +%Y%m%d)"

# 推送到远程仓库(私有仓库)
dotfiles remote add origin [email protected]:BuckHuang/dotfiles.git
dotfiles push -u origin main

# 在新机器上恢复
# git clone [email protected]:BuckHuang/dotfiles.git /home/user/.dotfiles
# dotfiles checkout

自动化备份定时任务


# 每天凌晨 3:00:rsync 增量备份用户数据
# 每周日凌晨 4:00:Timeshift 系统快照
# 每周日凌晨 5:00:完整归档

crontab -e


# 每日增量备份
0 3 * * * /home/user/.local/bin/backup-userdata.sh >> /home/user/.local/log/backup-cron.log 2>&1

# 每周系统快照(周日)
0 4 * * 0 sudo timeshift --create --comments "每周快照 $(date +\%Y\%m\%d)"

# 每周完整归档(周日,快照之后)
0 5 * * 0 /home/user/.local/bin/archive-snapshot.sh

# 每月清理旧日志
0 6 1 * * find /home/user/.local/log/backup-*.log -mtime +90 -delete


磁盘空间监控与清理

日常监控


# 快速总览
df -h

# 各目录详细使用情况
du -sh /home/user/* | sort -rh | head -20

# 查找最大的文件
find /home/user -type f -size +100M -exec ls -lh {} \; 2>/dev/null | sort -k5 -rh | head -20

# 检查 inode 使用情况(大量小文件场景)
df -i

# 使用简单脚本进行监控
cat > /home/user/.local/bin/disk-check.sh << 'SCRIPT'
#!/bin/bash
THRESHOLD=90
df -h --output=pcent,target | tail -n +2 | while read -r pct mount; do
pct=${pct%\%}
if [ "$pct" -gt "$THRESHOLD" ]; then
echo "[警告] $mount 占用 ${pct}%"
fi
done
SCRIPT
chmod +x /home/user/.local/bin/disk-check.sh

APT 缓存清理


# 检查 apt 缓存大小
du -sh /var/cache/apt/archives/

# 删除已下载的软件包文件(安全操作)
sudo apt clean

# 删除不再需要的软件包
sudo apt autoremove --purge

# 删除过时的软件包缓存
sudo apt autoclean

# 一键清理命令
sudo apt clean && sudo apt autoremove --purge && sudo apt autoclean

Journald 日志清理


# 检查 journal 日志大小
journalctl --disk-usage

# 限制 journal 日志大小(编辑 /etc/systemd/journald.conf)
sudo sed -i 's/#SystemMaxUse=/SystemMaxUse=500M/' /etc/systemd/journald.conf
sudo sed -i 's/#SystemMaxFileSize=/SystemMaxFileSize=100M/' /etc/systemd/journald.conf
sudo systemctl restart systemd-journald

# 清理旧日志(保留最近 7 天)
sudo journalctl --vacuum-time=7d

# 按大小清理(最多保留 500MB)
sudo journalctl --vacuum-size=500M

# 再次检查
journalctl --disk-usage

通用清理


#!/bin/bash
# cleanup-system.sh -- 通用系统清理脚本

echo "=== 系统清理 ==="

# 1. APT 缓存
echo "[1/7] 清理 APT 缓存..."
sudo apt clean
sudo apt autoremove --purge -y

# 2. Journald 日志
echo "[2/7] 清理 journald 日志..."
sudo journalctl --vacuum-size=500M

# 3. 旧内核(保留最新的 2 个)
echo "[3/7] 移除旧内核..."
sudo apt autoremove --purge -y

# 4. 回收站
echo "[4/7] 清空回收站..."
rm -rf /home/user/.local/share/Trash/*

# 5. /tmp 中 7 天前的文件
echo "[5/7] 清理 /tmp..."
find /tmp -type f -atime +7 -delete 2>/dev/null

# 6. Docker(如使用)
echo "[6/7] 清理 Docker..."
docker system prune -af 2>/dev/null || true

# 7. Snap 软件包
echo "[7/7] 清理 snap 缓存..."
sudo snap list --all | grep "disabled" | awk '{print $1, $3}' | while read snapname revision; do
sudo snap remove "$snapname" --revision="$revision"
done

# 显示剩余空间
echo ""
echo "=== 清理后磁盘空间 ==="
df -h /

Vivado/FPGA 专用清理


#!/bin/bash
# cleanup-fpga-artifacts.sh -- 删除 Vivado 构建产物

WORK_DIR="/home/user/work/FPGA_Prj/Project"

# 删除 Vivado 项目文件(可重新生成)
find "$WORK_DIR" -type d -name "*.runs" -exec rm -rf {} + 2>/dev/null
find "$WORK_DIR" -type d -name "*.sim" -exec rm -rf {} + 2>/dev/null
find "$WORK_DIR" -type d -name "*.cache" -exec rm -rf {} + 2>/dev/null
find "$WORK_DIR" -type d -name ".Xil" -exec rm -rf {} + 2>/dev/null
find "$WORK_DIR" -type d -name "xsim.dir" -exec rm -rf {} + 2>/dev/null

# 删除日志/jou 文件
find "$WORK_DIR" -type f \( -name "vivado.jou" -o -name "vivado.log" \) -delete
find "$WORK_DIR" -type f \( -name "*.backup.jou" -o -name "*.backup.log" \) -delete
find "$WORK_DIR" -type f \( -name "*.str" -o -name "*.pb" \) -delete

# 删除生成的比特流文件(仅保留发布目录中的文件)
find "$WORK_DIR" -type f \( -name "*.bit" -o -name "*.bin" -o -name "*.mcs" \) \
! -path "*/releases/*" -delete

echo "FPGA 构建产物清理完成。"
du -sh "$WORK_DIR"

磁盘使用告警脚本


#!/bin/bash
# disk-alert.sh -- 当磁盘使用超过阈值时发送通知

THRESHOLD=85
MOUNT_POINTS=("/" "/home")

for mp in "${MOUNT_POINTS[@]}"; do
usage=$(df "$mp" --output=pcent | tail -1 | tr -d ' %')
if [ "$usage" -gt "$THRESHOLD" ]; then
notify-send -u critical "磁盘告警" "$mp 使用率已达 ${usage}%!"
echo "[$(date)] 告警:$mp 使用率 ${usage}%(阈值:${THRESHOLD}%)"
fi
done


内核升级与回滚

升级内核


# 检查当前内核版本
uname -r

# 列出可用内核
apt search linux-image | grep -E "^linux-image-[0-9]"

# 安装最新的稳定内核
sudo apt update
sudo apt install -y linux-image-generic linux-headers-generic

# HWE(硬件启用)内核(更好的硬件支持)
sudo apt install -y linux-image-generic-hwe-22.04 linux-headers-generic-hwe-22.04

# 安装特定版本的内核
sudo apt install -y linux-image-6.8.0-40-generic linux-headers-6.8.0-40-generic

# 为所有内核重新构建 initramfs(如需要)
sudo update-initramfs -u -k all

# 更新 GRUB
sudo update-grub

# 重启以加载新内核
sudo reboot

保留旧内核


# 列出已安装的内核
dpkg --list | grep linux-image

# 通常保留 2-3 个内核:
# - 当前正在运行的内核
# - 上一个已知正常的内核(用于回滚)
# - 再保留一个更早的作为额外保险

# 升级后不要盲目 autoremove 删除内核
# sudo apt-mark hold linux-image-<已知正常的版本>

回滚到上一个内核

方法 1:GRUB 启动菜单

# 1. 在启动过程中,按住 Shift(BIOS)或 Esc(UEFI)进入 GRUB 菜单
# 2. 选择 "Advanced options for Ubuntu"(Ubuntu 高级选项)
# 3. 选择旧版本的内核启动
# 4. 启动后,将旧内核设为默认值:

方法 2:永久设置默认内核

# 查找目标内核的 GRUB 菜单条目
awk -F\' '/menuentry |submenu / {print $1 $2}' /boot/grub/grub.cfg | nl

# 设置默认项(从 0 开始的索引,或完整的菜单条目名)
# 选项 A:按索引设置
sudo sed -i 's/GRUB_DEFAULT=0/GRUB_DEFAULT="1>3"/' /etc/default/grub
# 其中 "1>3" 表示子菜单 1,条目 3

# 选项 B:按精确的菜单条目字符串设置
sudo sed -i 's/GRUB_DEFAULT=0/GRUB_DEFAULT="Advanced options for Ubuntu>Ubuntu, with Linux 6.5.0-28-generic"/' /etc/default/grub

# 应用更改
sudo update-grub

方法 3:锁定旧内核防止被删除

# 锁定已知正常的内核,使 autoremove 不会删除它
sudo apt-mark hold linux-image-6.5.0-28-generic linux-headers-6.5.0-28-generic

# 准备删除时取消锁定
sudo apt-mark unhold linux-image-6.5.0-28-generic

删除旧内核


# 删除指定内核
sudo apt remove --purge linux-image-6.5.0-25-generic linux-headers-6.5.0-25-generic

# 删除所有不再使用的内核(至少保留 2 个!)
sudo apt autoremove --purge

# 验证剩余内核
dpkg --list | grep linux-image | grep "^ii"

内核问题排查

症状 可能原因 解决方法
启动时卡在 "Loading initial ramdisk" initramfs 问题 启动旧内核,运行 update-initramfs -u
升级后 NVIDIA 驱动失效 DKMS 未重新构建 启动旧内核,重新安装 nvidia-driver
升级后网络不可用 缺少驱动模块 检查 lspci -k,安装 linux-modules-extra
Wi-Fi 驱动缺失 新内核中未包含该模块 安装 linux-modules-extra-$(uname -r)

UFW 防火墙基础

安装与激活


# 安装(Ubuntu 通常已预装)
sudo apt install -y ufw

# 检查状态
sudo ufw status verbose

# 启用防火墙
sudo ufw enable

# 禁用防火墙(用于防止被锁定在外的情况)
sudo ufw disable

默认配置


# 安全默认值:拒绝入站,允许出站
sudo ufw default deny incoming
sudo ufw default allow outgoing

# 必要的服务
sudo ufw allow ssh # 端口 22
sudo ufw allow 7844/tcp # Cloudflared tunnel
sudo ufw allow 7890/tcp # Clash 代理(局域网)
sudo ufw allow from 192.168.1.0/24 # 允许整个局域网子网

# ToDesk 远程桌面
sudo ufw allow 21115:21119/tcp
sudo ufw allow 21116/udp

# 查看规则
sudo ufw status numbered

应用配置文件


# 列出可用的应用配置文件
sudo ufw app list

# 按应用名称放行
sudo ufw allow 'OpenSSH'

# 创建自定义应用配置
sudo cat > /etc/ufw/applications.d/fpga-dev << 'EOF'
[FPGA 开发]
title=FPGA 开发工具
description=FPGA 开发所需的端口
ports=6000:6010/tcp|6000:6010/udp|3121/tcp
EOF

sudo ufw app update 'FPGA 开发'
sudo ufw allow 'FPGA 开发'

常用规则模式


# 仅允许特定 IP 访问
sudo ufw allow from 192.168.1.50 to any port 22

# 允许端口范围
sudo ufw allow 8000:8100/tcp

# 速率限制(暴力破解防护)
sudo ufw limit ssh

# 删除规则
sudo ufw status numbered # 获取规则编号
sudo ufw delete 3 # 删除编号为 3 的规则

# 按规则描述删除
sudo ufw delete allow 8080/tcp

# 日志记录
sudo ufw logging on # 记录被阻止的连接
sudo ufw logging medium # 更详细的日志
# 日志位置:/var/log/ufw.log

快速封锁脚本


#!/bin/bash
# firewall-lockdown.sh -- 紧急限制性防火墙设置

echo "=== 紧急防火墙封锁 ==="

# 保留 SSH 和 Cloudflared
sudo ufw --force reset
sudo ufw default deny incoming
sudo ufw default allow outgoing
sudo ufw allow ssh
sudo ufw allow 7844/tcp

# 阻止其他所有连接
sudo ufw enable

echo "封锁已激活。要恢复正常:sudo ufw disable"

监控防火墙活动


# 实时日志监控
sudo tail -f /var/log/ufw.log

# 统计被阻止的连接次数
sudo grep "UFW BLOCK" /var/log/ufw.log | awk '{print $12}' | sort | uniq -c | sort -rn | head -20

# 检查最近一小时内被阻止的连接
sudo journalctl -u ufw --since "1 hour ago"


Cron 定时任务管理

Crontab 基础


# 编辑用户 crontab
crontab -e

# 列出当前 crontab
crontab -l

# 删除 crontab
crontab -r

# Cron 语法结构:
# ┌────────── 分钟(0-59)
# │ ┌────────── 小时(0-23)
# │ │ ┌────────── 日期(1-31)
# │ │ │ ┌────────── 月份(1-12)
# │ │ │ │ ┌────────── 星期(0-7,0/7=周日)
# │ │ │ │ │
# * * * * * 命令

FPGA 开发环境 crontab 示例


# 开发工作站 crontab 配置
# ================================

# 每日:Git 拉取所有仓库(早上 8:00)
0 8 * * 1-5 /home/user/.local/bin/pull-all.sh >> /home/user/.local/log/cron-pull.log 2>&1

# 每日:磁盘空间检查(上午 10:00)
0 10 * * 1-5 /home/user/.local/bin/disk-check.sh

# 每日:dconf 备份(凌晨 2:00)
0 2 * * * /usr/bin/dconf dump / > /home/user/.config/dconf/auto-backup-$(date +\%Y\%m\%d).conf

# 每周:系统清理(周日凌晨 3:00)
0 3 * * 0 /home/user/.local/bin/cleanup-system.sh >> /home/user/.local/log/cron-cleanup.log 2>&1

# 每周:用户数据备份(周日凌晨 4:00)
0 4 * * 0 /home/user/.local/bin/backup-userdata.sh

# 每日:早上 8 点收集 Git 数据供 Conky 显示脚本使用
0 8 * * * /home/user/.local/bin/conky-daily-data.sh

# 每小时:代理连接检查(工作时间)
0 8-20 * * 1-5 /home/user/.local/bin/check-proxy.sh

# 每月:日志轮转
0 2 1 * * find /home/user/.local/log -name "*.log" -mtime +30 -delete

# @reboot:启动后台服务
@reboot /home/user/.local/bin/start-services.sh

Cron 最佳实践

1. 始终重定向输出:

# 不好:输出流向系统邮件(或丢失)
0 3 * * * /home/user/.local/bin/backup.sh

# 好:输出重定向到日志文件
0 3 * * * /home/user/.local/bin/backup.sh >> /home/user/.local/log/backup.log 2>&1

2. 使用完整路径:

# 不好:PATH 可能与交互式 Shell 不同
0 8 * * * status-all.sh

# 好:完整的二进制文件和脚本路径
0 8 * * * /home/user/.local/bin/status-all.sh

3. 转义百分号:

# % 在 crontab 中有特殊含义(表示换行)
# 需要用 \% 进行转义
0 2 * * * /usr/bin/dconf dump / > /home/user/.config/dconf/backup-$(date +\%Y\%m\%d).conf

4. 长时间运行的任务使用锁文件:

#!/bin/bash
# safe-cron-job.sh -- 带锁文件的 cron 任务

LOCKFILE="/tmp/$(basename "$0").lock"

# 检查任务是否已在运行
if [ -f "$LOCKFILE" ]; then
echo "[$(date)] 任务已在运行,退出。"
exit 0
fi

trap "rm -f $LOCKFILE" EXIT
touch "$LOCKFILE"

# === 在此处编写实际任务 ===
echo "[$(date)] 任务开始..."

# 执行耗时操作
sleep 60

echo "[$(date)] 任务完成。"

系统 Cron 目录


# 系统级 cron 目录(放入这些目录的脚本会在指定时间间隔执行)
ls -la /etc/cron.hourly/
ls -la /etc/cron.daily/
ls -la /etc/cron.weekly/
ls -la /etc/cron.monthly/

# 自定义系统 cron 任务
ls -la /etc/cron.d/

# 示例:系统备份脚本
sudo cat > /etc/cron.daily/fpga-cleanup << 'EOF'
#!/bin/bash
find /home/user/work -type d -name "*.runs" -mtime +7 -exec rm -rf {} \; 2>/dev/null
find /home/user/work -type d -name "*.sim" -mtime +7 -exec rm -rf {} \; 2>/dev/null
EOF
sudo chmod +x /etc/cron.daily/fpga-cleanup

调试 Cron 任务


# 检查 cron 服务是否在运行
sudo systemctl status cron

# 查看 cron 日志
sudo journalctl -u cron -f

# 手动测试 cron 任务
# 1. 复制 cron 命令
# 2. 在相同环境下通过 shell 运行
# 3. 检查重定向日志

# 检查 cron 环境变量 vs Shell 环境变量
# 临时在 crontab 中添加:
# * * * * * env > /tmp/cron-env.txt
# 对比:env > /tmp/shell-env.txt
# diff /tmp/cron-env.txt /tmp/shell-env.txt

# 常见问题:cron 使用 sh,而非 bash
# 解决方案:在命令中显式使用 bash
# * * * * * /bin/bash /home/user/.local/bin/myscript.sh


环境变量分层管理

理解加载顺序


登录 Shell                       非登录交互式 Shell
============                     =====================

1. /etc/profile 1. /etc/bash.bashrc
2. ~/.bash_profile 2. ~/.bashrc
(或 ~/.profile
或 ~/.bash_login)
3. ~/.bashrc(如被 source 引入)

对于 GUI/桌面会话,还会加载以下文件:

/etc/environment          (系统级,非常早期,不是脚本文件)
~/.pam_environment        (用户级,由 PAM 读取)
/etc/X11/Xsession         (X 会话启动)
~/.xsessionrc

实用的分层策略

第 1 层:系统级/etc/environment

# /etc/environment -- 仅支持 Key=Value 格式(不是 Shell 脚本)
# 系统级,由 PAM 为所有用户和所有会话加载

PATH="/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin"
LANG="en_US.UTF-8"
LC_ALL="en_US.UTF-8"

第 2 层:Profile~/.profile

# ~/.profile -- 仅用于登录 Shell
# 放置应该只在登录时执行一次的内容

# 将 ~/.local/bin 添加到 PATH(只执行一次)
if [ -d "$HOME/.local/bin" ]; then
PATH="$HOME/.local/bin:$PATH"
fi

# 启动 ssh-agent(只需要一个实例)
if [ -z "$SSH_AUTH_SOCK" ]; then
eval "$(ssh-agent -s)" > /dev/null
ssh-add ~/.ssh/id_ed25519 2>/dev/null
fi

# 设置默认编辑器
export EDITOR=vim
export VISUAL=code

# 设置区域
export LANG=en_US.UTF-8
export LC_ALL=en_US.UTF-8

第 3 层:Bashrc~/.bashrc

# ~/.bashrc -- 每个交互式 Shell(包括嵌套 Shell)
# 放置每个终端窗口都需要的内容

# === PATH 扩展 ===
export PATH="/home/user/tools/verible/bin:$PATH"
export PATH="/home/user/tools/node/bin:$PATH"
export PATH="/home/user/tools/go/bin:$PATH"
export PATH="/home/user/go/bin:$PATH"
export PATH="/home/user/.local/bin:$PATH"
export PATH="/home/user/.cargo/bin:$PATH"

# === FPGA 工具别名 ===
alias vivado='/opt/Xilinx/Vivado/2024.1/bin/vivado'
alias vsim='/opt/ModelSim/modeltech/bin/vsim'
alias vcom='/opt/ModelSim/modeltech/bin/vcom'
alias gtkwave='gtkwave -S /home/user/.config/gtkwave/session.gtkw'

# === Git 工作效率别名 ===
alias gs='git status'
alias ga='git add'
alias gc='git commit'
alias gp='git push'
alias gl='git pull'
alias gd='git diff'
alias glog='git log --oneline --graph --all --decorate -20'
alias gdiff='git-diff'
alias gstat='status-all.sh'

# === 目录导航别名 ===
alias ll='ls -alF'
alias la='ls -A'
alias l='ls -CF'
alias ..='cd ..'
alias ...='cd ../..'
alias work='cd /home/user/work'
alias fpga='cd /home/user/work/FPGA_Prj/Project'
alias ila='cd /home/user/work/fpga_ila'

# === 网络 & 代理 ===
alias proxy-on='proxychains4 -q'
alias proxy-status='curl -x http://127.0.0.1:7890 -s -o /dev/null -w "%{http_code}" https://www.google.com && echo ""'
# (带目标 URL 的 Tunnel 状态缓存检查)
alias tunnel-status='cloudflared tunnel list 2>/dev/null'

# === Docker 别名 ===
alias dps='docker ps'
alias dpsa='docker ps -a'
alias dcu='docker compose up -d'
alias dcd='docker compose down'
alias dcl='docker compose logs -f'

# === Shell 提示符 ===
parse_git_branch() {
git branch 2>/dev/null | sed -e '/^[^*]/d' -e 's/* \(.*\)/(\1)/'
}
if [ "$color_prompt" = yes ]; then
PS1='${debian_chroot:+($debian_chroot)}\[\033[01;32m\]\u@\h\[\033[00m\]:\[\033[01;34m\]\w\[\033[00m\]\[\033[33m\]$(parse_git_branch)\[\033[00m\]\$ '
else
PS1='${debian_chroot:+($debian_chroot)}\u@\h:\w$(parse_git_branch)\$ '
fi

# === 历史记录 ===
export HISTSIZE=10000
export HISTFILESIZE=20000
export HISTCONTROL=ignoreboth:erasedups
shopt -s histappend

# === 自动安装时避免 apt-get 提示交互 ===
export DEBIAN_FRONTEND=noninteractive

第 4 层:各项目独立配置.env 文件或 direnv)

# 安装 direnv
sudo apt install -y direnv

# 添加到 ~/.bashrc
eval "$(direnv hook bash)"

# 创建项目专属的 .envrc
# /home/user/work/FPGA_Prj/Project/Top/.envrc
cat > /home/user/work/FPGA_Prj/Project/Top/.envrc << 'EOF'
# Vivado 环境
source /opt/Xilinx/Vivado/2024.1/settings64.sh

# 项目专用路径
export FPGA_TOP_DIR="$(pwd)"
export RTL_DIR="${FPGA_TOP_DIR}/rtl"
export SIM_DIR="${FPGA_TOP_DIR}/sim"
export CONSTRAINTS_DIR="${FPGA_TOP_DIR}/constraints"

# 允许 direnv 加载
direnv allow
EOF

第 5 层:应用程序配置

# Git 全局配置(~/.gitconfig)
# HTop 配置(~/.config/htop/htoprc)
# VSCode 设置(~/.config/Code/User/settings.json)
# GNOME/dconf 设置(通过 dconf 管理)
# SSH 配置(~/.ssh/config)


# 在 ~/.bashrc 中,用 source 引入模块化配置文件
# 这样可以保持 .bashrc 简洁且易于管理

if [ -f ~/.bash_aliases ]; then
. ~/.bash_aliases
fi

if [ -f ~/.bash_paths ]; then
. ~/.bash_paths
fi

if [ -f ~/.bash_fpga ]; then
. ~/.bash_fpga
fi

# ~/.bash_paths
export PATH="/home/user/tools/verible/bin:$PATH"
export PATH="/home/user/tools/node/bin:$PATH"
export PATH="/home/user/.local/bin:$PATH"

# ~/.bash_fpga
source /opt/Xilinx/Vivado/2024.1/settings64.sh
export PATH="/opt/ModelSim/modeltech/bin:$PATH"
alias pull-all='/home/user/.local/bin/pull-all.sh'
alias status-all='/home/user/.local/bin/status-all.sh'
alias tag-all='/home/user/.local/bin/tag-all.sh'

调试环境变量问题


# 显示完整的环境加载顺序
# 登录 Shell:
bash -l -c 'echo $PATH'

# 非登录 Shell:
bash -c 'echo $PATH'

# 检查变量是否已设置及其来源
echo "EDITOR=$EDITOR"
echo "PATH=$PATH" | tr ':' '\n' | nl

# 检查 PATH 中的重复条目
echo "$PATH" | tr ':' '\n' | sort | uniq -d

# 追踪哪个文件设置了某个变量
grep -r "export VARIABLE" /etc/profile /etc/bash.bashrc ~/.bashrc ~/.profile ~/.bash_profile 2>/dev/null

# 对比两个 Shell 环境之间的差异
diff <(bash -l -c 'env' | sort) <(bash -c 'env' | sort)


快速参考卡片


# 软件安装
sudo apt install -y <软件包>                          # 系统工具
# 开发工具 → /home/user/tools/<工具>-<版本>/

# Sudo
sudo visudo # 安全地编辑 sudoers 文件
sudo -k # 使缓存的认证凭据失效

# 备份
sudo timeshift --create # 系统快照
rsync -avh /home/user/work/ /mnt/backup/work/ # 用户数据备份

# 磁盘空间
df -h # 总览
ncdu /home/user # 交互式磁盘使用分析
sudo apt clean && sudo apt autoremove --purge # 清理
sudo journalctl --vacuum-size=500M # 清理 journald 日志
find /home/user -type f -size +500M -exec ls -lh {} \; # 查找大文件

# 内核
uname -r # 当前内核版本
dpkg --list | grep linux-image # 已安装的内核
sudo apt-mark hold linux-image-<版本> # 锁定内核版本

# 防火墙
sudo ufw status # 检查状态
sudo ufw allow <端口> # 开放端口
sudo ufw status numbered && sudo ufw delete N # 删除规则

# Cron
crontab -e # 编辑
crontab -l # 列出
sudo journalctl -u cron -f # 调试

# 环境变量
env # 显示所有环境变量
echo $PATH | tr ':' '\n' # PATH 逐行分解
grep "export" ~/.bashrc ~/.profile # 查找配置来源