diff --git a/.gitea/ci/bootstrap_workspace.sh b/.gitea/ci/bootstrap_workspace.sh index 8a0c153..7fc81c8 100644 --- a/.gitea/ci/bootstrap_workspace.sh +++ b/.gitea/ci/bootstrap_workspace.sh @@ -50,14 +50,6 @@ build_mirror_path() { printf '%s/%s/%s.git\n' "${mirror_root}" "${owner}" "${repo_name}" } -build_mirror_lock_path() { - local mirror_root=$1 - local owner=$2 - local repo_name=$3 - - printf '%s.lock\n' "$(build_mirror_path "${mirror_root}" "${owner}" "${repo_name}")" -} - build_job_workspace_root() { local workspace_root=$1 local owner=$2 diff --git a/.gitea/workflows/update_stats_badge.yaml b/.gitea/workflows/update_stats_badge.yaml index d835476..fa2c79f 100644 --- a/.gitea/workflows/update_stats_badge.yaml +++ b/.gitea/workflows/update_stats_badge.yaml @@ -433,20 +433,20 @@ jobs: echo "" # 构建排除参数 - EXCLUDE_PARAMS="" + EXCLUDE_FIND_ARGS=() IFS=',' read -ra EXCLUDES <<< "${{ env.EXCLUDE_DIRS }}" for dir in "${EXCLUDES[@]}"; do - EXCLUDE_PARAMS="$EXCLUDE_PARAMS -not -path '*/$dir/*'" + EXCLUDE_FIND_ARGS+=(-not -path "*/$dir/*") done # 获取所有文件 echo "🔍 扫描文件..." # 统计总文件数 - TOTAL_FILES=$(eval "find . -type f $EXCLUDE_PARAMS" | wc -l) + TOTAL_FILES=$(find . -type f "${EXCLUDE_FIND_ARGS[@]}" | wc -l) # 统计总代码行数(排除空行) - TOTAL_CODE=$(eval "find . -type f $EXCLUDE_PARAMS -exec grep -cHv '^[[:space:]]*$' {} + 2>/dev/null" | awk -F: '{sum+=$2} END {print sum+0}') + 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" @@ -489,10 +489,10 @@ jobs: mkdir -p /tmp/lang_stats # 构建排除参数 - EXCLUDE_PARAMS="" + EXCLUDE_FIND_ARGS=() IFS=',' read -ra EXCLUDES <<< "${{ env.EXCLUDE_DIRS }}" for dir in "${EXCLUDES[@]}"; do - EXCLUDE_PARAMS="$EXCLUDE_PARAMS -not -path '*/$dir/*'" + EXCLUDE_FIND_ARGS+=(-not -path "*/$dir/*") done LANGUAGE_COUNT=0 @@ -507,22 +507,22 @@ jobs: echo "🔍 统计 $display_name..." # 构建扩展名查找条件 - FIND_CONDITIONS="" + LANG_FIND_ARGS=() IFS=',' read -ra EXTS <<< "$extensions" for ext in "${EXTS[@]}"; do - if [ -z "$FIND_CONDITIONS" ]; then - FIND_CONDITIONS="-name '*.$ext'" + if [ "${#LANG_FIND_ARGS[@]}" -eq 0 ]; then + LANG_FIND_ARGS+=(-name "*.$ext") else - FIND_CONDITIONS="$FIND_CONDITIONS -o -name '*.$ext'" + LANG_FIND_ARGS+=(-o -name "*.$ext") fi done # 统计文件数 - FILE_COUNT=$(eval "find . -type f \( $FIND_CONDITIONS \) $EXCLUDE_PARAMS" | wc -l) + FILE_COUNT=$(find . -type f \( "${LANG_FIND_ARGS[@]}" \) "${EXCLUDE_FIND_ARGS[@]}" | wc -l) if [ "$FILE_COUNT" -gt 0 ]; then # 统计代码行数 - CODE_LINES=$(eval "find . -type f \( $FIND_CONDITIONS \) $EXCLUDE_PARAMS -exec grep -cHv '^[[:space:]]*$' {} + 2>/dev/null" | awk -F: '{sum+=$2} END {print sum+0}') + 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 diff --git a/docker-runner/common/check_crlf.sh b/docker-runner/common/check_crlf.sh index 54fe78d..027c02d 100644 --- a/docker-runner/common/check_crlf.sh +++ b/docker-runner/common/check_crlf.sh @@ -90,29 +90,6 @@ echo "" if [ $FIXED_COUNT -gt 0 ]; then echo -e "${GREEN}✓ 所有问题已自动修复!${NC}" - echo "" - echo "建议执行以下命令重启容器:" - echo " docker compose down" - echo " docker compose build --no-cache" - echo " docker compose up -d" - echo "" - - # 询问是否立即重启 - read -p "是否立即重启容器? (y/N): " -n 1 -r - echo "" - if [[ $REPLY =~ ^[Yy]$ ]]; then - echo "正在重启容器..." - docker compose down - docker compose build --no-cache - docker compose up -d - sleep 3 - echo "" - echo "容器状态:" - docker compose ps - echo "" - echo "查看日志:" - docker logs gitea-runner --tail=30 - fi else echo -e "${GREEN}✓ 所有文件格式正确,无需修复!${NC}" fi diff --git a/docker-runner/common/entrypoint.sh b/docker-runner/common/entrypoint.sh index aaa5044..c74957f 100644 --- a/docker-runner/common/entrypoint.sh +++ b/docker-runner/common/entrypoint.sh @@ -20,6 +20,24 @@ 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 支持(可选) # ============================================ @@ -104,13 +122,7 @@ if [ "$ENABLE_BUILDX" = "true" ]; then EOF # 创建 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 + create_buildx_builder # 验证 echo "Verifying Buildx..." @@ -125,15 +137,7 @@ EOF echo "✓ Buildx already configured" # 确保 builder 可用 - docker buildx use gitea-multiarch 2>/dev/null || { - echo "⚠ Recreating Buildx builder..." - docker buildx rm gitea-multiarch 2>/dev/null || true - docker buildx create \ - --name gitea-multiarch \ - --driver docker-container \ - --bootstrap \ - --use 2>/dev/null - } + ensure_buildx_builder fi echo "" diff --git a/docker-runner/common/register.sh b/docker-runner/common/register.sh index 6005db3..4b568d9 100644 --- a/docker-runner/common/register.sh +++ b/docker-runner/common/register.sh @@ -133,12 +133,11 @@ echo "✓ Configuration file generated!" # 创建缓存目录 mkdir -p cache -# 使用 Python 修改配置(最可靠的方法) +# 使用 Python 修改配置 echo "" echo "Configuring runner settings..." -if command -v python3 &> /dev/null; then - python3 << PYEOF +python3 << PYEOF import yaml import sys @@ -184,32 +183,6 @@ except Exception as e: sys.exit(1) PYEOF - PYTHON_EXIT=$? - - if [ $PYTHON_EXIT -ne 0 ]; then - echo "" - echo "⚠ Python configuration failed, using basic sed..." - - # 基本的 sed 修改(只修改简单的值,不动 labels) - sed -i 's/capacity: 1/capacity: 4/g' config.yaml || true - sed -i 's/enabled: false/enabled: true/g' config.yaml || true - sed -i 's|dir: ""|dir: ./cache|g' config.yaml || true - - echo "✓ Basic configuration applied" - echo " Note: Please manually verify labels in config.yaml match .runner" - fi -else - echo "⚠ Python3 not found, applying basic configuration..." - - # 基本的 sed 修改 - sed -i 's/capacity: 1/capacity: 4/g' config.yaml || true - sed -i 's/enabled: false/enabled: true/g' config.yaml || true - sed -i 's|dir: ""|dir: ./cache|g' config.yaml || true - - echo "✓ Basic configuration applied" - echo " Note: Labels will use act_runner defaults" -fi - # 验证配置文件 echo "" echo "Validating configuration..." diff --git a/docker-runner/common/setup.sh b/docker-runner/common/setup.sh index 08da6f1..3fae1e7 100644 --- a/docker-runner/common/setup.sh +++ b/docker-runner/common/setup.sh @@ -5,10 +5,8 @@ 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}" -if [ -f "${UPGRADE_HELPER}" ]; then - # shellcheck source=/dev/null - source "${UPGRADE_HELPER}" -fi +# shellcheck source=/dev/null +source "${UPGRADE_HELPER}" echo "==========================================" echo " Gitea Runner Installation Script " @@ -42,15 +40,13 @@ echo "Available versions: https://dl.gitea.com/act_runner/" echo "" DEFAULT_RUNNER_VERSION="${DEFAULT_RUNNER_VERSION_FALLBACK}" -if declare -F resolve_latest_version_or_fallback >/dev/null 2>&1; then - DEFAULT_RUNNER_VERSION=$(resolve_latest_version_or_fallback "${DEFAULT_RUNNER_VERSION_FALLBACK}") -fi +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 declare -F validate_version >/dev/null 2>&1 && ! validate_version "${RUNNER_VERSION}"; then +if ! validate_version "${RUNNER_VERSION}"; then echo "✗ Invalid version format: ${RUNNER_VERSION}" exit 1 fi @@ -118,10 +114,7 @@ echo "" # 下载到持久化目录 if curl -L "$DOWNLOAD_URL" -o "$INSTALL_PATH"; then chmod +x "$INSTALL_PATH" - - if declare -F validate_binary_arch_or_fail >/dev/null 2>&1; then - validate_binary_arch_or_fail "$INSTALL_PATH" "$RUNNER_ARCH" - fi + validate_binary_arch_or_fail "$INSTALL_PATH" "$RUNNER_ARCH" # 同时创建软链接到系统路径 ln -sf "$INSTALL_PATH" "$SYSTEM_PATH" diff --git a/tests/check_crlf_test.sh b/tests/check_crlf_test.sh index d1af648..ba9bb9d 100644 --- a/tests/check_crlf_test.sh +++ b/tests/check_crlf_test.sh @@ -38,6 +38,10 @@ test_check_crlf_works_from_preset_directory() { ) ! rg -q "文件不存在" "${output_file}" || fail "check_crlf.sh should inspect sibling common scripts even when invoked from preset directory" + ! rg -q "是否立即重启容器" "${output_file}" || fail "check_crlf.sh should not prompt to restart containers" + ! rg -q "docker compose down" "${output_file}" || fail "check_crlf.sh should not include compose restart commands" + ! rg -q "docker compose build --no-cache" "${output_file}" || fail "check_crlf.sh should not take responsibility for rebuilding containers" + ! rg -q "docker compose up -d" "${output_file}" || fail "check_crlf.sh should not take responsibility for starting containers" for file_name in entrypoint.sh setup.sh upgrade.sh register.sh manage.sh; do ! has_crlf "${common_dir}/${file_name}" || fail "${file_name} should have CRLF fixed" diff --git a/tests/template_defaults_test.sh b/tests/template_defaults_test.sh index 0cd6e1a..5a5d2e4 100644 --- a/tests/template_defaults_test.sh +++ b/tests/template_defaults_test.sh @@ -60,10 +60,12 @@ test_workflow_doc_describes_workspace_architecture() { file="${REPO_ROOT}/WORKFLOW.md" - grep -q 'Git bare 镜像 + 工作副本' "${file}" || fail "WORKFLOW.md should explicitly describe the bare mirror plus worktree-style model" - grep -q '共享仓库缓存' "${file}" || fail "WORKFLOW.md should describe the shared repository cache model" - grep -q '独立工作副本' "${file}" || fail "WORKFLOW.md should describe isolated per-job work copies" - grep -q '任务结束后' "${file}" || fail "WORKFLOW.md should mention cleanup after workflow completion" + grep -Eq '^## .*运行模型' "${file}" || fail "WORKFLOW.md should include a run model section" + grep -q 'bare' "${file}" || fail "WORKFLOW.md should describe the bare mirror model" + grep -q '工作副本' "${file}" || fail "WORKFLOW.md should describe isolated work copies" + grep -Eq '共享.*缓存|缓存.*共享' "${file}" || fail "WORKFLOW.md should describe the shared cache model" + grep -Eq '并发|隔离' "${file}" || fail "WORKFLOW.md should mention concurrency or isolation tradeoffs" + grep -Eq '结束后.*清理|清理.*工作副本' "${file}" || fail "WORKFLOW.md should mention cleanup after workflow completion" ! rg -q 'bootstrap_workspace\.sh' "${file}" || fail "WORKFLOW.md should describe architecture rather than helper implementation" ! rg -q '/data/git-mirrors|/home/workspace/jobs' "${file}" || fail "WORKFLOW.md should avoid implementation-specific workspace paths" } @@ -88,13 +90,13 @@ test_readme_has_project_intro_and_navigation() { file="${REPO_ROOT}/README.md" - grep -q '^## 📖 项目简介$' "${file}" || fail "README.md should provide a project intro section" - grep -q '^## 📂 文档导航$' "${file}" || fail "README.md should provide a document navigation section" + grep -Eq '^## .*项目简介' "${file}" || fail "README.md should provide a project intro section" + grep -Eq '^## .*文档导航' "${file}" || fail "README.md should provide a document navigation section" grep -q '\[DEPLOYMENT.md\](\./DEPLOYMENT.md)' "${file}" || fail "README.md should link to DEPLOYMENT.md" grep -q '\[WORKFLOW.md\](\./WORKFLOW.md)' "${file}" || fail "README.md should link to WORKFLOW.md" - grep -q '具体部署步骤.*\[DEPLOYMENT.md\](\./DEPLOYMENT.md)' "${file}" || fail "README.md should delegate deployment details to DEPLOYMENT.md" ! rg -q '^## 🚀 快速提示$' "${file}" || fail "README.md should not carry concrete deployment tip sections" ! rg -q '^## ⚙️ 当前默认行为$' "${file}" || fail "README.md should not carry default behavior details" + ! rg -q 'docker compose (build|up|exec)' "${file}" || fail "README.md should stay high-level and leave compose commands to deployment docs" } test_deployment_doc_stays_runner_focused() { @@ -152,6 +154,51 @@ test_preset_compose_supports_runner_identity_overrides() { fail "buildx arch preset should allow DEFAULT_RUNNER_LABEL override via env" } +test_register_requires_python_yaml_path() { + local file + + file="${REPO_ROOT}/docker-runner/common/register.sh" + + ! rg -q 'Python configuration failed, using basic sed' "${file}" || fail "register.sh should not fall back to basic sed when Python config fails" + ! rg -q 'Python3 not found, applying basic configuration' "${file}" || fail "register.sh should not continue with basic configuration when Python3 is missing" + ! rg -q "sed -i 's/capacity: 1/capacity: 4/g' config.yaml" "${file}" || fail "register.sh should not mutate config.yaml via sed fallback" + grep -q "python3 << PYEOF" "${file}" || fail "register.sh should keep Python-based config generation" +} + +test_setup_requires_upgrade_helper() { + local file + + file="${REPO_ROOT}/docker-runner/common/setup.sh" + + grep -q '^# shellcheck source=/dev/null$' "${file}" || fail "setup.sh should source upgrade.sh directly" + grep -q '^source "\${UPGRADE_HELPER}"$' "${file}" || fail "setup.sh should require sourcing upgrade.sh" + ! grep -Fq 'if [ -f "${UPGRADE_HELPER}" ]; then' "${file}" || fail "setup.sh should not treat upgrade helper as optional" + ! rg -q 'declare -F resolve_latest_version_or_fallback' "${file}" || fail "setup.sh should not guard helper functions with declare -F" + ! rg -q 'declare -F validate_version' "${file}" || fail "setup.sh should not guard validate_version with declare -F" + ! rg -q 'declare -F validate_binary_arch_or_fail' "${file}" || fail "setup.sh should not guard binary validation with declare -F" +} + +test_entrypoint_uses_shared_buildx_builder_creation() { + local file + + file="${REPO_ROOT}/docker-runner/common/entrypoint.sh" + + grep -q '^create_buildx_builder() {$' "${file}" || fail "entrypoint.sh should extract Buildx builder creation into a helper" + grep -q '^ensure_buildx_builder() {$' "${file}" || fail "entrypoint.sh should extract Buildx builder selection into a helper" + grep -q '^[[:space:]]*create_buildx_builder$' "${file}" || fail "entrypoint.sh should use shared builder creation during initial setup" + grep -q '^[[:space:]]*ensure_buildx_builder$' "${file}" || fail "entrypoint.sh should use shared builder selection during reuse" +} + +test_stats_workflow_avoids_eval_find() { + local file + + file="${REPO_ROOT}/.gitea/workflows/update_stats_badge.yaml" + + ! rg -q 'eval "find ' "${file}" || fail "stats workflow should avoid eval when building find commands" + grep -q 'EXCLUDE_FIND_ARGS=()' "${file}" || fail "stats workflow should build reusable find exclusion arrays" + grep -q 'LANG_FIND_ARGS=()' "${file}" || fail "stats workflow should build language-specific find arguments via arrays" +} + test_preset_compose_uses_env_for_instance test_workflows_do_not_hardcode_company_server test_stats_workflow_uses_workflow_secret_consistently @@ -164,5 +211,9 @@ test_deployment_doc_stays_runner_focused test_presets_define_expected_hostname test_preset_env_examples_exist test_preset_compose_supports_runner_identity_overrides +test_register_requires_python_yaml_path +test_setup_requires_upgrade_helper +test_entrypoint_uses_shared_buildx_builder_creation +test_stats_workflow_avoids_eval_find echo "template_defaults_test.sh: PASS" diff --git a/tests/workspace_helper_test.sh b/tests/workspace_helper_test.sh index 795fd88..b14cc8f 100644 --- a/tests/workspace_helper_test.sh +++ b/tests/workspace_helper_test.sh @@ -53,14 +53,6 @@ test_sanitize_job_name() { assert_eq "release-notes-job" "${actual}" "job names should be filesystem-safe" } -test_build_lock_path() { - local actual - - actual=$(build_mirror_lock_path "/data/git-mirrors" "csh" "actions-template") - - assert_eq "/data/git-mirrors/csh/actions-template.git.lock" "${actual}" "lock path should sit beside the bare mirror" -} - test_repo_owner_and_name_parsing() { local actual @@ -167,7 +159,6 @@ test_presets_do_not_mount_workspace_helper() { test_repo_path_layout test_job_identity_prefers_run_metadata test_sanitize_job_name -test_build_lock_path test_repo_owner_and_name_parsing test_prepare_and_cleanup_workspace test_register_default_capacity_is_four