Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
44 changes: 18 additions & 26 deletions .github/workflows/docker-publish.yml
Original file line number Diff line number Diff line change
@@ -1,68 +1,60 @@
name: Docker Image CI
name: Docker Publish (Multi-Arch)

on:
push:
branches: [ "master", "main" ]
# 当发布新版本时触发
tags: [ 'v*.*.*' ]
branches: ["master", "main"]
tags: ["v*.*.*"]
pull_request:
branches: [ "master", "main" ]
branches: ["master", "main"]
workflow_dispatch:

env:
# GitHub Container Registry 的地址
REGISTRY: ghcr.io
# 镜像名称,默认为 GitHub 用户名/仓库名
IMAGE_NAME: ${{ github.repository }}
IMAGE_NAME: lifj25/codex-console

jobs:
build-and-push-image:
runs-on: ubuntu-latest
permissions:
contents: read
packages: write
# 如果需要签名生成的镜像,可以使用 id-token: write

steps:
- name: Checkout repository
uses: actions/checkout@v4

# 设置 Docker Buildx 用于构建多平台镜像 (可选)
- name: Set up QEMU
uses: docker/setup-qemu-action@v3

- name: Set up Docker Buildx
uses: docker/setup-buildx-action@v3

# 登录到 Docker 镜像仓库
# 如果只是在 PR 中测试构建,则跳过登录
- name: Log in to the Container registry
- name: Log in to Docker Hub
if: github.event_name != 'pull_request'
uses: docker/login-action@v3
with:
registry: ${{ env.REGISTRY }}
username: ${{ github.actor }}
password: ${{ secrets.GITHUB_TOKEN }}
username: ${{ secrets.DOCKERHUB_USERNAME }}
password: ${{ secrets.DOCKERHUB_TOKEN }}

# 提取 Docker 镜像的元数据(标签、注释等)
- name: Extract metadata (tags, labels) for Docker
id: meta
uses: docker/metadata-action@v5
with:
images: ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}
images: docker.io/${{ env.IMAGE_NAME }}
tags: |
type=schedule
type=ref,event=branch
type=ref,event=pr
type=semver,pattern={{version}}
type=semver,pattern={{major}}.{{minor}}
type=semver,pattern={{major}}
type=sha
type=raw,value=latest,enable={{is_default_branch}}
type=raw,value=latest,enable=${{ startsWith(github.ref, 'refs/tags/v') }}

# 构建并推送 Docker 镜像
- name: Build and push Docker image
- name: Build and push Docker image (linux/amd64,linux/arm64)
uses: docker/build-push-action@v5
with:
context: .
platforms: linux/amd64,linux/arm64
push: ${{ github.event_name != 'pull_request' }}
provenance: false
tags: ${{ steps.meta.outputs.tags }}
labels: ${{ steps.meta.outputs.labels }}
cache-from: type=gha
cache-to: type=gha,mode=max
cache-to: type=gha,mode=max
284 changes: 284 additions & 0 deletions .github/workflows/upstream-release-sync.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,284 @@
name: Upstream Release Sync Docker

on:
schedule:
- cron: "7 * * * *"
workflow_dispatch:
inputs:
upstream_tag:
description: "Optional upstream tag, for example v1.1.2"
required: false
type: string
force_republish:
description: "Force rebuild and push even if the version tag already exists"
required: false
default: false
type: boolean
publish_latest:
description: "Also publish the latest tag for this run"
required: false
default: true
type: boolean

env:
UPSTREAM_REPO: https://github.com/dou-jiang/codex-console.git
IMAGE_NAME: lifj25/codex-console
PLATFORMS: linux/amd64,linux/arm64

jobs:
sync-and-publish:
runs-on: ubuntu-latest
permissions:
contents: read
concurrency:
group: upstream-release-sync
cancel-in-progress: false
steps:
- name: Checkout repository
uses: actions/checkout@v4
with:
fetch-depth: 0

- name: Configure git identity
shell: bash
run: |
git config user.name "github-actions[bot]"
git config user.email "41898282+github-actions[bot]@users.noreply.github.com"

- name: Resolve upstream tag
id: resolve
shell: bash
run: |
set -Eeuo pipefail

git remote add upstream "${UPSTREAM_REPO}" || git remote set-url upstream "${UPSTREAM_REPO}"
git fetch --force --tags upstream

if [[ -n "${{ github.event.inputs.upstream_tag }}" ]]; then
UPSTREAM_TAG="${{ github.event.inputs.upstream_tag }}"
else
UPSTREAM_TAG="$(git ls-remote --tags --refs --sort='version:refname' upstream 'v*' | tail -n 1 | awk '{print $2}' | sed 's#refs/tags/##')"
fi

if [[ -z "${UPSTREAM_TAG}" ]]; then
echo "未找到上游 release tag" >&2
exit 1
fi

VERSION="${UPSTREAM_TAG#v}"

