├── .gitattributes ├── .github └── workflows │ └── docker-build.yml ├── .idea ├── .gitignore ├── github_action_docker_build_web.iml ├── inspectionProfiles │ └── profiles_settings.xml ├── misc.xml ├── modules.xml └── vcs.xml ├── LICENSE ├── README.md ├── config.env ├── doc ├── actions_secrets.png ├── page.png ├── pat_permissions.png └── workflow_permissions.png └── worker.js /.gitattributes: -------------------------------------------------------------------------------- 1 | # Auto detect text files and perform LF normalization 2 | * text=auto 3 | -------------------------------------------------------------------------------- /.github/workflows/docker-build.yml: -------------------------------------------------------------------------------- 1 | name: 构建并推送 Docker Image 2 | 3 | on: 4 | workflow_dispatch: 5 | inputs: 6 | config_content: 7 | description: 'Content of config.env' 8 | required: true 9 | 10 | jobs: 11 | build: 12 | runs-on: ubuntu-22.04 13 | steps: 14 | # 检出代码 15 | - name: 检出代码 16 | uses: actions/checkout@v2 17 | 18 | # 保存 config.env 内容到 config_history/.env 19 | - name: 保存 config.env 内容 20 | run: | 21 | mkdir -p config_history 22 | echo "${{ github.event.inputs.config_content }}" > config_history/${{ github.run_id }}.env 23 | git config --global user.name 'GitHub Actions' 24 | git config --global user.email 'actions@github.com' 25 | git add config_history/${{ github.run_id }}.env 26 | git commit -m "Save config.env for run ${{ github.run_id }}" 27 | # 使用 PAT 进行身份验证 28 | git remote set-url origin https://${{ secrets.PAT }}@github.com/${{ github.repository }}.git 29 | # 拉取最新更改(避免冲突) 30 | git pull origin main 31 | git push origin main 32 | 33 | # 加载 config.env 配置 34 | - name: 加载 config.env 配置 35 | run: | 36 | set -o allexport 37 | source config_history/${{ github.run_id }}.env 38 | set +o allexport 39 | # 将所有环境变量导出到 GitHub Actions 专用的环境文件 40 | for var in $(cat config_history/${{ github.run_id }}.env | grep -v '^#' | sed 's/=.*//'); do 41 | echo "$var=${!var}" >> $GITHUB_ENV 42 | done 43 | 44 | # 输出变量值 45 | - name: 输出变量值 46 | run: | 47 | echo "GITHUB_REPO: $GITHUB_REPO" 48 | echo "BRANCH: $BRANCH" 49 | echo "ARCHITECTURES: $ARCHITECTURES" 50 | echo "DOCKER_TAG: $DOCKER_TAG" 51 | echo "DOCKERFILE_AMD64: $DOCKERFILE_AMD64" 52 | echo "DOCKERFILE_ARM64: $DOCKERFILE_ARM64" 53 | 54 | # 拉取指定分支的 GitHub 仓库源码 55 | - name: 拉取指定分支的代码 56 | run: | 57 | echo "Original GITHUB_REPO: $GITHUB_REPO" 58 | # 提取仓库路径(移除 https://github.com/ 和 .git 部分) 59 | REPO_PATH=$(echo "$GITHUB_REPO" | sed 's|https://github.com/||' | sed 's/\.git$//') 60 | echo "Processed REPO_PATH: $REPO_PATH" 61 | echo "Cloning repository from https://github.com/$REPO_PATH branch $BRANCH" 62 | git clone --single-branch --branch "$BRANCH" "https://${{ secrets.PAT }}@github.com/$REPO_PATH.git" repo 63 | cd repo 64 | 65 | # 设置 Docker Buildx 66 | - name: 设置 Docker Buildx 67 | uses: docker/setup-buildx-action@v2 68 | 69 | # 登录 Docker Hub 70 | - name: 登录 Docker Hub 71 | uses: docker/login-action@v2 72 | with: 73 | username: ${{ env.DOCKER_USERNAME }} 74 | password: ${{ secrets.DOCKER_PASSWORD }} 75 | 76 | # 为多个架构构建并推送 Docker 镜像 77 | - name: 为多个架构构建并推送 Docker 镜像 78 | working-directory: ./repo 79 | run: | 80 | echo "Building for architectures: $ARCHITECTURES" 81 | echo "Using Docker tag: $DOCKER_TAG" 82 | 83 | TAGS="" 84 | for arch in $(echo $ARCHITECTURES | tr ',' ' '); do 85 | case $arch in 86 | linux/amd64) 87 | DOCKERFILE_PATH=$DOCKERFILE_AMD64 88 | ARCH_SUFFIX="amd64" 89 | ;; 90 | linux/arm64) 91 | DOCKERFILE_PATH=$DOCKERFILE_ARM64 92 | ARCH_SUFFIX="arm64" 93 | ;; 94 | *) 95 | echo "Unsupported architecture: $arch" 96 | exit 1 97 | ;; 98 | esac 99 | 100 | echo "Building for $arch using Dockerfile: $DOCKERFILE_PATH" 101 | 102 | # 构建并推送带有架构特定标签的镜像 103 | docker buildx build --platform "$arch" -t "$DOCKER_USERNAME/$DOCKER_REPO_NAME:$DOCKER_TAG-$ARCH_SUFFIX" --file $DOCKERFILE_PATH --push . 104 | 105 | # 收集所有架构的镜像标签 106 | TAGS="$TAGS $DOCKER_USERNAME/$DOCKER_REPO_NAME:$DOCKER_TAG-$ARCH_SUFFIX" 107 | done 108 | 109 | # 创建多架构镜像清单 110 | docker buildx imagetools create -t "$DOCKER_USERNAME/$DOCKER_REPO_NAME:$DOCKER_TAG" $TAGS 111 | env: 112 | DOCKER_TAG: ${{ env.DOCKER_TAG }} 113 | DOCKERFILE_AMD64: ${{ env.DOCKERFILE_AMD64 }} 114 | DOCKERFILE_ARM64: ${{ env.DOCKERFILE_ARM64 }} 115 | -------------------------------------------------------------------------------- /.idea/.gitignore: -------------------------------------------------------------------------------- 1 | # 默认忽略的文件 2 | /shelf/ 3 | /workspace.xml 4 | -------------------------------------------------------------------------------- /.idea/github_action_docker_build_web.iml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /.idea/inspectionProfiles/profiles_settings.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 6 | -------------------------------------------------------------------------------- /.idea/misc.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 6 | 7 | -------------------------------------------------------------------------------- /.idea/modules.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /.idea/vcs.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2025 gua12345 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # 基于 GitHub Actions 的 Docker 打包系统 2 | 3 | ## 为什么做这个系统 4 | 5 | 以往使用 GitHub Actions 打包 Docker 镜像的流程较为繁琐,具体步骤如下: 6 | 7 | 1. 填写 `config.env` 文件。 8 | 2. 保存文件。 9 | 3. 手动触发 workflow。 10 | 4. 进入 `Actions` 页面。 11 | 5. 点击对应的 workflow。 12 | 6. 点击具体的 job,才能查看实时打包情况。 13 | 14 | 这种方式步骤多且不够直观,用户体验较差。 15 | 16 | **现在的流程** 通过一个简洁的网页界面大幅简化了操作: 17 | 18 | 1. 打开网页。 19 | 2. 填写必要信息。 20 | 3. 点击“开始构建”。 21 | 4. 点击“查看构建详情”,即可直接跳转到 GitHub Actions 的实时打包情况。 22 | 23 | 此外,系统还支持**查看历史构建配置**,方便用户快速重新打包错误的 Docker 镜像,提升效率。 24 | 25 | ## 优势 26 | 27 | - **简化流程**:从繁琐的多步骤操作简化为几次点击,大幅提升用户体验。 28 | - **实时查看**:直接跳转到 GitHub Actions 的实时打包情况,无需手动导航。 29 | - **历史记录**:支持查看和复用之前的构建配置,快速修复打包错误。 30 | 31 | ## 使用方法 32 | 33 | 按照以下步骤配置并使用本系统: 34 | 35 | 1. **克隆仓库** 36 | 将本项目克隆到您的 GitHub 账号下。 37 | 38 | 2. **获取 GitHub PAT** 39 | 访问 [GitHub PAT 设置页面](https://github.com/settings/tokens),生成一个 Personal Access Token (PAT),并确保勾选以下权限: 40 | - `repo`(全选) 41 | - `workflow` 42 | ![PAT Permissions](doc/pat_permissions.png) 43 | 44 | 3. **设置 Workflow 权限** 45 | 在仓库中进入 `Settings` > `Actions` > `General`,确保 Workflow 具有修改仓库文件的权限。 46 | ![Workflow Permissions](doc/workflow_permissions.png) 47 | 48 | 4. **设置 Actions Secrets** 49 | 在仓库的 `Settings` > `Secrets and variables` > `Actions` 中,添加以下两个 Secrets: 50 | - `DOCKER_PASSWORD`:您的 Docker Hub 账号密码。 51 | - `PAT`:您在第 2 步中生成的 GitHub PAT。 52 | ![Actions Secrets](doc/actions_secrets.png) 53 | 54 | 5. **配置 Cloudflare Worker** 55 | 将 `worker.js` 文件复制到您的 Cloudflare Worker 中,并根据需要填写相应信息(例如 API 密钥、域名等)。 56 | 57 | 6. **将仓库转为私有** 58 | 为保护您的配置和 Secrets,建议将仓库设置为私有。 59 | 60 | 7. **访问系统** 61 | 完成以上步骤后,您可以通过访问 `您的域名/您的路径密码` 来使用 Docker 构建系统。 62 | ![System Page](doc/page.png) 63 | 64 | ## 图片显示说明 65 | 66 | 所有图片已放置在仓库的 `doc` 目录下,并在 README.md 中使用相对路径引用(如 `doc/pat_permissions.png`)。请确保克隆仓库后,`doc` 目录中的图片文件完整,以保证显示正常。 67 | 68 | ## 开源协议 69 | 70 | 本项目采用 [MIT](LICENSE) 开源协议,详情请参阅仓库根目录下的 `LICENSE` 文件。 71 | 72 | -------------------------------------------------------------------------------- /config.env: -------------------------------------------------------------------------------- 1 | GITHUB_REPO=https://github.com/gua12345/grok2api_python.git 2 | DOCKER_USERNAME=gua12345 3 | DOCKER_REPO_NAME=grok2api_python 4 | DOCKER_TAG=test 5 | BRANCH=test 6 | ARCHITECTURES=linux/amd64,linux/arm64 7 | DOCKERFILE_AMD64=Dockerfile 8 | DOCKERFILE_ARM64=Dockerfile -------------------------------------------------------------------------------- /doc/actions_secrets.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/gua12345/github_action_docker_build_web/62d1e8e37f7d054d72522273ed468da00e7414a6/doc/actions_secrets.png -------------------------------------------------------------------------------- /doc/page.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/gua12345/github_action_docker_build_web/62d1e8e37f7d054d72522273ed468da00e7414a6/doc/page.png -------------------------------------------------------------------------------- /doc/pat_permissions.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/gua12345/github_action_docker_build_web/62d1e8e37f7d054d72522273ed468da00e7414a6/doc/pat_permissions.png -------------------------------------------------------------------------------- /doc/workflow_permissions.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/gua12345/github_action_docker_build_web/62d1e8e37f7d054d72522273ed468da00e7414a6/doc/workflow_permissions.png -------------------------------------------------------------------------------- /worker.js: -------------------------------------------------------------------------------- 1 | addEventListener('fetch', event => { 2 | event.respondWith(handleRequest(event.request)); 3 | }); 4 | 5 | const GITHUB_TOKEN = '你的github PAT'; 6 | const REPO_OWNER = '你的github用户名'; 7 | const REPO_NAME = 'github_action_docker_build_web'; 8 | const DOCKER_USERNAME = '你的DOCKER HUB用户名'; 9 | const PASSWORD = '你的路径密码'; // 设置您的密码 10 | 11 | const GITHUB_HEADERS = { 12 | 'Authorization': `Bearer ${GITHUB_TOKEN}`, 13 | 'Content-Type': 'application/json', 14 | 'User-Agent': 'Cloudflare-Worker-Docker-Builder' 15 | }; 16 | 17 | 18 | function mapStatus(status, conclusion) { 19 | if (status === 'completed') { 20 | if (conclusion === 'success') { 21 | return { text: '构建成功', class: 'status-success' }; 22 | } else { 23 | return { text: '构建失败', class: 'status-failure' }; 24 | } 25 | } else if (status === 'in_progress' || status === 'queued') { 26 | return { text: '在队列中', class: 'status-in-progress' }; 27 | } else { 28 | return { text: '未知状态', class: 'status-unknown' }; 29 | } 30 | } 31 | 32 | async function handleRequest(request) { 33 | const url = new URL(request.url); 34 | const path = url.pathname; 35 | 36 | if (path !== `/${PASSWORD}`) { 37 | return new Response('Unauthorized', { status: 401 }); 38 | } 39 | 40 | if (request.method === 'POST') { 41 | try { 42 | const contentType = request.headers.get('content-type') || ''; 43 | let formData; 44 | if (contentType.includes('multipart/form-data')) { 45 | formData = await request.formData(); 46 | } else { 47 | const text = await request.text(); 48 | formData = new URLSearchParams(text); 49 | } 50 | const getConfigValue = (key) => { 51 | const value = formData.get(key); 52 | if (!value) throw new Error(`Missing required field: ${key}`); 53 | return value; 54 | }; 55 | const architectures = formData.getAll('architectures'); 56 | if (architectures.length === 0) throw new Error('Must select at least one architecture'); 57 | 58 | const configContent = 59 | `GITHUB_REPO=${getConfigValue('githubRepo')}\n` + 60 | `BRANCH=${getConfigValue('branch')}\n` + 61 | `DOCKER_USERNAME=${getConfigValue('dockerUsername')}\n` + 62 | `DOCKER_REPO_NAME=${getConfigValue('dockerRepoName')}\n` + 63 | `DOCKER_TAG=${getConfigValue('dockerTag')}\n` + 64 | `ARCHITECTURES=${architectures.join(',')}\n` + 65 | `DOCKERFILE_AMD64=${getConfigValue('dockerfileAmd64')}\n` + 66 | `DOCKERFILE_ARM64=${getConfigValue('dockerfileArm64')}`; 67 | 68 | await updateConfigFile(configContent); 69 | 70 | const inputs = { config_content: configContent }; 71 | await triggerWorkflow(inputs); 72 | 73 | return new Response('Build triggered successfully!', { 74 | status: 200, 75 | headers: { 'Content-Type': 'text/plain' } 76 | }); 77 | } catch (error) { 78 | return new Response(`Error: ${error.message}`, { 79 | status: 500, 80 | headers: { 'Content-Type': 'text/plain' } 81 | }); 82 | } 83 | } 84 | 85 | const workflows = await getWorkflowHistory(); 86 | 87 | return new Response(` 88 | 89 | 90 | 91 | 92 | Docker构建系统 93 | 352 | 353 | 354 |

