feat(部署): 新增 deployment/ 内外网两套按侧部署包

按 demo/ 的写法把交付包改成「角色由目录决定」:iw/ 拷内网机、ow/ 拷外网机,
各自含镜像 tar、SQL、配置与 install.sh,install.sh 不再收 iw/ow 参数。

前端不再打进镜像,改为静态包放在 <侧>/web/ 只读挂进 nginx:1.27-alpine,
现场临时改页面直接替换文件 + restart web 即可。数据仍全部挂宿主机,
子目录命名与 deploy/offline 一致(pgdata/redis/mq/upload/logs/gate),
默认根改为包内 ./volumes,放数据盘只改 .env 的 SFILE_DATA_ROOT。

后端镜像 sfile-app:1.0.1 含传输优化那一批(实测组包 11.4×、解包 9.9×)。
出包脚本 deployment/scripts/build-package.sh 负责 jar→镜像→前端双包→sql→tar→VERSION。
This commit is contained in:
KodouWZF
2026-11-04 21:44:56 +08:00
parent 339f2c34ab
commit d39b3b035d
884 changed files with 316455 additions and 0 deletions

198
deployment/ow/scripts/install.sh Executable file
View File

@@ -0,0 +1,198 @@
#!/usr/bin/env bash
# =============================================================================
# s-file 一键安装(在本机执行;内网机用 iw/ 那份,外网机用 ow/ 那份)
#
# cd /opt/sfile-iw && ./scripts/install.sh
#
# 做六件事:环境自检 -> 校验交付物 -> 校验并载入镜像 tar -> 建数据目录 -> 起栈 -> 等健康
# 幂等:重复执行只重建有差异的容器,不动已初始化好的数据库
# 本目录就是配置:角色/口令/端口/目录全在同级 .env 里,改完重跑本脚本即可
# =============================================================================
set -euo pipefail
HERE="$(cd "$(dirname "$0")/.." && pwd)"
cd "$HERE"
envget() { grep -E "^[[:space:]]*$1=" .env | tail -1 | cut -d= -f2- | tr -d '"' | sed 's/[[:space:]]*$//'; }
step() { printf '\n\033[1;36m==> %s\033[0m\n' "$1"; }
die() { printf '\033[1;31m!!! %s\033[0m\n' "$1" >&2; exit 1; }
[ -f .env ] || die "缺少 .env(交付包不完整,本目录下应有一份出厂 .env)"
ROLE="$(envget SFILE_ROLE)"
case "$ROLE" in
iw|ow) ;;
*) die ".env 里 SFILE_ROLE=「$ROLE」不合法,只能是 iw(内网机)或 ow(外网机)" ;;
esac
echo "本机角色:$ROLE(传输方向 location=$(envget SFILE_TRANSFER_LOCATION))"
# ---------- 1. 前置自检 ----------
step "检查 docker 与 compose"
command -v docker >/dev/null 2>&1 || die "未安装 docker,请先装 Docker Engine(信创系统用厂商源,版本号 >= 20.10)"
docker info >/dev/null 2>&1 || die "docker 守护进程不可用,检查服务是否启动、当前用户是否在 docker 组"
if docker compose version >/dev/null 2>&1; then
DC="docker compose"
elif command -v docker-compose >/dev/null 2>&1; then
DC="docker-compose"
else
die "缺少 compose:安装 docker compose v2 插件,或用 docker-compose 命令"
fi
echo " 使用:$DC"
echo " docker server:$(docker version --format '{{.Server.Version}}')"
if grep -q "CHANGE_ME" .env; then
# MeshSafe 的 secret 只有真的启用检测时才必须填,否则现场没接 MeshSafe 就装不了机
if [ "$(envget MESHSAFE_ENABLED)" = "true" ] && grep -qE "^MESHSAFE_SECRET=.*CHANGE_ME" .env; then
die "启用了 MeshSafe 但 MESHSAFE_SECRET 还是占位值:按 MeshSafe 交付单改成白名单里的 secret,或把 MESHSAFE_ENABLED 改成 false"
fi
rest="$(grep -n CHANGE_ME .env | grep -v '^.*MESHSAFE_SECRET=' | cut -d: -f2 | tr '\n' ' ')"
[ -z "$rest" ] || die ".env 里还有 CHANGE_ME 未替换:$rest"
fi
step "检查交付物"
[ -d images ] || die "缺少 images/ 目录(镜像 tar 应在本目录的 images/ 下)"
tar_count=$(find images -maxdepth 1 -name '*.tar' | wc -l)
[ "$tar_count" -eq 5 ] || die "images/ 下应有 5 个 .tar(app/nginx/postgres/redis/rabbitmq),实际 $tar_count 个:$(ls images)"
for f in sql/01-init-table.sql sql/02-init-data.sql config/application.yml nginx/web.conf; do
[ -s "$f" ] || die "缺少 $f(交付包不完整)"
done
# 前端是静态包,缺了 nginx 起来只有默认页,看着像装好了其实没有界面
[ -s web/index.html ] || die "缺少 web/index.html —— 前端静态包没随包带过来"
[ -n "$(ls -A web/assets 2>/dev/null)" ] || die "web/assets 是空的 —— 前端静态包不完整"
echo " 镜像 tar:5 个,SQL / 后端配置 / nginx 配置 / 前端包就位"
# 端口占用先探一次:装到一半发现 80 被占,回滚比改 .env 麻烦
WEB_PORT="$(envget WEB_PORT)"
if command -v ss >/dev/null 2>&1 && ss -ltn 2>/dev/null | awk '{print $4}' | grep -qE "[:.]${WEB_PORT:-80}$"; then
printf ' \033[1;33m提示:宿主机 %s 端口已被占用,改 .env 的 WEB_PORT 后重跑\033[0m\n' "${WEB_PORT:-80}"
fi
# ---------- 2. 校验交付盘 + 镜像架构 ----------
# 650M 的 tar 经 U 盘/内网 scp 拷坏是最常见的现场故障,坏包 load 到一半才报错更难查
if [ -f images/SHA256SUMS.txt ] && [ "${SKIP_CHECKSUM:-0}" != "1" ]; then
step "校验镜像 tar 完整性(sha256,约半分钟)"
( cd images && sha256sum -c SHA256SUMS.txt ) \
|| die "有 tar 与 SHA256SUMS.txt 不一致:交付盘拷坏了,重新拷一份;确认无碍可 SKIP_CHECKSUM=1 跳过"
fi
step "核对镜像架构"
HOST_ARCH="$(uname -m)"
case "$HOST_ARCH" in
x86_64) EXPECT="amd64" ;;
aarch64|arm64) EXPECT="arm64" ;;
*) EXPECT="unknown" ;;
esac
echo " 本机:$HOST_ARCH (期望镜像架构 ${EXPECT})"
# 架构在镜像的 config blob 里,不在 manifest.json —— manifest.json 只给 Config 的路径
tar_arch() {
local t="$1" cfg
cfg="$(tar -xOf "$t" manifest.json 2>/dev/null | tr -d '\n' \
| { grep -oE '"Config":"[^"]+"' || true; } | awk -F'"' 'NR==1{print $4}')"
[ -n "$cfg" ] || { echo ""; return 0; }
tar -xOf "$t" "$cfg" 2>/dev/null | tr -d '\n' \
| { grep -oE '"architecture":"[a-z0-9-]+"' || true; } | awk -F'"' 'NR==1{print $4}'
}
if [ "$EXPECT" != "unknown" ]; then
# app 和 pg 各查一个:一个是自己构建的,一个是基础镜像,架构错了必是出盘时选错平台
for t in images/sfile-app.tar images/postgres.tar; do
[ -f "$t" ] || continue
# 不能用 `| head -1`:pipefail 下 head 提前关管道会让上游 grep 收到 SIGPIPE(141),脚本静默中断
IMG_ARCH="$(tar_arch "$t")"
if [ -n "${IMG_ARCH:-}" ] && [ "$IMG_ARCH" != "$EXPECT" ]; then
die "$(basename "$t") 是 $IMG_ARCH,本机是 $EXPECT —— 别硬装,回来重出对应架构的包"
fi
printf ' %-18s %s\n' "$(basename "$t")" "${IMG_ARCH:-未识别,继续}"
done
fi
# ---------- 3. 载入镜像 ----------
step "docker load 载入离线镜像"
for t in images/*.tar; do
echo " <- $t"
docker load -i "$t"
done
# compose 用的是 .env 里的 tag,缺一个就会去联网拉,离线机上直接卡死
for img in "$(envget APP_IMAGE)" "$(envget NGINX_IMAGE)" "$(envget PG_IMAGE)" \
"$(envget REDIS_IMAGE)" "$(envget MQ_IMAGE)"; do
docker image inspect "$img" >/dev/null 2>&1 \
|| die "载入后仍看不到镜像 $img —— tar 与 .env 的 tag 不一致,别继续,回来看 images/ 和 .env"
echo " ✓ $img"
done
# ---------- 4. 目录 ----------
step "创建数据与摆渡目录"
DATA_ROOT="$(envget SFILE_DATA_ROOT)"
GATE_SEND="$(envget GATE_SEND_DIR)"
GATE_RECV="$(envget GATE_RECV_DIR)"
[ -n "$DATA_ROOT" ] || die ".env 里 SFILE_DATA_ROOT 为空"
mkdir -p "$DATA_ROOT"/{upload,logs/nginx,pgdata,redis,mq}
mkdir -p "$GATE_SEND" "$GATE_RECV"
# 数据盘挂载点没起来时直接建目录会落到根分区,先确认一下
case "$DATA_ROOT" in
/*) mountpoint -q "$DATA_ROOT" 2>/dev/null \
|| printf ' \033[1;33m提示:%s 不是独立挂载点,确认容量够(上传+库+日志)\033[0m\n' "$DATA_ROOT" ;;
*) echo " 数据目录用本目录下的相对路径:$(cd "$DATA_ROOT" && pwd)" ;;
esac
# pgdata 提前给到容器内 postgres 的 uid=999,避免镜像入口脚本 chown 失败;
# redis/mq 的官方入口脚本自己会改属主,这里只保证目录存在且可写
chown -R 999:999 "$DATA_ROOT/pgdata" 2>/dev/null || true
chmod -R 750 "$DATA_ROOT" 2>/dev/null || true
# SELinux(麒麟/统信多为 enforcing):无 :z 标记的绑定挂载需要放开标签,否则库起不来、
# nginx 读不到前端包(表现为 403)。这里把 compose 里所有 bind 挂载点都标一遍
if command -v chcon >/dev/null 2>&1; then
chcon -Rt svirt_sandbox_file_t \
"$HERE/config" "$HERE/nginx" "$HERE/web" "$HERE/sql" \
"$DATA_ROOT" "$GATE_SEND" "$GATE_RECV" 2>/dev/null \
|| echo " chcon 失败(未启用 SELinux 可忽略)"
fi
echo " $DATA_ROOT / $GATE_SEND / $GATE_RECV 就位"
# ---------- 5. 起栈 ----------
step "启动服务(首次建库约 1-3 分钟)"
$DC up -d
step "等待健康检查"
for svc in pg redis mq app web; do
name="sfile-${ROLE}-${svc}"
printf ' %-24s ' "$name"
st=""
for _ in $(seq 1 60); do
st="$(docker inspect --format '{{if .State.Health}}{{.State.Health.Status}}{{else}}{{.State.Status}}{{end}}' "$name" 2>/dev/null || echo missing)"
case "$st" in
healthy|running) printf '\033[32m%s\033[0m\n' "$st"; break ;;
missing) sleep 2 ;;
*) sleep 5 ;;
esac
done
if [ "$st" != "healthy" ] && [ "$st" != "running" ]; then
printf '\033[31m超时(最后状态:%s)\033[0m\n' "${st:-unknown}"
$DC ps
echo " 看日志:docker logs --tail 200 ${name}"
exit 1
fi
done
# ---------- 6. 出口信息 ----------
IP="$(hostname -I 2>/dev/null | tr ' ' '\n' | { grep -E '^[0-9]+\.' || true; } | awk 'NR==1{print}')"
step "完成"
cat <<EOF
访问地址: http://${IP:-<本机IP>}:${WEB_PORT:-80}
角色: ${ROLE}(传输方向 location=$(envget SFILE_TRANSFER_LOCATION))
数据目录: $(cd "$DATA_ROOT" && pwd)
摆渡: 发出 $GATE_SEND / 收到 $GATE_RECV
MeshSafe: $(envget MESHSAFE_ENABLED) -> $(envget MESHSAFE_BASE_URL)
常用命令:
$DC ps 看状态
docker logs -f sfile-${ROLE}-app 看后端日志
$DC restart app 改完 .env / config 后重启后端
$DC restart web 替换 web/ 里的前端文件后刷新站点
EOF
echo " 首次登录后立刻改掉 admin 的默认口令。"