mirror of
https://github.com/yyhuni/xingrin.git
synced 2026-01-31 03:33:14 +08:00
fix:nuclei模板加速同步,模板下载到宿主机同步更新
This commit is contained in:
@@ -21,8 +21,8 @@ class SystemLogService:
|
||||
"""
|
||||
|
||||
def __init__(self):
|
||||
# 日志文件路径(容器内路径,通过 volume 挂载到宿主机 /opt/xingrin/logs)
|
||||
self.log_file = "/app/backend/logs/xingrin.log"
|
||||
# 日志文件路径(统一使用 /opt/xingrin/logs)
|
||||
self.log_file = "/opt/xingrin/logs/xingrin.log"
|
||||
self.default_lines = 200 # 默认返回行数
|
||||
self.max_lines = 10000 # 最大返回行数限制
|
||||
self.timeout_seconds = 3 # tail 命令超时时间
|
||||
|
||||
@@ -3,12 +3,17 @@
|
||||
项目安装后执行此命令,自动创建官方模板仓库记录。
|
||||
|
||||
使用方式:
|
||||
python manage.py init_nuclei_templates # 只创建记录
|
||||
python manage.py init_nuclei_templates # 只创建记录(检测本地已有仓库)
|
||||
python manage.py init_nuclei_templates --sync # 创建并同步(git clone)
|
||||
"""
|
||||
|
||||
import logging
|
||||
import subprocess
|
||||
from pathlib import Path
|
||||
|
||||
from django.conf import settings
|
||||
from django.core.management.base import BaseCommand
|
||||
from django.utils import timezone
|
||||
|
||||
from apps.engine.models import NucleiTemplateRepo
|
||||
from apps.engine.services import NucleiTemplateRepoService
|
||||
@@ -26,6 +31,20 @@ DEFAULT_REPOS = [
|
||||
]
|
||||
|
||||
|
||||
def get_local_commit_hash(local_path: Path) -> str:
|
||||
"""获取本地 Git 仓库的 commit hash"""
|
||||
if not (local_path / ".git").is_dir():
|
||||
return ""
|
||||
result = subprocess.run(
|
||||
["git", "-C", str(local_path), "rev-parse", "HEAD"],
|
||||
check=False,
|
||||
stdout=subprocess.PIPE,
|
||||
stderr=subprocess.PIPE,
|
||||
text=True,
|
||||
)
|
||||
return result.stdout.strip() if result.returncode == 0 else ""
|
||||
|
||||
|
||||
class Command(BaseCommand):
|
||||
help = "初始化 Nuclei 模板仓库(创建官方模板仓库记录)"
|
||||
|
||||
@@ -46,6 +65,8 @@ class Command(BaseCommand):
|
||||
force = options.get("force", False)
|
||||
|
||||
service = NucleiTemplateRepoService()
|
||||
base_dir = Path(getattr(settings, "NUCLEI_TEMPLATES_REPOS_BASE_DIR", "/opt/xingrin/nuclei-repos"))
|
||||
|
||||
created = 0
|
||||
skipped = 0
|
||||
synced = 0
|
||||
@@ -87,20 +108,30 @@ class Command(BaseCommand):
|
||||
|
||||
# 创建新仓库记录
|
||||
try:
|
||||
# 检查本地是否已有仓库(由 install.sh 预下载)
|
||||
local_path = base_dir / name
|
||||
local_commit = get_local_commit_hash(local_path)
|
||||
|
||||
repo = NucleiTemplateRepo.objects.create(
|
||||
name=name,
|
||||
repo_url=repo_url,
|
||||
local_path=str(local_path) if local_commit else "",
|
||||
commit_hash=local_commit,
|
||||
last_synced_at=timezone.now() if local_commit else None,
|
||||
)
|
||||
self.stdout.write(self.style.SUCCESS(
|
||||
f"[{name}] 创建成功: id={repo.id}"
|
||||
))
|
||||
|
||||
if local_commit:
|
||||
self.stdout.write(self.style.SUCCESS(
|
||||
f"[{name}] 创建成功(检测到本地仓库): commit={local_commit[:8]}"
|
||||
))
|
||||
else:
|
||||
self.stdout.write(self.style.SUCCESS(
|
||||
f"[{name}] 创建成功: id={repo.id}"
|
||||
))
|
||||
created += 1
|
||||
|
||||
# 初始化本地路径
|
||||
service.ensure_local_path(repo)
|
||||
|
||||
# 如果需要同步
|
||||
if do_sync:
|
||||
# 如果本地没有仓库且需要同步
|
||||
if not local_commit and do_sync:
|
||||
try:
|
||||
self.stdout.write(self.style.WARNING(
|
||||
f"[{name}] 正在同步(首次可能需要几分钟)..."
|
||||
|
||||
@@ -1,7 +1,8 @@
|
||||
"""初始化所有内置字典 Wordlist 记录
|
||||
|
||||
- 目录扫描默认字典: dir_default.txt -> /app/backend/wordlist/dir_default.txt
|
||||
- 子域名爆破默认字典: subdomains-top1million-110000.txt -> /app/backend/wordlist/subdomains-top1million-110000.txt
|
||||
内置字典从镜像内 /app/backend/wordlist/ 复制到运行时目录 /opt/xingrin/wordlists/:
|
||||
- 目录扫描默认字典: dir_default.txt
|
||||
- 子域名爆破默认字典: subdomains-top1million-110000.txt
|
||||
|
||||
可重复执行:如果已存在同名记录且文件有效则跳过,只在缺失或文件丢失时创建/修复。
|
||||
"""
|
||||
|
||||
@@ -76,8 +76,8 @@ class TaskDistributor:
|
||||
self.docker_image = settings.TASK_EXECUTOR_IMAGE
|
||||
if not self.docker_image:
|
||||
raise ValueError("TASK_EXECUTOR_IMAGE 未配置,请确保 IMAGE_TAG 环境变量已设置")
|
||||
self.results_mount = getattr(settings, 'CONTAINER_RESULTS_MOUNT', '/app/backend/results')
|
||||
self.logs_mount = getattr(settings, 'CONTAINER_LOGS_MOUNT', '/app/backend/logs')
|
||||
# 统一使用 /opt/xingrin 下的路径
|
||||
self.logs_mount = "/opt/xingrin/logs"
|
||||
self.submit_interval = getattr(settings, 'TASK_SUBMIT_INTERVAL', 5)
|
||||
|
||||
def get_online_workers(self) -> list[WorkerNode]:
|
||||
@@ -274,11 +274,8 @@ class TaskDistributor:
|
||||
network_arg = ""
|
||||
server_url = f"https://{settings.PUBLIC_HOST}:{settings.PUBLIC_PORT}"
|
||||
|
||||
# 挂载路径(所有节点统一使用固定路径)
|
||||
host_results_dir = settings.HOST_RESULTS_DIR # /opt/xingrin/results
|
||||
host_logs_dir = settings.HOST_LOGS_DIR # /opt/xingrin/logs
|
||||
host_fingerprints_dir = settings.HOST_FINGERPRINTS_DIR # /opt/xingrin/fingerprints
|
||||
host_wordlists_dir = settings.HOST_WORDLISTS_DIR # /opt/xingrin/wordlists
|
||||
# 挂载路径(统一挂载 /opt/xingrin)
|
||||
host_xingrin_dir = "/opt/xingrin"
|
||||
|
||||
# 环境变量:SERVER_URL + IS_LOCAL,其他配置容器启动时从配置中心获取
|
||||
# IS_LOCAL 用于 Worker 向配置中心声明身份,决定返回的数据库地址
|
||||
@@ -294,12 +291,9 @@ class TaskDistributor:
|
||||
"-e PREFECT_LOGGING_LEVEL=WARNING", # 日志级别(减少 DEBUG 噪音)
|
||||
]
|
||||
|
||||
# 挂载卷
|
||||
# 挂载卷(统一挂载整个 /opt/xingrin 目录)
|
||||
volumes = [
|
||||
f"-v {host_results_dir}:{self.results_mount}",
|
||||
f"-v {host_logs_dir}:{self.logs_mount}",
|
||||
f"-v {host_fingerprints_dir}:{host_fingerprints_dir}",
|
||||
f"-v {host_wordlists_dir}:{host_wordlists_dir}",
|
||||
f"-v {host_xingrin_dir}:{host_xingrin_dir}",
|
||||
]
|
||||
|
||||
# 构建命令行参数
|
||||
@@ -560,7 +554,7 @@ class TaskDistributor:
|
||||
try:
|
||||
# 构建 docker run 命令(清理过期扫描结果目录)
|
||||
script_args = {
|
||||
'results_dir': '/app/backend/results',
|
||||
'results_dir': '/opt/xingrin/results',
|
||||
'retention_days': retention_days,
|
||||
}
|
||||
|
||||
|
||||
@@ -390,8 +390,8 @@ class WorkerNodeViewSet(viewsets.ModelViewSet):
|
||||
},
|
||||
'redisUrl': worker_redis_url,
|
||||
'paths': {
|
||||
'results': getattr(settings, 'CONTAINER_RESULTS_MOUNT', '/app/backend/results'),
|
||||
'logs': getattr(settings, 'CONTAINER_LOGS_MOUNT', '/app/backend/logs'),
|
||||
'results': getattr(settings, 'CONTAINER_RESULTS_MOUNT', '/opt/xingrin/results'),
|
||||
'logs': getattr(settings, 'CONTAINER_LOGS_MOUNT', '/opt/xingrin/logs'),
|
||||
},
|
||||
'logging': {
|
||||
'level': os.getenv('LOG_LEVEL', 'INFO'),
|
||||
|
||||
@@ -83,7 +83,7 @@ def cleanup_results(results_dir: str, retention_days: int) -> dict:
|
||||
|
||||
def main():
|
||||
parser = argparse.ArgumentParser(description="清理任务")
|
||||
parser.add_argument("--results_dir", type=str, default="/app/backend/results", help="扫描结果目录")
|
||||
parser.add_argument("--results_dir", type=str, default="/opt/xingrin/results", help="扫描结果目录")
|
||||
parser.add_argument("--retention_days", type=int, default=7, help="保留天数")
|
||||
|
||||
args = parser.parse_args()
|
||||
|
||||
@@ -275,13 +275,22 @@ LOGGING = get_logging_config(debug=DEBUG)
|
||||
# 命令执行日志开关(供 apps.scan.utils.command_executor 使用)
|
||||
ENABLE_COMMAND_LOGGING = get_bool_env('ENABLE_COMMAND_LOGGING', True)
|
||||
|
||||
# 扫描工具基础路径(后端和 Worker 统一使用该路径前缀存放三方工具等文件)
|
||||
# ==================== 数据目录配置(统一使用 /opt/xingrin) ====================
|
||||
# 所有数据目录统一挂载到 /opt/xingrin,便于管理和备份
|
||||
|
||||
# 扫描工具基础路径
|
||||
SCAN_TOOLS_BASE_PATH = os.getenv('SCAN_TOOLS_PATH', '/opt/xingrin/tools')
|
||||
|
||||
# 字典文件基础路径(后端和 Worker 统一使用该路径前缀存放字典文件)
|
||||
# 字典文件基础路径
|
||||
WORDLISTS_BASE_PATH = os.getenv('WORDLISTS_PATH', '/opt/xingrin/wordlists')
|
||||
|
||||
# Nuclei 模板基础路径(custom / public 两类模板目录)
|
||||
# 指纹库基础路径
|
||||
FINGERPRINTS_BASE_PATH = os.getenv('FINGERPRINTS_PATH', '/opt/xingrin/fingerprints')
|
||||
|
||||
# Nuclei 模板仓库根目录(存放 git clone 的仓库)
|
||||
NUCLEI_TEMPLATES_REPOS_BASE_DIR = os.getenv('NUCLEI_TEMPLATES_REPOS_DIR', '/opt/xingrin/nuclei-repos')
|
||||
|
||||
# Nuclei 模板基础路径(custom / public 两类模板目录,已废弃,保留兼容)
|
||||
NUCLEI_CUSTOM_TEMPLATES_DIR = os.getenv('NUCLEI_CUSTOM_TEMPLATES_DIR', '/opt/xingrin/nuclei-templates/custom')
|
||||
NUCLEI_PUBLIC_TEMPLATES_DIR = os.getenv('NUCLEI_PUBLIC_TEMPLATES_DIR', '/opt/xingrin/nuclei-templates/public')
|
||||
|
||||
@@ -336,7 +345,7 @@ TASK_SUBMIT_INTERVAL = int(os.getenv('TASK_SUBMIT_INTERVAL', '6'))
|
||||
DOCKER_NETWORK_NAME = os.getenv('DOCKER_NETWORK_NAME', 'xingrin_network')
|
||||
|
||||
# 宿主机挂载源路径(所有节点统一使用固定路径)
|
||||
# 部署前需创建:mkdir -p /opt/xingrin/{results,logs,fingerprints,wordlists}
|
||||
# 部署前需创建:mkdir -p /opt/xingrin
|
||||
HOST_RESULTS_DIR = '/opt/xingrin/results'
|
||||
HOST_LOGS_DIR = '/opt/xingrin/logs'
|
||||
HOST_FINGERPRINTS_DIR = '/opt/xingrin/fingerprints'
|
||||
@@ -364,6 +373,6 @@ WORKER_REDIS_URL = os.getenv(
|
||||
'redis://redis:6379/0' if _is_internal_public else f'redis://{PUBLIC_HOST}:6379/0'
|
||||
)
|
||||
|
||||
# 容器内挂载目标路径(固定值,不需要修改)
|
||||
CONTAINER_RESULTS_MOUNT = '/app/backend/results'
|
||||
CONTAINER_LOGS_MOUNT = '/app/backend/logs'
|
||||
# 容器内挂载目标路径(统一使用 /opt/xingrin)
|
||||
CONTAINER_RESULTS_MOUNT = '/opt/xingrin/results'
|
||||
CONTAINER_LOGS_MOUNT = '/opt/xingrin/logs'
|
||||
|
||||
@@ -37,10 +37,10 @@ CORS_ALLOWED_ORIGINS=http://localhost:3000
|
||||
|
||||
# ==================== 路径配置(容器内路径) ====================
|
||||
# 扫描结果保存目录
|
||||
SCAN_RESULTS_DIR=/app/backend/results
|
||||
SCAN_RESULTS_DIR=/opt/xingrin/results
|
||||
# Django 日志目录
|
||||
# 注意:如果留空或删除此变量,日志将只输出到 Docker 控制台(标准输出),不写入文件
|
||||
LOG_DIR=/app/backend/logs
|
||||
LOG_DIR=/opt/xingrin/logs
|
||||
|
||||
# ==================== 日志级别配置 ====================
|
||||
# 应用日志级别:DEBUG / INFO / WARNING / ERROR
|
||||
|
||||
@@ -45,9 +45,8 @@ services:
|
||||
redis:
|
||||
condition: service_healthy
|
||||
volumes:
|
||||
# 统一使用固定路径(开发环境:ln -s ~/project/backend/results /opt/xingrin/results)
|
||||
- /opt/xingrin/results:/app/backend/results
|
||||
- /opt/xingrin/logs:/app/backend/logs
|
||||
# 统一挂载数据目录
|
||||
- /opt/xingrin:/opt/xingrin
|
||||
- /var/run/docker.sock:/var/run/docker.sock
|
||||
healthcheck:
|
||||
test: ["CMD", "curl", "-f", "http://localhost:8888/api/"]
|
||||
@@ -110,10 +109,7 @@ services:
|
||||
image: docker-worker:${IMAGE_TAG:-latest}-dev
|
||||
restart: "no"
|
||||
volumes:
|
||||
- /opt/xingrin/results:/app/backend/results
|
||||
- /opt/xingrin/logs:/app/backend/logs
|
||||
- /opt/xingrin/fingerprints:/opt/xingrin/fingerprints
|
||||
- /opt/xingrin/wordlists:/opt/xingrin/wordlists
|
||||
- /opt/xingrin:/opt/xingrin
|
||||
command: echo "Worker image built for development"
|
||||
|
||||
volumes:
|
||||
|
||||
@@ -47,11 +47,8 @@ services:
|
||||
redis:
|
||||
condition: service_healthy
|
||||
volumes:
|
||||
# 统一使用固定路径(部署时需创建:mkdir -p /opt/xingrin/{results,logs,fingerprints,wordlists})
|
||||
- /opt/xingrin/results:/app/backend/results
|
||||
- /opt/xingrin/logs:/app/backend/logs
|
||||
- /opt/xingrin/fingerprints:/opt/xingrin/fingerprints
|
||||
- /opt/xingrin/wordlists:/opt/xingrin/wordlists
|
||||
# 统一挂载数据目录
|
||||
- /opt/xingrin:/opt/xingrin
|
||||
# Docker Socket 挂载:允许 Django 服务器执行本地 docker 命令(用于本地 Worker 任务分发)
|
||||
- /var/run/docker.sock:/var/run/docker.sock
|
||||
healthcheck:
|
||||
|
||||
@@ -121,7 +121,8 @@ init_fingerprints() {
|
||||
# 初始化 Nuclei 模板仓库
|
||||
init_nuclei_templates() {
|
||||
log_step "初始化 Nuclei 模板仓库..."
|
||||
docker compose exec -T server python backend/manage.py init_nuclei_templates --sync
|
||||
# 只创建数据库记录,git clone 由 install.sh 在容器外完成(支持 xget 加速)
|
||||
docker compose exec -T server python backend/manage.py init_nuclei_templates
|
||||
log_info "Nuclei 模板仓库初始化完成"
|
||||
}
|
||||
|
||||
|
||||
34
install.sh
34
install.sh
@@ -449,9 +449,9 @@ step "[3/3] 初始化配置"
|
||||
|
||||
# 创建数据目录
|
||||
info "创建数据目录..."
|
||||
mkdir -p /opt/xingrin/{results,logs,fingerprints,wordlists}
|
||||
mkdir -p /opt/xingrin/{results,logs,fingerprints,wordlists,nuclei-repos}
|
||||
chmod -R 777 /opt/xingrin
|
||||
success "数据目录已创建: /opt/xingrin/{results,logs,fingerprints,wordlists}"
|
||||
success "数据目录已创建: /opt/xingrin/"
|
||||
|
||||
DOCKER_DIR="$ROOT_DIR/docker"
|
||||
if [ ! -d "$DOCKER_DIR" ]; then
|
||||
@@ -636,6 +636,36 @@ else
|
||||
fi
|
||||
fi
|
||||
|
||||
# ==============================================================================
|
||||
# 预下载 Nuclei 模板仓库(在容器外执行,支持 xget 加速)
|
||||
# ==============================================================================
|
||||
step "预下载 Nuclei 模板仓库..."
|
||||
NUCLEI_TEMPLATES_DIR="/opt/xingrin/nuclei-repos/nuclei-templates"
|
||||
NUCLEI_TEMPLATES_REPO="https://github.com/projectdiscovery/nuclei-templates.git"
|
||||
|
||||
# 确保目录存在
|
||||
mkdir -p /opt/xingrin/nuclei-repos
|
||||
|
||||
if [ -d "$NUCLEI_TEMPLATES_DIR/.git" ]; then
|
||||
info "Nuclei 模板仓库已存在,跳过下载"
|
||||
else
|
||||
# 构建 clone URL(如果启用了 xget 加速)
|
||||
if [ -n "$XGET_MIRROR" ]; then
|
||||
CLONE_URL="${XGET_MIRROR}/gh/${NUCLEI_TEMPLATES_REPO}"
|
||||
info "使用 Xget 加速下载: $CLONE_URL"
|
||||
else
|
||||
CLONE_URL="$NUCLEI_TEMPLATES_REPO"
|
||||
fi
|
||||
|
||||
# 执行 git clone
|
||||
if git clone --depth 1 "$CLONE_URL" "$NUCLEI_TEMPLATES_DIR"; then
|
||||
success "Nuclei 模板仓库下载完成"
|
||||
else
|
||||
warn "Nuclei 模板仓库下载失败,将在服务启动后重试"
|
||||
warn "您也可以稍后在 Web 界面「Nuclei 模板」中手动同步"
|
||||
fi
|
||||
fi
|
||||
|
||||
# ==============================================================================
|
||||
# 启动服务
|
||||
# ==============================================================================
|
||||
|
||||
52
uninstall.sh
52
uninstall.sh
@@ -64,7 +64,7 @@ fi
|
||||
# ==============================================================================
|
||||
# 1. 停止并删除全部容器/网络
|
||||
# ==============================================================================
|
||||
step "[1/6] 是否停止并删除全部容器/网络?(Y/n)"
|
||||
step "[1/5] 是否停止并删除全部容器/网络?(Y/n)"
|
||||
read -r ans_stop
|
||||
ans_stop=${ans_stop:-Y}
|
||||
|
||||
@@ -96,48 +96,30 @@ else
|
||||
fi
|
||||
|
||||
# ==============================================================================
|
||||
# 2. 删除扫描日志和结果目录
|
||||
# 2. 删除 /opt/xingrin 数据目录
|
||||
# ==============================================================================
|
||||
|
||||
OPT_LOGS_DIR="/opt/xingrin/logs"
|
||||
OPT_RESULTS_DIR="/opt/xingrin/results"
|
||||
OPT_XINGRIN_DIR="/opt/xingrin"
|
||||
|
||||
step "[2/6] 是否删除扫描日志和结果目录 ($OPT_LOGS_DIR, $OPT_RESULTS_DIR)?(Y/n)"
|
||||
read -r ans_logs
|
||||
ans_logs=${ans_logs:-Y}
|
||||
step "[2/5] 是否删除数据目录 ($OPT_XINGRIN_DIR)?(Y/n)"
|
||||
echo -e " ${YELLOW}包含:扫描结果、日志、指纹库、字典、Nuclei 模板等${RESET}"
|
||||
read -r ans_data
|
||||
ans_data=${ans_data:-Y}
|
||||
|
||||
if [[ $ans_logs =~ ^[Yy]$ ]]; then
|
||||
info "正在删除日志和结果目录..."
|
||||
rm -rf "$OPT_LOGS_DIR" "$OPT_RESULTS_DIR"
|
||||
success "已删除日志和结果目录。"
|
||||
if [[ $ans_data =~ ^[Yy]$ ]]; then
|
||||
info "正在删除数据目录..."
|
||||
rm -rf "$OPT_XINGRIN_DIR"
|
||||
success "已删除 $OPT_XINGRIN_DIR"
|
||||
else
|
||||
warn "已保留日志和结果目录。"
|
||||
warn "已保留数据目录。"
|
||||
fi
|
||||
|
||||
# ==============================================================================
|
||||
# 3. 删除 /opt/xingrin/tools 和 /opt/xingrin/wordlists
|
||||
# ==============================================================================
|
||||
TOOLS_DIR="/opt/xingrin/tools"
|
||||
WORDLISTS_DIR="/opt/xingrin/wordlists"
|
||||
|
||||
step "[3/6] 是否删除工具目录和字典目录 ($TOOLS_DIR, $WORDLISTS_DIR)?(Y/n)"
|
||||
read -r ans_tools
|
||||
ans_tools=${ans_tools:-Y}
|
||||
|
||||
if [[ $ans_tools =~ ^[Yy]$ ]]; then
|
||||
info "正在删除工具和字典目录..."
|
||||
rm -rf "$TOOLS_DIR" "$WORDLISTS_DIR"
|
||||
success "已删除 /opt/xingrin/tools 和 /opt/xingrin/wordlists。"
|
||||
else
|
||||
warn "已保留 /opt/xingrin/tools 和 /opt/xingrin/wordlists。"
|
||||
fi
|
||||
|
||||
# ==============================================================================
|
||||
# 4. 删除 docker/.env 配置文件
|
||||
# 3. 删除 docker/.env 配置文件
|
||||
# ==============================================================================
|
||||
ENV_FILE="$DOCKER_DIR/.env"
|
||||
|
||||
step "[4/6] 是否删除配置文件 ($ENV_FILE)?(Y/n)"
|
||||
step "[3/5] 是否删除配置文件 ($ENV_FILE)?(Y/n)"
|
||||
echo -e " ${YELLOW}注意:删除后下次安装将生成新的随机密码。${RESET}"
|
||||
read -r ans_env
|
||||
ans_env=${ans_env:-Y}
|
||||
@@ -151,9 +133,9 @@ else
|
||||
fi
|
||||
|
||||
# ==============================================================================
|
||||
# 5. 删除本地 Postgres 容器及数据卷(如果使用本地 DB)
|
||||
# 4. 删除本地 Postgres 容器及数据卷(如果使用本地 DB)
|
||||
# ==============================================================================
|
||||
step "[5/6] 若使用本地 PostgreSQL 容器:是否删除数据库容器和 volume?(Y/n)"
|
||||
step "[4/5] 若使用本地 PostgreSQL 容器:是否删除数据库容器和 volume?(Y/n)"
|
||||
read -r ans_db
|
||||
ans_db=${ans_db:-Y}
|
||||
|
||||
@@ -178,7 +160,7 @@ else
|
||||
warn "已保留本地 Postgres 容器和 volume。"
|
||||
fi
|
||||
|
||||
step "[6/6] 是否删除与 XingRin 相关的 Docker 镜像?(Y/n)"
|
||||
step "[5/5] 是否删除与 XingRin 相关的 Docker 镜像?(Y/n)"
|
||||
read -r ans_images
|
||||
ans_images=${ans_images:-Y}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user