refactor(stack): default 模式改为 php think run,原 Docker 部署拆分为 docker-serve 模式

- source/stack/default/ 重命名为 docker-serve,保留 Dockerfile/docker-compose/CI/run.sh
- 新 default 模式仅提供 CI 文件(流水线中自动切换 docker-serve 再构建)
- 更新 stack.json 增加 docker-serve 模式声明
- 更新 README.md 同步模式说明
This commit is contained in:
augushong
2026-05-27 20:49:13 +08:00
parent 7ecbd03730
commit 28337c9694
7 changed files with 176 additions and 8 deletions

View File

@@ -1,26 +1,37 @@
# Stack 模式目录规范
本目录用于维护模式化生效文件,由 `php think admin:stack:mode` 命令读取并覆盖到仓库根目录。
本目录用于维护"模式化生效文件",由 `php think admin:stack:mode` 命令读取并覆盖到仓库根目录。
## 目录结构
- `source/stack/stack.json`:全局清单,定义 `default_mode``managed_files``modes` 元数据。
- `source/stack/default/`:默认行为基线目录。
- `source/stack/{mode}/`:具体模式目录,按仓库相对路径放置文件。
- `source/stack/default/`:默认行为基线目录`php think run`,纯 PHP 内置服务器运行)
- `source/stack/{mode}/`:具体模式目录,按"仓库相对路径"放置文件。
## default 目录规则(强约束)
- `source/stack/default/` 必须与代码库默认行为一致。
- 默认行为文件变更时(如 `Dockerfile``docker-compose.yaml``.gitea/workflows/build-and-deploy.yml`),必须同步更新 `default` 目录对应文件
- 默认行为`php think run`ThinkPHP 内置服务器),不依赖 Docker
- 当默认行为文件变更时(如 `.gitea/workflows/build-and-deploy.yml`),必须同步更新 `default` 目录对应文件。
- 该规则通过目录维护规范与代码评审保障,不作为每次切换命令的运行时阻断条件。
## 模式覆盖规则
- 仅允许覆盖 `stack.json``managed_files` 中声明的文件。
- 切换时按“目标模式优先default 模式兜底”解析最终文件内容
- 切换时按以下优先级解析最终文件:
- 目标模式提供某文件:使用目标模式文件;
- 目标模式未提供某文件:回落使用 `default` 目录对应文件
- 首期固定模式:`default``full``base-build`
- 目标模式未提供某文件:回落使用 `default` 目录对应文件
- 两者均未提供:从根目录删除该文件(带备份,可回滚)
## 可用模式
| 模式 | 说明 |
|------|------|
| `default` | 默认基线,`php think run` 运行,无 Docker 文件 |
| `docker-serve` | Docker 部署模式基于基础镜像nginx+php-fpm |
| `full` | 全量构建模式(兼容历史行为,从 PHP 镜像从头构建) |
| `base-build` | 基础镜像 + 应用构建模式author_only |
| `docker-dev` | Docker 开发模式nginx+php-fpm+MySQL+Redis+phpMyAdmin+Xdebug |
## 基础镜像说明

View File

@@ -22,6 +22,10 @@ jobs:
with:
fetch-depth: 1
- name: 切换到 docker-serve 模式
shell: bash
run: php think admin:stack:mode apply docker-serve
- name: 生成 .env
shell: bash
env:

View File

@@ -0,0 +1,149 @@
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: ulthon/debian-php82-composer-node20-act:20260503082548
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
composer config -g repos.packagist composer https://nexus.hl7.top:1243/repository/composer-proxy/
composer install --no-dev --no-interaction --prefer-dist --optimize-autoloader
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
docker compose ps
docker compose exec -T ulthon_admin test -f /var/www/html/vendor/autoload.php
docker compose exec -T ulthon_admin php -r "require '/var/www/html/vendor/autoload.php'; echo 'autoload-ok'.PHP_EOL;"
docker compose logs --tail=80 ulthon_admin
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

@@ -12,7 +12,11 @@
],
"modes": {
"default": {
"description": "代码库默认行为基线",
"description": "代码库默认行为基线php think run",
"author_only": false
},
"docker-serve": {
"description": "Docker 部署模式基于基础镜像nginx+php-fpm",
"author_only": false
},
"full": {