Compare commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
3054338cf7 | ||
|
|
c3a6e6dcb6 | ||
|
|
207588ae36 | ||
|
|
df8a9ead9c | ||
|
|
799b1c319c | ||
|
|
d6654b4d6d | ||
|
|
a83c52ec2c | ||
|
|
ac8a781656 | ||
|
|
2dcf9f7da4 | ||
|
|
50efc4bbfa | ||
|
|
610c2b3e74 | ||
|
|
778297543a | ||
|
|
550e226a2b | ||
|
|
6552f8be47 | ||
|
|
1259495cbb | ||
|
|
d4fa498a4a | ||
|
|
8445c6a363 | ||
|
|
ebd0f94053 | ||
|
|
40fa747a94 | ||
|
|
e5db752c55 | ||
|
|
0579c2dc97 | ||
|
|
beab4e5696 | ||
|
|
173526be79 | ||
|
|
61e512c2ae | ||
|
|
95402f4830 | ||
|
|
124b953d38 | ||
|
|
2b5eaf27c9 | ||
|
|
a4d09c9063 | ||
|
|
ac05621817 | ||
|
|
34480c16e2 | ||
|
|
9d4c4a1f73 | ||
|
|
1a52ee08a2 | ||
|
|
99b3560b88 | ||
|
|
db75d0ebc5 | ||
|
|
1b5611cf3e | ||
|
|
44e92ab89a | ||
|
|
eaedcd3bb2 | ||
|
|
0e89b21c41 | ||
|
|
39820e656a | ||
|
|
4829434408 | ||
|
|
9c572b64d1 |
@@ -0,0 +1,278 @@
|
|||||||
|
#!/usr/bin/env python3
|
||||||
|
|
||||||
|
import argparse
|
||||||
|
import unicodedata
|
||||||
|
from dataclasses import dataclass
|
||||||
|
from pathlib import Path
|
||||||
|
from typing import List
|
||||||
|
|
||||||
|
|
||||||
|
LABEL_BG = "#555555"
|
||||||
|
DEFAULT_TOTAL_COLOR = "#1f6feb"
|
||||||
|
DEFAULT_FILES_COLOR = "#2da44e"
|
||||||
|
DEFAULT_LANGUAGE_COUNT_COLOR = "#8250df"
|
||||||
|
TEXT_COLOR = "#ffffff"
|
||||||
|
LIGHT_TEXT_COLOR = "#24292f"
|
||||||
|
LIGHT_BADGE_BG = "#f6f8fa"
|
||||||
|
LIGHT_BADGE_BORDER = "#d0d7de"
|
||||||
|
|
||||||
|
|
||||||
|
@dataclass
|
||||||
|
class LanguageStat:
|
||||||
|
lang_id: str
|
||||||
|
display_name: str
|
||||||
|
code_lines: int
|
||||||
|
formatted_lines: str
|
||||||
|
file_count: int
|
||||||
|
color: str
|
||||||
|
|
||||||
|
|
||||||
|
def parse_args() -> argparse.Namespace:
|
||||||
|
parser = argparse.ArgumentParser()
|
||||||
|
parser.add_argument("--output-dir", required=True)
|
||||||
|
parser.add_argument("--readme-path", required=True)
|
||||||
|
parser.add_argument("--repo", required=True)
|
||||||
|
parser.add_argument("--generated-by", required=True)
|
||||||
|
parser.add_argument("--updated-at", required=True)
|
||||||
|
parser.add_argument("--total-color", default=DEFAULT_TOTAL_COLOR)
|
||||||
|
parser.add_argument("--files-color", default=DEFAULT_FILES_COLOR)
|
||||||
|
parser.add_argument("--language-count-color", default=DEFAULT_LANGUAGE_COUNT_COLOR)
|
||||||
|
parser.add_argument("--total-code", required=True, type=int)
|
||||||
|
parser.add_argument("--formatted-total-code", required=True)
|
||||||
|
parser.add_argument("--total-files", required=True, type=int)
|
||||||
|
parser.add_argument("--formatted-total-files", required=True)
|
||||||
|
parser.add_argument("--language-count", required=True, type=int)
|
||||||
|
parser.add_argument("--exclude-dirs", required=True)
|
||||||
|
parser.add_argument("--min-lines-threshold", required=True, type=int)
|
||||||
|
parser.add_argument("--lang-summary", required=True)
|
||||||
|
return parser.parse_args()
|
||||||
|
|
||||||
|
|
||||||
|
def escape_xml(value: str) -> str:
|
||||||
|
return (
|
||||||
|
value.replace("&", "&")
|
||||||
|
.replace("<", "<")
|
||||||
|
.replace(">", ">")
|
||||||
|
.replace('"', """)
|
||||||
|
.replace("'", "'")
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
def display_units(value: str) -> int:
|
||||||
|
units = 0
|
||||||
|
for char in value:
|
||||||
|
if unicodedata.east_asian_width(char) in {"F", "W"}:
|
||||||
|
units += 2
|
||||||
|
else:
|
||||||
|
units += 1
|
||||||
|
return units
|
||||||
|
|
||||||
|
|
||||||
|
def text_width(value: str) -> int:
|
||||||
|
return display_units(value) * 7 + 20
|
||||||
|
|
||||||
|
|
||||||
|
def normalize_color(value: str, fallback: str) -> str:
|
||||||
|
if not value:
|
||||||
|
return fallback
|
||||||
|
return value if value.startswith("#") else f"#{value}"
|
||||||
|
|
||||||
|
|
||||||
|
def parse_language_summary(path: Path) -> List[LanguageStat]:
|
||||||
|
stats: List[LanguageStat] = []
|
||||||
|
|
||||||
|
if not path.exists():
|
||||||
|
return stats
|
||||||
|
|
||||||
|
for raw_line in path.read_text(encoding="utf-8").splitlines():
|
||||||
|
line = raw_line.strip()
|
||||||
|
if not line:
|
||||||
|
continue
|
||||||
|
|
||||||
|
parts = line.split("|")
|
||||||
|
if len(parts) < 6:
|
||||||
|
continue
|
||||||
|
|
||||||
|
while len(parts) < 7:
|
||||||
|
parts.append("")
|
||||||
|
|
||||||
|
lang_id, display_name, code_lines, formatted_lines, file_count, color, _icon = parts[:7]
|
||||||
|
stats.append(
|
||||||
|
LanguageStat(
|
||||||
|
lang_id=lang_id,
|
||||||
|
display_name=display_name,
|
||||||
|
code_lines=int(code_lines),
|
||||||
|
formatted_lines=formatted_lines,
|
||||||
|
file_count=int(file_count),
|
||||||
|
color=normalize_color(color, DEFAULT_TOTAL_COLOR),
|
||||||
|
)
|
||||||
|
)
|
||||||
|
|
||||||
|
return stats
|
||||||
|
|
||||||
|
|
||||||
|
def split_badge_svg(label: str, message: str, color: str) -> str:
|
||||||
|
height = 20
|
||||||
|
left_width = max(46, text_width(label))
|
||||||
|
right_width = max(46, text_width(message))
|
||||||
|
total_width = left_width + right_width
|
||||||
|
left_text_x = left_width / 2
|
||||||
|
right_text_x = left_width + right_width / 2
|
||||||
|
|
||||||
|
return f"""<svg xmlns="http://www.w3.org/2000/svg" width="{total_width}" height="{height}" role="img" aria-label="{escape_xml(label)}: {escape_xml(message)}">
|
||||||
|
<title>{escape_xml(label)}: {escape_xml(message)}</title>
|
||||||
|
<rect width="{left_width}" height="{height}" fill="{LABEL_BG}" />
|
||||||
|
<rect x="{left_width}" width="{right_width}" height="{height}" fill="{escape_xml(color)}" />
|
||||||
|
<rect width="{total_width}" height="{height}" rx="3" fill="transparent" />
|
||||||
|
<g fill="{TEXT_COLOR}" text-anchor="middle" font-family="DejaVu Sans,Verdana,Geneva,sans-serif" font-size="11">
|
||||||
|
<text x="{left_text_x:.1f}" y="14">{escape_xml(label)}</text>
|
||||||
|
<text x="{right_text_x:.1f}" y="14">{escape_xml(message)}</text>
|
||||||
|
</g>
|
||||||
|
</svg>
|
||||||
|
"""
|
||||||
|
|
||||||
|
|
||||||
|
def flat_badge_svg(label: str, message: str, color: str) -> str:
|
||||||
|
height = 20
|
||||||
|
gap = 10
|
||||||
|
label_width = max(34, text_width(label) - 4)
|
||||||
|
message_width = max(38, text_width(message) - 4)
|
||||||
|
total_width = label_width + message_width + gap + 20
|
||||||
|
label_x = 10
|
||||||
|
message_x = label_x + label_width + gap
|
||||||
|
label_text_x = label_x + label_width / 2
|
||||||
|
message_text_x = message_x + message_width / 2
|
||||||
|
|
||||||
|
return f"""<svg xmlns="http://www.w3.org/2000/svg" width="{total_width}" height="{height}" role="img" aria-label="{escape_xml(label)}: {escape_xml(message)}">
|
||||||
|
<title>{escape_xml(label)}: {escape_xml(message)}</title>
|
||||||
|
<rect width="{total_width}" height="{height}" rx="10" fill="{LIGHT_BADGE_BG}" stroke="{LIGHT_BADGE_BORDER}" />
|
||||||
|
<rect x="{message_x}" y="3" width="{message_width}" height="14" rx="7" fill="{escape_xml(color)}" />
|
||||||
|
<g text-anchor="middle" font-family="DejaVu Sans,Verdana,Geneva,sans-serif" font-size="11">
|
||||||
|
<text x="{label_text_x:.1f}" y="14" fill="{LIGHT_TEXT_COLOR}">{escape_xml(label)}</text>
|
||||||
|
<text x="{message_text_x:.1f}" y="14" fill="{TEXT_COLOR}">{escape_xml(message)}</text>
|
||||||
|
</g>
|
||||||
|
</svg>
|
||||||
|
"""
|
||||||
|
|
||||||
|
|
||||||
|
def write_badge(path: Path, label: str, message: str, color: str, variant: str = "split") -> None:
|
||||||
|
if variant == "flat":
|
||||||
|
svg = flat_badge_svg(label, message, color)
|
||||||
|
else:
|
||||||
|
svg = split_badge_svg(label, message, color)
|
||||||
|
path.write_text(svg, encoding="utf-8")
|
||||||
|
|
||||||
|
|
||||||
|
def render_readme(args: argparse.Namespace, languages: List[LanguageStat], badge_dir_name: str) -> str:
|
||||||
|
language_rows = []
|
||||||
|
|
||||||
|
for stat in languages:
|
||||||
|
share = (stat.code_lines * 100 / args.total_code) if args.total_code else 0.0
|
||||||
|
language_rows.append(
|
||||||
|
f"| **{stat.display_name}** | {stat.formatted_lines} | {stat.file_count} | {share:.1f}% | "
|
||||||
|
f" |"
|
||||||
|
)
|
||||||
|
|
||||||
|
if language_rows:
|
||||||
|
language_table = "\n".join(language_rows)
|
||||||
|
else:
|
||||||
|
language_table = "| _未找到符合条件的语言_ | - | - | - | - |"
|
||||||
|
|
||||||
|
return f"""# 📊 代码统计详细报告
|
||||||
|
|
||||||
|
> 此分支由 {args.generated_by} 自动生成和维护
|
||||||
|
>
|
||||||
|
> ⚠️ **请勿手动修改此分支的内容!**
|
||||||
|
>
|
||||||
|
> 🤖 由 {args.generated_by} 自动生成
|
||||||
|
>
|
||||||
|
> 📅 更新时间: {args.updated_at}
|
||||||
|
|
||||||
|
## 📈 总体统计
|
||||||
|
|
||||||
|
| 统计项 | 数值 | 徽章 |
|
||||||
|
|--------|------|------|
|
||||||
|
| 💻 总代码行数 | **{args.formatted_total_code}** 行 |  |
|
||||||
|
| 📁 总文件数 | **{args.formatted_total_files}** 个 |  |
|
||||||
|
| 🌐 语言种类 | **{args.language_count}** 种 |  |
|
||||||
|
|
||||||
|
## 🌈 语言分布
|
||||||
|
|
||||||
|
| 语言 | 代码行数 | 文件数 | 占比 | 徽章 |
|
||||||
|
|------|----------|--------|------|------|
|
||||||
|
{language_table}
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## ⚙️ 配置说明
|
||||||
|
|
||||||
|
### Token 配置
|
||||||
|
|
||||||
|
- 当前使用: **WORKFLOW**
|
||||||
|
- 需要在 Settings -> Actions -> Secrets 中配置 `WORKFLOW`
|
||||||
|
|
||||||
|
### 排除规则
|
||||||
|
|
||||||
|
当前排除的目录:
|
||||||
|
```
|
||||||
|
{args.exclude_dirs}
|
||||||
|
```
|
||||||
|
|
||||||
|
### 阈值设置
|
||||||
|
|
||||||
|
- 最小代码行数阈值: **{args.min_lines_threshold}** 行
|
||||||
|
- 低于此阈值的语言不会生成徽章
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
*📊 统计仓库:`{args.repo}`*
|
||||||
|
"""
|
||||||
|
|
||||||
|
|
||||||
|
def main() -> None:
|
||||||
|
args = parse_args()
|
||||||
|
output_dir = Path(args.output_dir)
|
||||||
|
readme_path = Path(args.readme_path)
|
||||||
|
|
||||||
|
output_dir.mkdir(parents=True, exist_ok=True)
|
||||||
|
readme_path.parent.mkdir(parents=True, exist_ok=True)
|
||||||
|
|
||||||
|
languages = parse_language_summary(Path(args.lang_summary))
|
||||||
|
badge_dir_name = output_dir.name
|
||||||
|
|
||||||
|
write_badge(
|
||||||
|
output_dir / "total-lines.svg",
|
||||||
|
"代码",
|
||||||
|
f"{args.formatted_total_code} 行",
|
||||||
|
normalize_color(args.total_color, DEFAULT_TOTAL_COLOR),
|
||||||
|
variant="flat",
|
||||||
|
)
|
||||||
|
write_badge(
|
||||||
|
output_dir / "total-files.svg",
|
||||||
|
"文件",
|
||||||
|
f"{args.formatted_total_files} 个",
|
||||||
|
normalize_color(args.files_color, DEFAULT_FILES_COLOR),
|
||||||
|
variant="flat",
|
||||||
|
)
|
||||||
|
write_badge(
|
||||||
|
output_dir / "language-count.svg",
|
||||||
|
"语言",
|
||||||
|
f"{args.language_count} 种",
|
||||||
|
normalize_color(args.language_count_color, DEFAULT_LANGUAGE_COUNT_COLOR),
|
||||||
|
variant="flat",
|
||||||
|
)
|
||||||
|
|
||||||
|
for stat in languages:
|
||||||
|
write_badge(
|
||||||
|
output_dir / f"{stat.lang_id}-lines.svg",
|
||||||
|
stat.display_name,
|
||||||
|
f"{stat.formatted_lines} 行",
|
||||||
|
stat.color,
|
||||||
|
variant="split",
|
||||||
|
)
|
||||||
|
|
||||||
|
readme_path.write_text(render_readme(args, languages, badge_dir_name), encoding="utf-8")
|
||||||
|
|
||||||
|
|
||||||
|
if __name__ == "__main__":
|
||||||
|
main()
|
||||||
@@ -1,9 +1,10 @@
|
|||||||
name: 📦 自动发布
|
name: 📦 自动发布
|
||||||
|
|
||||||
on:
|
on:
|
||||||
push:
|
workflow_run:
|
||||||
tags:
|
workflows: ["🧰 准备环境"]
|
||||||
- "[0-9]*"
|
types:
|
||||||
|
- completed
|
||||||
|
|
||||||
concurrency:
|
concurrency:
|
||||||
group: release-${{ github.repository }}
|
group: release-${{ github.repository }}
|
||||||
@@ -16,7 +17,7 @@ env:
|
|||||||
# ===== Token 配置 =====
|
# ===== Token 配置 =====
|
||||||
# Gitea/GitHub 访问令牌
|
# Gitea/GitHub 访问令牌
|
||||||
# 用途:
|
# 用途:
|
||||||
# 1. Git 操作:克隆仓库、推送提交
|
# 1. Git 操作:推送提交(仓库读取由 Prepare 完成)
|
||||||
# 2. API 调用:创建 Release、上传附件
|
# 2. API 调用:创建 Release、上传附件
|
||||||
# 权限要求:
|
# 权限要求:
|
||||||
# - repo (仓库完整访问权限)
|
# - repo (仓库完整访问权限)
|
||||||
@@ -26,22 +27,22 @@ env:
|
|||||||
#
|
#
|
||||||
# 💡 提示:同一个 token 可用于所有操作
|
# 💡 提示:同一个 token 可用于所有操作
|
||||||
# 如需分离权限,可配置两个不同的 token:
|
# 如需分离权限,可配置两个不同的 token:
|
||||||
# - GIT_TOKEN: 用于 git clone/push
|
# - GIT_TOKEN: 用于 git push
|
||||||
# - API_TOKEN: 用于 API 调用
|
# - API_TOKEN: 用于 API 调用
|
||||||
ACCESS_TOKEN: ${{ secrets.WORKFLOW }}
|
ACCESS_TOKEN: ${{ secrets.WORKFLOW }}
|
||||||
|
|
||||||
# ===== 工作区配置 =====
|
# ===== 工作区配置 =====
|
||||||
# 完整克隆的工作目录 - 自建的runner可以复用工作区
|
WORKSPACE_ROOT: "/data/workspace"
|
||||||
WORKSPACE_DIR: "/home/workspace"
|
WORKSPACE_SLOT: "release"
|
||||||
|
|
||||||
# ===== 分支配置 =====
|
# ===== 分支配置 =====
|
||||||
# 主分支名称(用于推送 CHANGELOG 更新)
|
# 主分支名称(用于推送 CHANGELOG 更新)
|
||||||
# 如果留空,会自动检测(main 或 master)
|
# 必须明确配置主分支(例如 main 或 master)
|
||||||
MAIN_BRANCH: "main"
|
MAIN_BRANCH: "main"
|
||||||
|
|
||||||
# ===== 服务器配置 =====
|
# ===== 服务器配置 =====
|
||||||
# Gitea 服务器地址(用于生成头像链接和 API 调用)
|
# Gitea 服务器地址(用于生成头像链接和 API 调用)
|
||||||
GITEA_SERVER: "https://git.mytsl.cn"
|
GITEA_SERVER: "${{ github.server_url }}"
|
||||||
|
|
||||||
# ===== CHANGELOG 配置 =====
|
# ===== CHANGELOG 配置 =====
|
||||||
# CHANGELOG 变更列表标题
|
# CHANGELOG 变更列表标题
|
||||||
@@ -75,8 +76,8 @@ env:
|
|||||||
|
|
||||||
# ===== Git 提交配置 =====
|
# ===== Git 提交配置 =====
|
||||||
# Git 用户配置
|
# Git 用户配置
|
||||||
GIT_USER_NAME: "ci-bot"
|
GIT_USER_NAME: "ci[bot]"
|
||||||
GIT_USER_EMAIL: "ci-bot@tinysoft.com.cn"
|
GIT_USER_EMAIL: "ci[bot]@tinysoft.com.cn"
|
||||||
# Git 提交消息模板
|
# Git 提交消息模板
|
||||||
# 可用变量: {version} - 将被替换为实际的版本号
|
# 可用变量: {version} - 将被替换为实际的版本号
|
||||||
# [skip ci] 标记防止触发无限循环
|
# [skip ci] 标记防止触发无限循环
|
||||||
@@ -127,10 +128,100 @@ env:
|
|||||||
# 4. CHANGELOG内容是叠加而非覆盖
|
# 4. CHANGELOG内容是叠加而非覆盖
|
||||||
|
|
||||||
jobs:
|
jobs:
|
||||||
|
route-release:
|
||||||
|
if: ${{ github.event.workflow_run.conclusion == 'success' && contains(github.event.workflow_run.path, '@refs/tags/') }}
|
||||||
|
runs-on: standard-ubuntu-22
|
||||||
|
permissions:
|
||||||
|
contents: read
|
||||||
|
outputs:
|
||||||
|
should_release: ${{ steps.route.outputs.should_release }}
|
||||||
|
version: ${{ steps.route.outputs.version }}
|
||||||
|
tag_ref: ${{ steps.route.outputs.tag_ref }}
|
||||||
|
source_sha: ${{ steps.route.outputs.source_sha }}
|
||||||
|
env:
|
||||||
|
UPSTREAM_PATH: ${{ github.event.workflow_run.path }}
|
||||||
|
HEAD_SHA: ${{ github.event.workflow_run.head_sha }}
|
||||||
|
|
||||||
|
steps:
|
||||||
|
- name: 🧭 恢复并验证 Tag
|
||||||
|
id: route
|
||||||
|
shell: bash
|
||||||
|
run: |
|
||||||
|
set -euo pipefail
|
||||||
|
|
||||||
|
if ! command -v git >/dev/null 2>&1; then
|
||||||
|
echo "Prepare 成功后仍缺少发布路由所需命令: git" >&2
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
echo "should_release=false" >> "$GITHUB_OUTPUT"
|
||||||
|
|
||||||
|
case "$HEAD_SHA" in
|
||||||
|
''|*[!0-9a-fA-F]*)
|
||||||
|
echo "无效的上游提交 SHA: $HEAD_SHA" >&2
|
||||||
|
exit 1
|
||||||
|
;;
|
||||||
|
esac
|
||||||
|
|
||||||
|
FULL_REF="${UPSTREAM_PATH##*@}"
|
||||||
|
case "$FULL_REF" in
|
||||||
|
refs/tags/*) ;;
|
||||||
|
*)
|
||||||
|
echo "workflow_run.path 未包含有效 Tag ref: $UPSTREAM_PATH" >&2
|
||||||
|
exit 1
|
||||||
|
;;
|
||||||
|
esac
|
||||||
|
|
||||||
|
VERSION="${FULL_REF#refs/tags/}"
|
||||||
|
case "$VERSION" in
|
||||||
|
[0-9]*) ;;
|
||||||
|
*)
|
||||||
|
echo "跳过非版本 Tag: $VERSION"
|
||||||
|
exit 0
|
||||||
|
;;
|
||||||
|
esac
|
||||||
|
|
||||||
|
if ! git check-ref-format "$FULL_REF"; then
|
||||||
|
echo "数字开头的 Tag ref 格式无效: $FULL_REF" >&2
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
REPO_NAME="${{ github.event.repository.name }}"
|
||||||
|
REPOSITORY_DIR="${WORKSPACE_ROOT}/${REPO_NAME}/repository.git"
|
||||||
|
if [ ! -d "$REPOSITORY_DIR" ] || \
|
||||||
|
[ "$(git --git-dir="$REPOSITORY_DIR" rev-parse --is-bare-repository 2>/dev/null)" != "true" ]; then
|
||||||
|
echo "Prepare 未创建有效共享 bare 仓库: $REPOSITORY_DIR" >&2
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
if ! git --git-dir="$REPOSITORY_DIR" cat-file -e "${HEAD_SHA}^{commit}"; then
|
||||||
|
echo "共享仓库中不存在上游提交: $HEAD_SHA" >&2
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
if ! tag_sha=$(git --git-dir="$REPOSITORY_DIR" rev-parse "${FULL_REF}^{commit}" 2>/dev/null); then
|
||||||
|
echo "共享仓库中不存在可解析的 Tag: $FULL_REF" >&2
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
if [ "$tag_sha" != "$HEAD_SHA" ]; then
|
||||||
|
echo "Tag/SHA 不匹配: $FULL_REF -> $tag_sha,上游为 $HEAD_SHA" >&2
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
echo "should_release=true" >> "$GITHUB_OUTPUT"
|
||||||
|
echo "version=$VERSION" >> "$GITHUB_OUTPUT"
|
||||||
|
echo "tag_ref=$FULL_REF" >> "$GITHUB_OUTPUT"
|
||||||
|
echo "source_sha=$HEAD_SHA" >> "$GITHUB_OUTPUT"
|
||||||
|
echo "发布路由通过: $VERSION @ $HEAD_SHA"
|
||||||
|
|
||||||
changelog-and-release:
|
changelog-and-release:
|
||||||
runs-on: ubuntu-22.04
|
needs: route-release
|
||||||
|
if: ${{ needs.route-release.outputs.should_release == 'true' }}
|
||||||
|
runs-on: standard-ubuntu-22
|
||||||
permissions:
|
permissions:
|
||||||
contents: write
|
contents: write
|
||||||
|
env:
|
||||||
|
VERSION: ${{ needs.route-release.outputs.version }}
|
||||||
|
TAG_REF: ${{ needs.route-release.outputs.tag_ref }}
|
||||||
|
SOURCE_SHA: ${{ needs.route-release.outputs.source_sha }}
|
||||||
|
|
||||||
steps:
|
steps:
|
||||||
- name: ⚙️ 加载配置
|
- name: ⚙️ 加载配置
|
||||||
@@ -141,7 +232,7 @@ jobs:
|
|||||||
echo "======================================"
|
echo "======================================"
|
||||||
|
|
||||||
# 从环境变量读取配置
|
# 从环境变量读取配置
|
||||||
VERSION="${{ github.ref_name }}"
|
VERSION="${{ env.VERSION }}"
|
||||||
|
|
||||||
KEYWORDS="${{ env.PRERELEASE_KEYWORDS }}"
|
KEYWORDS="${{ env.PRERELEASE_KEYWORDS }}"
|
||||||
STRIP_REGEX="-($KEYWORDS)[0-9]*$" # 用于 sed 删除后缀
|
STRIP_REGEX="-($KEYWORDS)[0-9]*$" # 用于 sed 删除后缀
|
||||||
@@ -228,137 +319,93 @@ jobs:
|
|||||||
echo "======================================"
|
echo "======================================"
|
||||||
echo ""
|
echo ""
|
||||||
|
|
||||||
- name: 📥 克隆仓库
|
- name: 📥 对齐发布 Worktree
|
||||||
id: clone
|
id: prepare_worktree
|
||||||
|
shell: bash
|
||||||
run: |
|
run: |
|
||||||
echo "======================================"
|
set -euo pipefail
|
||||||
echo "🚀 开始准备仓库"
|
|
||||||
echo "======================================"
|
for command in git python3 curl flock realpath; do
|
||||||
|
if ! command -v "$command" >/dev/null 2>&1; then
|
||||||
|
echo "Prepare 成功后仍缺少发布所需命令: $command" >&2
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
done
|
||||||
|
|
||||||
REPO_NAME="${{ github.event.repository.name }}"
|
REPO_NAME="${{ github.event.repository.name }}"
|
||||||
REPO_DIR="${{ env.WORKSPACE_DIR }}/$REPO_NAME"
|
HEAD_SHA="${{ env.SOURCE_SHA }}"
|
||||||
|
|
||||||
echo "📁 仓库名称: $REPO_NAME"
|
|
||||||
echo "📍 目标目录: $REPO_DIR"
|
|
||||||
echo "🌐 服务器: ${GITHUB_SERVER_URL}"
|
|
||||||
echo ""
|
|
||||||
|
|
||||||
# 检查仓库状态
|
|
||||||
if [ -d "$REPO_DIR" ]; then
|
|
||||||
echo "📂 目录已存在,检查 Git 仓库状态..."
|
|
||||||
|
|
||||||
if [ -d "$REPO_DIR/.git" ]; then
|
|
||||||
# 目录存在且是有效的 Git 仓库
|
|
||||||
echo "✓ 发现有效的 Git 仓库,执行增量更新..."
|
|
||||||
cd "$REPO_DIR"
|
|
||||||
|
|
||||||
# 清理工作区
|
|
||||||
git clean -fdx
|
|
||||||
git reset --hard
|
|
||||||
|
|
||||||
# 获取最新代码和标签,同时清理远程已删除的 tag
|
|
||||||
echo "📥 拉取最新代码和标签..."
|
|
||||||
git fetch --all --tags --force --prune --prune-tags
|
|
||||||
echo "✓ 已同步远程状态(包括已删除的 tag)"
|
|
||||||
|
|
||||||
echo "✓ 仓库已更新"
|
|
||||||
else
|
|
||||||
# 目录存在但不是 Git 仓库(可能之前运行失败)
|
|
||||||
echo "⚠️ 目录存在但不是有效的 Git 仓库"
|
|
||||||
echo "🧹 清理损坏的目录..."
|
|
||||||
rm -rf "$REPO_DIR"
|
|
||||||
echo "✓ 已清理"
|
|
||||||
|
|
||||||
# 重新克隆
|
|
||||||
echo "📥 克隆仓库..."
|
|
||||||
mkdir -p "${{ env.WORKSPACE_DIR }}"
|
|
||||||
|
|
||||||
git clone \
|
|
||||||
https://oauth2:${{ env.ACCESS_TOKEN }}@${GITHUB_SERVER_URL#https://}/${{ github.repository }}.git \
|
|
||||||
"$REPO_DIR"
|
|
||||||
|
|
||||||
if [ $? -ne 0 ]; then
|
|
||||||
echo "❌ 克隆失败"
|
|
||||||
cat /tmp/git_clone.log
|
|
||||||
|
|
||||||
# 清理残留
|
|
||||||
if [ -d "$REPO_DIR" ]; then
|
|
||||||
rm -rf "$REPO_DIR"
|
|
||||||
fi
|
|
||||||
|
|
||||||
exit 1
|
|
||||||
fi
|
|
||||||
|
|
||||||
cd "$REPO_DIR"
|
|
||||||
echo "✓ 仓库已克隆"
|
|
||||||
fi
|
|
||||||
else
|
|
||||||
# 目录不存在,首次克隆
|
|
||||||
echo "📥 克隆仓库(首次)..."
|
|
||||||
mkdir -p "${{ env.WORKSPACE_DIR }}"
|
|
||||||
|
|
||||||
git clone \
|
|
||||||
https://oauth2:${{ env.ACCESS_TOKEN }}@${GITHUB_SERVER_URL#https://}/${{ github.repository }}.git \
|
|
||||||
"$REPO_DIR"
|
|
||||||
|
|
||||||
if [ $? -eq 0 ]; then
|
|
||||||
cd "$REPO_DIR"
|
|
||||||
echo "✓ 仓库已克隆"
|
|
||||||
else
|
|
||||||
echo "❌ 克隆失败"
|
|
||||||
exit 1
|
|
||||||
fi
|
|
||||||
fi
|
|
||||||
|
|
||||||
echo ""
|
|
||||||
echo "🔍 验证主分支配置..."
|
|
||||||
|
|
||||||
# 检查是否配置了主分支
|
|
||||||
if [ -z "${{ env.MAIN_BRANCH }}" ]; then
|
|
||||||
echo "❌ 错误: 未配置主分支"
|
|
||||||
echo ""
|
|
||||||
echo "请在 workflow 配置文件的 env 区域设置 MAIN_BRANCH:"
|
|
||||||
echo ""
|
|
||||||
echo "env:"
|
|
||||||
echo " MAIN_BRANCH: \"main\" # 或 \"master\""
|
|
||||||
echo ""
|
|
||||||
exit 1
|
|
||||||
fi
|
|
||||||
|
|
||||||
MAIN_BRANCH="${{ env.MAIN_BRANCH }}"
|
MAIN_BRANCH="${{ env.MAIN_BRANCH }}"
|
||||||
echo "✓ 使用配置的主分支: $MAIN_BRANCH"
|
REPOSITORY_DIR="${WORKSPACE_ROOT}/${REPO_NAME}/repository.git"
|
||||||
|
WORKTREE_DIR="${WORKSPACE_ROOT}/${REPO_NAME}/worktrees/release"
|
||||||
|
WORKTREE_LOCK="${WORKSPACE_ROOT}/${REPO_NAME}/worktree-admin.lock"
|
||||||
|
MAIN_REMOTE_REF="refs/remotes/origin/${MAIN_BRANCH}"
|
||||||
|
|
||||||
# 验证分支是否存在
|
case "$HEAD_SHA" in
|
||||||
if ! git show-ref --verify --quiet refs/remotes/origin/$MAIN_BRANCH; then
|
''|*[!0-9a-fA-F]*)
|
||||||
echo "❌ 错误: 配置的分支 '$MAIN_BRANCH' 不存在"
|
echo "无效的路由提交 SHA: $HEAD_SHA" >&2
|
||||||
echo ""
|
exit 1
|
||||||
echo "可用的远程分支:"
|
;;
|
||||||
git branch -r | grep -v HEAD
|
esac
|
||||||
echo ""
|
if [ -z "$MAIN_BRANCH" ]; then
|
||||||
echo "请在 workflow 配置文件中修改 MAIN_BRANCH 为正确的分支名"
|
echo "未配置 MAIN_BRANCH" >&2
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
if [ ! -d "$REPOSITORY_DIR" ] || \
|
||||||
|
[ "$(git --git-dir="$REPOSITORY_DIR" rev-parse --is-bare-repository 2>/dev/null)" != "true" ]; then
|
||||||
|
echo "Prepare 未创建有效共享 bare 仓库: $REPOSITORY_DIR" >&2
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
if ! git --git-dir="$REPOSITORY_DIR" cat-file -e "${HEAD_SHA}^{commit}"; then
|
||||||
|
echo "共享仓库中不存在发布提交: $HEAD_SHA" >&2
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
if ! git --git-dir="$REPOSITORY_DIR" show-ref --verify --quiet "$MAIN_REMOTE_REF"; then
|
||||||
|
echo "Prepare 未 fetch 配置的主分支: $MAIN_REMOTE_REF" >&2
|
||||||
exit 1
|
exit 1
|
||||||
fi
|
fi
|
||||||
|
|
||||||
echo "✓ 分支 '$MAIN_BRANCH' 已验证存在"
|
if [ ! -f "$WORKTREE_DIR/.git" ]; then
|
||||||
|
exec 9>"$WORKTREE_LOCK"
|
||||||
|
flock 9
|
||||||
|
|
||||||
# 切换到主分支
|
if [ ! -f "$WORKTREE_DIR/.git" ]; then
|
||||||
echo ""
|
if [ -e "$WORKTREE_DIR" ]; then
|
||||||
echo "🌿 切换到主分支: $MAIN_BRANCH"
|
echo "发布 Worktree 路径已存在但不是 linked worktree: $WORKTREE_DIR" >&2
|
||||||
git checkout $MAIN_BRANCH
|
exit 1
|
||||||
git pull origin $MAIN_BRANCH
|
fi
|
||||||
echo "✓ 已切换到主分支并更新到最新"
|
mkdir -p "$(dirname "$WORKTREE_DIR")"
|
||||||
|
git --git-dir="$REPOSITORY_DIR" worktree add --detach "$WORKTREE_DIR" "$HEAD_SHA"
|
||||||
|
fi
|
||||||
|
|
||||||
# 导出到环境变量供后续步骤使用
|
flock -u 9
|
||||||
echo "REPO_DIR=$REPO_DIR" >> $GITHUB_ENV
|
fi
|
||||||
echo "REPO_NAME=$REPO_NAME" >> $GITHUB_ENV
|
|
||||||
echo "MAIN_BRANCH=$MAIN_BRANCH" >> $GITHUB_ENV
|
|
||||||
|
|
||||||
echo ""
|
common_dir=$(git -C "$WORKTREE_DIR" rev-parse --path-format=absolute --git-common-dir)
|
||||||
echo "✅ 仓库准备完成"
|
if [ "$common_dir" != "$(realpath "$REPOSITORY_DIR")" ]; then
|
||||||
echo "======================================"
|
echo "发布 Worktree 不属于共享仓库: $WORKTREE_DIR" >&2
|
||||||
echo ""
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
- name: 🔍 检查 Bot 提交
|
git -C "$WORKTREE_DIR" checkout --detach --force "$HEAD_SHA"
|
||||||
|
git -C "$WORKTREE_DIR" reset --hard "$HEAD_SHA"
|
||||||
|
git -C "$WORKTREE_DIR" clean -ffdx
|
||||||
|
|
||||||
|
actual_sha=$(git -C "$WORKTREE_DIR" rev-parse HEAD)
|
||||||
|
if [ "$actual_sha" != "$HEAD_SHA" ]; then
|
||||||
|
echo "发布 Worktree 提交不匹配: 期望 $HEAD_SHA,实际 $actual_sha" >&2
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
git -C "$WORKTREE_DIR" config user.name "${{ env.GIT_USER_NAME }}"
|
||||||
|
git -C "$WORKTREE_DIR" config user.email "${{ env.GIT_USER_EMAIL }}"
|
||||||
|
|
||||||
|
echo "REPOSITORY_DIR=$REPOSITORY_DIR" >> "$GITHUB_ENV"
|
||||||
|
echo "REPO_DIR=$WORKTREE_DIR" >> "$GITHUB_ENV"
|
||||||
|
echo "REPO_NAME=$REPO_NAME" >> "$GITHUB_ENV"
|
||||||
|
echo "MAIN_REMOTE_REF=$MAIN_REMOTE_REF" >> "$GITHUB_ENV"
|
||||||
|
echo "发布 Worktree: $WORKTREE_DIR @ $actual_sha"
|
||||||
|
|
||||||
|
- name: 🤖 检查 Bot 提交
|
||||||
id: check_bot
|
id: check_bot
|
||||||
run: |
|
run: |
|
||||||
echo "======================================"
|
echo "======================================"
|
||||||
@@ -367,7 +414,7 @@ jobs:
|
|||||||
|
|
||||||
cd ${{ env.REPO_DIR }}
|
cd ${{ env.REPO_DIR }}
|
||||||
|
|
||||||
COMMIT_MSG=$(git log -1 --pretty=%B ${{ github.ref_name }})
|
COMMIT_MSG=$(git log -1 --pretty=%B "${{ env.SOURCE_SHA }}")
|
||||||
echo "📝 Tag commit message:"
|
echo "📝 Tag commit message:"
|
||||||
echo "$COMMIT_MSG"
|
echo "$COMMIT_MSG"
|
||||||
echo ""
|
echo ""
|
||||||
@@ -394,7 +441,7 @@ jobs:
|
|||||||
|
|
||||||
cd ${{ env.REPO_DIR }}
|
cd ${{ env.REPO_DIR }}
|
||||||
|
|
||||||
VERSION="${{ github.ref_name }}"
|
VERSION="${{ env.VERSION }}"
|
||||||
CHANGELOG_VERSION="${{ env.CHANGELOG_VERSION }}"
|
CHANGELOG_VERSION="${{ env.CHANGELOG_VERSION }}"
|
||||||
|
|
||||||
echo "🏷️ Tag: $VERSION"
|
echo "🏷️ Tag: $VERSION"
|
||||||
@@ -443,6 +490,38 @@ jobs:
|
|||||||
COMMIT_RANGE="${PREVIOUS_TAG}..${VERSION}"
|
COMMIT_RANGE="${PREVIOUS_TAG}..${VERSION}"
|
||||||
fi
|
fi
|
||||||
|
|
||||||
|
# 计算 Release Notes 的增量基准 tag,规则按当前发布是否为预发布(rc)区分:
|
||||||
|
# - 预发布(rc):基准 = 紧邻的上一个 tag(不跳过同版本 rc)
|
||||||
|
# 例如 0.0.5-rc2 的基准是 0.0.5-rc1,Release Notes 只展示 rc1→rc2 的新提交,
|
||||||
|
# 避免同一版本各 rc 之间大量重复。
|
||||||
|
# - 正式版:基准 = 上一个"不同 CHANGELOG 版本"的 tag(即上面的 PREVIOUS_TAG)
|
||||||
|
# 例如 0.0.5 正式版会跳过 0.0.5-rc1/rc2,基准落到 0.0.4-rc2,
|
||||||
|
# Release Notes 覆盖整个 0.0.5 开发周期的全量提交。
|
||||||
|
# 注意:CHANGELOG.md 始终是整个版本的累积(strip 模式),与此处无关。
|
||||||
|
if [[ "${RELEASE_IS_PRERELEASE}" == "true" ]]; then
|
||||||
|
NOTES_BASE_TAG=""
|
||||||
|
while IFS= read -r tag; do
|
||||||
|
[ -z "$tag" ] && continue
|
||||||
|
if [ "$tag" != "$VERSION" ]; then
|
||||||
|
NOTES_BASE_TAG="$tag"
|
||||||
|
break
|
||||||
|
fi
|
||||||
|
done <<< "$ALL_TAGS"
|
||||||
|
echo "🏷️ 预发布:Release Notes 基准 = 紧邻上一个 tag"
|
||||||
|
else
|
||||||
|
NOTES_BASE_TAG="$PREVIOUS_TAG"
|
||||||
|
echo "🏷️ 正式版:Release Notes 基准 = 上一个不同版本 tag(覆盖整个版本周期)"
|
||||||
|
fi
|
||||||
|
|
||||||
|
if [ -z "$NOTES_BASE_TAG" ]; then
|
||||||
|
echo "ℹ️ 未找到基准 tag,Release Notes 将包含全部提交"
|
||||||
|
RELEASE_NOTES_RANGE="HEAD"
|
||||||
|
else
|
||||||
|
echo "📍 Release Notes 增量基准: $NOTES_BASE_TAG"
|
||||||
|
RELEASE_NOTES_RANGE="${NOTES_BASE_TAG}..${VERSION}"
|
||||||
|
fi
|
||||||
|
echo "RELEASE_NOTES_RANGE=$RELEASE_NOTES_RANGE" >> $GITHUB_ENV
|
||||||
|
|
||||||
echo ""
|
echo ""
|
||||||
echo "🔍 扫描提交记录..."
|
echo "🔍 扫描提交记录..."
|
||||||
|
|
||||||
@@ -621,17 +700,20 @@ jobs:
|
|||||||
echo "👥 收集贡献者..."
|
echo "👥 收集贡献者..."
|
||||||
|
|
||||||
# 收集贡献者 (去除 bot)
|
# 收集贡献者 (去除 bot)
|
||||||
|
# 动态过滤配置的 bot 用户名
|
||||||
if [ "$COMMIT_RANGE" = "HEAD" ]; then
|
if [ "$COMMIT_RANGE" = "HEAD" ]; then
|
||||||
CONTRIBUTORS=$(git log --pretty=format:"%an" --no-merges | \
|
CONTRIBUTORS=$(git log --pretty=format:"%an" --no-merges | \
|
||||||
grep -v "github-actions\[bot\]" | \
|
grep -v "github-actions\[bot\]" | \
|
||||||
grep -v "dependabot\[bot\]" | \
|
grep -v "dependabot\[bot\]" | \
|
||||||
grep -v "renovate\[bot\]" | \
|
grep -v "renovate\[bot\]" | \
|
||||||
|
grep -vxF "${{ env.GIT_USER_NAME }}" | \
|
||||||
sort -u)
|
sort -u)
|
||||||
else
|
else
|
||||||
CONTRIBUTORS=$(git log $COMMIT_RANGE --pretty=format:"%an" --no-merges | \
|
CONTRIBUTORS=$(git log $COMMIT_RANGE --pretty=format:"%an" --no-merges | \
|
||||||
grep -v "github-actions\[bot\]" | \
|
grep -v "github-actions\[bot\]" | \
|
||||||
grep -v "dependabot\[bot\]" | \
|
grep -v "dependabot\[bot\]" | \
|
||||||
grep -v "renovate\[bot\]" | \
|
grep -v "renovate\[bot\]" | \
|
||||||
|
grep -vxF "${{ env.GIT_USER_NAME }}" | \
|
||||||
sort -u)
|
sort -u)
|
||||||
fi
|
fi
|
||||||
|
|
||||||
@@ -654,8 +736,12 @@ jobs:
|
|||||||
echo ""
|
echo ""
|
||||||
echo "💾 写入 CHANGELOG.md..."
|
echo "💾 写入 CHANGELOG.md..."
|
||||||
|
|
||||||
# 切换到主分支
|
# 从 Prepare 已 fetch 的远端引用准备主分支,再写入 CHANGELOG
|
||||||
git checkout ${{ env.MAIN_BRANCH }}
|
MAIN_BRANCH="${{ env.MAIN_BRANCH }}"
|
||||||
|
MAIN_REMOTE_REF="${{ env.MAIN_REMOTE_REF }}"
|
||||||
|
git -C "${{ env.REPO_DIR }}" checkout --force -B "$MAIN_BRANCH" "$MAIN_REMOTE_REF"
|
||||||
|
git -C "${{ env.REPO_DIR }}" reset --hard "$MAIN_REMOTE_REF"
|
||||||
|
git -C "${{ env.REPO_DIR }}" clean -ffdx
|
||||||
|
|
||||||
# ============================================
|
# ============================================
|
||||||
# 更新或创建 CHANGELOG.md
|
# 更新或创建 CHANGELOG.md
|
||||||
@@ -672,8 +758,8 @@ jobs:
|
|||||||
BEGIN { in_version=0 }
|
BEGIN { in_version=0 }
|
||||||
/^## :bookmark: / && $0 ~ version"$" { in_version=1; next }
|
/^## :bookmark: / && $0 ~ version"$" { in_version=1; next }
|
||||||
/^## :bookmark: / && in_version { in_version=0 }
|
/^## :bookmark: / && in_version { in_version=0 }
|
||||||
in_version && /\(\[[0-9a-f]{7}\]/ {
|
in_version && /\(\[[0-9a-f][0-9a-f][0-9a-f][0-9a-f][0-9a-f][0-9a-f][0-9a-f]\]/ {
|
||||||
if (match($0, /\[([0-9a-f]{7})\]/)) {
|
if (match($0, /\[[0-9a-f][0-9a-f][0-9a-f][0-9a-f][0-9a-f][0-9a-f][0-9a-f]\]/)) {
|
||||||
hash = substr($0, RSTART+1, RLENGTH-2)
|
hash = substr($0, RSTART+1, RLENGTH-2)
|
||||||
print hash
|
print hash
|
||||||
}
|
}
|
||||||
@@ -845,9 +931,10 @@ jobs:
|
|||||||
git commit -m "${{ env.COMMIT_MSG }}"
|
git commit -m "${{ env.COMMIT_MSG }}"
|
||||||
|
|
||||||
echo "📤 推送到远程 ${{ env.MAIN_BRANCH }} 分支..."
|
echo "📤 推送到远程 ${{ env.MAIN_BRANCH }} 分支..."
|
||||||
git push https://oauth2:${{ env.ACCESS_TOKEN }}@${GITHUB_SERVER_URL#https://}/${{ github.repository }}.git ${{ env.MAIN_BRANCH }}
|
if git -c credential.helper= \
|
||||||
|
-c 'credential.helper=!f() { test "$1" = get || exit 0; printf "%s\n" username=oauth2; printf "%s\n" "password=${ACCESS_TOKEN}"; }; f' \
|
||||||
if [ $? -eq 0 ]; then
|
-c credential.interactive=never \
|
||||||
|
-C "${{ env.REPO_DIR }}" push origin "${{ env.MAIN_BRANCH }}"; then
|
||||||
echo "✅ 推送成功"
|
echo "✅ 推送成功"
|
||||||
else
|
else
|
||||||
echo "❌ 推送失败"
|
echo "❌ 推送失败"
|
||||||
@@ -863,43 +950,139 @@ jobs:
|
|||||||
if: steps.changelog.outputs.changelog_updated == 'true'
|
if: steps.changelog.outputs.changelog_updated == 'true'
|
||||||
run: |
|
run: |
|
||||||
echo "======================================"
|
echo "======================================"
|
||||||
echo "📄 提取 Release Notes"
|
echo "📄 生成增量 Release Notes"
|
||||||
echo "======================================"
|
echo "======================================"
|
||||||
|
|
||||||
cd ${{ env.REPO_DIR }}
|
cd ${{ env.REPO_DIR }}
|
||||||
|
|
||||||
# 确保使用最新的 CHANGELOG
|
# Release Notes 只展示本次 rc 相对上一个 tag 的增量提交
|
||||||
git pull origin ${{ env.MAIN_BRANCH }}
|
# (CHANGELOG.md 仍是整个版本的累积,二者用途不同)
|
||||||
|
RELEASE_NOTES_RANGE="${{ env.RELEASE_NOTES_RANGE }}"
|
||||||
|
REPO_URL="${{ github.server_url }}/${{ github.repository }}"
|
||||||
|
echo "📊 增量范围: $RELEASE_NOTES_RANGE"
|
||||||
|
|
||||||
python3 << 'PYSCRIPT'
|
TEMP_COMMITS=$(mktemp)
|
||||||
import re
|
git log $RELEASE_NOTES_RANGE --no-merges --reverse \
|
||||||
|
--format="COMMIT_START%n%H%n%an%n%s%nBODY_START%n%b%nBODY_END" > "$TEMP_COMMITS"
|
||||||
|
|
||||||
changelog_version = "${{ env.CHANGELOG_VERSION }}"
|
declare -a COMMITS_HASH
|
||||||
|
declare -a COMMITS_AUTHOR
|
||||||
|
declare -a COMMITS_MESSAGE
|
||||||
|
declare -a COMMITS_BODY
|
||||||
|
|
||||||
with open('CHANGELOG.md', 'r', encoding='utf-8') as f:
|
IFS='|' read -ra IGNORE_PATTERNS <<< "${{ env.IGNORE_PATTERNS_PROCESSED }}"
|
||||||
content = f.read()
|
|
||||||
|
|
||||||
# 匹配版本区块
|
COMMIT_INDEX=0
|
||||||
pattern = rf'## :bookmark: {re.escape(changelog_version)}\s*\n(.*?)(?=\n---\n|\n## :bookmark: |\Z)'
|
CURRENT_HASH=""
|
||||||
match = re.search(pattern, content, re.DOTALL)
|
CURRENT_AUTHOR=""
|
||||||
|
CURRENT_MESSAGE=""
|
||||||
|
CURRENT_BODY=""
|
||||||
|
IN_BODY=false
|
||||||
|
|
||||||
if match:
|
flush_commit() {
|
||||||
version_content = match.group(1).strip()
|
[ -z "$CURRENT_HASH" ] && return
|
||||||
# 清理多余的空行
|
for pattern in "${IGNORE_PATTERNS[@]}"; do
|
||||||
version_content = re.sub(r'\n\s*\n\s*\n+', '\n\n', version_content).strip()
|
[ -z "$pattern" ] && continue
|
||||||
# 移除可能的分隔线
|
if echo "$CURRENT_MESSAGE" | grep -qiE "$pattern"; then
|
||||||
version_content = re.sub(r'^-{3,}\s*\n', '', version_content)
|
return
|
||||||
print(f"✓ Found changelog for version {changelog_version}")
|
fi
|
||||||
print(f"📊 Content length: {len(version_content)} characters")
|
done
|
||||||
else:
|
COMMITS_HASH[$COMMIT_INDEX]="$CURRENT_HASH"
|
||||||
version_content = "⚠️ No changelog found for this version"
|
COMMITS_AUTHOR[$COMMIT_INDEX]="$CURRENT_AUTHOR"
|
||||||
print(f"⚠️ WARNING: Version {changelog_version} not found in CHANGELOG")
|
COMMITS_MESSAGE[$COMMIT_INDEX]="$CURRENT_MESSAGE"
|
||||||
|
COMMITS_BODY[$COMMIT_INDEX]="$CURRENT_BODY"
|
||||||
|
COMMIT_INDEX=$((COMMIT_INDEX + 1))
|
||||||
|
}
|
||||||
|
|
||||||
with open('/tmp/release-body.txt', 'w', encoding='utf-8') as f:
|
while IFS= read -r line; do
|
||||||
f.write(version_content)
|
if [[ "$line" == "COMMIT_START" ]]; then
|
||||||
|
flush_commit
|
||||||
|
CURRENT_HASH=""
|
||||||
|
CURRENT_AUTHOR=""
|
||||||
|
CURRENT_MESSAGE=""
|
||||||
|
CURRENT_BODY=""
|
||||||
|
IN_BODY=false
|
||||||
|
elif [[ "$line" == "BODY_START" ]]; then
|
||||||
|
IN_BODY=true
|
||||||
|
CURRENT_BODY=""
|
||||||
|
elif [[ "$line" == "BODY_END" ]]; then
|
||||||
|
IN_BODY=false
|
||||||
|
elif [ "$IN_BODY" = true ]; then
|
||||||
|
if [ -n "$CURRENT_BODY" ]; then
|
||||||
|
CURRENT_BODY="${CURRENT_BODY}"$'\n'"${line}"
|
||||||
|
else
|
||||||
|
CURRENT_BODY="$line"
|
||||||
|
fi
|
||||||
|
else
|
||||||
|
if [ -z "$CURRENT_HASH" ]; then
|
||||||
|
CURRENT_HASH="$line"
|
||||||
|
elif [ -z "$CURRENT_AUTHOR" ]; then
|
||||||
|
CURRENT_AUTHOR="$line"
|
||||||
|
elif [ -z "$CURRENT_MESSAGE" ]; then
|
||||||
|
CURRENT_MESSAGE="$line"
|
||||||
|
fi
|
||||||
|
fi
|
||||||
|
done < "$TEMP_COMMITS"
|
||||||
|
flush_commit
|
||||||
|
rm -f "$TEMP_COMMITS"
|
||||||
|
|
||||||
print("✅ Release notes generated successfully")
|
VALID_COUNT=$COMMIT_INDEX
|
||||||
PYSCRIPT
|
echo "✅ 增量有效提交: $VALID_COUNT"
|
||||||
|
|
||||||
|
NOTES="${CHANGELOG_SECTION_TITLE}\n\n"
|
||||||
|
for ((i=0; i<$VALID_COUNT; i++)); do
|
||||||
|
hash="${COMMITS_HASH[$i]}"
|
||||||
|
author="${COMMITS_AUTHOR[$i]}"
|
||||||
|
message="${COMMITS_MESSAGE[$i]}"
|
||||||
|
body="${COMMITS_BODY[$i]}"
|
||||||
|
short_hash="${hash:0:7}"
|
||||||
|
COMMIT_LINK="([${short_hash}](${REPO_URL}/commit/${hash}))"
|
||||||
|
AUTHOR_INFO=""
|
||||||
|
[ -n "$author" ] && AUTHOR_INFO=" by @${author}"
|
||||||
|
|
||||||
|
if [ -n "$body" ]; then
|
||||||
|
cleaned_body=$(echo "$body" | sed -e :a -e '/^\s*$/d;')
|
||||||
|
else
|
||||||
|
cleaned_body=""
|
||||||
|
fi
|
||||||
|
|
||||||
|
if [ -n "$cleaned_body" ]; then
|
||||||
|
NOTES="${NOTES}- ${message} \n"
|
||||||
|
while IFS= read -r bline; do
|
||||||
|
[ -z "$bline" ] && continue
|
||||||
|
NOTES="${NOTES} ${bline} \n"
|
||||||
|
done <<< "$cleaned_body"
|
||||||
|
NOTES="${NOTES} ${COMMIT_LINK}${AUTHOR_INFO}\n"
|
||||||
|
else
|
||||||
|
NOTES="${NOTES}- ${message} ${COMMIT_LINK}${AUTHOR_INFO}\n"
|
||||||
|
fi
|
||||||
|
done
|
||||||
|
|
||||||
|
# 增量贡献者(同样过滤 bot)
|
||||||
|
if [ "$RELEASE_NOTES_RANGE" = "HEAD" ]; then
|
||||||
|
CONTRIBUTORS=$(git log --pretty=format:"%an" --no-merges)
|
||||||
|
else
|
||||||
|
CONTRIBUTORS=$(git log $RELEASE_NOTES_RANGE --pretty=format:"%an" --no-merges)
|
||||||
|
fi
|
||||||
|
CONTRIBUTORS=$(echo "$CONTRIBUTORS" | \
|
||||||
|
grep -v "github-actions\[bot\]" | \
|
||||||
|
grep -v "dependabot\[bot\]" | \
|
||||||
|
grep -v "renovate\[bot\]" | \
|
||||||
|
grep -vxF "${{ env.GIT_USER_NAME }}" | \
|
||||||
|
sort -u)
|
||||||
|
|
||||||
|
if [ -n "$CONTRIBUTORS" ]; then
|
||||||
|
NOTES="${NOTES}\n${{ env.CHANGELOG_CONTRIBUTORS_TITLE }}\n\n"
|
||||||
|
while IFS= read -r name; do
|
||||||
|
[ -z "$name" ] && continue
|
||||||
|
NOTES="${NOTES}<a href=\"${{ env.GITEA_SERVER }}/${name}\">\n"
|
||||||
|
NOTES="${NOTES} <img src=\"${{ env.GITEA_SERVER }}/${name}.png\" alt=\"${name}\" width=\"35\" height=\"35\" style=\"border-radius: 50%;\" onerror=\"this.src='${{ env.GITEA_SERVER }}/assets/img/avatar_default.png'\" />\n"
|
||||||
|
NOTES="${NOTES}</a>\n"
|
||||||
|
done <<< "$CONTRIBUTORS"
|
||||||
|
fi
|
||||||
|
|
||||||
|
echo -e "$NOTES" > /tmp/release-body.txt
|
||||||
|
echo "✅ Release notes 生成完成 ($(wc -c < /tmp/release-body.txt) 字节)"
|
||||||
|
|
||||||
echo "======================================"
|
echo "======================================"
|
||||||
echo ""
|
echo ""
|
||||||
@@ -920,41 +1103,26 @@ jobs:
|
|||||||
echo "📄 Draft: ${{ env.RELEASE_IS_DRAFT }}"
|
echo "📄 Draft: ${{ env.RELEASE_IS_DRAFT }}"
|
||||||
echo ""
|
echo ""
|
||||||
|
|
||||||
# 转换 bash 布尔值为 Python 布尔值
|
python3 << 'PYSCRIPT'
|
||||||
if [ "${{ env.RELEASE_IS_DRAFT }}" = "true" ]; then
|
|
||||||
PY_DRAFT="True"
|
|
||||||
else
|
|
||||||
PY_DRAFT="False"
|
|
||||||
fi
|
|
||||||
|
|
||||||
if [ "${{ env.RELEASE_IS_PRERELEASE }}" = "true" ]; then
|
|
||||||
PY_PRERELEASE="True"
|
|
||||||
else
|
|
||||||
PY_PRERELEASE="False"
|
|
||||||
fi
|
|
||||||
|
|
||||||
echo "🔄 转换布尔值: draft=${{ env.RELEASE_IS_DRAFT }} → $PY_DRAFT, prerelease=${{ env.RELEASE_IS_PRERELEASE }} → $PY_PRERELEASE"
|
|
||||||
echo ""
|
|
||||||
|
|
||||||
python3 << EOF
|
|
||||||
import json
|
import json
|
||||||
|
import os
|
||||||
|
|
||||||
with open('/tmp/release-body.txt', 'r') as f:
|
with open('/tmp/release-body.txt', 'r', encoding='utf-8') as release_body_file:
|
||||||
body = f.read()
|
body = release_body_file.read()
|
||||||
|
|
||||||
payload = {
|
payload = {
|
||||||
"tag_name": "${{ github.ref_name }}",
|
"tag_name": os.environ["VERSION"],
|
||||||
"name": "${{ env.RELEASE_TITLE_PROCESSED }}",
|
"name": os.environ["RELEASE_TITLE_PROCESSED"],
|
||||||
"body": body,
|
"body": body,
|
||||||
"draft": $PY_DRAFT,
|
"draft": os.environ["RELEASE_IS_DRAFT"] == "true",
|
||||||
"prerelease": $PY_PRERELEASE
|
"prerelease": os.environ["RELEASE_IS_PRERELEASE"] == "true",
|
||||||
}
|
}
|
||||||
|
|
||||||
with open('/tmp/payload.json', 'w', encoding='utf-8') as f:
|
with open('/tmp/payload.json', 'w', encoding='utf-8') as payload_file:
|
||||||
json.dump(payload, f, ensure_ascii=False, indent=2)
|
json.dump(payload, payload_file, ensure_ascii=False, indent=2)
|
||||||
|
|
||||||
print("✓ Payload prepared")
|
print("✓ Payload prepared")
|
||||||
EOF
|
PYSCRIPT
|
||||||
|
|
||||||
echo "🌐 发送 API 请求..."
|
echo "🌐 发送 API 请求..."
|
||||||
HTTP_CODE=$(curl -s -o /tmp/release_response.json -w "%{http_code}" -X POST \
|
HTTP_CODE=$(curl -s -o /tmp/release_response.json -w "%{http_code}" -X POST \
|
||||||
@@ -1045,11 +1213,11 @@ jobs:
|
|||||||
echo "======================================"
|
echo "======================================"
|
||||||
echo ""
|
echo ""
|
||||||
|
|
||||||
- name: 📊 生成 Workflow Summary
|
- name: 📋 生成 Workflow Summary
|
||||||
if: always()
|
if: always()
|
||||||
run: |
|
run: |
|
||||||
echo "======================================"
|
echo "======================================"
|
||||||
echo "📊 生成 Workflow Summary"
|
echo "📋 生成 Workflow Summary"
|
||||||
echo "======================================"
|
echo "======================================"
|
||||||
|
|
||||||
cat >> $GITHUB_STEP_SUMMARY << 'EOFSUMMARY'
|
cat >> $GITHUB_STEP_SUMMARY << 'EOFSUMMARY'
|
||||||
@@ -1059,7 +1227,7 @@ jobs:
|
|||||||
|
|
||||||
| 项目 | 结果 |
|
| 项目 | 结果 |
|
||||||
|------|------|
|
|------|------|
|
||||||
| 🏷️ Tag 版本 | **`${{ github.ref_name }}`** |
|
| 🏷️ Tag 版本 | **`${{ env.VERSION }}`** |
|
||||||
| 📝 CHANGELOG 版本 | **`${{ env.CHANGELOG_VERSION }}`** |
|
| 📝 CHANGELOG 版本 | **`${{ env.CHANGELOG_VERSION }}`** |
|
||||||
| 🌿 主分支 | **`${{ env.MAIN_BRANCH }}`** |
|
| 🌿 主分支 | **`${{ env.MAIN_BRANCH }}`** |
|
||||||
| 🌐 Gitea 服务器 | **`${{ env.GITEA_SERVER }}`** |
|
| 🌐 Gitea 服务器 | **`${{ env.GITEA_SERVER }}`** |
|
||||||
@@ -1075,16 +1243,6 @@ jobs:
|
|||||||
|
|
||||||
检测到此提交由 Bot 创建(包含 `[skip ci]` 标记),为防止无限循环,已跳过执行。
|
检测到此提交由 Bot 创建(包含 `[skip ci]` 标记),为防止无限循环,已跳过执行。
|
||||||
EOFBOT
|
EOFBOT
|
||||||
elif [ "${{ steps.check_version.outputs.version_exists }}" = "true" ]; then
|
|
||||||
cat >> $GITHUB_STEP_SUMMARY << 'EOFEXIST'
|
|
||||||
| 📋 执行状态 | ⏭️ 已跳过 (版本已存在) |
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
⏭️ **工作流已跳过**
|
|
||||||
|
|
||||||
版本 `${{ env.CHANGELOG_VERSION }}` 已存在于 CHANGELOG.md 中。
|
|
||||||
EOFEXIST
|
|
||||||
elif [ "${{ steps.changelog.outputs.changelog_updated }}" = "true" ]; then
|
elif [ "${{ steps.changelog.outputs.changelog_updated }}" = "true" ]; then
|
||||||
if [ "${{ steps.changelog.outputs.content_changed }}" = "true" ]; then
|
if [ "${{ steps.changelog.outputs.content_changed }}" = "true" ]; then
|
||||||
cat >> $GITHUB_STEP_SUMMARY << 'EOFSUCCESS'
|
cat >> $GITHUB_STEP_SUMMARY << 'EOFSUCCESS'
|
||||||
@@ -1097,7 +1255,6 @@ jobs:
|
|||||||
- ✅ CHANGELOG.md 已更新
|
- ✅ CHANGELOG.md 已更新
|
||||||
- ✅ 更改已推送到主分支 (`${{ env.MAIN_BRANCH }}`)
|
- ✅ 更改已推送到主分支 (`${{ env.MAIN_BRANCH }}`)
|
||||||
- ✅ Release 已创建
|
- ✅ Release 已创建
|
||||||
- ✅ CHANGELOG.md 已作为附件上传
|
|
||||||
EOFSUCCESS
|
EOFSUCCESS
|
||||||
else
|
else
|
||||||
cat >> $GITHUB_STEP_SUMMARY << 'EOFREBUILD'
|
cat >> $GITHUB_STEP_SUMMARY << 'EOFREBUILD'
|
||||||
@@ -1109,7 +1266,6 @@ jobs:
|
|||||||
|
|
||||||
- ℹ️ CHANGELOG.md 无需更新(内容已存在)
|
- ℹ️ CHANGELOG.md 无需更新(内容已存在)
|
||||||
- ✅ Release 已创建
|
- ✅ Release 已创建
|
||||||
- ✅ CHANGELOG.md 已作为附件上传
|
|
||||||
|
|
||||||
💡 **说明**: 检测到这是重建已删除的 tag,CHANGELOG 中已包含所有提交内容,因此直接从现有内容创建 Release。
|
💡 **说明**: 检测到这是重建已删除的 tag,CHANGELOG 中已包含所有提交内容,因此直接从现有内容创建 Release。
|
||||||
EOFREBUILD
|
EOFREBUILD
|
||||||
@@ -1142,7 +1298,7 @@ jobs:
|
|||||||
|
|
||||||
- 📝 [查看 CHANGELOG](${GITHUB_SERVER_URL}/${GITHUB_REPOSITORY}/src/branch/${{ env.MAIN_BRANCH }}/CHANGELOG.md)
|
- 📝 [查看 CHANGELOG](${GITHUB_SERVER_URL}/${GITHUB_REPOSITORY}/src/branch/${{ env.MAIN_BRANCH }}/CHANGELOG.md)
|
||||||
- 🚀 [查看 Releases](${GITHUB_SERVER_URL}/${GITHUB_REPOSITORY}/releases)
|
- 🚀 [查看 Releases](${GITHUB_SERVER_URL}/${GITHUB_REPOSITORY}/releases)
|
||||||
- 🔧 [查看 Workflow 配置](${GITHUB_SERVER_URL}/${GITHUB_REPOSITORY}/blob/${{ env.MAIN_BRANCH }}/.github/workflows/changelog_and_release.yml)
|
- 🔧 [查看 Workflow 配置](${GITHUB_SERVER_URL}/${GITHUB_REPOSITORY}/src/branch/${{ env.MAIN_BRANCH }}/.gitea/workflows/changelog_and_release.yml)
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
@@ -1172,12 +1328,9 @@ jobs:
|
|||||||
if [ "${{ steps.check_bot.outputs.is_bot_commit }}" = "true" ]; then
|
if [ "${{ steps.check_bot.outputs.is_bot_commit }}" = "true" ]; then
|
||||||
echo "⏭️ 已跳过: Bot 提交检测"
|
echo "⏭️ 已跳过: Bot 提交检测"
|
||||||
echo " 原因: 检测到 [skip ci] 标记,防止无限循环"
|
echo " 原因: 检测到 [skip ci] 标记,防止无限循环"
|
||||||
elif [ "${{ steps.check_version.outputs.version_exists }}" = "true" ]; then
|
|
||||||
echo "⏭️ 已跳过: 版本已存在"
|
|
||||||
echo " 版本: ${{ env.CHANGELOG_VERSION }}"
|
|
||||||
elif [ "${{ steps.changelog.outputs.changelog_updated }}" = "true" ]; then
|
elif [ "${{ steps.changelog.outputs.changelog_updated }}" = "true" ]; then
|
||||||
echo "📊 执行结果:"
|
echo "📊 执行结果:"
|
||||||
echo " - Tag: ${{ github.ref_name }}"
|
echo " - Tag: ${{ env.VERSION }}"
|
||||||
echo " - CHANGELOG 版本: ${{ env.CHANGELOG_VERSION }}"
|
echo " - CHANGELOG 版本: ${{ env.CHANGELOG_VERSION }}"
|
||||||
echo " - 主分支: ${{ env.MAIN_BRANCH }}"
|
echo " - 主分支: ${{ env.MAIN_BRANCH }}"
|
||||||
echo " - Release 标题: ${{ env.RELEASE_TITLE_PROCESSED }}"
|
echo " - Release 标题: ${{ env.RELEASE_TITLE_PROCESSED }}"
|
||||||
@@ -1188,11 +1341,9 @@ jobs:
|
|||||||
echo "✅ 已完成的操作:"
|
echo "✅ 已完成的操作:"
|
||||||
echo " - CHANGELOG.md 已更新并推送到 ${{ env.MAIN_BRANCH }} 分支"
|
echo " - CHANGELOG.md 已更新并推送到 ${{ env.MAIN_BRANCH }} 分支"
|
||||||
echo " - Release 已创建"
|
echo " - Release 已创建"
|
||||||
echo " - CHANGELOG.md 已上传为附件"
|
|
||||||
else
|
else
|
||||||
echo "✅ 已完成的操作:"
|
echo "✅ 已完成的操作:"
|
||||||
echo " - Release 已创建(从现有 CHANGELOG 内容)"
|
echo " - Release 已创建(从现有 CHANGELOG 内容)"
|
||||||
echo " - CHANGELOG.md 已上传为附件"
|
|
||||||
echo ""
|
echo ""
|
||||||
echo "💡 说明: 检测到这是重建已删除的 tag,CHANGELOG 中已包含"
|
echo "💡 说明: 检测到这是重建已删除的 tag,CHANGELOG 中已包含"
|
||||||
echo " 所有提交内容,因此直接从现有内容创建 Release。"
|
echo " 所有提交内容,因此直接从现有内容创建 Release。"
|
||||||
|
|||||||
@@ -1,33 +0,0 @@
|
|||||||
name: Hello from ImmortalWrt
|
|
||||||
|
|
||||||
on:
|
|
||||||
push:
|
|
||||||
branches:
|
|
||||||
- main
|
|
||||||
|
|
||||||
jobs:
|
|
||||||
say-hello:
|
|
||||||
runs-on: company
|
|
||||||
|
|
||||||
steps:
|
|
||||||
- name: 打印问候
|
|
||||||
run: echo "Hello form ImmortalWrt Router!"
|
|
||||||
|
|
||||||
- name: 显示系统信息
|
|
||||||
run: |
|
|
||||||
echo "===== 系统信息 ====="
|
|
||||||
uname -a
|
|
||||||
cat /etc/openwrt_release || echo "不是 OpenWrt 环境"
|
|
||||||
|
|
||||||
- name: 显示 CPU 和内存
|
|
||||||
run: |
|
|
||||||
echo "===== CPU 信息 ====="
|
|
||||||
cat /proc/cpuinfo | grep "model name" | head -1 || echo "ARM 架构"
|
|
||||||
echo "===== 内存信息 ====="
|
|
||||||
free -h
|
|
||||||
|
|
||||||
- name: 显示磁盘空间
|
|
||||||
run: df -h
|
|
||||||
|
|
||||||
- name: 列出当前目录
|
|
||||||
run: ls -la
|
|
||||||
@@ -0,0 +1,174 @@
|
|||||||
|
name: 🧰 准备环境
|
||||||
|
|
||||||
|
on:
|
||||||
|
push:
|
||||||
|
workflow_dispatch:
|
||||||
|
|
||||||
|
concurrency:
|
||||||
|
group: prepare-${{ github.repository }}
|
||||||
|
cancel-in-progress: false
|
||||||
|
|
||||||
|
env:
|
||||||
|
ACCESS_TOKEN: ${{ secrets.WORKFLOW }}
|
||||||
|
WORKSPACE_ROOT: "/data/workspace"
|
||||||
|
|
||||||
|
jobs:
|
||||||
|
prepare:
|
||||||
|
runs-on: standard-ubuntu-22
|
||||||
|
permissions:
|
||||||
|
contents: read
|
||||||
|
|
||||||
|
steps:
|
||||||
|
- name: 🔧 检查并安装共享工具
|
||||||
|
id: tools
|
||||||
|
shell: bash
|
||||||
|
run: |
|
||||||
|
set -euo pipefail
|
||||||
|
|
||||||
|
declare -A package_for=(
|
||||||
|
[git]="git"
|
||||||
|
[python3]="python3"
|
||||||
|
[curl]="curl"
|
||||||
|
[jq]="jq"
|
||||||
|
[bc]="bc"
|
||||||
|
[flock]="util-linux"
|
||||||
|
[realpath]="coreutils"
|
||||||
|
)
|
||||||
|
required_commands=(git python3 curl jq bc flock realpath)
|
||||||
|
missing_commands=()
|
||||||
|
missing_packages=()
|
||||||
|
declare -A seen_packages=()
|
||||||
|
|
||||||
|
for command in "${required_commands[@]}"; do
|
||||||
|
if ! command -v "$command" >/dev/null 2>&1; then
|
||||||
|
package="${package_for[$command]}"
|
||||||
|
missing_commands+=("$command")
|
||||||
|
if [[ -z "${seen_packages[$package]+x}" ]]; then
|
||||||
|
missing_packages+=("$package")
|
||||||
|
seen_packages[$package]=1
|
||||||
|
fi
|
||||||
|
fi
|
||||||
|
done
|
||||||
|
|
||||||
|
installed=false
|
||||||
|
if [ "${#missing_packages[@]}" -gt 0 ]; then
|
||||||
|
echo "缺失命令: ${missing_commands[*]}"
|
||||||
|
echo "准备安装: ${missing_packages[*]}"
|
||||||
|
|
||||||
|
if [ "$(id -u)" -eq 0 ]; then
|
||||||
|
APT=(apt-get)
|
||||||
|
elif command -v sudo >/dev/null 2>&1; then
|
||||||
|
APT=(sudo apt-get)
|
||||||
|
else
|
||||||
|
echo "无法安装缺失命令;当前用户不是 root 且没有 sudo" >&2
|
||||||
|
echo "命令: ${missing_commands[*]}" >&2
|
||||||
|
echo "包: ${missing_packages[*]}" >&2
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
"${APT[@]}" update -qq
|
||||||
|
"${APT[@]}" install -y -qq "${missing_packages[@]}"
|
||||||
|
installed=true
|
||||||
|
fi
|
||||||
|
|
||||||
|
for command in "${required_commands[@]}"; do
|
||||||
|
if ! command -v "$command" >/dev/null 2>&1; then
|
||||||
|
echo "安装后仍找不到命令: $command (${package_for[$command]})" >&2
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
done
|
||||||
|
|
||||||
|
{
|
||||||
|
echo "## Prepare 环境"
|
||||||
|
echo ""
|
||||||
|
echo "| 命令 | 版本 |"
|
||||||
|
echo "| --- | --- |"
|
||||||
|
for command in "${required_commands[@]}"; do
|
||||||
|
version_output=$("$command" --version 2>&1 || true)
|
||||||
|
version_line=${version_output%%$'\n'*}
|
||||||
|
version_line=${version_line//|/\\|}
|
||||||
|
printf '| `%s` | `%s` |\n' "$command" "$version_line"
|
||||||
|
done
|
||||||
|
echo ""
|
||||||
|
if [ "$installed" = true ]; then
|
||||||
|
echo "安装的包:\`${missing_packages[*]}\`"
|
||||||
|
else
|
||||||
|
echo "安装的包:无,所有命令已存在。"
|
||||||
|
fi
|
||||||
|
} >> "$GITHUB_STEP_SUMMARY"
|
||||||
|
|
||||||
|
echo "installed=$installed" >> "$GITHUB_OUTPUT"
|
||||||
|
|
||||||
|
- name: 📥 准备共享 bare 仓库
|
||||||
|
shell: bash
|
||||||
|
run: |
|
||||||
|
set -euo pipefail
|
||||||
|
|
||||||
|
REPO_NAME="${{ github.event.repository.name }}"
|
||||||
|
REMOTE_URL="${{ github.server_url }}/${{ github.repository }}.git"
|
||||||
|
HEAD_SHA="${{ github.sha }}"
|
||||||
|
REPOSITORY_DIR="${WORKSPACE_ROOT}/${REPO_NAME}/repository.git"
|
||||||
|
|
||||||
|
case "$HEAD_SHA" in
|
||||||
|
''|*[!0-9a-fA-F]*)
|
||||||
|
echo "无效的触发提交 SHA: $HEAD_SHA" >&2
|
||||||
|
exit 1
|
||||||
|
;;
|
||||||
|
esac
|
||||||
|
case "$REMOTE_URL" in
|
||||||
|
https://*) ;;
|
||||||
|
*)
|
||||||
|
echo "拒绝通过非 HTTPS remote 使用 WORKFLOW token: $REMOTE_URL" >&2
|
||||||
|
exit 1
|
||||||
|
;;
|
||||||
|
esac
|
||||||
|
if [ -z "$ACCESS_TOKEN" ]; then
|
||||||
|
echo "未配置 WORKFLOW secret,无法读取仓库" >&2
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
mkdir -p "$(dirname "$REPOSITORY_DIR")"
|
||||||
|
if [ ! -e "$REPOSITORY_DIR" ]; then
|
||||||
|
git init --bare "$REPOSITORY_DIR"
|
||||||
|
else
|
||||||
|
if [ ! -d "$REPOSITORY_DIR" ]; then
|
||||||
|
echo "共享仓库路径存在但不是目录: $REPOSITORY_DIR" >&2
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
if ! is_bare=$(git --git-dir="$REPOSITORY_DIR" rev-parse --is-bare-repository 2>/dev/null) || \
|
||||||
|
[ "$is_bare" != "true" ]; then
|
||||||
|
echo "共享仓库路径不是有效 bare 仓库: $REPOSITORY_DIR" >&2
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
fi
|
||||||
|
|
||||||
|
if git --git-dir="$REPOSITORY_DIR" remote get-url origin >/dev/null 2>&1; then
|
||||||
|
git --git-dir="$REPOSITORY_DIR" remote set-url origin "$REMOTE_URL"
|
||||||
|
else
|
||||||
|
git --git-dir="$REPOSITORY_DIR" remote add origin "$REMOTE_URL"
|
||||||
|
fi
|
||||||
|
|
||||||
|
git \
|
||||||
|
-c credential.helper= \
|
||||||
|
-c 'credential.helper=!f() { test "$1" = get || exit 0; printf "%s\n" username=oauth2; printf "%s\n" "password=${ACCESS_TOKEN}"; }; f' \
|
||||||
|
-c credential.interactive=never \
|
||||||
|
--git-dir="$REPOSITORY_DIR" fetch \
|
||||||
|
--prune --prune-tags --tags --force origin \
|
||||||
|
'+refs/heads/*:refs/remotes/origin/*'
|
||||||
|
|
||||||
|
if ! git --git-dir="$REPOSITORY_DIR" cat-file -e "${HEAD_SHA}^{commit}"; then
|
||||||
|
echo "共享仓库中不存在触发提交: $HEAD_SHA" >&2
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
resolved_sha=$(git --git-dir="$REPOSITORY_DIR" rev-parse "${HEAD_SHA}^{commit}")
|
||||||
|
{
|
||||||
|
echo ""
|
||||||
|
echo "## Prepare 仓库"
|
||||||
|
echo ""
|
||||||
|
echo "- bare 仓库:\`$REPOSITORY_DIR\`"
|
||||||
|
echo "- 触发提交:\`$resolved_sha\`"
|
||||||
|
echo "- 远端:\`$REMOTE_URL\`"
|
||||||
|
} >> "$GITHUB_STEP_SUMMARY"
|
||||||
|
|
||||||
|
echo "Prepare 完成: $REPOSITORY_DIR @ $resolved_sha"
|
||||||
@@ -0,0 +1,102 @@
|
|||||||
|
name: 🖥️ Ubuntu 系统信息
|
||||||
|
|
||||||
|
on:
|
||||||
|
push:
|
||||||
|
branches:
|
||||||
|
- main
|
||||||
|
workflow_dispatch: # 允许手动触发
|
||||||
|
|
||||||
|
jobs:
|
||||||
|
show-system-info:
|
||||||
|
runs-on: ubuntu-22.04
|
||||||
|
|
||||||
|
steps:
|
||||||
|
- name: 👋 打印问候
|
||||||
|
run: |
|
||||||
|
echo "====================================="
|
||||||
|
echo " Ubuntu System Information Check "
|
||||||
|
echo "====================================="
|
||||||
|
echo ""
|
||||||
|
|
||||||
|
- name: ℹ️ 显示系统信息
|
||||||
|
run: |
|
||||||
|
echo "===== 🐧 系统版本 ====="
|
||||||
|
cat /etc/os-release
|
||||||
|
echo ""
|
||||||
|
echo "===== 🧩 内核信息 ====="
|
||||||
|
uname -a
|
||||||
|
echo ""
|
||||||
|
echo "===== 🏷️ 主机名 ====="
|
||||||
|
hostname
|
||||||
|
echo ""
|
||||||
|
echo "===== ⏱️ 系统启动时间 ====="
|
||||||
|
uptime
|
||||||
|
|
||||||
|
- name: ⚙️ 显示 CPU 信息
|
||||||
|
run: |
|
||||||
|
echo "===== ⚙️ CPU 详细信息 ====="
|
||||||
|
lscpu
|
||||||
|
echo ""
|
||||||
|
echo "===== 🏷️ CPU 型号 ====="
|
||||||
|
cat /proc/cpuinfo | grep "model name" | head -1
|
||||||
|
echo ""
|
||||||
|
echo "===== 🔢 CPU 核心数 ====="
|
||||||
|
nproc
|
||||||
|
|
||||||
|
- name: 🧠 显示内存信息
|
||||||
|
run: |
|
||||||
|
echo "===== 📊 内存使用情况 ====="
|
||||||
|
free -h
|
||||||
|
echo ""
|
||||||
|
echo "===== 🧠 内存详细信息 ====="
|
||||||
|
cat /proc/meminfo | grep -E 'MemTotal|MemFree|MemAvailable'
|
||||||
|
|
||||||
|
- name: 💾 显示磁盘信息
|
||||||
|
run: |
|
||||||
|
echo "===== 📊 磁盘使用情况 ====="
|
||||||
|
df -h
|
||||||
|
echo ""
|
||||||
|
echo "===== 🗂️ 磁盘分区信息 ====="
|
||||||
|
lsblk
|
||||||
|
|
||||||
|
- name: 🌐 显示网络信息
|
||||||
|
run: |
|
||||||
|
echo "===== 🔌 网络接口 ====="
|
||||||
|
cat /proc/net/dev
|
||||||
|
echo ""
|
||||||
|
echo "===== 🧭 路由表 ====="
|
||||||
|
cat /proc/net/route
|
||||||
|
echo ""
|
||||||
|
echo "===== 📡 DNS 配置 ====="
|
||||||
|
cat /etc/resolv.conf
|
||||||
|
|
||||||
|
- name: 📦 显示已安装的软件
|
||||||
|
run: |
|
||||||
|
echo "===== 🐍 Python 版本 ====="
|
||||||
|
python3 --version
|
||||||
|
echo ""
|
||||||
|
echo "===== 🟢 Node.js 版本 ====="
|
||||||
|
node --version || echo "Node.js 未安装"
|
||||||
|
echo ""
|
||||||
|
echo "===== 🐳 Docker 版本 ====="
|
||||||
|
docker --version || echo "Docker 未安装"
|
||||||
|
echo ""
|
||||||
|
echo "===== 🔀 Git 版本 ====="
|
||||||
|
git --version
|
||||||
|
|
||||||
|
- name: 🔧 显示环境变量
|
||||||
|
run: |
|
||||||
|
echo "===== 🔧 重要环境变量 ====="
|
||||||
|
echo "PATH: $PATH"
|
||||||
|
echo "HOME: $HOME"
|
||||||
|
echo "USER: $USER"
|
||||||
|
echo "SHELL: $SHELL"
|
||||||
|
echo "PWD: $PWD"
|
||||||
|
|
||||||
|
- name: 📂 显示当前目录
|
||||||
|
run: |
|
||||||
|
echo "===== 📍 当前工作目录 ====="
|
||||||
|
pwd
|
||||||
|
echo ""
|
||||||
|
echo "===== 📄 目录内容 ====="
|
||||||
|
ls -lah
|
||||||
@@ -1,976 +0,0 @@
|
|||||||
name: 📊 代码统计徽章
|
|
||||||
|
|
||||||
on:
|
|
||||||
push:
|
|
||||||
branches: [main, master]
|
|
||||||
workflow_dispatch:
|
|
||||||
|
|
||||||
# ==========================================
|
|
||||||
# 🔧 配置区域 - 根据你的项目修改
|
|
||||||
# ==========================================
|
|
||||||
env:
|
|
||||||
# ===== Token 配置 =====
|
|
||||||
# 建议在 Settings -> Secrets 中配置 STATS_TOKEN 以获得更好的权限控制
|
|
||||||
ACCESS_TOKEN: ${{ secrets.WORKFLOW }}
|
|
||||||
|
|
||||||
# ===== 工作区配置 =====
|
|
||||||
# 完整克隆的工作目录
|
|
||||||
WORKSPACE_DIR: "/home/workspace"
|
|
||||||
|
|
||||||
# ===== 分支配置 =====
|
|
||||||
# 徽章数据存储分支(可配置)
|
|
||||||
BADGE_BRANCH: "stats"
|
|
||||||
# 徽章文件存储目录
|
|
||||||
BADGE_DIR: "badges"
|
|
||||||
|
|
||||||
# ===== 排除配置 =====
|
|
||||||
# 全局排除的目录(这些目录会被完全忽略)
|
|
||||||
EXCLUDE_DIRS: "node_modules,dist,build,out,target,vendor,.venv,venv,__pycache__,.git,.github"
|
|
||||||
|
|
||||||
# 特殊包含规则(即使在排除目录中,也统计这些扩展名的文件)
|
|
||||||
# 格式: 目录:扩展名列表
|
|
||||||
# 例如: 'dist:js,css' 表示即使 dist 被排除,也统计其中的 .js 和 .css 文件
|
|
||||||
SPECIAL_INCLUDES: ""
|
|
||||||
# 示例: 'dist:js,css|vendor:go,mod'
|
|
||||||
|
|
||||||
# ===== 徽章颜色配置 =====
|
|
||||||
COLOR_TOTAL: "blue"
|
|
||||||
COLOR_FILES: "green"
|
|
||||||
COLOR_DEFAULT: "brightgreen"
|
|
||||||
|
|
||||||
# ===== 徽章样式配置 =====
|
|
||||||
BADGE_STYLE: "flat" # 可选: flat, flat-square, plastic, for-the-badge, social
|
|
||||||
|
|
||||||
# ===== 输出配置 =====
|
|
||||||
# 是否生成详细报告
|
|
||||||
GENERATE_DETAILED_REPORT: "true"
|
|
||||||
# 是否输出到 workflow summary
|
|
||||||
OUTPUT_TO_SUMMARY: "true"
|
|
||||||
# 最小代码行数阈值(低于此值的语言不生成徽章)
|
|
||||||
MIN_LINES_THRESHOLD: "10"
|
|
||||||
|
|
||||||
# ===== Git 配置 =====
|
|
||||||
GIT_USER_NAME: "ci-bot"
|
|
||||||
GIT_USER_EMAIL: "ci-bot@tinysoft.com.cn"
|
|
||||||
|
|
||||||
# ===== 平台配置 =====
|
|
||||||
# 平台类型: github 或 gitea
|
|
||||||
PLATFORM: "gitea"
|
|
||||||
# Git 服务器 URL(Gitea 示例: https://gitea.example.com)
|
|
||||||
GIT_SERVER_URL: "https://git.mytsl.cn"
|
|
||||||
# 仓库路径(格式: owner/repo)
|
|
||||||
REPO_PATH: "${{ github.repository }}"
|
|
||||||
# Raw 文件基础 URL
|
|
||||||
# GitHub: https://raw.githubusercontent.com/{owner}/{repo}/{branch}/{path}
|
|
||||||
# Gitea: https://gitea.example.com/{owner}/{repo}/raw/branch/{branch}/{path}
|
|
||||||
RAW_URL_BASE: 'https://git.mytsl.cn/${{ github.repository }}/raw/branch'
|
|
||||||
|
|
||||||
# ==========================================
|
|
||||||
# 🎨 语言分组配置
|
|
||||||
# 格式: 组名:后缀列表:显示名称:颜色:图标(可选)
|
|
||||||
# 图标使用 simple-icons 的名称,如 cplusplus, typescript 等
|
|
||||||
# ==========================================
|
|
||||||
LANGUAGE_GROUPS: |
|
|
||||||
cpp:hpp,cpp,cxx,cc,h,c:C/C++:00599C:cplusplus
|
|
||||||
python:py:Python:3572A5:python
|
|
||||||
typescript:ts,tsx:TypeScript:3178c6:typescript
|
|
||||||
javascript:js,jsx:JavaScript:F7DF1E:javascript
|
|
||||||
java:java:Java:007396:java
|
|
||||||
go:go:Go:00ADD8:go
|
|
||||||
rust:rs:Rust:CE412B:rust
|
|
||||||
shell:sh,bash:Shell:4EAA25:gnubash
|
|
||||||
yaml:yml,yaml:YAML:CB171E
|
|
||||||
tsf:tsl,tsf:TSF:9945FF
|
|
||||||
|
|
||||||
jobs:
|
|
||||||
update-stats:
|
|
||||||
runs-on: ubuntu-22.04
|
|
||||||
permissions:
|
|
||||||
contents: write
|
|
||||||
|
|
||||||
steps:
|
|
||||||
- name: 🔍 验证 Token 配置
|
|
||||||
id: validate_token
|
|
||||||
run: |
|
|
||||||
echo "🔐 验证访问令牌..."
|
|
||||||
|
|
||||||
if [ -z "${{ env.ACCESS_TOKEN }}" ]; then
|
|
||||||
echo "❌ 错误: 未配置访问令牌"
|
|
||||||
echo "请在 Settings -> Secrets 中配置 STATS_TOKEN 或确保 GITHUB_TOKEN 可用"
|
|
||||||
exit 1
|
|
||||||
fi
|
|
||||||
|
|
||||||
# 检测使用的是哪个 token
|
|
||||||
if [ -n "${{ secrets.STATS_TOKEN }}" ]; then
|
|
||||||
echo "✅ 使用自定义 STATS_TOKEN"
|
|
||||||
echo "token_type=STATS_TOKEN" >> $GITHUB_OUTPUT
|
|
||||||
else
|
|
||||||
echo "✅ 使用默认 GITHUB_TOKEN"
|
|
||||||
echo "token_type=GITHUB_TOKEN" >> $GITHUB_OUTPUT
|
|
||||||
fi
|
|
||||||
|
|
||||||
echo "🔗 仓库: ${{ github.repository }}"
|
|
||||||
echo "🌿 分支: ${{ github.ref_name }}"
|
|
||||||
echo "📝 提交: ${GITHUB_SHA:0:7}"
|
|
||||||
|
|
||||||
- name: 🔧 检查必需工具
|
|
||||||
id: check_deps
|
|
||||||
run: |
|
|
||||||
echo "======================================"
|
|
||||||
echo "🔍 检查依赖工具"
|
|
||||||
echo "======================================"
|
|
||||||
|
|
||||||
MISSING_TOOLS=()
|
|
||||||
|
|
||||||
# 检查 bc(用于数字计算)
|
|
||||||
if ! command -v bc &> /dev/null; then
|
|
||||||
echo "⚠️ bc 未安装"
|
|
||||||
MISSING_TOOLS+=("bc")
|
|
||||||
else
|
|
||||||
echo "✓ bc 已安装"
|
|
||||||
fi
|
|
||||||
|
|
||||||
# 检查 jq(用于 JSON 处理)
|
|
||||||
if ! command -v jq &> /dev/null; then
|
|
||||||
echo "⚠️ jq 未安装"
|
|
||||||
MISSING_TOOLS+=("jq")
|
|
||||||
else
|
|
||||||
echo "✓ jq 已安装"
|
|
||||||
fi
|
|
||||||
|
|
||||||
# 如果有缺失的工具,安装它们
|
|
||||||
if [ ${#MISSING_TOOLS[@]} -gt 0 ]; then
|
|
||||||
echo ""
|
|
||||||
echo "📦 安装缺失的工具: ${MISSING_TOOLS[*]}"
|
|
||||||
|
|
||||||
# 🔧 智能判断是否需要 sudo
|
|
||||||
# Docker 环境通常以 root 运行,不需要 sudo
|
|
||||||
if [ "$EUID" -eq 0 ] || [ "$(id -u)" -eq 0 ]; then
|
|
||||||
# 当前是 root 用户,直接执行
|
|
||||||
echo "🔑 检测到 root 权限,直接安装"
|
|
||||||
apt-get update -qq
|
|
||||||
apt-get install -y -qq "${MISSING_TOOLS[@]}"
|
|
||||||
elif command -v sudo &> /dev/null; then
|
|
||||||
# 有 sudo 命令,使用 sudo
|
|
||||||
echo "🔑 使用 sudo 安装"
|
|
||||||
sudo apt-get update -qq
|
|
||||||
sudo apt-get install -y -qq "${MISSING_TOOLS[@]}"
|
|
||||||
else
|
|
||||||
# 既不是 root 也没有 sudo
|
|
||||||
echo "❌ 错误:没有足够权限安装工具"
|
|
||||||
echo "请在 Docker 镜像中预装以下工具:"
|
|
||||||
echo " ${MISSING_TOOLS[*]}"
|
|
||||||
echo ""
|
|
||||||
echo "方案1: 使用包含这些工具的镜像"
|
|
||||||
echo "方案2: 在 Dockerfile 中添加:"
|
|
||||||
echo " RUN apt-get update && apt-get install -y bc jq"
|
|
||||||
exit 1
|
|
||||||
fi
|
|
||||||
|
|
||||||
echo "installed=true" >> $GITHUB_OUTPUT
|
|
||||||
echo "✅ 工具安装完成"
|
|
||||||
else
|
|
||||||
echo "installed=false" >> $GITHUB_OUTPUT
|
|
||||||
echo "✅ 所有工具已就绪"
|
|
||||||
fi
|
|
||||||
|
|
||||||
echo "======================================"
|
|
||||||
echo ""
|
|
||||||
|
|
||||||
- name: 📥 克隆主仓库
|
|
||||||
id: clone_main
|
|
||||||
run: |
|
|
||||||
echo "======================================"
|
|
||||||
echo "🚀 开始准备主仓库"
|
|
||||||
echo "======================================"
|
|
||||||
|
|
||||||
REPO_NAME="${{ github.event.repository.name }}"
|
|
||||||
REPO_DIR="${{ env.WORKSPACE_DIR }}/$REPO_NAME"
|
|
||||||
|
|
||||||
echo "📁 仓库名称: $REPO_NAME"
|
|
||||||
echo "📍 目标目录: $REPO_DIR"
|
|
||||||
echo "🌐 服务器: ${GITHUB_SERVER_URL}"
|
|
||||||
echo "🌿 分支: ${{ github.ref_name }}"
|
|
||||||
echo ""
|
|
||||||
|
|
||||||
# 检查仓库状态
|
|
||||||
if [ -d "$REPO_DIR" ]; then
|
|
||||||
echo "📂 目录已存在,检查 Git 仓库状态..."
|
|
||||||
|
|
||||||
if [ -d "$REPO_DIR/.git" ]; then
|
|
||||||
# 目录存在且是有效的 Git 仓库
|
|
||||||
echo "✓ 发现有效的 Git 仓库,执行增量更新..."
|
|
||||||
cd "$REPO_DIR"
|
|
||||||
|
|
||||||
# 清理工作区
|
|
||||||
git clean -fdx
|
|
||||||
git reset --hard
|
|
||||||
|
|
||||||
# 获取最新代码
|
|
||||||
echo "📥 拉取最新代码..."
|
|
||||||
git fetch --all --tags --force
|
|
||||||
|
|
||||||
echo "✓ 仓库已更新"
|
|
||||||
else
|
|
||||||
# 目录存在但不是 Git 仓库(可能之前运行失败)
|
|
||||||
echo "⚠️ 目录存在但不是有效的 Git 仓库"
|
|
||||||
echo "🧹 清理损坏的目录..."
|
|
||||||
rm -rf "$REPO_DIR"
|
|
||||||
echo "✓ 已清理"
|
|
||||||
|
|
||||||
# 重新克隆
|
|
||||||
echo "📥 克隆仓库..."
|
|
||||||
mkdir -p "${{ env.WORKSPACE_DIR }}"
|
|
||||||
|
|
||||||
git clone \
|
|
||||||
https://oauth2:${{ env.ACCESS_TOKEN }}@${GITHUB_SERVER_URL#https://}/${{ github.repository }}.git \
|
|
||||||
"$REPO_DIR"
|
|
||||||
|
|
||||||
if [ $? -ne 0 ]; then
|
|
||||||
echo "❌ 克隆失败"
|
|
||||||
exit 1
|
|
||||||
fi
|
|
||||||
|
|
||||||
cd "$REPO_DIR"
|
|
||||||
echo "✓ 仓库已克隆"
|
|
||||||
fi
|
|
||||||
else
|
|
||||||
# 目录不存在,首次克隆
|
|
||||||
echo "📥 克隆仓库(首次)..."
|
|
||||||
mkdir -p "${{ env.WORKSPACE_DIR }}"
|
|
||||||
|
|
||||||
git clone \
|
|
||||||
https://oauth2:${{ env.ACCESS_TOKEN }}@${GITHUB_SERVER_URL#https://}/${{ github.repository }}.git \
|
|
||||||
"$REPO_DIR"
|
|
||||||
|
|
||||||
if [ $? -ne 0 ]; then
|
|
||||||
echo "❌ 克隆失败"
|
|
||||||
exit 1
|
|
||||||
fi
|
|
||||||
|
|
||||||
cd "$REPO_DIR"
|
|
||||||
echo "✓ 仓库已克隆"
|
|
||||||
fi
|
|
||||||
|
|
||||||
# 切换到目标分支
|
|
||||||
echo "🏷️ 切换到分支: ${{ github.ref_name }}"
|
|
||||||
git checkout -f ${{ github.ref_name }}
|
|
||||||
|
|
||||||
echo "✅ 仓库准备成功"
|
|
||||||
echo ""
|
|
||||||
|
|
||||||
# 配置 Git 用户信息
|
|
||||||
echo "⚙️ 配置 Git 用户信息..."
|
|
||||||
git config user.name "${{ env.GIT_USER_NAME }}"
|
|
||||||
git config user.email "${{ env.GIT_USER_EMAIL }}"
|
|
||||||
|
|
||||||
# 显示仓库信息
|
|
||||||
echo "📊 仓库信息:"
|
|
||||||
echo " - 当前分支: $(git branch --show-current)"
|
|
||||||
echo " - 最新提交: $(git log -1 --oneline)"
|
|
||||||
echo " - 远程地址: $(git remote get-url origin | sed 's/oauth2:.*@/oauth2:***@/')"
|
|
||||||
echo ""
|
|
||||||
|
|
||||||
# 导出环境变量
|
|
||||||
echo "REPO_DIR=$REPO_DIR" >> $GITHUB_ENV
|
|
||||||
echo "REPO_NAME=$REPO_NAME" >> $GITHUB_ENV
|
|
||||||
|
|
||||||
echo "✅ 主仓库准备完成"
|
|
||||||
echo "======================================"
|
|
||||||
echo ""
|
|
||||||
|
|
||||||
- name: ⚙️ 准备统计分支
|
|
||||||
id: prepare_stats_branch
|
|
||||||
run: |
|
|
||||||
echo "======================================"
|
|
||||||
echo "🔧 准备统计分支"
|
|
||||||
echo "======================================"
|
|
||||||
|
|
||||||
cd "${{ env.REPO_DIR }}"
|
|
||||||
|
|
||||||
# 获取所有远程分支
|
|
||||||
git fetch origin --prune
|
|
||||||
|
|
||||||
# 检查统计分支是否存在
|
|
||||||
if git ls-remote --heads origin ${{ env.BADGE_BRANCH }} | grep -q ${{ env.BADGE_BRANCH }}; then
|
|
||||||
echo "✅ 统计分支 '${{ env.BADGE_BRANCH }}' 已存在"
|
|
||||||
|
|
||||||
# 检出统计分支
|
|
||||||
git fetch origin ${{ env.BADGE_BRANCH }}:${{ env.BADGE_BRANCH }}
|
|
||||||
git checkout ${{ env.BADGE_BRANCH }}
|
|
||||||
|
|
||||||
echo "📂 当前分支内容:"
|
|
||||||
ls -la
|
|
||||||
|
|
||||||
# 返回主分支
|
|
||||||
git checkout ${{ github.ref_name }}
|
|
||||||
|
|
||||||
echo "branch_exists=true" >> $GITHUB_OUTPUT
|
|
||||||
else
|
|
||||||
echo "🆕 统计分支 '${{ env.BADGE_BRANCH }}' 不存在,将创建"
|
|
||||||
echo "branch_exists=false" >> $GITHUB_OUTPUT
|
|
||||||
fi
|
|
||||||
|
|
||||||
echo "======================================"
|
|
||||||
echo ""
|
|
||||||
|
|
||||||
- name: 🌱 创建统计分支
|
|
||||||
if: steps.prepare_stats_branch.outputs.branch_exists == 'false'
|
|
||||||
run: |
|
|
||||||
echo "======================================"
|
|
||||||
echo "🆕 创建新的统计分支"
|
|
||||||
echo "======================================"
|
|
||||||
|
|
||||||
cd "${{ env.REPO_DIR }}"
|
|
||||||
|
|
||||||
# 🔴 安全检查:确认在正确的目录
|
|
||||||
echo "🔍 当前目录: $(pwd)"
|
|
||||||
echo "🔍 .git 状态: $([ -d .git ] && echo '✓ 存在' || echo '✗ 不存在')"
|
|
||||||
|
|
||||||
if [ ! -d .git ]; then
|
|
||||||
echo "❌ 错误:.git 目录不存在,无法创建分支"
|
|
||||||
exit 1
|
|
||||||
fi
|
|
||||||
|
|
||||||
# 创建孤立分支(orphan branch)
|
|
||||||
# 📝 说明:孤立分支与主分支没有共同的提交历史,适合存储独立的数据
|
|
||||||
echo "🌱 创建孤立分支..."
|
|
||||||
git checkout --orphan ${{ env.BADGE_BRANCH }}
|
|
||||||
|
|
||||||
# 🔴 再次确认 .git 仍然存在
|
|
||||||
if [ ! -d .git ]; then
|
|
||||||
echo "❌ 严重错误:.git 目录丢失!"
|
|
||||||
exit 1
|
|
||||||
fi
|
|
||||||
|
|
||||||
# 🧹 清空所有文件(但保留 .git 目录)
|
|
||||||
# ❓ 为什么要删除?
|
|
||||||
# - stats 分支是独立的数据存储分支,不需要主分支的代码文件
|
|
||||||
# - 只需要存储徽章 JSON 文件和统计报告
|
|
||||||
# - 保持分支干净,避免混淆
|
|
||||||
#
|
|
||||||
# ✅ 能否复用仓库?
|
|
||||||
# - 可以!.git 目录包含所有分支的完整信息
|
|
||||||
# - 切换到主分支时,主分支的文件会恢复
|
|
||||||
# - 各分支独立,互不影响
|
|
||||||
|
|
||||||
echo "🧹 清理文件(保护 .git 和 .gitea)..."
|
|
||||||
|
|
||||||
# 步骤 1: 使用 git rm 清理已跟踪的文件
|
|
||||||
git rm -rf . 2>/dev/null || true
|
|
||||||
|
|
||||||
# 步骤 2: 安全地删除剩余文件
|
|
||||||
# 使用更严格的保护,确保不会误删 .git
|
|
||||||
echo "🔍 查找需要删除的文件..."
|
|
||||||
find . -maxdepth 1 -type f ! -name '.gitignore' -delete 2>/dev/null || true
|
|
||||||
find . -maxdepth 1 -type d ! -name '.' ! -name '..' ! -name '.git' ! -name '.gitea' -exec rm -rf {} + 2>/dev/null || true
|
|
||||||
|
|
||||||
# 🔴 最终检查:确认 .git 仍然完好
|
|
||||||
echo "🔍 最终检查..."
|
|
||||||
if [ ! -d .git ]; then
|
|
||||||
echo "❌ 致命错误:.git 目录在清理过程中被删除!"
|
|
||||||
echo "这不应该发生,请检查 find 命令"
|
|
||||||
exit 1
|
|
||||||
fi
|
|
||||||
echo "✓ .git 目录完好"
|
|
||||||
echo "✓ 文件清理完成"
|
|
||||||
echo ""
|
|
||||||
|
|
||||||
# 创建初始 README
|
|
||||||
cat > README.md << 'EOF'
|
|
||||||
# 📊 代码统计徽章数据
|
|
||||||
|
|
||||||
> 此分支由 GitHub Actions 自动生成和维护
|
|
||||||
>
|
|
||||||
> ⚠️ **请勿手动修改此分支的内容!**
|
|
||||||
|
|
||||||
## 📁 目录结构
|
|
||||||
|
|
||||||
```
|
|
||||||
badges/
|
|
||||||
├── total-lines.json # 总代码行数徽章
|
|
||||||
├── total-files.json # 总文件数徽章
|
|
||||||
├── {language}-lines.json # 各语言代码行数徽章
|
|
||||||
├── {language}-files.json # 各语言文件数徽章
|
|
||||||
└── README.md # 详细统计报告
|
|
||||||
```
|
|
||||||
|
|
||||||
## 🔄 更新机制
|
|
||||||
|
|
||||||
- ✅ 每次 push 到主分支时自动更新
|
|
||||||
- ✅ 可通过 workflow_dispatch 手动触发
|
|
||||||
|
|
||||||
## 📊 数据来源
|
|
||||||
|
|
||||||
所有统计数据基于主分支的最新代码自动生成。
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
*🤖 由 GitHub Actions 自动维护*
|
|
||||||
EOF
|
|
||||||
|
|
||||||
# 创建徽章目录
|
|
||||||
mkdir -p ${{ env.BADGE_DIR }}
|
|
||||||
|
|
||||||
# 添加并提交
|
|
||||||
git add .
|
|
||||||
git commit -m "chore: 初始化统计分支"
|
|
||||||
|
|
||||||
# 推送到远程
|
|
||||||
git push https://oauth2:${{ env.ACCESS_TOKEN }}@${GITHUB_SERVER_URL#https://}/${{ github.repository }}.git ${{ env.BADGE_BRANCH }}
|
|
||||||
|
|
||||||
echo "✅ 统计分支创建完成"
|
|
||||||
echo "======================================"
|
|
||||||
echo ""
|
|
||||||
|
|
||||||
# 🔴 重要:返回主分支,后续统计需要在主分支进行
|
|
||||||
echo "🔄 切换回主分支: ${{ github.ref_name }}"
|
|
||||||
git checkout ${{ github.ref_name }}
|
|
||||||
echo "✓ 已切换回主分支"
|
|
||||||
echo ""
|
|
||||||
|
|
||||||
- name: 📊 统计总代码量
|
|
||||||
id: total
|
|
||||||
run: |
|
|
||||||
echo "======================================"
|
|
||||||
echo "📊 统计总代码量"
|
|
||||||
echo "======================================"
|
|
||||||
|
|
||||||
cd "${{ env.REPO_DIR }}"
|
|
||||||
|
|
||||||
# 🔴 重要:确保在主分支进行统计(不要统计 stats 分支)
|
|
||||||
echo "🔍 确认当前分支..."
|
|
||||||
CURRENT_BRANCH=$(git branch --show-current)
|
|
||||||
if [ "$CURRENT_BRANCH" != "${{ github.ref_name }}" ]; then
|
|
||||||
echo "⚠️ 当前在分支: $CURRENT_BRANCH,切换到主分支: ${{ github.ref_name }}"
|
|
||||||
git checkout ${{ github.ref_name }}
|
|
||||||
else
|
|
||||||
echo "✓ 已在主分支: $CURRENT_BRANCH"
|
|
||||||
fi
|
|
||||||
echo ""
|
|
||||||
|
|
||||||
# 构建排除参数
|
|
||||||
EXCLUDE_PARAMS=""
|
|
||||||
IFS=',' read -ra EXCLUDES <<< "${{ env.EXCLUDE_DIRS }}"
|
|
||||||
for dir in "${EXCLUDES[@]}"; do
|
|
||||||
EXCLUDE_PARAMS="$EXCLUDE_PARAMS -not -path '*/$dir/*'"
|
|
||||||
done
|
|
||||||
|
|
||||||
# 获取所有文件
|
|
||||||
echo "🔍 扫描文件..."
|
|
||||||
|
|
||||||
# 统计总文件数
|
|
||||||
TOTAL_FILES=$(eval "find . -type f $EXCLUDE_PARAMS" | wc -l)
|
|
||||||
|
|
||||||
# 统计总代码行数(排除空行)
|
|
||||||
TOTAL_CODE=$(eval "find . -type f $EXCLUDE_PARAMS -exec grep -cHv '^[[:space:]]*$' {} + 2>/dev/null" | awk -F: '{sum+=$2} END {print sum+0}')
|
|
||||||
|
|
||||||
echo "📊 统计结果:"
|
|
||||||
echo " - 总文件数: $TOTAL_FILES"
|
|
||||||
echo " - 总代码行: $TOTAL_CODE"
|
|
||||||
|
|
||||||
# 格式化数字(添加千分位)
|
|
||||||
FORMATTED_FILES=$(printf "%'d" $TOTAL_FILES)
|
|
||||||
FORMATTED_CODE=$(printf "%'d" $TOTAL_CODE)
|
|
||||||
|
|
||||||
# 输出到环境变量
|
|
||||||
echo "total_files=$TOTAL_FILES" >> $GITHUB_OUTPUT
|
|
||||||
echo "total_code=$TOTAL_CODE" >> $GITHUB_OUTPUT
|
|
||||||
echo "formatted_files=$FORMATTED_FILES" >> $GITHUB_OUTPUT
|
|
||||||
echo "formatted_code=$FORMATTED_CODE" >> $GITHUB_OUTPUT
|
|
||||||
|
|
||||||
echo "======================================"
|
|
||||||
echo ""
|
|
||||||
|
|
||||||
- name: 📋 统计各语言代码量
|
|
||||||
id: languages
|
|
||||||
run: |
|
|
||||||
echo "======================================"
|
|
||||||
echo "📋 统计各语言代码量"
|
|
||||||
echo "======================================"
|
|
||||||
|
|
||||||
cd "${{ env.REPO_DIR }}"
|
|
||||||
|
|
||||||
# 🔴 重要:确保在主分支进行统计
|
|
||||||
echo "🔍 确认当前分支..."
|
|
||||||
CURRENT_BRANCH=$(git branch --show-current)
|
|
||||||
if [ "$CURRENT_BRANCH" != "${{ github.ref_name }}" ]; then
|
|
||||||
echo "⚠️ 当前在分支: $CURRENT_BRANCH,切换到主分支: ${{ github.ref_name }}"
|
|
||||||
git checkout ${{ github.ref_name }}
|
|
||||||
else
|
|
||||||
echo "✓ 已在主分支: $CURRENT_BRANCH"
|
|
||||||
fi
|
|
||||||
echo ""
|
|
||||||
|
|
||||||
# 创建临时目录
|
|
||||||
mkdir -p /tmp/lang_stats
|
|
||||||
|
|
||||||
# 构建排除参数
|
|
||||||
EXCLUDE_PARAMS=""
|
|
||||||
IFS=',' read -ra EXCLUDES <<< "${{ env.EXCLUDE_DIRS }}"
|
|
||||||
for dir in "${EXCLUDES[@]}"; do
|
|
||||||
EXCLUDE_PARAMS="$EXCLUDE_PARAMS -not -path '*/$dir/*'"
|
|
||||||
done
|
|
||||||
|
|
||||||
LANGUAGE_COUNT=0
|
|
||||||
|
|
||||||
# 读取语言配置并统计
|
|
||||||
echo "${{ env.LANGUAGE_GROUPS }}" | while IFS= read -r line; do
|
|
||||||
[ -z "$line" ] && continue
|
|
||||||
|
|
||||||
# 解析配置行: 组名:后缀列表:显示名称:颜色:图标
|
|
||||||
IFS=':' read -r lang_id extensions display_name color icon <<< "$line"
|
|
||||||
|
|
||||||
echo "🔍 统计 $display_name..."
|
|
||||||
|
|
||||||
# 构建扩展名查找条件
|
|
||||||
FIND_CONDITIONS=""
|
|
||||||
IFS=',' read -ra EXTS <<< "$extensions"
|
|
||||||
for ext in "${EXTS[@]}"; do
|
|
||||||
if [ -z "$FIND_CONDITIONS" ]; then
|
|
||||||
FIND_CONDITIONS="-name '*.$ext'"
|
|
||||||
else
|
|
||||||
FIND_CONDITIONS="$FIND_CONDITIONS -o -name '*.$ext'"
|
|
||||||
fi
|
|
||||||
done
|
|
||||||
|
|
||||||
# 统计文件数
|
|
||||||
FILE_COUNT=$(eval "find . -type f \( $FIND_CONDITIONS \) $EXCLUDE_PARAMS" | wc -l)
|
|
||||||
|
|
||||||
if [ "$FILE_COUNT" -gt 0 ]; then
|
|
||||||
# 统计代码行数
|
|
||||||
CODE_LINES=$(eval "find . -type f \( $FIND_CONDITIONS \) $EXCLUDE_PARAMS -exec grep -cHv '^[[:space:]]*$' {} + 2>/dev/null" | awk -F: '{sum+=$2} END {print sum+0}')
|
|
||||||
|
|
||||||
# 只保存超过阈值的语言
|
|
||||||
if [ "$CODE_LINES" -ge "${{ env.MIN_LINES_THRESHOLD }}" ]; then
|
|
||||||
FORMATTED_LINES=$(printf "%'d" $CODE_LINES)
|
|
||||||
|
|
||||||
echo " - 文件数: $FILE_COUNT"
|
|
||||||
echo " - 代码行: $FORMATTED_LINES"
|
|
||||||
|
|
||||||
# 保存到临时文件
|
|
||||||
echo "$lang_id|$display_name|$CODE_LINES|$FORMATTED_LINES|$FILE_COUNT|$color|$icon" >> /tmp/lang_stats/${lang_id}.txt
|
|
||||||
LANGUAGE_COUNT=$((LANGUAGE_COUNT + 1))
|
|
||||||
else
|
|
||||||
echo " ⏩ 跳过(少于 ${{ env.MIN_LINES_THRESHOLD }} 行)"
|
|
||||||
fi
|
|
||||||
else
|
|
||||||
echo " ⏩ 未找到文件"
|
|
||||||
fi
|
|
||||||
echo ""
|
|
||||||
done
|
|
||||||
|
|
||||||
# 汇总所有语言数据
|
|
||||||
cat /tmp/lang_stats/*.txt 2>/dev/null | sort -t'|' -k3 -nr > /tmp/lang_summary.txt || touch /tmp/lang_summary.txt
|
|
||||||
|
|
||||||
# 计算实际找到的语言数
|
|
||||||
ACTUAL_COUNT=$(wc -l < /tmp/lang_summary.txt)
|
|
||||||
|
|
||||||
echo "language_count=$ACTUAL_COUNT" >> $GITHUB_OUTPUT
|
|
||||||
|
|
||||||
echo "✅ 找到 $ACTUAL_COUNT 种语言"
|
|
||||||
echo "======================================"
|
|
||||||
echo ""
|
|
||||||
|
|
||||||
- name: 🎨 生成徽章数据
|
|
||||||
id: generate_badges
|
|
||||||
run: |
|
|
||||||
echo "======================================"
|
|
||||||
echo "🎨 生成徽章数据"
|
|
||||||
echo "======================================"
|
|
||||||
|
|
||||||
cd "${{ env.REPO_DIR }}"
|
|
||||||
|
|
||||||
# 确保在统计分支
|
|
||||||
git checkout ${{ env.BADGE_BRANCH }}
|
|
||||||
|
|
||||||
# 确保徽章目录存在
|
|
||||||
mkdir -p ${{ env.BADGE_DIR }}
|
|
||||||
|
|
||||||
GENERATED_COUNT=0
|
|
||||||
|
|
||||||
# 生成总代码行数徽章
|
|
||||||
echo "📊 生成总代码行数徽章..."
|
|
||||||
cat > ${{ env.BADGE_DIR }}/total-lines.json << EOF
|
|
||||||
{
|
|
||||||
"schemaVersion": 1,
|
|
||||||
"label": "代码",
|
|
||||||
"message": "${{ steps.total.outputs.formatted_code }} 行",
|
|
||||||
"color": "${{ env.COLOR_TOTAL }}",
|
|
||||||
"style": "${{ env.BADGE_STYLE }}"
|
|
||||||
}
|
|
||||||
EOF
|
|
||||||
GENERATED_COUNT=$((GENERATED_COUNT + 1))
|
|
||||||
|
|
||||||
# 生成总文件数徽章
|
|
||||||
echo "📁 生成总文件数徽章..."
|
|
||||||
cat > ${{ env.BADGE_DIR }}/total-files.json << EOF
|
|
||||||
{
|
|
||||||
"schemaVersion": 1,
|
|
||||||
"label": "文件",
|
|
||||||
"message": "${{ steps.total.outputs.formatted_files }} 个",
|
|
||||||
"color": "${{ env.COLOR_FILES }}",
|
|
||||||
"style": "${{ env.BADGE_STYLE }}"
|
|
||||||
}
|
|
||||||
EOF
|
|
||||||
GENERATED_COUNT=$((GENERATED_COUNT + 1))
|
|
||||||
|
|
||||||
# 生成各语言徽章
|
|
||||||
if [ -f /tmp/lang_summary.txt ] && [ -s /tmp/lang_summary.txt ]; then
|
|
||||||
echo ""
|
|
||||||
echo "🌐 生成语言徽章..."
|
|
||||||
|
|
||||||
while IFS='|' read -r lang_id display_name code_lines formatted_lines file_count color icon; do
|
|
||||||
[ -z "$lang_id" ] && continue
|
|
||||||
|
|
||||||
echo " - $display_name"
|
|
||||||
|
|
||||||
# 生成代码行数徽章(使用 heredoc)
|
|
||||||
if [ -n "$icon" ]; then
|
|
||||||
# 带图标的徽章
|
|
||||||
cat > ${{ env.BADGE_DIR }}/${lang_id}-lines.json << EOFJSON
|
|
||||||
{
|
|
||||||
"schemaVersion": 1,
|
|
||||||
"label": "$display_name",
|
|
||||||
"message": "$formatted_lines 行",
|
|
||||||
"color": "$color",
|
|
||||||
"style": "${{ env.BADGE_STYLE }}",
|
|
||||||
"namedLogo": "$icon"
|
|
||||||
}
|
|
||||||
EOFJSON
|
|
||||||
else
|
|
||||||
# 不带图标的徽章
|
|
||||||
cat > ${{ env.BADGE_DIR }}/${lang_id}-lines.json << EOFJSON
|
|
||||||
{
|
|
||||||
"schemaVersion": 1,
|
|
||||||
"label": "$display_name",
|
|
||||||
"message": "$formatted_lines 行",
|
|
||||||
"color": "$color",
|
|
||||||
"style": "${{ env.BADGE_STYLE }}"
|
|
||||||
}
|
|
||||||
EOFJSON
|
|
||||||
fi
|
|
||||||
GENERATED_COUNT=$((GENERATED_COUNT + 1))
|
|
||||||
|
|
||||||
# 生成文件数徽章
|
|
||||||
cat > ${{ env.BADGE_DIR }}/${lang_id}-files.json << EOFJSON
|
|
||||||
{
|
|
||||||
"schemaVersion": 1,
|
|
||||||
"label": "$display_name 文件",
|
|
||||||
"message": "$file_count 个",
|
|
||||||
"color": "$color",
|
|
||||||
"style": "${{ env.BADGE_STYLE }}"
|
|
||||||
}
|
|
||||||
EOFJSON
|
|
||||||
GENERATED_COUNT=$((GENERATED_COUNT + 1))
|
|
||||||
done < /tmp/lang_summary.txt
|
|
||||||
fi
|
|
||||||
|
|
||||||
echo ""
|
|
||||||
echo "generated_count=$GENERATED_COUNT" >> $GITHUB_OUTPUT
|
|
||||||
echo "✅ 已生成 $GENERATED_COUNT 个徽章"
|
|
||||||
echo "======================================"
|
|
||||||
echo ""
|
|
||||||
|
|
||||||
- name: 📝 生成详细统计报告
|
|
||||||
if: env.GENERATE_DETAILED_REPORT == 'true'
|
|
||||||
run: |
|
|
||||||
echo "======================================"
|
|
||||||
echo "📝 生成详细统计报告"
|
|
||||||
echo "======================================"
|
|
||||||
|
|
||||||
cd "${{ env.REPO_DIR }}"
|
|
||||||
|
|
||||||
# 确保在统计分支
|
|
||||||
git checkout ${{ env.BADGE_BRANCH }}
|
|
||||||
|
|
||||||
# 生成 README
|
|
||||||
cat > ${{ env.BADGE_DIR }}/README.md << 'EOFMD'
|
|
||||||
# 📊 代码统计详细报告
|
|
||||||
|
|
||||||
> 🤖 由 GitHub Actions 自动生成
|
|
||||||
>
|
|
||||||
> 📅 更新时间: TIMESTAMP_PLACEHOLDER
|
|
||||||
|
|
||||||
## 📈 总体统计
|
|
||||||
|
|
||||||
| 统计项 | 数值 | 徽章 |
|
|
||||||
|--------|------|------|
|
|
||||||
| 💻 总代码行数 | **TOTAL_CODE_PLACEHOLDER** 行 |  |
|
|
||||||
| 📁 总文件数 | **TOTAL_FILES_PLACEHOLDER** 个 |  |
|
|
||||||
| 🌐 语言种类 | **LANG_COUNT_PLACEHOLDER** 种 | - |
|
|
||||||
|
|
||||||
## 🌈 语言分布
|
|
||||||
|
|
||||||
EOFMD
|
|
||||||
|
|
||||||
# 替换占位符
|
|
||||||
sed -i "s|TIMESTAMP_PLACEHOLDER|$(date -u '+%Y-%m-%d %H:%M:%S UTC')|g" ${{ env.BADGE_DIR }}/README.md
|
|
||||||
sed -i "s|TOTAL_CODE_PLACEHOLDER|${{ steps.total.outputs.formatted_code }}|g" ${{ env.BADGE_DIR }}/README.md
|
|
||||||
sed -i "s|TOTAL_FILES_PLACEHOLDER|${{ steps.total.outputs.formatted_files }}|g" ${{ env.BADGE_DIR }}/README.md
|
|
||||||
sed -i "s|LANG_COUNT_PLACEHOLDER|${{ steps.languages.outputs.language_count }}|g" ${{ env.BADGE_DIR }}/README.md
|
|
||||||
sed -i "s|REPO_PLACEHOLDER|${{ github.repository }}|g" ${{ env.BADGE_DIR }}/README.md
|
|
||||||
sed -i "s|BRANCH_PLACEHOLDER|${{ env.BADGE_BRANCH }}|g" ${{ env.BADGE_DIR }}/README.md
|
|
||||||
sed -i "s|BADGE_DIR_PLACEHOLDER|${{ env.BADGE_DIR }}|g" ${{ env.BADGE_DIR }}/README.md
|
|
||||||
sed -i "s|RAW_URL_PLACEHOLDER|${{ env.RAW_URL_BASE }}|g" ${{ env.BADGE_DIR }}/README.md
|
|
||||||
|
|
||||||
# 添加语言统计表格
|
|
||||||
if [ -f /tmp/lang_summary.txt ] && [ -s /tmp/lang_summary.txt ]; then
|
|
||||||
cat >> ${{ env.BADGE_DIR }}/README.md << 'EOFTABLE'
|
|
||||||
| 语言 | 代码行数 | 文件数 | 占比 | 徽章 |
|
|
||||||
|------|----------|--------|------|------|
|
|
||||||
EOFTABLE
|
|
||||||
|
|
||||||
TOTAL_CODE=${{ steps.total.outputs.total_code }}
|
|
||||||
while IFS='|' read -r lang_id display_name code_lines formatted_lines file_count color icon; do
|
|
||||||
[ -z "$lang_id" ] && continue
|
|
||||||
|
|
||||||
if [ "$TOTAL_CODE" -gt 0 ]; then
|
|
||||||
PERCENT=$(echo "scale=1; $code_lines * 100 / $TOTAL_CODE" | bc)
|
|
||||||
else
|
|
||||||
PERCENT="0.0"
|
|
||||||
fi
|
|
||||||
|
|
||||||
cat >> ${{ env.BADGE_DIR }}/README.md << EOFLANG
|
|
||||||
| **${display_name}** | ${formatted_lines} | ${file_count} | ${PERCENT}% |  |
|
|
||||||
EOFLANG
|
|
||||||
done < /tmp/lang_summary.txt
|
|
||||||
fi
|
|
||||||
|
|
||||||
# 添加配置说明
|
|
||||||
cat >> ${{ env.BADGE_DIR }}/README.md << EOFMD
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## ⚙️ 配置说明
|
|
||||||
|
|
||||||
### Token 配置
|
|
||||||
|
|
||||||
- 当前使用: **${{ steps.validate_token.outputs.token_type }}**
|
|
||||||
- 推荐配置自定义 `STATS_TOKEN` 以获得更好的权限控制
|
|
||||||
|
|
||||||
### 排除规则
|
|
||||||
|
|
||||||
当前排除的目录:
|
|
||||||
```
|
|
||||||
${{ env.EXCLUDE_DIRS }}
|
|
||||||
```
|
|
||||||
|
|
||||||
### 阈值设置
|
|
||||||
|
|
||||||
- 最小代码行数阈值: **${{ env.MIN_LINES_THRESHOLD }}** 行
|
|
||||||
- 低于此阈值的语言将不会生成徽章
|
|
||||||
|
|
||||||
### 徽章样式
|
|
||||||
|
|
||||||
- 当前样式: **${{ env.BADGE_STYLE }}**
|
|
||||||
- 可选样式: flat, flat-square, plastic, for-the-badge, social
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
<div align="center">
|
|
||||||
|
|
||||||
*📊 由 [GitHub Actions](${GITHUB_SERVER_URL}/${GITHUB_REPOSITORY}/actions) 自动生成和更新*
|
|
||||||
|
|
||||||
*🤖 生成时间: $(date -u '+%Y-%m-%d %H:%M:%S UTC')*
|
|
||||||
|
|
||||||
</div>
|
|
||||||
EOFMD
|
|
||||||
|
|
||||||
echo "✅ 详细统计报告已生成"
|
|
||||||
echo "======================================"
|
|
||||||
echo ""
|
|
||||||
|
|
||||||
- name: 📤 提交并推送到统计分支
|
|
||||||
id: commit
|
|
||||||
run: |
|
|
||||||
echo "======================================"
|
|
||||||
echo "📤 提交并推送更新"
|
|
||||||
echo "======================================"
|
|
||||||
|
|
||||||
cd "${{ env.REPO_DIR }}"
|
|
||||||
|
|
||||||
# 确保在统计分支
|
|
||||||
git checkout ${{ env.BADGE_BRANCH }}
|
|
||||||
|
|
||||||
# 配置 Git 用户信息
|
|
||||||
git config user.name "${{ env.GIT_USER_NAME }}"
|
|
||||||
git config user.email "${{ env.GIT_USER_EMAIL }}"
|
|
||||||
|
|
||||||
# 添加所有更改
|
|
||||||
git add ${{ env.BADGE_DIR }}/
|
|
||||||
|
|
||||||
# 检查是否有变更
|
|
||||||
if git diff --staged --quiet; then
|
|
||||||
echo "📌 没有变更,跳过提交"
|
|
||||||
echo "changed=false" >> $GITHUB_OUTPUT
|
|
||||||
echo ""
|
|
||||||
else
|
|
||||||
echo "📝 准备提交..."
|
|
||||||
|
|
||||||
# 生成提交信息
|
|
||||||
COMMIT_MSG="chore: 更新代码统计 [$(date '+%Y-%m-%d %H:%M')]
|
|
||||||
|
|
||||||
📊 统计摘要:
|
|
||||||
- 总代码: ${{ steps.total.outputs.formatted_code }} 行
|
|
||||||
- 总文件: ${{ steps.total.outputs.formatted_files }} 个
|
|
||||||
- 语言数: ${{ steps.languages.outputs.language_count }} 种
|
|
||||||
- 徽章数: ${{ steps.generate_badges.outputs.generated_count }} 个
|
|
||||||
|
|
||||||
🔗 触发提交: ${GITHUB_SHA:0:7}
|
|
||||||
🤖 由 GitHub Actions 自动生成"
|
|
||||||
|
|
||||||
git commit -m "$COMMIT_MSG"
|
|
||||||
|
|
||||||
echo "📤 推送到远程 ${{ env.BADGE_BRANCH }} 分支..."
|
|
||||||
|
|
||||||
# 推送到远程(使用 TOKEN)
|
|
||||||
git push https://oauth2:${{ env.ACCESS_TOKEN }}@${GITHUB_SERVER_URL#https://}/${{ github.repository }}.git ${{ env.BADGE_BRANCH }}
|
|
||||||
|
|
||||||
if [ $? -eq 0 ]; then
|
|
||||||
echo "✅ 推送成功"
|
|
||||||
echo "changed=true" >> $GITHUB_OUTPUT
|
|
||||||
else
|
|
||||||
echo "❌ 推送失败"
|
|
||||||
exit 1
|
|
||||||
fi
|
|
||||||
fi
|
|
||||||
|
|
||||||
echo "======================================"
|
|
||||||
echo ""
|
|
||||||
|
|
||||||
- name: 📈 生成 Workflow Summary
|
|
||||||
if: env.OUTPUT_TO_SUMMARY == 'true' && success()
|
|
||||||
run: |
|
|
||||||
cat >> $GITHUB_STEP_SUMMARY << 'EOFSUMMARY'
|
|
||||||
# 📊 代码统计完成
|
|
||||||
|
|
||||||
## ✅ 执行结果
|
|
||||||
|
|
||||||
| 项目 | 结果 |
|
|
||||||
|------|------|
|
|
||||||
| 🔐 Token 类型 | **${{ steps.validate_token.outputs.token_type }}** |
|
|
||||||
| 🔧 依赖检查 | ${{ steps.check_deps.outputs.installed == 'true' && '✅ 已安装缺失工具' || '✅ 所有工具就绪' }} |
|
|
||||||
| 📊 总代码行数 | **${{ steps.total.outputs.formatted_code }}** 行 |
|
|
||||||
| 📁 总文件数 | **${{ steps.total.outputs.formatted_files }}** 个 |
|
|
||||||
| 🌐 语言种类 | **${{ steps.languages.outputs.language_count }}** 种 |
|
|
||||||
| 🎨 生成徽章 | **${{ steps.generate_badges.outputs.generated_count }}** 个 |
|
|
||||||
| 📤 提交状态 | ${{ steps.commit.outputs.changed == 'true' && '✅ 已更新' || '📌 无变更' }} |
|
|
||||||
|
|
||||||
## 🔗 快速链接
|
|
||||||
|
|
||||||
- 📊 [查看详细统计报告](${GITHUB_SERVER_URL}/${GITHUB_REPOSITORY}/src/branch/${{ env.BADGE_BRANCH }}/${{ env.BADGE_DIR }}/README.md)
|
|
||||||
- 🎨 [浏览徽章文件](${GITHUB_SERVER_URL}/${GITHUB_REPOSITORY}/src/branch/${{ env.BADGE_BRANCH }}/${{ env.BADGE_DIR }})
|
|
||||||
- 🔧 [查看 Workflow 配置](${GITHUB_SERVER_URL}/${GITHUB_REPOSITORY}/blob/${{ github.ref_name }}/.github/workflows/update_stats_badge.yaml)
|
|
||||||
|
|
||||||
## 📝 语言分布
|
|
||||||
|
|
||||||
EOFSUMMARY
|
|
||||||
|
|
||||||
# 添加语言分布表格
|
|
||||||
if [ -f /tmp/lang_summary.txt ] && [ -s /tmp/lang_summary.txt ]; then
|
|
||||||
cat >> $GITHUB_STEP_SUMMARY << 'EOFTABLE'
|
|
||||||
| 语言 | 代码行数 | 文件数 | 占比 |
|
|
||||||
|------|----------|--------|------|
|
|
||||||
EOFTABLE
|
|
||||||
|
|
||||||
TOTAL_CODE=${{ steps.total.outputs.total_code }}
|
|
||||||
sort -t'|' -k3 -nr /tmp/lang_summary.txt | head -10 | while IFS='|' read -r lang_id display_name code_lines formatted_lines file_count color icon; do
|
|
||||||
[ -z "$lang_id" ] && continue
|
|
||||||
|
|
||||||
if [ "$TOTAL_CODE" -gt 0 ]; then
|
|
||||||
PERCENT=$(echo "scale=1; $code_lines * 100 / $TOTAL_CODE" | bc)
|
|
||||||
else
|
|
||||||
PERCENT="0.0"
|
|
||||||
fi
|
|
||||||
|
|
||||||
echo "| **${display_name}** | ${formatted_lines} | ${file_count} | ${PERCENT}% |" >> $GITHUB_STEP_SUMMARY
|
|
||||||
done
|
|
||||||
else
|
|
||||||
echo "" >> $GITHUB_STEP_SUMMARY
|
|
||||||
echo "> ⚠️ 未找到符合条件的语言文件" >> $GITHUB_STEP_SUMMARY
|
|
||||||
fi
|
|
||||||
|
|
||||||
# 添加工作区信息
|
|
||||||
cat >> $GITHUB_STEP_SUMMARY << 'EOFWS'
|
|
||||||
|
|
||||||
## 📂 工作区信息
|
|
||||||
|
|
||||||
| 项目 | 值 |
|
|
||||||
|------|-----|
|
|
||||||
| 📁 仓库名称 | `${{ env.REPO_NAME }}` |
|
|
||||||
| 📍 工作目录 | `${{ env.REPO_DIR }}` |
|
|
||||||
| 🌿 统计分支 | `${{ env.BADGE_BRANCH }}` |
|
|
||||||
| 📂 徽章目录 | `${{ env.BADGE_DIR }}` |
|
|
||||||
|
|
||||||
EOFWS
|
|
||||||
|
|
||||||
echo "" >> $GITHUB_STEP_SUMMARY
|
|
||||||
echo "---" >> $GITHUB_STEP_SUMMARY
|
|
||||||
echo "" >> $GITHUB_STEP_SUMMARY
|
|
||||||
echo "*⏱️ 执行时间: $(date '+%Y-%m-%d %H:%M:%S %Z')*" >> $GITHUB_STEP_SUMMARY
|
|
||||||
|
|
||||||
- name: 💡 输出使用说明
|
|
||||||
if: success()
|
|
||||||
run: |
|
|
||||||
echo "================================================"
|
|
||||||
echo "✅ 代码统计任务执行完成!"
|
|
||||||
echo "================================================"
|
|
||||||
echo ""
|
|
||||||
|
|
||||||
echo "🔐 Token 信息:"
|
|
||||||
echo " - 使用: ${{ steps.validate_token.outputs.token_type }}"
|
|
||||||
echo ""
|
|
||||||
|
|
||||||
# 根据实际结果输出
|
|
||||||
if [ "${{ steps.commit.outputs.changed }}" == "true" ]; then
|
|
||||||
echo "📊 统计结果:"
|
|
||||||
echo " - 总代码: ${{ steps.total.outputs.formatted_code }} 行"
|
|
||||||
echo " - 总文件: ${{ steps.total.outputs.formatted_files }} 个"
|
|
||||||
echo " - 语言数: ${{ steps.languages.outputs.language_count }} 种"
|
|
||||||
echo " - 徽章数: ${{ steps.generate_badges.outputs.generated_count }} 个"
|
|
||||||
echo ""
|
|
||||||
echo "📤 已推送更新到分支: ${{ env.BADGE_BRANCH }}"
|
|
||||||
else
|
|
||||||
echo "📌 统计数据无变更,未产生新的提交"
|
|
||||||
fi
|
|
||||||
|
|
||||||
echo ""
|
|
||||||
echo "📂 工作区信息:"
|
|
||||||
echo " - 仓库目录: ${{ env.REPO_DIR }}"
|
|
||||||
echo " - 统计分支: ${{ env.BADGE_BRANCH }}"
|
|
||||||
echo ""
|
|
||||||
echo "🔗 查看详细报告:"
|
|
||||||
echo " ${GITHUB_SERVER_URL}/${GITHUB_REPOSITORY}/src/branch/${{ env.BADGE_BRANCH }}/${{ env.BADGE_DIR }}/README.md"
|
|
||||||
echo ""
|
|
||||||
echo "🎨 徽章目录:"
|
|
||||||
echo " ${GITHUB_SERVER_URL}/${GITHUB_REPOSITORY}/src/branch/${{ env.BADGE_BRANCH }}/${{ env.BADGE_DIR }}"
|
|
||||||
echo ""
|
|
||||||
echo "================================================"
|
|
||||||
|
|
||||||
# 如果有警告或特殊情况,也输出
|
|
||||||
if [ "${{ steps.check_deps.outputs.installed }}" == "true" ]; then
|
|
||||||
echo ""
|
|
||||||
echo "ℹ️ 本次运行安装了缺失的依赖工具"
|
|
||||||
fi
|
|
||||||
|
|
||||||
if [ "${{ steps.languages.outputs.language_count }}" == "0" ]; then
|
|
||||||
echo ""
|
|
||||||
echo "⚠️ 警告: 未找到符合条件的语言文件"
|
|
||||||
echo " 请检查 LANGUAGE_GROUPS 配置和 MIN_LINES_THRESHOLD 设置"
|
|
||||||
fi
|
|
||||||
|
|
||||||
# 清理工作区(可选)
|
|
||||||
if [ "${{ env.CLEANUP_WORKSPACE }}" == "true" ]; then
|
|
||||||
echo ""
|
|
||||||
echo "🧹 清理工作区..."
|
|
||||||
rm -rf "${{ env.WORKSPACE_DIR }}"
|
|
||||||
echo "✅ 清理完成"
|
|
||||||
fi
|
|
||||||
|
|
||||||
- name: 🧹 清理工作区
|
|
||||||
if: always()
|
|
||||||
run: |
|
|
||||||
echo "🧹 清理临时文件..."
|
|
||||||
rm -rf /tmp/lang_stats /tmp/lang_summary.txt /tmp/total_stats*.json
|
|
||||||
echo "✅ 清理完成"
|
|
||||||
@@ -0,0 +1,659 @@
|
|||||||
|
name: 📊 代码统计徽章
|
||||||
|
|
||||||
|
on:
|
||||||
|
workflow_run:
|
||||||
|
workflows: ["🧰 准备环境"]
|
||||||
|
types:
|
||||||
|
- completed
|
||||||
|
|
||||||
|
concurrency:
|
||||||
|
group: stats-${{ github.repository }}
|
||||||
|
cancel-in-progress: false
|
||||||
|
|
||||||
|
# ==========================================
|
||||||
|
# 🔧 配置区域 - 根据你的项目修改
|
||||||
|
# ==========================================
|
||||||
|
env:
|
||||||
|
# ===== Token 配置 =====
|
||||||
|
# 请在 Settings -> Secrets 中配置 WORKFLOW secret
|
||||||
|
ACCESS_TOKEN: ${{ secrets.WORKFLOW }}
|
||||||
|
|
||||||
|
# ===== 工作区配置 =====
|
||||||
|
WORKSPACE_ROOT: "/data/workspace"
|
||||||
|
WORKSPACE_SLOT: "stats"
|
||||||
|
SOURCE_BRANCH: ${{ github.event.workflow_run.head_branch }}
|
||||||
|
SOURCE_SHA: ${{ github.event.workflow_run.head_sha }}
|
||||||
|
|
||||||
|
# ===== 分支配置 =====
|
||||||
|
# 徽章数据存储分支(可配置)
|
||||||
|
BADGE_BRANCH: "stats"
|
||||||
|
# 徽章文件存储目录
|
||||||
|
BADGE_DIR: "badges"
|
||||||
|
|
||||||
|
# ===== 排除配置 =====
|
||||||
|
# 全局排除的目录(这些目录会被完全忽略)
|
||||||
|
EXCLUDE_DIRS: "node_modules,dist,build,out,target,vendor,.venv,venv,__pycache__,.git,.github"
|
||||||
|
|
||||||
|
# 特殊包含规则(即使在排除目录中,也统计这些扩展名的文件)
|
||||||
|
# 格式: 目录:扩展名列表
|
||||||
|
# 例如: 'dist:js,css' 表示即使 dist 被排除,也统计其中的 .js 和 .css 文件
|
||||||
|
SPECIAL_INCLUDES: ""
|
||||||
|
# 示例: 'dist:js,css|vendor:go,mod'
|
||||||
|
|
||||||
|
# ===== SVG 徽章颜色配置 =====
|
||||||
|
COLOR_TOTAL: "blue"
|
||||||
|
COLOR_FILES: "green"
|
||||||
|
COLOR_LANGUAGE_COUNT: "purple"
|
||||||
|
|
||||||
|
# ===== 输出配置 =====
|
||||||
|
# 是否输出到 workflow summary
|
||||||
|
OUTPUT_TO_SUMMARY: "true"
|
||||||
|
# 最小代码行数阈值(低于此值的语言不生成徽章)
|
||||||
|
MIN_LINES_THRESHOLD: "10"
|
||||||
|
|
||||||
|
# ===== Git 配置 =====
|
||||||
|
GIT_USER_NAME: "ci[bot]"
|
||||||
|
GIT_USER_EMAIL: "ci[bot]@tinysoft.com.cn"
|
||||||
|
|
||||||
|
# ==========================================
|
||||||
|
# 🎨 语言分组配置
|
||||||
|
# 格式: 组名:后缀列表:显示名称:颜色:图标(可选)
|
||||||
|
# 图标使用 simple-icons 的名称,如 cplusplus, typescript 等
|
||||||
|
# ==========================================
|
||||||
|
LANGUAGE_GROUPS: |
|
||||||
|
cpp:hpp,cpp,cxx,cc,h,c:C/C++:00599C:cplusplus
|
||||||
|
python:py:Python:3572A5:python
|
||||||
|
typescript:ts,tsx:TypeScript:3178c6:typescript
|
||||||
|
javascript:js,jsx:JavaScript:F7DF1E:javascript
|
||||||
|
java:java:Java:007396:java
|
||||||
|
go:go:Go:00ADD8:go
|
||||||
|
rust:rs:Rust:CE412B:rust
|
||||||
|
shell:sh,bash:Shell:4EAA25:gnubash
|
||||||
|
yaml:yml,yaml:YAML:CB171E
|
||||||
|
tsf:tsl,tsf:TSF:9945FF
|
||||||
|
|
||||||
|
jobs:
|
||||||
|
update-stats:
|
||||||
|
if: ${{ github.event.workflow_run.conclusion == 'success' && (github.event.workflow_run.head_branch == 'main' || github.event.workflow_run.head_branch == 'master') }}
|
||||||
|
runs-on: standard-ubuntu-22
|
||||||
|
permissions:
|
||||||
|
contents: write
|
||||||
|
|
||||||
|
steps:
|
||||||
|
- name: 🔐 验证 Token 配置
|
||||||
|
id: validate_token
|
||||||
|
run: |
|
||||||
|
echo "🔐 验证访问令牌..."
|
||||||
|
|
||||||
|
if [ -z "${{ env.ACCESS_TOKEN }}" ]; then
|
||||||
|
echo "❌ 错误: 未配置访问令牌"
|
||||||
|
echo "请在 Settings -> Secrets 中配置 WORKFLOW secret"
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
echo "✅ 使用 WORKFLOW secret"
|
||||||
|
echo "token_type=WORKFLOW" >> $GITHUB_OUTPUT
|
||||||
|
|
||||||
|
echo "🔗 仓库: ${{ github.repository }}"
|
||||||
|
echo "🌿 上游分支: ${{ env.SOURCE_BRANCH }}"
|
||||||
|
echo "📝 上游提交: ${{ env.SOURCE_SHA }}"
|
||||||
|
|
||||||
|
- name: 📥 对齐统计 Worktree
|
||||||
|
id: prepare_worktree
|
||||||
|
shell: bash
|
||||||
|
run: |
|
||||||
|
set -euo pipefail
|
||||||
|
|
||||||
|
for command in git python3 bc flock realpath; do
|
||||||
|
if ! command -v "$command" >/dev/null 2>&1; then
|
||||||
|
echo "Prepare 成功后仍缺少统计所需命令: $command" >&2
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
done
|
||||||
|
|
||||||
|
REPO_NAME="${{ github.event.repository.name }}"
|
||||||
|
HEAD_SHA="${{ env.SOURCE_SHA }}"
|
||||||
|
REPOSITORY_DIR="${WORKSPACE_ROOT}/${REPO_NAME}/repository.git"
|
||||||
|
WORKTREE_DIR="${WORKSPACE_ROOT}/${REPO_NAME}/worktrees/stats"
|
||||||
|
WORKTREE_LOCK="${WORKSPACE_ROOT}/${REPO_NAME}/worktree-admin.lock"
|
||||||
|
|
||||||
|
case "$HEAD_SHA" in
|
||||||
|
''|*[!0-9a-fA-F]*)
|
||||||
|
echo "无效的上游提交 SHA: $HEAD_SHA" >&2
|
||||||
|
exit 1
|
||||||
|
;;
|
||||||
|
esac
|
||||||
|
if [ ! -d "$REPOSITORY_DIR" ] || \
|
||||||
|
[ "$(git --git-dir="$REPOSITORY_DIR" rev-parse --is-bare-repository 2>/dev/null)" != "true" ]; then
|
||||||
|
echo "Prepare 未创建有效共享 bare 仓库: $REPOSITORY_DIR" >&2
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
if ! git --git-dir="$REPOSITORY_DIR" cat-file -e "${HEAD_SHA}^{commit}"; then
|
||||||
|
echo "共享仓库中不存在上游提交: $HEAD_SHA" >&2
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
if [ ! -f "$WORKTREE_DIR/.git" ]; then
|
||||||
|
exec 9>"$WORKTREE_LOCK"
|
||||||
|
flock 9
|
||||||
|
|
||||||
|
if [ ! -f "$WORKTREE_DIR/.git" ]; then
|
||||||
|
if [ -e "$WORKTREE_DIR" ]; then
|
||||||
|
echo "统计 Worktree 路径已存在但不是 linked worktree: $WORKTREE_DIR" >&2
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
mkdir -p "$(dirname "$WORKTREE_DIR")"
|
||||||
|
git --git-dir="$REPOSITORY_DIR" worktree add --detach "$WORKTREE_DIR" "$HEAD_SHA"
|
||||||
|
fi
|
||||||
|
|
||||||
|
flock -u 9
|
||||||
|
fi
|
||||||
|
|
||||||
|
common_dir=$(git -C "$WORKTREE_DIR" rev-parse --path-format=absolute --git-common-dir)
|
||||||
|
if [ "$common_dir" != "$(realpath "$REPOSITORY_DIR")" ]; then
|
||||||
|
echo "统计 Worktree 不属于共享仓库: $WORKTREE_DIR" >&2
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
git -C "$WORKTREE_DIR" checkout --detach --force "$HEAD_SHA"
|
||||||
|
git -C "$WORKTREE_DIR" reset --hard "$HEAD_SHA"
|
||||||
|
git -C "$WORKTREE_DIR" clean -ffdx
|
||||||
|
|
||||||
|
actual_sha=$(git -C "$WORKTREE_DIR" rev-parse HEAD)
|
||||||
|
if [ "$actual_sha" != "$HEAD_SHA" ]; then
|
||||||
|
echo "统计 Worktree 提交不匹配: 期望 $HEAD_SHA,实际 $actual_sha" >&2
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
git -C "$WORKTREE_DIR" config user.name "${{ env.GIT_USER_NAME }}"
|
||||||
|
git -C "$WORKTREE_DIR" config user.email "${{ env.GIT_USER_EMAIL }}"
|
||||||
|
|
||||||
|
echo "REPOSITORY_DIR=$REPOSITORY_DIR" >> "$GITHUB_ENV"
|
||||||
|
echo "REPO_DIR=$WORKTREE_DIR" >> "$GITHUB_ENV"
|
||||||
|
echo "REPO_NAME=$REPO_NAME" >> "$GITHUB_ENV"
|
||||||
|
echo "统计 Worktree: $WORKTREE_DIR @ $actual_sha"
|
||||||
|
|
||||||
|
- name: 🌿 准备统计分支
|
||||||
|
id: prepare_stats_branch
|
||||||
|
run: |
|
||||||
|
set -euo pipefail
|
||||||
|
|
||||||
|
REPOSITORY_DIR="${{ env.REPOSITORY_DIR }}"
|
||||||
|
WORKTREE_DIR="${{ env.REPO_DIR }}"
|
||||||
|
BADGE_BRANCH="${{ env.BADGE_BRANCH }}"
|
||||||
|
SOURCE_SHA="${{ env.SOURCE_SHA }}"
|
||||||
|
|
||||||
|
if git --git-dir="$REPOSITORY_DIR" show-ref --verify --quiet "refs/remotes/origin/${BADGE_BRANCH}"; then
|
||||||
|
git -C "$WORKTREE_DIR" checkout --force -B "$BADGE_BRANCH" "refs/remotes/origin/${BADGE_BRANCH}"
|
||||||
|
echo "branch_exists=true" >> "$GITHUB_OUTPUT"
|
||||||
|
else
|
||||||
|
if git --git-dir="$REPOSITORY_DIR" show-ref --verify --quiet "refs/heads/${BADGE_BRANCH}"; then
|
||||||
|
git -C "$WORKTREE_DIR" branch -D "$BADGE_BRANCH"
|
||||||
|
fi
|
||||||
|
echo "branch_exists=false" >> "$GITHUB_OUTPUT"
|
||||||
|
fi
|
||||||
|
|
||||||
|
git -C "$WORKTREE_DIR" checkout --detach --force "$SOURCE_SHA"
|
||||||
|
|
||||||
|
- name: 🌱 创建统计分支
|
||||||
|
if: steps.prepare_stats_branch.outputs.branch_exists == 'false'
|
||||||
|
run: |
|
||||||
|
echo "======================================"
|
||||||
|
echo "🆕 创建新的统计分支"
|
||||||
|
echo "======================================"
|
||||||
|
|
||||||
|
cd "${{ env.REPO_DIR }}"
|
||||||
|
git rev-parse --is-inside-work-tree >/dev/null
|
||||||
|
|
||||||
|
# 创建孤立分支(orphan branch)
|
||||||
|
# 📝 说明:孤立分支与主分支没有共同的提交历史,适合存储独立的数据
|
||||||
|
echo "🌱 创建孤立分支..."
|
||||||
|
git checkout --orphan "${{ env.BADGE_BRANCH }}"
|
||||||
|
|
||||||
|
# 🧹 清空所有文件(但保留 .git 目录)
|
||||||
|
# ❓ 为什么要删除?
|
||||||
|
# - stats 分支是独立的数据存储分支,不需要主分支的代码文件
|
||||||
|
# - 只需要存储徽章 JSON 文件和统计报告
|
||||||
|
# - 保持分支干净,避免混淆
|
||||||
|
#
|
||||||
|
# ✅ 能否复用仓库?
|
||||||
|
# - 可以!.git 目录包含所有分支的完整信息
|
||||||
|
# - 切换到主分支时,主分支的文件会恢复
|
||||||
|
# - 各分支独立,互不影响
|
||||||
|
|
||||||
|
echo "🧹 清理文件(保护 .git 和 .gitea)..."
|
||||||
|
|
||||||
|
# 步骤 1: 使用 git rm 清理已跟踪的文件
|
||||||
|
git rm -rf . 2>/dev/null || true
|
||||||
|
|
||||||
|
# 步骤 2: 安全地删除剩余文件
|
||||||
|
# 使用更严格的保护,确保不会误删 .git
|
||||||
|
echo "🔍 查找需要删除的文件..."
|
||||||
|
find . -maxdepth 1 -type f ! -name '.git' ! -name '.gitignore' -delete 2>/dev/null || true
|
||||||
|
find . -maxdepth 1 -type d ! -name '.' ! -name '..' ! -name '.gitea' -exec rm -rf {} + 2>/dev/null || true
|
||||||
|
|
||||||
|
# 🔴 最终检查:确认 .git 仍然完好
|
||||||
|
echo "🔍 最终检查..."
|
||||||
|
git rev-parse --is-inside-work-tree >/dev/null
|
||||||
|
echo "✓ .git 目录完好"
|
||||||
|
echo "✓ 文件清理完成"
|
||||||
|
echo ""
|
||||||
|
|
||||||
|
# 创建初始 README
|
||||||
|
cat > README.md << 'EOF'
|
||||||
|
# 📊 代码统计徽章数据
|
||||||
|
|
||||||
|
> 此分支由 Gitea Actions 自动生成和维护
|
||||||
|
>
|
||||||
|
> ⚠️ **请勿手动修改此分支的内容!**
|
||||||
|
|
||||||
|
## 📁 目录结构
|
||||||
|
|
||||||
|
```
|
||||||
|
README.md # 详细统计报告
|
||||||
|
badges/
|
||||||
|
├── total-lines.svg # 总代码行数徽章
|
||||||
|
├── total-files.svg # 总文件数徽章
|
||||||
|
├── language-count.svg # 语言种类徽章
|
||||||
|
└── {language}-lines.svg # 各语言代码行数徽章
|
||||||
|
```
|
||||||
|
|
||||||
|
## 🔄 更新机制
|
||||||
|
|
||||||
|
- ✅ 主分支的 Prepare 成功后自动更新
|
||||||
|
- ✅ 可在 main/master 上手动运行 Prepare
|
||||||
|
|
||||||
|
## 📊 数据来源
|
||||||
|
|
||||||
|
所有统计数据基于主分支的最新代码自动生成。
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
*🤖 由 Gitea Actions 自动维护*
|
||||||
|
EOF
|
||||||
|
|
||||||
|
# 创建徽章目录
|
||||||
|
mkdir -p "${{ env.BADGE_DIR }}"
|
||||||
|
|
||||||
|
# 添加并提交
|
||||||
|
git add .
|
||||||
|
git commit -m ":bar_chart: chore(stats): 初始化统计分支"
|
||||||
|
|
||||||
|
# 推送到远程
|
||||||
|
git -C "${{ env.REPO_DIR }}" \
|
||||||
|
-c credential.helper= \
|
||||||
|
-c 'credential.helper=!f() { test "$1" = get || exit 0; printf "%s\n" username=oauth2; printf "%s\n" "password=${ACCESS_TOKEN}"; }; f' \
|
||||||
|
-c credential.interactive=never \
|
||||||
|
push origin "${{ env.BADGE_BRANCH }}"
|
||||||
|
|
||||||
|
echo "✅ 统计分支创建完成"
|
||||||
|
echo "======================================"
|
||||||
|
echo ""
|
||||||
|
|
||||||
|
echo "🔄 返回上游提交: ${{ env.SOURCE_SHA }}"
|
||||||
|
git checkout --detach --force "${{ env.SOURCE_SHA }}"
|
||||||
|
echo "✓ 已返回上游提交"
|
||||||
|
echo ""
|
||||||
|
|
||||||
|
- name: 📊 统计总代码量
|
||||||
|
id: total
|
||||||
|
run: |
|
||||||
|
echo "======================================"
|
||||||
|
echo "📊 统计总代码量"
|
||||||
|
echo "======================================"
|
||||||
|
|
||||||
|
cd "${{ env.REPO_DIR }}"
|
||||||
|
|
||||||
|
ACTUAL_SHA=$(git rev-parse HEAD)
|
||||||
|
if [ "$ACTUAL_SHA" != "${{ env.SOURCE_SHA }}" ]; then
|
||||||
|
echo "源码 Worktree 不在上游提交: 期望 ${{ env.SOURCE_SHA }},实际 $ACTUAL_SHA" >&2
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
# 构建排除参数
|
||||||
|
EXCLUDE_FIND_ARGS=()
|
||||||
|
IFS=',' read -ra EXCLUDES <<< "${{ env.EXCLUDE_DIRS }}"
|
||||||
|
for dir in "${EXCLUDES[@]}"; do
|
||||||
|
EXCLUDE_FIND_ARGS+=(-not -path "*/$dir/*")
|
||||||
|
done
|
||||||
|
|
||||||
|
# 获取所有文件
|
||||||
|
echo "🔍 扫描文件..."
|
||||||
|
|
||||||
|
# 统计总文件数
|
||||||
|
TOTAL_FILES=$(find . -type f "${EXCLUDE_FIND_ARGS[@]}" | wc -l)
|
||||||
|
|
||||||
|
# 统计总代码行数(排除空行)
|
||||||
|
TOTAL_CODE=$(find . -type f "${EXCLUDE_FIND_ARGS[@]}" -exec grep -cHv '^[[:space:]]*$' {} + 2>/dev/null | awk -F: '{sum+=$2} END {print sum+0}')
|
||||||
|
|
||||||
|
echo "📊 统计结果:"
|
||||||
|
echo " - 总文件数: $TOTAL_FILES"
|
||||||
|
echo " - 总代码行: $TOTAL_CODE"
|
||||||
|
|
||||||
|
# 格式化数字(添加千分位)
|
||||||
|
FORMATTED_FILES=$(printf "%'d" $TOTAL_FILES)
|
||||||
|
FORMATTED_CODE=$(printf "%'d" $TOTAL_CODE)
|
||||||
|
|
||||||
|
# 输出到环境变量
|
||||||
|
echo "total_files=$TOTAL_FILES" >> $GITHUB_OUTPUT
|
||||||
|
echo "total_code=$TOTAL_CODE" >> $GITHUB_OUTPUT
|
||||||
|
echo "formatted_files=$FORMATTED_FILES" >> $GITHUB_OUTPUT
|
||||||
|
echo "formatted_code=$FORMATTED_CODE" >> $GITHUB_OUTPUT
|
||||||
|
|
||||||
|
echo "======================================"
|
||||||
|
echo ""
|
||||||
|
|
||||||
|
- name: 📋 统计各语言代码量
|
||||||
|
id: languages
|
||||||
|
run: |
|
||||||
|
echo "======================================"
|
||||||
|
echo "📋 统计各语言代码量"
|
||||||
|
echo "======================================"
|
||||||
|
|
||||||
|
cd "${{ env.REPO_DIR }}"
|
||||||
|
|
||||||
|
ACTUAL_SHA=$(git rev-parse HEAD)
|
||||||
|
if [ "$ACTUAL_SHA" != "${{ env.SOURCE_SHA }}" ]; then
|
||||||
|
echo "源码 Worktree 不在上游提交: 期望 ${{ env.SOURCE_SHA }},实际 $ACTUAL_SHA" >&2
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
# 创建临时目录
|
||||||
|
mkdir -p /tmp/lang_stats
|
||||||
|
|
||||||
|
# 构建排除参数
|
||||||
|
EXCLUDE_FIND_ARGS=()
|
||||||
|
IFS=',' read -ra EXCLUDES <<< "${{ env.EXCLUDE_DIRS }}"
|
||||||
|
for dir in "${EXCLUDES[@]}"; do
|
||||||
|
EXCLUDE_FIND_ARGS+=(-not -path "*/$dir/*")
|
||||||
|
done
|
||||||
|
|
||||||
|
LANGUAGE_COUNT=0
|
||||||
|
|
||||||
|
# 读取语言配置并统计
|
||||||
|
echo "${{ env.LANGUAGE_GROUPS }}" | while IFS= read -r line; do
|
||||||
|
[ -z "$line" ] && continue
|
||||||
|
|
||||||
|
# 解析配置行: 组名:后缀列表:显示名称:颜色:图标
|
||||||
|
IFS=':' read -r lang_id extensions display_name color icon <<< "$line"
|
||||||
|
|
||||||
|
echo "🔍 统计 $display_name..."
|
||||||
|
|
||||||
|
# 构建扩展名查找条件
|
||||||
|
LANG_FIND_ARGS=()
|
||||||
|
IFS=',' read -ra EXTS <<< "$extensions"
|
||||||
|
for ext in "${EXTS[@]}"; do
|
||||||
|
if [ "${#LANG_FIND_ARGS[@]}" -eq 0 ]; then
|
||||||
|
LANG_FIND_ARGS+=(-name "*.$ext")
|
||||||
|
else
|
||||||
|
LANG_FIND_ARGS+=(-o -name "*.$ext")
|
||||||
|
fi
|
||||||
|
done
|
||||||
|
|
||||||
|
# 统计文件数
|
||||||
|
FILE_COUNT=$(find . -type f \( "${LANG_FIND_ARGS[@]}" \) "${EXCLUDE_FIND_ARGS[@]}" | wc -l)
|
||||||
|
|
||||||
|
if [ "$FILE_COUNT" -gt 0 ]; then
|
||||||
|
# 统计代码行数
|
||||||
|
CODE_LINES=$(find . -type f \( "${LANG_FIND_ARGS[@]}" \) "${EXCLUDE_FIND_ARGS[@]}" -exec grep -cHv '^[[:space:]]*$' {} + 2>/dev/null | awk -F: '{sum+=$2} END {print sum+0}')
|
||||||
|
|
||||||
|
# 只保存超过阈值的语言
|
||||||
|
if [ "$CODE_LINES" -ge "${{ env.MIN_LINES_THRESHOLD }}" ]; then
|
||||||
|
FORMATTED_LINES=$(printf "%'d" $CODE_LINES)
|
||||||
|
|
||||||
|
echo " - 文件数: $FILE_COUNT"
|
||||||
|
echo " - 代码行: $FORMATTED_LINES"
|
||||||
|
|
||||||
|
# 保存到临时文件
|
||||||
|
echo "$lang_id|$display_name|$CODE_LINES|$FORMATTED_LINES|$FILE_COUNT|$color|$icon" >> /tmp/lang_stats/${lang_id}.txt
|
||||||
|
LANGUAGE_COUNT=$((LANGUAGE_COUNT + 1))
|
||||||
|
else
|
||||||
|
echo " ⏩ 跳过(少于 ${{ env.MIN_LINES_THRESHOLD }} 行)"
|
||||||
|
fi
|
||||||
|
else
|
||||||
|
echo " ⏩ 未找到文件"
|
||||||
|
fi
|
||||||
|
echo ""
|
||||||
|
done
|
||||||
|
|
||||||
|
# 汇总所有语言数据
|
||||||
|
cat /tmp/lang_stats/*.txt 2>/dev/null | sort -t'|' -k3 -nr > /tmp/lang_summary.txt || touch /tmp/lang_summary.txt
|
||||||
|
|
||||||
|
# 计算实际找到的语言数
|
||||||
|
ACTUAL_COUNT=$(wc -l < /tmp/lang_summary.txt)
|
||||||
|
|
||||||
|
echo "language_count=$ACTUAL_COUNT" >> $GITHUB_OUTPUT
|
||||||
|
|
||||||
|
echo "✅ 找到 $ACTUAL_COUNT 种语言"
|
||||||
|
echo "======================================"
|
||||||
|
echo ""
|
||||||
|
|
||||||
|
- name: 🎨 生成 SVG 徽章与统计报告
|
||||||
|
id: generate_badges
|
||||||
|
run: |
|
||||||
|
echo "======================================"
|
||||||
|
echo "🎨 生成 SVG 徽章与统计报告"
|
||||||
|
echo "======================================"
|
||||||
|
|
||||||
|
cd "${{ env.REPO_DIR }}"
|
||||||
|
|
||||||
|
# 在上游提交渲染产物,避免依赖 stats 分支中的脚本文件
|
||||||
|
git checkout --detach --force "${{ env.SOURCE_SHA }}"
|
||||||
|
|
||||||
|
RENDER_ROOT=$(mktemp -d)
|
||||||
|
UPDATED_AT=$(date -u '+%Y-%m-%d %H:%M:%S UTC')
|
||||||
|
|
||||||
|
python3 .gitea/ci/render_stats_svgs.py \
|
||||||
|
--output-dir "$RENDER_ROOT/${{ env.BADGE_DIR }}" \
|
||||||
|
--readme-path "$RENDER_ROOT/README.md" \
|
||||||
|
--repo "${{ github.repository }}" \
|
||||||
|
--generated-by "Gitea Actions" \
|
||||||
|
--updated-at "$UPDATED_AT" \
|
||||||
|
--total-color "${{ env.COLOR_TOTAL }}" \
|
||||||
|
--files-color "${{ env.COLOR_FILES }}" \
|
||||||
|
--language-count-color "${{ env.COLOR_LANGUAGE_COUNT }}" \
|
||||||
|
--total-code "${{ steps.total.outputs.total_code }}" \
|
||||||
|
--formatted-total-code "${{ steps.total.outputs.formatted_code }}" \
|
||||||
|
--total-files "${{ steps.total.outputs.total_files }}" \
|
||||||
|
--formatted-total-files "${{ steps.total.outputs.formatted_files }}" \
|
||||||
|
--language-count "${{ steps.languages.outputs.language_count }}" \
|
||||||
|
--exclude-dirs "${{ env.EXCLUDE_DIRS }}" \
|
||||||
|
--min-lines-threshold "${{ env.MIN_LINES_THRESHOLD }}" \
|
||||||
|
--lang-summary /tmp/lang_summary.txt
|
||||||
|
|
||||||
|
git checkout --force "${{ env.BADGE_BRANCH }}"
|
||||||
|
rm -rf "${{ env.BADGE_DIR }}"
|
||||||
|
mkdir -p "${{ env.BADGE_DIR }}"
|
||||||
|
cp -f "$RENDER_ROOT/README.md" README.md
|
||||||
|
cp -f "$RENDER_ROOT/${{ env.BADGE_DIR }}"/* "${{ env.BADGE_DIR }}/"
|
||||||
|
GENERATED_COUNT=$(find "$RENDER_ROOT/${{ env.BADGE_DIR }}" -maxdepth 1 -type f | wc -l | tr -d ' ')
|
||||||
|
rm -rf "$RENDER_ROOT"
|
||||||
|
|
||||||
|
echo ""
|
||||||
|
echo "generated_count=$GENERATED_COUNT" >> $GITHUB_OUTPUT
|
||||||
|
echo "✅ 已生成 $GENERATED_COUNT 个 SVG 徽章"
|
||||||
|
echo "======================================"
|
||||||
|
echo ""
|
||||||
|
|
||||||
|
- name: 📤 提交并推送到统计分支
|
||||||
|
id: commit
|
||||||
|
run: |
|
||||||
|
echo "======================================"
|
||||||
|
echo "📤 提交并推送更新"
|
||||||
|
echo "======================================"
|
||||||
|
|
||||||
|
cd "${{ env.REPO_DIR }}"
|
||||||
|
|
||||||
|
# 确保在统计分支
|
||||||
|
git checkout ${{ env.BADGE_BRANCH }}
|
||||||
|
|
||||||
|
# 配置 Git 用户信息
|
||||||
|
git config user.name "${{ env.GIT_USER_NAME }}"
|
||||||
|
git config user.email "${{ env.GIT_USER_EMAIL }}"
|
||||||
|
|
||||||
|
# 添加所有更改
|
||||||
|
git add README.md ${{ env.BADGE_DIR }}/
|
||||||
|
|
||||||
|
# 检查是否有变更
|
||||||
|
if git diff --staged --quiet; then
|
||||||
|
echo "📌 没有变更,跳过提交"
|
||||||
|
echo "changed=false" >> $GITHUB_OUTPUT
|
||||||
|
echo ""
|
||||||
|
else
|
||||||
|
echo "📝 准备提交..."
|
||||||
|
|
||||||
|
# 生成提交信息
|
||||||
|
COMMIT_MSG=":bar_chart: chore(stats): 更新代码统计 [$(date '+%Y-%m-%d %H:%M')]
|
||||||
|
|
||||||
|
📊 统计摘要:
|
||||||
|
- 总代码: ${{ steps.total.outputs.formatted_code }} 行
|
||||||
|
- 总文件: ${{ steps.total.outputs.formatted_files }} 个
|
||||||
|
- 语言数: ${{ steps.languages.outputs.language_count }} 种
|
||||||
|
- 徽章数: ${{ steps.generate_badges.outputs.generated_count }} 个
|
||||||
|
|
||||||
|
🔗 触发提交: ${{ env.SOURCE_SHA }}
|
||||||
|
🤖 由 Gitea Actions 自动生成"
|
||||||
|
|
||||||
|
git commit -m "$COMMIT_MSG"
|
||||||
|
|
||||||
|
echo "📤 推送到远程 ${{ env.BADGE_BRANCH }} 分支..."
|
||||||
|
|
||||||
|
# 推送到远程(使用 TOKEN)
|
||||||
|
if git -C "${{ env.REPO_DIR }}" \
|
||||||
|
-c credential.helper= \
|
||||||
|
-c 'credential.helper=!f() { test "$1" = get || exit 0; printf "%s\n" username=oauth2; printf "%s\n" "password=${ACCESS_TOKEN}"; }; f' \
|
||||||
|
-c credential.interactive=never \
|
||||||
|
push origin "${{ env.BADGE_BRANCH }}"; then
|
||||||
|
echo "✅ 推送成功"
|
||||||
|
echo "changed=true" >> $GITHUB_OUTPUT
|
||||||
|
else
|
||||||
|
echo "❌ 推送失败"
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
fi
|
||||||
|
|
||||||
|
echo "======================================"
|
||||||
|
echo ""
|
||||||
|
|
||||||
|
- name: 📋 生成 Workflow Summary
|
||||||
|
if: env.OUTPUT_TO_SUMMARY == 'true' && success()
|
||||||
|
run: |
|
||||||
|
cat >> $GITHUB_STEP_SUMMARY << 'EOFSUMMARY'
|
||||||
|
# 📊 代码统计完成
|
||||||
|
|
||||||
|
## ✅ 执行结果
|
||||||
|
|
||||||
|
| 项目 | 结果 |
|
||||||
|
|------|------|
|
||||||
|
| 🔐 Token 类型 | **${{ steps.validate_token.outputs.token_type }}** |
|
||||||
|
| 🌿 来源分支 | `${{ env.SOURCE_BRANCH }}` |
|
||||||
|
| 📝 来源提交 | `${{ env.SOURCE_SHA }}` |
|
||||||
|
| 📊 总代码行数 | **${{ steps.total.outputs.formatted_code }}** 行 |
|
||||||
|
| 📁 总文件数 | **${{ steps.total.outputs.formatted_files }}** 个 |
|
||||||
|
| 🌐 语言种类 | **${{ steps.languages.outputs.language_count }}** 种 |
|
||||||
|
| 🎨 生成徽章 | **${{ steps.generate_badges.outputs.generated_count }}** 个 |
|
||||||
|
| 📤 提交状态 | ${{ steps.commit.outputs.changed == 'true' && '✅ 已更新' || '📌 无变更' }} |
|
||||||
|
|
||||||
|
## 🔗 快速链接
|
||||||
|
|
||||||
|
- 📊 [查看详细统计报告](${GITHUB_SERVER_URL}/${GITHUB_REPOSITORY}/src/branch/${{ env.BADGE_BRANCH }}/README.md)
|
||||||
|
- 🎨 [浏览徽章文件](${GITHUB_SERVER_URL}/${GITHUB_REPOSITORY}/src/branch/${{ env.BADGE_BRANCH }}/${{ env.BADGE_DIR }})
|
||||||
|
- 🔧 [查看 Workflow 配置](${GITHUB_SERVER_URL}/${GITHUB_REPOSITORY}/src/branch/${{ env.SOURCE_BRANCH }}/.gitea/workflows/update_stats_badge.yml)
|
||||||
|
|
||||||
|
## 📝 语言分布
|
||||||
|
|
||||||
|
EOFSUMMARY
|
||||||
|
|
||||||
|
# 添加语言分布表格
|
||||||
|
if [ -f /tmp/lang_summary.txt ] && [ -s /tmp/lang_summary.txt ]; then
|
||||||
|
cat >> $GITHUB_STEP_SUMMARY << 'EOFTABLE'
|
||||||
|
| 语言 | 代码行数 | 文件数 | 占比 |
|
||||||
|
|------|----------|--------|------|
|
||||||
|
EOFTABLE
|
||||||
|
|
||||||
|
TOTAL_CODE=${{ steps.total.outputs.total_code }}
|
||||||
|
sort -t'|' -k3 -nr /tmp/lang_summary.txt | head -10 | while IFS='|' read -r lang_id display_name code_lines formatted_lines file_count color icon; do
|
||||||
|
[ -z "$lang_id" ] && continue
|
||||||
|
|
||||||
|
if [ "$TOTAL_CODE" -gt 0 ]; then
|
||||||
|
PERCENT=$(echo "scale=1; $code_lines * 100 / $TOTAL_CODE" | bc)
|
||||||
|
else
|
||||||
|
PERCENT="0.0"
|
||||||
|
fi
|
||||||
|
|
||||||
|
echo "| **${display_name}** | ${formatted_lines} | ${file_count} | ${PERCENT}% |" >> $GITHUB_STEP_SUMMARY
|
||||||
|
done
|
||||||
|
else
|
||||||
|
echo "" >> $GITHUB_STEP_SUMMARY
|
||||||
|
echo "> ⚠️ 未找到符合条件的语言文件" >> $GITHUB_STEP_SUMMARY
|
||||||
|
fi
|
||||||
|
|
||||||
|
# 添加工作区信息
|
||||||
|
cat >> $GITHUB_STEP_SUMMARY << 'EOFWS'
|
||||||
|
|
||||||
|
## 📂 工作区信息
|
||||||
|
|
||||||
|
| 项目 | 值 |
|
||||||
|
|------|-----|
|
||||||
|
| 📁 仓库名称 | `${{ env.REPO_NAME }}` |
|
||||||
|
| 📍 工作目录 | `${{ env.REPO_DIR }}` |
|
||||||
|
| 🌿 统计分支 | `${{ env.BADGE_BRANCH }}` |
|
||||||
|
| 📂 徽章目录 | `${{ env.BADGE_DIR }}` |
|
||||||
|
|
||||||
|
EOFWS
|
||||||
|
|
||||||
|
echo "" >> $GITHUB_STEP_SUMMARY
|
||||||
|
echo "---" >> $GITHUB_STEP_SUMMARY
|
||||||
|
echo "" >> $GITHUB_STEP_SUMMARY
|
||||||
|
echo "*⏱️ 执行时间: $(date '+%Y-%m-%d %H:%M:%S %Z')*" >> $GITHUB_STEP_SUMMARY
|
||||||
|
|
||||||
|
- name: 💡 输出使用说明
|
||||||
|
if: success()
|
||||||
|
run: |
|
||||||
|
echo "================================================"
|
||||||
|
echo "✅ 代码统计任务执行完成!"
|
||||||
|
echo "================================================"
|
||||||
|
echo ""
|
||||||
|
|
||||||
|
echo "🔐 Token 信息:"
|
||||||
|
echo " - 使用: ${{ steps.validate_token.outputs.token_type }}"
|
||||||
|
echo ""
|
||||||
|
|
||||||
|
# 根据实际结果输出
|
||||||
|
if [ "${{ steps.commit.outputs.changed }}" == "true" ]; then
|
||||||
|
echo "📊 统计结果:"
|
||||||
|
echo " - 总代码: ${{ steps.total.outputs.formatted_code }} 行"
|
||||||
|
echo " - 总文件: ${{ steps.total.outputs.formatted_files }} 个"
|
||||||
|
echo " - 语言数: ${{ steps.languages.outputs.language_count }} 种"
|
||||||
|
echo " - 徽章数: ${{ steps.generate_badges.outputs.generated_count }} 个"
|
||||||
|
echo ""
|
||||||
|
echo "📤 已推送更新到分支: ${{ env.BADGE_BRANCH }}"
|
||||||
|
else
|
||||||
|
echo "📌 统计数据无变更,未产生新的提交"
|
||||||
|
fi
|
||||||
|
|
||||||
|
echo ""
|
||||||
|
echo "📂 工作区信息:"
|
||||||
|
echo " - 仓库目录: ${{ env.REPO_DIR }}"
|
||||||
|
echo " - 统计分支: ${{ env.BADGE_BRANCH }}"
|
||||||
|
echo ""
|
||||||
|
echo "🔗 查看详细报告:"
|
||||||
|
echo " ${GITHUB_SERVER_URL}/${GITHUB_REPOSITORY}/src/branch/${{ env.BADGE_BRANCH }}/README.md"
|
||||||
|
echo ""
|
||||||
|
echo "🎨 徽章目录:"
|
||||||
|
echo " ${GITHUB_SERVER_URL}/${GITHUB_REPOSITORY}/src/branch/${{ env.BADGE_BRANCH }}/${{ env.BADGE_DIR }}"
|
||||||
|
echo ""
|
||||||
|
echo "================================================"
|
||||||
|
|
||||||
|
if [ "${{ steps.languages.outputs.language_count }}" == "0" ]; then
|
||||||
|
echo ""
|
||||||
|
echo "⚠️ 警告: 未找到符合条件的语言文件"
|
||||||
|
echo " 请检查 LANGUAGE_GROUPS 配置和 MIN_LINES_THRESHOLD 设置"
|
||||||
|
fi
|
||||||
|
|
||||||
|
- name: 🧹 清理工作区
|
||||||
|
if: always()
|
||||||
|
run: |
|
||||||
|
echo "🧹 清理临时文件..."
|
||||||
|
rm -rf /tmp/lang_stats /tmp/lang_summary.txt
|
||||||
|
echo "✅ 清理完成"
|
||||||
@@ -18,3 +18,9 @@ Sessionx.vim
|
|||||||
tags
|
tags
|
||||||
# Persistent undo
|
# Persistent undo
|
||||||
[._]*.un~
|
[._]*.un~
|
||||||
|
|
||||||
|
# Environment files
|
||||||
|
.env
|
||||||
|
|
||||||
|
# Runner runtime data
|
||||||
|
docker-runner/presets/*/runner-data/
|
||||||
|
|||||||
+115
@@ -1,5 +1,105 @@
|
|||||||
# CHANGELOG
|
# CHANGELOG
|
||||||
|
|
||||||
|
## :bookmark: 0.0.5
|
||||||
|
|
||||||
|
### :pencil: What's Changed
|
||||||
|
|
||||||
|
- :sparkles: 新增 act_runner 自动升级脚本并同步最新版本探测 ([9d4c4a1](https://git.mytsl.cn/csh/actions-template/commit/9d4c4a1f7341f1ecbf663cf2a03c3434fc6ee314)) by @csh
|
||||||
|
- :recycle: 迁移 docker compose 并修复 runner 升级版本识别 ([34480c1](https://git.mytsl.cn/csh/actions-template/commit/34480c16e2e1d8a9cc38a9eb9f98a0d7b8ff544c)) by @csh
|
||||||
|
- :recycle: 隔离 workflow 工作区并提升默认 runner 并发 ([ac05621](https://git.mytsl.cn/csh/actions-template/commit/ac0562181762501eff49dde9231f65fccf2d7da4)) by @csh
|
||||||
|
- :wrench: 参数化实例配置并补充 preset env 示例 ([a4d09c9](https://git.mytsl.cn/csh/actions-template/commit/a4d09c906381f4cde4490074a950a1aaa7679ade)) by @csh
|
||||||
|
- :wrench: 统一 stats workflow 的 token 契约 ([2b5eaf2](https://git.mytsl.cn/csh/actions-template/commit/2b5eaf27c9b51ca676d24a23da044abfe14dfccc)) by @csh
|
||||||
|
- :memo: 修正 workflow 文档文件名与链接路径 ([124b953](https://git.mytsl.cn/csh/actions-template/commit/124b953d38b47f243524f8adf9f713143de1ba18)) by @csh
|
||||||
|
- :wrench: 收紧本地脚本与 runner 产物边界 ([95402f4](https://git.mytsl.cn/csh/actions-template/commit/95402f483054ea3a550fe9469cb4c1e864978e54)) by @csh
|
||||||
|
- :memo: 收紧文档入口与导航层次 ([61e512c](https://git.mytsl.cn/csh/actions-template/commit/61e512c2aef133cc5aa2263acca1cab006296317)) by @csh
|
||||||
|
- :wrench: 为 runner preset 显式设置系统 hostname ([173526b](https://git.mytsl.cn/csh/actions-template/commit/173526be7929dd2e05347bd4a4847a3b4d8e4f1a)) by @csh
|
||||||
|
- :recycle: 改用 shared mirror clone 准备 job 工作副本 ([beab4e5](https://git.mytsl.cn/csh/actions-template/commit/beab4e5696e209fb91ac321beb310526b5a5cc42)) by @csh
|
||||||
|
- :memo: 调整文档分层并补充 workflow 运行模型 ([0579c2d](https://git.mytsl.cn/csh/actions-template/commit/0579c2dc9754b67148cf4d2fe057a163f6e4483a)) by @csh
|
||||||
|
- :bug: fix(docker_runner): validate act_runner binary arch ([e5db752](https://git.mytsl.cn/csh/actions-template/commit/e5db752c55d320c3cddc663fff65d18376a41af8)) by @csh
|
||||||
|
- :wrench: chore(runner): allow preset identity overrides via env ([40fa747](https://git.mytsl.cn/csh/actions-template/commit/40fa747a941cffe87239eca31391545a0d330984)) by @csh
|
||||||
|
- :recycle: refactor(runner): simplify shell helpers and workflow scripts ([ebd0f94](https://git.mytsl.cn/csh/actions-template/commit/ebd0f94053603eb024ebd2b1d0d73d6981649ba9)) by @csh
|
||||||
|
- :bug: fix(buildx_runner): verify multiarch with explicit platforms ([8445c6a](https://git.mytsl.cn/csh/actions-template/commit/8445c6a36363381d17246923d7312ee7041c9493)) by @csh
|
||||||
|
- :memo: docs(readme): align document navigation headings ([d4fa498](https://git.mytsl.cn/csh/actions-template/commit/d4fa498a4a737c88f283d9fad3756a682d4ec362)) by @csh
|
||||||
|
- :bug: fix(stats): switch badges to self-hosted svg ([1259495](https://git.mytsl.cn/csh/actions-template/commit/1259495cbb0ebccd0972df1c0817b53e8e6e7736)) by @csh
|
||||||
|
- :art: refine(stats): simplify summary and badge colors ([6552f8b](https://git.mytsl.cn/csh/actions-template/commit/6552f8be47aa48dad26ebbc685284e14a56f4611)) by @csh
|
||||||
|
- :art: refine(stats): rebalance summary badge styling ([550e226](https://git.mytsl.cn/csh/actions-template/commit/550e226a2b6bdd3ca8b2e80f782abf2e35546b8b)) by @csh
|
||||||
|
- :wrench: chore(stats): add emoji commit prefixes for generated updates ([7782975](https://git.mytsl.cn/csh/actions-template/commit/778297543ab9cfe42d792d7c3fef9ffef9b6e193)) by @csh
|
||||||
|
- :recycle: refactor(ci): use fixed workflow slots ([610c2b3](https://git.mytsl.cn/csh/actions-template/commit/610c2b3e743f34a6a9e4ee6061e55037ecacad4c)) by @csh
|
||||||
|
- :recycle: refactor(ci): 重构为 Prepare 上游 + workflow_run 下游消费者模型
|
||||||
|
- 新增 prepare.yml:统一检查/安装公共工具并集中 fetch 到共享 bare 仓库
|
||||||
|
- 统计与发布改为消费成功的 Prepare workflow_run,各自维护 stats/release worktree
|
||||||
|
- 四个 workflow name 统一为带 emoji 的中文,同步 workflow_run 触发引用
|
||||||
|
- 系统信息 workflow 保持独立直接触发
|
||||||
|
- 删除 tests/ 手动验证脚本
|
||||||
|
([50efc4b](https://git.mytsl.cn/csh/actions-template/commit/50efc4bbfad9a897e931e9e6c1b2c2825d4d025b)) by @csh
|
||||||
|
- :bug: fix(ci): 补全 prepare.yml 缺失的 GIT_CONFIG_VALUE_1
|
||||||
|
GIT_CONFIG_COUNT=3 但 env 段漏写 GIT_CONFIG_KEY_1/VALUE_1,
|
||||||
|
credential helper 函数串被错放到 KEY_1,导致 git 报
|
||||||
|
missing config value GIT_CONFIG_VALUE_1 而无法 fetch。
|
||||||
|
([2dcf9f7](https://git.mytsl.cn/csh/actions-template/commit/2dcf9f7da481cfe9e18550faf11068fef2688f72)) by @csh
|
||||||
|
- :recycle: refactor(ci): 将 GIT_CONFIG 环境变量改为内联 -c 认证
|
||||||
|
- 删除三个 workflow env 段的 GIT_CONFIG_COUNT/KEY/VALUE 变量
|
||||||
|
- 在需要认证的 git 命令上内联 -c credential.helper/interactive
|
||||||
|
- prepare fetch、stats 两处 push、release push 各自内联
|
||||||
|
- 保留 ACCESS_TOKEN,helper 运行时取用
|
||||||
|
([ac8a781](https://git.mytsl.cn/csh/actions-template/commit/ac8a7816567344f48d97e86cd2c7a9ed2a43bcd1)) by @csh
|
||||||
|
- :bug: fix(ci): 修复贡献者列表未过滤 ci[bot] 的问题
|
||||||
|
grep 正则中 [bot] 被解析为字符类,导致 ^ci[bot]$ 无法匹配字面量。
|
||||||
|
改用 grep -vxF 按字面整行匹配过滤 bot 用户名。
|
||||||
|
([d6654b4](https://git.mytsl.cn/csh/actions-template/commit/d6654b4d6d7b03f7188cb9c1f49ce4c377eca235)) by @csh
|
||||||
|
|
||||||
|
|
||||||
|
- :bug: fix(changelog): 清除 0.0.5 段残留的 ci[bot] 贡献者 ([df8a9ea](https://git.mytsl.cn/csh/actions-template/commit/df8a9ead9c286652e1cfb8bc6dd4ae79ccfb0f8a)) by @csh
|
||||||
|
- :bug: fix(ci): 修复 mawk 不支持区间量词导致 CHANGELOG 去重失效
|
||||||
|
去重用的 awk 正则含 [0-9a-f]{7} 区间量词,ubuntu:22.04 默认的
|
||||||
|
mawk 不支持区间表达式,会把 {7} 当字面量,导致 EXISTING_HASHES
|
||||||
|
恒为空、去重完全失效,rc2 把 rc1 的提交整批重复插入。
|
||||||
|
改用不依赖区间的等价写法,并清理 0.0.5 段已产生的重复内容。
|
||||||
|
([207588a](https://git.mytsl.cn/csh/actions-template/commit/207588ae369d045c1ec3178ce4f6969b3b499981)) by @csh
|
||||||
|
|
||||||
|
### :busts_in_silhouette: Contributors
|
||||||
|
|
||||||
|
<a href="https://git.mytsl.cn/csh">
|
||||||
|
<img src="https://git.mytsl.cn/csh.png" alt="csh" width="35" height="35" style="border-radius: 50%;" onerror="this.src='https://git.mytsl.cn/assets/img/avatar_default.png'" />
|
||||||
|
</a>
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## :bookmark: 0.0.4
|
||||||
|
|
||||||
|
### :pencil: What's Changed
|
||||||
|
|
||||||
|
- :rocket: 优化bot名字,更具辨识度 ([4829434](https://git.mytsl.cn/csh/actions-template/commit/4829434408f46ebfad41293c46c9de1d94b157f3)) by @csh
|
||||||
|
- :recycle: 重构代码和`docker-runner`项目结构
|
||||||
|
:sparkles: 支持`docker`部署`archlinux`,预装`clang`跨平台编译工具链
|
||||||
|
([39820e6](https://git.mytsl.cn/csh/actions-template/commit/39820e656a9822e3b31df93d0abc33c02b2e56d1)) by @csh
|
||||||
|
- :bug: 修复`lsb_release`命令报错 ([0e89b21](https://git.mytsl.cn/csh/actions-template/commit/0e89b21c415bb62e02bace18038195c82d6374c7)) by @csh
|
||||||
|
- :bug: Bug修复 ([eaedcd3](https://git.mytsl.cn/csh/actions-template/commit/eaedcd3bb21e9ec8ed58e03b63ad85761a77cb48)) by @csh
|
||||||
|
|
||||||
|
|
||||||
|
- :rocket: 优化bot名字,更具辨识度 ([4829434](https://git.mytsl.cn/csh/actions-template/commit/4829434408f46ebfad41293c46c9de1d94b157f3)) by @csh
|
||||||
|
- :recycle: 重构代码和`docker-runner`项目结构
|
||||||
|
:sparkles: 支持`docker`部署`archlinux`,预装`clang`跨平台编译工具链
|
||||||
|
([39820e6](https://git.mytsl.cn/csh/actions-template/commit/39820e656a9822e3b31df93d0abc33c02b2e56d1)) by @csh
|
||||||
|
- :bug: 修复`lsb_release`命令报错 ([0e89b21](https://git.mytsl.cn/csh/actions-template/commit/0e89b21c415bb62e02bace18038195c82d6374c7)) by @csh
|
||||||
|
- :bug: Bug修复 ([eaedcd3](https://git.mytsl.cn/csh/actions-template/commit/eaedcd3bb21e9ec8ed58e03b63ad85761a77cb48)) by @csh
|
||||||
|
- :bug: 修复贡献者过滤自定义bot名称 ([1b5611c](https://git.mytsl.cn/csh/actions-template/commit/1b5611cf3ecf79ca7039c1c859e3030982a35e7a)) by @csh
|
||||||
|
- :sparkles: `gitea`的`runner`参数化,可以通过`docker-compose.yml`进行配置
|
||||||
|
:bug: 修复`Dockerfile`缺少系统包
|
||||||
|
([db75d0e](https://git.mytsl.cn/csh/actions-template/commit/db75d0ebc57c925c06e2f1c1bc15ef6436def502)) by @csh
|
||||||
|
- :remove: remove(规范):删除版本规范等,后续采用 [新规范](https://git.mytsl.cn/csh/tsl-playbook.git) ([99b3560](https://git.mytsl.cn/csh/actions-template/commit/99b3560b88cffb719860c8c7ea4fea490b8b099c)) by @csh
|
||||||
|
|
||||||
|
### :busts_in_silhouette: Contributors
|
||||||
|
|
||||||
|
<a href="https://git.mytsl.cn/ci-bot">
|
||||||
|
<img src="https://git.mytsl.cn/ci-bot.png" alt="ci-bot" width="35" height="35" style="border-radius: 50%;" onerror="this.src='https://git.mytsl.cn/assets/img/avatar_default.png'" />
|
||||||
|
</a>
|
||||||
|
<a href="https://git.mytsl.cn/csh">
|
||||||
|
<img src="https://git.mytsl.cn/csh.png" alt="csh" width="35" height="35" style="border-radius: 50%;" onerror="this.src='https://git.mytsl.cn/assets/img/avatar_default.png'" />
|
||||||
|
</a>
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
## :bookmark: 0.0.3
|
## :bookmark: 0.0.3
|
||||||
|
|
||||||
### :pencil: What's Changed
|
### :pencil: What's Changed
|
||||||
@@ -25,6 +125,21 @@
|
|||||||
- :bug: ok了嘛 ([3a17dc4](https://git.mytsl.cn/csh/actions-template/commit/3a17dc4b52163fbea3c2fe60c617acfe7d8cd19c)) by @csh
|
- :bug: ok了嘛 ([3a17dc4](https://git.mytsl.cn/csh/actions-template/commit/3a17dc4b52163fbea3c2fe60c617acfe7d8cd19c)) by @csh
|
||||||
- :bug: 不行就先放弃了 ([1b7eed5](https://git.mytsl.cn/csh/actions-template/commit/1b7eed5d93af889b7d8ed7d862899e36cbddaaf1)) by @csh
|
- :bug: 不行就先放弃了 ([1b7eed5](https://git.mytsl.cn/csh/actions-template/commit/1b7eed5d93af889b7d8ed7d862899e36cbddaaf1)) by @csh
|
||||||
|
|
||||||
|
|
||||||
|
- :bug: 移除默认上传`CHANGELOG.md` ([9961226](https://git.mytsl.cn/csh/actions-template/commit/99612267047e4d5c48d55888eeb0d05ba12c5da1)) by @csh
|
||||||
|
- :recycle: 重构`changelog_and_release.yml` ([1f535e0](https://git.mytsl.cn/csh/actions-template/commit/1f535e0675efe166b5d38f0f0778f40d8663d318)) by @csh
|
||||||
|
- :bug: 修复commit信息提取错误 ([bf6698a](https://git.mytsl.cn/csh/actions-template/commit/bf6698ab3e7f2020dcee899f6a06166b0e6a5168)) by @csh
|
||||||
|
- 更新 CHANGELOG.md ([4ed8099](https://git.mytsl.cn/csh/actions-template/commit/4ed8099158f0f753b53a526ccba7dfc7aaad739a)) by @csh
|
||||||
|
- :bug: fix bug ([fc76a1f](https://git.mytsl.cn/csh/actions-template/commit/fc76a1f625c4c6cb42e50cadb487b7607a8d2f0a)) by @csh
|
||||||
|
- :bug: fix bug ([fa21511](https://git.mytsl.cn/csh/actions-template/commit/fa21511d1ab98e415036046bc6950f8a3b322f25)) by @csh
|
||||||
|
- :bug: 修复匹配正则问题 ([9a472a4](https://git.mytsl.cn/csh/actions-template/commit/9a472a42711746ecb739eee4475b4a4125c75312)) by @csh
|
||||||
|
- :bug: 匹配逻辑 ([160c5eb](https://git.mytsl.cn/csh/actions-template/commit/160c5ebade5bf9e1a1e99412026ccd7d1cca6996)) by @csh
|
||||||
|
- :bug: 修复过滤转义失败 ([e71d9d0](https://git.mytsl.cn/csh/actions-template/commit/e71d9d01fdbbd19c24dcf57382ec52fb42e228be)) by @csh
|
||||||
|
- :bug: ok了嘛 ([3a17dc4](https://git.mytsl.cn/csh/actions-template/commit/3a17dc4b52163fbea3c2fe60c617acfe7d8cd19c)) by @csh
|
||||||
|
- :bug: 不行就先放弃了 ([1b7eed5](https://git.mytsl.cn/csh/actions-template/commit/1b7eed5d93af889b7d8ed7d862899e36cbddaaf1)) by @csh
|
||||||
|
- :wrench: `docker`的默认配置变更 ([c81aac9](https://git.mytsl.cn/csh/actions-template/commit/c81aac970cd50f7a3a08ccb4d7790a374efae721)) by @csh
|
||||||
|
- :wrench: 工作流的bot名字变更为`ci-bot` ([49f11fb](https://git.mytsl.cn/csh/actions-template/commit/49f11fb2d814e1fc59f8b05120bdd57fa44b9bad)) by @csh
|
||||||
|
|
||||||
### :busts_in_silhouette: Contributors
|
### :busts_in_silhouette: Contributors
|
||||||
|
|
||||||
<a href="https://git.mytsl.cn/csh">
|
<a href="https://git.mytsl.cn/csh">
|
||||||
|
|||||||
@@ -1,69 +0,0 @@
|
|||||||
# 模板说明
|
|
||||||
|
|
||||||
## :label: 图例说明
|
|
||||||
|
|
||||||
| 图标 | 代码 | 说明 | 图标 | 代码 | 说明 |
|
|
||||||
| :-----------: | :-------------- | :------- | :-------------------: | :---------------------- | :------------ |
|
|
||||||
| :tada: | `:tada:` | 首次发布 | :sparkles: | `:sparkles:` | 新功能 |
|
|
||||||
| :bug: | `:bug:` | Bug 修复 | :rocket: | `:rocket:` | 性能优化 |
|
|
||||||
| :art: | `:art:` | 代码样式 | :recycle: | `:recycle:` | 代码重构 |
|
|
||||||
| :package: | `:package:` | 依赖更新 | :lock: | `:lock:` | 安全修复 |
|
|
||||||
| :warning: | `:warning:` | 废弃警告 | :wrench: | `:wrench:` | 配置变更 |
|
|
||||||
| :wastebasket: | `:wastebasket:` | 删除功能 | :busts_in_silhouette: | `:busts_in_silhouette:` | 贡献者 |
|
|
||||||
| :memo: | `:memo:` | 文档更新 | :bookmark: | `:bookmark:` | 发行/版本标签 |
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## :pushpin: 版本号规范
|
|
||||||
|
|
||||||
### SemVer - 语义版本号
|
|
||||||
|
|
||||||
#### 格式
|
|
||||||
|
|
||||||
```txt
|
|
||||||
MAJOR.MINOR.PATCH
|
|
||||||
```
|
|
||||||
|
|
||||||
#### 字段说明
|
|
||||||
|
|
||||||
| 字段 | 说明 | 何时递增 |
|
|
||||||
| --------- | -------- | ------------------ |
|
|
||||||
| **MAJOR** | 主版本号 | 不兼容的 API 修改 |
|
|
||||||
| **MINOR** | 次版本号 | 向后兼容的功能新增 |
|
|
||||||
| **PATCH** | 修订号 | 向后兼容的问题修正 |
|
|
||||||
|
|
||||||
#### 更新规则
|
|
||||||
|
|
||||||
- **MAJOR(主版本)**:破坏性变更,不向后兼容。递增后 MINOR 和 PATCH 重置为 0
|
|
||||||
- **MINOR(次版本)**:新增功能但保持兼容。递增后 PATCH 重置为 0
|
|
||||||
- **PATCH(修订)**:仅修复 bug,不新增功能
|
|
||||||
|
|
||||||
#### 示例
|
|
||||||
|
|
||||||
```txt
|
|
||||||
1.0.0 # 首个稳定版
|
|
||||||
1.1.0 # 新增功能
|
|
||||||
1.1.1 # Bug 修复
|
|
||||||
2.0.0 # 破坏性变更
|
|
||||||
```
|
|
||||||
|
|
||||||
#### Pre-release 版本
|
|
||||||
|
|
||||||
```txt
|
|
||||||
1.0.0-alpha.1
|
|
||||||
1.0.0-beta
|
|
||||||
1.0.0-rc.1
|
|
||||||
1.0.0
|
|
||||||
```
|
|
||||||
|
|
||||||
#### 初始开发阶段
|
|
||||||
|
|
||||||
```txt
|
|
||||||
0.1.0 # 初始版本
|
|
||||||
0.2.0 # API 未稳定
|
|
||||||
1.0.0 # 首个稳定版 ✅
|
|
||||||
```
|
|
||||||
|
|
||||||
**注意**:`0.x.x` 版本可以随时破坏兼容性
|
|
||||||
|
|
||||||
---
|
|
||||||
+362
-1465
File diff suppressed because it is too large
Load Diff
@@ -9,15 +9,13 @@
|
|||||||
这是一个完整的 Gitea Runner 模板项目,提供:
|
这是一个完整的 Gitea Runner 模板项目,提供:
|
||||||
|
|
||||||
- 🐳 **Docker 部署方案**:开箱即用的 Runner 容器化部署
|
- 🐳 **Docker 部署方案**:开箱即用的 Runner 容器化部署
|
||||||
- 📝 **Workflow 示例**:自动化 CHANGELOG 和 Release 工作流
|
- 📝 **Workflow 示例**:自动化 CHANGELOG、Release 和统计徽章工作流
|
||||||
- 📚 **文档规范**:统一的文档格式和版本管理规范
|
- 📚 **文档规范**:统一的部署说明与工作流说明
|
||||||
- 🔧 **配置指南**:详细的配置说明和最佳实践
|
- 🔧 **配置指南**:预设配置、注册流程和运行方式说明
|
||||||
|
|
||||||
## 📂 文档导航
|
## 📂 文档导航
|
||||||
|
|
||||||
### 🚀 Runner
|
### 🚀 [DEPLOYMENT.md](./DEPLOYMENT.md)
|
||||||
|
|
||||||
#### [DEPLOYMENT.md](./DEPLOYMENT.md)
|
|
||||||
|
|
||||||
**Gitea Runner Docker 部署完整教程**
|
**Gitea Runner Docker 部署完整教程**
|
||||||
|
|
||||||
@@ -33,7 +31,7 @@
|
|||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
### 📋 [Workflow](./WORKFLOW.md)
|
### 📋 [WORKFLOW.md](./WORKFLOW.md)
|
||||||
|
|
||||||
**Gitea Actions 自动化工作流示例**
|
**Gitea Actions 自动化工作流示例**
|
||||||
|
|
||||||
@@ -41,22 +39,15 @@
|
|||||||
|
|
||||||
- 💡 工作流配置说明
|
- 💡 工作流配置说明
|
||||||
- 🔧 如何使用和定制
|
- 🔧 如何使用和定制
|
||||||
- 🔄 案例:`changelog_and_release.yml` - 自动更新 CHANGELOG 和自动创建 Release
|
- 🧰 案例:`prepare.yml` - 检查并补齐公共工具,集中准备共享 bare 仓库
|
||||||
|
- 📊 案例:`update_stats_badge.yml` - 在成功的 Prepare 主分支运行后生成统计徽章
|
||||||
|
- 📦 案例:`changelog_and_release.yml` - 在成功的 Prepare 版本 Tag 运行后生成 CHANGELOG 和 Release
|
||||||
|
- 🖥️ 案例:`ubuntu_system_info.yml` - 直接触发的独立系统信息任务,可与 Prepare 并行
|
||||||
|
|
||||||
👉 **实用的 Actions 工作流,可直接复制使用**
|
👉 **实用的 Actions 工作流,可直接复制使用**
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
### 📝 [Conventions](./CONVENTIONS.md)
|
仓库相关工作流采用 `Prepare → workflow_run 消费者` 模型;系统信息不读取仓库,因此保持独立触发。详细的共享目录、并发和恢复规则见 [WORKFLOW.md](./WORKFLOW.md)。
|
||||||
|
|
||||||
**文档编写规范和版本命名规则**
|
具体部署步骤、预设选择、注册流程、升级方式和故障排查请查看 [DEPLOYMENT.md](./DEPLOYMENT.md)。
|
||||||
|
|
||||||
包含内容:
|
|
||||||
|
|
||||||
- 📊 Emoji 图标使用规范
|
|
||||||
- 🔢 版本号命名规则(SemVer)
|
|
||||||
- 📝 [CHANGELOG](./CHANGELOG.md) 模板
|
|
||||||
|
|
||||||
👉 **维护项目文档的统一规范,贡献者必读**
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|||||||
+64
-15
@@ -2,24 +2,51 @@
|
|||||||
|
|
||||||
本目录包含 Gitea Actions 的自动化工作流示例,展示如何使用 Gitea Actions 实现 CI/CD 自动化。
|
本目录包含 Gitea Actions 的自动化工作流示例,展示如何使用 Gitea Actions 实现 CI/CD 自动化。
|
||||||
|
|
||||||
|
- 先看仓库总览:回到 [README.md](./README.md)
|
||||||
|
- 需要部署或维护 runner:参考 [DEPLOYMENT.md](./DEPLOYMENT.md)
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
## 📂 文件结构
|
## 📂 文件结构
|
||||||
|
|
||||||
```txt
|
```txt
|
||||||
.gitea/workflows/
|
.gitea/workflows/
|
||||||
├── changelog-and-release.yml # CHANGELOG 生成 + Release 创建
|
├── prepare.yml # 公共工具 + shared bare repository
|
||||||
├── update_stats_badge.yml # 代码行数的统计
|
├── changelog_and_release.yml # Prepare 成功后的 Tag 发布消费者
|
||||||
└── ... # 更多 workflow 待添加
|
├── update_stats_badge.yml # Prepare 成功后的主分支统计消费者
|
||||||
|
└── ubuntu_system_info.yml # 与仓库无关、独立直接触发
|
||||||
```
|
```
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
|
## 🧱 运行模型
|
||||||
|
|
||||||
|
```text
|
||||||
|
push / workflow_dispatch
|
||||||
|
├─ Prepare
|
||||||
|
│ ├─ 检查并按需安装公共工具
|
||||||
|
│ └─ fetch → /data/workspace/<repo>/repository.git
|
||||||
|
│
|
||||||
|
└─ Ubuntu System Information(自身触发条件命中时独立并行)
|
||||||
|
|
||||||
|
Prepare completed successfully
|
||||||
|
├─ main/master → update_stats_badge.yml
|
||||||
|
└─ refs/tags/[0-9]* → changelog_and_release.yml
|
||||||
|
```
|
||||||
|
|
||||||
|
`Prepare` 是仓库相关链路中唯一安装公共工具和 fetch 的 workflow。它保存一个 persistent bare repository,但不创建任何消费者 worktree。
|
||||||
|
|
||||||
|
统计和发布通过 `workflow_run` 读取上游 `head_sha`;发布还从 Gitea 的 `workflow_run.path`(例如 `prepare.yml@refs/tags/1.2.0`)恢复原始 Tag。两个消费者分别拥有 `worktrees/stats` 和 `worktrees/release`,首次创建时通过 `worktree-admin.lock` 串行修改 Git worktree metadata,每次复用前都执行 detached checkout、hard reset 和 clean。
|
||||||
|
|
||||||
|
三个仓库相关 workflow 使用各自的 per-repository concurrency group 且不取消排队运行。统计与发布使用不同 worktree,因此可以并行;系统信息 workflow 不依赖仓库或 Prepare,也可以独立并行。
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
## 🎯 功能列表
|
## 🎯 功能列表
|
||||||
|
|
||||||
### ✅ 已实现
|
### ✅ 已实现
|
||||||
|
|
||||||
#### 1. 📦 自动发布工作流 (`changelog-and-release.yml`)
|
#### 1. 📦 自动发布工作流 (`changelog_and_release.yml`)
|
||||||
|
|
||||||
**功能**:在推送 tag 时自动生成 CHANGELOG 并创建 Release
|
**功能**:在推送 tag 时自动生成 CHANGELOG 并创建 Release
|
||||||
|
|
||||||
@@ -35,44 +62,66 @@
|
|||||||
|
|
||||||
**触发方式**:
|
**触发方式**:
|
||||||
|
|
||||||
|
推送数字开头的 Tag 会先触发 `Prepare`。Prepare 成功后,发布 routing job 从 `workflow_run.path` 恢复 Tag,确认 Tag peeled commit 等于 `head_sha`,随后才生成 CHANGELOG 和 Release。
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
git tag 1.0.0
|
git tag 1.0.0
|
||||||
git push origin 1.0.0
|
git push origin 1.0.0
|
||||||
```
|
```
|
||||||
|
|
||||||
**文件**:[changelog-and-release.yml](.gitea/workflows/changelog_and_release.yml)
|
**文件**:[changelog_and_release.yml](.gitea/workflows/changelog_and_release.yml)
|
||||||
|
|
||||||
💡 **详细配置**:查看 `changelog-and-release.yml` 文件顶部的 `env` 区域,所有配置项都有详细注释说明
|
💡 **详细配置**:查看 `changelog_and_release.yml` 文件顶部的 `env` 区域,所有配置项都有详细注释说明
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
#### 2. 📊 代码统计徽章工作流 (`update-stats-badge.yml`)
|
#### 2. 📊 代码统计徽章工作流 (`update_stats_badge.yml`)
|
||||||
|
|
||||||
**功能**:自动统计代码行数并生成徽章数据
|
**功能**:自动统计代码行数并生成 SVG 徽章与统计报告
|
||||||
|
|
||||||
**特性**:
|
**特性**:
|
||||||
|
|
||||||
- 📈 统计总代码行数和文件数
|
- 📈 统计总代码行数和文件数
|
||||||
- 🌐 按语言分组统计
|
- 🌐 按语言分组统计
|
||||||
- 🎨 自动生成徽章 JSON 数据
|
- 🎨 自动生成仓库内自托管的 SVG 徽章
|
||||||
- 📊 生成统计摘要(JSON 格式)
|
- 📊 在 `stats` 分支根目录生成统计报告 README
|
||||||
- 🚫 灵活的目录排除配置
|
- 🚫 灵活的目录排除配置
|
||||||
|
|
||||||
**触发方式**:
|
**触发方式**:
|
||||||
|
|
||||||
|
主分支 push 或在 `main`/`master` 上手动运行 Prepare 后,统计 workflow 通过成功的 `workflow_run` 启动,并始终从上游 `head_sha` 的 detached checkout 读取源码。
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
# 推送到主分支时自动触发
|
# 推送到主分支时自动触发
|
||||||
git push origin main
|
git push origin main
|
||||||
|
|
||||||
# 手动触发
|
# 手动触发
|
||||||
# 仓库 → Actions → Update Code Statistics Badge → Run workflow
|
# 仓库 → Actions → Prepare → Run workflow
|
||||||
```
|
```
|
||||||
|
|
||||||
**配置文件**:[update-stats-badge.yml](.gitea/workflows/update_stats_badge.yaml)
|
**配置文件**:[update_stats_badge.yml](.gitea/workflows/update_stats_badge.yml)
|
||||||
|
|
||||||
**markdown引用格式**: ``
|
**markdown引用格式**: ``
|
||||||
|
|
||||||
💡 **详细配置**:查看 `update-stats-badge.yml` 文件顶部的 `env` 区域,包含语言分组、颜色、排除目录等配置
|
**产物结构**:
|
||||||
|
|
||||||
|
```txt
|
||||||
|
stats
|
||||||
|
├── README.md
|
||||||
|
└── badges/
|
||||||
|
├── total-lines.svg
|
||||||
|
├── total-files.svg
|
||||||
|
├── language-count.svg
|
||||||
|
└── <language>-lines.svg
|
||||||
|
```
|
||||||
|
|
||||||
|
💡 **详细配置**:查看 `update_stats_badge.yml` 文件顶部的 `env` 区域,包含语言分组、颜色、排除目录等配置
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
#### 3. 🖥️ 系统信息工作流 (`ubuntu_system_info.yml`)
|
||||||
|
|
||||||
|
该 workflow 不读取仓库内容,保留自己的 `push` 和 `workflow_dispatch` 触发器;它不使用 `workflow_run`、`repository.git` 或任何消费者 worktree。
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
@@ -109,7 +158,7 @@ git push origin main
|
|||||||
**检查清单**:
|
**检查清单**:
|
||||||
|
|
||||||
- [ ] Workflow 文件在 `.gitea/workflows/` 目录
|
- [ ] Workflow 文件在 `.gitea/workflows/` 目录
|
||||||
- [ ] 文件名正确(如 `changelog-and-release.yml`)
|
- [ ] 文件名正确(如 `changelog_and_release.yml`)
|
||||||
- [ ] Token 已正确配置(Secret 名称为 `WORKFLOW`)
|
- [ ] Token 已正确配置(Secret 名称为 `WORKFLOW`)
|
||||||
- [ ] Tag 格式正确(数字开头,如 `1.0.0`)
|
- [ ] Tag 格式正确(数字开头,如 `1.0.0`)
|
||||||
- [ ] Gitea Actions 已启用
|
- [ ] Gitea Actions 已启用
|
||||||
|
|||||||
@@ -1,321 +0,0 @@
|
|||||||
#!/bin/bash
|
|
||||||
set -e
|
|
||||||
|
|
||||||
echo "=========================================="
|
|
||||||
echo " Gitea Runner Registration Script "
|
|
||||||
echo "=========================================="
|
|
||||||
echo ""
|
|
||||||
|
|
||||||
# 检查 act_runner 是否安装
|
|
||||||
if ! command -v act_runner &> /dev/null; then
|
|
||||||
echo "✗ act_runner is not installed!"
|
|
||||||
echo ""
|
|
||||||
echo "Please run the setup script first:"
|
|
||||||
echo " docker-compose exec gitea-runner /data/setup.sh"
|
|
||||||
exit 1
|
|
||||||
fi
|
|
||||||
|
|
||||||
echo "✓ act_runner found: $(act_runner --version)"
|
|
||||||
echo ""
|
|
||||||
|
|
||||||
# 获取注册信息并验证
|
|
||||||
while true; do
|
|
||||||
read -p "Enter Gitea instance URL (e.g., https://gitea.example.com): " GITEA_INSTANCE
|
|
||||||
|
|
||||||
# 验证 URL 格式
|
|
||||||
if [[ ! "$GITEA_INSTANCE" =~ ^https?:// ]]; then
|
|
||||||
echo "✗ Error: URL must start with http:// or https://"
|
|
||||||
echo ""
|
|
||||||
continue
|
|
||||||
fi
|
|
||||||
|
|
||||||
# 移除末尾的斜杠
|
|
||||||
GITEA_INSTANCE="${GITEA_INSTANCE%/}"
|
|
||||||
echo "✓ URL validated: $GITEA_INSTANCE"
|
|
||||||
break
|
|
||||||
done
|
|
||||||
|
|
||||||
read -p "Enter registration token: " GITEA_TOKEN
|
|
||||||
|
|
||||||
if [ -z "$GITEA_TOKEN" ]; then
|
|
||||||
echo "✗ Error: Token cannot be empty!"
|
|
||||||
exit 1
|
|
||||||
fi
|
|
||||||
|
|
||||||
read -p "Enter runner name (default: docker-runner): " RUNNER_NAME
|
|
||||||
RUNNER_NAME=${RUNNER_NAME:-docker-runner}
|
|
||||||
|
|
||||||
# 多个 label(逗号分隔,无空格)
|
|
||||||
# ubuntu-22.04:host://ubuntu:22.04,ubuntu-20.04:host://ubuntu:20.04,node:docker://node:18
|
|
||||||
read -p "Enter runner labels (default: ubuntu-22.04:host://ubuntu:22.04): " RUNNER_LABELS
|
|
||||||
RUNNER_LABELS=${RUNNER_LABELS:-ubuntu-22.04:host://ubuntu:22.04}
|
|
||||||
|
|
||||||
# 创建 runner 目录
|
|
||||||
RUNNER_DIR="/data/runners/${RUNNER_NAME}"
|
|
||||||
mkdir -p "$RUNNER_DIR"
|
|
||||||
cd "$RUNNER_DIR"
|
|
||||||
|
|
||||||
echo ""
|
|
||||||
echo "Registration Information:"
|
|
||||||
echo " Instance: $GITEA_INSTANCE"
|
|
||||||
echo " Name: $RUNNER_NAME"
|
|
||||||
echo " Labels: $RUNNER_LABELS"
|
|
||||||
echo " Directory: $RUNNER_DIR"
|
|
||||||
echo ""
|
|
||||||
|
|
||||||
# 检查是否已经注册
|
|
||||||
if [ -f ".runner" ] || [ -f "config.yaml" ]; then
|
|
||||||
echo "⚠ Runner already exists in this directory!"
|
|
||||||
read -p "Do you want to re-register? This will overwrite existing configuration. (y/N): " -n 1 -r
|
|
||||||
echo
|
|
||||||
if [[ ! $REPLY =~ ^[Yy]$ ]]; then
|
|
||||||
echo "Registration cancelled."
|
|
||||||
exit 0
|
|
||||||
fi
|
|
||||||
|
|
||||||
echo "Cleaning up existing runner..."
|
|
||||||
|
|
||||||
# 停止现有 runner
|
|
||||||
echo "Stopping existing runner..."
|
|
||||||
supervisorctl stop "runner-${RUNNER_NAME}" 2>/dev/null || true
|
|
||||||
|
|
||||||
# 删除 supervisor 配置
|
|
||||||
rm -f "/etc/supervisor/conf.d/runner-${RUNNER_NAME}.conf"
|
|
||||||
|
|
||||||
# 重新加载 supervisor
|
|
||||||
supervisorctl reread 2>/dev/null || true
|
|
||||||
supervisorctl update 2>/dev/null || true
|
|
||||||
|
|
||||||
# 删除日志
|
|
||||||
rm -f "/var/log/supervisor/runner-${RUNNER_NAME}".*.log*
|
|
||||||
|
|
||||||
# 删除旧配置和缓存
|
|
||||||
rm -f .runner config.yaml
|
|
||||||
rm -rf cache
|
|
||||||
fi
|
|
||||||
|
|
||||||
# 执行注册
|
|
||||||
echo ""
|
|
||||||
echo "Registering runner..."
|
|
||||||
act_runner register \
|
|
||||||
--instance "$GITEA_INSTANCE" \
|
|
||||||
--token "$GITEA_TOKEN" \
|
|
||||||
--name "$RUNNER_NAME" \
|
|
||||||
--labels "$RUNNER_LABELS" \
|
|
||||||
--no-interactive
|
|
||||||
|
|
||||||
if [ ! -f ".runner" ]; then
|
|
||||||
echo ""
|
|
||||||
echo "✗ Registration failed! .runner file not created."
|
|
||||||
exit 1
|
|
||||||
fi
|
|
||||||
|
|
||||||
echo "✓ Registration successful!"
|
|
||||||
|
|
||||||
# 生成配置文件
|
|
||||||
echo ""
|
|
||||||
echo "Generating config.yaml..."
|
|
||||||
act_runner generate-config > config.yaml
|
|
||||||
|
|
||||||
echo "✓ Configuration file generated!"
|
|
||||||
|
|
||||||
# 创建缓存目录
|
|
||||||
mkdir -p cache
|
|
||||||
|
|
||||||
# 使用 Python 修改配置(最可靠的方法)
|
|
||||||
echo ""
|
|
||||||
echo "Configuring runner settings..."
|
|
||||||
|
|
||||||
if command -v python3 &> /dev/null; then
|
|
||||||
python3 << PYEOF
|
|
||||||
import yaml
|
|
||||||
import sys
|
|
||||||
|
|
||||||
try:
|
|
||||||
# 读取生成的配置
|
|
||||||
with open('config.yaml', 'r') as f:
|
|
||||||
config = yaml.safe_load(f)
|
|
||||||
|
|
||||||
# 读取 .runner 获取实际注册的 labels
|
|
||||||
import json
|
|
||||||
with open('.runner', 'r') as f:
|
|
||||||
runner_data = json.load(f)
|
|
||||||
|
|
||||||
# 使用 .runner 中的 labels(这是实际注册的)
|
|
||||||
registered_labels = runner_data.get('labels', [])
|
|
||||||
|
|
||||||
# 修改配置
|
|
||||||
if 'runner' not in config:
|
|
||||||
config['runner'] = {}
|
|
||||||
|
|
||||||
# 使用实际注册的 labels
|
|
||||||
config['runner']['labels'] = registered_labels
|
|
||||||
config['runner']['capacity'] = 2
|
|
||||||
|
|
||||||
# 启用缓存
|
|
||||||
if 'cache' not in config:
|
|
||||||
config['cache'] = {}
|
|
||||||
config['cache']['enabled'] = True
|
|
||||||
config['cache']['dir'] = './cache'
|
|
||||||
|
|
||||||
# 保存配置
|
|
||||||
with open('config.yaml', 'w') as f:
|
|
||||||
yaml.dump(config, f, default_flow_style=False, sort_keys=False)
|
|
||||||
|
|
||||||
print("✓ Configuration updated using Python")
|
|
||||||
print(f" - Labels: {registered_labels}")
|
|
||||||
print(f" - Capacity: 2")
|
|
||||||
print(f" - Cache enabled: ./cache")
|
|
||||||
sys.exit(0)
|
|
||||||
|
|
||||||
except Exception as e:
|
|
||||||
print(f"✗ Python configuration failed: {e}", file=sys.stderr)
|
|
||||||
sys.exit(1)
|
|
||||||
PYEOF
|
|
||||||
|
|
||||||
PYTHON_EXIT=$?
|
|
||||||
|
|
||||||
if [ $PYTHON_EXIT -ne 0 ]; then
|
|
||||||
echo ""
|
|
||||||
echo "⚠ Python configuration failed, using basic sed..."
|
|
||||||
|
|
||||||
# 基本的 sed 修改(只修改简单的值,不动 labels)
|
|
||||||
sed -i 's/capacity: 1/capacity: 2/g' config.yaml || true
|
|
||||||
sed -i 's/enabled: false/enabled: true/g' config.yaml || true
|
|
||||||
sed -i 's|dir: ""|dir: ./cache|g' config.yaml || true
|
|
||||||
|
|
||||||
echo "✓ Basic configuration applied"
|
|
||||||
echo " Note: Please manually verify labels in config.yaml match .runner"
|
|
||||||
fi
|
|
||||||
else
|
|
||||||
echo "⚠ Python3 not found, applying basic configuration..."
|
|
||||||
|
|
||||||
# 基本的 sed 修改
|
|
||||||
sed -i 's/capacity: 1/capacity: 2/g' config.yaml || true
|
|
||||||
sed -i 's/enabled: false/enabled: true/g' config.yaml || true
|
|
||||||
sed -i 's|dir: ""|dir: ./cache|g' config.yaml || true
|
|
||||||
|
|
||||||
echo "✓ Basic configuration applied"
|
|
||||||
echo " Note: Labels will use act_runner defaults"
|
|
||||||
fi
|
|
||||||
|
|
||||||
# 验证配置文件
|
|
||||||
echo ""
|
|
||||||
echo "Validating configuration..."
|
|
||||||
|
|
||||||
# 检查 YAML 语法
|
|
||||||
if command -v python3 &> /dev/null; then
|
|
||||||
python3 << PYEOF
|
|
||||||
import yaml
|
|
||||||
import sys
|
|
||||||
try:
|
|
||||||
with open('config.yaml', 'r') as f:
|
|
||||||
yaml.safe_load(f)
|
|
||||||
print("✓ config.yaml syntax is valid")
|
|
||||||
sys.exit(0)
|
|
||||||
except Exception as e:
|
|
||||||
print(f"✗ config.yaml syntax error: {e}", file=sys.stderr)
|
|
||||||
sys.exit(1)
|
|
||||||
PYEOF
|
|
||||||
|
|
||||||
if [ $? -ne 0 ]; then
|
|
||||||
echo ""
|
|
||||||
echo "✗ Configuration file has syntax errors!"
|
|
||||||
echo " Backup available at: config.yaml.bak"
|
|
||||||
exit 1
|
|
||||||
fi
|
|
||||||
fi
|
|
||||||
|
|
||||||
# 显示配置摘要
|
|
||||||
echo ""
|
|
||||||
echo "Configuration Summary:"
|
|
||||||
echo "-------------------------------------------"
|
|
||||||
echo ".runner labels:"
|
|
||||||
cat .runner | grep -A 10 '"labels"' | head -15
|
|
||||||
echo ""
|
|
||||||
echo "config.yaml labels:"
|
|
||||||
grep -A 5 "^ labels:" config.yaml | head -10
|
|
||||||
|
|
||||||
# 创建 supervisor 配置
|
|
||||||
echo ""
|
|
||||||
echo "Creating supervisor configuration..."
|
|
||||||
cat > "/etc/supervisor/conf.d/runner-${RUNNER_NAME}.conf" <<EOF
|
|
||||||
[program:runner-${RUNNER_NAME}]
|
|
||||||
command=/usr/local/bin/act_runner daemon --config ${RUNNER_DIR}/config.yaml
|
|
||||||
directory=${RUNNER_DIR}
|
|
||||||
autostart=true
|
|
||||||
autorestart=true
|
|
||||||
stderr_logfile=/var/log/supervisor/runner-${RUNNER_NAME}.err.log
|
|
||||||
stdout_logfile=/var/log/supervisor/runner-${RUNNER_NAME}.out.log
|
|
||||||
stdout_logfile_maxbytes=50MB
|
|
||||||
stdout_logfile_backups=10
|
|
||||||
stderr_logfile_maxbytes=50MB
|
|
||||||
stderr_logfile_backups=10
|
|
||||||
user=root
|
|
||||||
environment=HOME="/root"
|
|
||||||
EOF
|
|
||||||
|
|
||||||
echo "✓ Supervisor configuration created"
|
|
||||||
|
|
||||||
# 重新加载 supervisor
|
|
||||||
echo ""
|
|
||||||
echo "Reloading supervisor..."
|
|
||||||
supervisorctl reread
|
|
||||||
supervisorctl update
|
|
||||||
|
|
||||||
echo "Starting runner..."
|
|
||||||
sleep 2
|
|
||||||
# 启动 runner
|
|
||||||
supervisorctl restart "runner-${RUNNER_NAME}" 2>/dev/null || \
|
|
||||||
supervisorctl start "runner-${RUNNER_NAME}"
|
|
||||||
|
|
||||||
# 等待启动
|
|
||||||
sleep 3
|
|
||||||
|
|
||||||
# 显示状态
|
|
||||||
RUNNER_STATUS=$(supervisorctl status "runner-${RUNNER_NAME}" 2>/dev/null || echo "UNKNOWN")
|
|
||||||
|
|
||||||
echo ""
|
|
||||||
echo "=========================================="
|
|
||||||
echo "✓ Runner registered and started!"
|
|
||||||
echo "=========================================="
|
|
||||||
echo ""
|
|
||||||
echo "Runner Information:"
|
|
||||||
echo " Name: $RUNNER_NAME"
|
|
||||||
echo " Directory: $RUNNER_DIR"
|
|
||||||
echo " Status: $RUNNER_STATUS"
|
|
||||||
echo ""
|
|
||||||
echo "Configuration files:"
|
|
||||||
echo " .runner: $(ls -lh .runner 2>/dev/null | awk '{print $5}' || echo 'N/A')"
|
|
||||||
echo " config.yaml: $(ls -lh config.yaml 2>/dev/null | awk '{print $5}' || echo 'N/A')"
|
|
||||||
echo ""
|
|
||||||
echo "Useful commands:"
|
|
||||||
echo " View logs: docker-compose exec gitea-runner /data/manage.sh logs ${RUNNER_NAME}"
|
|
||||||
echo " Follow logs: docker-compose exec gitea-runner /data/manage.sh follow ${RUNNER_NAME}"
|
|
||||||
echo " Check status: docker-compose exec gitea-runner /data/manage.sh status"
|
|
||||||
echo " Restart: docker-compose exec gitea-runner /data/manage.sh restart ${RUNNER_NAME}"
|
|
||||||
echo ""
|
|
||||||
|
|
||||||
# 显示最近的日志
|
|
||||||
if [ -f "/var/log/supervisor/runner-${RUNNER_NAME}.out.log" ]; then
|
|
||||||
echo "Recent logs:"
|
|
||||||
echo "-------------------------------------------"
|
|
||||||
tail -n 20 "/var/log/supervisor/runner-${RUNNER_NAME}.out.log" 2>/dev/null || echo "No logs yet"
|
|
||||||
echo ""
|
|
||||||
fi
|
|
||||||
|
|
||||||
# 检查是否有错误
|
|
||||||
if [ -f "/var/log/supervisor/runner-${RUNNER_NAME}.err.log" ]; then
|
|
||||||
# 只查找 error/fatal/panic 级别的日志
|
|
||||||
ERR_CONTENT=$(grep -E 'level=(error|fatal|panic)' \
|
|
||||||
"/var/log/supervisor/runner-${RUNNER_NAME}.err.log" | tail -n 5 2>/dev/null)
|
|
||||||
|
|
||||||
if [ -n "$ERR_CONTENT" ]; then
|
|
||||||
echo "❌ Recent errors detected:"
|
|
||||||
echo "-------------------------------------------"
|
|
||||||
echo "$ERR_CONTENT"
|
|
||||||
echo ""
|
|
||||||
echo "Check full error log with:"
|
|
||||||
echo " docker-compose exec gitea-runner cat /var/log/supervisor/runner-${RUNNER_NAME}.err.log"
|
|
||||||
fi
|
|
||||||
fi
|
|
||||||
@@ -10,11 +10,13 @@ RED='\033[0;31m'
|
|||||||
GREEN='\033[0;32m'
|
GREEN='\033[0;32m'
|
||||||
YELLOW='\033[1;33m'
|
YELLOW='\033[1;33m'
|
||||||
NC='\033[0m'
|
NC='\033[0m'
|
||||||
|
SCRIPT_DIR=$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)
|
||||||
|
|
||||||
# 要检查的文件列表
|
# 要检查的文件列表
|
||||||
SCRIPT_FILES=(
|
SCRIPT_FILES=(
|
||||||
"entrypoint.sh"
|
"entrypoint.sh"
|
||||||
"setup.sh"
|
"setup.sh"
|
||||||
|
"upgrade.sh"
|
||||||
"register.sh"
|
"register.sh"
|
||||||
"manage.sh"
|
"manage.sh"
|
||||||
)
|
)
|
||||||
@@ -30,9 +32,10 @@ echo ""
|
|||||||
|
|
||||||
for file in "${SCRIPT_FILES[@]}"; do
|
for file in "${SCRIPT_FILES[@]}"; do
|
||||||
TOTAL_FILES=$((TOTAL_FILES + 1))
|
TOTAL_FILES=$((TOTAL_FILES + 1))
|
||||||
|
file_path="${SCRIPT_DIR}/${file}"
|
||||||
|
|
||||||
# 检查文件是否存在
|
# 检查文件是否存在
|
||||||
if [ ! -f "$file" ]; then
|
if [ ! -f "${file_path}" ]; then
|
||||||
echo -e "${RED}✗ $file - 文件不存在,跳过${NC}"
|
echo -e "${RED}✗ $file - 文件不存在,跳过${NC}"
|
||||||
continue
|
continue
|
||||||
fi
|
fi
|
||||||
@@ -41,14 +44,14 @@ for file in "${SCRIPT_FILES[@]}"; do
|
|||||||
|
|
||||||
# 检查换行符
|
# 检查换行符
|
||||||
HAS_CRLF=false
|
HAS_CRLF=false
|
||||||
if file "$file" | grep -qi "CRLF\|with CR"; then
|
if file "${file_path}" | grep -qi "CRLF\|with CR"; then
|
||||||
HAS_CRLF=true
|
HAS_CRLF=true
|
||||||
NEEDS_FIX=true
|
NEEDS_FIX=true
|
||||||
fi
|
fi
|
||||||
|
|
||||||
# 检查权限
|
# 检查权限
|
||||||
NEEDS_CHMOD=false
|
NEEDS_CHMOD=false
|
||||||
if [ ! -x "$file" ]; then
|
if [ ! -x "${file_path}" ]; then
|
||||||
NEEDS_CHMOD=true
|
NEEDS_CHMOD=true
|
||||||
NEEDS_FIX=true
|
NEEDS_FIX=true
|
||||||
fi
|
fi
|
||||||
@@ -63,13 +66,13 @@ for file in "${SCRIPT_FILES[@]}"; do
|
|||||||
|
|
||||||
# 修复换行符
|
# 修复换行符
|
||||||
if [ "$HAS_CRLF" = true ]; then
|
if [ "$HAS_CRLF" = true ]; then
|
||||||
sed -i 's/\r$//' "$file" 2>/dev/null || sed -i '' 's/\r$//' "$file" 2>/dev/null
|
sed -i 's/\r$//' "${file_path}" 2>/dev/null || sed -i '' 's/\r$//' "${file_path}" 2>/dev/null
|
||||||
echo -n -e "${YELLOW}[换行符已修复]${NC} "
|
echo -n -e "${YELLOW}[换行符已修复]${NC} "
|
||||||
fi
|
fi
|
||||||
|
|
||||||
# 修复权限
|
# 修复权限
|
||||||
if [ "$NEEDS_CHMOD" = true ]; then
|
if [ "$NEEDS_CHMOD" = true ]; then
|
||||||
chmod +x "$file"
|
chmod +x "${file_path}"
|
||||||
echo -n -e "${YELLOW}[权限已修复]${NC} "
|
echo -n -e "${YELLOW}[权限已修复]${NC} "
|
||||||
fi
|
fi
|
||||||
|
|
||||||
@@ -87,29 +90,6 @@ echo ""
|
|||||||
|
|
||||||
if [ $FIXED_COUNT -gt 0 ]; then
|
if [ $FIXED_COUNT -gt 0 ]; then
|
||||||
echo -e "${GREEN}✓ 所有问题已自动修复!${NC}"
|
echo -e "${GREEN}✓ 所有问题已自动修复!${NC}"
|
||||||
echo ""
|
|
||||||
echo "建议执行以下命令重启容器:"
|
|
||||||
echo " docker-compose down"
|
|
||||||
echo " docker-compose build --no-cache"
|
|
||||||
echo " docker-compose up -d"
|
|
||||||
echo ""
|
|
||||||
|
|
||||||
# 询问是否立即重启
|
|
||||||
read -p "是否立即重启容器? (y/N): " -n 1 -r
|
|
||||||
echo ""
|
|
||||||
if [[ $REPLY =~ ^[Yy]$ ]]; then
|
|
||||||
echo "正在重启容器..."
|
|
||||||
docker-compose down
|
|
||||||
docker-compose build --no-cache
|
|
||||||
docker-compose up -d
|
|
||||||
sleep 3
|
|
||||||
echo ""
|
|
||||||
echo "容器状态:"
|
|
||||||
docker-compose ps
|
|
||||||
echo ""
|
|
||||||
echo "查看日志:"
|
|
||||||
docker logs gitea-runner --tail=30
|
|
||||||
fi
|
|
||||||
else
|
else
|
||||||
echo -e "${GREEN}✓ 所有文件格式正确,无需修复!${NC}"
|
echo -e "${GREEN}✓ 所有文件格式正确,无需修复!${NC}"
|
||||||
fi
|
fi
|
||||||
@@ -10,31 +10,54 @@ PERSISTENT_BIN="/data/bin"
|
|||||||
RUNNER_PATH="$PERSISTENT_BIN/act_runner"
|
RUNNER_PATH="$PERSISTENT_BIN/act_runner"
|
||||||
SYSTEM_LINK="/usr/local/bin/act_runner"
|
SYSTEM_LINK="/usr/local/bin/act_runner"
|
||||||
|
|
||||||
|
# 读取环境变量配置
|
||||||
|
ENABLE_BUILDX="${ENABLE_BUILDX:-false}"
|
||||||
|
BINFMT_METHOD="${BINFMT_METHOD:-update-binfmts}" # update-binfmts 或 tonistiigi
|
||||||
|
|
||||||
# 创建必要目录
|
# 创建必要目录
|
||||||
mkdir -p /data/runners
|
mkdir -p /data/runners
|
||||||
mkdir -p "$PERSISTENT_BIN"
|
mkdir -p "$PERSISTENT_BIN"
|
||||||
mkdir -p /data/buildx
|
|
||||||
mkdir -p /var/log/supervisor
|
mkdir -p /var/log/supervisor
|
||||||
mkdir -p /var/run
|
mkdir -p /var/run
|
||||||
|
|
||||||
# ============================================
|
create_buildx_builder() {
|
||||||
# 初始化 Docker Buildx 支持
|
docker buildx create \
|
||||||
# ============================================
|
--name gitea-multiarch \
|
||||||
echo ""
|
--driver docker-container \
|
||||||
echo "Initializing Docker Buildx..."
|
--bootstrap \
|
||||||
|
--use \
|
||||||
|
--config /data/buildx/buildkitd.toml 2>/dev/null || \
|
||||||
|
docker buildx use gitea-multiarch 2>/dev/null
|
||||||
|
}
|
||||||
|
|
||||||
# 启动 Docker 守护进程(如果使用主机 socket 则跳过)
|
ensure_buildx_builder() {
|
||||||
if [ -S /var/run/docker.sock ]; then
|
docker buildx use gitea-multiarch 2>/dev/null || {
|
||||||
|
echo "⚠ Recreating Buildx builder..."
|
||||||
|
docker buildx rm gitea-multiarch 2>/dev/null || true
|
||||||
|
create_buildx_builder
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
# ============================================
|
||||||
|
# 初始化 Docker Buildx 支持(可选)
|
||||||
|
# ============================================
|
||||||
|
if [ "$ENABLE_BUILDX" = "true" ]; then
|
||||||
|
echo ""
|
||||||
|
echo "Initializing Docker Buildx..."
|
||||||
|
mkdir -p /data/buildx
|
||||||
|
|
||||||
|
# 启动 Docker 守护进程(如果使用主机 socket 则跳过)
|
||||||
|
if [ -S /var/run/docker.sock ]; then
|
||||||
echo "✓ Using host Docker socket"
|
echo "✓ Using host Docker socket"
|
||||||
else
|
else
|
||||||
echo "Starting Docker daemon..."
|
echo "Starting Docker daemon..."
|
||||||
dockerd > /var/log/dockerd.log 2>&1 &
|
dockerd > /var/log/dockerd.log 2>&1 &
|
||||||
sleep 5
|
sleep 5
|
||||||
fi
|
fi
|
||||||
|
|
||||||
# 等待 Docker 就绪
|
# 等待 Docker 就绪
|
||||||
echo "Waiting for Docker daemon..."
|
echo "Waiting for Docker daemon..."
|
||||||
for i in {1..30}; do
|
for i in {1..30}; do
|
||||||
if docker info > /dev/null 2>&1; then
|
if docker info > /dev/null 2>&1; then
|
||||||
echo "✓ Docker daemon is ready"
|
echo "✓ Docker daemon is ready"
|
||||||
break
|
break
|
||||||
@@ -45,33 +68,50 @@ for i in {1..30}; do
|
|||||||
exit 1
|
exit 1
|
||||||
fi
|
fi
|
||||||
sleep 1
|
sleep 1
|
||||||
done
|
done
|
||||||
|
|
||||||
# 注册 QEMU binfmt
|
# 注册 QEMU binfmt
|
||||||
echo ""
|
echo ""
|
||||||
echo "Registering QEMU binary formats..."
|
echo "Registering QEMU binary formats..."
|
||||||
update-binfmts --enable 2>/dev/null || {
|
|
||||||
|
if [ "$BINFMT_METHOD" = "tonistiigi" ]; then
|
||||||
|
# 使用 tonistiigi/binfmt(更可靠)
|
||||||
|
if docker run --privileged --rm tonistiigi/binfmt --install arm64,amd64 > /var/log/binfmt-install.log 2>&1; then
|
||||||
|
echo "✓ Installed binfmt via tonistiigi/binfmt"
|
||||||
|
grep -m1 "Installed" /var/log/binfmt-install.log || true
|
||||||
|
else
|
||||||
|
echo "⚠ binfmt install via tonistiigi/binfmt failed, trying local tools..."
|
||||||
|
if command -v update-binfmts > /dev/null 2>&1; then
|
||||||
|
update-binfmts --enable 2>/dev/null || true
|
||||||
|
else
|
||||||
|
echo "⚠ update-binfmts not available; multi-arch emulation may be limited"
|
||||||
|
fi
|
||||||
|
fi
|
||||||
|
else
|
||||||
|
# 使用 update-binfmts(传统方法)
|
||||||
|
update-binfmts --enable 2>/dev/null || {
|
||||||
echo "⚠ binfmt_misc not available, trying to mount..."
|
echo "⚠ binfmt_misc not available, trying to mount..."
|
||||||
mount binfmt_misc -t binfmt_misc /proc/sys/fs/binfmt_misc 2>/dev/null || true
|
mount binfmt_misc -t binfmt_misc /proc/sys/fs/binfmt_misc 2>/dev/null || true
|
||||||
update-binfmts --enable
|
update-binfmts --enable
|
||||||
}
|
}
|
||||||
|
fi
|
||||||
|
|
||||||
# 验证多架构支持
|
# 验证多架构支持
|
||||||
echo "Verifying multi-arch support..."
|
echo "Verifying multi-arch support..."
|
||||||
if docker run --rm arm64v8/alpine uname -m > /dev/null 2>&1; then
|
if docker run --rm --platform linux/arm64 alpine uname -m > /dev/null 2>&1; then
|
||||||
echo " ✓ arm64 support verified"
|
echo " ✓ arm64 support verified"
|
||||||
else
|
else
|
||||||
echo " ⚠ arm64 verification failed"
|
echo " ⚠ arm64 verification failed"
|
||||||
fi
|
fi
|
||||||
|
|
||||||
if docker run --rm amd64/alpine uname -m > /dev/null 2>&1; then
|
if docker run --rm --platform linux/amd64 alpine uname -m > /dev/null 2>&1; then
|
||||||
echo " ✓ amd64 support verified"
|
echo " ✓ amd64 support verified"
|
||||||
else
|
else
|
||||||
echo " ⚠ amd64 verification failed"
|
echo " ⚠ amd64 verification failed"
|
||||||
fi
|
fi
|
||||||
|
|
||||||
# 配置 Buildx
|
# 配置 Buildx
|
||||||
if [ ! -f "/data/buildx/.configured" ]; then
|
if [ ! -f "/data/buildx/.configured" ]; then
|
||||||
echo ""
|
echo ""
|
||||||
echo "Setting up Buildx for the first time..."
|
echo "Setting up Buildx for the first time..."
|
||||||
|
|
||||||
@@ -82,13 +122,7 @@ if [ ! -f "/data/buildx/.configured" ]; then
|
|||||||
EOF
|
EOF
|
||||||
|
|
||||||
# 创建 Buildx builder
|
# 创建 Buildx builder
|
||||||
docker buildx create \
|
create_buildx_builder
|
||||||
--name gitea-multiarch \
|
|
||||||
--driver docker-container \
|
|
||||||
--bootstrap \
|
|
||||||
--use \
|
|
||||||
--config /data/buildx/buildkitd.toml 2>/dev/null || \
|
|
||||||
docker buildx use gitea-multiarch 2>/dev/null
|
|
||||||
|
|
||||||
# 验证
|
# 验证
|
||||||
echo "Verifying Buildx..."
|
echo "Verifying Buildx..."
|
||||||
@@ -99,22 +133,15 @@ EOF
|
|||||||
|
|
||||||
echo "✓ Buildx configured successfully!"
|
echo "✓ Buildx configured successfully!"
|
||||||
docker buildx inspect | grep "Platforms:" | head -1
|
docker buildx inspect | grep "Platforms:" | head -1
|
||||||
else
|
else
|
||||||
echo "✓ Buildx already configured"
|
echo "✓ Buildx already configured"
|
||||||
|
|
||||||
# 确保 builder 可用
|
# 确保 builder 可用
|
||||||
docker buildx use gitea-multiarch 2>/dev/null || {
|
ensure_buildx_builder
|
||||||
echo "⚠ Recreating Buildx builder..."
|
fi
|
||||||
docker buildx rm gitea-multiarch 2>/dev/null || true
|
|
||||||
docker buildx create \
|
|
||||||
--name gitea-multiarch \
|
|
||||||
--driver docker-container \
|
|
||||||
--bootstrap \
|
|
||||||
--use 2>/dev/null
|
|
||||||
}
|
|
||||||
fi
|
|
||||||
|
|
||||||
echo ""
|
echo ""
|
||||||
|
fi
|
||||||
|
|
||||||
# ============================================
|
# ============================================
|
||||||
# 检查 act_runner 安装
|
# 检查 act_runner 安装
|
||||||
@@ -162,7 +189,7 @@ else
|
|||||||
echo "⚠ act_runner not installed yet!"
|
echo "⚠ act_runner not installed yet!"
|
||||||
echo ""
|
echo ""
|
||||||
echo "Please run the setup script first:"
|
echo "Please run the setup script first:"
|
||||||
echo " docker-compose exec gitea-runner /data/setup.sh"
|
echo " docker compose exec gitea-runner /data/setup.sh"
|
||||||
echo ""
|
echo ""
|
||||||
echo "Container is waiting..."
|
echo "Container is waiting..."
|
||||||
|
|
||||||
@@ -220,7 +247,7 @@ if [ $RUNNER_COUNT -eq 0 ]; then
|
|||||||
echo "⚠ No runners registered yet!"
|
echo "⚠ No runners registered yet!"
|
||||||
echo ""
|
echo ""
|
||||||
echo "Please run the register script to add a runner:"
|
echo "Please run the register script to add a runner:"
|
||||||
echo " docker-compose exec gitea-runner /data/register.sh"
|
echo " docker compose exec gitea-runner /data/register.sh"
|
||||||
echo ""
|
echo ""
|
||||||
fi
|
fi
|
||||||
|
|
||||||
@@ -11,7 +11,7 @@ if ! command -v act_runner &> /dev/null; then
|
|||||||
echo "✗ act_runner is not installed!"
|
echo "✗ act_runner is not installed!"
|
||||||
echo ""
|
echo ""
|
||||||
echo "Please run the setup script first:"
|
echo "Please run the setup script first:"
|
||||||
echo " docker-compose exec gitea-runner /data/setup.sh"
|
echo " docker compose exec gitea-runner /data/setup.sh"
|
||||||
exit 1
|
exit 1
|
||||||
fi
|
fi
|
||||||
|
|
||||||
@@ -20,12 +20,17 @@ echo ""
|
|||||||
|
|
||||||
# 获取注册信息并验证
|
# 获取注册信息并验证
|
||||||
while true; do
|
while true; do
|
||||||
|
if [ -n "${GITEA_INSTANCE:-}" ]; then
|
||||||
|
echo "Using Gitea instance from env: $GITEA_INSTANCE"
|
||||||
|
else
|
||||||
read -p "Enter Gitea instance URL (e.g., https://gitea.example.com): " GITEA_INSTANCE
|
read -p "Enter Gitea instance URL (e.g., https://gitea.example.com): " GITEA_INSTANCE
|
||||||
|
fi
|
||||||
|
|
||||||
# 验证 URL 格式
|
# 验证 URL 格式
|
||||||
if [[ ! "$GITEA_INSTANCE" =~ ^https?:// ]]; then
|
if [[ ! "$GITEA_INSTANCE" =~ ^https?:// ]]; then
|
||||||
echo "✗ Error: URL must start with http:// or https://"
|
echo "✗ Error: URL must start with http:// or https://"
|
||||||
echo ""
|
echo ""
|
||||||
|
unset GITEA_INSTANCE
|
||||||
continue
|
continue
|
||||||
fi
|
fi
|
||||||
|
|
||||||
@@ -35,20 +40,26 @@ while true; do
|
|||||||
break
|
break
|
||||||
done
|
done
|
||||||
|
|
||||||
read -p "Enter registration token: " GITEA_TOKEN
|
if [ -n "${GITEA_TOKEN:-}" ]; then
|
||||||
|
echo "Using registration token from env."
|
||||||
|
else
|
||||||
|
read -p "Enter registration token: " GITEA_TOKEN
|
||||||
|
fi
|
||||||
|
|
||||||
if [ -z "$GITEA_TOKEN" ]; then
|
if [ -z "$GITEA_TOKEN" ]; then
|
||||||
echo "✗ Error: Token cannot be empty!"
|
echo "✗ Error: Token cannot be empty!"
|
||||||
exit 1
|
exit 1
|
||||||
fi
|
fi
|
||||||
|
|
||||||
read -p "Enter runner name (default: docker-runner): " RUNNER_NAME
|
DEFAULT_NAME="${DEFAULT_RUNNER_NAME:-docker-runner}"
|
||||||
RUNNER_NAME=${RUNNER_NAME:-docker-runner}
|
read -p "Enter runner name (default: $DEFAULT_NAME): " RUNNER_NAME
|
||||||
|
RUNNER_NAME=${RUNNER_NAME:-$DEFAULT_NAME}
|
||||||
|
|
||||||
# 多个 label(逗号分隔,无空格)
|
# 多个 label(逗号分隔,无空格)
|
||||||
# ubuntu-22.04:host://ubuntu:22.04,ubuntu-20.04:host://ubuntu:20.04,node:docker://node:18
|
# ubuntu-22.04:host://ubuntu:22.04,ubuntu-20.04:host://ubuntu:20.04,node:docker://node:18
|
||||||
read -p "Enter runner labels (default: ubuntu-22.04:host://ubuntu:22.04): " RUNNER_LABELS
|
DEFAULT_LABEL="${DEFAULT_RUNNER_LABEL:-ubuntu-22.04:host://ubuntu:22.04}"
|
||||||
RUNNER_LABELS=${RUNNER_LABELS:-ubuntu-22.04:host://ubuntu:22.04}
|
read -p "Enter runner labels (default: $DEFAULT_LABEL): " RUNNER_LABELS
|
||||||
|
RUNNER_LABELS=${RUNNER_LABELS:-$DEFAULT_LABEL}
|
||||||
|
|
||||||
# 创建 runner 目录
|
# 创建 runner 目录
|
||||||
RUNNER_DIR="/data/runners/${RUNNER_NAME}"
|
RUNNER_DIR="/data/runners/${RUNNER_NAME}"
|
||||||
@@ -122,12 +133,11 @@ echo "✓ Configuration file generated!"
|
|||||||
# 创建缓存目录
|
# 创建缓存目录
|
||||||
mkdir -p cache
|
mkdir -p cache
|
||||||
|
|
||||||
# 使用 Python 修改配置(最可靠的方法)
|
# 使用 Python 修改配置
|
||||||
echo ""
|
echo ""
|
||||||
echo "Configuring runner settings..."
|
echo "Configuring runner settings..."
|
||||||
|
|
||||||
if command -v python3 &> /dev/null; then
|
python3 << PYEOF
|
||||||
python3 << PYEOF
|
|
||||||
import yaml
|
import yaml
|
||||||
import sys
|
import sys
|
||||||
|
|
||||||
@@ -150,7 +160,7 @@ try:
|
|||||||
|
|
||||||
# 使用实际注册的 labels
|
# 使用实际注册的 labels
|
||||||
config['runner']['labels'] = registered_labels
|
config['runner']['labels'] = registered_labels
|
||||||
config['runner']['capacity'] = 2
|
config['runner']['capacity'] = 4
|
||||||
|
|
||||||
# 启用缓存
|
# 启用缓存
|
||||||
if 'cache' not in config:
|
if 'cache' not in config:
|
||||||
@@ -164,7 +174,7 @@ try:
|
|||||||
|
|
||||||
print("✓ Configuration updated using Python")
|
print("✓ Configuration updated using Python")
|
||||||
print(f" - Labels: {registered_labels}")
|
print(f" - Labels: {registered_labels}")
|
||||||
print(f" - Capacity: 2")
|
print(f" - Capacity: 4")
|
||||||
print(f" - Cache enabled: ./cache")
|
print(f" - Cache enabled: ./cache")
|
||||||
sys.exit(0)
|
sys.exit(0)
|
||||||
|
|
||||||
@@ -173,32 +183,6 @@ except Exception as e:
|
|||||||
sys.exit(1)
|
sys.exit(1)
|
||||||
PYEOF
|
PYEOF
|
||||||
|
|
||||||
PYTHON_EXIT=$?
|
|
||||||
|
|
||||||
if [ $PYTHON_EXIT -ne 0 ]; then
|
|
||||||
echo ""
|
|
||||||
echo "⚠ Python configuration failed, using basic sed..."
|
|
||||||
|
|
||||||
# 基本的 sed 修改(只修改简单的值,不动 labels)
|
|
||||||
sed -i 's/capacity: 1/capacity: 2/g' config.yaml || true
|
|
||||||
sed -i 's/enabled: false/enabled: true/g' config.yaml || true
|
|
||||||
sed -i 's|dir: ""|dir: ./cache|g' config.yaml || true
|
|
||||||
|
|
||||||
echo "✓ Basic configuration applied"
|
|
||||||
echo " Note: Please manually verify labels in config.yaml match .runner"
|
|
||||||
fi
|
|
||||||
else
|
|
||||||
echo "⚠ Python3 not found, applying basic configuration..."
|
|
||||||
|
|
||||||
# 基本的 sed 修改
|
|
||||||
sed -i 's/capacity: 1/capacity: 2/g' config.yaml || true
|
|
||||||
sed -i 's/enabled: false/enabled: true/g' config.yaml || true
|
|
||||||
sed -i 's|dir: ""|dir: ./cache|g' config.yaml || true
|
|
||||||
|
|
||||||
echo "✓ Basic configuration applied"
|
|
||||||
echo " Note: Labels will use act_runner defaults"
|
|
||||||
fi
|
|
||||||
|
|
||||||
# 验证配置文件
|
# 验证配置文件
|
||||||
echo ""
|
echo ""
|
||||||
echo "Validating configuration..."
|
echo "Validating configuration..."
|
||||||
@@ -290,10 +274,10 @@ echo " .runner: $(ls -lh .runner 2>/dev/null | awk '{print $5}' || echo 'N
|
|||||||
echo " config.yaml: $(ls -lh config.yaml 2>/dev/null | awk '{print $5}' || echo 'N/A')"
|
echo " config.yaml: $(ls -lh config.yaml 2>/dev/null | awk '{print $5}' || echo 'N/A')"
|
||||||
echo ""
|
echo ""
|
||||||
echo "Useful commands:"
|
echo "Useful commands:"
|
||||||
echo " View logs: docker-compose exec gitea-runner /data/manage.sh logs ${RUNNER_NAME}"
|
echo " View logs: docker compose exec gitea-runner /data/manage.sh logs ${RUNNER_NAME}"
|
||||||
echo " Follow logs: docker-compose exec gitea-runner /data/manage.sh follow ${RUNNER_NAME}"
|
echo " Follow logs: docker compose exec gitea-runner /data/manage.sh follow ${RUNNER_NAME}"
|
||||||
echo " Check status: docker-compose exec gitea-runner /data/manage.sh status"
|
echo " Check status: docker compose exec gitea-runner /data/manage.sh status"
|
||||||
echo " Restart: docker-compose exec gitea-runner /data/manage.sh restart ${RUNNER_NAME}"
|
echo " Restart: docker compose exec gitea-runner /data/manage.sh restart ${RUNNER_NAME}"
|
||||||
echo ""
|
echo ""
|
||||||
|
|
||||||
# 显示最近的日志
|
# 显示最近的日志
|
||||||
@@ -316,6 +300,6 @@ if [ -f "/var/log/supervisor/runner-${RUNNER_NAME}.err.log" ]; then
|
|||||||
echo "$ERR_CONTENT"
|
echo "$ERR_CONTENT"
|
||||||
echo ""
|
echo ""
|
||||||
echo "Check full error log with:"
|
echo "Check full error log with:"
|
||||||
echo " docker-compose exec gitea-runner cat /var/log/supervisor/runner-${RUNNER_NAME}.err.log"
|
echo " docker compose exec gitea-runner cat /var/log/supervisor/runner-${RUNNER_NAME}.err.log"
|
||||||
fi
|
fi
|
||||||
fi
|
fi
|
||||||
@@ -1,5 +1,13 @@
|
|||||||
#!/bin/bash
|
#!/bin/bash
|
||||||
|
|
||||||
|
# 共享升级脚本中的版本探测逻辑
|
||||||
|
SCRIPT_DIR=$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)
|
||||||
|
UPGRADE_HELPER="${SCRIPT_DIR}/upgrade.sh"
|
||||||
|
DEFAULT_RUNNER_VERSION_FALLBACK="${DEFAULT_RUNNER_VERSION_FALLBACK:-0.2.13}"
|
||||||
|
|
||||||
|
# shellcheck source=/dev/null
|
||||||
|
source "${UPGRADE_HELPER}"
|
||||||
|
|
||||||
echo "=========================================="
|
echo "=========================================="
|
||||||
echo " Gitea Runner Installation Script "
|
echo " Gitea Runner Installation Script "
|
||||||
echo "=========================================="
|
echo "=========================================="
|
||||||
@@ -30,8 +38,18 @@ fi
|
|||||||
# 获取要安装的版本
|
# 获取要安装的版本
|
||||||
echo "Available versions: https://dl.gitea.com/act_runner/"
|
echo "Available versions: https://dl.gitea.com/act_runner/"
|
||||||
echo ""
|
echo ""
|
||||||
read -p "Enter version to install (default: 0.2.13): " RUNNER_VERSION
|
|
||||||
RUNNER_VERSION=${RUNNER_VERSION:-0.2.13}
|
DEFAULT_RUNNER_VERSION="${DEFAULT_RUNNER_VERSION_FALLBACK}"
|
||||||
|
DEFAULT_RUNNER_VERSION=$(resolve_latest_version_or_fallback "${DEFAULT_RUNNER_VERSION_FALLBACK}")
|
||||||
|
|
||||||
|
echo "Default install version: ${DEFAULT_RUNNER_VERSION}"
|
||||||
|
read -p "Enter version to install (default: ${DEFAULT_RUNNER_VERSION}): " RUNNER_VERSION
|
||||||
|
RUNNER_VERSION=${RUNNER_VERSION:-$DEFAULT_RUNNER_VERSION}
|
||||||
|
|
||||||
|
if ! validate_version "${RUNNER_VERSION}"; then
|
||||||
|
echo "✗ Invalid version format: ${RUNNER_VERSION}"
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
ARCH=$(uname -m)
|
ARCH=$(uname -m)
|
||||||
case "$ARCH" in
|
case "$ARCH" in
|
||||||
@@ -96,6 +114,7 @@ echo ""
|
|||||||
# 下载到持久化目录
|
# 下载到持久化目录
|
||||||
if curl -L "$DOWNLOAD_URL" -o "$INSTALL_PATH"; then
|
if curl -L "$DOWNLOAD_URL" -o "$INSTALL_PATH"; then
|
||||||
chmod +x "$INSTALL_PATH"
|
chmod +x "$INSTALL_PATH"
|
||||||
|
validate_binary_arch_or_fail "$INSTALL_PATH" "$RUNNER_ARCH"
|
||||||
|
|
||||||
# 同时创建软链接到系统路径
|
# 同时创建软链接到系统路径
|
||||||
ln -sf "$INSTALL_PATH" "$SYSTEM_PATH"
|
ln -sf "$INSTALL_PATH" "$SYSTEM_PATH"
|
||||||
@@ -112,10 +131,9 @@ if curl -L "$DOWNLOAD_URL" -o "$INSTALL_PATH"; then
|
|||||||
echo ""
|
echo ""
|
||||||
echo "Next steps:"
|
echo "Next steps:"
|
||||||
echo "1. Register the runner:"
|
echo "1. Register the runner:"
|
||||||
echo " docker-compose exec gitea-runner /data/register.sh"
|
echo " docker compose exec gitea-runner /data/register.sh"
|
||||||
echo ""
|
echo ""
|
||||||
echo "2. Restart the container:"
|
echo "2. Start using the runner after registration."
|
||||||
echo " docker-compose restart"
|
|
||||||
echo ""
|
echo ""
|
||||||
echo "Note: act_runner is saved in persistent storage"
|
echo "Note: act_runner is saved in persistent storage"
|
||||||
echo " and will survive container restarts."
|
echo " and will survive container restarts."
|
||||||
@@ -0,0 +1,368 @@
|
|||||||
|
#!/bin/bash
|
||||||
|
|
||||||
|
INSTALL_PATH="/data/bin/act_runner"
|
||||||
|
SYSTEM_PATH="/usr/local/bin/act_runner"
|
||||||
|
RUNNER_INDEX_URL="${RUNNER_INDEX_URL:-https://dl.gitea.com/act_runner/}"
|
||||||
|
|
||||||
|
print_usage() {
|
||||||
|
cat <<'EOF'
|
||||||
|
Usage: /data/upgrade.sh [options]
|
||||||
|
|
||||||
|
Upgrade act_runner without re-registering existing runners.
|
||||||
|
|
||||||
|
Options:
|
||||||
|
--version <version> Upgrade to a specific version instead of auto-detecting latest
|
||||||
|
--arch <arch> Override detected architecture: amd64, arm64, arm-7
|
||||||
|
--runner <name> Restart only the specified runner (can be used multiple times)
|
||||||
|
-y, --yes Skip confirmation prompt
|
||||||
|
-h, --help Show this help message
|
||||||
|
EOF
|
||||||
|
}
|
||||||
|
|
||||||
|
log() {
|
||||||
|
echo "$@"
|
||||||
|
}
|
||||||
|
|
||||||
|
fail() {
|
||||||
|
echo "✗ $*" >&2
|
||||||
|
exit 1
|
||||||
|
}
|
||||||
|
|
||||||
|
extract_version_number() {
|
||||||
|
sed -nE 's/.*version[[:space:]]+v?([0-9]+\.[0-9]+\.[0-9]+).*/\1/p' | head -n 1
|
||||||
|
}
|
||||||
|
|
||||||
|
extract_versions_from_listing() {
|
||||||
|
grep -oE '/act_runner/[0-9]+\.[0-9]+\.[0-9]+/' | \
|
||||||
|
sed -E 's#^/act_runner/([0-9]+\.[0-9]+\.[0-9]+)/$#\1#' | \
|
||||||
|
awk '!seen[$0]++'
|
||||||
|
}
|
||||||
|
|
||||||
|
pick_latest_version() {
|
||||||
|
awk 'NF' | sort -V | tail -n 1
|
||||||
|
}
|
||||||
|
|
||||||
|
fetch_latest_version() {
|
||||||
|
curl -fsSL "${RUNNER_INDEX_URL}" | extract_versions_from_listing | pick_latest_version
|
||||||
|
}
|
||||||
|
|
||||||
|
resolve_latest_version_or_fallback() {
|
||||||
|
local fallback_version=$1
|
||||||
|
local fetcher_cmd=${2:-fetch_latest_version}
|
||||||
|
local detected_version=""
|
||||||
|
|
||||||
|
if command -v "${fetcher_cmd}" >/dev/null 2>&1; then
|
||||||
|
detected_version=$("${fetcher_cmd}" 2>/dev/null || true)
|
||||||
|
fi
|
||||||
|
|
||||||
|
if [ -n "${detected_version}" ] && validate_version "${detected_version}"; then
|
||||||
|
echo "${detected_version}"
|
||||||
|
else
|
||||||
|
echo "${fallback_version}"
|
||||||
|
fi
|
||||||
|
}
|
||||||
|
|
||||||
|
validate_version() {
|
||||||
|
local version=$1
|
||||||
|
|
||||||
|
[[ "${version}" =~ ^[0-9]+\.[0-9]+\.[0-9]+$ ]]
|
||||||
|
}
|
||||||
|
|
||||||
|
detect_runner_arch() {
|
||||||
|
local arch
|
||||||
|
arch=$(uname -m)
|
||||||
|
|
||||||
|
case "${arch}" in
|
||||||
|
x86_64)
|
||||||
|
echo "amd64"
|
||||||
|
;;
|
||||||
|
aarch64|arm64)
|
||||||
|
echo "arm64"
|
||||||
|
;;
|
||||||
|
armv7l)
|
||||||
|
echo "arm-7"
|
||||||
|
;;
|
||||||
|
*)
|
||||||
|
fail "Unsupported architecture: ${arch}"
|
||||||
|
;;
|
||||||
|
esac
|
||||||
|
}
|
||||||
|
|
||||||
|
detect_binary_arch_from_file() {
|
||||||
|
local binary_path=$1
|
||||||
|
local magic=""
|
||||||
|
local low_byte=""
|
||||||
|
local high_byte=""
|
||||||
|
local machine_hex=""
|
||||||
|
|
||||||
|
if [ ! -f "${binary_path}" ]; then
|
||||||
|
echo "unknown"
|
||||||
|
return 0
|
||||||
|
fi
|
||||||
|
|
||||||
|
magic=$(od -An -t x1 -N 4 "${binary_path}" 2>/dev/null | tr -d ' \n')
|
||||||
|
if [ "${magic}" != "7f454c46" ]; then
|
||||||
|
echo "unknown"
|
||||||
|
return 0
|
||||||
|
fi
|
||||||
|
|
||||||
|
read -r low_byte high_byte <<<"$(od -An -t x1 -j 18 -N 2 "${binary_path}" 2>/dev/null)"
|
||||||
|
if [ -z "${low_byte:-}" ] || [ -z "${high_byte:-}" ]; then
|
||||||
|
echo "unknown"
|
||||||
|
return 0
|
||||||
|
fi
|
||||||
|
|
||||||
|
machine_hex="${high_byte}${low_byte}"
|
||||||
|
|
||||||
|
case "${machine_hex}" in
|
||||||
|
003e)
|
||||||
|
echo "amd64"
|
||||||
|
;;
|
||||||
|
00b7)
|
||||||
|
echo "arm64"
|
||||||
|
;;
|
||||||
|
0028)
|
||||||
|
echo "arm-7"
|
||||||
|
;;
|
||||||
|
*)
|
||||||
|
echo "unknown"
|
||||||
|
;;
|
||||||
|
esac
|
||||||
|
}
|
||||||
|
|
||||||
|
binary_arch_matches_target() {
|
||||||
|
local binary_path=$1
|
||||||
|
local expected_arch=$2
|
||||||
|
local detected_arch=""
|
||||||
|
|
||||||
|
detected_arch=$(detect_binary_arch_from_file "${binary_path}")
|
||||||
|
[ "${detected_arch}" = "${expected_arch}" ]
|
||||||
|
}
|
||||||
|
|
||||||
|
validate_binary_arch_or_fail() {
|
||||||
|
local binary_path=$1
|
||||||
|
local expected_arch=$2
|
||||||
|
local detected_arch=""
|
||||||
|
|
||||||
|
detected_arch=$(detect_binary_arch_from_file "${binary_path}")
|
||||||
|
|
||||||
|
if [ "${detected_arch}" = "unknown" ]; then
|
||||||
|
fail "Unable to detect downloaded binary architecture: ${binary_path}"
|
||||||
|
fi
|
||||||
|
|
||||||
|
if [ "${detected_arch}" != "${expected_arch}" ]; then
|
||||||
|
fail "Downloaded binary architecture mismatch: expected ${expected_arch}, got ${detected_arch}"
|
||||||
|
fi
|
||||||
|
}
|
||||||
|
|
||||||
|
get_installed_version() {
|
||||||
|
local output=""
|
||||||
|
|
||||||
|
if [ -x "${INSTALL_PATH}" ]; then
|
||||||
|
output=$("${INSTALL_PATH}" --version 2>/dev/null || true)
|
||||||
|
elif command -v act_runner >/dev/null 2>&1; then
|
||||||
|
output=$(act_runner --version 2>/dev/null || true)
|
||||||
|
fi
|
||||||
|
|
||||||
|
if [ -n "${output}" ]; then
|
||||||
|
extract_version_number <<<"${output}"
|
||||||
|
fi
|
||||||
|
}
|
||||||
|
|
||||||
|
get_installed_binary_arch() {
|
||||||
|
if [ -x "${INSTALL_PATH}" ]; then
|
||||||
|
detect_binary_arch_from_file "${INSTALL_PATH}"
|
||||||
|
elif command -v act_runner >/dev/null 2>&1; then
|
||||||
|
detect_binary_arch_from_file "$(command -v act_runner)"
|
||||||
|
else
|
||||||
|
echo "unknown"
|
||||||
|
fi
|
||||||
|
}
|
||||||
|
|
||||||
|
list_registered_runners() {
|
||||||
|
local runner_dir
|
||||||
|
|
||||||
|
if [ ! -d "/data/runners" ]; then
|
||||||
|
return 0
|
||||||
|
fi
|
||||||
|
|
||||||
|
for runner_dir in /data/runners/*/; do
|
||||||
|
if [ -d "${runner_dir}" ] && [ -f "${runner_dir}/.runner" ]; then
|
||||||
|
basename "${runner_dir}"
|
||||||
|
fi
|
||||||
|
done
|
||||||
|
}
|
||||||
|
|
||||||
|
build_download_url() {
|
||||||
|
local version=$1
|
||||||
|
local arch=$2
|
||||||
|
|
||||||
|
echo "https://dl.gitea.com/act_runner/${version}/act_runner-${version}-linux-${arch}"
|
||||||
|
}
|
||||||
|
|
||||||
|
confirm_or_exit() {
|
||||||
|
local auto_confirm=$1
|
||||||
|
|
||||||
|
if [ "${auto_confirm}" = "true" ]; then
|
||||||
|
return 0
|
||||||
|
fi
|
||||||
|
|
||||||
|
read -p "Proceed with upgrade? (y/N): " -n 1 -r
|
||||||
|
echo
|
||||||
|
if [[ ! "${REPLY}" =~ ^[Yy]$ ]]; then
|
||||||
|
echo "Upgrade cancelled."
|
||||||
|
exit 0
|
||||||
|
fi
|
||||||
|
}
|
||||||
|
|
||||||
|
download_and_install() {
|
||||||
|
local version=$1
|
||||||
|
local arch=$2
|
||||||
|
local url
|
||||||
|
local tmp_path
|
||||||
|
local downloaded_version
|
||||||
|
|
||||||
|
mkdir -p "$(dirname "${INSTALL_PATH}")"
|
||||||
|
url=$(build_download_url "${version}" "${arch}")
|
||||||
|
tmp_path=$(mktemp "${INSTALL_PATH}.tmp.XXXXXX")
|
||||||
|
|
||||||
|
trap 'rm -f "${tmp_path}"' EXIT
|
||||||
|
|
||||||
|
log "Downloading ${url}"
|
||||||
|
curl -fL "${url}" -o "${tmp_path}"
|
||||||
|
chmod +x "${tmp_path}"
|
||||||
|
validate_binary_arch_or_fail "${tmp_path}" "${arch}"
|
||||||
|
|
||||||
|
downloaded_version=$("${tmp_path}" --version 2>/dev/null | extract_version_number)
|
||||||
|
if [ "${downloaded_version}" != "${version}" ]; then
|
||||||
|
fail "Downloaded binary version mismatch: expected ${version}, got ${downloaded_version:-unknown}"
|
||||||
|
fi
|
||||||
|
|
||||||
|
mv "${tmp_path}" "${INSTALL_PATH}"
|
||||||
|
ln -sf "${INSTALL_PATH}" "${SYSTEM_PATH}"
|
||||||
|
trap - EXIT
|
||||||
|
}
|
||||||
|
|
||||||
|
restart_runner_process() {
|
||||||
|
local runner_name=$1
|
||||||
|
|
||||||
|
if ! command -v supervisorctl >/dev/null 2>&1; then
|
||||||
|
log "⚠ supervisorctl not found, please restart the container manually."
|
||||||
|
return 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
log "Restarting runner '${runner_name}'..."
|
||||||
|
supervisorctl restart "runner-${runner_name}"
|
||||||
|
}
|
||||||
|
|
||||||
|
restart_selected_runners() {
|
||||||
|
local requested_runners=("$@")
|
||||||
|
local runner_names=()
|
||||||
|
local runner_name
|
||||||
|
|
||||||
|
if [ "${#requested_runners[@]}" -gt 0 ]; then
|
||||||
|
runner_names=("${requested_runners[@]}")
|
||||||
|
else
|
||||||
|
while IFS= read -r runner_name; do
|
||||||
|
[ -n "${runner_name}" ] && runner_names+=("${runner_name}")
|
||||||
|
done < <(list_registered_runners)
|
||||||
|
fi
|
||||||
|
|
||||||
|
if [ "${#runner_names[@]}" -eq 0 ]; then
|
||||||
|
log "No registered runners found. Binary upgraded, no process restart needed."
|
||||||
|
return 0
|
||||||
|
fi
|
||||||
|
|
||||||
|
for runner_name in "${runner_names[@]}"; do
|
||||||
|
restart_runner_process "${runner_name}"
|
||||||
|
done
|
||||||
|
}
|
||||||
|
|
||||||
|
main() {
|
||||||
|
local target_version=""
|
||||||
|
local target_arch=""
|
||||||
|
local auto_confirm="false"
|
||||||
|
local current_version=""
|
||||||
|
local current_arch="unknown"
|
||||||
|
local runner_names=()
|
||||||
|
|
||||||
|
while [ $# -gt 0 ]; do
|
||||||
|
case "$1" in
|
||||||
|
--version)
|
||||||
|
[ $# -ge 2 ] || fail "--version requires a value"
|
||||||
|
target_version=$2
|
||||||
|
shift 2
|
||||||
|
;;
|
||||||
|
--arch)
|
||||||
|
[ $# -ge 2 ] || fail "--arch requires a value"
|
||||||
|
target_arch=$2
|
||||||
|
shift 2
|
||||||
|
;;
|
||||||
|
--runner)
|
||||||
|
[ $# -ge 2 ] || fail "--runner requires a value"
|
||||||
|
runner_names+=("$2")
|
||||||
|
shift 2
|
||||||
|
;;
|
||||||
|
-y|--yes)
|
||||||
|
auto_confirm="true"
|
||||||
|
shift
|
||||||
|
;;
|
||||||
|
-h|--help)
|
||||||
|
print_usage
|
||||||
|
exit 0
|
||||||
|
;;
|
||||||
|
*)
|
||||||
|
fail "Unknown option: $1"
|
||||||
|
;;
|
||||||
|
esac
|
||||||
|
done
|
||||||
|
|
||||||
|
current_version=$(get_installed_version || true)
|
||||||
|
|
||||||
|
if [ -z "${target_version}" ]; then
|
||||||
|
log "Fetching latest act_runner version from ${RUNNER_INDEX_URL}"
|
||||||
|
target_version=$(fetch_latest_version)
|
||||||
|
[ -n "${target_version}" ] || fail "Unable to detect the latest act_runner version"
|
||||||
|
fi
|
||||||
|
|
||||||
|
validate_version "${target_version}" || fail "Invalid version: ${target_version}"
|
||||||
|
|
||||||
|
if [ -z "${target_arch}" ]; then
|
||||||
|
target_arch=$(detect_runner_arch)
|
||||||
|
fi
|
||||||
|
|
||||||
|
current_arch=$(get_installed_binary_arch || true)
|
||||||
|
|
||||||
|
if [ -n "${current_version}" ] && \
|
||||||
|
[ "${current_version}" = "${target_version}" ] && \
|
||||||
|
[ "${current_arch}" = "${target_arch}" ]; then
|
||||||
|
log "act_runner is already at the requested version and architecture: ${current_version} (${current_arch})"
|
||||||
|
exit 0
|
||||||
|
fi
|
||||||
|
|
||||||
|
log "Upgrade summary:"
|
||||||
|
log " Current version: ${current_version:-not installed}"
|
||||||
|
log " Current arch: ${current_arch:-unknown}"
|
||||||
|
log " Target version: ${target_version}"
|
||||||
|
log " Architecture: ${target_arch}"
|
||||||
|
log " Install path: ${INSTALL_PATH}"
|
||||||
|
if [ "${#runner_names[@]}" -gt 0 ]; then
|
||||||
|
log " Restart target: ${runner_names[*]}"
|
||||||
|
else
|
||||||
|
log " Restart target: all registered runners"
|
||||||
|
fi
|
||||||
|
echo
|
||||||
|
|
||||||
|
confirm_or_exit "${auto_confirm}"
|
||||||
|
download_and_install "${target_version}" "${target_arch}"
|
||||||
|
restart_selected_runners "${runner_names[@]}"
|
||||||
|
|
||||||
|
log
|
||||||
|
log "✓ act_runner upgraded successfully"
|
||||||
|
log " Installed version: $(get_installed_version || echo unknown)"
|
||||||
|
}
|
||||||
|
|
||||||
|
if [[ "${BASH_SOURCE[0]}" == "${0}" ]]; then
|
||||||
|
set -euo pipefail
|
||||||
|
main "$@"
|
||||||
|
fi
|
||||||
@@ -0,0 +1,4 @@
|
|||||||
|
GITEA_INSTANCE=https://git.mytsl.cn
|
||||||
|
GITEA_TOKEN=
|
||||||
|
DEFAULT_RUNNER_NAME=buildx-archlinux
|
||||||
|
DEFAULT_RUNNER_LABEL=archlinux:host://archlinux:latest,company-server:host://archlinux:latest,buildx-archlinux:host://archlinux:latest
|
||||||
@@ -0,0 +1,74 @@
|
|||||||
|
FROM archlinux:latest
|
||||||
|
|
||||||
|
SHELL ["/bin/bash", "-o", "pipefail", "-c"]
|
||||||
|
|
||||||
|
# 初始化密钥和软件源
|
||||||
|
RUN pacman-key --init && \
|
||||||
|
pacman-key --populate archlinux && \
|
||||||
|
sed -i 's/^#ParallelDownloads/ParallelDownloads/' /etc/pacman.conf && \
|
||||||
|
printf "\n[archlinuxcn]\nServer = https://mirrors.ustc.edu.cn/archlinuxcn/\$arch\n" >> /etc/pacman.conf && \
|
||||||
|
pacman -Syy --noconfirm
|
||||||
|
|
||||||
|
# 安装基础工具、Buildx 支持和开发环境
|
||||||
|
RUN pacman -Syu --noconfirm && \
|
||||||
|
pacman -S --noconfirm --disable-download-timeout archlinuxcn-keyring && \
|
||||||
|
pacman -S --noconfirm --needed \
|
||||||
|
base-devel \
|
||||||
|
ca-certificates \
|
||||||
|
clang \
|
||||||
|
lld \
|
||||||
|
libc++ \
|
||||||
|
libc++abi \
|
||||||
|
cmake \
|
||||||
|
ninja \
|
||||||
|
curl \
|
||||||
|
git \
|
||||||
|
nodejs \
|
||||||
|
npm \
|
||||||
|
python \
|
||||||
|
python-pyyaml \
|
||||||
|
python-pip \
|
||||||
|
paru \
|
||||||
|
sudo \
|
||||||
|
vim \
|
||||||
|
supervisor \
|
||||||
|
qemu-user-static \
|
||||||
|
qemu-user-static-binfmt \
|
||||||
|
fakeroot \
|
||||||
|
docker \
|
||||||
|
docker-buildx \
|
||||||
|
&& pacman -Scc --noconfirm
|
||||||
|
|
||||||
|
# AUR 用户(paru 需非 root 运行)
|
||||||
|
RUN useradd -m aur && \
|
||||||
|
echo "aur ALL=(ALL) NOPASSWD: ALL" > /etc/sudoers.d/aur && \
|
||||||
|
chmod 0440 /etc/sudoers.d/aur
|
||||||
|
|
||||||
|
# 安装额外的 AUR 工具
|
||||||
|
USER aur
|
||||||
|
WORKDIR /home/aur
|
||||||
|
RUN paru -S --noconfirm --needed --noprogressbar conan-bin llvm-mingw-w64-toolchain-ucrt-bin && \
|
||||||
|
paru -Scc --noconfirm && \
|
||||||
|
rm -rf ~/.cache/paru
|
||||||
|
|
||||||
|
# 切换回 root
|
||||||
|
USER root
|
||||||
|
WORKDIR /root
|
||||||
|
|
||||||
|
# 交叉工具链可用:确保 windres 在 PATH,避免 CMake 查找失败
|
||||||
|
ENV PATH="/opt/llvm-mingw/llvm-mingw-ucrt/bin:${PATH}"
|
||||||
|
ENV RC="/opt/llvm-mingw/llvm-mingw-ucrt/bin/x86_64-w64-mingw32-windres"
|
||||||
|
|
||||||
|
# 验证 qemu 和 docker 安装
|
||||||
|
RUN qemu-aarch64-static --version && \
|
||||||
|
qemu-x86_64-static --version && \
|
||||||
|
docker --version
|
||||||
|
|
||||||
|
# 创建必要目录
|
||||||
|
RUN mkdir -p /data /etc/supervisor/conf.d /var/log/supervisor
|
||||||
|
|
||||||
|
# 设置工作目录
|
||||||
|
WORKDIR /data
|
||||||
|
|
||||||
|
# 使用自定义入口点
|
||||||
|
ENTRYPOINT ["/data/entrypoint.sh"]
|
||||||
@@ -0,0 +1,39 @@
|
|||||||
|
services:
|
||||||
|
gitea-runner:
|
||||||
|
build: .
|
||||||
|
container_name: gitea-runner-buildx-archlinux
|
||||||
|
hostname: arch
|
||||||
|
restart: unless-stopped
|
||||||
|
network_mode: "host" # 使用宿主机网络
|
||||||
|
privileged: true # Buildx 需要 privileged 模式
|
||||||
|
volumes:
|
||||||
|
- ./runner-data:/data
|
||||||
|
- ../../common/setup.sh:/data/setup.sh:ro
|
||||||
|
- ../../common/upgrade.sh:/data/upgrade.sh:ro
|
||||||
|
- ../../common/register.sh:/data/register.sh:ro
|
||||||
|
- ../../common/manage.sh:/data/manage.sh:ro
|
||||||
|
- ../../common/entrypoint.sh:/data/entrypoint.sh:ro
|
||||||
|
- /var/run/docker.sock:/var/run/docker.sock
|
||||||
|
|
||||||
|
environment:
|
||||||
|
- TZ=Asia/Shanghai
|
||||||
|
|
||||||
|
# Arch Linux Buildx 配置 - 使用 tonistiigi/binfmt
|
||||||
|
- ENABLE_BUILDX=true
|
||||||
|
- BINFMT_METHOD=tonistiigi
|
||||||
|
- GITEA_INSTANCE=${GITEA_INSTANCE}
|
||||||
|
- GITEA_TOKEN=${GITEA_TOKEN}
|
||||||
|
- DEFAULT_RUNNER_NAME=${DEFAULT_RUNNER_NAME:-buildx-archlinux}
|
||||||
|
- DEFAULT_RUNNER_LABEL=${DEFAULT_RUNNER_LABEL:-archlinux:host://archlinux:latest,company-server:host://archlinux:latest,buildx-archlinux:host://archlinux:latest}
|
||||||
|
|
||||||
|
# 如果需要使用代理,取消下面的注释并修改为你的代理地址
|
||||||
|
# 注意:容器内访问宿主机需要使用 host.docker.internal 或宿主机IP
|
||||||
|
# - http_proxy=http://host.docker.internal:20122
|
||||||
|
# - https_proxy=http://host.docker.internal:20122
|
||||||
|
# - HTTP_PROXY=http://host.docker.internal:20122
|
||||||
|
# - HTTPS_PROXY=http://host.docker.internal:20122
|
||||||
|
# - no_proxy=localhost,127.0.0.1
|
||||||
|
|
||||||
|
# Linux 系统需要取消下面的注释以支持 host.docker.internal
|
||||||
|
# extra_hosts:
|
||||||
|
# - "host.docker.internal:host-gateway"
|
||||||
@@ -0,0 +1,4 @@
|
|||||||
|
GITEA_INSTANCE=https://git.mytsl.cn
|
||||||
|
GITEA_TOKEN=
|
||||||
|
DEFAULT_RUNNER_NAME=buildx-ubuntu-22
|
||||||
|
DEFAULT_RUNNER_LABEL=ubuntu-22.04:host://ubuntu:22.04,company-server:host://ubuntu:22.04,buildx-ubuntu-22:host://ubuntu:22.04
|
||||||
+15
-3
@@ -15,12 +15,24 @@ RUN apt-get update && apt-get install -y \
|
|||||||
lsb-release \
|
lsb-release \
|
||||||
qemu-user-static \
|
qemu-user-static \
|
||||||
binfmt-support \
|
binfmt-support \
|
||||||
|
apt-transport-https \
|
||||||
&& rm -rf /var/lib/apt/lists/*
|
&& rm -rf /var/lib/apt/lists/*
|
||||||
|
|
||||||
# 安装 Docker(包含 Buildx 插件)
|
# 安装 Docker(包含 Buildx 插件)
|
||||||
RUN curl -fsSL https://get.docker.com -o get-docker.sh && \
|
RUN install -m 0755 -d /etc/apt/keyrings && \
|
||||||
sh get-docker.sh && \
|
curl -fsSL https://download.docker.com/linux/ubuntu/gpg | gpg --dearmor -o /etc/apt/keyrings/docker.gpg && \
|
||||||
rm get-docker.sh
|
chmod a+r /etc/apt/keyrings/docker.gpg && \
|
||||||
|
echo \
|
||||||
|
"deb [arch=$(dpkg --print-architecture) signed-by=/etc/apt/keyrings/docker.gpg] https://download.docker.com/linux/ubuntu \
|
||||||
|
$(lsb_release -cs) stable" > /etc/apt/sources.list.d/docker.list && \
|
||||||
|
apt-get update && \
|
||||||
|
apt-get install -y \
|
||||||
|
docker-ce \
|
||||||
|
docker-ce-cli \
|
||||||
|
containerd.io \
|
||||||
|
docker-buildx-plugin \
|
||||||
|
docker-compose-plugin \
|
||||||
|
&& rm -rf /var/lib/apt/lists/*
|
||||||
|
|
||||||
# 验证安装
|
# 验证安装
|
||||||
RUN docker --version && \
|
RUN docker --version && \
|
||||||
+15
-6
@@ -1,21 +1,30 @@
|
|||||||
services:
|
services:
|
||||||
gitea-runner:
|
gitea-runner:
|
||||||
build: .
|
build: .
|
||||||
container_name: gitea-runner
|
container_name: gitea-runner-buildx-ubuntu-22
|
||||||
|
hostname: ubuntu
|
||||||
restart: unless-stopped
|
restart: unless-stopped
|
||||||
network_mode: "host" # 使用宿主机网络
|
network_mode: "host" # 使用宿主机网络
|
||||||
privileged: true
|
privileged: true # Buildx 需要 privileged 模式
|
||||||
volumes:
|
volumes:
|
||||||
- ./runner-data:/data
|
- ./runner-data:/data
|
||||||
- ./setup.sh:/data/setup.sh:ro
|
- ../../common/setup.sh:/data/setup.sh:ro
|
||||||
- ./register.sh:/data/register.sh:ro
|
- ../../common/upgrade.sh:/data/upgrade.sh:ro
|
||||||
- ./manage.sh:/data/manage.sh:ro
|
- ../../common/register.sh:/data/register.sh:ro
|
||||||
- ./entrypoint.sh:/data/entrypoint.sh:ro
|
- ../../common/manage.sh:/data/manage.sh:ro
|
||||||
|
- ../../common/entrypoint.sh:/data/entrypoint.sh:ro
|
||||||
- /var/run/docker.sock:/var/run/docker.sock
|
- /var/run/docker.sock:/var/run/docker.sock
|
||||||
|
|
||||||
environment:
|
environment:
|
||||||
- TZ=Asia/Shanghai
|
- TZ=Asia/Shanghai
|
||||||
|
|
||||||
|
# Buildx 配置 - 启用多架构构建
|
||||||
|
- ENABLE_BUILDX=true
|
||||||
|
- GITEA_INSTANCE=${GITEA_INSTANCE}
|
||||||
|
- GITEA_TOKEN=${GITEA_TOKEN}
|
||||||
|
- DEFAULT_RUNNER_NAME=${DEFAULT_RUNNER_NAME:-buildx-ubuntu-22}
|
||||||
|
- DEFAULT_RUNNER_LABEL=${DEFAULT_RUNNER_LABEL:-ubuntu-22.04:host://ubuntu:22.04,company-server:host://ubuntu:22.04,buildx-ubuntu-22:host://ubuntu:22.04}
|
||||||
|
|
||||||
# 如果需要使用代理,取消下面的注释并修改为你的代理地址
|
# 如果需要使用代理,取消下面的注释并修改为你的代理地址
|
||||||
# 注意:容器内访问宿主机需要使用 host.docker.internal 或宿主机IP
|
# 注意:容器内访问宿主机需要使用 host.docker.internal 或宿主机IP
|
||||||
# - http_proxy=http://host.docker.internal:20122
|
# - http_proxy=http://host.docker.internal:20122
|
||||||
@@ -0,0 +1,4 @@
|
|||||||
|
GITEA_INSTANCE=https://git.mytsl.cn
|
||||||
|
GITEA_TOKEN=
|
||||||
|
DEFAULT_RUNNER_NAME=standard-ubuntu-22
|
||||||
|
DEFAULT_RUNNER_LABEL=ubuntu-22.04:host://ubuntu:22.04,company-server:host://ubuntu:22.04,standard-ubuntu-22:host://ubuntu:22.04
|
||||||
+1
@@ -18,6 +18,7 @@ RUN apt-get update && apt-get install -y \
|
|||||||
jq \
|
jq \
|
||||||
python3 \
|
python3 \
|
||||||
python3-yaml \
|
python3-yaml \
|
||||||
|
lsb-release \
|
||||||
supervisor \
|
supervisor \
|
||||||
ca-certificates \
|
ca-certificates \
|
||||||
&& rm -rf /var/lib/apt/lists/*
|
&& rm -rf /var/lib/apt/lists/*
|
||||||
+14
-6
@@ -1,20 +1,29 @@
|
|||||||
services:
|
services:
|
||||||
gitea-runner:
|
gitea-runner:
|
||||||
build: .
|
build: .
|
||||||
container_name: gitea-runner
|
container_name: gitea-runner-standard-ubuntu-22
|
||||||
|
hostname: ubuntu
|
||||||
restart: unless-stopped
|
restart: unless-stopped
|
||||||
network_mode: "host" # 使用宿主机网络
|
network_mode: "host" # 使用宿主机网络
|
||||||
volumes:
|
volumes:
|
||||||
- ./runner-data:/data
|
- ./runner-data:/data
|
||||||
- ./setup.sh:/data/setup.sh:ro
|
- ../../common/setup.sh:/data/setup.sh:ro
|
||||||
- ./register.sh:/data/register.sh:ro
|
- ../../common/upgrade.sh:/data/upgrade.sh:ro
|
||||||
- ./manage.sh:/data/manage.sh:ro
|
- ../../common/register.sh:/data/register.sh:ro
|
||||||
- ./entrypoint.sh:/data/entrypoint.sh:ro
|
- ../../common/manage.sh:/data/manage.sh:ro
|
||||||
|
- ../../common/entrypoint.sh:/data/entrypoint.sh:ro
|
||||||
- /var/run/docker.sock:/var/run/docker.sock
|
- /var/run/docker.sock:/var/run/docker.sock
|
||||||
|
|
||||||
environment:
|
environment:
|
||||||
- TZ=Asia/Shanghai
|
- TZ=Asia/Shanghai
|
||||||
|
|
||||||
|
# Standard 配置 - 不启用 Buildx
|
||||||
|
- ENABLE_BUILDX=false
|
||||||
|
- GITEA_INSTANCE=${GITEA_INSTANCE}
|
||||||
|
- GITEA_TOKEN=${GITEA_TOKEN}
|
||||||
|
- DEFAULT_RUNNER_NAME=${DEFAULT_RUNNER_NAME:-standard-ubuntu-22}
|
||||||
|
- DEFAULT_RUNNER_LABEL=${DEFAULT_RUNNER_LABEL:-ubuntu-22.04:host://ubuntu:22.04,company-server:host://ubuntu:22.04,standard-ubuntu-22:host://ubuntu:22.04}
|
||||||
|
|
||||||
# 如果需要使用代理,取消下面的注释并修改为你的代理地址
|
# 如果需要使用代理,取消下面的注释并修改为你的代理地址
|
||||||
# 注意:容器内访问宿主机需要使用 host.docker.internal 或宿主机IP
|
# 注意:容器内访问宿主机需要使用 host.docker.internal 或宿主机IP
|
||||||
# - http_proxy=http://host.docker.internal:20122
|
# - http_proxy=http://host.docker.internal:20122
|
||||||
@@ -26,4 +35,3 @@ services:
|
|||||||
# Linux 系统需要取消下面的注释以支持 host.docker.internal
|
# Linux 系统需要取消下面的注释以支持 host.docker.internal
|
||||||
# extra_hosts:
|
# extra_hosts:
|
||||||
# - "host.docker.internal:host-gateway"
|
# - "host.docker.internal:host-gateway"
|
||||||
|
|
||||||
@@ -1,134 +0,0 @@
|
|||||||
#!/bin/bash
|
|
||||||
set -e
|
|
||||||
|
|
||||||
echo "==================================="
|
|
||||||
echo "Gitea Runner Container Starting..."
|
|
||||||
echo "==================================="
|
|
||||||
|
|
||||||
# 定义路径
|
|
||||||
PERSISTENT_BIN="/data/bin"
|
|
||||||
RUNNER_PATH="$PERSISTENT_BIN/act_runner"
|
|
||||||
SYSTEM_LINK="/usr/local/bin/act_runner"
|
|
||||||
|
|
||||||
# 创建必要目录
|
|
||||||
mkdir -p /data/runners
|
|
||||||
mkdir -p "$PERSISTENT_BIN"
|
|
||||||
mkdir -p /var/log/supervisor
|
|
||||||
mkdir -p /var/run
|
|
||||||
|
|
||||||
# 创建主 supervisor 配置文件
|
|
||||||
cat > /etc/supervisor/supervisord.conf <<EOF
|
|
||||||
[supervisord]
|
|
||||||
nodaemon=true
|
|
||||||
logfile=/var/log/supervisor/supervisord.log
|
|
||||||
pidfile=/var/run/supervisord.pid
|
|
||||||
|
|
||||||
[unix_http_server]
|
|
||||||
file=/var/run/supervisor.sock
|
|
||||||
chmod=0700
|
|
||||||
|
|
||||||
[supervisorctl]
|
|
||||||
serverurl=unix:///var/run/supervisor.sock
|
|
||||||
|
|
||||||
[rpcinterface:supervisor]
|
|
||||||
supervisor.rpcinterface_factory = supervisor.rpcinterface:make_main_rpcinterface
|
|
||||||
|
|
||||||
[include]
|
|
||||||
files=/etc/supervisor/conf.d/*.conf
|
|
||||||
EOF
|
|
||||||
|
|
||||||
# 检查持久化目录中的 act_runner
|
|
||||||
if [ -f "$RUNNER_PATH" ]; then
|
|
||||||
echo "✓ Found act_runner in persistent storage"
|
|
||||||
echo " Location: $RUNNER_PATH"
|
|
||||||
|
|
||||||
# 创建软链接到系统路径(如果不存在)
|
|
||||||
if [ ! -L "$SYSTEM_LINK" ] || [ ! -e "$SYSTEM_LINK" ]; then
|
|
||||||
ln -sf "$RUNNER_PATH" "$SYSTEM_LINK"
|
|
||||||
echo " Created system link: $SYSTEM_LINK -> $RUNNER_PATH"
|
|
||||||
fi
|
|
||||||
|
|
||||||
# 验证版本
|
|
||||||
RUNNER_VERSION=$("$SYSTEM_LINK" --version 2>/dev/null || echo "unknown")
|
|
||||||
echo " Version: $RUNNER_VERSION"
|
|
||||||
elif [ -f "$SYSTEM_LINK" ]; then
|
|
||||||
# 旧版本可能在系统路径,迁移到持久化目录
|
|
||||||
echo "⚠ Found act_runner in system path, migrating to persistent storage..."
|
|
||||||
cp "$SYSTEM_LINK" "$RUNNER_PATH"
|
|
||||||
chmod +x "$RUNNER_PATH"
|
|
||||||
ln -sf "$RUNNER_PATH" "$SYSTEM_LINK"
|
|
||||||
echo " ✓ Migrated to: $RUNNER_PATH"
|
|
||||||
else
|
|
||||||
# 没有找到 act_runner
|
|
||||||
echo "⚠ act_runner not installed yet!"
|
|
||||||
echo ""
|
|
||||||
echo "Please run the setup script first:"
|
|
||||||
echo " docker-compose exec gitea-runner /data/setup.sh"
|
|
||||||
echo ""
|
|
||||||
echo "This will download and install the Gitea Runner to persistent storage."
|
|
||||||
echo "Container is waiting..."
|
|
||||||
|
|
||||||
# 等待 act_runner 安装
|
|
||||||
while [ ! -f "$RUNNER_PATH" ] && [ ! -f "$SYSTEM_LINK" ]; do
|
|
||||||
sleep 10
|
|
||||||
done
|
|
||||||
|
|
||||||
# 再次检查并创建链接
|
|
||||||
if [ -f "$RUNNER_PATH" ]; then
|
|
||||||
ln -sf "$RUNNER_PATH" "$SYSTEM_LINK"
|
|
||||||
echo "✓ act_runner detected and linked!"
|
|
||||||
elif [ -f "$SYSTEM_LINK" ]; then
|
|
||||||
cp "$SYSTEM_LINK" "$RUNNER_PATH"
|
|
||||||
ln -sf "$RUNNER_PATH" "$SYSTEM_LINK"
|
|
||||||
echo "✓ act_runner detected and migrated!"
|
|
||||||
fi
|
|
||||||
fi
|
|
||||||
|
|
||||||
# 为每个已注册的 runner 创建 supervisor 配置
|
|
||||||
echo ""
|
|
||||||
echo "Scanning for registered runners..."
|
|
||||||
RUNNER_COUNT=0
|
|
||||||
|
|
||||||
if [ -d "/data/runners" ]; then
|
|
||||||
for runner_dir in /data/runners/*/; do
|
|
||||||
if [ -d "$runner_dir" ]; then
|
|
||||||
runner_name=$(basename "$runner_dir")
|
|
||||||
|
|
||||||
if [ -f "$runner_dir/.runner" ] && [ -f "$runner_dir/config.yaml" ]; then
|
|
||||||
echo "Found runner: $runner_name"
|
|
||||||
|
|
||||||
# 创建该 runner 的 supervisor 配置
|
|
||||||
cat > "/etc/supervisor/conf.d/runner-${runner_name}.conf" <<EOF
|
|
||||||
[program:runner-${runner_name}]
|
|
||||||
command=/usr/local/bin/act_runner daemon --config ${runner_dir}/config.yaml
|
|
||||||
directory=${runner_dir}
|
|
||||||
autostart=true
|
|
||||||
autorestart=true
|
|
||||||
stderr_logfile=/var/log/supervisor/runner-${runner_name}.err.log
|
|
||||||
stdout_logfile=/var/log/supervisor/runner-${runner_name}.out.log
|
|
||||||
user=root
|
|
||||||
environment=HOME="/root"
|
|
||||||
EOF
|
|
||||||
RUNNER_COUNT=$((RUNNER_COUNT + 1))
|
|
||||||
fi
|
|
||||||
fi
|
|
||||||
done
|
|
||||||
fi
|
|
||||||
|
|
||||||
if [ $RUNNER_COUNT -eq 0 ]; then
|
|
||||||
echo "⚠ No runners registered yet!"
|
|
||||||
echo ""
|
|
||||||
echo "Please run the register script to add a runner:"
|
|
||||||
echo " docker-compose exec gitea-runner /data/register.sh"
|
|
||||||
echo ""
|
|
||||||
echo "Container will continue running and wait for runners..."
|
|
||||||
fi
|
|
||||||
|
|
||||||
echo "Total runners configured: $RUNNER_COUNT"
|
|
||||||
echo ""
|
|
||||||
echo "==================================="
|
|
||||||
echo "Starting Supervisor..."
|
|
||||||
echo "==================================="
|
|
||||||
|
|
||||||
# 启动 supervisord(使用主配置文件)
|
|
||||||
exec /usr/bin/supervisord -c /etc/supervisor/supervisord.conf
|
|
||||||
@@ -1,288 +0,0 @@
|
|||||||
#!/bin/bash
|
|
||||||
|
|
||||||
echo "=========================================="
|
|
||||||
echo " Gitea Runner Management Tool "
|
|
||||||
echo "=========================================="
|
|
||||||
echo ""
|
|
||||||
|
|
||||||
# 函数:列出所有 runners
|
|
||||||
list_runners() {
|
|
||||||
echo "Registered Runners:"
|
|
||||||
echo "-------------------------------------------"
|
|
||||||
|
|
||||||
if [ ! -d "/data/runners" ] || [ -z "$(ls -A /data/runners 2>/dev/null)" ]; then
|
|
||||||
echo "No runners registered yet."
|
|
||||||
return
|
|
||||||
fi
|
|
||||||
|
|
||||||
printf "%-20s %-15s %-30s\n" "Name" "Status" "Log File"
|
|
||||||
echo "-------------------------------------------"
|
|
||||||
|
|
||||||
for runner_dir in /data/runners/*/; do
|
|
||||||
if [ -d "$runner_dir" ]; then
|
|
||||||
runner_name=$(basename "$runner_dir")
|
|
||||||
|
|
||||||
if [ -f "$runner_dir/.runner" ]; then
|
|
||||||
# 获取状态
|
|
||||||
status=$(supervisorctl status "runner-${runner_name}" 2>/dev/null | awk '{print $2}')
|
|
||||||
[ -z "$status" ] && status="NOT_LOADED"
|
|
||||||
|
|
||||||
log_file="/var/log/supervisor/runner-${runner_name}.out.log"
|
|
||||||
|
|
||||||
printf "%-20s %-15s %-30s\n" "$runner_name" "$status" "$log_file"
|
|
||||||
fi
|
|
||||||
fi
|
|
||||||
done
|
|
||||||
echo ""
|
|
||||||
}
|
|
||||||
|
|
||||||
# 函数:查看 runner 详细信息
|
|
||||||
show_runner() {
|
|
||||||
local runner_name=$1
|
|
||||||
local runner_dir="/data/runners/${runner_name}"
|
|
||||||
|
|
||||||
if [ ! -d "$runner_dir" ]; then
|
|
||||||
echo "✗ Runner '$runner_name' not found!"
|
|
||||||
return 1
|
|
||||||
fi
|
|
||||||
|
|
||||||
echo "Runner Details: $runner_name"
|
|
||||||
echo "-------------------------------------------"
|
|
||||||
echo "Directory: $runner_dir"
|
|
||||||
|
|
||||||
if [ -f "$runner_dir/config.yaml" ]; then
|
|
||||||
echo ""
|
|
||||||
echo "Configuration:"
|
|
||||||
cat "$runner_dir/config.yaml"
|
|
||||||
fi
|
|
||||||
|
|
||||||
echo ""
|
|
||||||
echo "Status:"
|
|
||||||
supervisorctl status "runner-${runner_name}" 2>/dev/null | awk '{print $2}'
|
|
||||||
echo ""
|
|
||||||
}
|
|
||||||
|
|
||||||
# 函数:删除 runner
|
|
||||||
delete_runner() {
|
|
||||||
local runner_name=$1
|
|
||||||
local runner_dir="/data/runners/${runner_name}"
|
|
||||||
|
|
||||||
if [ ! -d "$runner_dir" ]; then
|
|
||||||
echo "✗ Runner '$runner_name' not found!"
|
|
||||||
return 1
|
|
||||||
fi
|
|
||||||
|
|
||||||
echo "⚠ Warning: This will permanently delete runner '$runner_name'"
|
|
||||||
read -p "Are you sure? (y/N): " -n 1 -r
|
|
||||||
echo
|
|
||||||
|
|
||||||
if [[ ! $REPLY =~ ^[Yy]$ ]]; then
|
|
||||||
echo "Deletion cancelled."
|
|
||||||
return 0
|
|
||||||
fi
|
|
||||||
|
|
||||||
echo "Stopping runner..."
|
|
||||||
supervisorctl stop "runner-${runner_name}" 2>/dev/null || true
|
|
||||||
|
|
||||||
echo "Removing configuration..."
|
|
||||||
rm -f "/etc/supervisor/conf.d/runner-${runner_name}.conf"
|
|
||||||
|
|
||||||
echo "Deleting runner directory..."
|
|
||||||
rm -rf "$runner_dir"
|
|
||||||
|
|
||||||
echo "Updating supervisor..."
|
|
||||||
supervisorctl reread
|
|
||||||
supervisorctl update
|
|
||||||
|
|
||||||
echo ""
|
|
||||||
echo "✓ Runner '$runner_name' deleted successfully!"
|
|
||||||
}
|
|
||||||
|
|
||||||
# 函数:查看 runner 日志
|
|
||||||
logs_runner() {
|
|
||||||
local runner_name=$1
|
|
||||||
local lines=${2:-50}
|
|
||||||
|
|
||||||
local log_file="/var/log/supervisor/runner-${runner_name}.out.log"
|
|
||||||
|
|
||||||
if [ ! -f "$log_file" ]; then
|
|
||||||
echo "✗ Log file not found for runner '$runner_name'"
|
|
||||||
return 1
|
|
||||||
fi
|
|
||||||
|
|
||||||
echo "Showing last $lines lines of '$runner_name' logs:"
|
|
||||||
echo "-------------------------------------------"
|
|
||||||
tail -n "$lines" "$log_file"
|
|
||||||
}
|
|
||||||
|
|
||||||
# 函数:实时查看日志
|
|
||||||
follow_logs() {
|
|
||||||
local runner_name=$1
|
|
||||||
local log_file="/var/log/supervisor/runner-${runner_name}.out.log"
|
|
||||||
|
|
||||||
if [ ! -f "$log_file" ]; then
|
|
||||||
echo "✗ Log file not found for runner '$runner_name'"
|
|
||||||
return 1
|
|
||||||
fi
|
|
||||||
|
|
||||||
echo "Following logs for '$runner_name' (Press Ctrl+C to exit):"
|
|
||||||
echo "-------------------------------------------"
|
|
||||||
tail -f "$log_file"
|
|
||||||
}
|
|
||||||
|
|
||||||
# 函数:启动/停止/重启 runner
|
|
||||||
control_runner() {
|
|
||||||
local action=$1
|
|
||||||
local runner_name=$2
|
|
||||||
|
|
||||||
case $action in
|
|
||||||
start|stop|restart)
|
|
||||||
echo "${action^}ing runner '$runner_name'..."
|
|
||||||
supervisorctl "$action" "runner-${runner_name}"
|
|
||||||
;;
|
|
||||||
*)
|
|
||||||
echo "✗ Invalid action: $action"
|
|
||||||
return 1
|
|
||||||
;;
|
|
||||||
esac
|
|
||||||
}
|
|
||||||
|
|
||||||
# 函数:显示所有 runner 状态
|
|
||||||
status_all() {
|
|
||||||
echo "All Runners Status:"
|
|
||||||
echo "-------------------------------------------"
|
|
||||||
supervisorctl status | grep "^runner-" || echo "No runners running."
|
|
||||||
echo ""
|
|
||||||
}
|
|
||||||
|
|
||||||
# 主菜单
|
|
||||||
show_menu() {
|
|
||||||
echo "Choose an action:"
|
|
||||||
echo " 1) List all runners"
|
|
||||||
echo " 2) Show runner details"
|
|
||||||
echo " 3) Add new runner"
|
|
||||||
echo " 4) Delete runner"
|
|
||||||
echo " 5) Start runner"
|
|
||||||
echo " 6) Stop runner"
|
|
||||||
echo " 7) Restart runner"
|
|
||||||
echo " 8) View runner logs"
|
|
||||||
echo " 9) Follow runner logs (real-time)"
|
|
||||||
echo " 10) Show all runners status"
|
|
||||||
echo " 0) Exit"
|
|
||||||
echo ""
|
|
||||||
}
|
|
||||||
|
|
||||||
# 主程序
|
|
||||||
if [ $# -eq 0 ]; then
|
|
||||||
# 交互模式
|
|
||||||
while true; do
|
|
||||||
show_menu
|
|
||||||
read -p "Enter your choice: " choice
|
|
||||||
echo ""
|
|
||||||
|
|
||||||
case $choice in
|
|
||||||
1)
|
|
||||||
list_runners
|
|
||||||
;;
|
|
||||||
2)
|
|
||||||
read -p "Enter runner name: " runner_name
|
|
||||||
show_runner "$runner_name"
|
|
||||||
;;
|
|
||||||
3)
|
|
||||||
echo "Starting registration process..."
|
|
||||||
/data/register.sh
|
|
||||||
;;
|
|
||||||
4)
|
|
||||||
read -p "Enter runner name to delete: " runner_name
|
|
||||||
delete_runner "$runner_name"
|
|
||||||
;;
|
|
||||||
5)
|
|
||||||
read -p "Enter runner name to start: " runner_name
|
|
||||||
control_runner start "$runner_name"
|
|
||||||
;;
|
|
||||||
6)
|
|
||||||
read -p "Enter runner name to stop: " runner_name
|
|
||||||
control_runner stop "$runner_name"
|
|
||||||
;;
|
|
||||||
7)
|
|
||||||
read -p "Enter runner name to restart: " runner_name
|
|
||||||
control_runner restart "$runner_name"
|
|
||||||
;;
|
|
||||||
8)
|
|
||||||
read -p "Enter runner name: " runner_name
|
|
||||||
read -p "Number of lines (default 50): " lines
|
|
||||||
logs_runner "$runner_name" "${lines:-50}"
|
|
||||||
;;
|
|
||||||
9)
|
|
||||||
read -p "Enter runner name: " runner_name
|
|
||||||
follow_logs "$runner_name"
|
|
||||||
;;
|
|
||||||
10)
|
|
||||||
status_all
|
|
||||||
;;
|
|
||||||
0)
|
|
||||||
echo "Goodbye!"
|
|
||||||
exit 0
|
|
||||||
;;
|
|
||||||
*)
|
|
||||||
echo "Invalid choice!"
|
|
||||||
;;
|
|
||||||
esac
|
|
||||||
|
|
||||||
echo ""
|
|
||||||
read -p "Press Enter to continue..."
|
|
||||||
clear
|
|
||||||
done
|
|
||||||
else
|
|
||||||
# 命令行模式
|
|
||||||
case $1 in
|
|
||||||
list|ls)
|
|
||||||
list_runners
|
|
||||||
;;
|
|
||||||
show|info)
|
|
||||||
show_runner "$2"
|
|
||||||
;;
|
|
||||||
add|register)
|
|
||||||
/data/register.sh
|
|
||||||
;;
|
|
||||||
delete|rm|remove)
|
|
||||||
delete_runner "$2"
|
|
||||||
;;
|
|
||||||
start)
|
|
||||||
control_runner start "$2"
|
|
||||||
;;
|
|
||||||
stop)
|
|
||||||
control_runner stop "$2"
|
|
||||||
;;
|
|
||||||
restart)
|
|
||||||
control_runner restart "$2"
|
|
||||||
;;
|
|
||||||
logs)
|
|
||||||
logs_runner "$2" "${3:-50}"
|
|
||||||
;;
|
|
||||||
follow)
|
|
||||||
follow_logs "$2"
|
|
||||||
;;
|
|
||||||
status)
|
|
||||||
status_all
|
|
||||||
;;
|
|
||||||
*)
|
|
||||||
echo "Usage: $0 [command] [runner_name]"
|
|
||||||
echo ""
|
|
||||||
echo "Commands:"
|
|
||||||
echo " list - List all runners"
|
|
||||||
echo " show <name> - Show runner details"
|
|
||||||
echo " add - Add new runner"
|
|
||||||
echo " delete <name> - Delete a runner"
|
|
||||||
echo " start <name> - Start a runner"
|
|
||||||
echo " stop <name> - Stop a runner"
|
|
||||||
echo " restart <name> - Restart a runner"
|
|
||||||
echo " logs <name> [lines] - View runner logs"
|
|
||||||
echo " follow <name> - Follow runner logs (real-time)"
|
|
||||||
echo " status - Show all runners status"
|
|
||||||
echo ""
|
|
||||||
echo "Or run without arguments for interactive mode."
|
|
||||||
exit 1
|
|
||||||
;;
|
|
||||||
esac
|
|
||||||
fi
|
|
||||||
@@ -1,141 +0,0 @@
|
|||||||
#!/bin/bash
|
|
||||||
|
|
||||||
echo "=========================================="
|
|
||||||
echo " Gitea Runner Installation Script "
|
|
||||||
echo "=========================================="
|
|
||||||
echo ""
|
|
||||||
|
|
||||||
# 持久化安装路径
|
|
||||||
INSTALL_PATH="/data/bin/act_runner"
|
|
||||||
SYSTEM_PATH="/usr/local/bin/act_runner"
|
|
||||||
|
|
||||||
# 创建目录
|
|
||||||
mkdir -p /data/bin
|
|
||||||
|
|
||||||
# 检查是否已安装
|
|
||||||
if [ -f "$INSTALL_PATH" ]; then
|
|
||||||
CURRENT_VERSION=$($INSTALL_PATH --version 2>/dev/null | grep -oP 'version \K[0-9.]+' || echo "unknown")
|
|
||||||
echo "⚠ act_runner already installed (version: $CURRENT_VERSION)"
|
|
||||||
echo " Location: $INSTALL_PATH"
|
|
||||||
echo ""
|
|
||||||
read -p "Do you want to reinstall/upgrade? (y/N): " -n 1 -r
|
|
||||||
echo
|
|
||||||
if [[ ! $REPLY =~ ^[Yy]$ ]]; then
|
|
||||||
echo "Installation cancelled."
|
|
||||||
exit 0
|
|
||||||
fi
|
|
||||||
rm -f "$INSTALL_PATH" "$SYSTEM_PATH"
|
|
||||||
fi
|
|
||||||
|
|
||||||
# 获取要安装的版本
|
|
||||||
echo "Available versions: https://dl.gitea.com/act_runner/"
|
|
||||||
echo ""
|
|
||||||
read -p "Enter version to install (default: 0.2.13): " RUNNER_VERSION
|
|
||||||
RUNNER_VERSION=${RUNNER_VERSION:-0.2.13}
|
|
||||||
|
|
||||||
ARCH=$(uname -m)
|
|
||||||
case "$ARCH" in
|
|
||||||
x86_64)
|
|
||||||
RUNNER_ARCH="amd64"
|
|
||||||
;;
|
|
||||||
aarch64|arm64)
|
|
||||||
RUNNER_ARCH="arm64"
|
|
||||||
;;
|
|
||||||
armv7l)
|
|
||||||
RUNNER_ARCH="arm-7"
|
|
||||||
;;
|
|
||||||
*)
|
|
||||||
echo "⚠ Unknown architecture: $ARCH"
|
|
||||||
RUNNER_ARCH="arm64"
|
|
||||||
;;
|
|
||||||
esac
|
|
||||||
|
|
||||||
# 确认架构
|
|
||||||
echo ""
|
|
||||||
echo "Detected architecture: $ARCH -> $RUNNER_ARCH"
|
|
||||||
read -p "Is this correct? (Y/n): " -n 1 -r
|
|
||||||
echo
|
|
||||||
if [[ $REPLY =~ ^[Nn]$ ]]; then
|
|
||||||
echo ""
|
|
||||||
echo "Available architectures:"
|
|
||||||
echo " 1. amd64 (x86_64)"
|
|
||||||
echo " 2. arm64 (aarch64)"
|
|
||||||
echo " 3. arm-7 (armv7l)"
|
|
||||||
read -p "Select architecture (1-3): " ARCH_CHOICE
|
|
||||||
case "$ARCH_CHOICE" in
|
|
||||||
1) RUNNER_ARCH="amd64" ;;
|
|
||||||
2) RUNNER_ARCH="arm64" ;;
|
|
||||||
3) RUNNER_ARCH="arm-7" ;;
|
|
||||||
*)
|
|
||||||
echo "Invalid choice. Exiting."
|
|
||||||
exit 1
|
|
||||||
;;
|
|
||||||
esac
|
|
||||||
fi
|
|
||||||
|
|
||||||
DOWNLOAD_URL="https://dl.gitea.com/act_runner/${RUNNER_VERSION}/act_runner-${RUNNER_VERSION}-linux-${RUNNER_ARCH}"
|
|
||||||
|
|
||||||
echo ""
|
|
||||||
echo "Download Configuration:"
|
|
||||||
echo " Version: $RUNNER_VERSION"
|
|
||||||
echo " Architecture: $RUNNER_ARCH"
|
|
||||||
echo " URL: $DOWNLOAD_URL"
|
|
||||||
echo " Install Location: $INSTALL_PATH (persistent)"
|
|
||||||
echo ""
|
|
||||||
read -p "Proceed with download? (Y/n): " -n 1 -r
|
|
||||||
echo
|
|
||||||
if [[ $REPLY =~ ^[Nn]$ ]]; then
|
|
||||||
echo "Installation cancelled."
|
|
||||||
exit 0
|
|
||||||
fi
|
|
||||||
|
|
||||||
echo ""
|
|
||||||
echo "Downloading act_runner..."
|
|
||||||
echo ""
|
|
||||||
|
|
||||||
# 下载到持久化目录
|
|
||||||
if curl -L "$DOWNLOAD_URL" -o "$INSTALL_PATH"; then
|
|
||||||
chmod +x "$INSTALL_PATH"
|
|
||||||
|
|
||||||
# 同时创建软链接到系统路径
|
|
||||||
ln -sf "$INSTALL_PATH" "$SYSTEM_PATH"
|
|
||||||
|
|
||||||
# 验证安装
|
|
||||||
if $INSTALL_PATH --version; then
|
|
||||||
echo ""
|
|
||||||
echo "=========================================="
|
|
||||||
echo "✓ act_runner installed successfully!"
|
|
||||||
echo "=========================================="
|
|
||||||
echo ""
|
|
||||||
echo "Version: $($INSTALL_PATH --version)"
|
|
||||||
echo "Location: $INSTALL_PATH (persistent storage)"
|
|
||||||
echo ""
|
|
||||||
echo "Next steps:"
|
|
||||||
echo "1. Register the runner:"
|
|
||||||
echo " docker-compose exec gitea-runner /data/register.sh"
|
|
||||||
echo ""
|
|
||||||
echo "2. Restart the container:"
|
|
||||||
echo " docker-compose restart"
|
|
||||||
echo ""
|
|
||||||
echo "Note: act_runner is saved in persistent storage"
|
|
||||||
echo " and will survive container restarts."
|
|
||||||
echo ""
|
|
||||||
else
|
|
||||||
echo ""
|
|
||||||
echo "✗ Installation verification failed!"
|
|
||||||
rm -f "$INSTALL_PATH" "$SYSTEM_PATH"
|
|
||||||
exit 1
|
|
||||||
fi
|
|
||||||
else
|
|
||||||
echo ""
|
|
||||||
echo "✗ Download failed!"
|
|
||||||
echo "Please check:"
|
|
||||||
echo " - Internet connection"
|
|
||||||
echo " - Version number is correct: $RUNNER_VERSION"
|
|
||||||
echo " - Architecture is correct: $RUNNER_ARCH"
|
|
||||||
echo " - URL is accessible: $DOWNLOAD_URL"
|
|
||||||
echo ""
|
|
||||||
echo "You can check available versions at:"
|
|
||||||
echo " https://dl.gitea.com/act_runner/"
|
|
||||||
exit 1
|
|
||||||
fi
|
|
||||||
Reference in New Issue
Block a user