Compare commits

...

16 Commits

Author SHA1 Message Date
yyhuni
e8a5e0cea8 Update README.md 2026-01-17 17:09:49 +08:00
yyhuni
3308908d7a Update README.md 2026-01-16 21:43:08 +08:00
yyhuni
a8402cfffa Update project status in README 2026-01-16 17:56:44 +08:00
yyhuni
dce4e12667 Update README.md 2026-01-16 17:56:26 +08:00
github-actions[bot]
bd1dd2c0d5 chore: bump version to v1.5.8 2026-01-11 19:34:26 +08:00
yyhuni
0b6560ac17 Update README.md 2026-01-10 16:28:30 +08:00
yyhuni
943a4cb960 docs(docker): remove default credentials from startup message
- Remove hardcoded default username and password display from docker startup script
- Remove warning message about changing password after first login
- Improve security by not exposing default credentials in startup output
- Simplifies startup message output for cleaner user experience
2026-01-10 11:21:14 +08:00
yyhuni
eb2d853b76 docs: remove emoji symbols from README for better accessibility
- Remove shield emoji (🛡️) from main title
- Replace emoji prefixes in navigation links with plain text anchors
- Remove emoji icons from section headers (🌐, 📚, , 📦, 🤝, 📧, 🎁, , 🙏, ⚠️, 🌟, 📄)
- Replace emoji status indicators (, ⚠️, 🔍, 💡, ) with plain text equivalents
- Remove emoji bullet points and replace with standard formatting
- Simplify documentation for improved readability and cross-platform compatibility
2026-01-10 11:17:43 +08:00
github-actions[bot]
1184c18b74 chore: bump version to v1.5.7 2026-01-10 03:10:45 +00:00
yyhuni
8a6f1b6f24 feat(engine): add --force-sub flag for selective engine config updates
- Add --force-sub command flag to init_default_engine management command
- Allow updating only sub-engines while preserving user-customized full scan config
- Update docker/scripts/init-data.sh to always update full scan engine configuration
- Change docker/server/start.sh to use --force flag for initial engine setup
- Improve update.sh with better logging functions and formatted output
- Add color-coded log functions (log_step, log_ok, log_info, log_warn, log_error)
- Enhance update.sh UI with better visual formatting and warning messages
- Refactor error messages and user prompts for improved clarity
- This enables safer upgrades by preserving custom full scan configurations while updating sub-engines
2026-01-10 11:04:42 +08:00
yyhuni
255d505aba refactor(scan): remove deprecated amass engine configurations
- Remove amass_passive engine configuration from subdomain discovery defaults
- Remove amass_active engine configuration from subdomain discovery defaults
- Simplify engine configuration by eliminating unused amass-based scanners
- Streamline the default engine template for better maintainability
2026-01-10 10:51:07 +08:00
github-actions[bot]
d06a9bab1f chore: bump version to v1.5.7-dev 2026-01-10 02:48:21 +00:00
yyhuni
6d5c776bf7 chore: improve version detection and update deployment configuration
- Update version detection to support IMAGE_TAG environment variable for Docker containers
- Add fallback mechanism to check multiple version file paths (/app/VERSION and project root)
- Add IMAGE_TAG environment variable to docker-compose.dev.yml and docker-compose.yml
- Fix frontend access URL in start.sh to include correct port (8083)
- Update upgrade warning message in update.sh to recommend fresh installation with latest code
- Improve robustness of version retrieval with better error handling for missing files
2026-01-10 10:41:36 +08:00
github-actions[bot]
bf058dd67b chore: bump version to v1.5.6-dev 2026-01-10 02:33:15 +00:00
yyhuni
0532d7c8b8 feat(notifications): add WeChat Work (WeChat Enterprise) notification support
- Add wecom notification channel configuration to mock notification settings
- Initialize wecom with disabled state and empty webhook URL by default
- Update notification settings response to include wecom configuration
- Enable WeChat Work as an alternative notification channel alongside Discord
2026-01-10 10:29:33 +08:00
yyhuni
2ee9b5ffa2 更新版本 2026-01-10 10:27:48 +08:00
21 changed files with 593 additions and 167 deletions

129
README.md
View File

