54 Commits
Author SHA1 Message Date
ci[bot] 81f8dd988f 📊 chore(stats): 更新代码统计 [2026-07-21 03:15]
📊 统计摘要:
- 总代码: 4,490 行
- 总文件: 26 个
- 语言数: 3 种
- 徽章数: 6 个

🔗 触发提交: 18c4dfa811
🤖 由 Gitea Actions 自动生成
2026-07-21 03:15:02 +00:00
ci[bot] 8514e1c56b 📊 chore(stats): 更新代码统计 [2026-07-20 11:00]
📊 统计摘要:
- 总代码: 4459 行
- 总文件: 26 个
- 语言数: 3 种
- 徽章数: 6 个

🔗 触发提交: 3054338cf7
🤖 由 Gitea Actions 自动生成
2026-07-20 11:00:17 +00:00
ci[bot] 8d62cea2cd 📊 chore(stats): 更新代码统计 [2026-07-20 10:14]
📊 统计摘要:
- 总代码: 4459 行
- 总文件: 26 个
- 语言数: 3 种
- 徽章数: 6 个

🔗 触发提交: 3054338cf7
🤖 由 Gitea Actions 自动生成
2026-07-20 10:14:08 +00:00
ci[bot] b63d5560b3 📊 chore(stats): 更新代码统计 [2026-07-20 09:32]
📊 统计摘要:
- 总代码: 4330 行
- 总文件: 26 个
- 语言数: 3 种
- 徽章数: 6 个

🔗 触发提交: 207588ae36
🤖 由 Gitea Actions 自动生成
2026-07-20 09:32:38 +00:00
ci[bot] 74ed2c051a 📊 chore(stats): 更新代码统计 [2026-07-20 09:18]
📊 统计摘要:
- 总代码: 4369 行
- 总文件: 26 个
- 语言数: 3 种
- 徽章数: 6 个

🔗 触发提交: df8a9ead9c
🤖 由 Gitea Actions 自动生成
2026-07-20 09:18:14 +00:00
ci[bot] 4d0fe8cef3 📊 chore(stats): 更新代码统计 [2026-07-20 09:13]
📊 统计摘要:
- 总代码: 4329 行
- 总文件: 26 个
- 语言数: 3 种
- 徽章数: 6 个

🔗 触发提交: d6654b4d6d
🤖 由 Gitea Actions 自动生成
2026-07-20 09:13:20 +00:00
ci[bot] 22747aeec5 📊 chore(stats): 更新代码统计 [2026-07-20 08:39]
📊 统计摘要:
- 总代码: 4280 行
- 总文件: 26 个
- 语言数: 3 种
- 徽章数: 6 个

🔗 触发提交: ac8a781656
🤖 由 Gitea Actions 自动生成
2026-07-20 08:39:15 +00:00
ci[bot] f5f4336e65 📊 chore(stats): 更新代码统计 [2026-07-20 08:26]
📊 统计摘要:
- 总代码: 4286 行
- 总文件: 26 个
- 语言数: 3 种
- 徽章数: 6 个

🔗 触发提交: 2dcf9f7da4
🤖 由 Gitea Actions 自动生成
2026-07-20 08:26:39 +00:00
ci[bot] d76afccaed 📊 chore(stats): 更新代码统计 [2026-05-22 08:49]
📊 统计摘要:
- 总代码: 4913 行
- 总文件: 30 个
- 语言数: 3 种
- 徽章数: 6 个

🔗 触发提交: 7782975
🤖 由 Gitea Actions 自动生成
2026-05-22 08:49:03 +00:00
ci[bot] cdf7e2e3d8 chore: 更新代码统计 [2026-05-22 08:03]
📊 统计摘要:
- 总代码: 4906 行
- 总文件: 30 个
- 语言数: 3 种
- 徽章数: 6 个

🔗 触发提交: 550e226
🤖 由 Gitea Actions 自动生成
2026-05-22 08:03:25 +00:00
ci[bot] 4a525b77f4 chore: 更新代码统计 [2026-05-22 07:53]
📊 统计摘要:
- 总代码: 4902 行
- 总文件: 30 个
- 语言数: 3 种
- 徽章数: 6 个

🔗 触发提交: 6552f8b
🤖 由 Gitea Actions 自动生成
2026-05-22 07:53:08 +00:00
ci[bot] ba21857f91 chore: 更新代码统计 [2026-05-22 07:45]
📊 统计摘要:
- 总代码: 4868 行
- 总文件: 30 个
- 语言数: 3 种
- 徽章数: 6 个

🔗 触发提交: 1259495
🤖 由 Gitea Actions 自动生成
2026-05-22 07:45:31 +00:00
ci[bot] 87365c7bfb chore: 更新代码统计 [2026-05-22 03:30]
📊 统计摘要:
- 总代码: 4591 行
- 总文件: 28 个
- 语言数: 2 种
- 徽章数: 6 个

🔗 触发提交: 0579c2d
🤖 由 GitHub Actions 自动生成
2026-05-22 03:30:25 +00:00
ci[bot] 8e228349ee chore: 更新代码统计 [2026-05-22 02:42]
📊 统计摘要:
- 总代码: 4590 行
- 总文件: 28 个
- 语言数: 2 种
- 徽章数: 6 个

🔗 触发提交: beab4e5
🤖 由 GitHub Actions 自动生成
2026-05-22 02:42:36 +00:00
ci[bot] 3de13f81d6 chore: 更新代码统计 [2026-05-07 07:15]
📊 统计摘要:
- 总代码: 4117 行
- 总文件: 21 个
- 语言数: 2 种
- 徽章数: 6 个

🔗 触发提交: 34480c1
🤖 由 GitHub Actions 自动生成
2026-05-07 07:15:41 +00:00
ci[bot] 55f742c8e6 chore: 更新代码统计 [2026-01-08 06:28]
📊 统计摘要:
- 总代码: 3770 行
- 总文件: 19 个
- 语言数: 2 种
- 徽章数: 6 个

🔗 触发提交: 1a52ee0
🤖 由 GitHub Actions 自动生成
2026-01-08 06:28:32 +00:00
ci[bot] 6bffca5e51 chore: 更新代码统计 [2025-12-12 09:54]
📊 统计摘要:
- 总代码: 3817 行
- 总文件: 20 个
- 语言数: 2 种
- 徽章数: 6 个

🔗 触发提交: 99b3560
🤖 由 GitHub Actions 自动生成
2025-12-12 09:54:17 +00:00
ci[bot] 34576593b4 chore: 更新代码统计 [2025-12-10 06:45]
📊 统计摘要:
- 总代码: 3817 行
- 总文件: 20 个
- 语言数: 2 种
- 徽章数: 6 个

🔗 触发提交: db75d0e
🤖 由 GitHub Actions 自动生成
2025-12-10 06:45:08 +00:00
ci[bot] d57f76280e chore: 更新代码统计 [2025-12-09 09:52]
📊 统计摘要:
- 总代码: 3777 行
- 总文件: 20 个
- 语言数: 2 种
- 徽章数: 6 个

🔗 触发提交: 1b5611c
🤖 由 GitHub Actions 自动生成
2025-12-09 09:52:38 +00:00
ci[bot] b2ee6a8dcc chore: 更新代码统计 [2025-12-09 08:37]
📊 统计摘要:
- 总代码: 3777 行
- 总文件: 20 个
- 语言数: 2 种
- 徽章数: 6 个

🔗 触发提交: 44e92ab
🤖 由 GitHub Actions 自动生成
2025-12-09 08:37:08 +00:00
ci[bot] 72c3b3acd8 chore: 更新代码统计 [2025-12-09 08:24]
📊 统计摘要:
- 总代码: 3759 行
- 总文件: 20 个
- 语言数: 2 种
- 徽章数: 6 个

🔗 触发提交: eaedcd3
🤖 由 GitHub Actions 自动生成
2025-12-09 08:24:04 +00:00
ci[bot] c005a8d410 chore: 更新代码统计 [2025-12-09 08:18]
📊 统计摘要:
- 总代码: 3759 行
- 总文件: 20 个
- 语言数: 2 种
- 徽章数: 6 个

