> 经过生产验证的 Ubuntu 开发工作站管理规范:软件安装、安全、备份、监控及环境配置。
| 类型 | 安装位置 | 安装方式 | 示例 |
|---|---|---|---|
| 系统工具 | 系统路径(/usr/bin、/usr/local/bin) |
apt install |
git、vim、curl、gcc、make、systemd 服务 |
| 开发工具 | /home/user/tools/ |
手动安装、压缩包解压、源码编译 | Vivado、ModelSim、Verible、Veridian、node、go |
# 核心开发软件包
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}"
# ~/.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
# 安全地编辑 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
# 创建受保护的密码文件
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 用户名
# 生成 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
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 备份。
#!/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"
#!/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
#!/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 缓存大小
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
# 检查 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 /
#!/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. 在启动过程中,按住 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) |
# 安装(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"
# 编辑用户 crontab
crontab -e
# 列出当前 crontab
crontab -l
# 删除 crontab
crontab -r
# Cron 语法结构:
# ┌────────── 分钟(0-59)
# │ ┌────────── 小时(0-23)
# │ │ ┌────────── 日期(1-31)
# │ │ │ ┌────────── 月份(1-12)
# │ │ │ │ ┌────────── 星期(0-7,0/7=周日)
# │ │ │ │ │
# * * * * * 命令
# 开发工作站 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
# 不好:输出流向系统邮件(或丢失)
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 目录(放入这些目录的脚本会在指定时间间隔执行)
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 服务是否在运行
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
/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 # 查找配置来源