Compare commits
1
Commits
main
..
0f1c571d05
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
0f1c571d05 |
No files matched your search
-26
@@ -1,26 +0,0 @@
|
||||
# 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?
|
||||
/debug/
|
||||
./LinuxDiskAutoExpansion.iml
|
||||
@@ -1,8 +0,0 @@
|
||||
<?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>
|
||||
@@ -1,218 +1,3 @@
|
||||
# Linux 根目录自动扩容脚本
|
||||
# LinuxDiskAutoExpansion
|
||||
|
||||
一键自动将 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://git.opensource-studio.info/database-mysql/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 # 主脚本
|
||||
├── debug/
|
||||
│ └── logs/ # 调试日志
|
||||
│ ├── ubuntu.log
|
||||
│ └── centos.log
|
||||
├── README.md # 项目说明文档
|
||||
├── USAGE.md # 详细使用文档
|
||||
└── LinuxDiskAutoExpansion.iml
|
||||
```
|
||||
|
||||
## 许可证
|
||||
|
||||
Apache 2.0 License
|
||||
本脚本用于自动将 Linux 服务器根目录 (`/`) 扩容至磁盘最大可用空间。脚本会自动识别服务器的磁盘架构(LVM 或标准分区),并根据实际架构选择对应的扩容策略,全程无需人工干预。
|
||||
@@ -1,494 +0,0 @@
|
||||
# 使用文档 — 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://git.opensource-studio.info/database-mysql/LinuxDiskAutoExpansion.git
|
||||
cd LinuxDiskAutoExpansion
|
||||
```
|
||||
|
||||
**方式二:直接上传**
|
||||
|
||||
将 `src/main.sh` 上传到服务器任意目录。
|
||||
|
||||
```bash
|
||||
# 使用 scp 上传
|
||||
scp src/main.sh user@server:/tmp/
|
||||
```
|
||||
|
||||
**方式三:直接下载**
|
||||
|
||||
```bash
|
||||
wget -O /tmp/main.sh https://git.opensource-studio.info/database-mysql/LinuxDiskAutoExpansion/src/commit/8bce42bc846f6404052c64a6511a29e1b1bc7d95/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 |
|
||||
|
||||
磁盘扩容完成后,运行本脚本即可完成操作系统层面的扩容。
|
||||
-681
@@ -1,681 +0,0 @@
|
||||
#!/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
|
||||
|
||||
# 检查 parted 或 sfdisk(用于获取分区信息)
|
||||
if ! command -v parted &>/dev/null && ! command -v sfdisk &>/dev/null; then
|
||||
info "parted/sfdisk 未安装,正在安装 parted..."
|
||||
install_package "parted"
|
||||
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) || true
|
||||
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) || true
|
||||
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+$') || true
|
||||
if [[ -z "$num" ]]; then
|
||||
# 尝试 p 分隔格式,如 /dev/mmcblk0p1 -> 1
|
||||
num=$(echo "$part" | grep -oP 'p\K\d+$') || true
|
||||
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 || true; } | tee -a "$LOG_FILE"
|
||||
info "卷组 (VG):"
|
||||
{ vgs 2>/dev/null || true; } | tee -a "$LOG_FILE"
|
||||
info "逻辑卷 (LV):"
|
||||
{ lvs 2>/dev/null || true; } | tee -a "$LOG_FILE"
|
||||
echo ""
|
||||
fi
|
||||
info "===================================="
|
||||
}
|
||||
|
||||
#---------------------------------------
|
||||
# 检查是否有可扩容的空间
|
||||
#---------------------------------------
|
||||
check_expandable_space() {
|
||||
if [[ "$LVM_DETECTED" == true ]]; then
|
||||
check_lvm_space
|
||||
else
|
||||
check_partition_space
|
||||
fi
|
||||
}
|
||||
|
||||
#---------------------------------------
|
||||
# 安全提取数值(去除小数/前缀/后缀,兼容 set -e)
|
||||
#---------------------------------------
|
||||
parse_int() {
|
||||
# 从字符串中提取整数部分
|
||||
# 输入: "8217.60" / "<19.00" / "8.22g" / " 100 " / "0" 等
|
||||
# 输出: 纯整数字符串,如 "8217" / "19" / "8" / "100" / "0"
|
||||
local val="$1"
|
||||
# 去除首尾空格、< > 前缀、非数字字符(保留小数点)
|
||||
val=$(echo "$val" | tr -d '<>' | sed 's/[^0-9.]//g')
|
||||
if [[ -z "$val" ]] || [[ "$val" == "." ]]; then
|
||||
echo "0"
|
||||
return
|
||||
fi
|
||||
# 用 awk 取整数部分
|
||||
echo "$val" | awk '{printf "%d", $1+0}'
|
||||
}
|
||||
|
||||
#---------------------------------------
|
||||
# 从设备路径解析 LVM 的 VG 和 LV 名称
|
||||
#---------------------------------------
|
||||
resolve_lvm_names() {
|
||||
local device="$1"
|
||||
# 尝试多种方法从设备路径获取 VG 名和 LV 路径
|
||||
# 输入: /dev/mapper/cl_centos85-root 或 /dev/dm-0 等
|
||||
# 输出: 设置 ROOT_VG 和 ROOT_LV_PATH 全局变量
|
||||
|
||||
# 方法1: 直接用 lvs 查询(某些系统支持 /dev/mapper/ 路径)
|
||||
ROOT_VG=$(lvs --noheadings -o vg_name "$device" 2>/dev/null | xargs) || true
|
||||
if [[ -n "$ROOT_VG" ]]; then
|
||||
ROOT_LV_PATH="$device"
|
||||
return 0
|
||||
fi
|
||||
|
||||
# 方法2: 使用 dmsetup splitname(最可靠)
|
||||
if command -v dmsetup &>/dev/null && [[ "$device" == /dev/mapper/* ]]; then
|
||||
local dm_name
|
||||
dm_name=$(basename "$device")
|
||||
ROOT_VG=$(dmsetup splitname "$dm_name" -o vg_name --noheadings 2>/dev/null | xargs) || true
|
||||
local lv_name
|
||||
lv_name=$(dmsetup splitname "$dm_name" -o lv_name --noheadings 2>/dev/null | xargs) || true
|
||||
if [[ -n "$ROOT_VG" && -n "$lv_name" ]]; then
|
||||
ROOT_LV_PATH="${ROOT_VG}/${lv_name}"
|
||||
return 0
|
||||
fi
|
||||
fi
|
||||
|
||||
# 方法3: 从 /dev/mapper/ 路径手动解析
|
||||
# 规则: -- 转义为 -,然后第一个 - 分隔 VG 和 LV
|
||||
if [[ "$device" == /dev/mapper/* ]]; then
|
||||
local mapper_name
|
||||
mapper_name=$(basename "$device")
|
||||
# 先将 -- 替换为占位符,再将 - 替换为 /,最后将占位符还原为 -
|
||||
local vg_lv
|
||||
vg_lv=$(echo "$mapper_name" | sed 's/--/\x00/g' | tr '-' '/' | tr '\0' '-')
|
||||
ROOT_VG=$(echo "$vg_lv" | cut -d'/' -f1)
|
||||
local lv_name
|
||||
lv_name=$(echo "$vg_lv" | cut -d'/' -f2)
|
||||
if [[ -n "$ROOT_VG" && -n "$lv_name" ]]; then
|
||||
ROOT_LV_PATH="${ROOT_VG}/${lv_name}"
|
||||
return 0
|
||||
fi
|
||||
fi
|
||||
|
||||
# 方法4: 遍历所有 LV,查找匹配当前挂载设备的
|
||||
local all_lvs
|
||||
all_lvs=$(lvs --noheadings -o lv_path,vg_name,lv_name 2>/dev/null) || true
|
||||
while IFS= read -r line; do
|
||||
local lv_p lv_vg lv_name
|
||||
lv_p=$(echo "$line" | awk '{print $1}')
|
||||
lv_vg=$(echo "$line" | awk '{print $2}')
|
||||
lv_name=$(echo "$line" | awk '{print $3}')
|
||||
# 检查此 LV 是否是根设备
|
||||
if [[ -L "$device" ]]; then
|
||||
local real_dev
|
||||
real_dev=$(readlink -f "$device")
|
||||
local lv_real
|
||||
lv_real=$(readlink -f "$lv_p" 2>/dev/null) || true
|
||||
if [[ "$real_dev" == "$lv_real" ]]; then
|
||||
ROOT_VG="$lv_vg"
|
||||
ROOT_LV_PATH="${lv_vg}/${lv_name}"
|
||||
return 0
|
||||
fi
|
||||
fi
|
||||
done <<< "$all_lvs"
|
||||
|
||||
return 1
|
||||
}
|
||||
|
||||
#---------------------------------------
|
||||
# LVM 模式:检查 VG 剩余空间
|
||||
#---------------------------------------
|
||||
check_lvm_space() {
|
||||
# 解析 VG 和 LV 路径
|
||||
if ! resolve_lvm_names "$ROOT_DEVICE"; then
|
||||
die "无法获取根逻辑卷所在的卷组"
|
||||
fi
|
||||
if [[ -z "$ROOT_VG" ]]; then
|
||||
die "无法获取根逻辑卷所在的卷组"
|
||||
fi
|
||||
info "根目录所在卷组 (VG): $ROOT_VG"
|
||||
info "逻辑卷路径: $ROOT_LV_PATH"
|
||||
|
||||
# 获取 VG 的可用空间(人类可读格式,用于显示)
|
||||
VG_FREE_HUMAN=$(vgs --noheadings -o vg_name,vg_free --units g --nosuffix 2>/dev/null | awk -v vg="$ROOT_VG" '$1==vg{print $2}' | tr -d '<> ') || true
|
||||
info "卷组 $ROOT_VG 可用空间: ${VG_FREE_HUMAN}G"
|
||||
|
||||
# 检查 VG 是否有剩余空间(以 MB 为单位判断)
|
||||
local vg_free_raw
|
||||
vg_free_raw=$(vgs --noheadings -o vg_name,vg_free --units m --nosuffix 2>/dev/null | awk -v vg="$ROOT_VG" '$1==vg{print $2}') || true
|
||||
VG_FREE_NUM=$(parse_int "$vg_free_raw")
|
||||
info "卷组可用空间: ${VG_FREE_NUM}MB"
|
||||
|
||||
if [[ -z "$VG_FREE_NUM" ]] || [[ "$VG_FREE_NUM" -lt 100 ]]; then
|
||||
# VG 没有空间,检查是否是因为 PV 没有占满磁盘
|
||||
info "卷组可用空间不足,将检查是否需要扩展物理卷 (PV)..."
|
||||
expand_pv_if_needed
|
||||
# 重新获取可用空间
|
||||
vg_free_raw=$(vgs --noheadings -o vg_name,vg_free --units m --nosuffix 2>/dev/null | awk -v vg="$ROOT_VG" '$1==vg{print $2}') || true
|
||||
VG_FREE_NUM=$(parse_int "$vg_free_raw")
|
||||
VG_FREE_HUMAN=$(vgs --noheadings -o vg_name,vg_free --units g --nosuffix 2>/dev/null | awk -v vg="$ROOT_VG" '$1==vg{print $2}' | tr -d '<> ') || true
|
||||
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 --noheadings -o pv_name,vg_name 2>/dev/null | awk -v vg="$ROOT_VG" '$2==vg{print $1}' | head -1 | xargs) || true
|
||||
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) || disk_size=0
|
||||
sector_size=$(blockdev --getss "$parent_disk" 2>/dev/null) || sector_size=512
|
||||
total_sectors=$((disk_size / sector_size))
|
||||
|
||||
if [[ "$pv_is_partition" == true ]]; then
|
||||
local part_num
|
||||
part_num=$(get_partition_number "$pv_dev")
|
||||
|
||||
# 获取分区结束扇区(优先使用 parted,备选 sfdisk)
|
||||
if command -v parted &>/dev/null; then
|
||||
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 }')
|
||||
elif command -v sfdisk &>/dev/null; then
|
||||
part_end_sector=$(sfdisk -d "$parent_disk" 2>/dev/null | \
|
||||
awk -F'[=,]' -v num="$part_num" '{for(i=1;i<=NF;i++) if($i ~ /start=/) {split($i,a,"="); if(a[2]+0 > 0) start=a[2]} if($i ~ /size=/) {split($i,b,"="); size=b[2]}} NR==num {print start+size-1}')
|
||||
fi
|
||||
|
||||
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) || disk_size=0
|
||||
sector_size=$(blockdev --getss "$ROOT_DISK" 2>/dev/null) || sector_size=512
|
||||
total_sectors=$((disk_size / sector_size))
|
||||
|
||||
# 获取分区结束扇区(优先使用 parted,备选 sfdisk)
|
||||
local part_end_sector
|
||||
if command -v parted &>/dev/null; then
|
||||
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 }')
|
||||
elif command -v sfdisk &>/dev/null; then
|
||||
part_end_sector=$(sfdisk -d "$ROOT_DISK" 2>/dev/null | \
|
||||
awk -F'[=,]' -v num="$part_num" '{for(i=1;i<=NF;i++) if($i ~ /start=/) {split($i,a,"="); if(a[2]+0 > 0) start=a[2]} if($i ~ /size=/) {split($i,b,"="); size=b[2]}} NR==num {print start+size-1}')
|
||||
fi
|
||||
|
||||
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_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 || true; } | tee -a "$LOG_FILE"
|
||||
{ vgs 2>/dev/null || true; } | tee -a "$LOG_FILE"
|
||||
{ lvs 2>/dev/null || true; } | 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 "$@"
|
||||
Reference in new issue
Block a user