🔗 触发提交: 0e89b21
🤖 由 GitHub Actions 自动生成
2025-12-09 08:18:14 +00:00
ci[bot] 82e6663b89 chore: 更新代码统计 [2025-12-09 08:08]
📊 统计摘要:
- 总代码: 3759 行
- 总文件: 20 个
- 语言数: 2 种
- 徽章数: 6 个

🔗 触发提交: 39820e6
🤖 由 GitHub Actions 自动生成
2025-12-09 08:08:56 +00:00
ci[bot] a1f6c6eef0 chore: 更新代码统计 [2025-11-17 10:19]
📊 统计摘要:
- 总代码: 5322 行
- 总文件: 22 个
- 语言数: 2 种
- 徽章数: 6 个

🔗 触发提交: 4829434
🤖 由 GitHub Actions 自动生成
2025-11-17 10:19:43 +00:00
ci-bot 8973b2f8a3 chore: 更新代码统计 [2025-11-17 10:08]
📊 统计摘要:
- 总代码: 5322 行
- 总文件: 22 个
- 语言数: 2 种
- 徽章数: 6 个

🔗 触发提交: 9c572b6
🤖 由 GitHub Actions 自动生成
2025-11-17 10:08:55 +00:00
ci-bot b226bf6ff1 chore: 更新代码统计 [2025-11-17 10:07]
📊 统计摘要:
- 总代码: 4813 行
- 总文件: 22 个
- 语言数: 2 种
- 徽章数: 6 个

🔗 触发提交: 49f11fb
🤖 由 GitHub Actions 自动生成
2025-11-17 10:07:35 +00:00
github-actions[bot] c24237a587 chore: 更新代码统计 [2025-11-17 09:52]
📊 统计摘要:
- 总代码: 4813 行
- 总文件: 22 个
- 语言数: 2 种
- 徽章数: 6 个

🔗 触发提交: 5ec0af5
🤖 由 GitHub Actions 自动生成
2025-11-17 09:52:59 +00:00
github-actions[bot] 100a097f7f chore: 更新代码统计 [2025-11-03 06:29]
📊 统计摘要:
- 总代码: 4813 行
- 总文件: 22 个
- 语言数: 2 种
- 徽章数: 6 个

🔗 触发提交: 566a175
🤖 由 GitHub Actions 自动生成
2025-11-03 06:29:58 +00:00
github-actions[bot] 419d520413 chore: 更新代码统计 [2025-11-03 06:29]
📊 统计摘要:
- 总代码: 4813 行
- 总文件: 22 个
- 语言数: 2 种
- 徽章数: 6 个

🔗 触发提交: 1b7eed5
🤖 由 GitHub Actions 自动生成
2025-11-03 06:29:26 +00:00
github-actions[bot] b4e1778a75 chore: 更新代码统计 [2025-11-03 06:09]
📊 统计摘要:
- 总代码: 4813 行
- 总文件: 22 个
- 语言数: 2 种
- 徽章数: 6 个

🔗 触发提交: e71d9d0
🤖 由 GitHub Actions 自动生成
2025-11-03 06:09:08 +00:00
github-actions[bot] 21f2931cfa chore: 更新代码统计 [2025-11-03 04:04]
📊 统计摘要:
- 总代码: 4813 行
- 总文件: 22 个
- 语言数: 2 种
- 徽章数: 6 个

🔗 触发提交: 160c5eb
🤖 由 GitHub Actions 自动生成
2025-11-03 04:04:24 +00:00
github-actions[bot] e5717d6413 chore: 更新代码统计 [2025-11-03 04:00]
📊 统计摘要:
- 总代码: 4813 行
- 总文件: 22 个
- 语言数: 2 种
- 徽章数: 6 个

🔗 触发提交: fb4e7f3
🤖 由 GitHub Actions 自动生成
2025-11-03 04:00:42 +00:00
github-actions[bot] f23c6d417c chore: 更新代码统计 [2025-11-03 03:39]
📊 统计摘要:
- 总代码: 4813 行
- 总文件: 22 个
- 语言数: 2 种
- 徽章数: 6 个

🔗 触发提交: 0d3ccad
🤖 由 GitHub Actions 自动生成
2025-11-03 03:39:00 +00:00
github-actions[bot] d42f38fb29 chore: 更新代码统计 [2025-11-03 03:38]
📊 统计摘要:
- 总代码: 4813 行
- 总文件: 22 个
- 语言数: 2 种
- 徽章数: 6 个

🔗 触发提交: fa21511
🤖 由 GitHub Actions 自动生成
2025-11-03 03:38:42 +00:00
github-actions[bot] cce73ac0f3 chore: 更新代码统计 [2025-11-03 03:28]
📊 统计摘要:
- 总代码: 4813 行
- 总文件: 22 个
- 语言数: 2 种
- 徽章数: 6 个

🔗 触发提交: 7373553
🤖 由 GitHub Actions 自动生成
2025-11-03 03:28:32 +00:00
github-actions[bot] 1ae47b2388 chore: 更新代码统计 [2025-11-03 03:25]
📊 统计摘要:
- 总代码: 4813 行
- 总文件: 22 个
- 语言数: 2 种
- 徽章数: 6 个

🔗 触发提交: 3308b43
🤖 由 GitHub Actions 自动生成
2025-11-03 03:25:18 +00:00
github-actions[bot] 2578802bce chore: 更新代码统计 [2025-11-03 03:24]
📊 统计摘要:
- 总代码: 4813 行
- 总文件: 22 个
- 语言数: 2 种
- 徽章数: 6 个

🔗 触发提交: f422a75
🤖 由 GitHub Actions 自动生成
2025-11-03 03:24:58 +00:00
github-actions[bot] f2636d2a37 chore: 更新代码统计 [2025-11-03 03:22]
📊 统计摘要:
- 总代码: 4813 行
- 总文件: 22 个
- 语言数: 2 种
- 徽章数: 6 个

🔗 触发提交: 4ed8099
🤖 由 GitHub Actions 自动生成
2025-11-03 03:22:12 +00:00
github-actions[bot] da5c8da808 chore: 更新代码统计 [2025-11-03 03:19]
📊 统计摘要:
- 总代码: 4813 行
- 总文件: 22 个
- 语言数: 2 种
- 徽章数: 6 个

🔗 触发提交: bf6698a
🤖 由 GitHub Actions 自动生成
2025-11-03 03:19:56 +00:00
github-actions[bot] f703246a05 chore: 更新代码统计 [2025-11-03 02:44]
📊 统计摘要:
- 总代码: 4813 行
- 总文件: 22 个
- 语言数: 2 种
- 徽章数: 6 个

🔗 触发提交: b57c0cc
🤖 由 GitHub Actions 自动生成
2025-11-03 02:44:10 +00:00
github-actions[bot] da0cc81b10 chore: 更新代码统计 [2025-11-03 02:43]
📊 统计摘要:
- 总代码: 4813 行
- 总文件: 22 个
- 语言数: 2 种
- 徽章数: 6 个

🔗 触发提交: 1f535e0
🤖 由 GitHub Actions 自动生成
2025-11-03 02:43:38 +00:00
github-actions[bot] 6e96329d85 chore: 更新代码统计 [2025-11-02 15:17]
📊 统计摘要:
- 总代码: 5174 行
- 总文件: 22 个
- 语言数: 2 种
- 徽章数: 6 个

🔗 触发提交: 9961226
🤖 由 GitHub Actions 自动生成
2025-11-02 15:17:16 +00:00
github-actions[bot] 006a7b41c5 chore: 更新代码统计 [2025-11-02 15:15]
📊 统计摘要:
- 总代码: 4813 行
- 总文件: 22 个
- 语言数: 2 种
- 徽章数: 6 个

🔗 触发提交: b4c47c4
🤖 由 GitHub Actions 自动生成
2025-11-02 15:15:06 +00:00
github-actions[bot] f27f0850d2 chore: 更新代码统计 [2025-11-02 15:14]
📊 统计摘要:
- 总代码: 4813 行
- 总文件: 22 个
- 语言数: 2 种
- 徽章数: 6 个

🔗 触发提交: ec5785c
🤖 由 GitHub Actions 自动生成
2025-11-02 15:14:40 +00:00
github-actions[bot] ccc3f83c6d chore: 更新代码统计 [2025-11-02 15:03]
📊 统计摘要:
- 总代码: 4813 行
- 总文件: 22 个
- 语言数: 2 种
- 徽章数: 6 个