echo "upstream_tag=${UPSTREAM_TAG}" >> "$GITHUB_OUTPUT"
echo "version=${VERSION}" >> "$GITHUB_OUTPUT"

- name: Check image existence
id: image
shell: bash
run: |
set -Eeuo pipefail

VERSION="${{ steps.resolve.outputs.version }}"
FORCE_REPUBLISH="${{ github.event_name == 'workflow_dispatch' && github.event.inputs.force_republish || 'false' }}"
if docker manifest inspect "${IMAGE_NAME}:${VERSION}" >/dev/null 2>&1; then
echo "exists=true" >> "$GITHUB_OUTPUT"
if [[ "$FORCE_REPUBLISH" == 'true' ]]; then
echo "镜像 ${IMAGE_NAME}:${VERSION} 已存在,但本次启用了强制重发,将继续构建。" >> "$GITHUB_STEP_SUMMARY"
else
echo "镜像 ${IMAGE_NAME}:${VERSION} 已存在,跳过构建。" >> "$GITHUB_STEP_SUMMARY"
fi
else
echo "exists=false" >> "$GITHUB_OUTPUT"
fi

- name: Stop when image already exists
if: steps.image.outputs.exists == 'true' && !(github.event_name == 'workflow_dispatch' && github.event.inputs.force_republish == 'true')
shell: bash
run: exit 0

- name: Prepare source from upstream tag
id: prepare
if: steps.image.outputs.exists != 'true' || (github.event_name == 'workflow_dispatch' && github.event.inputs.force_republish == 'true')
shell: bash
run: |
set -Eeuo pipefail

LIGHTWEIGHT_DOCKERFILE="$RUNNER_TEMP/Dockerfile.lightweight"
cp "$GITHUB_WORKSPACE/Dockerfile" "$LIGHTWEIGHT_DOCKERFILE"

git checkout --detach "refs/tags/${{ steps.resolve.outputs.upstream_tag }}"
cp "$LIGHTWEIGHT_DOCKERFILE" "$GITHUB_WORKSPACE/Dockerfile"

python - <<'PY'
from pathlib import Path
import re

file_path = Path("src/web/routes/registration.py")
text = file_path.read_text()

if "request.registration_type" in text:
signature_pattern = re.compile(
r"(async def run_outlook_batch_registration\([\s\S]*?new_api_service_ids: List\[int\] = None,\n)(\):)",
re.M,
)
text, sig_count = signature_pattern.subn(
r"\1 registration_type: str = RoleTag.CHILD.value,\n\2",
text,
count=1,
)
if sig_count == 0 and "registration_type: str = RoleTag.CHILD.value" not in text:
raise SystemExit("Outlook batch signature patch target not found")

call_pattern = re.compile(
r"(auto_upload_tm=auto_upload_tm,\n\s*tm_service_ids=tm_service_ids,\n\s*auto_upload_new_api=auto_upload_new_api,\n\s*new_api_service_ids=new_api_service_ids,\n)(\s*\))",
re.M,
)
text, call_count = call_pattern.subn(
r"\1 registration_type=registration_type,\n\2",
text,
count=1,
)
if call_count == 0 and "registration_type=registration_type" not in text:
raise SystemExit("Outlook batch run_batch_registration patch target not found")

file_path.write_text(text)
PY

python -m py_compile src/web/routes/registration.py

echo "build_revision=$(git rev-parse HEAD)" >> "$GITHUB_OUTPUT"

- name: Set up QEMU
if: steps.image.outputs.exists != 'true' || (github.event_name == 'workflow_dispatch' && github.event.inputs.force_republish == 'true')
uses: docker/setup-qemu-action@v3

- name: Set up Docker Buildx
if: steps.image.outputs.exists != 'true' || (github.event_name == 'workflow_dispatch' && github.event.inputs.force_republish == 'true')
uses: docker/setup-buildx-action@v3

- name: Log in to Docker Hub
if: steps.image.outputs.exists != 'true' || (github.event_name == 'workflow_dispatch' && github.event.inputs.force_republish == 'true')
uses: docker/login-action@v3
with:
username: ${{ secrets.DOCKERHUB_USERNAME }}
password: ${{ secrets.DOCKERHUB_TOKEN }}

- name: Generate image tags
id: tags
if: steps.image.outputs.exists != 'true' || (github.event_name == 'workflow_dispatch' && github.event.inputs.force_republish == 'true')
shell: bash
run: |
set -Eeuo pipefail

TAGS="${IMAGE_NAME}:${{ steps.resolve.outputs.version }}"
PUBLISH_LATEST="${{ github.event_name == 'workflow_dispatch' && github.event.inputs.publish_latest || 'true' }}"
if [[ "$PUBLISH_LATEST" == 'true' ]]; then
TAGS="${TAGS}\n${IMAGE_NAME}:latest"
fi

{
echo "tags<<EOF"
printf '%b\n' "$TAGS"
echo "EOF"
} >> "$GITHUB_OUTPUT"

