feat(stack): 新增 stack 模式管理功能

- 新增 `php think admin:stack:mode` 命令,支持 list/use/current/rollback 操作
- 新增 StackModeService 服务,负责模式切换、备份与回滚逻辑
- 在 source/stack/ 目录下添加 default、full、base-build 三种模式的配置文件
- 更新 UlthonAdminService 以注册新的命令行工具
This commit is contained in:
augushong
2026-04-24 23:20:13 +08:00
parent 0945d42d0a
commit b44fcfd86c
15 changed files with 1010 additions and 0 deletions

28
source/stack/README.md Normal file
View File

@@ -0,0 +1,28 @@
# Stack 模式目录规范
本目录用于维护“模式化生效文件”,由 `php think admin:stack:mode` 命令读取并覆盖到仓库根目录。
## 目录结构
- `source/stack/stack.json`:全局清单,定义 `default_mode``managed_files``modes` 元数据。
- `source/stack/default/`:默认行为基线目录。
- `source/stack/{mode}/`:具体模式目录,按“仓库相对路径”放置文件。
## default 目录规则(强约束)
- `source/stack/default/` 必须与代码库默认行为一致。
- 当默认行为文件变更时(如 `Dockerfile``docker-compose.yaml``.gitea/workflows/build-and-deploy.yml`),必须同步更新 `default` 目录对应文件。
- 该规则通过目录维护规范与代码评审保障,不作为每次切换命令的运行时阻断条件。
## 模式覆盖规则
- 仅允许覆盖 `stack.json``managed_files` 中声明的文件。
- 切换时按“目标模式优先default 模式兜底”解析最终文件内容:
- 目标模式提供某文件:使用目标模式文件;
- 目标模式未提供某文件:回落使用 `default` 目录对应文件。
- 首期固定模式:`default``full``base-build`
## 基础镜像说明
- `base-build/docker/Dockerfile.base` 为基础镜像构建文件,默认标记为作者维护范围(`author_only=true`)。
- 推荐标签策略:`latest` + 时间戳(如 `20260424120000`)。

View File

@@ -0,0 +1,33 @@
ARG BASE_IMAGE=ulthon/ulthon_admin-base:latest
FROM ${BASE_IMAGE}
# 设置工作目录
WORKDIR /var/www/html
# 预先拷贝 composer 文件并安装依赖,利用 Docker 缓存
COPY composer.json composer.lock /var/www/html/
RUN composer install --no-dev --no-interaction --no-scripts --no-autoloader
# 将当前目录下的文件拷贝到工作目录
COPY . /var/www/html
# 生成自动加载文件
RUN composer dump-autoload --optimize --no-dev --classmap-authoritative
VOLUME /var/www/html/runtime
VOLUME /var/www/html/public/storage
VOLUME /var/www/html/public/build
VOLUME /var/www/html/storage
# 挂载主目录,也可以选择直接挂载主目录,可以把上面的几个指定的目录删掉
# VOLUME ["/var/www/html"]
# 暴露 Nginx 端口
EXPOSE 80
RUN chmod +x /var/www/html/docker/run.sh
# 启动 Nginx PHP 然后阻塞
ENTRYPOINT ["/var/www/html/docker/run.sh"]
CMD ["server"]

View File

@@ -0,0 +1,20 @@
name: ulthon_admin
services:
ulthon_admin:
# 正式环境中您应当构建一个完整镜像使用镜像名称或id运行不要使用dockerfile
# image: ulthon/ulthon_admin:v1
build:
context: . # Dockerfile 所在的目录
dockerfile: Dockerfile # Dockerfile 的名称
restart: always
ports:
- "88:80" # HTTP
volumes:
- ./:/var/www/html # 直接分发代码可以去掉注释并将下面的目录增加注释
# - ./runtime:/var/www/html/runtime
# - ./public/storage:/var/www/html/public/storage
# - ./public/build:/var/www/html/public/build
# - ./storage:/var/www/html/storage

View File