🔗 触发提交: 84e2751
🤖 由 GitHub Actions 自动生成
2025-11-02 15:03:32 +00:00
github-actions[bot] 2f0664e2be chore: 更新代码统计 [2025-11-02 14:51]
📊 统计摘要:
- 总代码: 4813 行
- 总文件: 22 个
- 语言数: 2 种
- 徽章数: 6 个

🔗 触发提交: 0bb627f
🤖 由 GitHub Actions 自动生成
2025-11-02 14:51:24 +00:00
github-actions[bot] c4ffd65315 chore: 更新代码统计 [2025-11-02 14:39]
📊 统计摘要:
- 总代码: 4813 行
- 总文件: 22 个
- 语言数: 2 种
- 徽章数: 6 个

🔗 触发提交: 9cde246
🤖 由 GitHub Actions 自动生成
2025-11-02 14:39:20 +00:00
csh 463400f5cb 更新 README.md 2025-11-02 22:13:19 +08:00
github-actions[bot] 46c3cef0ef chore: 更新代码统计 [2025-11-02 14:12]
📊 统计摘要:
- 总代码: 4797 行
- 总文件: 21 个
- 语言数: 2 种
- 徽章数: 6 个

🔗 触发提交: aaeed0d
🤖 由 GitHub Actions 自动生成
2025-11-02 14:12:23 +00:00
github-actions[bot] b6ddb49883 chore: 更新代码统计 [2025-11-02 14:08]
📊 统计摘要:
- 总代码: 4813 行
- 总文件: 22 个
- 语言数: 2 种
- 徽章数: 6 个

🔗 触发提交: f57e698
🤖 由 GitHub Actions 自动生成
2025-11-02 14:08:26 +00:00
github-actions[bot] 2900790062 chore: 更新代码统计 [2025-11-02 13:56]
📊 统计摘要:
- 总代码: 4813 行
- 总文件: 22 个
- 语言数: 2 种
- 徽章数: 6 个

🔗 触发提交: 2ac94af
🤖 由 GitHub Actions 自动生成
2025-11-02 13:56:46 +00:00
github-actions[bot] cb10dd42fe chore: 更新代码统计 [2025-11-02 13:50]
📊 统计摘要:
- 总代码: 4813 行
- 总文件: 22 个
- 语言数: 2 种
- 徽章数: 6 个

🔗 触发提交: d7a0c7a
🤖 由 GitHub Actions 自动生成
2025-11-02 13:50:44 +00:00
github-actions[bot] 35bf12107b chore: 更新代码统计 [2025-11-02 13:34]
📊 统计摘要:
- 总代码: 4813 行
- 总文件: 22 个
- 语言数: 2 种
- 徽章数: 6 个

