安装完一个全新的 Debian 安装之后,总有一些 packages 是我每次都要装的。与其手动逐个安装,我会把它们放进一个简单的 Bash 脚本,用一条 command 全部装好。

对我来说,这是完成基本软件包安装的一种轻量做法,效率也够用。仅仅为了安装一份 packages 清单,没有必要动用自动化平台。

所以,我在安装完 Debian 之后大致会做两件事:

  • 运行一个简单的安装后脚本(post-install);
  • 安装所有需要的 packages。

脚本相当精简,里面包含两类 packages:

  • 几乎在任何桌面上都用得上的基础工具;
  • 我特别想要的个人桌面 packages。

如果这套设置之后从安装软件包扩展到系统配置,我就会转向 Ansible。

请注意,本指南专门针对的是以软件包为中心的安装后处理,并不是一份完整、通用的 Debian 或 Ubuntu 安装后指南。如果需要完整的安装后步骤,可以参考以下链接:

1. 为 Debian / Ubuntu 创建一个简单的安装后 Bash 脚本

把所有脚本放在同一个目录里比较方便。先创建一个 scripts 目录:

mkdir -p ~/scripts

创建脚本:

nano ~/scripts/install-deb-essential-packages.sh

加入:

#!/usr/bin/env bash
set -euo pipefail
PACKAGES=(
# Basic tools
git
curl
wget
ca-certificates
unzip
build-essential
# Personal desktop packages
firmware-cirrus
papirus-icon-theme
timeshift
gnome-shell-extension-manager
vim
gnome-shell-extension-dashtodock
gnome-themes-extra
flatpak
gnome-software-plugin-flatpak
steam-installer
)
echo "Updating package lists..."
sudo apt update
echo "Installing packages..."
sudo apt install -y "${PACKAGES[@]}"
echo "Done."

脚本中的安装命令通过 sudo 执行,也就是需要管理员权限,并且会实际改动系统上的软件包状态。这一点在运行之前需要确认。

请注意,有些 packages 只针对特定平台提供。因此可能需要编辑这个脚本,根据自己的 Linux 发行版增删 packages。把要安装的 packages 都加进去之后,保存并关闭脚本。

赋予可执行权限:

chmod +x ~/scripts/install-deb-essential-packages.sh

运行脚本,在 Debian/Ubuntu 系统上一次性安装全部 packages:

~/scripts/install-deb-essential-packages.sh

同一个脚本也可以用在其他基于 Debian 的系统上,例如 Ubuntu 及其衍生版 Linux Mint、Pop!_OS、ElementaryOS 和 Zorin OS。

如前所述,有些 packages 可能只在特定版本中提供。请确认脚本中的 packages 在自己的 Linux 发行版上可用。可以按下面链接中的方法检查软件包可用性:

2. 为什么选这些基础软件包?

第一组刻意保持精简。

git

用于克隆仓库和管理配置文件。

git clone ...

curl

一个通用的命令行工具(general-p-purpose),用于通过网络协议传输数据。很多安装说明和脚本都会用到它。

wget

另一个常用的命令行下载工具。

ca-certificates

提供软件进行 HTTPS/TLS 连接时使用的受信任 CA 证书。

unzip

下载的软件和项目经常以 .zip 压缩包的形式提供,所以它很有 用。

build-essential

提供标准的基础编译工具链,包括 gcc 和 make 等工具。Debian 明确把它列为构建基础环境的关键软件包。

build-essential 对每个桌面用户来说并非严格必需。如果确定不会编译软件,可以把它去掉。这里保留它,是因为对一台面向开发的个人机器来说,它是一个有用的基线。

3. 为什么用 Bash 数组?

与其手动执行:

sudo apt install git
sudo apt install curl
sudo apt install wget
sudo apt install unzip

不如维护一份清单:

PACKAGES=(
git
curl
wget
unzip
)

然后用一条命令安装全部:

sudo apt install -y "${PACKAGES[@]}"

