1. 为什么我写了一份 Ubuntu 24.04 LTS 初始化脚本如果你跟我一样隔三差五就要在服务器商后台点几下鼠标、开一台全新的 Ubuntu 24.04 LTS或者在虚拟机里反复折腾系统大概率已经受够了那套“装系统五分钟配环境半小时”的流程。裸机新系统拿到手永远是一副“欠收拾”的样子国外源慢成狗、没有基本开发工具、SSH 还得手动调、中文输入法得自己装……这些事单看都不难但架不住每次都重复来一遍。我前前后后手动初始化了几十台机器之后终于忍不住把整个过程写成了一份脚本放在 GitHub 上用的时候一行命令就能跑完。这份脚本的目标很明确在系统刚装完、只有一个 root 账号或者刚创建完 sudo 用户的状态下一次性把软件源、系统更新、基础工具、开发环境、常用软件、中文输入法、系统美化全部搞定。跑完之后你再进去就是一个基本能直接干活的环境。它适合谁用三种人最需要经常在云服务器上开新机的运维或后端开发省掉重复劳动本地用虚拟机折腾各种发行版、每半个月就想重装一次系统的人刚接触 Ubuntu 24.04 LTS、想跳过“到处搜教程然后一个个复制命令”的新手。这不是什么新概念网上类似的初始化脚本一抓一大把但我这份脚本最大的特点是模块化、可裁剪、幂等。你不需要整套照搬只需要看懂里面的思路、挑自己需要的部分抄走就行。2. 初始化脚本的整体设计思路2.1 先想清楚你要“变成什么样”才算初始化完成写脚本之前我一直坚持一个习惯不要一上来就堆命令先列清单。初始化一台 Ubuntu 24.04 LTS我把它拆成六个模块软件源与系统更新换国内镜像源、更新索引、升级软件包基础工具链curl、wget、git、vim、htop 这类走到哪都用得到的开发环境GCC/make、Python 3 pip、Node.js以及 Docker 这种容器工具Shell 与环境配置Zsh Oh My Zsh、常用环境变量、SSH 配置桌面用户体验中文输入法、字体、常用 GUI 软件这个是可选项服务器用不到最后清理删除下载缓存、自动移除用不到的旧内核依赖。为什么要把“清理”单独算一步因为很多人装完软件就跑了系统里堆着一堆.deb缓存和孤立依赖等过几个月被磁盘占用报警惊到了才想起来清。脚本里顺手做掉不费事。2.2 模块化脚本的三个原则幂等、可裁剪、可重复跑写初始化脚本和写业务代码一个道理最怕的就是跑第二次直接炸掉。所以我的脚本实现了三个基本原则第一个原则是幂等。同一个脚本同一个机器跑两遍和跑一遍的结果应该是一致的。比如换源的操作如果检测到sources.list文件里已经有mirrors.tuna.tsinghua.edu.cn字样就直接跳过替换逻辑而不是无脑再覆盖一次更不是用sed硬塞一行进去导致文件里出现重复记录。类似地创建目录之前先判断存不存在追加环境变量之前先检查.bashrc里是否已经声明过。第二个原则是可裁剪。脚本用函数封装每个模块顶部有一段配置区里面是一堆形如INSTALL_DOCKER1的开关。你要装就保持为1不要就把1改成0整段逻辑自动跳过。这样一份脚本既能服务 2C4G 的轻量云服务器也能服务一台 64G 内存的开发工作站——服务器跑基础模块工作站再把桌面体验打开互不干扰。第三个原则是可重复跑。因为有了幂等设计脚本就算跑到一半因为网络中断挂掉或者你中途 CtrlC 取消了修复了问题再跑一次也不会出幺蛾子。这一点在实际使用中太重要了。2.3 换源背后的“为什么”为什么选择清华源而不是别的镜像软件的下载速度是国外 VPS 用户永远的痛。archive.ubuntu.com在高峰期的连接速度我实测过很多次有时候只有几十 KB/s一个apt update能卡到怀疑人生。换源这件事有多种姿势我在脚本里用的是“手动生成sources.list”的方案。为什么不推荐直接替换成某一段网上抄来的源内容因为你不知道那段源内容对应的 Ubuntu 版本是什么。Ubuntu 24.04 LTS 的代号是Noble Numbat源路径分布和 22.04Jammy不同如果拿错版本的文件apt update会刷出一堆 404。我在脚本里选择了清华 TUNA 源mirrors.tuna.tsinghua.edu.cn。它的优势有两个一个是域名解析稳定阿里云、腾讯云的镜像源在部分海外 VPS 上解析有问题清华源整体表现最稳另一个是 HTTPS 支持完善不需要额外配置。当然如果你在国内的云服务器上跑换成阿里源或腾讯源反而更快脚本配置区里留了口子改一个变量就行。换源之后一定要做的动作是apt update如果更新完发现有不存在的路径报错大概率就是源文件里的组件路径不对。Ubuntu 24.04 的sources.list只需要四条核心记录deb https://mirrors.tuna.tsinghua.edu.cn/ubuntu/ noble main restricted universe multiverse deb https://mirrors.tuna.tsinghua.edu.cn/ubuntu/ noble-updates main restricted universe multiverse deb https://mirrors.tuna.tsinghua.edu.cn/ubuntu/ noble-backports main restricted universe multiverse deb https://mirrors.tuna.tsinghua.edu.cn/ubuntu/ noble-security main restricted universe multiverse第一条是主仓库第二、三、四条分别对应滚动更新、回溯更新和安全更新。不要漏掉noble-security很多人换了源之后系统一直不推送安全补丁就是因为在旧的sources.list结构里security 源指向的是security.ubuntu.com而新系统默认打包并注释了它只留了noble-security这一条路径。3. 核心模块的细节拆解与实操要点3.1 系统基础配置从换源到 SSH一条龙搞定换完源紧接着就是系统更新。这里有一个细节apt upgrade要不要加-y参数我倾向于加但前提是你明确知道你的系统不是在生产环境的关键节点上跑着重要服务。初始化脚本的适用场景是新装机升级什么包都不会影响线上业务所以直接强制跑完反而省心。如果是生产环境里你手贱想跑这份脚本那我劝你在配置区把AUTO_UPGRADE设为0。SSH 配置这块比较隐晦但也最重要。很多云服务器的默认 SSH 配置是允许密码登录的这就等于把爆破的窗口开着。脚本要做两件事把PermitRootLogin的配置改为prohibit-password意思是禁止 root 用密码方式远程登录但保留密钥方式——前提是你把公钥放到了~/.ssh/authorized_keys把PasswordAuthentication改为no直接关闭密码认证只允许密钥。这里必须提醒一句如果你改了这两项但公钥并没有写进 authorized_keys也不要重启 sshd否则你会把自己锁在门外。弄完之后必须新开一个终端窗口、确认能通过密钥登录后再重启服务。这个坑我见人踩过不止一次我自己早期也中招过一次只能去服务商后台用 VNC 救援模式救回来非常折腾。3.2 基础工具链安装的几个“隐藏点”基础工具这块表面上就是apt install一串包名但你真正能从这里学到东西的是“哪几个包是必须要装的、为什么”。curl和wget属于基本中的基本git是版本管理标准件htop用来实时看 CPUunzip和zip看起来不起眼但实际装软件的时候总有要用到解压的场景提前装好避免临时抓瞎。另外还有两个容易忽略的software-properties-common和ca-certificates。前者是add-apt-repository命令的依赖没有它你连 PPA 都加不了而 Docker 这类软件又偏偏非要走apt仓库——实际上 Docker 官方推荐的方式就是添加他们自己的仓库再装绕不开这个包。后者管 HTTPS 证书链装任何走 HTTPS 的软件源都离不开。还有一个我个人极其推荐的工作习惯装tree和ncdu。tree用来快速看目录结构ncdu是一个交互式的磁盘占用分析器比du -sh *直观得多。这俩包加起来几 MB带来的效率提升是长期的。3.3 开发环境安装Docker 与 Python 的取舍开发环境模块是这份脚本里最有争议的部分因为“开发环境”这四个字的定义因人而异。我在脚本里做的是一个最小可用的配置而不是一个全家桶GCC / make / build-essential基础编译工具链用来应对“拿到源码自己编译”的场景Python 3 python3-pip python3-venvUbuntu 24.04 LTS 自带的 Python 3.12用系统包管理器装 pip 和 venv 支持即可。在这里我强烈不建议用pip install往系统 Python 里装包一律走虚拟环境这个后面讲坑的时候细说Docker现在开发基本绕不开容器直接装一份并把当前用户加进docker组脚本里判断了一下用户组存在才执行装完后当前用户不用sudo就能直接docker psNode.js通过 NodeSource 仓库装的 LTS 版本。因为 Ubuntu 24.04 默认源里的 Node.js 版本偏老很多新工具链会有兼容性警告用 NodeSource 的setup_lts.x脚本装完就是当前 LTS。Docker 安装有个不大不小的坑Ubuntu 24.04 LTS 默认安装了docker.io这个软件包吗实际上部分云镜像里是有的但它的版本通常落后于 Docker 官方仓库。如果两套混着来一会儿显示装了、一会儿又命令不存在极其折磨人。我的做法是先检测并移除docker.io和containerd然后再走 Docker 官方 APT 仓库安装流程。# 安装 Docker 前的清理 for pkg in docker.io docker-doc docker-compose-v2 podman-docker containerd runc; do apt-get remove -y $pkg || true done3.4 Shell 与桌面配置别把“看起来舒服”和“能用”搞混Shell 配置这部分我默认安装了Zsh Oh My Zsh并顺手装了几个常用插件zsh-autosuggestions、zsh-syntax-highlighting。切换默认 shell 用的是chsh -s $(which zsh)。这个模块平时在服务器上跑没问题但有一个需要知道的前提切换默认 shell 之后你原有的.bashrc里配置的环境变量就不会被加载了。所以我额外做了一步在.zshrc的末尾自动追加一行source ~/.profile如果存在的话保证之前在.bashrc里配过的 PATH 不会被莫名其妙弄丢。桌面配置则是完全可选的部分而且只对安装桌面版 Ubuntu 的机器有意义。我在这个模块里做了三件事安装fcitx5 fcitx5-rime 中文字体提供中文输入能力装Google Chrome和VS Code走的是官网.deb下载安装不走 snap写了一份字体配置文件把默认字体替换成思源黑体和中英文混排更协调的组合。关于中文输入法这里有两个常见的“反直觉”点需要注意Ubuntu 24.04 LTS 的桌面上如果之前装过IBus相关组件GNOME 默认自带和 fcitx5 会发生输入法框架冲突表现为输入法指示器不出现、切换不生效。解决方案倒是简单要么卸载 IBus要么设置环境变量INPUT_METHODfcitx和GTK_IM_MODULEfcitx并重新登录。实际操作中我选后者因为 IBus 被 GNOME 深度集成硬卸载容易引起别的问题。还有一点是关于snap 的Ubuntu 24.04 LTS 里 Firefox 默认就是 snap 版。如果你和我一样不喜欢 snap 的启动速度和自动更新机制脚本里可以加一步“用 Mozilla 官方 APT 仓库替换 Firefox snap 版”的操作。这个操作不难就是从 Mozilla 官网下载 APT 仓库配置指向packages.mozilla.org然后apt install firefox装上真正的 deb 版。4. 实操过程与核心环节实现因为我平时主要会在云服务器和虚拟机之间反复切换环境这里直接分享一份我在一台 2C4G 的轻量云服务器上跑完整套脚本的实录过程包括准备工作、执行命令和最终效果。整套过程差不多 8 到 10 分钟取决于网络速度。4.1 准备工作拿到机器后的第一件事拿到一台新机器后我的习惯是先用最小化的 SSH 命令连上去看看状态ssh root你的服务器IP然后马上做三件事更新系统自带索引因为源可能还是初始的国外源、安装sudo、创建普通用户并授权。这里我插一句很多云服务商默认让你用 root 登录我一般会创建一个deploy用户并加入sudo组后面日常开发操作都走普通用户只有明确的系统管理操作才用sudo。准备用户这部分其实也可以脚本化我在初始化脚本中用一个函数来封装创建用户的逻辑create_user() { if id $INIT_USER /dev/null; then echo 用户 $INIT_USER 已存在跳过创建 else useradd -m -s /bin/bash -G sudo $INIT_USER echo $INIT_USER:$DEFAULT_PASSWORD | chpasswd fi # 确保 sudo 组内用户可以免密执行 sudo可选 }这里我特别要提醒一个细节如果你不想在日志里暴露明文密码脚本里就不要写死密码让执行时交互输入。为了演示方便我写了$DEFAULT_PASSWORD但实际脚本建议改成通过环境变量传入或者第一次登录后立刻改密码。4.2 执行脚本的三个阶段脚本的完整执行流程可以分成三个阶段阶段一初始化阶段约 1 分钟。脚本会先检查当前系统版本确认是 Ubuntu 24.04 LTS 而不是其他版本。接着检测架构是amd64还是arm64因为有些软件比如 Chrome只提供 amd64 的 deb 包跑在 ARM 机器上就得跳过。这一阶段还会检查apt进程是否被占用如果系统正好在自动更新就等它跑完。阶段二核心配置阶段约 5~6 分钟。换源完成后执行apt update然后开始装基础工具和开发环境。这部分耗时不固定Docker 仓库和 NodeSource 仓库的下载速度是主要的变量。如果网络质量不佳脚本里做了一个重试机制——下载超时会自动重试三次重试还不行就直接跳过并打印告警避免整条流程卡死在某个软件源上。阶段三桌面与收尾阶段约 2 分钟。这阶段只会在INSTALL_GUI1时执行。装完中文输入法后我习惯顺手执行一次fc-cache -f强制刷新字体缓存避免部分环境下新字体不生效的问题。最后清理apt缓存和自动移除孤立依赖打印一份简短的“安装摘要”把安装过的软件包数量、是否成功换源、Shell 是否切换成功等关键信息汇总出来。4.3 关于“静态 IP 配置”一个比较隐蔽的坑服务器或者虚拟机环境里还有一个常被问到的需求修改主机名和静态 IP。动态主机名容易搞混机器我经常在一台机器上开多个实例不写清楚主机名隔两天自己都分不清哪台是哪台。脚本的做法是hostnamectl set-hostname $NEW_HOSTNAME然后修改/etc/hosts把旧主机名的映射替换成新的。这里有个坑hostnamectl改完之后当前 shell 的主机名不会立刻变需要重开一个 SSH 会话才生效。所以脚本执行日志里我特意打印了一句“主机名已修改重连后生效”免得你以为脚本出 bug 了。静态 IP 配置则比较尴尬在 Ubuntu 24.04 里网络管理已经从ifupdown迁移到了netplan配置/etc/network/interfaces在老版本上有效新版本根本不吃你这一套。我在脚本里提供了一段netplan配置的生成函数但默认不启用因为这个功能实在是太容易把网络搞挂了。除非你明确知道当前网卡名称用ip a查看一般是eth0、ens3这类并且确定没有 DHCP 干扰不然我强烈不建议在远程服务器上动网络配置。本地虚拟机随便玩远程服务器尤其是云端机器绝大部分情况都应该保留 DHCP 自动分配用服务商提供的控制台管理 IP 就行。5. 常见问题与排错实录5.1 apt update 报 404 或 Release 文件失效这个问题的 99% 原因都是源文件写错了版本代号或组件名。Ubuntu 24.04 LTS 的代号是noble不是 23.10 的mantic也不是 22.04 的jammy。如果你从网上随手抄了一段源的地址里面混着其他版本的路径apt 就会在更新索引时收到 404然后报错说某个 Release 文件不存在。排错方法很简单手动看源文件内容cat /etc/apt/sources.list用我上面给的模板核对一下确保所有路径都是noble。另外注意检查/etc/apt/sources.list.d/目录下有没有其他第三方源的.list文件有的第三方源本身年久失修同样报 404。全局换源时容易漏掉这些次要文件排查思路要放宽。还有一种情况是自定义源里签名过期Ubuntu 24.04 对 GPG 密钥的验证比较严格换源时如果导入过旧签名会提示NO_PUBKEY。解决方式是手动导入缺失密钥apt-key adv --keyserver keyserver.ubuntu.com --recv-keys 缺失的KEYID注意apt-key已经标记为 deprecated新思路是把公钥文件放到/etc/apt/keyrings/目录然后用signed-by指过去。但这个属于进阶话题先用apt-key解决燃眉之急也没问题。5.2 脚本跑到一半中断能直接重跑吗能。这也是我设计时考虑的重点。因为脚本整体是幂等的已经完成的操作会自动跳过比如换源逻辑判断源文件已经是指定镜像直接跳过已安装的软件包会被dpkg -s检测到跳过安装用户已存在则不会重复创建。不过有一个特殊情况如果脚本在apt install过程中被强杀dpkg的锁文件可能还在。重跑之前建议先手动执行一次修复dpkg --configure -a apt install -f可以理解成把上次没干完的活强行收尾一次然后再重新跑脚本就不会遇到“无法获取 dpkg 前端锁”的报错了。5.3 中文输入法切不出来该查哪里很多人在 Ubuntu 24.04 上配好 fcitx5 之后发现按 CtrlSpace 没有反应。除了前面提到的 IBus 冲突问题我的排查顺序是先确认 fcitx5 是不是真的在运行ps aux | grep fcitx5再看环境变量是否正确echo $GTK_IM_MODULE应该输出fcitx验证当前的输入法框架是否被 fcitx 接管im-config -m如果显示ibus就要执行im-config -n fcitx5并注销重登最后检查有没有装对应的中文字体和输入方案rime 如果没有配置/usr/share/rime-data或者~/.config/fcitx/rime下的方案文件即使框架没问题也不会出候选词。我的脚本里做了一个额外的保险操作在/etc/environment里强制写入了三行环境变量确保 X11 和 Wayland 环境下都能被识别GTK_IM_MODULEfcitx QT_IM_MODULEfcitx XMODIFIERSimfcitx这里有个值得注意的问题如果你用的是 Waylandfcitx5 的接管情况跟 X11 下略有差异但还好 fcitx5 本身对 Wayland 的支持已经足够成熟绝大多数桌面场景都能正常用。如果还是不行检查一下是不是用了精简桌面环境比如 Ubuntu Server 自己装的 GNOME有些环境缺gnome-settings-daemon组件也会导致输入法状态同步不正常。5.4 安装 Node.js 后 npm 命令找不到或版本不对这类问题多半出在 NodeSource 仓库安装脚本的执行顺序上。NodeSource 提供的是setup_lts.x的 shell 脚本它会检测当前发行版版本并配置对应的 APT 仓库。如果直接在 Ubuntu 24.04 上运行它需要保证lsb-release、wget这些基础依赖已经存在否则脚本中途退出仓库配不完整apt install nodejs得到的还是系统的旧版。我的建议是安装 Node.js 前先跑一次基础的 apt 更新然后确认curl、wget已安装再执行curl -fsSL https://deb.nodesource.com/setup_lts.x | bash - apt install -y nodejs装完验证一下版本node -v应该输出 v20 或 v22 这样的 LTS 版本号如果还是老版本就要看 NodeSource 仓库有没有被正确写入/etc/apt/sources.list.d/。5.5 Python 虚拟环境相关为什么用 pip 装包会报 externally-managed-environment这是 Ubuntu 24.04 LTS 引入 PEP 668 之后最大的变化。在 23.04 之前的版本里pip install一般都能直接装进系统 Python但 24.04 明确禁止了这种操作运行时会抛出一个externally-managed-environment错误大意是“你的 Python 环境由系统 apt 管理不要用 pip 乱装”。很多人第一次碰到会觉得是系统坏了其实这是官方故意设计的保护机制。应对方式有三种给 pip 加--break-system-packages参数不推荐等于强行绕过保护用apt install python3-pip装集成的 pip再为每个项目创建 venv推荐用 uv、poetry 这类新一代包管理工具来管虚拟环境个人最爱。我的初始化脚本里会在配置区给一个开关如果设置为 1则在系统层面为常见用户创建一个~/venv目录作为默认虚拟环境并自动写入source到~/.zshrc这样每次登录 shell 都在虚拟环境里不用重复创建。你要是觉得这个操作有侵入性把开关调成 0 就是最干净的默认行为。6. 初始化脚本之外一些值得坚持的长期习惯脚本本身写得再好也只是“一次性解决方案”长期维护系统还得靠一套好习惯。我在踩了几年坑后总结了这几条经验第一任何系统初始化操作都要落成版本控制的脚本文件。不管你是自己写还是抄别人的把脚本放到一个 Git 仓库里管起来。不要觉得这是小题大做我见过太多人“这台机器手动配另一台又手动配配完两台还不一样”。版本化管理至少让你每次初始化一台机器时都是可预期的、一致的。如果哪天系统版本大版本升级脚本里的某条命令失效了你也能通过 Git 历史快速定位是哪次更新的问题。第二不要在 root 用户下跑初始化脚本。创建一个普通用户授权 sudo然后用普通用户身份来跑初始化。root 下的环境变量和~/.bashrc跟普通用户完全不同你在 root 下配得再完美切回自己的账号还得再折腾一遍。而且 root 下误操作的成本太高脚本是无差别攻击的一句路径写错就可能搞坏全局。第三分阶段执行比一把梭靠谱。新的初始化脚本第一次在机器上跑的时候我都是分成“换源更新”“装基础工具”“装开发环境”“桌面配置”四段来执行每段跑完顺手确认一下结果确认没坑了再接下来跑。等你确认了整份脚本在你的场景下是稳定的才开始一把梭全量跑。这能让你在脚本出错时最快定位到是哪一步引入的问题。第四定期给系统做快照。云服务器商的控制台基本都支持磁盘快照或自定义镜像在跑初始化脚本之前先做一次快照是我个人强烈建议的保命操作。假设脚本里某个软件源把系统搞挂了或者某个配置把网络弄断了快照就是你后悔药的来源。虚拟机上就更方便了开跑之前打个快照出了问题一巴掌拍回去几十秒的事。之前我也觉得“就装个软件不至于”直到有一次我把openssh-server给误卸了当时远程连接瞬间断开机器在外地机房只能求机房管理员帮忙从那以后快照就成了标配。第五脚本别只满足“能用”要让人能读、能改、能懂。我见过太多人写的脚本全是一行行命令堆砌没有函数封装、没有注释、没有开关变量。这种脚本一旦环境有变化你根本不敢改只能推倒重来。我在脚本每个函数开头都用注释写清楚这个函数干什么、依赖什么、需要什么前置条件每个开关变量都有说明注释。看着多写了几行注释但三个月后再回来看自己写的脚本时你会感谢当时的自己。7. 关于脚本的后续演进方向这份初始化脚本目前还是基于 Ubuntu 24.04 LTS 开发的但随着时间推移我肯定会维护下去。后续我想加几个能力一个是更完善的错误处理机制。目前脚本遇到命令失败大部分只是打印告警然后继续这可能导致后面几步跑在一个“半崩溃”的系统上。理想状态是每个关键步骤有明确的退出码检查一旦失败就停下来并将日志自动写入一个文件供分析。另一个是Ansible 化改造。单机下用 bash 脚本很顺手但如果你要同时初始化五台服务器bash 脚本就需要自己处理并发和重复执行的问题。Ansible 天然支持多主机执行和幂等操作把现有的这些模块逻辑用 Ansible Role 重新组织一下直接一条命令推送到所有机器上是更省心也更符合生产环境的方式。还有一个方向是软件包的选择做成清单驱动。现在的脚本里软件包列表是硬编码在一个数组里的。以后我希望改成读入一个packages.txt文件这样不同机器、不同用途就能用不同的软件清单而不需要为了减少几个包去改动整个脚本。这类调整单独用 yaml 或 json 来描述包列表配合一个解析函数来把列表转成apt install的参数工作量不大但灵活性提高很多。不过说到底初始化脚本只是基础设施的一个起点。真正让系统稳定运行的还是在日常使用中积累的维护经验。大家如果在自己服务器上跑通这份脚本或者在改造它的过程中碰到什么有意思的问题欢迎在评论里交流。
