dev
This commit is contained in:
179
domain-api/deploy/linux/README.md
Normal file
179
domain-api/deploy/linux/README.md
Normal file
@@ -0,0 +1,179 @@
|
||||
# domainCheck Linux 部署说明
|
||||
|
||||
本文档用于将 `domain-api` 与 `domainCheck Worker` 部署到 Linux,并由 `systemd` 托管。
|
||||
|
||||
## 一、建议目录结构
|
||||
|
||||
```text
|
||||
/opt/domaincheck
|
||||
├── domain-api
|
||||
├── domain-web
|
||||
└── domainCheck
|
||||
```
|
||||
|
||||
说明:
|
||||
|
||||
- `domain-api` 提供 Web 后台接口
|
||||
- `domain-web` 为前端静态文件项目
|
||||
- `domainCheck` 保留当前检测核心与 Worker
|
||||
|
||||
## 二、准备 Python 环境
|
||||
|
||||
建议使用 Python 3.11。
|
||||
|
||||
```bash
|
||||
cd /opt/domaincheck/domainCheck
|
||||
python3.11 -m venv .venv
|
||||
source .venv/bin/activate
|
||||
pip install -r requirements.txt
|
||||
|
||||
cd /opt/domaincheck/domain-api
|
||||
pip install fastapi uvicorn pydantic-settings psycopg2-binary redis openpyxl python-multipart
|
||||
```
|
||||
|
||||
## 三、准备配置文件
|
||||
|
||||
### 1. domainCheck/.env
|
||||
|
||||
`domain-api` 默认会读取 `/opt/domaincheck/domainCheck/.env`。
|
||||
|
||||
至少确认下面这些配置正确:
|
||||
|
||||
```env
|
||||
DB_HOST=127.0.0.1
|
||||
DB_PORT=5432
|
||||
DB_DATABASE=domain
|
||||
DB_USER=postgres
|
||||
DB_PASSWORD=postgres
|
||||
|
||||
REDIS_HOST=127.0.0.1
|
||||
REDIS_PORT=6379
|
||||
REDIS_PASSWORD=
|
||||
REDIS_DB=0
|
||||
```
|
||||
|
||||
### 2. 可选的 domain-api 环境变量
|
||||
|
||||
可以参考 `.env.example`。
|
||||
|
||||
常用项:
|
||||
|
||||
- `API_HOST`
|
||||
- `API_PORT`
|
||||
- `WORKER_MODE`
|
||||
- `WORKER_SERVICE_NAME`
|
||||
- `API_SERVICE_NAME`
|
||||
- `ADMIN_USERNAME`
|
||||
- `ADMIN_PASSWORD`
|
||||
|
||||
## 四、部署 systemd
|
||||
|
||||
模板文件:
|
||||
|
||||
- `deploy/systemd/domain-api.service`
|
||||
- `deploy/systemd/domain-worker.service`
|
||||
|
||||
复制到系统目录:
|
||||
|
||||
```bash
|
||||
sudo cp /opt/domaincheck/domain-api/deploy/systemd/domain-api.service /etc/systemd/system/domaincheck-api.service
|
||||
sudo cp /opt/domaincheck/domain-api/deploy/systemd/domain-worker.service /etc/systemd/system/domaincheck-worker.service
|
||||
```
|
||||
|
||||
然后按实际机器修改:
|
||||
|
||||
- `WorkingDirectory`
|
||||
- `ExecStart`
|
||||
- `User`
|
||||
- `Group`
|
||||
- `Environment`
|
||||
|
||||
## 五、启动顺序
|
||||
|
||||
```bash
|
||||
sudo systemctl daemon-reload
|
||||
sudo systemctl enable domaincheck-api
|
||||
sudo systemctl enable domaincheck-worker
|
||||
sudo systemctl start domaincheck-api
|
||||
sudo systemctl start domaincheck-worker
|
||||
```
|
||||
|
||||
查看状态:
|
||||
|
||||
```bash
|
||||
sudo systemctl status domaincheck-api
|
||||
sudo systemctl status domaincheck-worker
|
||||
```
|
||||
|
||||
查看日志:
|
||||
|
||||
```bash
|
||||
journalctl -u domaincheck-api -n 200 --no-pager
|
||||
journalctl -u domaincheck-worker -n 200 --no-pager
|
||||
```
|
||||
|
||||
## 六、联调检查
|
||||
|
||||
### 1. API 健康检查
|
||||
|
||||
```bash
|
||||
curl http://127.0.0.1:8100/health
|
||||
```
|
||||
|
||||
期望看到:
|
||||
|
||||
- `status=ok`
|
||||
- `worker_mode=linux-systemd`
|
||||
|
||||
### 2. Web 后台系统设置
|
||||
|
||||
在 Web 后台里确认:
|
||||
|
||||
- `Worker 运行模式 = linux-systemd`
|
||||
- `Worker 服务名 = domaincheck-worker`
|
||||
- `API 服务名 = domaincheck-api`
|
||||
|
||||
### 3. 运行中心
|
||||
|
||||
进入 `运行中心`,确认:
|
||||
|
||||
- API 在线
|
||||
- Worker 在线
|
||||
- 进程数大于 0
|
||||
- 最近启动时间正常
|
||||
|
||||
### 4. 运行 API 自测脚本
|
||||
|
||||
```bash
|
||||
cd /opt/domaincheck/domain-api
|
||||
python deploy/linux/smoke_test.py --base-url http://127.0.0.1:8100
|
||||
```
|
||||
|
||||
如果同时希望把 Web 首页一起纳入检查:
|
||||
|
||||
```bash
|
||||
python deploy/linux/smoke_test.py --base-url http://127.0.0.1:8100 --web-url http://127.0.0.1
|
||||
```
|
||||
|
||||
## 七、上线建议
|
||||
|
||||
- 先保持 `Windows 桌面版 + Web/Linux` 并行一段时间
|
||||
- 先让 Web 后台接管设置、导入、筛选、导出、日志
|
||||
- 再让 Linux Worker 接管主检测任务
|
||||
- 确认稳定后,再逐步淡出桌面检测端
|
||||
|
||||
## 八、联调诊断采集
|
||||
|
||||
如需导出一份联调诊断包,可执行:
|
||||
|
||||
```bash
|
||||
cd /opt/domaincheck/domain-api/deploy/linux
|
||||
bash collect_diagnostics.sh /opt/domaincheck
|
||||
```
|
||||
|
||||
会输出:
|
||||
|
||||
- `diagnostics_dir=...`
|
||||
- `diagnostics_archive=...`
|
||||
|
||||
把生成的归档包发回即可继续排障。
|
||||
60
domain-api/deploy/linux/collect_diagnostics.sh
Normal file
60
domain-api/deploy/linux/collect_diagnostics.sh
Normal file
@@ -0,0 +1,60 @@
|
||||
#!/usr/bin/env bash
|
||||
set -euo pipefail
|
||||
|
||||
BASE_DIR="${1:-/opt/domaincheck}"
|
||||
OUT_DIR="${2:-$BASE_DIR/diagnostics}"
|
||||
STAMP="$(date +%Y%m%d_%H%M%S)"
|
||||
BUNDLE_DIR="$OUT_DIR/diag_$STAMP"
|
||||
|
||||
mkdir -p "$BUNDLE_DIR"
|
||||
|
||||
write_cmd() {
|
||||
local name="$1"
|
||||
shift
|
||||
{
|
||||
echo "# command: $*"
|
||||
echo
|
||||
"$@"
|
||||
} > "$BUNDLE_DIR/$name.txt" 2>&1 || true
|
||||
}
|
||||
|
||||
copy_if_exists() {
|
||||
local src="$1"
|
||||
local dest="$2"
|
||||
if [ -f "$src" ]; then
|
||||
cp "$src" "$dest"
|
||||
fi
|
||||
}
|
||||
|
||||
write_cmd "systemctl_api" systemctl status domaincheck-api --no-pager
|
||||
write_cmd "systemctl_worker" systemctl status domaincheck-worker --no-pager
|
||||
write_cmd "journal_api" journalctl -u domaincheck-api -n 200 --no-pager
|
||||
write_cmd "journal_worker" journalctl -u domaincheck-worker -n 200 --no-pager
|
||||
write_cmd "api_health_curl" curl -sS http://127.0.0.1:8100/health
|
||||
write_cmd "api_preflight_curl" curl -sS http://127.0.0.1:8100/api/v1/runtime/preflight
|
||||
write_cmd "api_runtime_curl" curl -sS http://127.0.0.1:8100/api/v1/runtime/status
|
||||
write_cmd "ps_processes" ps -ef
|
||||
write_cmd "ss_listen" ss -lntp
|
||||
write_cmd "df_h" df -h
|
||||
write_cmd "free_h" free -h
|
||||
|
||||
copy_if_exists "$BASE_DIR/domainCheck/.env" "$BUNDLE_DIR/domainCheck.env"
|
||||
copy_if_exists "$BASE_DIR/domain-api/.env" "$BUNDLE_DIR/domain-api.env"
|
||||
|
||||
if [ -d "$BASE_DIR/domain-api/runtime" ]; then
|
||||
cp -r "$BASE_DIR/domain-api/runtime" "$BUNDLE_DIR/domain-api-runtime"
|
||||
fi
|
||||
|
||||
if [ -f "$BASE_DIR/domainCheck/logs/app.log" ]; then
|
||||
tail -n 300 "$BASE_DIR/domainCheck/logs/app.log" > "$BUNDLE_DIR/app_tail.log" 2>&1 || true
|
||||
fi
|
||||
|
||||
if [ -f "$BASE_DIR/domainCheck/detect_worker.log" ]; then
|
||||
tail -n 500 "$BASE_DIR/domainCheck/detect_worker.log" > "$BUNDLE_DIR/detect_worker_tail.log" 2>&1 || true
|
||||
fi
|
||||
|
||||
ARCHIVE="$OUT_DIR/diag_$STAMP.tar.gz"
|
||||
tar -czf "$ARCHIVE" -C "$OUT_DIR" "diag_$STAMP"
|
||||
|
||||
echo "diagnostics_dir=$BUNDLE_DIR"
|
||||
echo "diagnostics_archive=$ARCHIVE"
|
||||
103
domain-api/deploy/linux/smoke_test.py
Normal file
103
domain-api/deploy/linux/smoke_test.py
Normal file
@@ -0,0 +1,103 @@
|
||||
from __future__ import annotations
|
||||
|
||||
import argparse
|
||||
import json
|
||||
import sys
|
||||
from datetime import datetime
|
||||
|
||||
import requests
|
||||
|
||||
|
||||
def _fetch_json(session: requests.Session, url: str, timeout: int = 15) -> tuple[bool, str, dict | None]:
|
||||
try:
|
||||
response = session.get(url, timeout=timeout)
|
||||
response.raise_for_status()
|
||||
return True, f"{response.status_code}", response.json()
|
||||
except Exception as exc:
|
||||
return False, str(exc), None
|
||||
|
||||
|
||||
def _fetch_text(session: requests.Session, url: str, timeout: int = 15) -> tuple[bool, str]:
|
||||
try:
|
||||
response = session.get(url, timeout=timeout)
|
||||
response.raise_for_status()
|
||||
return True, f"{response.status_code}"
|
||||
except Exception as exc:
|
||||
return False, str(exc)
|
||||
|
||||
|
||||
def main() -> int:
|
||||
if hasattr(sys.stdout, "reconfigure"):
|
||||
try:
|
||||
sys.stdout.reconfigure(encoding="utf-8")
|
||||
except Exception:
|
||||
pass
|
||||
|
||||
parser = argparse.ArgumentParser(description="domainCheck Web/API smoke test")
|
||||
parser.add_argument("--base-url", default="http://127.0.0.1:8100", help="domain-api base URL")
|
||||
parser.add_argument("--web-url", default="", help="optional domain-web URL, e.g. http://127.0.0.1:3200")
|
||||
parser.add_argument("--output", default="", help="optional JSON report output path")
|
||||
args = parser.parse_args()
|
||||
|
||||
base_url = args.base_url.rstrip("/")
|
||||
session = requests.Session()
|
||||
|
||||
checks: list[dict[str, object]] = []
|
||||
|
||||
endpoints = [
|
||||
("health", f"{base_url}/health"),
|
||||
("runtime_status", f"{base_url}/api/v1/runtime/status"),
|
||||
("runtime_preflight", f"{base_url}/api/v1/runtime/preflight"),
|
||||
("dashboard_overview", f"{base_url}/api/v1/dashboard/overview"),
|
||||
("settings_export", f"{base_url}/api/v1/settings/export"),
|
||||
("settings_backups", f"{base_url}/api/v1/settings/backups"),
|
||||
("detect_status", f"{base_url}/api/v1/detect/status"),
|
||||
("imports_summary", f"{base_url}/api/v1/imports/summary"),
|
||||
("exports", f"{base_url}/api/v1/exports"),
|
||||
("logs_latest", f"{base_url}/api/v1/logs/latest"),
|
||||
]
|
||||
|
||||
for name, url in endpoints:
|
||||
ok, message, payload = _fetch_json(session, url)
|
||||
checks.append(
|
||||
{
|
||||
"name": name,
|
||||
"url": url,
|
||||
"ok": ok,
|
||||
"message": message,
|
||||
"payload_excerpt": payload if ok and name in {"health", "runtime_preflight", "settings_export"} else None,
|
||||
}
|
||||
)
|
||||
|
||||
if args.web_url:
|
||||
web_url = args.web_url.rstrip("/")
|
||||
ok, message = _fetch_text(session, web_url)
|
||||
checks.append(
|
||||
{
|
||||
"name": "web_home",
|
||||
"url": web_url,
|
||||
"ok": ok,
|
||||
"message": message,
|
||||
"payload_excerpt": None,
|
||||
}
|
||||
)
|
||||
|
||||
passed = all(bool(item["ok"]) for item in checks)
|
||||
report = {
|
||||
"generated_at": datetime.now().isoformat(timespec="seconds"),
|
||||
"base_url": base_url,
|
||||
"web_url": args.web_url.rstrip("/") if args.web_url else "",
|
||||
"ok": passed,
|
||||
"checks": checks,
|
||||
}
|
||||
|
||||
if args.output:
|
||||
with open(args.output, "w", encoding="utf-8") as handle:
|
||||
json.dump(report, handle, ensure_ascii=False, indent=2)
|
||||
|
||||
print(json.dumps(report, ensure_ascii=False, indent=2))
|
||||
return 0 if passed else 1
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
sys.exit(main())
|
||||
18
domain-api/deploy/systemd/domain-api.service
Normal file
18
domain-api/deploy/systemd/domain-api.service
Normal file
@@ -0,0 +1,18 @@
|
||||
[Unit]
|
||||
Description=domainCheck API
|
||||
After=network.target redis.service postgresql.service
|
||||
|
||||
[Service]
|
||||
Type=simple
|
||||
WorkingDirectory=/opt/domaincheck/domain-api
|
||||
Environment="WORKER_MODE=linux-systemd"
|
||||
Environment="API_HOST=0.0.0.0"
|
||||
Environment="API_PORT=8100"
|
||||
ExecStart=/opt/domaincheck/domainCheck/.venv/bin/python -m uvicorn app.main:app --host 0.0.0.0 --port 8100
|
||||
Restart=always
|
||||
RestartSec=5
|
||||
User=www-data
|
||||
Group=www-data
|
||||
|
||||
[Install]
|
||||
WantedBy=multi-user.target
|
||||
16
domain-api/deploy/systemd/domain-worker.service
Normal file
16
domain-api/deploy/systemd/domain-worker.service
Normal file
@@ -0,0 +1,16 @@
|
||||
[Unit]
|
||||
Description=domainCheck Worker
|
||||
After=network.target redis.service postgresql.service
|
||||
|
||||
[Service]
|
||||
Type=simple
|
||||
WorkingDirectory=/opt/domaincheck/domainCheck
|
||||
Environment="WORKER_MODE=linux-systemd"
|
||||
ExecStart=/opt/domaincheck/domainCheck/.venv/bin/python /opt/domaincheck/domainCheck/detect_worker.py
|
||||
Restart=always
|
||||
RestartSec=5
|
||||
User=www-data
|
||||
Group=www-data
|
||||
|
||||
[Install]
|
||||
WantedBy=multi-user.target
|
||||
Reference in New Issue
Block a user