na-tools 1.0.1__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- na_tools-1.0.1/.github/workflows/release.yml +45 -0
- na_tools-1.0.1/.gitignore +10 -0
- na_tools-1.0.1/.python-version +1 -0
- na_tools-1.0.1/LICENSE +21 -0
- na_tools-1.0.1/PKG-INFO +81 -0
- na_tools-1.0.1/README.md +69 -0
- na_tools-1.0.1/pyproject.toml +17 -0
- na_tools-1.0.1/src/na_tools/__init__.py +3 -0
- na_tools-1.0.1/src/na_tools/__main__.py +6 -0
- na_tools-1.0.1/src/na_tools/cli.py +39 -0
- na_tools-1.0.1/src/na_tools/commands/__init__.py +0 -0
- na_tools-1.0.1/src/na_tools/commands/backup.py +218 -0
- na_tools-1.0.1/src/na_tools/commands/config_cmd.py +307 -0
- na_tools-1.0.1/src/na_tools/commands/install.py +139 -0
- na_tools-1.0.1/src/na_tools/commands/list_cmd.py +54 -0
- na_tools-1.0.1/src/na_tools/commands/logs.py +43 -0
- na_tools-1.0.1/src/na_tools/commands/restore.py +283 -0
- na_tools-1.0.1/src/na_tools/commands/status.py +39 -0
- na_tools-1.0.1/src/na_tools/commands/update.py +61 -0
- na_tools-1.0.1/src/na_tools/commands/use.py +57 -0
- na_tools-1.0.1/src/na_tools/core/__init__.py +0 -0
- na_tools-1.0.1/src/na_tools/core/compose.py +145 -0
- na_tools-1.0.1/src/na_tools/core/config.py +142 -0
- na_tools-1.0.1/src/na_tools/core/docker.py +364 -0
- na_tools-1.0.1/src/na_tools/core/na_config.py +158 -0
- na_tools-1.0.1/src/na_tools/core/platform.py +142 -0
- na_tools-1.0.1/src/na_tools/utils/__init__.py +0 -0
- na_tools-1.0.1/src/na_tools/utils/console.py +53 -0
- na_tools-1.0.1/src/na_tools/utils/crypto.py +10 -0
- na_tools-1.0.1/src/na_tools/utils/network.py +59 -0
- na_tools-1.0.1/src/na_tools/utils/privilege.py +39 -0
- na_tools-1.0.1/uv.lock +240 -0
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
name: Release to PyPI
|
|
2
|
+
|
|
3
|
+
on:
|
|
4
|
+
push:
|
|
5
|
+
tags:
|
|
6
|
+
- 'v*.*.*'
|
|
7
|
+
workflow_dispatch:
|
|
8
|
+
|
|
9
|
+
jobs:
|
|
10
|
+
release:
|
|
11
|
+
name: Build and Release
|
|
12
|
+
runs-on: ubuntu-latest
|
|
13
|
+
permissions:
|
|
14
|
+
contents: write
|
|
15
|
+
|
|
16
|
+
steps:
|
|
17
|
+
- name: Checkout Code
|
|
18
|
+
uses: actions/checkout@v4
|
|
19
|
+
|
|
20
|
+
- name: Set up uv
|
|
21
|
+
uses: astral-sh/setup-uv@v5
|
|
22
|
+
with:
|
|
23
|
+
enable-cache: true
|
|
24
|
+
|
|
25
|
+
- name: Set up Python
|
|
26
|
+
uses: actions/setup-python@v5
|
|
27
|
+
with:
|
|
28
|
+
python-version-file: "pyproject.toml"
|
|
29
|
+
|
|
30
|
+
- name: Build project
|
|
31
|
+
run: uv build
|
|
32
|
+
|
|
33
|
+
- name: Publish to PyPI
|
|
34
|
+
uses: pypa/gh-action-pypi-publish@release/v1
|
|
35
|
+
with:
|
|
36
|
+
password: ${{ secrets.PYPI_TOKEN }}
|
|
37
|
+
|
|
38
|
+
- name: Create GitHub Release
|
|
39
|
+
uses: softprops/action-gh-release@v2
|
|
40
|
+
with:
|
|
41
|
+
generate_release_notes: true
|
|
42
|
+
files: |
|
|
43
|
+
dist/*
|
|
44
|
+
env:
|
|
45
|
+
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
3.13
|
na_tools-1.0.1/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 NekroAI
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
na_tools-1.0.1/PKG-INFO
ADDED
|
@@ -0,0 +1,81 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: na-tools
|
|
3
|
+
Version: 1.0.1
|
|
4
|
+
Summary: Nekro Agent 跨平台自动部署 CLI 工具
|
|
5
|
+
License-File: LICENSE
|
|
6
|
+
Requires-Python: >=3.10
|
|
7
|
+
Requires-Dist: click>=8.1
|
|
8
|
+
Requires-Dist: httpx>=0.27
|
|
9
|
+
Requires-Dist: pyyaml>=6.0
|
|
10
|
+
Requires-Dist: rich>=13.0
|
|
11
|
+
Description-Content-Type: text/markdown
|
|
12
|
+
|
|
13
|
+
# NA-Tools
|
|
14
|
+
|
|
15
|
+
**Nekro Agent 跨平台自动部署 CLI 工具**
|
|
16
|
+
|
|
17
|
+
支持 macOS / Linux,提供一键安装、更新、备份、恢复和配置管理。
|
|
18
|
+
|
|
19
|
+
## 安装
|
|
20
|
+
|
|
21
|
+
```bash
|
|
22
|
+
# 需要 Python 3.10+
|
|
23
|
+
pip install -e .
|
|
24
|
+
|
|
25
|
+
# 或使用 uv
|
|
26
|
+
uv sync
|
|
27
|
+
```
|
|
28
|
+
|
|
29
|
+
## 命令
|
|
30
|
+
|
|
31
|
+
| 命令 | 说明 |
|
|
32
|
+
|------|------|
|
|
33
|
+
| `na-tools install` | 安装 Nekro Agent(Docker 检测 → 配置 → 部署) |
|
|
34
|
+
| `na-tools update` | 更新服务到最新版本 |
|
|
35
|
+
| `na-tools backup` | 备份数据和配置 |
|
|
36
|
+
| `na-tools restore [file]` | 从备份恢复(不指定文件则从列表选择) |
|
|
37
|
+
| `na-tools config` | 快捷配置 nekro-agent.yaml |
|
|
38
|
+
| `na-tools status` | 查看服务状态 |
|
|
39
|
+
| `na-tools logs [service]` | 查看服务日志 |
|
|
40
|
+
| `na-tools list` | 列出所有已安装的 Nekro Agent 及序号 |
|
|
41
|
+
| `na-tools use <id/path>` | 切换当前激活的数据目录 |
|
|
42
|
+
|
|
43
|
+
## 快速开始
|
|
44
|
+
|
|
45
|
+
```bash
|
|
46
|
+
# 一键安装
|
|
47
|
+
na-tools install
|
|
48
|
+
|
|
49
|
+
# 配置模型 API
|
|
50
|
+
na-tools config model
|
|
51
|
+
|
|
52
|
+
# 添加管理员
|
|
53
|
+
na-tools config admin --add 12345678
|
|
54
|
+
|
|
55
|
+
# 更新到最新版
|
|
56
|
+
na-tools update
|
|
57
|
+
|
|
58
|
+
# 备份数据
|
|
59
|
+
na-tools backup
|
|
60
|
+
|
|
61
|
+
# 恢复备份(交互式选择)
|
|
62
|
+
na-tools restore
|
|
63
|
+
|
|
64
|
+
# 查看多开或所有安装实例
|
|
65
|
+
na-tools list
|
|
66
|
+
|
|
67
|
+
# 切换到另一个安装实例
|
|
68
|
+
na-tools use 1
|
|
69
|
+
|
|
70
|
+
# 查看状态
|
|
71
|
+
na-tools status
|
|
72
|
+
```
|
|
73
|
+
|
|
74
|
+
## 跨平台支持
|
|
75
|
+
|
|
76
|
+
| 功能 | Linux | macOS |
|
|
77
|
+
|------|-------|-------|
|
|
78
|
+
| Docker 安装 | ✅ 自动 | ⚠️ 引导 |
|
|
79
|
+
| 服务部署 | ✅ | ✅ |
|
|
80
|
+
| 备份恢复 | ✅ | ✅ |
|
|
81
|
+
| 配置管理 | ✅ | ✅ |
|
na_tools-1.0.1/README.md
ADDED
|
@@ -0,0 +1,69 @@
|
|
|
1
|
+
# NA-Tools
|
|
2
|
+
|
|
3
|
+
**Nekro Agent 跨平台自动部署 CLI 工具**
|
|
4
|
+
|
|
5
|
+
支持 macOS / Linux,提供一键安装、更新、备份、恢复和配置管理。
|
|
6
|
+
|
|
7
|
+
## 安装
|
|
8
|
+
|
|
9
|
+
```bash
|
|
10
|
+
# 需要 Python 3.10+
|
|
11
|
+
pip install -e .
|
|
12
|
+
|
|
13
|
+
# 或使用 uv
|
|
14
|
+
uv sync
|
|
15
|
+
```
|
|
16
|
+
|
|
17
|
+
## 命令
|
|
18
|
+
|
|
19
|
+
| 命令 | 说明 |
|
|
20
|
+
|------|------|
|
|
21
|
+
| `na-tools install` | 安装 Nekro Agent(Docker 检测 → 配置 → 部署) |
|
|
22
|
+
| `na-tools update` | 更新服务到最新版本 |
|
|
23
|
+
| `na-tools backup` | 备份数据和配置 |
|
|
24
|
+
| `na-tools restore [file]` | 从备份恢复(不指定文件则从列表选择) |
|
|
25
|
+
| `na-tools config` | 快捷配置 nekro-agent.yaml |
|
|
26
|
+
| `na-tools status` | 查看服务状态 |
|
|
27
|
+
| `na-tools logs [service]` | 查看服务日志 |
|
|
28
|
+
| `na-tools list` | 列出所有已安装的 Nekro Agent 及序号 |
|
|
29
|
+
| `na-tools use <id/path>` | 切换当前激活的数据目录 |
|
|
30
|
+
|
|
31
|
+
## 快速开始
|
|
32
|
+
|
|
33
|
+
```bash
|
|
34
|
+
# 一键安装
|
|
35
|
+
na-tools install
|
|
36
|
+
|
|
37
|
+
# 配置模型 API
|
|
38
|
+
na-tools config model
|
|
39
|
+
|
|
40
|
+
# 添加管理员
|
|
41
|
+
na-tools config admin --add 12345678
|
|
42
|
+
|
|
43
|
+
# 更新到最新版
|
|
44
|
+
na-tools update
|
|
45
|
+
|
|
46
|
+
# 备份数据
|
|
47
|
+
na-tools backup
|
|
48
|
+
|
|
49
|
+
# 恢复备份(交互式选择)
|
|
50
|
+
na-tools restore
|
|
51
|
+
|
|
52
|
+
# 查看多开或所有安装实例
|
|
53
|
+
na-tools list
|
|
54
|
+
|
|
55
|
+
# 切换到另一个安装实例
|
|
56
|
+
na-tools use 1
|
|
57
|
+
|
|
58
|
+
# 查看状态
|
|
59
|
+
na-tools status
|
|
60
|
+
```
|
|
61
|
+
|
|
62
|
+
## 跨平台支持
|
|
63
|
+
|
|
64
|
+
| 功能 | Linux | macOS |
|
|
65
|
+
|------|-------|-------|
|
|
66
|
+
| Docker 安装 | ✅ 自动 | ⚠️ 引导 |
|
|
67
|
+
| 服务部署 | ✅ | ✅ |
|
|
68
|
+
| 备份恢复 | ✅ | ✅ |
|
|
69
|
+
| 配置管理 | ✅ | ✅ |
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
[project]
|
|
2
|
+
name = "na-tools"
|
|
3
|
+
version = "1.0.1"
|
|
4
|
+
description = "Nekro Agent 跨平台自动部署 CLI 工具"
|
|
5
|
+
readme = "README.md"
|
|
6
|
+
requires-python = ">=3.10"
|
|
7
|
+
dependencies = ["click>=8.1", "rich>=13.0", "httpx>=0.27", "pyyaml>=6.0"]
|
|
8
|
+
|
|
9
|
+
[project.scripts]
|
|
10
|
+
na-tools = "na_tools.cli:main"
|
|
11
|
+
|
|
12
|
+
[build-system]
|
|
13
|
+
requires = ["hatchling"]
|
|
14
|
+
build-backend = "hatchling.build"
|
|
15
|
+
|
|
16
|
+
[tool.hatch.build.targets.wheel]
|
|
17
|
+
packages = ["src/na_tools"]
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
"""NA-Tools CLI 入口。"""
|
|
2
|
+
|
|
3
|
+
import click
|
|
4
|
+
|
|
5
|
+
from . import __version__
|
|
6
|
+
from .commands.backup import backup
|
|
7
|
+
from .commands.config_cmd import config
|
|
8
|
+
from .commands.install import install
|
|
9
|
+
from .commands.logs import logs
|
|
10
|
+
from .commands.restore import restore
|
|
11
|
+
from .commands.status import status
|
|
12
|
+
from .commands.update import update
|
|
13
|
+
from .commands.use import use
|
|
14
|
+
from .commands.list_cmd import list_cmd
|
|
15
|
+
|
|
16
|
+
|
|
17
|
+
@click.group()
|
|
18
|
+
@click.version_option(version=__version__, prog_name="na-tools")
|
|
19
|
+
def main() -> None:
|
|
20
|
+
"""NA-Tools: Nekro Agent 部署管理工具
|
|
21
|
+
|
|
22
|
+
支持一键安装、更新、备份、恢复和配置管理。
|
|
23
|
+
"""
|
|
24
|
+
pass
|
|
25
|
+
|
|
26
|
+
|
|
27
|
+
main.add_command(install)
|
|
28
|
+
main.add_command(update)
|
|
29
|
+
main.add_command(backup)
|
|
30
|
+
main.add_command(restore)
|
|
31
|
+
main.add_command(config)
|
|
32
|
+
main.add_command(status)
|
|
33
|
+
main.add_command(logs)
|
|
34
|
+
main.add_command(use)
|
|
35
|
+
main.add_command(list_cmd)
|
|
36
|
+
|
|
37
|
+
|
|
38
|
+
if __name__ == "__main__":
|
|
39
|
+
main()
|
|
File without changes
|
|
@@ -0,0 +1,218 @@
|
|
|
1
|
+
"""backup 命令:备份 Nekro Agent 数据。"""
|
|
2
|
+
|
|
3
|
+
import tarfile
|
|
4
|
+
from datetime import datetime
|
|
5
|
+
from pathlib import Path
|
|
6
|
+
|
|
7
|
+
import click
|
|
8
|
+
|
|
9
|
+
from ..core.compose import compose_exists
|
|
10
|
+
from ..core.docker import DockerEnv
|
|
11
|
+
from ..core.platform import default_data_dir
|
|
12
|
+
from ..utils.privilege import with_sudo_fallback
|
|
13
|
+
from ..utils.console import error, info, success, warning
|
|
14
|
+
|
|
15
|
+
|
|
16
|
+
@click.group(invoke_without_command=True)
|
|
17
|
+
@click.pass_context
|
|
18
|
+
@with_sudo_fallback
|
|
19
|
+
@click.option("--data-dir", type=click.Path(), default=None, help="数据目录路径")
|
|
20
|
+
@click.option(
|
|
21
|
+
"--output", "-o", type=click.Path(), default=None, help="备份文件输出路径"
|
|
22
|
+
)
|
|
23
|
+
@click.option("--no-restart", is_flag=True, default=False, help="备份后不重启服务")
|
|
24
|
+
def backup(
|
|
25
|
+
ctx: click.Context, data_dir: str | None, output: str | None, no_restart: bool
|
|
26
|
+
) -> None:
|
|
27
|
+
"""备份 Nekro Agent 数据和配置。"""
|
|
28
|
+
if ctx.invoked_subcommand is not None:
|
|
29
|
+
return
|
|
30
|
+
|
|
31
|
+
data_dir_path = Path(data_dir or default_data_dir()).expanduser().resolve()
|
|
32
|
+
|
|
33
|
+
if not data_dir_path.exists():
|
|
34
|
+
error(f"数据目录不存在: {data_dir_path}")
|
|
35
|
+
raise click.Abort()
|
|
36
|
+
|
|
37
|
+
docker = DockerEnv()
|
|
38
|
+
env_path = data_dir_path / ".env"
|
|
39
|
+
|
|
40
|
+
# 生成备份文件名
|
|
41
|
+
timestamp = datetime.now().strftime("%Y%m%d_%H%M%S")
|
|
42
|
+
if output:
|
|
43
|
+
backup_path = Path(output)
|
|
44
|
+
else:
|
|
45
|
+
backup_dir = Path("~/.config/na-tools/backup").expanduser() / data_dir_path.name
|
|
46
|
+
backup_dir.mkdir(parents=True, exist_ok=True)
|
|
47
|
+
backup_path = backup_dir / f"{data_dir_path.name}_backup_{timestamp}.tar.gz"
|
|
48
|
+
|
|
49
|
+
backup_path.parent.mkdir(parents=True, exist_ok=True)
|
|
50
|
+
|
|
51
|
+
# 准备备份存储卷(在停止服务前解析卷名)
|
|
52
|
+
volume_backups_map: list[
|
|
53
|
+
tuple[str, str, Path]
|
|
54
|
+
] = [] # (volume_name, backup_filename, backup_path)
|
|
55
|
+
volumes_dir = data_dir_path / "volumes_backup_tmp"
|
|
56
|
+
|
|
57
|
+
if compose_exists(data_dir_path) and docker.compose_installed:
|
|
58
|
+
config = docker.get_compose_config(
|
|
59
|
+
cwd=data_dir_path, env_file=env_path if env_path.exists() else None
|
|
60
|
+
)
|
|
61
|
+
if config and "services" in config and isinstance(config["services"], dict):
|
|
62
|
+
from typing import cast
|
|
63
|
+
|
|
64
|
+
services = cast(dict[str, dict[str, object]], config["services"])
|
|
65
|
+
|
|
66
|
+
# 映射关系: 服务名 -> 内部挂载点 -> 备份文件名
|
|
67
|
+
backup_targets = {
|
|
68
|
+
"nekro_postgres": ("/var/lib/postgresql/data", "postgres.tar.gz"),
|
|
69
|
+
"nekro_qdrant": ("/qdrant/storage", "qdrant.tar.gz"),
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
for svc_name, (internal_path, filename) in backup_targets.items():
|
|
73
|
+
if svc_name in services:
|
|
74
|
+
# 尝试解析卷名
|
|
75
|
+
real_volume_name = docker.get_service_volume(
|
|
76
|
+
cwd=data_dir_path,
|
|
77
|
+
service=svc_name,
|
|
78
|
+
target=internal_path,
|
|
79
|
+
env_file=env_path if env_path.exists() else None,
|
|
80
|
+
)
|
|
81
|
+
|
|
82
|
+
# 如果解析失败(容器未运行?),尝试从配置读取
|
|
83
|
+
if not real_volume_name:
|
|
84
|
+
svc_config = services[svc_name]
|
|
85
|
+
volumes_config = svc_config.get("volumes", [])
|
|
86
|
+
if isinstance(volumes_config, list):
|
|
87
|
+
volumes = cast(list[dict[str, str]], volumes_config)
|
|
88
|
+
for vol in volumes:
|
|
89
|
+
if not vol:
|
|
90
|
+
continue
|
|
91
|
+
if (
|
|
92
|
+
vol.get("type") == "volume"
|
|
93
|
+
and vol.get("target") == internal_path
|
|
94
|
+
):
|
|
95
|
+
real_volume_name = vol.get("source")
|
|
96
|
+
break
|
|
97
|
+
|
|
98
|
+
if real_volume_name:
|
|
99
|
+
volume_backups_map.append(
|
|
100
|
+
(real_volume_name, filename, volumes_dir / filename)
|
|
101
|
+
)
|
|
102
|
+
|
|
103
|
+
# 停止服务
|
|
104
|
+
should_restart = False
|
|
105
|
+
if compose_exists(data_dir_path) and docker.compose_installed:
|
|
106
|
+
info("正在停止服务以确保数据一致性...")
|
|
107
|
+
_ = docker.down(
|
|
108
|
+
cwd=data_dir_path, env_file=env_path if env_path.exists() else None
|
|
109
|
+
)
|
|
110
|
+
should_restart = True
|
|
111
|
+
|
|
112
|
+
# 执行卷备份
|
|
113
|
+
volume_backups: list[Path] = []
|
|
114
|
+
if volume_backups_map:
|
|
115
|
+
volumes_dir.mkdir(exist_ok=True)
|
|
116
|
+
for vol_name, filename, backup_file in volume_backups_map:
|
|
117
|
+
info(f"正在备份存储卷 {vol_name}...")
|
|
118
|
+
|
|
119
|
+
# 使用 alpine 打包
|
|
120
|
+
success_backup = docker.run_ephemeral(
|
|
121
|
+
image="alpine:latest",
|
|
122
|
+
cmd=["tar", "czf", f"/backup/{filename}", "-C", "/data", "."],
|
|
123
|
+
volumes={vol_name: "/data", str(volumes_dir): "/backup"},
|
|
124
|
+
)
|
|
125
|
+
|
|
126
|
+
if success_backup:
|
|
127
|
+
volume_backups.append(backup_file)
|
|
128
|
+
success(f"卷备份完成: {filename}")
|
|
129
|
+
else:
|
|
130
|
+
error(f"卷备份失败: {vol_name}")
|
|
131
|
+
|
|
132
|
+
# 打包数据
|
|
133
|
+
info(f"正在备份数据到: {backup_path}")
|
|
134
|
+
try:
|
|
135
|
+
with tarfile.open(backup_path, "w:gz") as tar:
|
|
136
|
+
# 添加主数据目录
|
|
137
|
+
tar.add(
|
|
138
|
+
data_dir_path,
|
|
139
|
+
arcname=data_dir_path.name,
|
|
140
|
+
filter=lambda x: None if "volumes_backup_tmp" in x.name else x,
|
|
141
|
+
)
|
|
142
|
+
|
|
143
|
+
# 添加卷备份
|
|
144
|
+
if volume_backups:
|
|
145
|
+
# 在 tar 中创建一个 volumes 目录
|
|
146
|
+
for vb in volume_backups:
|
|
147
|
+
tar.add(vb, arcname=f"volumes/{vb.name}")
|
|
148
|
+
|
|
149
|
+
success(
|
|
150
|
+
f"备份完成: {backup_path} ({backup_path.stat().st_size / 1024 / 1024:.1f} MB)"
|
|
151
|
+
)
|
|
152
|
+
except Exception as e:
|
|
153
|
+
error(f"备份失败: {e}")
|
|
154
|
+
# 即使备份失败也要尝试重启
|
|
155
|
+
if should_restart and not no_restart:
|
|
156
|
+
info("正在重新启动服务...")
|
|
157
|
+
_ = docker.up(
|
|
158
|
+
cwd=data_dir_path, env_file=env_path if env_path.exists() else None
|
|
159
|
+
)
|
|
160
|
+
raise click.Abort()
|
|
161
|
+
finally:
|
|
162
|
+
# 清理临时卷备份目录
|
|
163
|
+
import shutil
|
|
164
|
+
|
|
165
|
+
if volumes_dir.exists():
|
|
166
|
+
shutil.rmtree(volumes_dir)
|
|
167
|
+
|
|
168
|
+
# 重启服务
|
|
169
|
+
if should_restart and not no_restart:
|
|
170
|
+
info("正在重新启动服务...")
|
|
171
|
+
if docker.up(
|
|
172
|
+
cwd=data_dir_path, env_file=env_path if env_path.exists() else None
|
|
173
|
+
):
|
|
174
|
+
success("服务已重新启动。")
|
|
175
|
+
else:
|
|
176
|
+
warning("服务重启失败,请手动启动。")
|
|
177
|
+
|
|
178
|
+
success("🎉 备份完成!")
|
|
179
|
+
|
|
180
|
+
|
|
181
|
+
@backup.command("list")
|
|
182
|
+
@click.pass_context
|
|
183
|
+
def list_backups(ctx: click.Context) -> None:
|
|
184
|
+
"""列出可用的备份文件。"""
|
|
185
|
+
data_dir = None
|
|
186
|
+
import typing
|
|
187
|
+
|
|
188
|
+
obj = typing.cast(object, ctx.obj)
|
|
189
|
+
if isinstance(obj, dict):
|
|
190
|
+
from typing import cast
|
|
191
|
+
|
|
192
|
+
obj_dict = cast(dict[str, object], obj)
|
|
193
|
+
val = obj_dict.get("data_dir")
|
|
194
|
+
if isinstance(val, str):
|
|
195
|
+
data_dir = val
|
|
196
|
+
data_dir_path = Path(data_dir or default_data_dir()).expanduser().resolve()
|
|
197
|
+
backup_dir = Path("~/.config/na-tools/backup").expanduser() / data_dir_path.name
|
|
198
|
+
|
|
199
|
+
if not backup_dir.exists():
|
|
200
|
+
info("备份目录不存在或为空。")
|
|
201
|
+
return
|
|
202
|
+
|
|
203
|
+
backups = sorted(
|
|
204
|
+
list(backup_dir.glob("*.tar.gz")),
|
|
205
|
+
key=lambda x: x.stat().st_mtime,
|
|
206
|
+
reverse=True,
|
|
207
|
+
)
|
|
208
|
+
|
|
209
|
+
if not backups:
|
|
210
|
+
info("没有任何历史备份。")
|
|
211
|
+
return
|
|
212
|
+
|
|
213
|
+
info("发现以下历史备份:")
|
|
214
|
+
for i, b in enumerate(backups, 1):
|
|
215
|
+
mtime = datetime.fromtimestamp(b.stat().st_mtime).strftime("%Y-%m-%d %H:%M:%S")
|
|
216
|
+
click.echo(
|
|
217
|
+
f" [{i}] {b.name} (备份时间: {mtime}, 大小: {b.stat().st_size / 1024 / 1024:.1f} MB)"
|
|
218
|
+
)
|