first commit

This commit is contained in:
2026-09-06 12:58:04 +08:00
commit 2477352d6f
10 changed files with 1393 additions and 0 deletions

10
.idea/.gitignore generated vendored Normal file
View File

@@ -0,0 +1,10 @@
# 默认忽略的文件
/shelf/
/workspace.xml
# 已忽略包含查询文件的默认文件夹
/queries/
# Datasource local storage ignored files
/dataSources/
/dataSources.local.xml
# 基于编辑器的 HTTP 客户端请求
/httpRequests/

5
.idea/codeStyles/codeStyleConfig.xml generated Normal file
View File

@@ -0,0 +1,5 @@
<component name="ProjectCodeStyleConfiguration">
<state>
<option name="PREFERRED_PROJECT_CODE_STYLE" value="Default" />
</state>
</component>

6
.idea/misc.xml generated Normal file
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
.idea/modules.xml generated Normal file
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>

6
.idea/vcs.xml generated Normal file
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>

73
LICENSE Normal file
View File

@@ -0,0 +1,73 @@
Apache License
Version 2.0, January 2004
http://www.apache.org/licenses/
TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
1. Definitions.
"License" shall mean the terms and conditions for use, reproduction, and distribution as defined by Sections 1 through 9 of this document.
"Licensor" shall mean the copyright owner or entity authorized by the copyright owner that is granting the License.
"Legal Entity" shall mean the union of the acting entity and all other entities that control, are controlled by, or are under common control with that entity. For the purposes of this definition, "control" means (i) the power, direct or indirect, to cause the direction or management of such entity, whether by contract or otherwise, or (ii) ownership of fifty percent (50%) or more of the outstanding shares, or (iii) beneficial ownership of such entity.
"You" (or "Your") shall mean an individual or Legal Entity exercising permissions granted by this License.
"Source" form shall mean the preferred form for making modifications, including but not limited to software source code, documentation source, and configuration files.
"Object" form shall mean any form resulting from mechanical transformation or translation of a Source form, including but not limited to compiled object code, generated documentation, and conversions to other media types.
"Work" shall mean the work of authorship, whether in Source or Object form, made available under the License, as indicated by a copyright notice that is included in or attached to the work (an example is provided in the Appendix below).
"Derivative Works" shall mean any work, whether in Source or Object form, that is based on (or derived from) the Work and for which the editorial revisions, annotations, elaborations, or other modifications represent, as a whole, an original work of authorship. For the purposes of this License, Derivative Works shall not include works that remain separable from, or merely link (or bind by name) to the interfaces of, the Work and Derivative Works thereof.
"Contribution" shall mean any work of authorship, including the original version of the Work and any modifications or additions to that Work or Derivative Works thereof, that is intentionally submitted to Licensor for inclusion in the Work by the copyright owner or by an individual or Legal Entity authorized to submit on behalf of the copyright owner. For the purposes of this definition, "submitted" means any form of electronic, verbal, or written communication sent to the Licensor or its representatives, including but not limited to communication on electronic mailing lists, source code control systems, and issue tracking systems that are managed by, or on behalf of, the Licensor for the purpose of discussing and improving the Work, but excluding communication that is conspicuously marked or otherwise designated in writing by the copyright owner as "Not a Contribution."
"Contributor" shall mean Licensor and any individual or Legal Entity on behalf of whom a Contribution has been received by Licensor and subsequently incorporated within the Work.
2. Grant of Copyright License. Subject to the terms and conditions of this License, each Contributor hereby grants to You a perpetual, worldwide, non-exclusive, no-charge, royalty-free, irrevocable copyright license to reproduce, prepare Derivative Works of, publicly display, publicly perform, sublicense, and distribute the Work and such Derivative Works in Source or Object form.
3. Grant of Patent License. Subject to the terms and conditions of this License, each Contributor hereby grants to You a perpetual, worldwide, non-exclusive, no-charge, royalty-free, irrevocable (except as stated in this section) patent license to make, have made, use, offer to sell, sell, import, and otherwise transfer the Work, where such license applies only to those patent claims licensable by such Contributor that are necessarily infringed by their Contribution(s) alone or by combination of their Contribution(s) with the Work to which such Contribution(s) was submitted. If You institute patent litigation against any entity (including a cross-claim or counterclaim in a lawsuit) alleging that the Work or a Contribution incorporated within the Work constitutes direct or contributory patent infringement, then any patent licenses granted to You under this License for that Work shall terminate as of the date such litigation is filed.
4. Redistribution. You may reproduce and distribute copies of the Work or Derivative Works thereof in any medium, with or without modifications, and in Source or Object form, provided that You meet the following conditions:
(a) You must give any other recipients of the Work or Derivative Works a copy of this License; and
(b) You must cause any modified files to carry prominent notices stating that You changed the files; and
(c) You must retain, in the Source form of any Derivative Works that You distribute, all copyright, patent, trademark, and attribution notices from the Source form of the Work, excluding those notices that do not pertain to any part of the Derivative Works; and
(d) If the Work includes a "NOTICE" text file as part of its distribution, then any Derivative Works that You distribute must include a readable copy of the attribution notices contained within such NOTICE file, excluding those notices that do not pertain to any part of the Derivative Works, in at least one of the following places: within a NOTICE text file distributed as part of the Derivative Works; within the Source form or documentation, if provided along with the Derivative Works; or, within a display generated by the Derivative Works, if and wherever such third-party notices normally appear. The contents of the NOTICE file are for informational purposes only and do not modify the License. You may add Your own attribution notices within Derivative Works that You distribute, alongside or as an addendum to the NOTICE text from the Work, provided that such additional attribution notices cannot be construed as modifying the License.
You may add Your own copyright statement to Your modifications and may provide additional or different license terms and conditions for use, reproduction, or distribution of Your modifications, or for any such Derivative Works as a whole, provided Your use, reproduction, and distribution of the Work otherwise complies with the conditions stated in this License.
5. Submission of Contributions. Unless You explicitly state otherwise, any Contribution intentionally submitted for inclusion in the Work by You to the Licensor shall be under the terms and conditions of this License, without any additional terms or conditions. Notwithstanding the above, nothing herein shall supersede or modify the terms of any separate license agreement you may have executed with Licensor regarding such Contributions.
6. Trademarks. This License does not grant permission to use the trade names, trademarks, service marks, or product names of the Licensor, except as required for reasonable and customary use in describing the origin of the Work and reproducing the content of the NOTICE file.
7. Disclaimer of Warranty. Unless required by applicable law or agreed to in writing, Licensor provides the Work (and each Contributor provides its Contributions) on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied, including, without limitation, any warranties or conditions of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A PARTICULAR PURPOSE. You are solely responsible for determining the appropriateness of using or redistributing the Work and assume any risks associated with Your exercise of permissions under this License.
8. Limitation of Liability. In no event and under no legal theory, whether in tort (including negligence), contract, or otherwise, unless required by applicable law (such as deliberate and grossly negligent acts) or agreed to in writing, shall any Contributor be liable to You for damages, including any direct, indirect, special, incidental, or consequential damages of any character arising as a result of this License or out of the use or inability to use the Work (including but not limited to damages for loss of goodwill, work stoppage, computer failure or malfunction, or any and all other commercial damages or losses), even if such Contributor has been advised of the possibility of such damages.
9. Accepting Warranty or Additional Liability. While redistributing the Work or Derivative Works thereof, You may choose to offer, and charge a fee for, acceptance of support, warranty, indemnity, or other liability obligations and/or rights consistent with this License. However, in accepting such obligations, You may act only on Your own behalf and on Your sole responsibility, not on behalf of any other Contributor, and only if You agree to indemnify, defend, and hold each Contributor harmless for any liability incurred by, or claims asserted against, such Contributor by reason of your accepting any such warranty or additional liability.
END OF TERMS AND CONDITIONS
APPENDIX: How to apply the Apache License to your work.
To apply the Apache License to your work, attach the following boilerplate notice, with the fields enclosed by brackets "[]" replaced with your own identifying information. (Don't include the brackets!) The text should be enclosed in the appropriate comment syntax for the file format. We also recommend that a file or class name and description of purpose be included on the same "printed page" as the copyright notice for easier identification within third-party archives.
Copyright 2026 database-mysql
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.

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>

213
README.md Normal file
View File

@@ -0,0 +1,213 @@
# Linux 根目录自动扩容脚本
一键自动将 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`,便于审计追溯
- **无人值守** — 全自动执行,适用于云服务器初始化、批量运维等场景
## 系统要求
| 项目 | 要求 |
|------|------|
| 操作系统 | LinuxDebian / 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% /
```
### 示例 2LVM 架构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% /
```
### 示例 3CentOS 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
USAGE.md Normal file
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` 只用了 29GBVG 中有约 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` 获取根目录所在设备和文件系统类型。
### 步骤 4LVM 检测
通过多重检测机制判断是否使用 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.04LVM + 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 / StreamLVM + 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 LinuxLVM + 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
src/main.sh Normal file
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_growfsXFS 文件系统需要)
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 "$@"