这样做的好处是:

  • 更快;
  • 更容易维护;
  • 容易修改;
  • 重装 Debian 后容易复用。

添加一个软件包只需要多加一行:

PACKAGES=(
git
curl
wget
neovim
)

4. 为什么用 set -euo pipefail?

你可能已经注意到,脚本开头是:

set -euo pipefail

这会让脚本更安全。

其中 -e 的作用是:当某条 command 失败时让脚本停止,而不是继续盲目执行。

对于一个小型的个人配置脚本来说,这是一个不错的默认选择。

5. 把个人软件包单独分开

随着清单变长,区分通用工具和自己特有的东西会很有帮助。

例如:

PACKAGES=(
# Basic tools
git
curl
wget
ca-certificates
unzip
build-essential
# Desktop
firmware-cirrus
papirus-icon-theme
gnome-shell-extension-manager
gnome-shell-extension-dashtodock
gnome-themes-extra
# Applications
timeshift
flatpak
gnome-software-plugin-flatpak
steam-installer
# Editor
vim
)

这样以后看脚本时更容易理解。

6. 什么时候用 Ansible?

有人会问,为什么不干脆用 Ansible 这类自动化平台了事。我不会只为一份软件包清单就用 Ansible。当我开始做下面这些事情时,Ansible 才变得有用:

  • 安装 packages;
  • 配置 Git;
  • 创建配置文件;
  • 配置 Vim;
  • 启用服务;
  • 配置 GNOME;
  • 应用系统设置。

或者当我希望多台机器拥有相同配置时。

单纯安装软件包,我用 Bash;需要可重复的系统配置,我才用 Ansible。

7. 安装 Ansible

Ansible 是一个用 Python 编写的开源命令行 IT 自动化工具。它可以配置系统、部署软件,并编排复杂工作流,以支持应用部署、系统更新等任务。

它以“无代理”著称,也就是说,它通过标准连接协议管理远程机器,而不需要在被管理机器上安装常驻软件。

工作原理

你用 YAML 编写人类可读的指令(称为 playbook),声明服务器应处于的目标状态。Ansible 通过 SSH(用于 Linux/Unix)或 WinRM(用于 Windows)连接,检查当前状态,只在必要时做出改动以匹配你的指令。被管理节点上不需要额外的代理或守护进程。

关键要点

  • 无代理架构: 与需要在每台服务器上运行常驻代理的工具不同,Ansible 使用现有的 SSH 守护进程(Windows 上则是 WinRM)作为传输方式。这减少了维护开销,也不需要管理代理软件的更新或兼容性。
  • 幂等性: 这是核心原则。Ansible 模块在设计上尽可能做到幂等,也就是只有当系统尚未达到目标状态时才会做出改动。多次运行同一个 playbook 会得到相同结果。注意:有些模块(例如 command 和 shell)本身不具备这种行为,需要手动保证。
  • 用 YAML 保持简单: Playbook 用 YAML 编写,这是一种人类可读的语言。这意味着自动化代码读起来常常像文档,即使不是专职程序员也能看懂。
  • 推送模型: 你从中央的“控制节点”执行 Ansible,它通过 SSH(Windows 上则是 WinRM)把配置变更推送到“被管理节点”。

关于 Ansible 的一个事实

虽然 Ansible 以“无代理”闻名,但它实际上确实会使用一个很小的临时程序,只是时间很短。每执行一个任务,Ansible 都会连接到被管理节点,把称为 Ansible 模块 的小程序推送过去。

这些程序被编写成系统目标状态的资源模型。Ansible 随后执行这些模块(默认通过 SSH),并在完成后删除它们。这样服务器会保持自动化之前的样子,不会留下常驻的 Ansible 软件。

在 Debian / Ubuntu 上安装 Ansible,运行下面的命令,需要管理员权限:

sudo apt update
sudo apt install ansible

查看版本:

ansible --version

示例输出:

ansible [core 2.16.3]
config file = None
configured module search path = ['/home/ostechnix/.ansible/plugins/modules', '/usr/share/ansible/plugins/modules']
ansible python module location = /usr/lib/python3/dist-packages/ansible
ansible collection location = /home/ostechnix/.ansible/collections:/usr/share/ansible/collections
executable location = /usr/bin/ansible
python version = 3.12.3 (main, Apr 10 2024, 05:33:47) [GCC 13.2.0] (/usr/bin/python3)
jinja version = 3.1.2
libyaml = True

8. 创建 Ansible 项目

对于个人桌面:

mkdir -p ~/ansible/debian-desktop
cd ~/ansible/debian-desktop

初始化 Git:

git init

初始结构:

debian-desktop/
├── inventory.ini
└── site.yml

9. 创建 inventory

创建 inventory.ini 文件:

inventory.ini

加入:

[desktop]
localhost ansible_connection=local

这告诉 Ansible 在本地管理当前这台计算机。

用 command 测试:

ansible -i inventory.ini desktop -m ping

示例输出:

localhost | SUCCESS => {
"ansible_facts": {
"discovered_interpreter_python": "/usr/bin/python3"
},
"changed": false,
"ping": "pong"
}

10. 创建 playbook

创建 site.yml 文件:

nano site.yml

加入以下内容:

---
- name: Configure my Debian desktop
hosts: desktop
become: true
tasks:
- name: Update apt cache
ansible.builtin.apt:
update_cache: true
- name: Install desktop packages
ansible.builtin.apt:
name:
- git
- curl
- wget
- ca-certificates
- unzip
- build-essential
- firmware-cirrus
- papirus-icon-theme
- timeshift
- gnome-shell-extension-manager
- vim
- gnome-shell-extension-dashtodock
- gnome-themes-extra
- flatpak
- gnome-software-plugin-flatpak
- steam-installer
state: present

同样,请把平台支持的 packages 加进上面的 playbook。保存并关闭。

运行 playbook:

ansible-playbook -i inventory.ini site.yml --ask-become-pass

示例输出:

BECOME password:
PLAY [Configure my Debian desktop] ********************************************************************************************************************************************************************************************************
TASK [Gathering Facts] ********************************************************************************************************************************************************************************************************************
ok: [localhost]
TASK [Update apt cache] *******************************************************************************************************************************************************************************************************************
changed: [localhost]
TASK [Install desktop packages] ***********************************************************************************************************************************************************************************************************
changed: [localhost]
PLAY RECAP ********************************************************************************************************************************************************************************************************************************
localhost : ok=3 changed=2 unreachable=0 failed=0 skipped=0 rescued=0 ignored=0

11. Bash 脚本与 Ansible 的区别

Bash 通常表达的是:运行这些 command。

Ansible 通常表达的是:让机器变成这个样子。

例如:

- name: Install Vim
ansible.builtin.apt:
name: vim
state: present

如果 Vim 没有安装,Ansible 会指示系统安装它;如果 Vim 已经安装,它就什么都不做。

这就是所谓的幂等性。

12. 加入配置

软件包安装跑通之后,Ansible 也可以管理配置。

例如:

- name: Create Vim configuration
ansible.builtin.copy:
content: |
set number
set relativenumber
syntax on
dest: "{{ ansible_facts.user_dir }}/.vimrc"
mode: "0644"

现在 Ansible 不只是安装软件,它还在配置这台机器。

13. playbook 变大后拆分

不必一开始就考虑 Ansible roles。

如果 site.yml 变得太大,可以把任务拆到不同文件里:

debian-desktop/
├── inventory.ini
├── site.yml
├── packages.yml
├── git.yml
└── desktop.yml

site.yml 是主 playbook,它导入并运行其他文件中的任务:

---
- name: Configure my Debian desktop
hosts: desktop
become: true
tasks:
- ansible.builtin.import_tasks: packages.yml
- ansible.builtin.import_tasks: git.yml
- ansible.builtin.import_tasks: desktop.yml

