Compare commits

..
2 Commits
Author SHA1 Message Date
database-mysql 1b8f8e1e59 first commit 2026-09-06 12:58:36 +08:00
database-mysql 2477352d6f first commit 2026-09-06 12:58:04 +08:00
10 changed files with 1343 additions and 2 deletions

No files matched your search

+24
View File
@@ -0,0 +1,24 @@
# Logs
logs
*.log
npm-debug.log*
yarn-debug.log*
yarn-error.log*
pnpm-debug.log*
lerna-debug.log*
node_modules
dist
dist-ssr
*.local
# Editor directories and files
.vscode/*
!.vscode/extensions.json
.idea
.DS_Store
*.suo
*.ntvs*
*.njsproj
*.sln
*.sw?
+10
View File
@@ -0,0 +1,10 @@
# 默认忽略的文件
/shelf/
/workspace.xml
# 已忽略包含查询文件的默认文件夹
/queries/
# Datasource local storage ignored files
/dataSources/
/dataSources.local.xml
# 基于编辑器的 HTTP 客户端请求
/httpRequests/
+5
View File
@@ -0,0 +1,5 @@
<component name="ProjectCodeStyleConfiguration">
<state>
<option name="PREFERRED_PROJECT_CODE_STYLE" value="Default" />
</state>
</component>
+6
View File
@@ -0,0 +1,6 @@
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="ProjectRootManager" version="2">
<output url="file://$PROJECT_DIR$/out" />
</component>
</project>
+8
View File
@@ -0,0 +1,8 @@
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="ProjectModuleManager">
<modules>
<module fileurl="file://$PROJECT_DIR$/LinuxDiskAutoExpansion.iml" filepath="$PROJECT_DIR$/LinuxDiskAutoExpansion.iml" />
</modules>
</component>
</project>
Generated
+6
View File
@@ -0,0 +1,6 @@
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="VcsDirectoryMappings">
<mapping directory="$PROJECT_DIR$" vcs="Git" />
</component>
</project>
+8
View File
@@ -0,0 +1,8 @@
<?xml version="1.0" encoding="UTF-8"?>
<module type="GENERAL_MODULE" version="4">
<component name="NewModuleRootManager" inherit-compiler-output="true">
<exclude-output />
<content url="file://$MODULE_DIR$" />
<orderEntry type="sourceFolder" forTests="false" />
</component>
</module>
+212 -2
View File
@@ -1,3 +1,213 @@
# LinuxDiskAutoExpansion # Linux 根目录自动扩容脚本
本脚本用于自动将 Linux 服务器根目录 (`/`) 扩容至磁盘最大可用空间。脚本会自动识别服务器的磁盘架构(LVM 或标准分区),并根据实际架构选择对应的扩容策略,全程无需人工干预。 一键自动将 Linux 服务器根目录 (`/`) 扩容至磁盘最大可用空间,全程无需人工干预。
## 功能特性
- **自动识别磁盘架构** — 智能检测 LVM / 非 LVM 分区方案,自动选择对应扩容策略
- **广泛系统兼容** — 适配 Debian、Ubuntu、CentOS、RHEL、Fedora、AlmaLinux、Rocky Linux、openSUSE 等主流发行版
- **多文件系统支持** — 支持 ext2/ext3/ext4、XFS、btrfs 文件系统的在线扩容
- **自动安装依赖** — 自动检测并安装 `growpart`、`xfsprogs` 等必要工具
- **模拟运行模式** — 支持 `--dry-run` 参数,只检测不执行,安全预览扩容计划
- **完整日志记录** — 全程操作日志写入 `/var/log/disk_auto_expand.log`,便于审计追溯
- **无人值守** — 全自动执行,适用于云服务器初始化、批量运维等场景
## 系统要求
| 项目 | 要求 |
|------|------|
| 操作系统 | Linux(Debian / Ubuntu / CentOS / RHEL / Fedora / AlmaLinux / Rocky / openSUSE 等) |
| 权限 | root(需使用 `sudo` 运行) |
| 文件系统 | ext2 / ext3 / ext4 / XFS / btrfs |
| 磁盘架构 | 标准分区 或 LVM(自动识别) |
| 前提条件 | 磁盘上存在未分配的可用空间 |
## 快速开始
### 下载并运行
```bash
# 下载脚本到服务器
git clone https://github.com/your-repo/LinuxDiskAutoExpansion.git
cd LinuxDiskAutoExpansion
# 模拟运行(推荐先执行,确认扩容计划无误)
sudo bash src/main.sh --dry-run
# 确认无误后,正式执行扩容
sudo bash src/main.sh
```
### 一键执行(适用于已上传脚本的场景)
```bash
sudo bash main.sh
```
## 参数说明
| 参数 | 说明 |
|------|------|
| `--dry-run` | 模拟运行模式,仅检测磁盘环境并输出扩容计划,不实际执行任何修改操作 |
| `-h` / `--help` | 显示帮助信息 |
## 工作原理
### 执行流程
```
权限检查 → 安装依赖 → 获取根分区信息 → 检测 LVM → 打印磁盘状态 → 检查可用空间 → 执行扩容 → 验证结果
```
### 非 LVM 模式(标准分区)
适用场景:根目录直接在磁盘分区上(如 `/dev/sda1`、`/dev/vda1`)。
```
磁盘: /dev/sda 50GB
┌────────────────────┬─────────────────────────┐
│ /dev/sda1 20GB │ 未分配 30GB │
│ (根目录 /) │ │
└────────────────────┴─────────────────────────┘
↓ 扩容后
┌─────────────────────────────────────────────┐
│ /dev/sda1 50GB (根目录 /) │
└─────────────────────────────────────────────┘
```
**扩容步骤:**
1. `growpart` — 扩展分区表,使分区占满整个磁盘
2. `resize2fs` / `xfs_growfs` / `btrfs filesystem resize` — 扩展文件系统
### LVM 模式
适用场景:根目录位于 LVM 逻辑卷上(如 `/dev/mapper/ubuntu--vg-ubuntu--lv`)。
```
磁盘: /dev/sda 50GB
┌─────────────────────────────────────────────┐
│ /dev/sda3 30GB (PV) │
│ ┌──────────────────────┬──────────────────┐ │
│ │ LV root 20GB │ VG Free 10GB │ │
│ │ (根目录 /) │ (未分配) │ │
│ └──────────────────────┴──────────────────┘ │
└─────────────────────────────────────────────┘
↓ 扩容后
┌─────────────────────────────────────────────┐
│ /dev/sda3 50GB (PV → 扩展后) │
│ ┌─────────────────────────────────────────┐ │
│ │ LV root 50GB (根目录 /) │ │
│ └─────────────────────────────────────────┘ │
└─────────────────────────────────────────────┘
```
**扩容步骤:**
1. `growpart` — 扩展 PV 所在分区(如分区未占满磁盘)
2. `pvresize` — 将物理卷扩展至分区最大
3. `lvextend -l +100%FREE` — 将 VG 全部剩余空间分配给根 LV
4. `resize2fs` / `xfs_growfs` / `btrfs filesystem resize` — 扩展文件系统
## 适配场景示例
### 示例 1:云服务器标准分区
云服务器 `/dev/vda` 共 100GB,根分区 `/dev/vda1` 仅分配 20GB。
```bash
$ df -hT /
Filesystem Type Size Used Avail Use% Mounted on
/dev/vda1 ext4 20G 3.2G 16G 18% /
$ sudo bash src/main.sh
# 扩容后
$ df -hT /
Filesystem Type Size Used Avail Use% Mounted on
/dev/vda1 ext4 99G 3.2G 92G 4% /
```
### 示例 2:LVM 架构(Ubuntu 默认安装)
Ubuntu 默认使用 LVM,`/dev/sda` 共 50GB,根逻辑卷仅 20GB。
```bash
$ df -hT /
Filesystem Type Size Used Avail Use% Mounted on
/dev/mapper/ubuntu--vg-ubuntu--lv ext4 20G 2.8G 16G 16% /
$ sudo bash src/main.sh
# 扩容后
$ df -hT /
Filesystem Type Size Used Avail Use% Mounted on
/dev/mapper/ubuntu--vg-ubuntu--lv ext4 49G 2.8G 44G 6% /
```
### 示例 3:CentOS LVM + XFS
CentOS 7/8 默认使用 XFS 文件系统 + LVM。
```bash
$ df -hT /
Filesystem Type Size Used Avail Use% Mounted on
/dev/mapper/centos-root xfs 20G 1.9G 19G 10% /
$ sudo bash src/main.sh
# 扩容后
$ df -hT /
Filesystem Type Size Used Avail Use% Mounted on
/dev/mapper/centos-root xfs 49G 1.9G 48G 4% /
```
## 日志
所有操作日志记录在 `/var/log/disk_auto_expand.log`,包括:
- 磁盘环境检测结果
- LVM 信息(如适用)
- 扩容前后磁盘状态对比
- 每一步操作的命令输出
```bash
# 查看扩容日志
cat /var/log/disk_auto_expand.log
```
## 常见问题
### Q: 提示"磁盘没有足够的未分配空间"
脚本检测到磁盘分区已占满,没有剩余空间。需要先通过云平台或虚拟化面板扩大磁盘容量(如从 50GB 扩容到 100GB),然后再运行此脚本。
### Q: 提示"卷组没有足够的可用空间"
LVM 场景下,VG 的可用空间不足。脚本会先尝试扩展 PV 占满磁盘。如果 PV 已占满磁盘但 VG 仍然没有空间,说明 VG 中空间已被其他 LV 占用。
### Q: 是否支持在线扩容?
支持。所有操作均在系统运行时完成,无需重启或卸载文件系统。
### Q: 是否安全?会不会丢数据?
脚本使用的 `growpart`、`lvextend`、`resize2fs`、`xfs_growfs` 均为 Linux 官方标准工具,支持在线无损扩容。建议首次使用时先通过 `--dry-run` 模式确认扩容计划。
### Q: 支持 NVMe 磁盘吗?
支持。脚本通过 `lsblk` 自动识别设备名,兼容 `/dev/sda`、`/dev/vda`、`/dev/nvme0n1` 等各类磁盘命名格式。
## 项目结构
```
LinuxDiskAutoExpansion/
├── src/
│ └── main.sh # 主脚本
├── README.md # 项目说明文档
└── LinuxDiskAutoExpansion.iml
```
## 许可证
Apache 2.0 License
+494
View File
@@ -0,0 +1,494 @@
# 使用文档 — Linux 根目录自动扩容脚本
## 目录
- [1. 概述](#1-概述)
- [2. 环境准备](#2-环境准备)
- [3. 安装与部署](#3-安装与部署)
- [4. 基本使用](#4-基本使用)
- [5. 运行模式详解](#5-运行模式详解)
- [6. 执行流程详解](#6-执行流程详解)
- [7. 各发行版使用示例](#7-各发行版使用示例)
- [8. LVM 架构详解](#8-lvm-架构详解)
- [9. 标准分区架构详解](#9-标准分区架构详解)
- [10. 日志说明](#10-日志说明)
- [11. 故障排查](#11-故障排查)
- [12. 注意事项](#12-注意事项)
---
## 1. 概述
本脚本用于自动将 Linux 服务器根目录 (`/`) 扩容至磁盘最大可用空间。脚本会自动识别服务器的磁盘架构(LVM 或标准分区),并根据实际架构选择对应的扩容策略,全程无需人工干预。
**核心能力:**
- 自动识别 LVM / 非 LVM 磁盘架构
- 自动识别文件系统类型(ext4 / XFS / btrfs)
- 自动安装缺失的依赖工具
- 将磁盘上所有未分配空间分配给根目录
- 支持在线扩容,无需重启服务器
- 支持模拟运行,预览扩容计划
---
## 2. 环境准备
### 2.1 前提条件
在运行脚本之前,请确认以下条件:
1. **磁盘已扩容** — 如果是云服务器(如阿里云、腾讯云、AWS 等),需要先在云平台控制台将磁盘容量扩大。例如将 `/dev/sda` 从 50GB 扩大到 100GB。
2. **存在未分配空间** — 磁盘上必须有尚未分配给分区的可用空间。可以通过以下命令检查:
```bash
# 查看磁盘分区情况
lsblk
df -hT /
```
如果 `lsblk` 显示的磁盘总大小大于根分区大小,说明有未分配空间。
3. **root 权限** — 脚本需要 root 权限运行。
### 2.2 检查当前磁盘状态
```bash
# 查看根目录使用情况和文件系统类型
df -hT /
# 查看所有磁盘和分区
lsblk -o NAME,SIZE,TYPE,FSTYPE,MOUNTPOINT
# 查看磁盘详细信息(需 root)
sudo fdisk -l
```
**判断是否有可扩容空间的示例:**
```
# lsblk 输出示例
NAME MAJ:MIN RM SIZE RO TYPE MOUNTPOINT
sda 8:0 0 50G 0 disk
├─sda1 8:1 0 1M 0 part
├─sda2 8:2 0 1G 0 part /boot
└─sda3 8:3 0 29G 0 part
└─ubuntu--vg-ubuntu--lv 253:0 0 20G 0 lvm /
```
上例中,`sda` 总共 50GB,`sda3` 只用了 29GB,VG 中有约 10GB 未分配空间,可以扩容。
---
## 3. 安装与部署
### 3.1 获取脚本
**方式一:Git 克隆(推荐)**
```bash
git clone https://github.com/your-repo/LinuxDiskAutoExpansion.git
cd LinuxDiskAutoExpansion
```
**方式二:直接上传**
将 `src/main.sh` 上传到服务器任意目录。
```bash
# 使用 scp 上传
scp src/main.sh user@server:/tmp/
```
**方式三:直接下载**
```bash
wget -O /tmp/main.sh https://raw.githubusercontent.com/your-repo/LinuxDiskAutoExpansion/main/src/main.sh
```
### 3.2 设置执行权限(可选)
```bash
chmod +x main.sh
```
> 注意:即使不设置执行权限,也可以通过 `bash main.sh` 方式运行。
---
## 4. 基本使用
### 4.1 命令格式
```bash
sudo bash main.sh [选项]
```
### 4.2 可用选项
| 选项 | 缩写 | 说明 |
|------|------|------|
| `--dry-run` | 无 | 模拟运行模式,仅检测环境并输出扩容计划,不实际执行任何修改 |
| `--help` | `-h` | 显示帮助信息并退出 |
### 4.3 推荐操作流程
```bash
# 第一步:模拟运行,查看扩容计划
sudo bash main.sh --dry-run
# 第二步:确认计划无误后,正式执行
sudo bash main.sh
# 第三步:验证扩容结果
df -hT /
```
---
## 5. 运行模式详解
### 5.1 模拟运行模式(--dry-run)
模拟运行模式会执行除实际扩容以外的所有检测步骤,输出将要执行的操作命令,但不会修改任何磁盘数据。
```bash
$ sudo bash main.sh --dry-run
========================================================
Linux 根目录自动扩容脚本
适配 Debian / Ubuntu / CentOS 等
========================================================
[2026-09-06 10:00:01] [INFO] 检查并安装必要依赖...
[2026-09-06 10:00:01] [INFO] 检测到包管理器: apt
[2026-09-06 10:00:02] [INFO] 获取根分区信息...
[2026-09-06 10:00:02] [INFO] 根目录所在设备: /dev/mapper/ubuntu--vg-ubuntu--lv
[2026-09-06 10:00:02] [INFO] 根目录文件系统: ext4
[2026-09-06 10:00:02] [INFO] 检测到 LVM 架构
[2026-09-06 10:00:03] [INFO] 根目录所在卷组 (VG): ubuntu-vg
[2026-09-06 10:00:03] [INFO] 可用于扩容的空间: 29G
[2026-09-06 10:00:03] [INFO] ========== 开始 LVM 模式扩容 ==========
[2026-09-06 10:00:03] [INFO] [模拟] 将执行: lvextend -l +100%FREE /dev/mapper/ubuntu--vg-ubuntu--lv
[2026-09-06 10:00:03] [INFO] [模拟] 将执行文件系统扩容: ext4 on /dev/mapper/ubuntu--vg-ubuntu--lv
```
**适用场景:**
- 首次使用脚本,了解扩容逻辑
- 生产环境变更前,确认扩容计划
- 排查问题时,收集磁盘环境信息
### 5.2 正式执行模式
不带任何参数直接运行,脚本将自动完成全部扩容操作。
```bash
sudo bash main.sh
```
---
## 6. 执行流程详解
脚本按以下步骤顺序执行:
### 步骤 1:权限检查
检查是否以 root 用户运行。非 root 用户将报错退出。
```
[ERROR] 此脚本需要 root 权限运行,请使用 sudo bash main.sh
```
### 步骤 2:依赖检查与安装
自动检测以下工具是否已安装,缺失时自动安装:
| 工具 | 用途 | 对应软件包 |
|------|------|-----------|
| `growpart` | 扩展磁盘分区 | `cloud-guest-utils`(Debian/Ubuntu)<br>`cloud-utils-growpart`(CentOS/RHEL) |
| `xfs_growfs` | 扩展 XFS 文件系统 | `xfsprogs` |
| `lsblk` | 查看磁盘信息 | `util-linux` |
### 步骤 3:获取根分区信息
通过 `findmnt` 获取根目录所在设备和文件系统类型。
### 步骤 4:LVM 检测
通过多重检测机制判断是否使用 LVM:
- 检查设备路径是否为 `/dev/mapper/*` 或 `/dev/dm-*`
- 通过 `lvs` 命令验证是否为 LVM 逻辑卷
- 通过 `lsblk` 的 TYPE 字段二次确认
### 步骤 5:打印当前磁盘状态
输出磁盘分区表、文件系统使用情况、LVM 信息(如适用),便于对比扩容前后变化。
### 步骤 6:检查可扩容空间
- **LVM 模式**:检查 VG 剩余空间;若不足,自动尝试扩展 PV
- **标准分区模式**:检查分区后是否有未分配扇区
### 步骤 7:执行扩容
根据检测到的架构自动执行对应的扩容命令。
### 步骤 8:验证结果
扩容完成后自动输出磁盘状态,与步骤 5 的输出对比即可确认扩容是否成功。
---
## 7. 各发行版使用示例
### 7.1 Ubuntu 20.04 / 22.04 / 24.04(LVM + ext4)
```bash
# 查看当前状态
df -hT /
# Filesystem Type Size Used Avail Use% Mounted on
# /dev/mapper/ubuntu--vg-ubuntu--lv ext4 20G 3.1G 16G 17% /
# 执行扩容
sudo bash main.sh
# 验证结果
df -hT /
# Filesystem Type Size Used Avail Use% Mounted on
# /dev/mapper/ubuntu--vg-ubuntu--lv ext4 49G 3.1G 44G 7% /
```
### 7.2 Debian 11 / 12(标准分区 + ext4)
```bash
# 查看当前状态
df -hT /
# Filesystem Type Size Used Avail Use% Mounted on
# /dev/vda1 ext4 20G 2.4G 16G 13% /
# 执行扩容
sudo bash main.sh
# 验证结果
df -hT /
# Filesystem Type Size Used Avail Use% Mounted on
# /dev/vda1 ext4 99G 2.4G 92G 3% /
```
### 7.3 CentOS 7 / 8 / Stream(LVM + XFS)
```bash
# 查看当前状态
df -hT /
# Filesystem Type Size Used Avail Use% Mounted on
# /dev/mapper/centos-root xfs 20G 1.8G 19G 9% /
# 先安装依赖(如果无法联网可跳过,脚本会自动处理)
# sudo yum install -y cloud-utils-growpart
# 执行扩容
sudo bash main.sh
# 验证结果
df -hT /
# Filesystem Type Size Used Avail Use% Mounted on
# /dev/mapper/centos-root xfs 49G 1.8G 48G 4% /
```
### 7.4 AlmaLinux / Rocky Linux(LVM + XFS)
与 CentOS 使用方式完全一致:
```bash
sudo bash main.sh
```
### 7.5 openSUSE / SLES
```bash
sudo bash main.sh
```
脚本会自动使用 `zypper` 安装依赖。
---
## 8. LVM 架构详解
### 8.1 识别 LVM 架构
以下特征表明服务器使用了 LVM:
- 根设备路径包含 `/dev/mapper/`,如 `/dev/mapper/ubuntu--vg-ubuntu--lv`
- `lsblk` 中 TYPE 列显示 `lvm`
- `df` 输出中的 Filesystem 为 mapper 设备
### 8.2 LVM 扩容流程
```
磁盘空间不足时的完整扩容链路:
1. growpart /dev/sda 3 ← 扩展 sda3 分区占满磁盘
2. pvresize /dev/sda3 ← 扩展物理卷
3. lvextend -l +100%FREE ← 扩展逻辑卷
4. resize2fs / xfs_growfs ← 扩展文件系统
```
### 8.3 LVM 术语说明
| 术语 | 缩写 | 说明 |
|------|------|------|
| 物理卷 | PV | 实际的磁盘分区或整块磁盘 |
| 卷组 | VG | 由一个或多个 PV 组成的存储池 |
| 逻辑卷 | LV | 从 VG 中划分出来的"虚拟分区",格式化后挂载使用 |
---
## 9. 标准分区架构详解
### 9.1 识别标准分区
以下特征表明根目录使用标准分区(非 LVM):
- 根设备路径为 `/dev/sda1`、`/dev/vda1`、`/dev/nvme0n1p1` 等
- `lsblk` 中 TYPE 列显示 `part`
### 9.2 标准分区扩容流程
```
1. growpart /dev/sda 1 ← 扩展 sda1 分区占满磁盘
2. resize2fs /dev/sda1 ← 扩展 ext4 文件系统
或 xfs_growfs / ← 扩展 XFS 文件系统
```
---
## 10. 日志说明
### 10.1 日志位置
```
/var/log/disk_auto_expand.log
```
### 10.2 日志内容
日志包含以下信息:
- 时间戳
- 日志级别(INFO / WARN / ERROR)
- 磁盘环境检测结果
- LVM 信息(PV / VG / LV)
- 扩容前后磁盘状态
- 执行命令的输出
### 10.3 查看日志
```bash
# 查看完整日志
cat /var/log/disk_auto_expand.log
# 查看最近的日志
tail -50 /var/log/disk_auto_expand.log
# 搜索错误信息
grep "ERROR" /var/log/disk_auto_expand.log
```
---
## 11. 故障排查
### 11.1 常见错误与解决方案
**错误:`此脚本需要 root 权限运行`**
原因:未使用 sudo 运行。
解决:
```bash
sudo bash main.sh
```
**错误:`磁盘没有足够的未分配空间进行扩容`**
原因:磁盘分区已占满全部空间,没有剩余空间可供分配。
解决:
1. 如果是云服务器,先在控制台扩大磁盘容量
2. 确认磁盘扩容后,重新运行脚本
**错误:`卷组没有足够的可用空间进行扩容`**
原因:LVM 场景下,VG 中可用空间不足 100MB。
解决:
1. 检查 PV 是否已占满磁盘:`pvs` 和 `lsblk` 对比
2. 如果 PV 未占满磁盘,脚本会自动尝试扩展
3. 如果 PV 已占满但 VG 仍无空间,说明空间已被其他 LV 占用
**错误:`无法识别包管理器,请手动安装`**
原因:使用了非主流发行版,脚本无法自动安装依赖。
解决:手动安装所需工具:
```bash
# Debian/Ubuntu
apt-get install -y cloud-guest-utils xfsprogs
# CentOS/RHEL
yum install -y cloud-utils-growpart xfsprogs
```
**警告:`growpart 执行失败,可能分区已是最大`**
说明:分区已经占满磁盘,这通常是正常情况。脚本会继续尝试后续步骤。
### 11.2 手动检查扩容是否成功
```bash
# 查看根目录大小
df -hT /
# 查看分区表
lsblk
# 查看 LVM 状态(如使用 LVM)
sudo pvs
sudo vgs
sudo lvs
```
---
## 12. 注意事项
### 12.1 安全建议
1. **首次使用请先 `--dry-run`** — 确认扩容计划无误后再正式执行
2. **重要数据请提前备份** — 虽然扩容操作是无损的,但生产环境建议做好备份
3. **检查日志** — 扩容完成后检查日志确认没有错误
### 12.2 限制说明
1. **仅支持扩容,不支持缩容** — 脚本仅将空间分配给根目录,不涉及缩小操作
2. **仅扩容根目录** — 脚本只处理 `/` 根目录的扩容,不涉及其他挂载点
3. **需要网络** — 首次运行可能需要联网安装依赖工具
4. **不支持加密分区** — 如果根分区使用了 LUKS 加密,脚本暂不支持
### 12.3 云平台扩容前置步骤
在运行脚本之前,需要先在云平台控制台扩大磁盘容量:
| 云平台 | 操作路径 |
|--------|---------|
| 阿里云 | ECS 控制台 → 实例 → 云盘 → 扩容 |
| 腾讯云 | CVM 控制台 → 实例 → 云硬盘 → 扩容 |
| 华为云 | ECS 控制台 → 云硬盘 → 扩容 |
| AWS | EC2 → Volumes → Modify Volume |
| Azure | Virtual Machines → Disks → Resize |
磁盘扩容完成后,运行本脚本即可完成操作系统层面的扩容。
+570
View File
@@ -0,0 +1,570 @@
#!/usr/bin/env bash
#===============================================================================
# Linux 根目录自动扩容脚本
# 适配: Debian / Ubuntu / CentOS / RHEL / Fedora / AlmaLinux / Rocky 等
# 功能: 自动识别 LVM / 非 LVM 架构,将磁盘未分配空间全部分配给根目录
# 用法: sudo bash main.sh
#===============================================================================
set -euo pipefail
#---------------------------------------
# 全局变量
#---------------------------------------
LOG_FILE="/var/log/disk_auto_expand.log"
ROOT_MOUNT="/"
LVM_DETECTED=false
DRY_RUN=false
#---------------------------------------
# 日志与输出
#---------------------------------------
log() {
local msg="[$(date '+%Y-%m-%d %H:%M:%S')] $*"
echo "$msg" | tee -a "$LOG_FILE"
}
info() { log "[INFO] $*"; }
warn() { log "[WARN] $*"; }
error() { log "[ERROR] $*"; }
die() { error "$@"; exit 1; }
#---------------------------------------
# 权限检查
#---------------------------------------
check_root() {
if [[ $EUID -ne 0 ]]; then
die "此脚本需要 root 权限运行,请使用 sudo bash $0"
fi
}
#---------------------------------------
# 参数解析
#---------------------------------------
parse_args() {
while [[ $# -gt 0 ]]; do
case "$1" in
--dry-run)
DRY_RUN=true
info "当前为模拟运行模式,不会实际执行扩容操作"
shift
;;
-h|--help)
echo "用法: sudo bash $0 [--dry-run]"
echo " --dry-run 模拟运行,不实际执行扩容"
exit 0
;;
*)
die "未知参数: $1,使用 -h 查看帮助"
;;
esac
done
}
#---------------------------------------
# 依赖检查与安装
#---------------------------------------
detect_pkg_manager() {
if command -v apt-get &>/dev/null; then
PKG_MANAGER="apt"
elif command -v yum &>/dev/null; then
PKG_MANAGER="yum"
elif command -v dnf &>/dev/null; then
PKG_MANAGER="dnf"
elif command -v zypper &>/dev/null; then
PKG_MANAGER="zypper"
else
PKG_MANAGER="unknown"
fi
}
install_package() {
local pkg="$1"
if [[ $DRY_RUN == true ]]; then
info "[模拟] 将安装包: $pkg"
return
fi
info "正在安装依赖包: $pkg"
case "$PKG_MANAGER" in
apt)
apt-get update -qq && apt-get install -y -qq "$pkg" >/dev/null 2>&1
;;
yum)
yum install -y -q "$pkg" >/dev/null 2>&1
;;
dnf)
dnf install -y -q "$pkg" >/dev/null 2>&1
;;
zypper)
zypper install -y "$pkg" >/dev/null 2>&1
;;
*)
die "无法识别包管理器,请手动安装: $pkg"
;;
esac
}
ensure_dependencies() {
detect_pkg_manager
info "检测到包管理器: $PKG_MANAGER"
# 检查 growpart(用于非 LVM 分区扩展)
if ! command -v growpart &>/dev/null; then
info "growpart 未安装,正在安装 cloud-guest-utils / cloud-utils-growpart..."
# 不同发行版包名不同,依次尝试
if [[ "$PKG_MANAGER" == "apt" ]]; then
install_package "cloud-guest-utils"
else
# CentOS/RHEL 系
if ! install_package "cloud-utils-growpart" 2>/dev/null; then
install_package "cloud-init" || warn "无法安装 growpart,非 LVM 模式可能不可用"
fi
fi
fi
# 检查 xfs_growfs(XFS 文件系统需要)
if ! command -v xfs_growfs &>/dev/null; then
if [[ "$PKG_MANAGER" == "apt" ]]; then
install_package "xfsprogs"
else
install_package "xfsprogs"
fi
fi
# 检查 lsblk
if ! command -v lsblk &>/dev/null; then
install_package "util-linux"
fi
}
#---------------------------------------
# 获取根分区信息
#---------------------------------------
get_root_info() {
# 获取根目录所在的设备(如 /dev/sda1, /dev/mapper/xxx)
ROOT_DEVICE=$(findmnt -n -o SOURCE "$ROOT_MOUNT")
if [[ -z "$ROOT_DEVICE" ]]; then
die "无法获取根目录所在设备"
fi
info "根目录所在设备: $ROOT_DEVICE"
# 获取文件系统类型
ROOT_FS=$(findmnt -n -o FSTYPE "$ROOT_MOUNT")
info "根目录文件系统: $ROOT_FS"
if [[ "$ROOT_FS" != "ext4" && "$ROOT_FS" != "ext3" && "$ROOT_FS" != "xfs" && "$ROOT_FS" != "btrfs" ]]; then
warn "文件系统类型 $ROOT_FS 可能不支持在线扩容,脚本将继续尝试"
fi
}
#---------------------------------------
# 检测 LVM
#---------------------------------------
detect_lvm() {
# 判断根设备是否为 LVM 逻辑卷
if [[ "$ROOT_DEVICE" == /dev/mapper/* ]] || [[ "$ROOT_DEVICE" == /dev/dm-* ]]; then
# 进一步确认是 LVM 而非其他映射设备
if lvs "$ROOT_DEVICE" &>/dev/null; then
LVM_DETECTED=true
info "检测到 LVM 架构"
fi
fi
# 二次检测:通过 lsblk 查看 TYPE
if [[ "$LVM_DETECTED" == false ]]; then
local root_type
root_type=$(lsblk -no TYPE "$ROOT_DEVICE" 2>/dev/null | head -1)
if [[ "$root_type" == "lvm" ]]; then
LVM_DETECTED=true
info "检测到 LVM 架构(通过 lsblk)"
fi
fi
if [[ "$LVM_DETECTED" == false ]]; then
info "未检测到 LVM,将使用标准分区扩容方式"
fi
}
#---------------------------------------
# 获取磁盘和分区信息
#---------------------------------------
get_disk_for_partition() {
local part="$1"
# 通过 lsblk 获取父级磁盘设备
local disk
disk=$(lsblk -no PKNAME "$part" 2>/dev/null | head -1)
if [[ -z "$disk" ]]; then
die "无法获取 $part 的父级磁盘设备"
fi
echo "/dev/$disk"
}
#---------------------------------------
# 获取分区号
#---------------------------------------
get_partition_number() {
local part="$1"
# 从设备名提取分区号,如 /dev/sda1 -> 1, /dev/vda2 -> 2
local num
num=$(echo "$part" | grep -oP '\d+$')
if [[ -z "$num" ]]; then
# 尝试 p 分隔格式,如 /dev/mmcblk0p1 -> 1
num=$(echo "$part" | grep -oP 'p\K\d+$')
fi
if [[ -z "$num" ]]; then
die "无法从 $part 提取分区号"
fi
echo "$num"
}
#---------------------------------------
# 打印当前磁盘使用情况
#---------------------------------------
print_disk_status() {
info "========== 当前磁盘状态 =========="
echo ""
info "--- 磁盘分区表 ---"
lsblk -o NAME,SIZE,TYPE,FSTYPE,MOUNTPOINT 2>/dev/null | tee -a "$LOG_FILE"
echo ""
info "--- 根目录文件系统 ---"
df -hT "$ROOT_MOUNT" 2>/dev/null | tee -a "$LOG_FILE"
echo ""
if [[ "$LVM_DETECTED" == true ]]; then
info "--- LVM 信息 ---"
info "物理卷 (PV):"
pvs 2>/dev/null | tee -a "$LOG_FILE"
info "卷组 (VG):"
vgs 2>/dev/null | tee -a "$LOG_FILE"
info "逻辑卷 (LV):"
lvs 2>/dev/null | tee -a "$LOG_FILE"
echo ""
fi
info "===================================="
}
#---------------------------------------
# 检查是否有可扩容的空间
#---------------------------------------
check_expandable_space() {
if [[ "$LVM_DETECTED" == true ]]; then
check_lvm_space
else
check_partition_space
fi
}
#---------------------------------------
# LVM 模式:检查 VG 剩余空间
#---------------------------------------
check_lvm_space() {
# 获取根设备所在的 VG
ROOT_VG=$(lvs -no vg_name "$ROOT_DEVICE" 2>/dev/null | xargs)
if [[ -z "$ROOT_VG" ]]; then
die "无法获取根逻辑卷所在的卷组"
fi
info "根目录所在卷组 (VG): $ROOT_VG"
# 获取 VG 的可用空间(单位:字节)
VG_FREE_BYTES=$(vgs -no vg_free "$ROOT_VG" --units b 2>/dev/null | xargs)
# 转换为人类可读格式
VG_FREE_HUMAN=$(vgs -no vg_free "$ROOT_VG" --units g 2>/dev/null | xargs)
info "卷组 $ROOT_VG 可用空间: ${VG_FREE_HUMAN}G"
# 检查 VG 是否有剩余空间
VG_FREE_NUM=$(vgs -no vg_free "$ROOT_VG" --units m 2>/dev/null | xargs | grep -oP '^\d+')
if [[ -z "$VG_FREE_NUM" ]] || [[ "$VG_FREE_NUM" -lt 100 ]]; then
# VG 没有空间,检查是否是因为 PV 没有占满磁盘
info "卷组可用空间不足,将检查是否需要扩展物理卷 (PV)..."
expand_pv_if_needed
# 重新获取可用空间
VG_FREE_NUM=$(vgs -no vg_free "$ROOT_VG" --units m 2>/dev/null | xargs | grep -oP '^\d+')
VG_FREE_HUMAN=$(vgs -no vg_free "$ROOT_VG" --units g 2>/dev/null | xargs)
if [[ -z "$VG_FREE_NUM" ]] || [[ "$VG_FREE_NUM" -lt 100 ]]; then
die "卷组 $ROOT_VG 没有足够的可用空间进行扩容(需要至少 100MB)"
fi
fi
info "可用于扩容的空间: ${VG_FREE_HUMAN}G"
}
#---------------------------------------
# 扩展 PV 使其占满磁盘
#---------------------------------------
expand_pv_if_needed() {
# 获取 VG 对应的 PV
local pv_dev
pv_dev=$(pvs -no pv_name "$ROOT_VG" 2>/dev/null | xargs)
if [[ -z "$pv_dev" ]]; then
die "无法获取卷组 $ROOT_VG 的物理卷"
fi
info "当前物理卷 (PV): $pv_dev"
# 判断 PV 是直接磁盘还是分区
local pv_is_partition=false
local parent_disk
# 如果 PV 是分区(如 /dev/sda2),需要扩展分区
if [[ "$pv_dev" =~ [0-9]+$ ]] || [[ "$pv_dev" =~ p[0-9]+$ ]]; then
pv_is_partition=true
parent_disk=$(get_disk_for_partition "$pv_dev")
info "PV 是分区 $pv_dev,父级磁盘: $parent_disk"
else
parent_disk="$pv_dev"
info "PV 直接使用整块磁盘: $pv_dev"
fi
# 检查磁盘是否有未分配空间
local disk_size sector_size total_sectors part_end_sector
disk_size=$(blockdev --getsize64 "$parent_disk" 2>/dev/null)
sector_size=$(blockdev --getss "$parent_disk" 2>/dev/null)
total_sectors=$((disk_size / sector_size))
if [[ "$pv_is_partition" == true ]]; then
local part_num
part_num=$(get_partition_number "$pv_dev")
# 获取分区结束扇区
part_end_sector=$(parted -s "$parent_disk" unit s print 2>/dev/null | \
awk -v num="$part_num" '$1 == num { gsub(/s/,"",$3); print $3 }')
if [[ -z "$part_end_sector" ]]; then
warn "无法获取分区结束扇区,尝试直接使用 pvresize..."
if [[ "$DRY_RUN" == false ]]; then
pvresize "$pv_dev" 2>/dev/null || true
fi
return
fi
# 检查分区后面是否有空间(至少 2048 扇区 = 1MB)
local remaining_sectors=$((total_sectors - part_end_sector - 1))
info "磁盘总扇区: $total_sectors, 分区结束扇区: $part_end_sector, 剩余扇区: $remaining_sectors"
if [[ $remaining_sectors -gt 2048 ]]; then
info "检测到磁盘有未分配空间,正在扩展分区 $pv_dev..."
if [[ "$DRY_RUN" == false ]]; then
# 使用 growpart 扩展分区
local disk_base part_no
disk_base=$(basename "$parent_disk")
part_no="$part_num"
growpart "$parent_disk" "$part_no" || warn "growpart 执行失败,可能分区已是最大"
# 扩展 PV
pvresize "$pv_dev" || warn "pvresize 执行失败"
else
info "[模拟] 将扩展分区 $pv_dev 并执行 pvresize"
fi
else
info "分区已占满磁盘,无需扩展分区"
fi
else
# PV 直接使用整块磁盘,直接 pvresize
info "PV 使用整块磁盘,执行 pvresize..."
if [[ "$DRY_RUN" == false ]]; then
pvresize "$pv_dev" || warn "pvresize 执行失败"
fi
fi
}
#---------------------------------------
# 非 LVM 模式:检查分区后是否有空间
#---------------------------------------
check_partition_space() {
ROOT_DISK=$(get_disk_for_partition "$ROOT_DEVICE")
info "根目录所在磁盘: $ROOT_DISK"
local part_num
part_num=$(get_partition_number "$ROOT_DEVICE")
info "根分区: $ROOT_DEVICE (分区号: $part_num)"
# 获取磁盘和分区的扇区信息
local disk_size sector_size total_sectors
disk_size=$(blockdev --getsize64 "$ROOT_DISK" 2>/dev/null)
sector_size=$(blockdev --getss "$ROOT_DISK" 2>/dev/null)
total_sectors=$((disk_size / sector_size))
# 获取分区结束扇区
local part_end_sector
part_end_sector=$(parted -s "$ROOT_DISK" unit s print 2>/dev/null | \
awk -v num="$part_num" '$1 == num { gsub(/s/,"",$3); print $3 }')
if [[ -z "$part_end_sector" ]]; then
die "无法获取分区 $ROOT_DEVICE 的结束扇区"
fi
local remaining_sectors=$((total_sectors - part_end_sector - 1))
local remaining_mb=$((remaining_sectors * sector_size / 1024 / 1024))
info "磁盘总大小: $((disk_size / 1024 / 1024 / 1024))GB"
info "分区后剩余扇区: $remaining_sectors (约 ${remaining_mb}MB)"
if [[ $remaining_sectors -le 2048 ]]; then
die "磁盘没有足够的未分配空间进行扩容"
fi
info "检测到约 ${remaining_mb}MB 未分配空间,可以进行扩容"
}
#---------------------------------------
# 执行扩容
#---------------------------------------
do_expand() {
if [[ "$LVM_DETECTED" == true ]]; then
do_expand_lvm
else
do_expand_partition
fi
}
#---------------------------------------
# LVM 模式扩容
#---------------------------------------
do_expand_lvm() {
info "========== 开始 LVM 模式扩容 =========="
# 获取 LV 路径
local lv_path="$ROOT_DEVICE"
info "目标逻辑卷: $lv_path"
if [[ "$DRY_RUN" == false ]]; then
# 扩展 LV 使用 VG 中所有剩余空间
info "正在扩展逻辑卷 $lv_path 使用全部可用空间..."
lvextend -l +100%FREE "$lv_path" 2>&1 | tee -a "$LOG_FILE"
# 扩展文件系统
resize_filesystem "$ROOT_DEVICE" "$ROOT_FS"
info "LVM 模式扩容完成!"
else
info "[模拟] 将执行: lvextend -l +100%FREE $lv_path"
info "[模拟] 将执行文件系统扩容: $ROOT_FS on $ROOT_DEVICE"
fi
}
#---------------------------------------
# 非 LVM 模式扩容
#---------------------------------------
do_expand_partition() {
info "========== 开始标准分区模式扩容 =========="
local part_num
part_num=$(get_partition_number "$ROOT_DEVICE")
if [[ "$DRY_RUN" == false ]]; then
# 第一步:扩展分区
info "正在扩展分区 $ROOT_DEVICE..."
growpart "$ROOT_DISK" "$part_num" 2>&1 | tee -a "$LOG_FILE"
# 第二步:扩展文件系统
resize_filesystem "$ROOT_DEVICE" "$ROOT_FS"
info "标准分区模式扩容完成!"
else
info "[模拟] 将执行: growpart $ROOT_DISK $part_num"
info "[模拟] 将执行文件系统扩容: $ROOT_FS on $ROOT_DEVICE"
fi
}
#---------------------------------------
# 扩展文件系统
#---------------------------------------
resize_filesystem() {
local device="$1"
local fs_type="$2"
info "正在扩展文件系统 (类型: $fs_type, 设备: $device)..."
case "$fs_type" in
ext2|ext3|ext4)
if [[ "$DRY_RUN" == false ]]; then
resize2fs "$device" 2>&1 | tee -a "$LOG_FILE"
else
info "[模拟] 将执行: resize2fs $device"
fi
;;
xfs)
if [[ "$DRY_RUN" == false ]]; then
xfs_growfs "$ROOT_MOUNT" 2>&1 | tee -a "$LOG_FILE"
else
info "[模拟] 将执行: xfs_growfs $ROOT_MOUNT"
fi
;;
btrfs)
if [[ "$DRY_RUN" == false ]]; then
btrfs filesystem resize max "$ROOT_MOUNT" 2>&1 | tee -a "$LOG_FILE"
else
info "[模拟] 将执行: btrfs filesystem resize max $ROOT_MOUNT"
fi
;;
*)
warn "不支持的文件系统类型: $fs_type,请手动扩展"
return 1
;;
esac
}
#---------------------------------------
# 验证扩容结果
#---------------------------------------
verify_result() {
info "========== 扩容后磁盘状态 =========="
echo ""
info "--- 根目录使用情况 ---"
df -hT "$ROOT_MOUNT" 2>/dev/null | tee -a "$LOG_FILE"
echo ""
info "--- 磁盘分区表 ---"
lsblk -o NAME,SIZE,TYPE,FSTYPE,MOUNTPOINT 2>/dev/null | tee -a "$LOG_FILE"
echo ""
if [[ "$LVM_DETECTED" == true ]]; then
info "--- LVM 信息 ---"
pvs 2>/dev/null | tee -a "$LOG_FILE"
vgs 2>/dev/null | tee -a "$LOG_FILE"
lvs 2>/dev/null | tee -a "$LOG_FILE"
echo ""
fi
info "===================================="
}
#---------------------------------------
# 主函数
#---------------------------------------
main() {
parse_args "$@"
echo ""
echo "========================================================"
echo " Linux 根目录自动扩容脚本"
echo " 适配 Debian / Ubuntu / CentOS 等"
echo "========================================================"
echo ""
# 1. 权限检查
check_root
# 2. 安装依赖
info "检查并安装必要依赖..."
ensure_dependencies
# 3. 获取根分区信息
info "获取根分区信息..."
get_root_info
# 4. 检测 LVM
detect_lvm
# 5. 打印当前状态
print_disk_status
# 6. 检查可扩容空间
info "检查可扩容空间..."
check_expandable_space
# 7. 执行扩容
do_expand
# 8. 验证结果
verify_result
info "根目录自动扩容完成!"
info "日志文件: $LOG_FILE"
echo ""
}
main "$@"