添加安装部署脚本
This commit is contained in:
@@ -21,6 +21,7 @@
|
|||||||
"hyperf/config": "~3.1.0",
|
"hyperf/config": "~3.1.0",
|
||||||
"hyperf/constants": "~3.1.0",
|
"hyperf/constants": "~3.1.0",
|
||||||
"hyperf/crontab": "~3.1.0",
|
"hyperf/crontab": "~3.1.0",
|
||||||
|
"gokure/hyperf-cors": "^2.1",
|
||||||
"hyperf/database-pgsql": "^3.1",
|
"hyperf/database-pgsql": "^3.1",
|
||||||
"hyperf/db-connection": "~3.1.0",
|
"hyperf/db-connection": "~3.1.0",
|
||||||
"hyperf/engine": "^2.10",
|
"hyperf/engine": "^2.10",
|
||||||
|
|||||||
@@ -0,0 +1,38 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
declare(strict_types=1);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* CORS 配置
|
||||||
|
*
|
||||||
|
* 前端 Vue (http://server:8080) 跨源访问后端 (http://server:9501) 时必需。
|
||||||
|
* 由于前端走 Bearer token(Authorization header)而非 cookie,
|
||||||
|
* supports_credentials 保持 false,allowed_origins 可使用 '*'。
|
||||||
|
*
|
||||||
|
* 生产环境如要收紧,把 allowed_origins 改成具体域名列表。
|
||||||
|
*/
|
||||||
|
return [
|
||||||
|
// 仅对 /api/* 启用 CORS(前端调用都带此前缀)
|
||||||
|
'paths' => ['api/*'],
|
||||||
|
|
||||||
|
// 允许的方法
|
||||||
|
'allowed_methods' => ['*'],
|
||||||
|
|
||||||
|
// 允许的源(通配;若改成 supports_credentials=true,必须列具体域名)
|
||||||
|
'allowed_origins' => ['*'],
|
||||||
|
|
||||||
|
// 允许的源(正则匹配,按需启用)
|
||||||
|
'allowed_origins_patterns' => [],
|
||||||
|
|
||||||
|
// 允许的请求头
|
||||||
|
'allowed_headers' => ['*'],
|
||||||
|
|
||||||
|
// 暴露给前端的响应头
|
||||||
|
'exposed_headers' => [],
|
||||||
|
|
||||||
|
// 预检请求缓存时间(秒)
|
||||||
|
'max_age' => 7200,
|
||||||
|
|
||||||
|
// 是否允许携带凭证(cookie / 客户端证书);本项目用 Bearer token,保持 false
|
||||||
|
'supports_credentials' => false,
|
||||||
|
];
|
||||||
@@ -11,6 +11,7 @@ declare(strict_types=1);
|
|||||||
*/
|
*/
|
||||||
return [
|
return [
|
||||||
'http' => [
|
'http' => [
|
||||||
|
\Gokure\HyperfCors\CorsMiddleware::class,
|
||||||
\App\Middleware\RequestLogMiddleware::class,
|
\App\Middleware\RequestLogMiddleware::class,
|
||||||
],
|
],
|
||||||
];
|
];
|
||||||
|
|||||||
+43
@@ -0,0 +1,43 @@
|
|||||||
|
# 部署到 /var/container/data/datahub/env/datahub-backend.env
|
||||||
|
# 仅存放非敏感配置;密码/token 由 podman secret 注入(见 scripts/create-secrets.sh)
|
||||||
|
|
||||||
|
APP_NAME=datahub
|
||||||
|
APP_ENV=prod
|
||||||
|
SCAN_CACHEABLE=true
|
||||||
|
|
||||||
|
# --- PostgreSQL (TimescaleDB) ---
|
||||||
|
DB_DRIVER=pgsql
|
||||||
|
DB_HOST=datahub-postgres
|
||||||
|
DB_PORT=5432
|
||||||
|
DB_DATABASE=datahub
|
||||||
|
DB_USERNAME=datahub
|
||||||
|
DB_CHARSET=utf8
|
||||||
|
DB_COLLATION=utf8_unicode_ci
|
||||||
|
DB_PREFIX=
|
||||||
|
DB_SCHEMA=public
|
||||||
|
DB_TIMEZONE=Asia/Shanghai
|
||||||
|
DB_SSL_MODE=disable
|
||||||
|
DB_MAX_IDLE_TIME=60
|
||||||
|
# DB_PASSWORD 由 podman secret 注入
|
||||||
|
|
||||||
|
# --- RabbitMQ ---
|
||||||
|
AMQP_HOST=datahub-rabbitmq
|
||||||
|
AMQP_PORT=5672
|
||||||
|
AMQP_USER=user
|
||||||
|
AMQP_ADMIN_USER=user
|
||||||
|
AMQP_VHOST=dataflow
|
||||||
|
AMQP_MAX_RETRIES=3
|
||||||
|
AMQP_CONSUMER_DEBUG_DELAY=0
|
||||||
|
RABBITMQ_MANAGEMENT_PORT=15672
|
||||||
|
# AMQP_PASSWORD / AMQP_ADMIN_PASSWORD 由 podman secret 注入
|
||||||
|
|
||||||
|
# --- JWT ---
|
||||||
|
JWT_HEADER_NAME=Authorization
|
||||||
|
SIMPLE_JWT_TTL=7200
|
||||||
|
SIMPLE_JWT_REFRESH_TTL=2592000
|
||||||
|
SIMPLE_JWT_PREFIX=dataflow
|
||||||
|
# SIMPLE_JWT_SECRET 由 podman secret 注入
|
||||||
|
|
||||||
|
# --- 外部依赖 ---
|
||||||
|
TOOLS_HOST=https://store-api-v2.wpic-tools.com/
|
||||||
|
# TOOLS_TOKEN 由 podman secret 注入
|
||||||
@@ -0,0 +1,5 @@
|
|||||||
|
# 部署到 /var/container/data/datahub/env/datahub-frontend.env
|
||||||
|
# 浏览器访问后端 API 的完整地址(含协议、主机、端口)
|
||||||
|
# 由 scripts/configure-env.sh 交互式生成,也可手动维护
|
||||||
|
|
||||||
|
API_BASE_URL=http://__SERVER_IP_OR_DOMAIN__:9501
|
||||||
@@ -0,0 +1,13 @@
|
|||||||
|
FROM node:22-alpine AS builder
|
||||||
|
WORKDIR /app
|
||||||
|
COPY frontend/package*.json ./
|
||||||
|
RUN npm ci
|
||||||
|
COPY frontend/ ./
|
||||||
|
RUN npm run build
|
||||||
|
|
||||||
|
FROM nginx:alpine
|
||||||
|
COPY --from=builder /app/dist /usr/share/nginx/html
|
||||||
|
COPY deploy/podman/images/frontend/nginx.conf /etc/nginx/conf.d/default.conf
|
||||||
|
COPY deploy/podman/images/frontend/docker-entrypoint.sh /docker-entrypoint.d/40-inject-config.sh
|
||||||
|
RUN chmod +x /docker-entrypoint.d/40-inject-config.sh
|
||||||
|
EXPOSE 80
|
||||||
@@ -0,0 +1,22 @@
|
|||||||
|
#!/bin/sh
|
||||||
|
# 由 nginx 官方镜像的入口脚本自动执行(位于 /docker-entrypoint.d/)
|
||||||
|
# 将 config.js 中的占位符替换为运行期环境变量
|
||||||
|
|
||||||
|
set -e
|
||||||
|
|
||||||
|
CONFIG_FILE=/usr/share/nginx/html/config.js
|
||||||
|
|
||||||
|
if [ ! -f "$CONFIG_FILE" ]; then
|
||||||
|
echo "[entrypoint] $CONFIG_FILE 不存在,跳过注入" >&2
|
||||||
|
exit 0
|
||||||
|
fi
|
||||||
|
|
||||||
|
if [ -z "${API_BASE_URL:-}" ]; then
|
||||||
|
echo "[entrypoint] 错误:必须设置 API_BASE_URL 环境变量" >&2
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
# 用 | 作分隔符,避免 URL 里的 / 干扰
|
||||||
|
sed -i "s|__API_BASE_URL__|${API_BASE_URL}|g" "$CONFIG_FILE"
|
||||||
|
|
||||||
|
echo "[entrypoint] 已注入 API_BASE_URL=${API_BASE_URL}"
|
||||||
@@ -0,0 +1,21 @@
|
|||||||
|
server {
|
||||||
|
listen 80;
|
||||||
|
server_name _;
|
||||||
|
|
||||||
|
root /usr/share/nginx/html;
|
||||||
|
index index.html;
|
||||||
|
|
||||||
|
gzip on;
|
||||||
|
gzip_types text/plain text/css application/json application/javascript text/xml application/xml application/xml+rss text/javascript image/svg+xml;
|
||||||
|
gzip_min_length 1024;
|
||||||
|
|
||||||
|
location ~* \.(?:css|js|woff2?|ttf|eot|svg|png|jpg|jpeg|gif|ico|webp)$ {
|
||||||
|
expires 7d;
|
||||||
|
add_header Cache-Control "public, immutable";
|
||||||
|
try_files $uri =404;
|
||||||
|
}
|
||||||
|
|
||||||
|
location / {
|
||||||
|
try_files $uri $uri/ /index.html;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,11 @@
|
|||||||
|
FROM docker.io/timescale/timescaledb:latest-pg16
|
||||||
|
|
||||||
|
LABEL maintainer="WPIC Datahub" \
|
||||||
|
app.name="datahub-postgres"
|
||||||
|
|
||||||
|
ENV TZ=Asia/Shanghai \
|
||||||
|
LANG=en_US.utf8
|
||||||
|
|
||||||
|
COPY deploy/podman/images/postgres/initdb/ /docker-entrypoint-initdb.d/
|
||||||
|
|
||||||
|
EXPOSE 5432
|
||||||
@@ -0,0 +1,11 @@
|
|||||||
|
-- 首次初始化时执行(仅在 $PGDATA 为空时触发)
|
||||||
|
-- 在 datahub 数据库内启用 TimescaleDB 扩展
|
||||||
|
-- 迁移 2026_01_29_141058_convert_orders_to_hypertable.php 等依赖此扩展
|
||||||
|
|
||||||
|
\connect datahub
|
||||||
|
|
||||||
|
CREATE EXTENSION IF NOT EXISTS timescaledb;
|
||||||
|
|
||||||
|
-- 可按需追加其他扩展,例如:
|
||||||
|
-- CREATE EXTENSION IF NOT EXISTS pg_stat_statements;
|
||||||
|
-- CREATE EXTENSION IF NOT EXISTS pg_trgm;
|
||||||
@@ -0,0 +1,26 @@
|
|||||||
|
[Unit]
|
||||||
|
Description=Datahub Backend (Hyperf)
|
||||||
|
After=network-online.target datahub-postgres.service datahub-rabbitmq.service
|
||||||
|
Requires=datahub-postgres.service datahub-rabbitmq.service
|
||||||
|
|
||||||
|
[Container]
|
||||||
|
ContainerName=datahub-backend
|
||||||
|
Image=localhost/datahub-backend:latest
|
||||||
|
Network=datahub.network
|
||||||
|
Volume=/var/container/data/datahub/backend-runtime:/opt/www/runtime:Z
|
||||||
|
PublishPort=9501:9501
|
||||||
|
|
||||||
|
EnvironmentFile=/var/container/data/datahub/env/datahub-backend.env
|
||||||
|
|
||||||
|
Secret=datahub-pg-password,type=env,target=DB_PASSWORD
|
||||||
|
Secret=datahub-rabbitmq-password,type=env,target=AMQP_PASSWORD
|
||||||
|
Secret=datahub-rabbitmq-password,type=env,target=AMQP_ADMIN_PASSWORD
|
||||||
|
Secret=datahub-jwt-secret,type=env,target=SIMPLE_JWT_SECRET
|
||||||
|
Secret=datahub-tools-token,type=env,target=TOOLS_TOKEN
|
||||||
|
|
||||||
|
[Service]
|
||||||
|
Restart=always
|
||||||
|
TimeoutStartSec=180
|
||||||
|
|
||||||
|
[Install]
|
||||||
|
WantedBy=default.target
|
||||||
@@ -0,0 +1,18 @@
|
|||||||
|
[Unit]
|
||||||
|
Description=Datahub Frontend (Nginx)
|
||||||
|
After=network-online.target datahub-backend.service
|
||||||
|
Requires=datahub-backend.service
|
||||||
|
|
||||||
|
[Container]
|
||||||
|
ContainerName=datahub-frontend
|
||||||
|
Image=localhost/datahub-frontend:latest
|
||||||
|
Network=datahub.network
|
||||||
|
PublishPort=8080:80
|
||||||
|
|
||||||
|
EnvironmentFile=/var/container/data/datahub/env/datahub-frontend.env
|
||||||
|
|
||||||
|
[Service]
|
||||||
|
Restart=always
|
||||||
|
|
||||||
|
[Install]
|
||||||
|
WantedBy=default.target
|
||||||
@@ -0,0 +1,31 @@
|
|||||||
|
[Unit]
|
||||||
|
Description=Datahub PostgreSQL (TimescaleDB)
|
||||||
|
After=network-online.target
|
||||||
|
Requires=datahub-network.service
|
||||||
|
|
||||||
|
[Container]
|
||||||
|
ContainerName=datahub-postgres
|
||||||
|
Image=localhost/datahub-postgres:latest
|
||||||
|
Network=datahub.network
|
||||||
|
Volume=/var/container/data/datahub/postgres:/var/lib/postgresql/data:Z,U
|
||||||
|
PublishPort=127.0.0.1:5416:5432
|
||||||
|
|
||||||
|
Environment=POSTGRES_DB=datahub
|
||||||
|
Environment=POSTGRES_USER=datahub
|
||||||
|
Environment=POSTGRES_PASSWORD_FILE=/run/secrets/datahub-pg-password
|
||||||
|
Environment=TZ=Asia/Shanghai
|
||||||
|
Environment=PGDATA=/var/lib/postgresql/data/pgdata
|
||||||
|
|
||||||
|
Secret=datahub-pg-password,type=mount,target=datahub-pg-password,mode=0444
|
||||||
|
|
||||||
|
HealthCmd=pg_isready -U datahub -d datahub
|
||||||
|
HealthInterval=10s
|
||||||
|
HealthTimeout=5s
|
||||||
|
HealthRetries=10
|
||||||
|
|
||||||
|
[Service]
|
||||||
|
Restart=always
|
||||||
|
TimeoutStartSec=180
|
||||||
|
|
||||||
|
[Install]
|
||||||
|
WantedBy=default.target
|
||||||
@@ -0,0 +1,30 @@
|
|||||||
|
[Unit]
|
||||||
|
Description=Datahub RabbitMQ
|
||||||
|
After=network-online.target
|
||||||
|
Requires=datahub-network.service
|
||||||
|
|
||||||
|
[Container]
|
||||||
|
ContainerName=datahub-rabbitmq
|
||||||
|
Image=docker.io/library/rabbitmq:3-management-alpine
|
||||||
|
Network=datahub.network
|
||||||
|
Volume=/var/container/data/datahub/rabbitmq:/var/lib/rabbitmq:Z,U
|
||||||
|
PublishPort=127.0.0.1:5672:5672
|
||||||
|
PublishPort=127.0.0.1:15672:15672
|
||||||
|
|
||||||
|
Environment=RABBITMQ_DEFAULT_USER=user
|
||||||
|
Environment=RABBITMQ_DEFAULT_PASS_FILE=/run/secrets/datahub-rabbitmq-password
|
||||||
|
Environment=RABBITMQ_DEFAULT_VHOST=dataflow
|
||||||
|
|
||||||
|
Secret=datahub-rabbitmq-password,type=mount,target=datahub-rabbitmq-password,mode=0444
|
||||||
|
|
||||||
|
HealthCmd=rabbitmq-diagnostics -q ping
|
||||||
|
HealthInterval=15s
|
||||||
|
HealthTimeout=10s
|
||||||
|
HealthRetries=10
|
||||||
|
|
||||||
|
[Service]
|
||||||
|
Restart=always
|
||||||
|
TimeoutStartSec=180
|
||||||
|
|
||||||
|
[Install]
|
||||||
|
WantedBy=default.target
|
||||||
@@ -0,0 +1,9 @@
|
|||||||
|
[Unit]
|
||||||
|
Description=Datahub internal network
|
||||||
|
|
||||||
|
[Network]
|
||||||
|
NetworkName=datahub
|
||||||
|
Driver=bridge
|
||||||
|
|
||||||
|
[Install]
|
||||||
|
WantedBy=default.target
|
||||||
@@ -0,0 +1,149 @@
|
|||||||
|
#!/usr/bin/env bash
|
||||||
|
# 部署环境体检脚本 - 判断阶段 0-1 是否已完成
|
||||||
|
#
|
||||||
|
# 用法:bash check-prereqs.sh
|
||||||
|
#
|
||||||
|
# 退出码:0 = 全部 OK;1 = 有必须修复的项目(MISS)
|
||||||
|
|
||||||
|
# 不开启 set -e:需要继续检查所有项目
|
||||||
|
|
||||||
|
B='\033[34m'; G='\033[32m'; Y='\033[33m'; R='\033[31m'; N='\033[0m'
|
||||||
|
|
||||||
|
OK_COUNT=0
|
||||||
|
WARN_COUNT=0
|
||||||
|
MISS_COUNT=0
|
||||||
|
FIX_CMDS=()
|
||||||
|
|
||||||
|
ok() { echo -e "${G}[OK]${N} $1"; OK_COUNT=$((OK_COUNT+1)); }
|
||||||
|
warn() { echo -e "${Y}[WARN]${N} $1"; WARN_COUNT=$((WARN_COUNT+1)); [ -n "${2:-}" ] && { echo " 修复: $2"; FIX_CMDS+=("$2"); }; }
|
||||||
|
miss() { echo -e "${R}[MISS]${N} $1"; MISS_COUNT=$((MISS_COUNT+1)); [ -n "${2:-}" ] && { echo " 修复: $2"; FIX_CMDS+=("$2"); }; }
|
||||||
|
|
||||||
|
echo -e "${B}=== datahub 部署环境体检 ===${N}"
|
||||||
|
echo "用户: $USER ($(id -u))"
|
||||||
|
echo "主机: $(hostname)"
|
||||||
|
echo "OS: $(. /etc/os-release 2>/dev/null && echo "$PRETTY_NAME" || echo unknown)"
|
||||||
|
echo
|
||||||
|
|
||||||
|
# ---------- 1. 必需的可执行程序 ----------
|
||||||
|
|
||||||
|
# podman + 版本
|
||||||
|
if command -v podman >/dev/null 2>&1; then
|
||||||
|
VER=$(podman --version | awk '{print $3}')
|
||||||
|
MAJOR=$(echo "$VER" | cut -d. -f1)
|
||||||
|
MINOR=$(echo "$VER" | cut -d. -f2)
|
||||||
|
if [[ "$MAJOR" =~ ^[0-9]+$ ]] && { (( MAJOR > 4 )) || { (( MAJOR == 4 )) && (( MINOR >= 4 )); }; }; then
|
||||||
|
ok "podman v$VER (≥ 4.4 支持 Quadlet)"
|
||||||
|
else
|
||||||
|
miss "podman v$VER 版本过旧(需 ≥ 4.4)" "升级 podman 到 4.4+(Ubuntu 22.04 用 22.10 PPA,或装 podman-stable)"
|
||||||
|
fi
|
||||||
|
else
|
||||||
|
miss "podman 未安装" "sudo apt install -y podman"
|
||||||
|
fi
|
||||||
|
|
||||||
|
# systemctl
|
||||||
|
command -v systemctl >/dev/null 2>&1 \
|
||||||
|
&& ok "systemctl 可用" \
|
||||||
|
|| miss "systemctl 不可用(非 systemd 系统?)"
|
||||||
|
|
||||||
|
# systemctl --user 是否可用(rootless 关键)
|
||||||
|
if systemctl --user list-units >/dev/null 2>&1; then
|
||||||
|
ok "systemctl --user 可用(rootless 必需)"
|
||||||
|
else
|
||||||
|
miss "systemctl --user 不可用" "确保用 SSH 直连登录(非 sudo su 切来),并且 XDG_RUNTIME_DIR=/run/user/\$(id -u) 已设置"
|
||||||
|
fi
|
||||||
|
|
||||||
|
# uidmap
|
||||||
|
command -v newuidmap >/dev/null 2>&1 \
|
||||||
|
&& ok "uidmap (newuidmap) 已安装" \
|
||||||
|
|| miss "uidmap 未安装(rootless 必需)" "sudo apt install -y uidmap"
|
||||||
|
|
||||||
|
# slirp4netns
|
||||||
|
command -v slirp4netns >/dev/null 2>&1 \
|
||||||
|
&& ok "slirp4netns 已安装" \
|
||||||
|
|| miss "slirp4netns 未安装" "sudo apt install -y slirp4netns"
|
||||||
|
|
||||||
|
# fuse-overlayfs(推荐)
|
||||||
|
command -v fuse-overlayfs >/dev/null 2>&1 \
|
||||||
|
&& ok "fuse-overlayfs 已安装(rootless 存储驱动推荐)" \
|
||||||
|
|| warn "fuse-overlayfs 未安装(不影响功能,但 rootless 性能更好)" "sudo apt install -y fuse-overlayfs"
|
||||||
|
|
||||||
|
# git
|
||||||
|
command -v git >/dev/null 2>&1 \
|
||||||
|
&& ok "git $(git --version | awk '{print $3}')" \
|
||||||
|
|| miss "git 未安装" "sudo apt install -y git"
|
||||||
|
|
||||||
|
# ---------- 2. rootless 用户配置 ----------
|
||||||
|
|
||||||
|
# subuid
|
||||||
|
if grep -q "^${USER}:" /etc/subuid 2>/dev/null; then
|
||||||
|
ok "/etc/subuid: $(grep "^${USER}:" /etc/subuid)"
|
||||||
|
else
|
||||||
|
miss "/etc/subuid 缺少 $USER 映射" "sudo usermod --add-subuids 100000-165535 $USER"
|
||||||
|
fi
|
||||||
|
|
||||||
|
# subgid
|
||||||
|
if grep -q "^${USER}:" /etc/subgid 2>/dev/null; then
|
||||||
|
ok "/etc/subgid: $(grep "^${USER}:" /etc/subgid)"
|
||||||
|
else
|
||||||
|
miss "/etc/subgid 缺少 $USER 映射" "sudo usermod --add-subgids 100000-165535 $USER"
|
||||||
|
fi
|
||||||
|
|
||||||
|
# lingering
|
||||||
|
if loginctl show-user "$USER" 2>/dev/null | grep -q "Linger=yes"; then
|
||||||
|
ok "lingering 已启用(登出 / 重启后服务依然运行)"
|
||||||
|
else
|
||||||
|
warn "lingering 未启用(用户登出后服务停 + 开机不自启)" "sudo loginctl enable-linger $USER"
|
||||||
|
fi
|
||||||
|
|
||||||
|
# ---------- 3. 数据目录可达性 ----------
|
||||||
|
|
||||||
|
if [ -d /var/container/data/datahub ]; then
|
||||||
|
if [ -w /var/container/data/datahub ]; then
|
||||||
|
ok "/var/container/data/datahub 已存在且可写"
|
||||||
|
else
|
||||||
|
warn "/var/container/data/datahub 存在但当前用户不可写" "sudo chown $USER:$USER /var/container/data/datahub"
|
||||||
|
fi
|
||||||
|
else
|
||||||
|
if sudo -n true 2>/dev/null; then
|
||||||
|
ok "/var/container/data/datahub 不存在(setup-data-dirs.sh 会创建,sudo 免密可用)"
|
||||||
|
else
|
||||||
|
warn "/var/container/data/datahub 不存在(setup-data-dirs.sh 将提示输入 sudo 密码)"
|
||||||
|
fi
|
||||||
|
fi
|
||||||
|
|
||||||
|
# ---------- 4. 网络端口(仅提示) ----------
|
||||||
|
|
||||||
|
for port in 8080 9501; do
|
||||||
|
if ss -ltn 2>/dev/null | awk '{print $4}' | grep -qE ":${port}\$"; then
|
||||||
|
warn "端口 $port 已被占用" "lsof -i :$port 查看占用进程"
|
||||||
|
fi
|
||||||
|
done
|
||||||
|
|
||||||
|
# ---------- 汇总 ----------
|
||||||
|
|
||||||
|
echo
|
||||||
|
echo "────────────────────────────────────"
|
||||||
|
echo -e "结果: ${G}${OK_COUNT} OK${N} / ${Y}${WARN_COUNT} WARN${N} / ${R}${MISS_COUNT} MISS${N}"
|
||||||
|
echo
|
||||||
|
|
||||||
|
if (( MISS_COUNT > 0 )); then
|
||||||
|
echo -e "${R}存在必须修复的项目${N},请按上面的'修复:'命令处理后重新运行本脚本。"
|
||||||
|
if (( ${#FIX_CMDS[@]} > 0 )); then
|
||||||
|
echo
|
||||||
|
echo -e "${B}--- 一键复制修复命令 ---${N}"
|
||||||
|
# 去重
|
||||||
|
printf '%s\n' "${FIX_CMDS[@]}" | awk '!seen[$0]++'
|
||||||
|
echo -e "${B}--- end ---${N}"
|
||||||
|
fi
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
if (( WARN_COUNT > 0 )); then
|
||||||
|
echo -e "${Y}有警告项${N}(不阻止部署,建议处理)"
|
||||||
|
echo "可执行 install.sh,但部分功能可能受限(例如 lingering 未启用 → 不自启)"
|
||||||
|
exit 0
|
||||||
|
fi
|
||||||
|
|
||||||
|
echo -e "${G}环境已就绪,可以执行:${N}"
|
||||||
|
echo -e " ${B}bash deploy/podman/scripts/install.sh${N}"
|
||||||
|
exit 0
|
||||||
@@ -0,0 +1,65 @@
|
|||||||
|
#!/usr/bin/env bash
|
||||||
|
# 交互式生成 env 文件
|
||||||
|
# 用法:bash configure-env.sh
|
||||||
|
|
||||||
|
set -euo pipefail
|
||||||
|
|
||||||
|
CONF_DIR=/var/container/data/datahub/env
|
||||||
|
SCRIPT_DIR=$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)
|
||||||
|
REPO_ROOT=$(cd "$SCRIPT_DIR/../../.." && pwd)
|
||||||
|
|
||||||
|
if [ ! -d "$CONF_DIR" ]; then
|
||||||
|
echo "[!] $CONF_DIR 不存在,请先执行: bash $SCRIPT_DIR/setup-data-dirs.sh" >&2
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
# ---------- 1. backend env ----------
|
||||||
|
BACKEND_ENV="$CONF_DIR/datahub-backend.env"
|
||||||
|
BACKEND_TPL="$REPO_ROOT/deploy/podman/env/datahub-backend.env.example"
|
||||||
|
|
||||||
|
if [ -f "$BACKEND_ENV" ]; then
|
||||||
|
echo "[=] $BACKEND_ENV 已存在,跳过"
|
||||||
|
else
|
||||||
|
cp "$BACKEND_TPL" "$BACKEND_ENV"
|
||||||
|
chmod 600 "$BACKEND_ENV"
|
||||||
|
echo "[+] 已生成 $BACKEND_ENV(如需调整 TOOLS_HOST 等参数请手动编辑)"
|
||||||
|
fi
|
||||||
|
|
||||||
|
# ---------- 2. frontend env:交互式询问 API_BASE_URL ----------
|
||||||
|
FRONTEND_ENV="$CONF_DIR/datahub-frontend.env"
|
||||||
|
|
||||||
|
CURRENT_VAL=""
|
||||||
|
if [ -f "$FRONTEND_ENV" ]; then
|
||||||
|
CURRENT_VAL=$(grep -E "^API_BASE_URL=" "$FRONTEND_ENV" | sed 's/^API_BASE_URL=//')
|
||||||
|
echo "[=] 当前 $FRONTEND_ENV 中 API_BASE_URL=$CURRENT_VAL"
|
||||||
|
fi
|
||||||
|
|
||||||
|
DEFAULT_IP=$(hostname -I 2>/dev/null | awk '{print $1}')
|
||||||
|
DEFAULT_URL="http://${DEFAULT_IP:-127.0.0.1}:9501"
|
||||||
|
SUGGEST="${CURRENT_VAL:-$DEFAULT_URL}"
|
||||||
|
|
||||||
|
echo
|
||||||
|
echo "请输入浏览器访问后端 API 的完整地址。"
|
||||||
|
echo "示例:http://192.168.1.100:9501 或 https://api.example.com"
|
||||||
|
read -rp "API_BASE_URL [$SUGGEST]: " INPUT
|
||||||
|
API_BASE_URL="${INPUT:-$SUGGEST}"
|
||||||
|
|
||||||
|
if ! [[ "$API_BASE_URL" =~ ^https?:// ]]; then
|
||||||
|
echo "[!] 地址必须以 http:// 或 https:// 开头" >&2
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
cat > "$FRONTEND_ENV" <<EOF
|
||||||
|
# 由 configure-env.sh 生成于 $(date -Iseconds)
|
||||||
|
API_BASE_URL=$API_BASE_URL
|
||||||
|
EOF
|
||||||
|
chmod 600 "$FRONTEND_ENV"
|
||||||
|
|
||||||
|
echo
|
||||||
|
echo "[+] 已写入 $FRONTEND_ENV"
|
||||||
|
echo " API_BASE_URL=$API_BASE_URL"
|
||||||
|
echo
|
||||||
|
echo "下一步:"
|
||||||
|
echo " 1. 创建/更新 podman secrets:bash $SCRIPT_DIR/create-secrets.sh"
|
||||||
|
echo " 2. 构建镜像后启动 datahub-frontend.service(容器启动时会自动注入此 URL)"
|
||||||
|
echo " 3. 已运行的服务若要换 IP:编辑 $FRONTEND_ENV 后 systemctl --user restart datahub-frontend.service"
|
||||||
@@ -0,0 +1,57 @@
|
|||||||
|
#!/usr/bin/env bash
|
||||||
|
# 交互式创建 4 个 podman secret,供 Quadlet 单元引用
|
||||||
|
# 用法:bash create-secrets.sh
|
||||||
|
|
||||||
|
set -euo pipefail
|
||||||
|
|
||||||
|
if ! command -v podman >/dev/null 2>&1; then
|
||||||
|
echo "未检测到 podman,请先安装" >&2
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
create_secret() {
|
||||||
|
local name=$1
|
||||||
|
local prompt=$2
|
||||||
|
local default_cmd=${3:-}
|
||||||
|
|
||||||
|
if podman secret exists "$name" 2>/dev/null; then
|
||||||
|
read -rp "secret [$name] 已存在,是否替换?(y/N): " ans
|
||||||
|
if [[ "${ans,,}" != "y" ]]; then
|
||||||
|
echo " 跳过 $name"
|
||||||
|
return
|
||||||
|
fi
|
||||||
|
podman secret rm "$name" >/dev/null
|
||||||
|
fi
|
||||||
|
|
||||||
|
local value
|
||||||
|
if [[ -n "$default_cmd" ]]; then
|
||||||
|
read -rp "$prompt(直接回车自动生成): " -s value
|
||||||
|
echo
|
||||||
|
if [[ -z "$value" ]]; then
|
||||||
|
value=$(eval "$default_cmd")
|
||||||
|
echo " 已自动生成"
|
||||||
|
fi
|
||||||
|
else
|
||||||
|
read -rp "$prompt: " -s value
|
||||||
|
echo
|
||||||
|
if [[ -z "$value" ]]; then
|
||||||
|
echo " 值不能为空" >&2
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
fi
|
||||||
|
|
||||||
|
printf '%s' "$value" | podman secret create "$name" -
|
||||||
|
echo " ✓ 创建 $name"
|
||||||
|
}
|
||||||
|
|
||||||
|
echo "=== 创建 datahub podman secrets ==="
|
||||||
|
echo
|
||||||
|
|
||||||
|
create_secret datahub-pg-password "PostgreSQL datahub 用户密码"
|
||||||
|
create_secret datahub-rabbitmq-password "RabbitMQ user 用户密码"
|
||||||
|
create_secret datahub-jwt-secret "JWT 签名 secret" "openssl rand -hex 32"
|
||||||
|
create_secret datahub-tools-token "TOOLS_TOKEN(外部 store-api 鉴权 token)"
|
||||||
|
|
||||||
|
echo
|
||||||
|
echo "完成。当前 secrets:"
|
||||||
|
podman secret ls
|
||||||
@@ -0,0 +1,144 @@
|
|||||||
|
#!/usr/bin/env bash
|
||||||
|
# datahub 一键部署脚本(阶段 3-7)
|
||||||
|
#
|
||||||
|
# 假设阶段 0-2 已完成:podman 已装 / subuid 已配 / lingering 已开 / 代码已 clone
|
||||||
|
#
|
||||||
|
# 用法:bash deploy/podman/scripts/install.sh
|
||||||
|
#
|
||||||
|
# 本脚本是幂等的:可重复运行;已存在的资源会跳过或询问。
|
||||||
|
|
||||||
|
set -euo pipefail
|
||||||
|
|
||||||
|
# ---------- 输出辅助 ----------
|
||||||
|
B='\033[34m'; G='\033[32m'; Y='\033[33m'; R='\033[31m'; N='\033[0m'
|
||||||
|
step() { echo -e "\n${B}==> [$1/5] $2${N}"; }
|
||||||
|
ok() { echo -e "${G}[OK]${N} $*"; }
|
||||||
|
warn() { echo -e "${Y}[!]${N} $*"; }
|
||||||
|
err() { echo -e "${R}[ERR]${N} $*" >&2; }
|
||||||
|
|
||||||
|
SCRIPT_DIR=$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)
|
||||||
|
REPO_ROOT=$(cd "$SCRIPT_DIR/../../.." && pwd)
|
||||||
|
QUADLET_DIR="$HOME/.config/containers/systemd"
|
||||||
|
|
||||||
|
# ---------- 前置检查 ----------
|
||||||
|
echo -e "${B}=== datahub 部署脚本 ===${N}"
|
||||||
|
echo "工作目录: $REPO_ROOT"
|
||||||
|
echo
|
||||||
|
|
||||||
|
# 调用体检脚本;MISS 不为 0 时退出码 1 → 整个安装流程中止
|
||||||
|
if ! bash "$SCRIPT_DIR/check-prereqs.sh"; then
|
||||||
|
err "环境体检未通过,请按上面提示处理后重试"
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
echo
|
||||||
|
|
||||||
|
# ---------- 1. 数据目录 ----------
|
||||||
|
step 1 "创建数据目录"
|
||||||
|
bash "$SCRIPT_DIR/setup-data-dirs.sh"
|
||||||
|
|
||||||
|
# ---------- 2. env 文件 ----------
|
||||||
|
step 2 "生成 env 文件(交互)"
|
||||||
|
bash "$SCRIPT_DIR/configure-env.sh"
|
||||||
|
|
||||||
|
# ---------- 3. secrets ----------
|
||||||
|
step 3 "创建 podman secrets(交互)"
|
||||||
|
bash "$SCRIPT_DIR/create-secrets.sh"
|
||||||
|
|
||||||
|
# ---------- 4. 构建镜像 ----------
|
||||||
|
step 4 "构建 3 个镜像"
|
||||||
|
cd "$REPO_ROOT"
|
||||||
|
|
||||||
|
echo -e "${B} -> datahub-postgres${N}"
|
||||||
|
podman build -t localhost/datahub-postgres:latest \
|
||||||
|
-f deploy/podman/images/postgres/Dockerfile .
|
||||||
|
|
||||||
|
echo -e "${B} -> datahub-backend${N}"
|
||||||
|
podman build -t localhost/datahub-backend:latest \
|
||||||
|
-f backend/Dockerfile backend/
|
||||||
|
|
||||||
|
echo -e "${B} -> datahub-frontend${N}"
|
||||||
|
podman build -t localhost/datahub-frontend:latest \
|
||||||
|
-f deploy/podman/images/frontend/Dockerfile .
|
||||||
|
|
||||||
|
ok "镜像构建完成"
|
||||||
|
podman images --format " {{.Repository}}:{{.Tag}} ({{.Size}})" | grep "^ localhost/datahub-" || true
|
||||||
|
|
||||||
|
# ---------- 5. Quadlet + 启动 ----------
|
||||||
|
step 5 "部署 Quadlet 单元并启动服务"
|
||||||
|
|
||||||
|
mkdir -p "$QUADLET_DIR"
|
||||||
|
cp "$REPO_ROOT"/deploy/podman/quadlet/* "$QUADLET_DIR/"
|
||||||
|
ok "Quadlet 单元已复制到 $QUADLET_DIR"
|
||||||
|
|
||||||
|
systemctl --user daemon-reload
|
||||||
|
|
||||||
|
# 等待容器达到 healthy(仅对配置了 healthcheck 的容器)
|
||||||
|
wait_healthy() {
|
||||||
|
local container=$1
|
||||||
|
local timeout=${2:-90}
|
||||||
|
local elapsed=0
|
||||||
|
while (( elapsed < timeout )); do
|
||||||
|
local status
|
||||||
|
status=$(podman inspect --format '{{.State.Health.Status}}' "$container" 2>/dev/null || echo "starting")
|
||||||
|
if [[ "$status" == "healthy" ]]; then
|
||||||
|
ok " $container healthy(${elapsed}s)"
|
||||||
|
return 0
|
||||||
|
fi
|
||||||
|
sleep 3
|
||||||
|
elapsed=$((elapsed + 3))
|
||||||
|
printf "."
|
||||||
|
done
|
||||||
|
echo
|
||||||
|
err "$container 在 ${timeout}s 内未达到 healthy"
|
||||||
|
return 1
|
||||||
|
}
|
||||||
|
|
||||||
|
# 启动顺序:PG → MQ → Backend → Frontend
|
||||||
|
echo -e "${B} 启动 datahub-postgres...${N}"
|
||||||
|
systemctl --user start datahub-postgres.service
|
||||||
|
wait_healthy datahub-postgres 90 || { journalctl --user -u datahub-postgres.service -n 50 --no-pager; exit 1; }
|
||||||
|
|
||||||
|
echo -e "${B} 启动 datahub-rabbitmq...${N}"
|
||||||
|
systemctl --user start datahub-rabbitmq.service
|
||||||
|
wait_healthy datahub-rabbitmq 90 || { journalctl --user -u datahub-rabbitmq.service -n 50 --no-pager; exit 1; }
|
||||||
|
|
||||||
|
echo -e "${B} 启动 datahub-backend...${N}"
|
||||||
|
systemctl --user start datahub-backend.service
|
||||||
|
sleep 5
|
||||||
|
if ! systemctl --user is-active --quiet datahub-backend.service; then
|
||||||
|
err "datahub-backend 未运行"
|
||||||
|
journalctl --user -u datahub-backend.service -n 50 --no-pager
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
ok " datahub-backend 已运行"
|
||||||
|
|
||||||
|
echo -e "${B} 启动 datahub-frontend...${N}"
|
||||||
|
systemctl --user start datahub-frontend.service
|
||||||
|
sleep 3
|
||||||
|
if ! systemctl --user is-active --quiet datahub-frontend.service; then
|
||||||
|
err "datahub-frontend 未运行"
|
||||||
|
journalctl --user -u datahub-frontend.service -n 50 --no-pager
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
ok " datahub-frontend 已运行"
|
||||||
|
|
||||||
|
# ---------- 完成 ----------
|
||||||
|
HOST_IP=$(hostname -I 2>/dev/null | awk '{print $1}')
|
||||||
|
echo
|
||||||
|
echo -e "${G}========================================${N}"
|
||||||
|
echo -e "${G} 部署完成!${N}"
|
||||||
|
echo -e "${G}========================================${N}"
|
||||||
|
echo
|
||||||
|
echo "服务访问:"
|
||||||
|
echo " 前端: http://${HOST_IP:-localhost}:8080"
|
||||||
|
echo " 后端: http://${HOST_IP:-localhost}:9501"
|
||||||
|
echo " RabbitMQ 控制台: http://${HOST_IP:-localhost}:15672 (账号 user)"
|
||||||
|
echo
|
||||||
|
echo "下一步(首次部署必做):"
|
||||||
|
echo -e " ${Y}podman exec -it datahub-backend php bin/hyperf.php migrate${N}"
|
||||||
|
echo
|
||||||
|
echo "常用命令:"
|
||||||
|
echo " podman ps # 看容器状态"
|
||||||
|
echo " systemctl --user status datahub-backend # 看服务状态"
|
||||||
|
echo " journalctl --user -u datahub-backend -f # 看实时日志"
|
||||||
|
echo " podman exec -it datahub-backend sh # 进容器调试"
|
||||||
@@ -0,0 +1,38 @@
|
|||||||
|
#!/usr/bin/env bash
|
||||||
|
# 创建宿主机数据目录 /var/container/data/datahub/
|
||||||
|
# 所有运行期产物(数据卷 + env 配置)统一放在此根目录下
|
||||||
|
#
|
||||||
|
# 用法:bash setup-data-dirs.sh
|
||||||
|
|
||||||
|
set -euo pipefail
|
||||||
|
|
||||||
|
DATA_BASE=/var/container/data/datahub
|
||||||
|
SUBDIRS=(postgres rabbitmq backend-runtime env)
|
||||||
|
|
||||||
|
# 1. 父目录需要 root 创建并把所有权移交给当前用户
|
||||||
|
if [ ! -d "$DATA_BASE" ]; then
|
||||||
|
echo "[*] $DATA_BASE 不存在,需要 sudo 创建"
|
||||||
|
sudo mkdir -p "$DATA_BASE"
|
||||||
|
sudo chown "$USER:$USER" "$DATA_BASE"
|
||||||
|
fi
|
||||||
|
|
||||||
|
# 2. 创建子目录
|
||||||
|
for d in "${SUBDIRS[@]}"; do
|
||||||
|
full="$DATA_BASE/$d"
|
||||||
|
if [ -d "$full" ]; then
|
||||||
|
echo "[=] $full 已存在,跳过"
|
||||||
|
else
|
||||||
|
mkdir -p "$full"
|
||||||
|
echo "[+] 创建 $full"
|
||||||
|
fi
|
||||||
|
done
|
||||||
|
|
||||||
|
echo
|
||||||
|
echo "完成。当前结构:"
|
||||||
|
ls -la "$DATA_BASE"
|
||||||
|
|
||||||
|
echo
|
||||||
|
echo "说明:"
|
||||||
|
echo " - postgres / rabbitmq 目录在容器首次启动时由 podman 自动 chown 给容器内用户(:U 标志)"
|
||||||
|
echo " - backend-runtime 容器以 root 运行,无需额外 chown"
|
||||||
|
echo " - env/ 存放 datahub-backend.env / datahub-frontend.env(由 configure-env.sh 生成)"
|
||||||
@@ -0,0 +1,130 @@
|
|||||||
|
#!/usr/bin/env bash
|
||||||
|
# datahub 卸载脚本
|
||||||
|
#
|
||||||
|
# 默认行为:停服务 + 移除 Quadlet + 删 secrets,保留数据目录和镜像
|
||||||
|
#
|
||||||
|
# 用法:bash uninstall.sh [选项]
|
||||||
|
# --purge-images 同时删除 3 个本地镜像
|
||||||
|
# --purge-data 同时删除 /var/container/data/datahub/ 数据目录(危险)
|
||||||
|
# -y, --yes 不询问确认(用于自动化)
|
||||||
|
|
||||||
|
set -euo pipefail
|
||||||
|
|
||||||
|
B='\033[34m'; G='\033[32m'; Y='\033[33m'; R='\033[31m'; N='\033[0m'
|
||||||
|
ok() { echo -e "${G}[OK]${N} $*"; }
|
||||||
|
warn() { echo -e "${Y}[!]${N} $*"; }
|
||||||
|
|
||||||
|
PURGE_IMAGES=false
|
||||||
|
PURGE_DATA=false
|
||||||
|
ASSUME_YES=false
|
||||||
|
|
||||||
|
for arg in "$@"; do
|
||||||
|
case "$arg" in
|
||||||
|
--purge-images) PURGE_IMAGES=true ;;
|
||||||
|
--purge-data) PURGE_DATA=true ;;
|
||||||
|
-y|--yes) ASSUME_YES=true ;;
|
||||||
|
-h|--help)
|
||||||
|
sed -n '2,11p' "$0"
|
||||||
|
exit 0
|
||||||
|
;;
|
||||||
|
*) echo "未知参数: $arg" >&2; exit 1 ;;
|
||||||
|
esac
|
||||||
|
done
|
||||||
|
|
||||||
|
QUADLET_DIR="$HOME/.config/containers/systemd"
|
||||||
|
DATA_DIR=/var/container/data/datahub
|
||||||
|
SERVICES=(datahub-frontend datahub-backend datahub-rabbitmq datahub-postgres)
|
||||||
|
QUADLET_FILES=(
|
||||||
|
datahub-frontend.container
|
||||||
|
datahub-backend.container
|
||||||
|
datahub-rabbitmq.container
|
||||||
|
datahub-postgres.container
|
||||||
|
datahub.network
|
||||||
|
)
|
||||||
|
SECRETS=(datahub-pg-password datahub-rabbitmq-password datahub-jwt-secret datahub-tools-token)
|
||||||
|
IMAGES=(localhost/datahub-postgres:latest localhost/datahub-backend:latest localhost/datahub-frontend:latest)
|
||||||
|
|
||||||
|
echo -e "${B}=== datahub 卸载脚本 ===${N}"
|
||||||
|
echo
|
||||||
|
echo "将执行以下操作:"
|
||||||
|
echo " - 停止 4 个 systemd 服务"
|
||||||
|
echo " - 移除 ${#QUADLET_FILES[@]} 个 Quadlet 单元"
|
||||||
|
echo " - 删除 ${#SECRETS[@]} 个 podman secrets"
|
||||||
|
echo -n " - 镜像: "
|
||||||
|
[ "$PURGE_IMAGES" = true ] && echo -e "${R}删除${N}" || echo "保留"
|
||||||
|
echo -n " - 数据目录 $DATA_DIR: "
|
||||||
|
[ "$PURGE_DATA" = true ] && echo -e "${R}删除(数据将丢失)${N}" || echo "保留"
|
||||||
|
echo
|
||||||
|
|
||||||
|
if [ "$ASSUME_YES" = false ]; then
|
||||||
|
read -rp "确认继续?(y/N): " ans
|
||||||
|
[[ "${ans,,}" == "y" ]] || { echo "已取消"; exit 0; }
|
||||||
|
fi
|
||||||
|
|
||||||
|
# 1. 停服务
|
||||||
|
echo -e "\n${B}-> 停止服务${N}"
|
||||||
|
for svc in "${SERVICES[@]}"; do
|
||||||
|
if systemctl --user is-active --quiet "$svc.service" 2>/dev/null; then
|
||||||
|
systemctl --user stop "$svc.service" || true
|
||||||
|
echo " 停止 $svc"
|
||||||
|
fi
|
||||||
|
done
|
||||||
|
|
||||||
|
# 2. 移除 Quadlet 文件
|
||||||
|
echo -e "\n${B}-> 移除 Quadlet 单元${N}"
|
||||||
|
for f in "${QUADLET_FILES[@]}"; do
|
||||||
|
if [ -f "$QUADLET_DIR/$f" ]; then
|
||||||
|
rm -f "$QUADLET_DIR/$f"
|
||||||
|
echo " 删除 $f"
|
||||||
|
fi
|
||||||
|
done
|
||||||
|
systemctl --user daemon-reload
|
||||||
|
|
||||||
|
# 3. 清理残留容器(保险起见)
|
||||||
|
for c in datahub-postgres datahub-rabbitmq datahub-backend datahub-frontend; do
|
||||||
|
if podman container exists "$c" 2>/dev/null; then
|
||||||
|
podman rm -f "$c" >/dev/null
|
||||||
|
echo " 删除残留容器 $c"
|
||||||
|
fi
|
||||||
|
done
|
||||||
|
|
||||||
|
# 4. 删 secrets
|
||||||
|
echo -e "\n${B}-> 删除 secrets${N}"
|
||||||
|
for s in "${SECRETS[@]}"; do
|
||||||
|
if podman secret exists "$s" 2>/dev/null; then
|
||||||
|
podman secret rm "$s" >/dev/null
|
||||||
|
echo " 删除 $s"
|
||||||
|
fi
|
||||||
|
done
|
||||||
|
|
||||||
|
# 5. 删镜像(可选)
|
||||||
|
if [ "$PURGE_IMAGES" = true ]; then
|
||||||
|
echo -e "\n${B}-> 删除镜像${N}"
|
||||||
|
for img in "${IMAGES[@]}"; do
|
||||||
|
if podman image exists "$img" 2>/dev/null; then
|
||||||
|
podman rmi "$img" >/dev/null && echo " 删除 $img"
|
||||||
|
fi
|
||||||
|
done
|
||||||
|
fi
|
||||||
|
|
||||||
|
# 6. 删数据目录(危险,双重确认)
|
||||||
|
if [ "$PURGE_DATA" = true ]; then
|
||||||
|
echo -e "\n${R}-> 删除数据目录${N}"
|
||||||
|
if [ "$ASSUME_YES" = false ]; then
|
||||||
|
echo -e "${R}警告:将永久删除 $DATA_DIR 及全部数据${N}"
|
||||||
|
read -rp "请输入完整的 'yes' 以确认: " ans
|
||||||
|
if [ "$ans" != "yes" ]; then
|
||||||
|
echo "数据目录保留"
|
||||||
|
ASSUME_YES=skip_data
|
||||||
|
fi
|
||||||
|
fi
|
||||||
|
if [ "$ASSUME_YES" != "skip_data" ]; then
|
||||||
|
sudo rm -rf "$DATA_DIR"
|
||||||
|
echo " 数据目录已删除"
|
||||||
|
fi
|
||||||
|
fi
|
||||||
|
|
||||||
|
echo
|
||||||
|
ok "卸载完成"
|
||||||
|
[ "$PURGE_DATA" = false ] && warn "数据保留在 $DATA_DIR(重新部署可直接恢复)"
|
||||||
|
[ "$PURGE_IMAGES" = false ] && warn "镜像保留(podman images | grep datahub 可见)"
|
||||||
@@ -0,0 +1,156 @@
|
|||||||
|
#!/usr/bin/env bash
|
||||||
|
# datahub 更新脚本:git pull + 智能重建 + 滚动重启
|
||||||
|
#
|
||||||
|
# 用法:bash update.sh [选项]
|
||||||
|
# --force-rebuild 不看 git diff,强制重建所有镜像
|
||||||
|
# --skip-migrate 检测到新迁移时不提示,直接跳过
|
||||||
|
|
||||||
|
set -euo pipefail
|
||||||
|
|
||||||
|
B='\033[34m'; G='\033[32m'; Y='\033[33m'; R='\033[31m'; N='\033[0m'
|
||||||
|
step() { echo -e "\n${B}=> $*${N}"; }
|
||||||
|
ok() { echo -e "${G}[OK]${N} $*"; }
|
||||||
|
warn() { echo -e "${Y}[!]${N} $*"; }
|
||||||
|
err() { echo -e "${R}[ERR]${N} $*" >&2; }
|
||||||
|
|
||||||
|
FORCE_REBUILD=false
|
||||||
|
SKIP_MIGRATE=false
|
||||||
|
for arg in "$@"; do
|
||||||
|
case "$arg" in
|
||||||
|
--force-rebuild) FORCE_REBUILD=true ;;
|
||||||
|
--skip-migrate) SKIP_MIGRATE=true ;;
|
||||||
|
-h|--help)
|
||||||
|
sed -n '2,7p' "$0"
|
||||||
|
exit 0
|
||||||
|
;;
|
||||||
|
*) echo "未知参数: $arg" >&2; exit 1 ;;
|
||||||
|
esac
|
||||||
|
done
|
||||||
|
|
||||||
|
SCRIPT_DIR=$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)
|
||||||
|
REPO_ROOT=$(cd "$SCRIPT_DIR/../../.." && pwd)
|
||||||
|
QUADLET_DIR="$HOME/.config/containers/systemd"
|
||||||
|
|
||||||
|
cd "$REPO_ROOT"
|
||||||
|
|
||||||
|
# 1. 必须是 git 仓库且工作区干净
|
||||||
|
if [ ! -d .git ]; then
|
||||||
|
err "$REPO_ROOT 不是 git 仓库"
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
if ! git diff --quiet || ! git diff --cached --quiet; then
|
||||||
|
err "工作区有未提交的变更,先 stash 或 commit"
|
||||||
|
git status --short
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
# 2. git pull
|
||||||
|
step "git pull"
|
||||||
|
OLD_HEAD=$(git rev-parse HEAD)
|
||||||
|
git pull --ff-only
|
||||||
|
NEW_HEAD=$(git rev-parse HEAD)
|
||||||
|
|
||||||
|
if [ "$OLD_HEAD" = "$NEW_HEAD" ] && [ "$FORCE_REBUILD" = false ]; then
|
||||||
|
ok "代码已是最新,无需更新(如需强制重建请加 --force-rebuild)"
|
||||||
|
exit 0
|
||||||
|
fi
|
||||||
|
|
||||||
|
# 3. 检测变更范围
|
||||||
|
if [ "$FORCE_REBUILD" = true ]; then
|
||||||
|
CH_BACKEND=true; CH_FRONTEND=true; CH_QUADLET=true
|
||||||
|
CH_PG_IMAGE=true; CH_MIGRATIONS=true
|
||||||
|
warn "强制重建:忽略 git diff"
|
||||||
|
else
|
||||||
|
DIFF=$(git diff --name-only "$OLD_HEAD" "$NEW_HEAD")
|
||||||
|
matches() { echo "$DIFF" | grep -qE "$1"; }
|
||||||
|
|
||||||
|
CH_BACKEND=$(matches '^backend/' && echo true || echo false)
|
||||||
|
CH_FRONTEND=$(matches '^(frontend/|deploy/podman/images/frontend/)' && echo true || echo false)
|
||||||
|
CH_QUADLET=$(matches '^deploy/podman/quadlet/' && echo true || echo false)
|
||||||
|
CH_PG_IMAGE=$(matches '^deploy/podman/images/postgres/' && echo true || echo false)
|
||||||
|
CH_MIGRATIONS=$(matches '^backend/migrations/' && echo true || echo false)
|
||||||
|
fi
|
||||||
|
|
||||||
|
echo
|
||||||
|
echo "变更检测:"
|
||||||
|
printf " %-20s %s\n" "backend image:" "$CH_BACKEND"
|
||||||
|
printf " %-20s %s\n" "frontend image:" "$CH_FRONTEND"
|
||||||
|
printf " %-20s %s\n" "postgres image:" "$CH_PG_IMAGE"
|
||||||
|
printf " %-20s %s\n" "quadlet files:" "$CH_QUADLET"
|
||||||
|
printf " %-20s %s\n" "migrations:" "$CH_MIGRATIONS"
|
||||||
|
|
||||||
|
# 4. 重建镜像
|
||||||
|
if [ "$CH_PG_IMAGE" = "true" ]; then
|
||||||
|
step "重建 postgres 镜像"
|
||||||
|
podman build -t localhost/datahub-postgres:latest \
|
||||||
|
-f deploy/podman/images/postgres/Dockerfile .
|
||||||
|
fi
|
||||||
|
|
||||||
|
if [ "$CH_BACKEND" = "true" ]; then
|
||||||
|
step "重建 backend 镜像"
|
||||||
|
podman build -t localhost/datahub-backend:latest \
|
||||||
|
-f backend/Dockerfile backend/
|
||||||
|
fi
|
||||||
|
|
||||||
|
if [ "$CH_FRONTEND" = "true" ]; then
|
||||||
|
step "重建 frontend 镜像"
|
||||||
|
podman build -t localhost/datahub-frontend:latest \
|
||||||
|
-f deploy/podman/images/frontend/Dockerfile .
|
||||||
|
fi
|
||||||
|
|
||||||
|
# 5. 同步 Quadlet(任何 quadlet 改动或强制时)
|
||||||
|
if [ "$CH_QUADLET" = "true" ] || [ "$FORCE_REBUILD" = true ]; then
|
||||||
|
step "同步 Quadlet 单元"
|
||||||
|
cp deploy/podman/quadlet/* "$QUADLET_DIR/"
|
||||||
|
systemctl --user daemon-reload
|
||||||
|
ok "Quadlet 已更新"
|
||||||
|
fi
|
||||||
|
|
||||||
|
# 6. 滚动重启受影响的服务
|
||||||
|
step "重启受影响的服务"
|
||||||
|
restart_svc() {
|
||||||
|
local svc=$1
|
||||||
|
echo " 重启 $svc.service"
|
||||||
|
systemctl --user restart "$svc.service"
|
||||||
|
sleep 2
|
||||||
|
if ! systemctl --user is-active --quiet "$svc.service"; then
|
||||||
|
err "$svc 重启失败"
|
||||||
|
journalctl --user -u "$svc.service" -n 40 --no-pager
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
}
|
||||||
|
|
||||||
|
[ "$CH_PG_IMAGE" = "true" ] && restart_svc datahub-postgres
|
||||||
|
[ "$CH_BACKEND" = "true" ] && restart_svc datahub-backend
|
||||||
|
[ "$CH_FRONTEND" = "true" ] && restart_svc datahub-frontend
|
||||||
|
|
||||||
|
# Quadlet 改了但镜像没改时,仍需重启对应服务
|
||||||
|
if [ "$CH_QUADLET" = "true" ]; then
|
||||||
|
DIFF_Q=$(git diff --name-only "$OLD_HEAD" "$NEW_HEAD" -- deploy/podman/quadlet/ 2>/dev/null || true)
|
||||||
|
[ "$CH_PG_IMAGE" = "false" ] && echo "$DIFF_Q" | grep -q "datahub-postgres" && restart_svc datahub-postgres
|
||||||
|
[ "$CH_BACKEND" = "false" ] && echo "$DIFF_Q" | grep -q "datahub-backend" && restart_svc datahub-backend
|
||||||
|
[ "$CH_FRONTEND" = "false" ] && echo "$DIFF_Q" | grep -q "datahub-frontend" && restart_svc datahub-frontend
|
||||||
|
echo "$DIFF_Q" | grep -q "datahub-rabbitmq" && restart_svc datahub-rabbitmq
|
||||||
|
fi
|
||||||
|
|
||||||
|
# 7. 迁移提示
|
||||||
|
if [ "$CH_MIGRATIONS" = "true" ] && [ "$SKIP_MIGRATE" = false ]; then
|
||||||
|
echo
|
||||||
|
warn "检测到新迁移文件:"
|
||||||
|
git diff --name-only "$OLD_HEAD" "$NEW_HEAD" -- backend/migrations/ | sed 's/^/ /'
|
||||||
|
read -rp "立即执行迁移?(Y/n): " ans
|
||||||
|
ans="${ans,,}"
|
||||||
|
if [[ -z "$ans" || "$ans" == "y" ]]; then
|
||||||
|
podman exec -it datahub-backend php bin/hyperf.php migrate
|
||||||
|
ok "迁移完成"
|
||||||
|
else
|
||||||
|
warn "跳过。后续可手动执行:podman exec -it datahub-backend php bin/hyperf.php migrate"
|
||||||
|
fi
|
||||||
|
elif [ "$CH_MIGRATIONS" = "true" ]; then
|
||||||
|
warn "检测到新迁移但已 --skip-migrate,请手动执行"
|
||||||
|
fi
|
||||||
|
|
||||||
|
echo
|
||||||
|
ok "更新完成"
|
||||||
|
echo " $OLD_HEAD"
|
||||||
|
echo " → $NEW_HEAD"
|
||||||
@@ -8,6 +8,7 @@
|
|||||||
</head>
|
</head>
|
||||||
<body>
|
<body>
|
||||||
<div id="app"></div>
|
<div id="app"></div>
|
||||||
|
<script src="/config.js"></script>
|
||||||
<script type="module" src="/src/main.ts"></script>
|
<script type="module" src="/src/main.ts"></script>
|
||||||
</body>
|
</body>
|
||||||
</html>
|
</html>
|
||||||
|
|||||||
@@ -0,0 +1,8 @@
|
|||||||
|
// 运行期注入的配置文件
|
||||||
|
// Vite 构建时此文件原样拷贝到 dist/;nginx 容器启动时由 docker-entrypoint.d/40-inject-config.sh
|
||||||
|
// 将 __API_BASE_URL__ 替换为真实地址(来自 API_BASE_URL 环境变量)
|
||||||
|
//
|
||||||
|
// 本地 dev 模式占位符不会被替换,request.ts 会自动 fallback 到 VITE_API_BASE_URL
|
||||||
|
window.__APP_CONFIG__ = {
|
||||||
|
apiBaseUrl: '__API_BASE_URL__',
|
||||||
|
};
|
||||||
@@ -13,7 +13,21 @@ interface RequestOptions extends RequestInit {
|
|||||||
timeout?: number
|
timeout?: number
|
||||||
}
|
}
|
||||||
|
|
||||||
const API_BASE_URL = import.meta.env.VITE_API_BASE_URL || ''
|
// 运行期配置优先(容器启动时由 nginx entrypoint 注入到 /config.js 写入 window.__APP_CONFIG__)
|
||||||
|
// 构建期 VITE_API_BASE_URL 作为本地 dev 的 fallback
|
||||||
|
function resolveApiBaseUrl(): string {
|
||||||
|
if (typeof window !== 'undefined') {
|
||||||
|
const runtime = (window as unknown as { __APP_CONFIG__?: { apiBaseUrl?: string } }).__APP_CONFIG__
|
||||||
|
const url = runtime?.apiBaseUrl
|
||||||
|
// 占位符未替换时(如本地直接打开 public/config.js)忽略
|
||||||
|
if (url && !(url.startsWith('__') && url.endsWith('__'))) {
|
||||||
|
return url
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return import.meta.env.VITE_API_BASE_URL || ''
|
||||||
|
}
|
||||||
|
|
||||||
|
const API_BASE_URL = resolveApiBaseUrl()
|
||||||
|
|
||||||
async function request<T = unknown>(url: string, options: RequestOptions = {}): Promise<T> {
|
async function request<T = unknown>(url: string, options: RequestOptions = {}): Promise<T> {
|
||||||
url = `${API_BASE_URL}${url}`
|
url = `${API_BASE_URL}${url}`
|
||||||
|
|||||||
Reference in New Issue
Block a user