355 | 356 | GitHub 357 | 358 | Docker构建系统 359 |

360 |
361 | 390 |
391 |

构建历史

392 |
393 | ${await Promise.all(workflows.map(async run => { 394 | const config = await getConfigContent(run.id); 395 | const status = mapStatus(run.status, run.conclusion); 396 | const pullCommand = config ? `docker pull ${config.DOCKER_USERNAME}/${config.DOCKER_REPO_NAME}:${config.DOCKER_TAG}` : 'N/A'; 397 | return ` 398 |
399 | 400 |
拉取命令: 401 | ${pullCommand} 402 |
403 |
构建时间:${new Date(run.created_at).toLocaleString('zh-CN', { timeZone: 'Asia/Shanghai' })}
404 |
状态:${status.text}
405 | 406 |
407 | 配置信息:
408 |
${config ? JSON.stringify(config, null, 2) : 'No configuration details available'}
409 |
410 |
411 | `; 412 | })).then(html => html.join(''))} 413 |
414 |
415 |
416 | 486 | 487 | 488 | `, { 489 | headers: { 'Content-Type': 'text/html' } 490 | }); 491 | } 492 | 493 | /** 494 | * 更新 GitHub 仓库中的配置文件 495 | * @param {string} content - 配置文件内容 496 | */ 497 | async function updateConfigFile(content) { 498 | const filePath = 'config.env'; 499 | const branch = 'main'; 500 | const getExistingFile = async () => { 501 | try { 502 | const res = await fetch( 503 | `https://api.github.com/repos/${REPO_OWNER}/${REPO_NAME}/contents/${filePath}?ref=${branch}`, 504 | { headers: GITHUB_HEADERS } 505 | ); 506 | return res.ok ? await res.json() : null; 507 | } catch { 508 | return null; 509 | } 510 | }; 511 | const existingFile = await getExistingFile(); 512 | const sha = existingFile?.sha; 513 | const response = await fetch( 514 | `https://api.github.com/repos/${REPO_OWNER}/${REPO_NAME}/contents/${filePath}`, 515 | { 516 | method: 'PUT', 517 | headers: GITHUB_HEADERS, 518 | body: JSON.stringify({ 519 | message: 'Update config.env', 520 | content: btoa(content), 521 | branch: branch, 522 | sha: sha 523 | }) 524 | } 525 | ); 526 | if (!response.ok) throw new Error('Failed to update config file'); 527 | } 528 | 529 | /** 530 | * 触发 GitHub Actions 工作流 531 | * @param {Object} inputs - 工作流输入参数 532 | */ 533 | async function triggerWorkflow(inputs) { 534 | const response = await fetch( 535 | `https://api.github.com/repos/${REPO_OWNER}/${REPO_NAME}/actions/workflows/docker-build.yml/dispatches`, 536 | { 537 | method: 'POST', 538 | headers: GITHUB_HEADERS, 539 | body: JSON.stringify({ 540 | ref: 'main', 541 | inputs: inputs 542 | }) 543 | } 544 | ); 545 | if (!response.ok) throw new Error('Failed to trigger workflow'); 546 | } 547 | 548 | async function getWorkflowHistory() { 549 | try { 550 | const response = await fetch( 551 | `https://api.github.com/repos/${REPO_OWNER}/${REPO_NAME}/actions/runs`, 552 | { headers: GITHUB_HEADERS } 553 | ); 554 | if (!response.ok) throw new Error('Failed to fetch workflow history'); 555 | const data = await response.json(); 556 | 557 | const workflowRuns = await Promise.all( 558 | data.workflow_runs.map(async run => { 559 | const jobsResponse = await fetch( 560 | `https://api.github.com/repos/${REPO_OWNER}/${REPO_NAME}/actions/runs/${run.id}/jobs`, 561 | { headers: GITHUB_HEADERS } 562 | ); 563 | const jobsData = await jobsResponse.json(); 564 | const firstJobId = jobsData.jobs && jobsData.jobs.length > 0 ? jobsData.jobs[0].id : null; 565 | 566 | return { 567 | id: run.id, 568 | created_at: run.created_at, 569 | status: run.status, 570 | conclusion: run.conclusion, 571 | html_url: firstJobId ? `${run.html_url}/job/${firstJobId}` : run.html_url, // 修复 URL 572 | job_id: firstJobId // 添加 Job ID 573 | }; 574 | }) 575 | ); 576 | 577 | return workflowRuns; 578 | } catch (error) { 579 | console.error('Error fetching workflow history:', error); 580 | return []; 581 | } 582 | } 583 | 584 | async function getConfigContent(runId) { 585 | try { 586 | const response = await fetch( 587 | `https://api.github.com/repos/${REPO_OWNER}/${REPO_NAME}/contents/config_history/${runId}.env`, 588 | { headers: GITHUB_HEADERS } 589 | ); 590 | if (!response.ok) return null; 591 | const data = await response.json(); 592 | const content = atob(data.content); 593 | const config = {}; 594 | content.split('\n').forEach(line => { 595 | const [key, value] = line.split('='); 596 | if (key && value) config[key.trim()] = value.trim(); 597 | }); 598 | return config; 599 | } catch (error) { 600 | return null; 601 | } 602 | } 603 | --------------------------------------------------------------------------------