各个文件有各自的用途:

  • packages.yml —— 包含安装和管理 APT 软件包的任务。
  • git.yml —— 包含配置 Git 的任务。
  • desktop.yml —— 包含配置 GNOME 及其他桌面设置的任务。

例如,packages.yml 可能包含:

---
- name: Install required packages
ansible.builtin.apt:
name:
- git
- curl
- wget
- vim
- flatpak
state: present

这样可以让 site.yml 保持精简,也让配置更容易理解和维护。

14. 复杂配置最终使用 Ansible roles

如果项目变得大得多,可以引入 Ansible roles。

结构最终可能类似:

debian-desktop/
├── inventory.ini
├── site.yml
└── roles/
├── packages/
├── git/
├── vim/
└── gnome/

每个 role 把与某个领域相关的任务、文件、模板、变量和配置集中在一起。

例如:

  • packages —— 管理已安装的软件包。
  • git —— 管理 Git 配置。
  • vim —— 管理 Vim 配置。
  • gnome —— 管理 GNOME 和桌面配置。

不要一开始就用 roles。 对于个人机器,先从一个 site.yml 开始。如果它变大,就拆成 packages.yml、git.yml、desktop.yml 这样的文件。只有当项目大到 roles 确实带来实际好处时,再引入它们。

15. 常用的 Ansible 命令

测试 Ansible:

ansible -i inventory.ini desktop -m ping

运行 playbook:

ansible-playbook -i inventory.ini site.yml --ask-become-pass

试运行:

ansible-playbook -i inventory.ini site.yml --check --ask-become-pass

输出更多信息:

ansible-playbook -i inventory.ini site.yml --ask-become-pass -v

16. 把配置放进 Git

对于 Bash 脚本:

cd ~/scripts
git init

对于 Ansible 项目:

cd ~/ansible/debian-desktop
git init

最终,Ansible 项目可以成为我桌面配置的权威描述。

17. Fedora 或基于 RHEL 的发行版怎么办?

脚本的 Bash 结构 可以复用到 Fedora 和其他基于 RHEL 的发行版上,但不能想当然地认为把 apt 换成 dnf 就够了。

在 Debian 上:

sudo apt update
sudo apt install -y "${PACKAGES[@]}"

在 Fedora/RHEL 系系统上,安装软件包一般使用:

sudo dnf install -y "${PACKAGES[@]}"

不过,软件包名称在不同发行版之间可能不同。

例如:

Debian              Fedora/RHEL
-------             ----------
git                 git
curl                curl
wget                wget
ca-certificates     ca-certificates
unzip               unzip
build-essential     gcc gcc-c++ make

有些 packages 可能名称完全不同,可能由不同的仓库提供,也可能根本不存在相同的形式。

因此,对于个人配置,通常更清晰的做法是维护彼此独立的脚本:

install-debian-essential-packages.sh
install-fedora-essential-packages.sh

Debian 脚本可以用:

sudo apt update
sudo apt install -y "${PACKAGES[@]}"

而 Fedora 脚本可以用:

sudo dnf install -y "${PACKAGES[@]}"

并配一份 Fedora 专用的软件包清单。

为什么不写一个脚本包办一切?

检测发行版是可行的:

if command -v apt >/dev/null 2>&1; then
sudo apt update
sudo apt install -y "${PACKAGES[@]}"
elif command -v dnf >/dev/null 2>&1; then
sudo dnf install -y "${PACKAGES[@]}"
else
echo "Unsupported package manager."
exit 1
fi

但一旦软件包名称不同,这种做法就会变得复杂。

对于简单的个人配置,分开的脚本更容易理解和维护。

如果配置最终需要支持多个发行版并变得复杂得多,那就是考虑使用 Ansible 的好时机。

18. 结论

可以看到,可以用一个简单的安装后 Bash 脚本,避免手动逐个安装相同的 packages。如果想自动化并复现整个系统的配置,就可以使用 Ansible。

在问题大到确实需要之前,不要引入 Ansible。

不要把问题复杂化。从简单开始。把重复的事情自动化。当需要管理的是配置本身时,再引入 Ansible。