🔗 触发提交: 948bcfa
🤖 由 GitHub Actions 自动生成
2025-11-02 13:34:29 +00:00
github-actions[bot] 9812ec1b3a chore: 初始化统计分支 2025-11-02 13:14:01 +00:00
31 changed files with 92 additions and 5269 deletions
-278
View File
@@ -1,278 +0,0 @@
#!/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("<", "&lt;")
.replace(">", "&gt;")
.replace('"', "&quot;")
.replace("'", "&apos;")
)
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"![{stat.display_name}](./{badge_dir_name}/{stat.lang_id}-lines.svg) |"
)
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}** 行 | ![总代码](./{badge_dir_name}/total-lines.svg) |
| 📁 总文件数 | **{args.formatted_total_files}** 个 | ![总文件](./{badge_dir_name}/total-files.svg) |
| 🌐 语言种类 | **{args.language_count}** 种 | ![语言种类](./{badge_dir_name}/language-count.svg) |
## 🌈 语言分布
| 语言 | 代码行数 | 文件数 | 占比 | 徽章 |
|------|----------|--------|------|------|
{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()
File diff suppressed because it is too large Load Diff
-174
View File
@@ -1,174 +0,0 @@
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"
-102
View File
@@ -1,102 +0,0 @@
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
-659
View File
@@ -1,659 +0,0 @@
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 "✅ 清理完成"
-26
View File
@@ -1,26 +0,0 @@
# ---> Vim
# Swap
[._]*.s[a-v][a-z]
!*.svg # comment out if you don't need vector files
[._]*.sw[a-p]
[._]s[a-rt-v][a-z]
[._]ss[a-gi-z]
[._]sw[a-p]
# Session
Session.vim
Sessionx.vim
# Temporary
.netrwhist
*~
# Auto-generated tag files
tags
# Persistent undo
[._]*.un~
# Environment files
.env
# Runner runtime data
docker-runner/presets/*/runner-data/
-131
View File
@@ -1,131 +0,0 @@
# CHANGELOG
## :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
### :pencil: What's Changed
- :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
- 更新 CHANGELOG.md [skip ci] ([7373553](https://git.mytsl.cn/csh/actions-template/commit/7373553270d20357a225aaf41334919086f7687d)) by @csh
- :bug: fix bug ([fa21511](https://git.mytsl.cn/csh/actions-template/commit/fa21511d1ab98e415036046bc6950f8a3b322f25)) 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
- :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
<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.2
*Released on 2025-11-02*
### :pencil: What's Changed
- :art: 代码格式化 ([`ec5785c`](https://github.com/csh/actions-template/commit/ec5785cc6af3267285623090e19b5c215d89d37f))
- :bug: 修复tag获取错误问题 ([`84e2751`](https://github.com/csh/actions-template/commit/84e275166844d9fd410a7aba3e32807683784970))
- :bug: 修复bug ([`0bb627f`](https://github.com/csh/actions-template/commit/0bb627fd34ce47e2baaf9b3856d12564da2423d9))
- :recycle: 重构`changelog_and_release` ([`9cde246`](https://github.com/csh/actions-template/commit/9cde246b208afe25ac6ecca67ecf4e7ad2e56ecc))
- :bug: 修复日期 ([`aaeed0d`](https://github.com/csh/actions-template/commit/aaeed0d9197af8f9c732ab9e4a2ae5dc99a40c00))
- :bug: 修复`update_state_badge.yml`自动生成的`README`日期错误 ([`f57e698`](https://github.com/csh/actions-template/commit/f57e6984c048f6dbfc5e8083dd68c8910a923487))
- :memo: 更新`WORKFLOW`说明 ([`2ac94af`](https://github.com/csh/actions-template/commit/2ac94aff1c499ca12bbe29652532bd4a8bef425d))
- :bug: help me ([`d7a0c7a`](https://github.com/csh/actions-template/commit/d7a0c7a85b04a3177790a8a74ebb1c5154e136de))
- :bug: 继续修修 ([`948bcfa`](https://github.com/csh/actions-template/commit/948bcfa7875b0276b649851c7445367b13c95438))
- :bug: 继续修复测试 ([`23a7fb5`](https://github.com/csh/actions-template/commit/23a7fb5a285c3f6f16071c82f6ba24137507a10d))
- :bug: 修`update_stats_badge.yml`,还没跑起来 ([`b616573`](https://github.com/csh/actions-template/commit/b616573bfd8374a8b007e8066cfcd053191d9717))
- :bug: 修复`update_stats_badge.yml` 克隆bug ([`5b3629f`](https://github.com/csh/actions-template/commit/5b3629fb732ecfdd78496736f1f861ec0f8d9eee))
- :bug: 修复token配置错误 ([`38e6c82`](https://github.com/csh/actions-template/commit/38e6c823948662e18e60bdc483112a2957352ffb))
- :bug: 修复runs-on错误 ([`376c1a2`](https://github.com/csh/actions-template/commit/376c1a28666378ff11c7d0669a61c4c980ca6261))
- :sparkles: 新增:代码统计徽章 action: `update_stats_badge.yml` ([`d5a7d55`](https://github.com/csh/actions-template/commit/d5a7d5506ec0f7d128c60f908bbd17198308c587))
- :demo: 文档更新 ([`c0aef0f`](https://github.com/csh/actions-template/commit/c0aef0f5316aa0fba1ae5686b8d7cc8c770e816e))
- :package: workflows的文件进行更名,并且调整后缀为`yml` ([`a21adb9`](https://github.com/csh/actions-template/commit/a21adb9fcc79839ddd717f862abe69e7e232fbca))
- :wastebasket: 移除发布Release时候自动上传CHANGELOG.md ([`8971cbf`](https://github.com/csh/actions-template/commit/8971cbffc3023891852a14d9917c8b9b2b7f8272))
---
## :bookmark: 0.0.1
### :pencil: What's Changed
- :wastebasket: 删除CHANGELOG,让工作流自动执行 ([d50ee5d](https://git.mytsl.cn/csh/actions-template/commit/d50ee5d)) by @csh
- :wrench: gitignore新增CHANGELOG.md ([29cd19e](https://git.mytsl.cn/csh/actions-template/commit/29cd19e)) by @csh
- :bug: 修复生成的CHANGELOG格式不对
:sparkles: 过滤commit新增`Initial commit`
:art: 代码格式化 ([b651daf](https://git.mytsl.cn/csh/actions-template/commit/b651daf)) by @csh
- :wastebasket: gitignore移除CHANGELOG.md,会导致工作流提交失败 ([27f35a6](https://git.mytsl.cn/csh/actions-template/commit/27f35a6)) 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>
---
-648
View File
@@ -1,648 +0,0 @@
# 🚀 Gitea Runner Docker 部署教程
## 📋 目录
- [简介](#-简介)
- [目录结构](#-目录结构)
- [版本选择](#-版本选择)
- [部署步骤](#-部署步骤)
- [管理命令](#-管理命令)
- [Buildx 多架构构建示例](#-buildx-多架构构建示例)
- [版本对比](#-版本对比)
- [常见问题](#-常见问题)
---
## 💡 简介
Gitea Runner 是 Gitea 的 CI/CD 执行器,类似于 GitLab Runner 或 GitHub Actions Runner,用于执行 Gitea Actions 工作流。
本项目提供了标准版和 Buildx 版两种部署方案,通过预设配置快速部署。
- 只想先看仓库总览和最快上手路径:回到 [README.md](./README.md)
- 需要查看 workflow 示例和 secret 约定:参考 [WORKFLOW.md](./WORKFLOW.md)
---
## 📂 目录结构
```txt
docker-runner/
├── common/ # 通用脚本(所有版本共用)
│ ├── check_crlf.sh # Windows 换行符检查工具(宿主机本地执行)
│ ├── entrypoint.sh # 容器启动脚本
│ ├── setup.sh # Runner 安装脚本
│ ├── upgrade.sh # Runner 升级脚本
│ ├── register.sh # Runner 注册脚本
│ └── manage.sh # Runner 管理脚本
└── presets/ # 预设配置(选择一个使用)
├── standard-ubuntu-22/ # 标准版 (Ubuntu 22.04)
│ ├── Dockerfile
│ └── docker-compose.yml
├── buildx-ubuntu-22/ # Buildx 版 (Ubuntu 22.04)
│ ├── Dockerfile
│ └── docker-compose.yml
└── buildx-archlinux/ # Buildx 版 (Arch Linux)
├── Dockerfile
└── docker-compose.yml
```
**说明:**
- `common/` 目录中的脚本由所有版本共享,其中 `entrypoint.sh``setup.sh``upgrade.sh``register.sh``manage.sh` 会通过 docker-compose.yml 挂载到容器
- `check_crlf.sh` 是宿主机本地检查工具,用于在构建前修复 `common/` 目录脚本的换行符和执行权限
- `presets/` 目录中每个子目录是一个完整的预设配置,包含 Dockerfile 和 docker-compose.yml
- 数据持久化在 `runner-data/` 目录(自动创建),包含 runner 配置和 act_runner 二进制文件
---
## 🎯 版本选择
### 📦 标准版(推荐)
**适合场景:**
- ✅ 大多数日常使用场景
- ✅ 只需在当前架构上运行应用
- ✅ 简单的 CI/CD 流程
- ✅ 不需要构建多架构容器镜像
**特点:**
- 🪶 轻量级,镜像体积约 400MB
- ⚡ 配置简单,启动快速(5-10 秒)
- 🔒 无需特权模式,更安全
- 🎯 易于维护
**预设:** `presets/standard-ubuntu-22/`
---
### 🚀 Buildx 多架构版
**适合场景:**
- ✅ 需要构建 arm64 + amd64 双架构镜像
- ✅ 发布容器镜像到公共仓库
- ✅ 跨平台应用开发和测试
- ✅ 高级 CI/CD 需求
**特点:**
- 🌐 支持 Docker Buildx 多架构构建
- 🔧 内置 QEMU 跨架构模拟
- 🤖 自动配置 Buildx builder
- ⚠️ 需要特权模式和 Docker socket
**预设选择:**
- `presets/buildx-ubuntu-22/` - Ubuntu 22.04 基础(推荐)
- `presets/buildx-archlinux/` - Arch Linux 滚动更新版本
---
## 🚀 部署步骤
### 标准版部署
#### 1. 进入预设目录
```bash
cd docker-runner/presets/standard-ubuntu-22/
```
#### 2. 准备实例配置
```bash
cp .env.example .env
```
按需编辑 `.env` 中的 `GITEA_INSTANCE``GITEA_TOKEN`
#### 3. (可选)检查换行符
如果从 Windows 复制文件,建议先在宿主机执行检查工具,修复 `../../common/` 下脚本的换行符和权限:
```bash
../../common/check_crlf.sh
```
#### 4. 构建并启动容器
```bash
docker compose build
docker compose up -d
```
#### 5. 安装 Runner
```bash
docker compose exec gitea-runner /data/setup.sh
```
脚本会自动探测最新 `act_runner` 版本作为默认值,然后提示你确认架构。
#### 6. 注册 Runner
```bash
docker compose exec gitea-runner /data/register.sh
```
输入你的 Gitea 实例 URL 和注册令牌。
Runner 注册后会自动启动,无需重启容器。新注册的 runner 默认并发为 `4`
#### 7. 验证运行状态
```bash
# 查看容器日志
docker compose logs -f
# 查看 runner 状态
docker compose exec gitea-runner /data/manage.sh status
```
---
### Buildx 版部署
#### 1. 选择并进入预设目录
```bash
# Ubuntu 22.04 版本(推荐)
cd docker-runner/presets/buildx-ubuntu-22/
# 或 Arch Linux 版本
cd docker-runner/presets/buildx-archlinux/
```
#### 2. 准备实例配置
```bash
cp .env.example .env
```
按需编辑 `.env` 中的 `GITEA_INSTANCE``GITEA_TOKEN`
#### 3. (可选)配置代理
如果需要代理,编辑 `docker-compose.yml` 取消注释并修改代理地址:
```yaml
environment:
- http_proxy=http://host.docker.internal:20122
- https_proxy=http://host.docker.internal:20122
```
#### 4. 构建并启动
```bash
docker compose build
docker compose up -d
```
#### 5. 验证 Buildx 初始化
```bash
# 查看日志,应该看到:
# ✓ Docker daemon is ready
# ✓ arm64 support verified
# ✓ amd64 support verified
# ✓ Buildx configured successfully!
docker compose logs -f
# 进入容器验证
docker compose exec gitea-runner docker buildx ls
# 应该看到 gitea-multiarch builder
```
#### 6. 安装和注册
```bash
docker compose exec gitea-runner /data/setup.sh
docker compose exec gitea-runner /data/register.sh
```
Runner 注册后会自动启动。
---
## 🔧 管理命令
所有版本的管理命令相同(脚本位于 `common/` 目录):
### 查看状态
```bash
# 列出所有 runners
docker compose exec gitea-runner /data/manage.sh list
# 查看所有 runners 状态
docker compose exec gitea-runner /data/manage.sh status
```
### 日志管理
```bash
# 查看日志(最后 50 行)
docker compose exec gitea-runner /data/manage.sh logs <runner-name>
# 指定行数
docker compose exec gitea-runner /data/manage.sh logs <runner-name> 100
# 实时跟踪日志
docker compose exec gitea-runner /data/manage.sh follow <runner-name>
```
### Runner 控制
```bash
# 启动 runner
docker compose exec gitea-runner /data/manage.sh start <runner-name>
# 停止 runner
docker compose exec gitea-runner /data/manage.sh stop <runner-name>
# 重启 runner
docker compose exec gitea-runner /data/manage.sh restart <runner-name>
# 删除 runner
docker compose exec gitea-runner /data/manage.sh delete <runner-name>
```
### 升级 act_runner
```bash
# 自动检查最新稳定版本,下载后重启所有已注册 runners
docker compose exec gitea-runner /data/upgrade.sh
# 指定版本
docker compose exec gitea-runner /data/upgrade.sh --version 0.6.1
# 仅重启指定 runner
docker compose exec gitea-runner /data/upgrade.sh --runner <runner-name>
```
`upgrade.sh` 只替换 `/data/bin/act_runner` 并重启 runner 进程,不会重新注册,也不需要重新输入 token。
### 添加多个 Runners
```bash
# 注册新的 runner(使用不同名称)
docker compose exec gitea-runner /data/register.sh
```
### 交互式管理
```bash
# 运行交互式菜单
docker compose exec gitea-runner /data/manage.sh
```
---
## 🔧 Buildx 多架构构建示例
### 示例 1: 基础构建
在 Gitea Actions workflow 中使用:
```yaml
name: Multi-Arch Build
on: [push]
jobs:
build:
runs-on: ubuntu-22.04
steps:
- uses: actions/checkout@v3
- name: Set up QEMU
uses: docker/setup-qemu-action@v2
- name: Set up Buildx
uses: docker/setup-buildx-action@v2
- name: Build and Push
uses: docker/build-push-action@v4
with:
context: .
platforms: linux/amd64,linux/arm64
tags: myapp:latest
push: true
```
### 示例 2: Go 应用优化构建
使用交叉编译可以大幅提升构建速度:
```dockerfile
FROM --platform=$BUILDPLATFORM golang:1.21 AS builder
ARG TARGETOS TARGETARCH
WORKDIR /app
COPY . .
RUN CGO_ENABLED=0 GOOS=$TARGETOS GOARCH=$TARGETARCH go build -o app
FROM alpine
COPY --from=builder /app/app /app
CMD ["/app"]
```
### 示例 3: 启用构建缓存
```yaml
- name: Build with cache
uses: docker/build-push-action@v4
with:
context: .
platforms: linux/amd64,linux/arm64
cache-from: type=registry,ref=myimage:buildcache
cache-to: type=registry,ref=myimage:buildcache,mode=max
tags: myimage:latest
push: true
```
---
## ⚖️ 版本对比
### 功能对比表
| 功能 | 标准版 | Buildx 版 |
| ---------------- | ------- | --------- |
| 基础 Runner 功能 | ✅ | ✅ |
| 当前架构原生构建 | ✅ | ✅ |
| 跨架构模拟构建 | ❌ | ✅ |
| 多架构镜像 | ❌ | ✅ |
| Buildx 支持 | ❌ | ✅ |
| QEMU 内置 | ❌ | ✅ |
| 特权模式要求 | ❌ | ✅ 必需 |
| Docker socket | 可选 | ✅ 必需 |
| 镜像大小 | ~400MB | ~850MB |
| 启动时间 | 5-10 秒 | 15-20 秒 |
| 复杂度 | 简单 | 中等 |
### 性能对比(Buildx 版)
在 ARM64 主机上使用 Buildx 构建不同架构的性能:
| 项目类型 | ARM64(原生) | AMD64(QEMU) | 双架构并行 |
| ------------ | ----------- | ----------- | ---------- |
| Alpine 镜像 | 5s | 15s | 18s |
| Node.js 应用 | 2min | 8min | 9min |
| Go 应用 | 1.5min | 6min | 7min |
| Go(交叉编译) | 1min | 1.5min | 2min |
**关键发现:**
- 🏆 原生架构构建最快
- 🐌 跨架构模拟慢 3-4 倍(QEMU)
- ⚡ 交叉编译比纯 QEMU 快 4 倍
- 💾 使用缓存可提速 80-90%
### 🎯 选择建议
#### 选择标准版
- ✅ 只需要在当前架构上运行应用
- ✅ 不需要发布多架构镜像
- ✅ 追求简单和轻量
- ✅ 不需要 Docker-in-Docker
#### 选择 Buildx 版
- ✅ 需要构建多架构镜像(如 amd64 + arm64)
- ✅ 发布到 Docker Hub 等公共仓库
- ✅ 跨平台应用开发
- ✅ CI/CD 需要多架构支持
---
## ❓ 常见问题
### Q1: 如何更新 Runner 版本?
```bash
docker compose exec gitea-runner /data/upgrade.sh
# 脚本会自动从官方目录页检测最新 act_runner 版本
```
如需固定版本:
```bash
docker compose exec gitea-runner /data/upgrade.sh --version 0.6.1
```
### Q2: 如何添加多个 Runners?
```bash
# 多次运行注册脚本,每次使用不同名称
docker compose exec gitea-runner /data/register.sh
# 查看所有 runners
docker compose exec gitea-runner /data/manage.sh list
```
### Q3: 容器启动失败 "exec format error"
**原因:** Windows 换行符问题(CRLF vs LF)
**解决:**
```bash
# 方法 1: 使用提供的检查工具
cd docker-runner/presets/<your-preset>/
../../common/check_crlf.sh
# 方法 2: 手动转换
sed -i 's/\r$//' ../../common/*.sh
# 重新构建
docker compose down
docker compose build --no-cache
docker compose up -d
```
### Q4: 如何完全重置?
```bash
# 停止并删除容器和卷
docker compose down -v
# 删除数据目录
rm -rf runner-data
# 重新构建
docker compose build --no-cache
docker compose up -d
```
### Q5: 如何查看日志?
```bash
# 容器日志
docker compose logs -f
# Runner 日志
docker compose exec gitea-runner /data/manage.sh logs <runner-name>
# 实时跟踪日志
docker compose exec gitea-runner /data/manage.sh follow <runner-name>
```
### Q6: Buildx 版 - 构建速度慢?
**优化方法:**
#### 1. 启用缓存
```yaml
- name: Build with cache
uses: docker/build-push-action@v4
with:
cache-from: type=registry,ref=myimage:buildcache
cache-to: type=registry,ref=myimage:buildcache,mode=max
platforms: linux/amd64,linux/arm64
```
#### 2. 使用交叉编译
对于 Go、Rust 等支持交叉编译的语言,优先使用交叉编译而非 QEMU 模拟:
```dockerfile
FROM --platform=$BUILDPLATFORM golang:1.21 AS builder
ARG TARGETOS TARGETARCH
RUN CGO_ENABLED=0 GOOS=$TARGETOS GOARCH=$TARGETARCH go build -o app
```
#### 3. 按需构建
```yaml
# 开发分支只构建当前架构,生产分支构建双架构
platforms: ${{ github.ref == 'refs/heads/main' && 'linux/amd64,linux/arm64' || 'linux/arm64' }}
```
### Q7: Buildx 版 - 如何验证 Buildx?
```bash
# 进入容器
docker compose exec gitea-runner bash
# 检查 builder
docker buildx ls
# 测试构建
echo 'FROM alpine' | docker buildx build --platform linux/amd64,linux/arm64 -
```
### Q8: 如何清理 Docker 资源?
#### 清理未使用的镜像
```bash
# 清理悬空镜像(dangling images)
docker image prune
# 清理所有未使用的镜像
docker image prune -a
# 查看镜像占用空间
docker system df
```
#### 清理 Buildx 缓存(仅 Buildx 版)
```bash
# 进入容器
docker compose exec gitea-runner bash
# 清理构建缓存(保留 builder)
docker buildx prune
# 清理所有构建缓存
docker buildx prune -a -f
# 删除 builder(会自动清理相关容器和镜像)
docker buildx rm gitea-multiarch
```
#### 完整清理流程
```bash
# 1. 停止所有容器
docker compose down
# 2. 清理 Buildx(如果使用 Buildx 版)
docker compose exec gitea-runner docker buildx rm gitea-multiarch
# 3. 清理未使用的镜像
docker image prune -a
# 4. 清理系统(慎用!会清理所有未使用资源)
docker system prune -a --volumes
# 5. 查看清理效果
docker system df
```
⚠️ **注意:** `docker system prune -a` 会删除所有未使用的镜像,包括其他项目的。
### Q9: 数据持久化在哪里?
所有数据持久化在 `runner-data/` 目录:
```txt
runner-data/
├── bin/
│ └── act_runner # Runner 可执行文件(持久化)
├── runners/ # Runner 配置目录
│ └── <runner-name>/
│ ├── .runner # 注册信息
│ ├── config.yaml # 配置文件
│ └── cache/ # 构建缓存
└── buildx/ # Buildx 配置(仅 Buildx 版)
└── .configured
```
容器重启或重建后,这些持久化数据不会丢失。
### Q10: 如何切换不同预设?
```bash
# 1. 停止当前容器
cd docker-runner/presets/<current-preset>/
docker compose down
# 2. 切换到新预设
cd ../buildx-ubuntu-22/
# 3. 复制数据目录(可选,保留旧数据)
cp -r ../standard-ubuntu-22/runner-data .
# 4. 启动新预设
docker compose build
docker compose up -d
```
---
## 📚 参考资源
- 📖 [Gitea Actions 官方文档](https://docs.gitea.com/usage/actions/overview)
- 🚀 [Gitea Runner 下载](https://dl.gitea.com/act_runner/)
- 🐳 [Docker Buildx 文档](https://docs.docker.com/reference/cli/docker/buildx/)
- 🔧 [Act Runner GitHub](https://gitea.com/gitea/act_runner)
---
## 💬 反馈与支持
如果你在使用过程中遇到问题,欢迎:
- 📝 提交 Issue
- 💡 分享你的使用经验
- 🌟 给项目点个 Star
---
+35 -38
View File
@@ -1,53 +1,50 @@
# 🎯 Gitea Runner Template
# 📊 代码统计详细报告
[![Docker](https://img.shields.io/badge/docker-ready-brightgreen.svg)](https://www.docker.com/)
> 此分支由 Gitea Actions 自动生成和维护
>
> ⚠️ **请勿手动修改此分支的内容!**
>
> 🤖 由 Gitea Actions 自动生成
>
> 📅 更新时间: 2026-07-21 03:15:02 UTC
> 🚀 Gitea Runner Docker 部署模板 + Actions Workflow 示例 + 文档规范
## 📈 总体统计
## 📖 项目简介
| 统计项 | 数值 | 徽章 |
|--------|------|------|
| 💻 总代码行数 | **4,490** 行 | ![总代码](./badges/total-lines.svg) |
| 📁 总文件数 | **26** 个 | ![总文件](./badges/total-files.svg) |
| 🌐 语言种类 | **3** 种 | ![语言种类](./badges/language-count.svg) |
这是一个完整的 Gitea Runner 模板项目,提供:
## 🌈 语言分布
- 🐳 **Docker 部署方案**:开箱即用的 Runner 容器化部署
- 📝 **Workflow 示例**:自动化 CHANGELOG、Release 和统计徽章工作流
- 📚 **文档规范**:统一的部署说明与工作流说明
- 🔧 **配置指南**:预设配置、注册流程和运行方式说明
## 📂 文档导航
### 🚀 [DEPLOYMENT.md](./DEPLOYMENT.md)
**Gitea Runner Docker 部署完整教程**
包含内容:
- 📦 标准版 vs Buildx 多架构版选择
- 📝 Dockerfile 和 docker-compose.yml 配置
- 🔧 安装、注册、管理脚本详解
- ⚙️ 完整的部署流程
- ❓ 常见问题和故障排除
👉 **完整的 Runner 部署教程,从零开始搭建**
| 语言 | 代码行数 | 文件数 | 占比 | 徽章 |
|------|----------|--------|------|------|
| **YAML** | 2,064 | 7 | 46.0% | ![YAML](./badges/yaml-lines.svg) |
| **Shell** | 1,251 | 6 | 27.9% | ![Shell](./badges/shell-lines.svg) |
| **Python** | 218 | 1 | 4.9% | ![Python](./badges/python-lines.svg) |
---
### 📋 [WORKFLOW.md](./WORKFLOW.md)
## ⚙️ 配置说明
**Gitea Actions 自动化工作流示例**
### Token 配置
包含内容:
- 当前使用: **WORKFLOW**
- 需要在 Settings -> Actions -> Secrets 中配置 `WORKFLOW`
- 💡 工作流配置说明
- 🔧 如何使用和定制
- 🧰 案例:`prepare.yml` - 检查并补齐公共工具,集中准备共享 bare 仓库
- 📊 案例:`update_stats_badge.yml` - 在成功的 Prepare 主分支运行后生成统计徽章
- 📦 案例:`changelog_and_release.yml` - 在成功的 Prepare 版本 Tag 运行后生成 CHANGELOG 和 Release
- 🖥️ 案例:`ubuntu_system_info.yml` - 直接触发的独立系统信息任务,可与 Prepare 并行
### 排除规则
👉 **实用的 Actions 工作流,可直接复制使用**
当前排除的目录:
```
node_modules,dist,build,out,target,vendor,.venv,venv,__pycache__,.git,.github
```
### 阈值设置
- 最小代码行数阈值: **10**
- 低于此阈值的语言不会生成徽章
---
仓库相关工作流采用 `Prepare → workflow_run 消费者` 模型;系统信息不读取仓库,因此保持独立触发。详细的共享目录、并发和恢复规则见 [WORKFLOW.md](./WORKFLOW.md)。
具体部署步骤、预设选择、注册流程、升级方式和故障排查请查看 [DEPLOYMENT.md](./DEPLOYMENT.md)。
*📊 统计仓库:`csh/actions-template`*
-212
View File
@@ -1,212 +0,0 @@
# Workflow
本目录包含 Gitea Actions 的自动化工作流示例,展示如何使用 Gitea Actions 实现 CI/CD 自动化。
- 先看仓库总览:回到 [README.md](./README.md)
- 需要部署或维护 runner:参考 [DEPLOYMENT.md](./DEPLOYMENT.md)
---
## 📂 文件结构
```txt
.gitea/workflows/
├── prepare.yml # 公共工具 + shared bare repository
├── changelog_and_release.yml # Prepare 成功后的 Tag 发布消费者
├── 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`)
**功能**:在推送 tag 时自动生成 CHANGELOG 并创建 Release
**特性**
- 🏷️ 自动识别语义版本号(SemVer)
- 📝 智能生成 CHANGELOG 条目
- 🔄 支持多个开发版本内容叠加
- 👥 自动提取贡献者列表
- 🚀 创建 GitHub/Gitea Release
- 📎 上传附件到 Release
- 🏷️ 自动识别 Pre-release 标记
**触发方式**
推送数字开头的 Tag 会先触发 `Prepare`。Prepare 成功后,发布 routing job 从 `workflow_run.path` 恢复 Tag,确认 Tag peeled commit 等于 `head_sha`,随后才生成 CHANGELOG 和 Release。
```bash
git tag 1.0.0
git push origin 1.0.0
```
**文件**[changelog_and_release.yml](.gitea/workflows/changelog_and_release.yml)
💡 **详细配置**:查看 `changelog_and_release.yml` 文件顶部的 `env` 区域,所有配置项都有详细注释说明
---
#### 2. 📊 代码统计徽章工作流 (`update_stats_badge.yml`)
**功能**:自动统计代码行数并生成 SVG 徽章与统计报告
**特性**
- 📈 统计总代码行数和文件数
- 🌐 按语言分组统计
- 🎨 自动生成仓库内自托管的 SVG 徽章
- 📊 在 `stats` 分支根目录生成统计报告 README
- 🚫 灵活的目录排除配置
**触发方式**
主分支 push 或在 `main`/`master` 上手动运行 Prepare 后,统计 workflow 通过成功的 `workflow_run` 启动,并始终从上游 `head_sha` 的 detached checkout 读取源码。
```bash
# 推送到主分支时自动触发
git push origin main
# 手动触发
# 仓库 → Actions → Prepare → Run workflow
```
**配置文件**[update_stats_badge.yml](.gitea/workflows/update_stats_badge.yml)
**markdown引用格式**: `![C++](https://你的gitea/用户名/仓库/raw/branch/stats/badges/cpp-lines.svg)`
**产物结构**
```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。
---
### 🔜 待扩展
更多自动化场景:
- 🧪 **自动测试** - 推送代码时运行单元测试
- 🏗️ **自动构建** - 构建并发布 Docker 镜像
- 📊 **代码质量** - 运行代码检查和静态分析
- 🌐 **自动部署** - 部署到生产环境
- 📧 **通知集成** - 发送构建结果到 Slack/邮件
- 🔐 **安全扫描** - 依赖漏洞扫描
---
## ❓ 常见问题
### Q1: 如何配置 Token
**步骤**
1. Gitea → 设置 → 应用 → 生成新令牌
2. 权限:勾选 **repo**
3. 仓库 → Settings → Actions → Secrets → 添加 Secret
4. 名称:`WORKFLOW`,值:你的 Token
💡 **提示**:一个 Token 即可完成所有操作(Git + API)
---
### Q2: `changelog_and_release` 没有触发?
**检查清单**
- [ ] Workflow 文件在 `.gitea/workflows/` 目录
- [ ] 文件名正确(如 `changelog_and_release.yml`
- [ ] Token 已正确配置(Secret 名称为 `WORKFLOW`
- [ ] Tag 格式正确(数字开头,如 `1.0.0`
- [ ] Gitea Actions 已启用
- [ ] Runner 正常运行
---
### Q3: `changelog_and_release` 版本号格式有要求吗?
推荐使用**语义化版本号(SemVer)**:
```bash
✅ 推荐格式:
1.0.0 # 正式版本
1.0.0-beta1 # 预发布版本
❌ 不推荐:
v1.0.0 # 不要使用 v 前缀
1.0.0-b1 # 后缀要完整
```
详细规范请参考 workflow 文件中的注释说明。
---
### Q4: 如何查看执行日志?
仓库 → Actions → 点击对应的 workflow 运行记录 → 查看详细日志
---
### Q5: Token 权限不足怎么办?
确保 Token 具有以下权限:
-**repo** - 完整仓库访问权限
- 用于读取代码
- 用于推送提交
- 用于创建 Release
如果还是失败,尝试重新生成 Token。
---
## 🤝 贡献
欢迎提交更多实用的 Workflow 示例!
---
## 📄 许可 MIT
+9
View File
@@ -0,0 +1,9 @@
<svg xmlns="http://www.w3.org/2000/svg" width="118" height="20" role="img" aria-label="语言: 3 种">
<title>语言: 3 种</title>
<rect width="118" height="20" rx="10" fill="#f6f8fa" stroke="#d0d7de" />
<rect x="64" y="3" width="44" height="14" rx="7" fill="#purple" />
<g text-anchor="middle" font-family="DejaVu Sans,Verdana,Geneva,sans-serif" font-size="11">
<text x="32.0" y="14" fill="#24292f">语言</text>
<text x="86.0" y="14" fill="#ffffff">3 种</text>
</g>
</svg>

After

Width:  |  Height:  |  Size: 495 B

+10
View File
@@ -0,0 +1,10 @@
<svg xmlns="http://www.w3.org/2000/svg" width="124" height="20" role="img" aria-label="Python: 218 行">
<title>Python: 218 行</title>
<rect width="62" height="20" fill="#555555" />
<rect x="62" width="62" height="20" fill="#3572A5" />
<rect width="124" height="20" rx="3" fill="transparent" />
<g fill="#ffffff" text-anchor="middle" font-family="DejaVu Sans,Verdana,Geneva,sans-serif" font-size="11">
<text x="31.0" y="14">Python</text>
<text x="93.0" y="14">218 行</text>
</g>
</svg>

After

Width:  |  Height:  |  Size: 508 B

+10
View File
@@ -0,0 +1,10 @@
<svg xmlns="http://www.w3.org/2000/svg" width="131" height="20" role="img" aria-label="Shell: 1,251 行">
<title>Shell: 1,251 行</title>
<rect width="55" height="20" fill="#555555" />
<rect x="55" width="76" height="20" fill="#4EAA25" />
<rect width="131" height="20" rx="3" fill="transparent" />
<g fill="#ffffff" text-anchor="middle" font-family="DejaVu Sans,Verdana,Geneva,sans-serif" font-size="11">
<text x="27.5" y="14">Shell</text>
<text x="93.0" y="14">1,251 行</text>
</g>
</svg>

After

Width:  |  Height:  |  Size: 511 B

+9
View File
@@ -0,0 +1,9 @@
<svg xmlns="http://www.w3.org/2000/svg" width="125" height="20" role="img" aria-label="文件: 26 个">
<title>文件: 26 个</title>
<rect width="125" height="20" rx="10" fill="#f6f8fa" stroke="#d0d7de" />
<rect x="64" y="3" width="51" height="14" rx="7" fill="#green" />
<g text-anchor="middle" font-family="DejaVu Sans,Verdana,Geneva,sans-serif" font-size="11">
<text x="32.0" y="14" fill="#24292f">文件</text>
<text x="89.5" y="14" fill="#ffffff">26 个</text>
</g>
</svg>

After

Width:  |  Height:  |  Size: 497 B

+9
View File
@@ -0,0 +1,9 @@
<svg xmlns="http://www.w3.org/2000/svg" width="146" height="20" role="img" aria-label="代码: 4,490 行">
<title>代码: 4,490 行</title>
<rect width="146" height="20" rx="10" fill="#f6f8fa" stroke="#d0d7de" />
<rect x="64" y="3" width="72" height="14" rx="7" fill="#blue" />
<g text-anchor="middle" font-family="DejaVu Sans,Verdana,Geneva,sans-serif" font-size="11">
<text x="32.0" y="14" fill="#24292f">代码</text>
<text x="100.0" y="14" fill="#ffffff">4,490 行</text>
</g>
</svg>

After

Width:  |  Height:  |  Size: 506 B

+10
View File
@@ -0,0 +1,10 @@
<svg xmlns="http://www.w3.org/2000/svg" width="124" height="20" role="img" aria-label="YAML: 2,064 行">
<title>YAML: 2,064 行</title>
<rect width="48" height="20" fill="#555555" />
<rect x="48" width="76" height="20" fill="#CB171E" />
<rect width="124" height="20" rx="3" fill="transparent" />
<g fill="#ffffff" text-anchor="middle" font-family="DejaVu Sans,Verdana,Geneva,sans-serif" font-size="11">
<text x="24.0" y="14">YAML</text>
<text x="86.0" y="14">2,064 行</text>
</g>
</svg>

After

Width:  |  Height:  |  Size: 508 B

-95
View File
@@ -1,95 +0,0 @@
#!/bin/bash
echo "=========================================="
echo " 文件格式快速修复工具"
echo "=========================================="
echo ""
# 颜色定义
RED='\033[0;31m'
GREEN='\033[0;32m'
YELLOW='\033[1;33m'
NC='\033[0m'
SCRIPT_DIR=$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)
# 要检查的文件列表
SCRIPT_FILES=(
"entrypoint.sh"
"setup.sh"
"upgrade.sh"
"register.sh"
"manage.sh"
)
FIXED_COUNT=0
TOTAL_FILES=0
echo "🔍 检查并修复以下文件:"
printf '%s\n' "${SCRIPT_FILES[@]}" | sed 's/^/ - /'
echo ""
echo "----------------------------------------"
echo ""
for file in "${SCRIPT_FILES[@]}"; do
TOTAL_FILES=$((TOTAL_FILES + 1))
file_path="${SCRIPT_DIR}/${file}"
# 检查文件是否存在
if [ ! -f "${file_path}" ]; then
echo -e "${RED}$file - 文件不存在,跳过${NC}"
continue
fi
NEEDS_FIX=false
# 检查换行符
HAS_CRLF=false
if file "${file_path}" | grep -qi "CRLF\|with CR"; then
HAS_CRLF=true
NEEDS_FIX=true
fi
# 检查权限
NEEDS_CHMOD=false
if [ ! -x "${file_path}" ]; then
NEEDS_CHMOD=true
NEEDS_FIX=true
fi
# 显示状态
echo -n "📄 $file ... "
if [ "$NEEDS_FIX" = false ]; then
echo -e "${GREEN}OK${NC}"
continue
fi
# 修复换行符
if [ "$HAS_CRLF" = true ]; then
sed -i 's/\r$//' "${file_path}" 2>/dev/null || sed -i '' 's/\r$//' "${file_path}" 2>/dev/null
echo -n -e "${YELLOW}[换行符已修复]${NC} "
fi
# 修复权限
if [ "$NEEDS_CHMOD" = true ]; then
chmod +x "${file_path}"
echo -n -e "${YELLOW}[权限已修复]${NC} "
fi
echo -e "${GREEN}${NC}"
FIXED_COUNT=$((FIXED_COUNT + 1))
done
echo ""
echo "=========================================="
echo "修复完成"
echo "=========================================="
echo "检查文件: $TOTAL_FILES"
echo "修复文件: $FIXED_COUNT"
echo ""
if [ $FIXED_COUNT -gt 0 ]; then
echo -e "${GREEN}✓ 所有问题已自动修复!${NC}"
else
echo -e "${GREEN}✓ 所有文件格式正确,无需修复!${NC}"
fi
-261
View File
@@ -1,261 +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"
# 读取环境变量配置
ENABLE_BUILDX="${ENABLE_BUILDX:-false}"
BINFMT_METHOD="${BINFMT_METHOD:-update-binfmts}" # update-binfmts 或 tonistiigi
# 创建必要目录
mkdir -p /data/runners
mkdir -p "$PERSISTENT_BIN"
mkdir -p /var/log/supervisor
mkdir -p /var/run
create_buildx_builder() {
docker buildx create \
--name gitea-multiarch \
--driver docker-container \
--bootstrap \
--use \
--config /data/buildx/buildkitd.toml 2>/dev/null || \
docker buildx use gitea-multiarch 2>/dev/null
}
ensure_buildx_builder() {
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"
else
echo "Starting Docker daemon..."
dockerd > /var/log/dockerd.log 2>&1 &
sleep 5
fi
# 等待 Docker 就绪
echo "Waiting for Docker daemon..."
for i in {1..30}; do
if docker info > /dev/null 2>&1; then
echo "✓ Docker daemon is ready"
break
fi
if [ $i -eq 30 ]; then
echo "✗ Docker daemon failed to start"
[ -f /var/log/dockerd.log ] && cat /var/log/dockerd.log
exit 1
fi
sleep 1
done
# 注册 QEMU binfmt
echo ""
echo "Registering QEMU binary formats..."
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..."
mount binfmt_misc -t binfmt_misc /proc/sys/fs/binfmt_misc 2>/dev/null || true
update-binfmts --enable
}
fi
# 验证多架构支持
echo "Verifying multi-arch support..."
if docker run --rm --platform linux/arm64 alpine uname -m > /dev/null 2>&1; then
echo " ✓ arm64 support verified"
else
echo " ⚠ arm64 verification failed"
fi
if docker run --rm --platform linux/amd64 alpine uname -m > /dev/null 2>&1; then
echo " ✓ amd64 support verified"
else
echo " ⚠ amd64 verification failed"
fi
# 配置 Buildx
if [ ! -f "/data/buildx/.configured" ]; then
echo ""
echo "Setting up Buildx for the first time..."
# 创建 BuildKit 配置
cat > /data/buildx/buildkitd.toml <<EOF
[worker.oci]
max-parallelism = 4
EOF
# 创建 Buildx builder
create_buildx_builder
# 验证
echo "Verifying Buildx..."
docker buildx inspect --bootstrap > /dev/null 2>&1
# 标记为已配置
touch /data/buildx/.configured
echo "✓ Buildx configured successfully!"
docker buildx inspect | grep "Platforms:" | head -1
else
echo "✓ Buildx already configured"
# 确保 builder 可用
ensure_buildx_builder
fi
echo ""
fi
# ============================================
# 检查 act_runner 安装
# ============================================
# 创建主 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
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 "Container is waiting..."
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
# ============================================
# 配置已注册的 Runners
# ============================================
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"
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",PATH="/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin"
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 ""
fi
echo "Total runners configured: $RUNNER_COUNT"
echo ""
echo "==================================="
echo "Starting Supervisor..."
echo "==================================="
# 启动 supervisord
exec /usr/bin/supervisord -c /etc/supervisor/supervisord.conf
-288
View File
@@ -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
-305
View File
@@ -1,305 +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
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
fi
# 验证 URL 格式
if [[ ! "$GITEA_INSTANCE" =~ ^https?:// ]]; then
echo "✗ Error: URL must start with http:// or https://"
echo ""
unset GITEA_INSTANCE
continue
fi
# 移除末尾的斜杠
GITEA_INSTANCE="${GITEA_INSTANCE%/}"
echo "✓ URL validated: $GITEA_INSTANCE"
break
done
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
echo "✗ Error: Token cannot be empty!"
exit 1
fi
DEFAULT_NAME="${DEFAULT_RUNNER_NAME:-docker-runner}"
read -p "Enter runner name (default: $DEFAULT_NAME): " RUNNER_NAME
RUNNER_NAME=${RUNNER_NAME:-$DEFAULT_NAME}
# 多个 label(逗号分隔,无空格)
# ubuntu-22.04:host://ubuntu:22.04,ubuntu-20.04:host://ubuntu:20.04,node:docker://node:18
DEFAULT_LABEL="${DEFAULT_RUNNER_LABEL:-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_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..."
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'] = 4
# 启用缓存
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: 4")
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
# 验证配置文件
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
-159
View File
@@ -1,159 +0,0 @@
#!/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 " 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 ""
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)
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"
validate_binary_arch_or_fail "$INSTALL_PATH" "$RUNNER_ARCH"
# 同时创建软链接到系统路径
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. Start using the runner after registration."
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
-368
View File
@@ -1,368 +0,0 @@
#!/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
@@ -1,4 +0,0 @@
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
@@ -1,74 +0,0 @@
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"]
@@ -1,39 +0,0 @@
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"
@@ -1,4 +0,0 @@
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
@@ -1,49 +0,0 @@
FROM ubuntu:22.04
# 设置环境变量避免交互式安装
ENV DEBIAN_FRONTEND=noninteractive
# 更新系统并安装必要软件
RUN apt-get update && apt-get install -y \
curl \
git \
python3 \
python3-yaml \
supervisor \
ca-certificates \
gnupg \
lsb-release \
qemu-user-static \
binfmt-support \
apt-transport-https \
&& rm -rf /var/lib/apt/lists/*
# 安装 Docker(包含 Buildx 插件)
RUN install -m 0755 -d /etc/apt/keyrings && \
curl -fsSL https://download.docker.com/linux/ubuntu/gpg | gpg --dearmor -o /etc/apt/keyrings/docker.gpg && \
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 && \
qemu-aarch64-static --version && \
qemu-x86_64-static --version
# 创建必要目录
RUN mkdir -p /data /etc/supervisor/conf.d /var/log/supervisor
# 设置工作目录
WORKDIR /data
# 使用自定义入口点
ENTRYPOINT ["/data/entrypoint.sh"]
@@ -1,38 +0,0 @@
services:
gitea-runner:
build: .
container_name: gitea-runner-buildx-ubuntu-22
hostname: ubuntu
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
# 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
# - 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"
@@ -1,4 +0,0 @@
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,33 +0,0 @@
FROM ubuntu:22.04
# 设置环境变量避免交互式安装
ENV DEBIAN_FRONTEND=noninteractive
# RUN apt-get update && \
# apt-get install -y ca-certificates && \
# update-ca-certificates && \
# rm -rf /var/lib/apt/lists/*
# RUN sed -i 's@//.*archive.ubuntu.com@//mirrors.ustc.edu.cn@g' /etc/apt/sources.list && \
# sed -i 's@//.*security.ubuntu.com@//mirrors.ustc.edu.cn@g' /etc/apt/sources.list
# 更新系统并安装必要软件
RUN apt-get update && apt-get install -y \
curl \
git \
jq \
python3 \
python3-yaml \
lsb-release \
supervisor \
ca-certificates \
&& rm -rf /var/lib/apt/lists/*
# 创建必要目录
RUN mkdir -p /data /etc/supervisor/conf.d /var/log/supervisor
# 设置工作目录
WORKDIR /data
# 使用自定义入口点
ENTRYPOINT ["/data/entrypoint.sh"]
@@ -1,37 +0,0 @@
services:
gitea-runner:
build: .
container_name: gitea-runner-standard-ubuntu-22
hostname: ubuntu
restart: unless-stopped
network_mode: "host" # 使用宿主机网络
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
# 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
# - 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"