@@ -0,0 +1,39 @@
FROM php:8.2-fpm-bookworm
RUN rm -rf /etc/apt/sources.list.d/* \
&& echo "deb http://mirrors.ustc.edu.cn/debian/ bookworm main contrib non-free non-free-firmware" > /etc/apt/sources.list \
&& echo "deb http://mirrors.ustc.edu.cn/debian/ bookworm-updates main contrib non-free non-free-firmware" >> /etc/apt/sources.list \
&& echo "deb http://mirrors.ustc.edu.cn/debian-security bookworm-security main contrib non-free non-free-firmware" >> /etc/apt/sources.list
RUN apt-get update
# 安装 nginx
RUN apt-get install -y nginx
ADD --chmod=0755 https://nexus.hl7.top:1243/repository/github-raw-proxy/mlocati/docker-php-extension-installer/releases/latest/download/install-php-extensions /usr/local/bin/
# 配置代理
RUN sed -i 's|aomedia.googlesource.com|nexus.hl7.top:1243/repository/raw-aomedia.googlesource.com|g' /usr/local/bin/install-php-extensions
RUN sed -i 's|chromium.googlesource.com|nexus.hl7.top:1243/repository/raw-chromium.googlesource.com|g' /usr/local/bin/install-php-extensions
RUN sed -i 's|https://github.com|https://nexus.hl7.top:1243/repository/github-raw-proxy|g' /usr/local/bin/install-php-extensions
RUN install-php-extensions pdo_mysql
RUN install-php-extensions gd
RUN install-php-extensions fileinfo
RUN install-php-extensions opcache
RUN install-php-extensions redis
RUN install-php-extensions event
RUN install-php-extensions imagick
RUN install-php-extensions zip
RUN install-php-extensions pcntl
# 清理默认 Nginx 配置
RUN rm /etc/nginx/sites-available/default /etc/nginx/sites-enabled/default
# 安装 Composer
COPY --from=composer:2 /usr/bin/composer /usr/local/bin/composer
RUN chmod +x /usr/local/bin/composer
RUN composer config -g repos.packagist composer https://nexus.hl7.top:1243/repository/composer-proxy/
# 设置工作目录
WORKDIR /var/www/html

View File

@@ -0,0 +1,140 @@
name: build-and-deploy
on:
push:
workflow_dispatch:
env:
REMOTE_APP_DIR: /data/projects/ulthon_admin/docker
PACKAGE_NAME: ulthon_admin_release.tar.gz
COMPOSE_PROJECT_NAME: ulthon_admin
DB_HOSTNAME: host.docker.internal
jobs:
deploy_host15:
name: 直传代码并部署到 Host15
runs-on: main
container:
image: catthehacker/ubuntu:act-latest
steps:
- name: Checkout
uses: actions/checkout@v4
with:
fetch-depth: 1
- name: 生成 .env
shell: bash
env:
MYSQL_PASSWORD: ${{ secrets.MYSQL_PASSWORD }}
DB_HOSTNAME: ${{ env.DB_HOSTNAME }}
run: |
set -euo pipefail
cp .example.env .env
awk -v host="$DB_HOSTNAME" -v newpwd="$MYSQL_PASSWORD" '
BEGIN { has_host = 0; has_pwd = 0 }
$0 ~ /^HOSTNAME=/ {
print "HOSTNAME=" host
has_host = 1
next
}
$0 ~ /^PASSWORD=/ {
print "PASSWORD=" newpwd
has_pwd = 1
next
}
{ print }
END {
if (!has_host) print "HOSTNAME=" host
if (!has_pwd) print "PASSWORD=" newpwd
}' .env > .env.tmp
mv .env.tmp .env
- name: 打包发布文件
shell: bash
env:
PACKAGE_NAME: ${{ env.PACKAGE_NAME }}
run: |
set -euo pipefail
TMP_PACKAGE="/tmp/${PACKAGE_NAME}"
rm -f "$TMP_PACKAGE" "$PACKAGE_NAME"
tar -czf "$TMP_PACKAGE" \
--exclude="$PACKAGE_NAME" \
--exclude-vcs \
--exclude="./runtime/*" \
--exclude="./.trae/*" \
--exclude="./source/clients/uniapp/node_modules/*" \
.
cp -f "$TMP_PACKAGE" "$PACKAGE_NAME"
- name: 创建远端目录
uses: appleboy/ssh-action@v0.1.10
env:
REMOTE_APP_DIR: ${{ env.REMOTE_APP_DIR }}
with:
host: ${{ secrets.UL_HOST15_IP }}
username: ${{ secrets.UL_HOST15_USER }}
password: ${{ secrets.UL_HOST15_PASSWORD }}
port: ${{ secrets.UL_HOST15_PORT }}
envs: REMOTE_APP_DIR
script: |
set -euo pipefail
mkdir -p "${REMOTE_APP_DIR}/incoming"
mkdir -p "${REMOTE_APP_DIR}/releases"
- name: 上传发布包与 compose
uses: appleboy/scp-action@v0.1.7
env:
REMOTE_APP_DIR: ${{ env.REMOTE_APP_DIR }}
PACKAGE_NAME: ${{ env.PACKAGE_NAME }}
with:
host: ${{ secrets.UL_HOST15_IP }}
username: ${{ secrets.UL_HOST15_USER }}
password: ${{ secrets.UL_HOST15_PASSWORD }}
port: ${{ secrets.UL_HOST15_PORT }}
source: "${{ env.PACKAGE_NAME }},docker-compose.yaml"
target: "${{ env.REMOTE_APP_DIR }}/incoming"
overwrite: true
- name: 远端解压并启动
uses: appleboy/ssh-action@v0.1.10
env:
REMOTE_APP_DIR: ${{ env.REMOTE_APP_DIR }}
PACKAGE_NAME: ${{ env.PACKAGE_NAME }}
COMPOSE_PROJECT_NAME: ${{ env.COMPOSE_PROJECT_NAME }}
GITHUB_RUN_ID: ${{ github.run_id }}
with:
host: ${{ secrets.UL_HOST15_IP }}
username: ${{ secrets.UL_HOST15_USER }}
password: ${{ secrets.UL_HOST15_PASSWORD }}
port: ${{ secrets.UL_HOST15_PORT }}
timeout: 120s
command_timeout: 60m
envs: REMOTE_APP_DIR,PACKAGE_NAME,COMPOSE_PROJECT_NAME,GITHUB_RUN_ID
script: |
set -euo pipefail
RELEASE_DIR="${REMOTE_APP_DIR}/releases/${GITHUB_RUN_ID}"
PACKAGE_PATH="${REMOTE_APP_DIR}/incoming/${PACKAGE_NAME}"
COMPOSE_PATH="${REMOTE_APP_DIR}/incoming/docker-compose.yaml"
rm -rf "${RELEASE_DIR}"
mkdir -p "${RELEASE_DIR}"
tar -xzf "${PACKAGE_PATH}" -C "${RELEASE_DIR}"
if [ -f "${COMPOSE_PATH}" ]; then
cp -f "${COMPOSE_PATH}" "${RELEASE_DIR}/docker-compose.yaml"
fi
cd "${RELEASE_DIR}"
export COMPOSE_PROJECT_NAME
docker compose down || true
docker compose up -d --build --remove-orphans
ln -sfn "${RELEASE_DIR}" "${REMOTE_APP_DIR}/current"
ls -1dt "${REMOTE_APP_DIR}/releases"/* 2>/dev/null | tail -n +6 | xargs -r rm -rf

View File

@@ -0,0 +1,81 @@
FROM php:8.2-fpm-bookworm
RUN rm -rf /etc/apt/sources.list.d/* \
&& echo "deb http://mirrors.ustc.edu.cn/debian/ bookworm main contrib non-free non-free-firmware" > /etc/apt/sources.list \
&& echo "deb http://mirrors.ustc.edu.cn/debian/ bookworm-updates main contrib non-free non-free-firmware" >> /etc/apt/sources.list \
&& echo "deb http://mirrors.ustc.edu.cn/debian-security bookworm-security main contrib non-free non-free-firmware" >> /etc/apt/sources.list
RUN apt-get update
# 安装nginx
RUN apt-get install -y nginx
ADD --chmod=0755 https://nexus.hl7.top:1243/repository/github-raw-proxy/mlocati/docker-php-extension-installer/releases/latest/download/install-php-extensions /usr/local/bin/
# 配置代理
RUN sed -i 's|aomedia.googlesource.com|nexus.hl7.top:1243/repository/raw-aomedia.googlesource.com|g' /usr/local/bin/install-php-extensions
RUN sed -i 's|chromium.googlesource.com|nexus.hl7.top:1243/repository/raw-chromium.googlesource.com|g' /usr/local/bin/install-php-extensions
RUN sed -i 's|https://github.com|https://nexus.hl7.top:1243/repository/github-raw-proxy|g' /usr/local/bin/install-php-extensions
RUN install-php-extensions pdo_mysql
RUN install-php-extensions gd
RUN install-php-extensions fileinfo
RUN install-php-extensions opcache
RUN install-php-extensions redis
RUN install-php-extensions event
RUN install-php-extensions imagick
RUN install-php-extensions zip
RUN install-php-extensions pcntl
# 清理默认 Nginx 配置
RUN rm /etc/nginx/sites-available/default /etc/nginx/sites-enabled/default
# 安装其他需要的依赖
# RUN apt-get install -y ffmpeg
# RUN apt-get install -y libreoffice
# RUN apt-get install -y redis-server
# RUN apt-get install -y git
# 设置工作目录
WORKDIR /var/www/html
# 安装 Composer
COPY --from=composer:2 /usr/bin/composer /usr/local/bin/composer
RUN chmod +x /usr/local/bin/composer
RUN composer config -g repos.packagist composer https://nexus.hl7.top:1243/repository/composer-proxy/
# 设置工作目录
WORKDIR /var/www/html
# 预先拷贝 composer 文件并安装依赖,利用 Docker 缓存
COPY composer.json composer.lock /var/www/html/
RUN composer install --no-dev --no-interaction --no-scripts --no-autoloader
# 将当前目录下的文件拷贝到工作目录
COPY . /var/www/html
# 生成自动加载文件
RUN composer dump-autoload --optimize --no-dev --classmap-authoritative
# 内部安装compsoer并安装依赖如果不需要可以注释掉
# RUN install-php-extensions @composer
VOLUME /var/www/html/runtime
VOLUME /var/www/html/public/storage
VOLUME /var/www/html/public/build
VOLUME /var/www/html/storage
# 挂载主目录,也可以选择直接挂载主目录,可以把上面的几个指定的目录删掉
# VOLUME ["/var/www/html"]
# 暴露 Nginx 端口
EXPOSE 80
RUN chmod +x /var/www/html/docker/run.sh
# 启动 Nginx PHP 然后阻塞
ENTRYPOINT ["/var/www/html/docker/run.sh"]
CMD ["server"]

View File

@@ -0,0 +1,20 @@
name: ulthon_admin
services:
ulthon_admin:
# 正式环境中您应当构建一个完整镜像使用镜像名称或id运行不要使用dockerfile
# image: ulthon/ulthon_admin:v1
build:
context: . # Dockerfile 所在的目录
dockerfile: Dockerfile # Dockerfile 的名称
restart: always
ports:
- "88:80" # HTTP
volumes:
- ./:/var/www/html # 直接分发代码可以去掉注释并将下面的目录增加注释
# - ./runtime:/var/www/html/runtime
# - ./public/storage:/var/www/html/public/storage
# - ./public/build:/var/www/html/public/build
# - ./storage:/var/www/html/storage

View File

@@ -0,0 +1,81 @@
FROM php:8.2-fpm-bookworm
RUN rm -rf /etc/apt/sources.list.d/* \
&& echo "deb http://mirrors.ustc.edu.cn/debian/ bookworm main contrib non-free non-free-firmware" > /etc/apt/sources.list \
&& echo "deb http://mirrors.ustc.edu.cn/debian/ bookworm-updates main contrib non-free non-free-firmware" >> /etc/apt/sources.list \
&& echo "deb http://mirrors.ustc.edu.cn/debian-security bookworm-security main contrib non-free non-free-firmware" >> /etc/apt/sources.list
RUN apt-get update
# 安装nginx
RUN apt-get install -y nginx
ADD --chmod=0755 https://nexus.hl7.top:1243/repository/github-raw-proxy/mlocati/docker-php-extension-installer/releases/latest/download/install-php-extensions /usr/local/bin/
# 配置代理
RUN sed -i 's|aomedia.googlesource.com|nexus.hl7.top:1243/repository/raw-aomedia.googlesource.com|g' /usr/local/bin/install-php-extensions
RUN sed -i 's|chromium.googlesource.com|nexus.hl7.top:1243/repository/raw-chromium.googlesource.com|g' /usr/local/bin/install-php-extensions
RUN sed -i 's|https://github.com|https://nexus.hl7.top:1243/repository/github-raw-proxy|g' /usr/local/bin/install-php-extensions
RUN install-php-extensions pdo_mysql
RUN install-php-extensions gd
RUN install-php-extensions fileinfo
RUN install-php-extensions opcache
RUN install-php-extensions redis
RUN install-php-extensions event
RUN install-php-extensions imagick
RUN install-php-extensions zip
RUN install-php-extensions pcntl
# 清理默认 Nginx 配置
RUN rm /etc/nginx/sites-available/default /etc/nginx/sites-enabled/default
# 安装其他需要的依赖
# RUN apt-get install -y ffmpeg
# RUN apt-get install -y libreoffice
# RUN apt-get install -y redis-server
# RUN apt-get install -y git
# 设置工作目录
WORKDIR /var/www/html
# 安装 Composer
COPY --from=composer:2 /usr/bin/composer /usr/local/bin/composer
RUN chmod +x /usr/local/bin/composer
RUN composer config -g repos.packagist composer https://nexus.hl7.top:1243/repository/composer-proxy/
# 设置工作目录
WORKDIR /var/www/html
# 预先拷贝 composer 文件并安装依赖,利用 Docker 缓存
COPY composer.json composer.lock /var/www/html/
RUN composer install --no-dev --no-interaction --no-scripts --no-autoloader
# 将当前目录下的文件拷贝到工作目录
COPY . /var/www/html
# 生成自动加载文件
RUN composer dump-autoload --optimize --no-dev --classmap-authoritative
# 内部安装compsoer并安装依赖如果不需要可以注释掉
# RUN install-php-extensions @composer
VOLUME /var/www/html/runtime
VOLUME /var/www/html/public/storage
VOLUME /var/www/html/public/build
VOLUME /var/www/html/storage
# 挂载主目录,也可以选择直接挂载主目录,可以把上面的几个指定的目录删掉
# VOLUME ["/var/www/html"]
# 暴露 Nginx 端口
EXPOSE 80
RUN chmod +x /var/www/html/docker/run.sh
# 启动 Nginx PHP 然后阻塞
ENTRYPOINT ["/var/www/html/docker/run.sh"]
CMD ["server"]

View File

@@ -0,0 +1,20 @@
name: ulthon_admin
services:
ulthon_admin:
# 正式环境中您应当构建一个完整镜像使用镜像名称或id运行不要使用dockerfile
# image: ulthon/ulthon_admin:v1
build:
context: . # Dockerfile 所在的目录
dockerfile: Dockerfile # Dockerfile 的名称
restart: always
ports:
- "88:80" # HTTP
volumes:
- ./:/var/www/html # 直接分发代码可以去掉注释并将下面的目录增加注释
# - ./runtime:/var/www/html/runtime
# - ./public/storage:/var/www/html/public/storage
# - ./public/build:/var/www/html/public/build
# - ./storage:/var/www/html/storage

22
source/stack/stack.json Normal file
View File

@@ -0,0 +1,22 @@
{
"default_mode": "default",
"managed_files": [
"Dockerfile",
"docker-compose.yaml",
".gitea/workflows/build-and-deploy.yml"
],
"modes": {
"default": {
"description": "代码库默认行为基线",
"author_only": false
},
"full": {
"description": "全量构建模式(兼容历史行为)",
"author_only": false
},
"base-build": {
"description": "基础镜像 + 应用构建模式",
"author_only": true
}
}
}