- name: Build and push Docker image
if: steps.image.outputs.exists != 'true' || (github.event_name == 'workflow_dispatch' && github.event.inputs.force_republish == 'true')
uses: docker/build-push-action@v5
with:
context: .
platforms: ${{ env.PLATFORMS }}
push: true
provenance: false
tags: ${{ steps.tags.outputs.tags }}
labels: |
org.opencontainers.image.source=${{ github.server_url }}/${{ github.repository }}
org.opencontainers.image.version=${{ steps.resolve.outputs.version }}
org.opencontainers.image.revision=${{ steps.prepare.outputs.build_revision }}

- name: Summarize result
if: always()
shell: bash
run: |
{
echo "## Upstream release sync"
echo "- Upstream tag: ${{ steps.resolve.outputs.upstream_tag }}"
echo "- Image: ${IMAGE_NAME}:${{ steps.resolve.outputs.version }}"
echo "- Platforms: ${PLATFORMS}"
if [[ "${{ steps.image.outputs.exists }}" == 'true' && "${{ github.event_name == 'workflow_dispatch' && github.event.inputs.force_republish || 'false' }}" != 'true' ]]; then
echo "- Result: skipped (already published)"
elif [[ "${{ job.status }}" == 'success' ]]; then
echo "- Result: published"
echo "- Build revision: ${{ steps.prepare.outputs.build_revision }}"
else
echo "- Result: failed"
fi
} >> "$GITHUB_STEP_SUMMARY"

- name: Send email notification
if: always() && (steps.image.outputs.exists != 'true' || (github.event_name == 'workflow_dispatch' && github.event.inputs.force_republish == 'true'))
env:
SMTP_HOST: smtp.exmail.qq.com
SMTP_PORT: '465'
SMTP_USERNAME: ${{ secrets.SMTP_USERNAME }}
SMTP_PASSWORD: ${{ secrets.SMTP_PASSWORD }}
EMAIL_FROM: ${{ secrets.SMTP_FROM }}
EMAIL_TO: [email protected]
UPSTREAM_TAG: ${{ steps.resolve.outputs.upstream_tag }}
IMAGE_NAME: ${{ env.IMAGE_NAME }}
VERSION: ${{ steps.resolve.outputs.version }}
BUILD_REVISION: ${{ steps.prepare.outputs.build_revision }}
JOB_STATUS: ${{ job.status }}
RUN_URL: ${{ github.server_url }}/${{ github.repository }}/actions/runs/${{ github.run_id }}
shell: bash
run: |
if [[ -z "${SMTP_USERNAME}" || -z "${SMTP_PASSWORD}" ]]; then
echo "SMTP 未配置,跳过邮件通知。" >> "$GITHUB_STEP_SUMMARY"
exit 0
fi

python - <<'PY'
import os
import smtplib
from email.message import EmailMessage

status = os.environ.get("JOB_STATUS", "unknown")
upstream_tag = os.environ.get("UPSTREAM_TAG", "unknown")
image_name = os.environ.get("IMAGE_NAME", "unknown")
version = os.environ.get("VERSION", "unknown")
build_revision = os.environ.get("BUILD_REVISION", "")
run_url = os.environ.get("RUN_URL", "")
email_from = os.environ.get("EMAIL_FROM") or os.environ.get("SMTP_USERNAME")

if status == "success":
subject = f"[codex-console] Docker 发布成功 {upstream_tag}"
body = "\n".join([
"自动同步并发布已完成。",
f"上游版本: {upstream_tag}",
f"镜像: {image_name}:{version}",
f"latest: {image_name}:latest (if enabled for this run)",
f"构建提交: {build_revision}",
f"运行详情: {run_url}",
])
else:
subject = f"[codex-console] Docker 发布失败 {upstream_tag}"
body = "\n".join([
"自动同步流程执行失败,请查看 GitHub Actions 日志。",
f"上游版本: {upstream_tag}",
f"目标镜像: {image_name}:{version}",
f"运行详情: {run_url}",
])

msg = EmailMessage()
msg["Subject"] = subject
msg["From"] = email_from
msg["To"] = os.environ["EMAIL_TO"]
msg.set_content(body)

host = os.environ["SMTP_HOST"]
port = int(os.environ.get("SMTP_PORT", "465"))
username = os.environ["SMTP_USERNAME"]
password = os.environ["SMTP_PASSWORD"]

with smtplib.SMTP_SSL(host, port) as server:
server.login(username, password)
server.send_message(msg)
PY
2 changes: 1 addition & 1 deletion Dockerfile
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ WORKDIR /app
# 设置环境变量
ENV PYTHONDONTWRITEBYTECODE=1 \
PYTHONUNBUFFERED=1 \
# WebUI 默认配置
TZ=Asia/Shanghai \
WEBUI_HOST=0.0.0.0 \
WEBUI_PORT=1455 \
LOG_LEVEL=info \
Expand Down
Loading