@@ -1,7 +1,7 @@
<h1 align="center">XingRin - 星环</h1> <h1 align="center">XingRin - 星环</h1>
<p align="center"> <p align="center">
<b>🛡️ 攻击面管理平台 (ASM) | 自动化资产发现与漏洞扫描系统</b> <b>攻击面管理平台 (ASM) | 自动化资产发现与漏洞扫描系统</b>
</p> </p>
<p align="center"> <p align="center">
@@ -12,29 +12,28 @@
</p> </p>
<p align="center"> <p align="center">
<a href="#-功能特性">功能特性</a> • <a href="#功能特性">功能特性</a> •
<a href="#-全局资产搜索">资产搜索</a> • <a href="#全局资产搜索">资产搜索</a> •
<a href="#-快速开始">快速开始</a> • <a href="#快速开始">快速开始</a> •
<a href="#-文档">文档</a> • <a href="#文档">文档</a> •
<a href="#-反馈与贡献">反馈与贡献</a> <a href="#反馈与贡献">反馈与贡献</a>
</p> </p>
<p align="center"> <p align="center">
<sub>🔍 关键词: ASM | 攻击面管理 | 漏洞扫描 | 资产发现 | 资产搜索 | Bug Bounty | 渗透测试 | Nuclei | 子域名枚举 | EASM</sub> <sub>关键词: ASM | 攻击面管理 | 漏洞扫描 | 资产发现 | 资产搜索 | Bug Bounty | 渗透测试 | Nuclei | 子域名枚举 | EASM</sub>
</p> </p>
--- ---
## 在线 Demo
## 🌐 在线 Demo
**[https://xingrin.vercel.app/](https://xingrin.vercel.app/)** **[https://xingrin.vercel.app/](https://xingrin.vercel.app/)**
> ⚠️ 仅用于 UI 展示,未接入后端数据库 > 仅用于 UI 展示,未接入后端数据库
--- ---
<p align="center"> <p align="center">
<b>🎨 现代化 UI </b> <b>现代化 UI</b>
</p> </p>
<p align="center"> <p align="center">
@@ -44,45 +43,45 @@
<img src="docs/screenshots/quantum-rose.png" alt="Quantum Rose" width="24%"> <img src="docs/screenshots/quantum-rose.png" alt="Quantum Rose" width="24%">
</p> </p>
## 📚 文档 ## 文档
- [📖 技术文档](./docs/README.md) - 技术文档导航(🚧 持续完善中) - [技术文档](./docs/README.md) - 技术文档导航(持续完善中)
- [🚀 快速开始](./docs/quick-start.md) - 一键安装和部署指南 - [快速开始](./docs/quick-start.md) - 一键安装和部署指南
- [🔄 版本管理](./docs/version-management.md) - Git Tag 驱动的自动化版本管理系统 - [版本管理](./docs/version-management.md) - Git Tag 驱动的自动化版本管理系统
- [📦 Nuclei 模板架构](./docs/nuclei-template-architecture.md) - 模板仓库的存储与同步 - [Nuclei 模板架构](./docs/nuclei-template-architecture.md) - 模板仓库的存储与同步
- [📖 字典文件架构](./docs/wordlist-architecture.md) - 字典文件的存储与同步 - [字典文件架构](./docs/wordlist-architecture.md) - 字典文件的存储与同步
- [🔍 扫描流程架构](./docs/scan-flow-architecture.md) - 完整扫描流程与工具编排 - [扫描流程架构](./docs/scan-flow-architecture.md) - 完整扫描流程与工具编排
--- ---
## 功能特性 ## 功能特性
### 扫描能力 ### 扫描能力
| 功能 | 状态 | 工具 | 说明 | | 功能 | 状态 | 工具 | 说明 |
|------|------|------|------| |------|------|------|------|
| 子域名扫描 | | Subfinder, Amass, PureDNS | 被动收集 + 主动爆破,聚合 50+ 数据源 | | 子域名扫描 | 已完成 | Subfinder, Amass, PureDNS | 被动收集 + 主动爆破,聚合 50+ 数据源 |
| 端口扫描 | | Naabu | 自定义端口范围 | | 端口扫描 | 已完成 | Naabu | 自定义端口范围 |
| 站点发现 | | HTTPX | HTTP 探测,自动获取标题、状态码、技术栈 | | 站点发现 | 已完成 | HTTPX | HTTP 探测,自动获取标题、状态码、技术栈 |
| 指纹识别 | | XingFinger | 2.7W+ 指纹规则,多源指纹库 | | 指纹识别 | 已完成 | XingFinger | 2.7W+ 指纹规则,多源指纹库 |
| URL 收集 | | Waymore, Katana | 历史数据 + 主动爬取 | | URL 收集 | 已完成 | Waymore, Katana | 历史数据 + 主动爬取 |
| 目录扫描 | | FFUF | 高速爆破,智能字典 | | 目录扫描 | 已完成 | FFUF | 高速爆破,智能字典 |
| 漏洞扫描 | | Nuclei, Dalfox | 9000+ POC 模板XSS 检测 | | 漏洞扫描 | 已完成 | Nuclei, Dalfox | 9000+ POC 模板XSS 检测 |
| 站点截图 | | Playwright | WebP 高压缩存储 | | 站点截图 | 已完成 | Playwright | WebP 高压缩存储 |
### 平台能力 ### 平台能力
| 功能 | 状态 | 说明 | | 功能 | 状态 | 说明 |
|------|------|------| |------|------|------|
| 目标管理 | | 多层级组织,支持域名/IP 目标 | | 目标管理 | 已完成 | 多层级组织,支持域名/IP 目标 |
| 资产快照 | | 扫描结果对比,追踪资产变化 | | 资产快照 | 已完成 | 扫描结果对比,追踪资产变化 |
| 黑名单过滤 | | 全局 + Target 级,支持通配符/CIDR | | 黑名单过滤 | 已完成 | 全局 + Target 级,支持通配符/CIDR |
| 定时任务 | | Cron 表达式,自动化周期扫描 | | 定时任务 | 已完成 | Cron 表达式,自动化周期扫描 |
| 分布式扫描 | | 多 Worker 节点,负载感知调度 | | 分布式扫描 | 已完成 | 多 Worker 节点,负载感知调度 |
| 全局搜索 | | 表达式语法,多字段组合查询 | | 全局搜索 | 已完成 | 表达式语法,多字段组合查询 |
| 通知推送 | | 企业微信、Telegram、Discord | | 通知推送 | 已完成 | 企业微信、Telegram、Discord |
| API 密钥管理 | | 可视化配置各数据源 API Key | | API 密钥管理 | 已完成 | 可视化配置各数据源 API Key |
### 扫描流程架构 ### 扫描流程架构
@@ -136,7 +135,7 @@ flowchart LR
详细说明请查看 [扫描流程架构文档](./docs/scan-flow-architecture.md) 详细说明请查看 [扫描流程架构文档](./docs/scan-flow-architecture.md)
### 🖥️ 分布式架构 ### 分布式架构
- **多节点扫描** - 支持部署多个 Worker 节点,横向扩展扫描能力 - **多节点扫描** - 支持部署多个 Worker 节点,横向扩展扫描能力
- **本地节点** - 零配置,安装即自动注册本地 Docker Worker - **本地节点** - 零配置,安装即自动注册本地 Docker Worker
- **远程节点** - SSH 一键部署远程 VPS 作为扫描节点 - **远程节点** - SSH 一键部署远程 VPS 作为扫描节点
@@ -181,7 +180,7 @@ flowchart TB
W3 -.心跳上报.-> REDIS W3 -.心跳上报.-> REDIS
``` ```
### 🔎 全局资产搜索 ### 全局资产搜索
- **多类型搜索** - 支持 Website 和 Endpoint 两种资产类型 - **多类型搜索** - 支持 Website 和 Endpoint 两种资产类型
- **表达式语法** - 支持 `=`(模糊)、`==`(精确)、`!=`(不等于)操作符 - **表达式语法** - 支持 `=`(模糊)、`==`(精确)、`!=`(不等于)操作符
- **逻辑组合** - 支持 `&&` (AND) 和 `||` (OR) 逻辑组合 - **逻辑组合** - 支持 `&&` (AND) 和 `||` (OR) 逻辑组合
@@ -205,14 +204,14 @@ host="admin" && tech="php" && status=="200"
url="/api/v1" && status!="404" url="/api/v1" && status!="404"
``` ```
### 📊 可视化界面 ### 可视化界面
- **数据统计** - 资产/漏洞统计仪表盘 - **数据统计** - 资产/漏洞统计仪表盘
- **实时通知** - WebSocket 消息推送 - **实时通知** - WebSocket 消息推送
- **通知推送** - 实时企业微信tgdiscard消息推送服务 - **通知推送** - 实时企业微信tgdiscard消息推送服务
--- ---
## 📦 快速开始 ## 快速开始
### 环境要求 ### 环境要求
@@ -230,11 +229,11 @@ cd xingrin
# 安装并启动(生产模式) # 安装并启动(生产模式)
sudo ./install.sh sudo ./install.sh
# 🇨🇳 中国大陆用户推荐使用镜像加速(第三方加速服务可能会失效,不保证长期可用) # 中国大陆用户推荐使用镜像加速(第三方加速服务可能会失效,不保证长期可用)
sudo ./install.sh --mirror sudo ./install.sh --mirror
``` ```
> **💡 --mirror 参数说明** > **--mirror 参数说明**
> - 自动配置 Docker 镜像加速(国内镜像源) > - 自动配置 Docker 镜像加速(国内镜像源)
> - 加速 Git 仓库克隆Nuclei 模板等) > - 加速 Git 仓库克隆Nuclei 模板等)
@@ -259,17 +258,17 @@ sudo ./restart.sh
sudo ./uninstall.sh sudo ./uninstall.sh
``` ```
## 🤝 反馈与贡献 ## 反馈与贡献
- 💡 **发现 Bug有新想法比如UI设计功能设计等** 欢迎点击右边链接进行提交建议 [Issue](https://github.com/yyhuni/xingrin/issues) 或者公众号私信 - **发现 Bug有新想法比如UI设计功能设计等** 欢迎点击右边链接进行提交建议 [Issue](https://github.com/yyhuni/xingrin/issues) 或者公众号私信
## 📧 联系 ## 联系
- 微信公众号: **塔罗安全学苑** - 微信公众号: **塔罗安全学苑**
- 微信群去公众号底下的菜单,有个交流群,点击就可以看到了,链接过期可以私信我拉你 - 微信群去公众号底下的菜单,有个交流群,点击就可以看到了,链接过期可以私信我拉你
<img src="docs/wechat-qrcode.png" alt="微信公众号" width="200"> <img src="docs/wechat-qrcode.png" alt="微信公众号" width="200">
### 🎁 关注公众号免费领取指纹库 ### 关注公众号免费领取指纹库
| 指纹库 | 数量 | | 指纹库 | 数量 |
|--------|------| |--------|------|
@@ -278,9 +277,9 @@ sudo ./uninstall.sh
| goby.json | 7,086 | | goby.json | 7,086 |
| FingerprintHub.json | 3,147 | | FingerprintHub.json | 3,147 |
> 💡 关注公众号回复「指纹」即可获取 > 关注公众号回复「指纹」即可获取
## 赞助支持 ## 赞助支持
如果这个项目对你有帮助谢谢请我能喝杯蜜雪冰城你的star和赞助是我免费更新的动力 如果这个项目对你有帮助谢谢请我能喝杯蜜雪冰城你的star和赞助是我免费更新的动力
@@ -289,14 +288,9 @@ sudo ./uninstall.sh
<img src="docs/zfb_pay.jpg" alt="支付宝" width="200"> <img src="docs/zfb_pay.jpg" alt="支付宝" width="200">
</p> </p>
### 🙏 感谢以下赞助
| 昵称 | 金额 |
|------|------|
| X闭关中 | ¥88 |
## ⚠️ 免责声明 ## 免责声明
**重要:请在使用前仔细阅读** **重要:请在使用前仔细阅读**
@@ -311,30 +305,29 @@ sudo ./uninstall.sh
- 遵守所在地区的法律法规 - 遵守所在地区的法律法规
- 承担因滥用产生的一切后果 - 承担因滥用产生的一切后果
## 🌟 Star History ## Star History
如果这个项目对你有帮助,请给一个 Star 支持一下! 如果这个项目对你有帮助,请给一个 Star 支持一下!
[![Star History Chart](https://api.star-history.com/svg?repos=yyhuni/xingrin&type=Date)](https://star-history.com/#yyhuni/xingrin&Date) [![Star History Chart](https://api.star-history.com/svg?repos=yyhuni/xingrin&type=Date)](https://star-history.com/#yyhuni/xingrin&Date)
## 📄 许可证 ## 许可证
本项目采用 [GNU General Public License v3.0](LICENSE) 许可证。 本项目采用 [GNU General Public License v3.0](LICENSE) 许可证。
### 允许的用途 ### 允许的用途
- 个人学习和研究 - 个人学习和研究
- 商业和非商业使用 - 商业和非商业使用
- 修改和分发 - 修改和分发
- 专利使用 - 专利使用
- 私人使用 - 私人使用
### 义务和限制 ### 义务和限制
- 📋 **开源义务**:分发时必须提供源代码 - **开源义务**:分发时必须提供源代码
- 📋 **相同许可**:衍生作品必须使用相同许可证 - **相同许可**:衍生作品必须使用相同许可证
- 📋 **版权声明**:必须保留原始版权和许可证声明 - **版权声明**:必须保留原始版权和许可证声明
- **责任免除**:不提供任何担保 - **责任免除**:不提供任何担保
- 未经授权的渗透测试 - 未经授权的渗透测试
- 任何违法行为 - 任何违法行为

View File

@@ -1 +1 @@
v1.5.4-dev v1.5.8

View File

@@ -14,6 +14,7 @@ from .views import (
LoginView, LogoutView, MeView, ChangePasswordView, LoginView, LogoutView, MeView, ChangePasswordView,
SystemLogsView, SystemLogFilesView, HealthCheckView, SystemLogsView, SystemLogFilesView, HealthCheckView,
GlobalBlacklistView, GlobalBlacklistView,
VersionView, CheckUpdateView,
) )
urlpatterns = [ urlpatterns = [
@@ -29,6 +30,8 @@ urlpatterns = [
# 系统管理 # 系统管理
path('system/logs/', SystemLogsView.as_view(), name='system-logs'), path('system/logs/', SystemLogsView.as_view(), name='system-logs'),
path('system/logs/files/', SystemLogFilesView.as_view(), name='system-log-files'), path('system/logs/files/', SystemLogFilesView.as_view(), name='system-log-files'),
path('system/version/', VersionView.as_view(), name='system-version'),
path('system/check-update/', CheckUpdateView.as_view(), name='system-check-update'),
# 黑名单管理PUT 全量替换模式) # 黑名单管理PUT 全量替换模式)
path('blacklist/rules/', GlobalBlacklistView.as_view(), name='blacklist-rules'), path('blacklist/rules/', GlobalBlacklistView.as_view(), name='blacklist-rules'),

View File

@@ -6,16 +6,19 @@
- 认证相关视图:登录、登出、用户信息、修改密码 - 认证相关视图:登录、登出、用户信息、修改密码
- 系统日志视图:实时日志查看 - 系统日志视图:实时日志查看
- 黑名单视图:全局黑名单规则管理 - 黑名单视图:全局黑名单规则管理
- 版本视图:系统版本和更新检查
""" """
from .health_views import HealthCheckView from .health_views import HealthCheckView
from .auth_views import LoginView, LogoutView, MeView, ChangePasswordView from .auth_views import LoginView, LogoutView, MeView, ChangePasswordView
from .system_log_views import SystemLogsView, SystemLogFilesView from .system_log_views import SystemLogsView, SystemLogFilesView
from .blacklist_views import GlobalBlacklistView from .blacklist_views import GlobalBlacklistView
from .version_views import VersionView, CheckUpdateView
__all__ = [ __all__ = [
'HealthCheckView', 'HealthCheckView',
'LoginView', 'LogoutView', 'MeView', 'ChangePasswordView', 'LoginView', 'LogoutView', 'MeView', 'ChangePasswordView',
'SystemLogsView', 'SystemLogFilesView', 'SystemLogsView', 'SystemLogFilesView',
'GlobalBlacklistView', 'GlobalBlacklistView',
'VersionView', 'CheckUpdateView',
] ]

View File

@@ -0,0 +1,136 @@
"""
系统版本相关视图
"""
import logging
from pathlib import Path
import requests
from rest_framework.request import Request
from rest_framework.response import Response
from rest_framework.views import APIView
from apps.common.error_codes import ErrorCodes
from apps.common.response_helpers import error_response, success_response
logger = logging.getLogger(__name__)
# GitHub 仓库信息
GITHUB_REPO = "yyhuni/xingrin"
GITHUB_API_URL = f"https://api.github.com/repos/{GITHUB_REPO}/releases/latest"
GITHUB_RELEASES_URL = f"https://github.com/{GITHUB_REPO}/releases"
def get_current_version() -> str:
"""读取当前版本号"""
import os
# 方式1从环境变量读取Docker 容器中推荐)
version = os.environ.get('IMAGE_TAG', '')
if version:
return version
# 方式2从文件读取开发环境
possible_paths = [
Path('/app/VERSION'),
Path(__file__).parent.parent.parent.parent.parent / 'VERSION',
]
for path in possible_paths:
try:
return path.read_text(encoding='utf-8').strip()
except (FileNotFoundError, OSError):
continue
return "unknown"
def compare_versions(current: str, latest: str) -> bool:
"""
比较版本号,判断是否有更新
Returns:
True 表示有更新可用
"""
def parse_version(v: str) -> tuple:
v = v.lstrip('v')
parts = v.split('.')
result = []
for part in parts:
if '-' in part:
num, _ = part.split('-', 1)
result.append(int(num))
else:
result.append(int(part))
return tuple(result)
try:
return parse_version(latest) > parse_version(current)
except (ValueError, AttributeError):
return False
class VersionView(APIView):
"""获取当前系统版本"""
def get(self, _request: Request) -> Response:
"""获取当前版本信息"""
return success_response(data={
'version': get_current_version(),
'github_repo': GITHUB_REPO,
})
class CheckUpdateView(APIView):
"""检查系统更新"""
def get(self, _request: Request) -> Response:
"""
检查是否有新版本
Returns:
- current_version: 当前版本
- latest_version: 最新版本
- has_update: 是否有更新
- release_url: 发布页面 URL
- release_notes: 更新说明(如果有)
"""
current_version = get_current_version()
try:
response = requests.get(
GITHUB_API_URL,
headers={'Accept': 'application/vnd.github.v3+json'},
timeout=10
)
if response.status_code == 404:
return success_response(data={
'current_version': current_version,
'latest_version': current_version,
'has_update': False,
'release_url': GITHUB_RELEASES_URL,
'release_notes': None,
})
response.raise_for_status()
release_data = response.json()
latest_version = release_data.get('tag_name', current_version)
has_update = compare_versions(current_version, latest_version)
return success_response(data={
'current_version': current_version,
'latest_version': latest_version,
'has_update': has_update,
'release_url': release_data.get('html_url', GITHUB_RELEASES_URL),
'release_notes': release_data.get('body'),
'published_at': release_data.get('published_at'),
})
except requests.RequestException as e:
logger.warning("检查更新失败: %s", e)
return error_response(
code=ErrorCodes.SERVER_ERROR,
message="无法连接到 GitHub请稍后重试",
)

View File

@@ -2,8 +2,9 @@
初始化默认扫描引擎 初始化默认扫描引擎
用法: 用法:
python manage.py init_default_engine # 只创建不存在的引擎(不覆盖已有) python manage.py init_default_engine # 只创建不存在的引擎(不覆盖已有)
python manage.py init_default_engine --force # 强制覆盖所有引擎配置 python manage.py init_default_engine --force # 强制覆盖所有引擎配置
python manage.py init_default_engine --force-sub # 只覆盖子引擎,保留 full scan
cd /root/my-vulun-scan/docker cd /root/my-vulun-scan/docker
docker compose exec server python backend/manage.py init_default_engine --force docker compose exec server python backend/manage.py init_default_engine --force
@@ -12,6 +13,7 @@
- 读取 engine_config_example.yaml 作为默认配置 - 读取 engine_config_example.yaml 作为默认配置
- 创建 full scan默认引擎+ 各扫描类型的子引擎 - 创建 full scan默认引擎+ 各扫描类型的子引擎
- 默认不覆盖已有配置,加 --force 才会覆盖 - 默认不覆盖已有配置,加 --force 才会覆盖
- 加 --force-sub 只覆盖子引擎配置,保留用户自定义的 full scan
""" """
from django.core.management.base import BaseCommand from django.core.management.base import BaseCommand
@@ -30,11 +32,18 @@ class Command(BaseCommand):
parser.add_argument( parser.add_argument(
'--force', '--force',
action='store_true', action='store_true',
help='强制覆盖已有的引擎配置', help='强制覆盖已有的引擎配置(包括 full scan 和子引擎)',
)
parser.add_argument(
'--force-sub',
action='store_true',
help='只覆盖子引擎配置,保留 full scan升级时使用',
) )
def handle(self, *args, **options): def handle(self, *args, **options):
force = options.get('force', False) force = options.get('force', False)
force_sub = options.get('force_sub', False)
# 读取默认配置文件 # 读取默认配置文件
config_path = Path(__file__).resolve().parent.parent.parent.parent / 'scan' / 'configs' / 'engine_config_example.yaml' config_path = Path(__file__).resolve().parent.parent.parent.parent / 'scan' / 'configs' / 'engine_config_example.yaml'
@@ -99,15 +108,22 @@ class Command(BaseCommand):
engine_name = f"{scan_type}" engine_name = f"{scan_type}"
sub_engine = ScanEngine.objects.filter(name=engine_name).first() sub_engine = ScanEngine.objects.filter(name=engine_name).first()
if sub_engine: if sub_engine:
if force: # force 或 force_sub 都会覆盖子引擎
if force or force_sub:
sub_engine.configuration = single_yaml sub_engine.configuration = single_yaml
sub_engine.save() sub_engine.save()
self.stdout.write(self.style.SUCCESS(f' ✓ 子引擎 {engine_name} 配置已更新 (ID: {sub_engine.id})')) self.stdout.write(self.style.SUCCESS(
f' ✓ 子引擎 {engine_name} 配置已更新 (ID: {sub_engine.id})'
))
else: else:
self.stdout.write(self.style.WARNING(f'{engine_name} 已存在,跳过(使用 --force 覆盖)')) self.stdout.write(self.style.WARNING(
f'{engine_name} 已存在,跳过(使用 --force 覆盖)'
))
else: else:
sub_engine = ScanEngine.objects.create( sub_engine = ScanEngine.objects.create(
name=engine_name, name=engine_name,
configuration=single_yaml, configuration=single_yaml,
) )
self.stdout.write(self.style.SUCCESS(f' ✓ 子引擎 {engine_name} 已创建 (ID: {sub_engine.id})')) self.stdout.write(self.style.SUCCESS(
f' ✓ 子引擎 {engine_name} 已创建 (ID: {sub_engine.id})'
))

View File

@@ -44,6 +44,8 @@ services:
restart: always restart: always
env_file: env_file:
- .env - .env
environment:
- IMAGE_TAG=${IMAGE_TAG:-dev}
ports: ports:
- "8888:8888" - "8888:8888"
depends_on: depends_on:

View File

@@ -48,6 +48,8 @@ services:
restart: always restart: always
env_file: env_file:
- .env - .env
environment:
- IMAGE_TAG=${IMAGE_TAG}
depends_on: depends_on:
redis: redis:
condition: service_healthy condition: service_healthy

View File

@@ -83,20 +83,20 @@ if not yaml_path.exists():
print('未找到配置文件,跳过') print('未找到配置文件,跳过')
exit(0) exit(0)
new_config = yaml_path.read_text()
# 检查是否已有 full scan 引擎 # 检查是否已有 full scan 引擎
engine = ScanEngine.objects.filter(name='full scan').first() engine = ScanEngine.objects.filter(name='full scan').first()
if engine: if engine:
if not engine.configuration or not engine.configuration.strip(): # 直接覆盖为最新配置
engine.configuration = yaml_path.read_text() engine.configuration = new_config
engine.save(update_fields=['configuration']) engine.save(update_fields=['configuration'])
print(f'已初始化引擎配置: {engine.name}') print(f'已更新引擎配置: {engine.name}')
else:
print(f'引擎已有配置,跳过')
else: else:
# 创建引擎 # 创建引擎
engine = ScanEngine.objects.create( engine = ScanEngine.objects.create(
name='full scan', name='full scan',
configuration=yaml_path.read_text(), configuration=new_config,
) )
print(f'已创建引擎: {engine.name}') print(f'已创建引擎: {engine.name}')
" "

View File

@@ -10,7 +10,7 @@ python manage.py migrate --noinput
echo " ✓ 数据库迁移完成" echo " ✓ 数据库迁移完成"
echo " [1.1/3] 初始化默认扫描引擎..." echo " [1.1/3] 初始化默认扫描引擎..."
python manage.py init_default_engine python manage.py init_default_engine --force
echo " ✓ 默认扫描引擎已就绪" echo " ✓ 默认扫描引擎已就绪"
echo " [1.2/3] 初始化默认目录字典..." echo " [1.2/3] 初始化默认目录字典..."

View File

@@ -182,7 +182,7 @@ echo -e "${BOLD}${GREEN}══════════════════
echo "" echo ""
echo -e "${BOLD}访问地址${NC}" echo -e "${BOLD}访问地址${NC}"
if [ "$WITH_FRONTEND" = true ]; then if [ "$WITH_FRONTEND" = true ]; then
echo -e " XingRin: ${CYAN}https://${ACCESS_HOST}/${NC}" echo -e " XingRin: ${CYAN}https://${ACCESS_HOST}:8083/${NC}"
echo -e " ${YELLOW}(HTTP 会自动跳转到 HTTPS)${NC}" echo -e " ${YELLOW}(HTTP 会自动跳转到 HTTPS)${NC}"
else else
echo -e " API: ${CYAN}通过前端或 nginx 访问(后端未暴露 8888${NC}" echo -e " API: ${CYAN}通过前端或 nginx 访问(后端未暴露 8888${NC}"
@@ -191,8 +191,3 @@ else
echo " cd frontend && pnpm dev" echo " cd frontend && pnpm dev"
fi fi
echo "" echo ""
echo -e "${BOLD}默认账号${NC}"
echo " 用户名: admin"
echo " 密码: admin"
echo -e " ${YELLOW}[!] 请首次登录后修改密码${NC}"
echo ""

View File

@@ -0,0 +1,189 @@
"use client"
import { useState } from 'react'
import { useTranslations } from 'next-intl'
import { useQueryClient } from '@tanstack/react-query'
import {
IconRadar,
IconRefresh,
IconExternalLink,
IconBrandGithub,
IconMessageReport,
IconBook,
IconFileText,
IconCheck,
IconArrowUp,
} from '@tabler/icons-react'
import {
Dialog,
DialogContent,
DialogHeader,
DialogTitle,
DialogTrigger,
} from '@/components/ui/dialog'
import { Button } from '@/components/ui/button'
import { Separator } from '@/components/ui/separator'
import { Badge } from '@/components/ui/badge'
import { useVersion } from '@/hooks/use-version'
import { VersionService } from '@/services/version.service'
import type { UpdateCheckResult } from '@/types/version.types'
interface AboutDialogProps {
children: React.ReactNode
}
export function AboutDialog({ children }: AboutDialogProps) {
const t = useTranslations('about')
const { data: versionData } = useVersion()
const queryClient = useQueryClient()
const [isChecking, setIsChecking] = useState(false)
const [updateResult, setUpdateResult] = useState<UpdateCheckResult | null>(null)
const [checkError, setCheckError] = useState<string | null>(null)
const handleCheckUpdate = async () => {
setIsChecking(true)
setCheckError(null)
try {
const result = await VersionService.checkUpdate()
setUpdateResult(result)
queryClient.setQueryData(['check-update'], result)
} catch {
setCheckError(t('checkFailed'))
} finally {
setIsChecking(false)
}
}
const currentVersion = updateResult?.currentVersion || versionData?.version || '-'
const latestVersion = updateResult?.latestVersion
const hasUpdate = updateResult?.hasUpdate
return (
<Dialog>
<DialogTrigger asChild>
{children}
</DialogTrigger>
<DialogContent className="sm:max-w-md">
<DialogHeader>
<DialogTitle>{t('title')}</DialogTitle>
</DialogHeader>
<div className="space-y-6">
{/* Logo and name */}
<div className="flex flex-col items-center py-4">
<div className="flex h-16 w-16 items-center justify-center rounded-2xl bg-primary/10 mb-3">
<IconRadar className="h-8 w-8 text-primary" />
</div>
<h2 className="text-xl font-semibold">XingRin</h2>
<p className="text-sm text-muted-foreground">{t('description')}</p>
</div>
{/* Version info */}
<div className="rounded-lg border p-4 space-y-3">
<div className="flex items-center justify-between">
<span className="text-sm text-muted-foreground">{t('currentVersion')}</span>
<span className="font-mono text-sm">{currentVersion}</span>
</div>
{updateResult && (
<div className="flex items-center justify-between">
<span className="text-sm text-muted-foreground">{t('latestVersion')}</span>
<div className="flex items-center gap-2">
<span className="font-mono text-sm">{latestVersion}</span>
{hasUpdate ? (
<Badge variant="default" className="gap-1">
<IconArrowUp className="h-3 w-3" />
{t('updateAvailable')}
</Badge>
) : (
<Badge variant="secondary" className="gap-1">
<IconCheck className="h-3 w-3" />
{t('upToDate')}
</Badge>
)}
</div>
</div>
)}
{checkError && (
<p className="text-sm text-destructive">{checkError}</p>
)}
<div className="flex gap-2">
<Button
variant="outline"
size="sm"
className="flex-1"
onClick={handleCheckUpdate}
disabled={isChecking}
>
<IconRefresh className={`h-4 w-4 mr-2 ${isChecking ? 'animate-spin' : ''}`} />
{isChecking ? t('checking') : t('checkUpdate')}
</Button>
{hasUpdate && updateResult?.releaseUrl && (
<Button
variant="default"
size="sm"
className="flex-1"
asChild
>
<a href={updateResult.releaseUrl} target="_blank" rel="noopener noreferrer">
<IconExternalLink className="h-4 w-4 mr-2" />
{t('viewRelease')}
</a>
</Button>
)}
</div>
{hasUpdate && (
<div className="rounded-md bg-muted p-3 text-sm text-muted-foreground">
<p>{t('updateHint')}</p>
<code className="mt-1 block rounded bg-background px-2 py-1 font-mono text-xs">
sudo ./update.sh
</code>
</div>
)}
</div>
<Separator />
{/* Links */}
<div className="grid grid-cols-2 gap-2">
<Button variant="ghost" size="sm" className="justify-start" asChild>
<a href="https://github.com/yyhuni/xingrin" target="_blank" rel="noopener noreferrer">
<IconBrandGithub className="h-4 w-4 mr-2" />
GitHub
</a>
</Button>
<Button variant="ghost" size="sm" className="justify-start" asChild>
<a href="https://github.com/yyhuni/xingrin/releases" target="_blank" rel="noopener noreferrer">
<IconFileText className="h-4 w-4 mr-2" />
{t('changelog')}
</a>
</Button>
<Button variant="ghost" size="sm" className="justify-start" asChild>
<a href="https://github.com/yyhuni/xingrin/issues" target="_blank" rel="noopener noreferrer">
<IconMessageReport className="h-4 w-4 mr-2" />
{t('feedback')}
</a>
</Button>
<Button variant="ghost" size="sm" className="justify-start" asChild>
<a href="https://github.com/yyhuni/xingrin#readme" target="_blank" rel="noopener noreferrer">
<IconBook className="h-4 w-4 mr-2" />
{t('docs')}
</a>
</Button>
</div>
{/* Footer */}
<p className="text-center text-xs text-muted-foreground">
© 2025 XingRin · MIT License
</p>
</div>
</DialogContent>
</Dialog>
)
}

View File

@@ -5,7 +5,6 @@ import type * as React from "react"
// Import various icons from Tabler Icons library // Import various icons from Tabler Icons library
import { import {
IconDashboard, // Dashboard icon IconDashboard, // Dashboard icon
IconHelp, // Help icon
IconListDetails, // List details icon IconListDetails, // List details icon
IconSettings, // Settings icon IconSettings, // Settings icon
IconUsers, // Users icon IconUsers, // Users icon
@@ -15,10 +14,10 @@ import {
IconServer, // Server icon IconServer, // Server icon
IconTerminal2, // Terminal icon IconTerminal2, // Terminal icon
IconBug, // Vulnerability icon IconBug, // Vulnerability icon
IconMessageReport, // Feedback icon
IconSearch, // Search icon IconSearch, // Search icon
IconKey, // API Key icon IconKey, // API Key icon
IconBan, // Blacklist icon IconBan, // Blacklist icon
IconInfoCircle, // About icon
} from "@tabler/icons-react" } from "@tabler/icons-react"
// Import internationalization hook // Import internationalization hook
import { useTranslations } from 'next-intl' import { useTranslations } from 'next-intl'
@@ -27,8 +26,8 @@ import { Link, usePathname } from '@/i18n/navigation'
// Import custom navigation components // Import custom navigation components
import { NavSystem } from "@/components/nav-system" import { NavSystem } from "@/components/nav-system"
import { NavSecondary } from "@/components/nav-secondary"
import { NavUser } from "@/components/nav-user" import { NavUser } from "@/components/nav-user"
import { AboutDialog } from "@/components/about-dialog"
// Import sidebar UI components // Import sidebar UI components
import { import {
Sidebar, Sidebar,
@@ -139,20 +138,6 @@ export function AppSidebar({ ...props }: React.ComponentProps<typeof Sidebar>) {
}, },
] ]
// Secondary navigation menu items
const navSecondary = [
{
title: t('feedback'),
url: "https://github.com/yyhuni/xingrin/issues",
icon: IconMessageReport,
},
{
title: t('help'),
url: "https://github.com/yyhuni/xingrin",
icon: IconHelp,
},
]
// System settings related menu items // System settings related menu items
const documents = [ const documents = [
{ {
@@ -271,8 +256,21 @@ export function AppSidebar({ ...props }: React.ComponentProps<typeof Sidebar>) {
{/* System settings navigation menu */} {/* System settings navigation menu */}
<NavSystem items={documents} /> <NavSystem items={documents} />
{/* Secondary navigation menu, using mt-auto to push to bottom */} {/* About system button */}
<NavSecondary items={navSecondary} className="mt-auto" /> <SidebarGroup className="mt-auto">
<SidebarGroupContent>
<SidebarMenu>
<SidebarMenuItem>
<AboutDialog>
<SidebarMenuButton>
<IconInfoCircle />
<span>{t('about')}</span>
</SidebarMenuButton>
</AboutDialog>
</SidebarMenuItem>
</SidebarMenu>
</SidebarGroupContent>
</SidebarGroup>
</SidebarContent> </SidebarContent>
{/* Sidebar footer */} {/* Sidebar footer */}

View File

@@ -58,14 +58,6 @@ subdomain_discovery:
enabled: true enabled: true
timeout: 600 # 10 minutes (required) timeout: 600 # 10 minutes (required)
amass_passive:
enabled: true
timeout: 600 # 10 minutes (required)
amass_active:
enabled: true
timeout: 1800 # 30 minutes (required)
sublist3r: sublist3r:
enabled: true enabled: true
timeout: 900 # 15 minutes (required) timeout: 900 # 15 minutes (required)

View File

@@ -0,0 +1,19 @@
import { useQuery } from '@tanstack/react-query'
import { VersionService } from '@/services/version.service'
export function useVersion() {
return useQuery({
queryKey: ['version'],
queryFn: () => VersionService.getVersion(),
staleTime: Infinity,
})
}
export function useCheckUpdate() {
return useQuery({
queryKey: ['check-update'],
queryFn: () => VersionService.checkUpdate(),
enabled: false, // 手动触发
staleTime: 5 * 60 * 1000, // 5 分钟缓存
})
}

View File

@@ -325,8 +325,7 @@
"notifications": "Notifications", "notifications": "Notifications",
"apiKeys": "API Keys", "apiKeys": "API Keys",
"globalBlacklist": "Global Blacklist", "globalBlacklist": "Global Blacklist",
"help": "Get Help", "about": "About"
"feedback": "Feedback"
}, },
"search": { "search": {
"title": "Asset Search", "title": "Asset Search",
@@ -2292,5 +2291,21 @@
"conflict": "Resource conflict, please check and try again", "conflict": "Resource conflict, please check and try again",
"unauthorized": "Please login first", "unauthorized": "Please login first",
"rateLimited": "Too many requests, please try again later" "rateLimited": "Too many requests, please try again later"
},
"about": {
"title": "About XingRin",
"description": "Attack Surface Management Platform",
"currentVersion": "Current Version",
"latestVersion": "Latest Version",
"checkUpdate": "Check Update",
"checking": "Checking...",
"checkFailed": "Failed to check update, please try again later",
"updateAvailable": "Update Available",
"upToDate": "Up to Date",
"viewRelease": "View Release",
"updateHint": "Run the following command in project root to update:",
"changelog": "Changelog",
"feedback": "Feedback",
"docs": "Documentation"
} }
} }

View File

@@ -325,8 +325,7 @@
"notifications": "通知设置", "notifications": "通知设置",
"apiKeys": "API 密钥", "apiKeys": "API 密钥",
"globalBlacklist": "全局黑名单", "globalBlacklist": "全局黑名单",
"help": "获取帮助", "about": "关于系统"
"feedback": "反馈建议"
}, },
"search": { "search": {
"title": "资产搜索", "title": "资产搜索",
@@ -2292,5 +2291,21 @@
"conflict": "资源冲突,请检查后重试", "conflict": "资源冲突,请检查后重试",
"unauthorized": "请先登录", "unauthorized": "请先登录",
"rateLimited": "请求过于频繁,请稍后重试" "rateLimited": "请求过于频繁,请稍后重试"
},
"about": {
"title": "关于 XingRin",
"description": "攻击面管理平台",
"currentVersion": "当前版本",
"latestVersion": "最新版本",
"checkUpdate": "检查更新",
"checking": "检查中...",
"checkFailed": "检查更新失败,请稍后重试",
"updateAvailable": "有更新",
"upToDate": "已是最新",
"viewRelease": "查看发布",
"updateHint": "在项目根目录运行以下命令更新:",
"changelog": "更新日志",
"feedback": "问题反馈",
"docs": "使用文档"
} }
} }

View File

@@ -9,6 +9,10 @@ export const mockNotificationSettings: NotificationSettings = {
enabled: true, enabled: true,
webhookUrl: 'https://discord.com/api/webhooks/1234567890/abcdefghijklmnop', webhookUrl: 'https://discord.com/api/webhooks/1234567890/abcdefghijklmnop',
}, },
wecom: {
enabled: false,
webhookUrl: '',
},
categories: { categories: {
scan: true, scan: true,
vulnerability: true, vulnerability: true,
@@ -30,6 +34,7 @@ export function updateMockNotificationSettings(
return { return {
message: 'Notification settings updated successfully', message: 'Notification settings updated successfully',
discord: mockNotificationSettings.discord, discord: mockNotificationSettings.discord,
wecom: mockNotificationSettings.wecom,
categories: mockNotificationSettings.categories, categories: mockNotificationSettings.categories,
} }
} }

View File

@@ -0,0 +1,14 @@
import { api } from '@/lib/api-client'
import type { VersionInfo, UpdateCheckResult } from '@/types/version.types'
export class VersionService {
static async getVersion(): Promise<VersionInfo> {
const res = await api.get<VersionInfo>('/system/version/')
return res.data
}
static async checkUpdate(): Promise<UpdateCheckResult> {
const res = await api.get<UpdateCheckResult>('/system/check-update/')
return res.data
}
}

View File

@@ -0,0 +1,13 @@
export interface VersionInfo {
version: string
githubRepo: string
}
export interface UpdateCheckResult {
currentVersion: string
latestVersion: string
hasUpdate: boolean
releaseUrl: string
releaseNotes: string | null
publishedAt: string | null
}

108
update.sh
View File

@@ -21,8 +21,8 @@ cd "$(dirname "$0")"
# 权限检查 # 权限检查
if [ "$EUID" -ne 0 ]; then if [ "$EUID" -ne 0 ]; then
echo -e "\033[0;31m[错误] 请使用 sudo 运行此脚本\033[0m" printf "\033[0;31m 请使用 sudo 运行此脚本\033[0m\n"
echo -e " 正确用法: \033[1msudo ./update.sh\033[0m" printf " 正确用法: \033[1msudo ./update.sh\033[0m\n"
exit 1 exit 1
fi fi
@@ -49,9 +49,17 @@ YELLOW='\033[1;33m'
RED='\033[0;31m' RED='\033[0;31m'
BLUE='\033[0;34m' BLUE='\033[0;34m'
CYAN='\033[0;36m' CYAN='\033[0;36m'
DIM='\033[2m'
BOLD='\033[1m' BOLD='\033[1m'
NC='\033[0m' NC='\033[0m'
# 日志函数
log_step() { printf "${CYAN}${NC} %s\n" "$1"; }
log_ok() { printf " ${GREEN}${NC} %s\n" "$1"; }
log_info() { printf " ${DIM}${NC} %s\n" "$1"; }
log_warn() { printf " ${YELLOW}!${NC} %s\n" "$1"; }
log_error() { printf "${RED}${NC} %s\n" "$1"; }
# 合并 .env 新配置项(保留用户已有值) # 合并 .env 新配置项(保留用户已有值)
merge_env_config() { merge_env_config() {
local example_file="docker/.env.example" local example_file="docker/.env.example"
@@ -70,58 +78,68 @@ merge_env_config() {
if ! grep -q "^${key}=" "$env_file"; then if ! grep -q "^${key}=" "$env_file"; then
printf '%s\n' "$line" >> "$env_file" printf '%s\n' "$line" >> "$env_file"
echo -e " ${GREEN}+${NC} 新增: $key" log_info "新增配置: $key"
((new_keys++)) ((new_keys++))
fi fi
done < "$example_file" done < "$example_file"
if [ $new_keys -gt 0 ]; then if [ $new_keys -gt 0 ]; then
echo -e " ${GREEN}OK${NC} 已添加 $new_keys 个新配置项" log_ok "已添加 $new_keys 个新配置项"
else else
echo -e " ${GREEN}OK${NC} 配置已是最新" log_ok "配置已是最新"
fi fi
} }
echo "" # 显示标题
echo -e "${BOLD}${BLUE}╔════════════════════════════════════════╗${NC}" printf "\n"
printf "${BOLD}${BLUE}┌────────────────────────────────────────┐${NC}\n"
if [ "$DEV_MODE" = true ]; then if [ "$DEV_MODE" = true ]; then
echo -e "${BOLD}${BLUE}║ 开发环境更新(本地构建) ║${NC}" printf "${BOLD}${BLUE}${NC} ${BOLD}XingRin 系统更新${NC} ${BOLD}${BLUE}${NC}\n"
printf "${BOLD}${BLUE}${NC} ${DIM}开发模式 · 本地构建${NC} ${BOLD}${BLUE}${NC}\n"
else else
echo -e "${BOLD}${BLUE}║ 生产环境更新Docker Hub${NC}" printf "${BOLD}${BLUE}${NC} ${BOLD}XingRin 系统更新${NC} ${BOLD}${BLUE}${NC}\n"
printf "${BOLD}${BLUE}${NC} ${DIM}生产模式 · Docker Hub${NC} ${BOLD}${BLUE}${NC}\n"
fi fi
echo -e "${BOLD}${BLUE}╚════════════════════════════════════════╝${NC}" printf "${BOLD}${BLUE}└────────────────────────────────────────┘${NC}\n"
echo "" printf "\n"
# 测试性功能警告 # 警告提示
echo -e "${BOLD}${YELLOW}[!] 警告:此功能为测试性功能,可能会导致升级失败${NC}" printf "${YELLOW}┌─ 注意事项 ─────────────────────────────┐${NC}\n"
echo -e "${YELLOW} 建议运行 ./uninstall.sh 后重新执行 ./install.sh 进行全新安装${NC}" printf "${YELLOW}${NC} • 此功能为测试性功能,可能导致升级失败 ${YELLOW}${NC}\n"
echo "" printf "${YELLOW}${NC} • 升级会覆盖所有默认引擎配置 ${YELLOW}${NC}\n"
echo -n -e "${YELLOW}是否继续更新?(y/N) ${NC}" printf "${YELLOW}${NC} • 自定义配置请先备份或创建新引擎 ${YELLOW}${NC}\n"
printf "${YELLOW}${NC} • 推荐:卸载后重新安装以获得最佳体验 ${YELLOW}${NC}\n"
printf "${YELLOW}└────────────────────────────────────────┘${NC}\n"
printf "\n"
printf "${YELLOW}是否继续更新?${NC} [y/N] "
read -r ans_continue read -r ans_continue
ans_continue=${ans_continue:-N} ans_continue=${ans_continue:-N}
if [[ ! $ans_continue =~ ^[Yy]$ ]]; then if [[ ! $ans_continue =~ ^[Yy]$ ]]; then
echo -e "${CYAN}已取消更新${NC}" printf "\n${DIM}已取消更新${NC}\n"
exit 0 exit 0
fi fi
echo "" printf "\n"
# Step 1: 停止服务 # Step 1: 停止服务
echo -e "${CYAN}[1/5]${NC} 停止服务..." log_step "停止服务..."
./stop.sh 2>&1 | sed 's/^/ /' ./stop.sh 2>&1 | sed 's/^/ /'
log_ok "服务已停止"
# Step 2: 拉取代码 # Step 2: 拉取代码
echo "" printf "\n"
echo -e "${CYAN}[2/5]${NC} 拉取代码..." log_step "拉取最新代码..."
git pull --rebase 2>&1 | sed 's/^/ /' if git pull --rebase 2>&1 | sed 's/^/ /'; then
if [ $? -ne 0 ]; then log_ok "代码已更新"
echo -e "${RED}[错误]${NC} git pull 失败,请手动解决冲突后重试" else
log_error "git pull 失败,请手动解决冲突后重试"
exit 1 exit 1
fi fi
# Step 3: 检查配置更新 + 版本同步 # Step 3: 检查配置更新 + 版本同步
echo "" printf "\n"
echo -e "${CYAN}[3/5]${NC} 检查配置更新..." log_step "同步配置..."
merge_env_config merge_env_config
# 版本同步:从 VERSION 文件更新 IMAGE_TAG # 版本同步:从 VERSION 文件更新 IMAGE_TAG
@@ -130,21 +148,20 @@ if [ -f "VERSION" ]; then
if [ -n "$NEW_VERSION" ]; then if [ -n "$NEW_VERSION" ]; then
if grep -q "^IMAGE_TAG=" "docker/.env"; then if grep -q "^IMAGE_TAG=" "docker/.env"; then
sed_inplace "s/^IMAGE_TAG=.*/IMAGE_TAG=$NEW_VERSION/" "docker/.env" sed_inplace "s/^IMAGE_TAG=.*/IMAGE_TAG=$NEW_VERSION/" "docker/.env"
echo -e " ${GREEN}+${NC} 版本同步: IMAGE_TAG=$NEW_VERSION"
else else
printf '%s\n' "IMAGE_TAG=$NEW_VERSION" >> "docker/.env" printf '%s\n' "IMAGE_TAG=$NEW_VERSION" >> "docker/.env"
echo -e " ${GREEN}+${NC} 新增版本: IMAGE_TAG=$NEW_VERSION"
fi fi
log_ok "版本同步: $NEW_VERSION"
fi fi
fi fi
# Step 4: 构建/拉取镜像 # Step 4: 构建/拉取镜像
echo "" printf "\n"
echo -e "${CYAN}[4/5]${NC} 更新镜像..." log_step "更新镜像..."
if [ "$DEV_MODE" = true ]; then if [ "$DEV_MODE" = true ]; then
# 开发模式:本地构建所有镜像(包括 Worker # 开发模式:本地构建所有镜像(包括 Worker
echo -e " 构建 Worker 镜像..." log_info "构建 Worker 镜像..."
# 读取 IMAGE_TAG # 读取 IMAGE_TAG
IMAGE_TAG=$(grep "^IMAGE_TAG=" "docker/.env" | cut -d'=' -f2) IMAGE_TAG=$(grep "^IMAGE_TAG=" "docker/.env" | cut -d'=' -f2)
@@ -153,24 +170,23 @@ if [ "$DEV_MODE" = true ]; then
fi fi
# 构建 Worker 镜像Worker 是临时容器,不在 compose 中,需要单独构建) # 构建 Worker 镜像Worker 是临时容器,不在 compose 中,需要单独构建)
docker build -t docker-worker -f docker/worker/Dockerfile . 2>&1 | sed 's/^/ /' docker build -t docker-worker -f docker/worker/Dockerfile . 2>&1 | sed 's/^/ /'
docker tag docker-worker docker-worker:${IMAGE_TAG} 2>&1 | sed 's/^/ /' docker tag docker-worker docker-worker:${IMAGE_TAG} 2>&1 | sed 's/^/ /'
echo -e " ${GREEN}OK${NC} Worker 镜像已构建: docker-worker:${IMAGE_TAG}" log_ok "Worker 镜像: docker-worker:${IMAGE_TAG}"
# 其他服务镜像由 start.sh --dev 构建 log_info "其他服务镜像将在启动时构建"
echo -e " 其他服务镜像将在启动时构建..."
else else
# 生产模式:镜像由 start.sh 拉取 log_info "镜像将在启动时从 Docker Hub 拉取"
echo -e " 镜像将在启动时从 Docker Hub 拉取..."
fi fi
# Step 5: 启动服务 # Step 5: 启动服务
echo "" printf "\n"
echo -e "${CYAN}[5/5]${NC} 启动服务..." log_step "启动服务..."
./start.sh "$@" ./start.sh "$@"
echo "" # 完成提示
echo -e "${BOLD}${GREEN}════════════════════════════════════════${NC}" printf "\n"
echo -e "${BOLD}${GREEN} 更新完成!${NC}" printf "${GREEN}┌────────────────────────────────────────┐${NC}\n"
echo -e "${BOLD}${GREEN}════════════════════════════════════════${NC}" printf "${GREEN}${NC} ${BOLD}${GREEN}${NC} ${BOLD}更新完成${NC} ${GREEN}${NC}\n"
echo "" printf "${GREEN}└────────────────────────────────────────┘${NC}\n"
printf "\n"