From 6e3b4c66d1e10458bd718970a65764dc1c51faec Mon Sep 17 00:00:00 2001 From: S Date: Thu, 26 Feb 2026 15:25:04 -0600 Subject: [PATCH] =?UTF-8?q?feat:=20add=20Phase=205=20=E2=80=94=20Migrate?= =?UTF-8?q?=20Pipelines=20(GitHub=20=E2=86=92=20Gitea=20Actions)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-Authored-By: Claude Opus 4.6 --- phase5_migrate_pipelines.sh | 165 ++++++++++++++++++++++++++++++++++++ phase5_post_check.sh | 68 +++++++++++++++ phase5_teardown.sh | 66 +++++++++++++++ 3 files changed, 299 insertions(+) create mode 100755 phase5_migrate_pipelines.sh create mode 100755 phase5_post_check.sh create mode 100755 phase5_teardown.sh diff --git a/phase5_migrate_pipelines.sh b/phase5_migrate_pipelines.sh new file mode 100755 index 0000000..fa4fd5a --- /dev/null +++ b/phase5_migrate_pipelines.sh @@ -0,0 +1,165 @@ +#!/usr/bin/env bash +set -euo pipefail + +# ============================================================================= +# phase5_migrate_pipelines.sh — Copy GitHub Actions → Gitea Actions workflows +# Depends on: Phase 4 complete (repos exist on Gitea primary) +# For each repo: +# 1. Clone from Gitea to temp directory +# 2. Copy .github/workflows/*.yml → .gitea/workflows/ +# 3. Apply compatibility fixes (github.* → gitea.* context vars) +# 4. Add migration header comment +# 5. Commit and push +# Idempotent: skips repos that already have .gitea/workflows/ with files. +# ============================================================================= + +SCRIPT_DIR="$(cd "$(dirname "$0")" && pwd)" +source "${SCRIPT_DIR}/lib/common.sh" + +load_env +require_vars GITEA_ADMIN_TOKEN GITEA_INTERNAL_URL GITEA_ORG_NAME \ + GITEA_ADMIN_USER GITEA_ADMIN_PASSWORD \ + REPO_1_NAME REPO_2_NAME REPO_3_NAME + +phase_header 5 "Migrate Pipelines (GitHub → Gitea Actions)" + +REPOS=("$REPO_1_NAME" "$REPO_2_NAME" "$REPO_3_NAME") +TEMP_BASE="/tmp/gitea-migration" +MIGRATION_HEADER="# Migrated from GitHub Actions — review for Gitea compatibility" + +# Clean up temp directory on exit (even on failure) +cleanup() { + rm -rf "$TEMP_BASE" +} +trap cleanup EXIT + +SUCCESS=0 +SKIPPED=0 +FAILED=0 + +for repo in "${REPOS[@]}"; do + log_info "--- Processing repo: ${repo} ---" + + # ------------------------------------------------------------------------- + # Idempotency: check if .gitea/workflows already exists via API + # The Gitea contents API returns 200 if the path exists. + # ------------------------------------------------------------------------- + if gitea_api GET "/repos/${GITEA_ORG_NAME}/${repo}/contents/.gitea/workflows" >/dev/null 2>&1; then + log_info ".gitea/workflows/ already exists in ${repo} — skipping" + SKIPPED=$((SKIPPED + 1)) + continue + fi + + # ------------------------------------------------------------------------- + # Step 1: Clone the repo to a temp directory + # Uses token-based auth in the clone URL so git can push later. + # ------------------------------------------------------------------------- + CLONE_DIR="${TEMP_BASE}/${repo}" + rm -rf "$CLONE_DIR" + mkdir -p "$CLONE_DIR" + + # Construct clone URL with embedded token for auth + # Format: http://token:TOKEN@host:port/org/repo.git + CLONE_URL=$(echo "${GITEA_INTERNAL_URL}" | sed "s|://|://${GITEA_ADMIN_USER}:${GITEA_ADMIN_TOKEN}@|") + log_info "Cloning ${repo}..." + git clone -q "${CLONE_URL}/${GITEA_ORG_NAME}/${repo}.git" "$CLONE_DIR" + + # ------------------------------------------------------------------------- + # Step 2: Check for GitHub workflows + # If the repo has no .github/workflows, log a warning and skip. + # This is expected — not every repo has CI pipelines. + # ------------------------------------------------------------------------- + if [[ ! -d "${CLONE_DIR}/.github/workflows" ]]; then + log_warn "No .github/workflows/ found in ${repo} — skipping" + SKIPPED=$((SKIPPED + 1)) + continue + fi + + # Count workflow files to verify there's something to migrate + WORKFLOW_FILES=$(find "${CLONE_DIR}/.github/workflows" -name "*.yml" -o -name "*.yaml" | wc -l | xargs) + if [[ "$WORKFLOW_FILES" -eq 0 ]]; then + log_warn "No .yml/.yaml files in .github/workflows/ for ${repo} — skipping" + SKIPPED=$((SKIPPED + 1)) + continue + fi + + log_info "Found ${WORKFLOW_FILES} workflow file(s) to migrate" + + # ------------------------------------------------------------------------- + # Step 3: Create .gitea/workflows and copy files + # ------------------------------------------------------------------------- + mkdir -p "${CLONE_DIR}/.gitea/workflows" + + for wf in "${CLONE_DIR}/.github/workflows/"*.yml "${CLONE_DIR}/.github/workflows/"*.yaml; do + # Skip glob pattern if no files matched (nullglob not set) + [[ -f "$wf" ]] || continue + + local_name=$(basename "$wf") + dest="${CLONE_DIR}/.gitea/workflows/${local_name}" + cp "$wf" "$dest" + + # ----------------------------------------------------------------- + # Step 4: Apply compatibility fixes + # Gitea Actions is mostly GitHub-compatible, but context variables + # use "gitea" prefix instead of "github". These replacements handle + # the most common cases. Marketplace actions (actions/checkout etc.) + # work as-is since Gitea mirrors them. + # ----------------------------------------------------------------- + + # Add migration header comment at the top of the file + # Using a temp file to prepend since sed -i on macOS has quirks + tmpwf=$(mktemp) + printf '%s\n' "$MIGRATION_HEADER" > "$tmpwf" + cat "$dest" >> "$tmpwf" + mv "$tmpwf" "$dest" + + # Replace GitHub-specific context variables with Gitea equivalents + # Using sed -i '' for macOS compatibility (GNU sed uses -i without arg) + sed -i '' \ + -e 's/github\.repository/gitea.repository/g' \ + -e 's/github\.event/gitea.event/g' \ + -e 's/github\.token/gitea.token/g' \ + -e 's/github\.server_url/gitea.server_url/g' \ + "$dest" + + log_info " Migrated: ${local_name}" + done + + # ------------------------------------------------------------------------- + # Step 5: Commit and push + # Configure git user for the commit (required for fresh clones) + # ------------------------------------------------------------------------- + cd "$CLONE_DIR" + git config user.name "Gitea Migration" + git config user.email "migration@gitea.local" + git add .gitea/ + git commit -q -m "Migrate workflows to Gitea Actions" + git push -q origin HEAD + cd "$SCRIPT_DIR" + + log_success "Workflows migrated for ${repo}" + SUCCESS=$((SUCCESS + 1)) +done + +# --------------------------------------------------------------------------- +# Known limitations — print as informational warnings +# --------------------------------------------------------------------------- +printf '\n' +log_warn "Known limitations (review manually):" +log_warn " - GitHub-specific marketplace actions may not work in Gitea" +log_warn " - Self-hosted runner tool caches may differ from GitHub-hosted" +log_warn " - OIDC/secrets need to be re-configured in Gitea repo settings" + +# --------------------------------------------------------------------------- +# Summary +# --------------------------------------------------------------------------- +printf '\n' +TOTAL=${#REPOS[@]} +log_info "Results: ${SUCCESS} migrated, ${SKIPPED} skipped, ${FAILED} failed (out of ${TOTAL})" + +if [[ $FAILED -gt 0 ]]; then + log_error "Some repos failed — check logs above" + exit 1 +fi + +log_success "Phase 5 complete — pipelines migrated" diff --git a/phase5_post_check.sh b/phase5_post_check.sh new file mode 100755 index 0000000..6821e42 --- /dev/null +++ b/phase5_post_check.sh @@ -0,0 +1,68 @@ +#!/usr/bin/env bash +set -euo pipefail + +# ============================================================================= +# phase5_post_check.sh — Verify Phase 5 (Pipeline Migration) succeeded +# Checks for each repo: +# 1. .gitea/workflows/ directory exists (via Gitea contents API) +# 2. At least one .yml file in the directory +# Repos without .github/workflows are expected to be skipped. +# Exits 0 only if ALL expected checks pass. +# ============================================================================= + +SCRIPT_DIR="$(cd "$(dirname "$0")" && pwd)" +source "${SCRIPT_DIR}/lib/common.sh" + +load_env +require_vars GITEA_ADMIN_TOKEN GITEA_INTERNAL_URL GITEA_ORG_NAME \ + REPO_1_NAME REPO_2_NAME REPO_3_NAME + +log_info "=== Phase 5 Post-Check ===" + +REPOS=("$REPO_1_NAME" "$REPO_2_NAME" "$REPO_3_NAME") +PASS=0 +FAIL=0 +SKIP=0 + +for repo in "${REPOS[@]}"; do + log_info "--- Checking repo: ${repo} ---" + + # Check if .gitea/workflows exists via Gitea contents API + WORKFLOWS_RESPONSE=$(gitea_api GET "/repos/${GITEA_ORG_NAME}/${repo}/contents/.gitea/workflows" 2>/dev/null || echo "") + + if [[ -z "$WORKFLOWS_RESPONSE" ]]; then + # Directory doesn't exist — check if the repo had GitHub workflows + # If not, this is expected (skipped during migration, not a failure) + GH_WORKFLOWS=$(gitea_api GET "/repos/${GITEA_ORG_NAME}/${repo}/contents/.github/workflows" 2>/dev/null || echo "") + if [[ -z "$GH_WORKFLOWS" ]]; then + log_info "${repo}: No .github/workflows/ — skipped (expected)" + SKIP=$((SKIP + 1)) + else + log_error "FAIL: ${repo}: .github/workflows/ exists but .gitea/workflows/ missing" + FAIL=$((FAIL + 1)) + fi + continue + fi + + # Check that at least one .yml file exists in the directory + YML_COUNT=$(printf '%s' "$WORKFLOWS_RESPONSE" | jq '[.[] | select(.name | test("\\.(yml|yaml)$"))] | length' 2>/dev/null || echo 0) + if [[ "$YML_COUNT" -gt 0 ]]; then + log_success "${repo}: .gitea/workflows/ has ${YML_COUNT} workflow file(s)" + PASS=$((PASS + 1)) + else + log_error "FAIL: ${repo}: .gitea/workflows/ exists but has no .yml files" + FAIL=$((FAIL + 1)) + fi +done + +# Summary +printf '\n' +log_info "Results: ${PASS} passed, ${SKIP} skipped (no source workflows), ${FAIL} failed" + +if [[ $FAIL -gt 0 ]]; then + log_error "Phase 5 post-check FAILED" + exit 1 +else + log_success "Phase 5 post-check PASSED" + exit 0 +fi diff --git a/phase5_teardown.sh b/phase5_teardown.sh new file mode 100755 index 0000000..0022f8c --- /dev/null +++ b/phase5_teardown.sh @@ -0,0 +1,66 @@ +#!/usr/bin/env bash +set -euo pipefail + +# ============================================================================= +# phase5_teardown.sh — Remove .gitea/workflows/ from all repos +# Clones each repo, removes the .gitea/workflows/ directory, commits + pushes. +# Safe to run if .gitea/workflows/ doesn't exist (skips with info message). +# ============================================================================= + +SCRIPT_DIR="$(cd "$(dirname "$0")" && pwd)" +source "${SCRIPT_DIR}/lib/common.sh" + +load_env +require_vars GITEA_ADMIN_TOKEN GITEA_INTERNAL_URL GITEA_ORG_NAME \ + GITEA_ADMIN_USER \ + REPO_1_NAME REPO_2_NAME REPO_3_NAME + +log_warn "=== Phase 5 Teardown: Remove Gitea Workflows ===" + +REPOS=("$REPO_1_NAME" "$REPO_2_NAME" "$REPO_3_NAME") +TEMP_BASE="/tmp/gitea-migration-teardown" + +cleanup() { + rm -rf "$TEMP_BASE" +} +trap cleanup EXIT + +printf 'This will remove .gitea/workflows/ from all repos. Continue? [y/N] ' +read -r confirm +if [[ ! "$confirm" =~ ^[Yy]$ ]]; then + log_info "Teardown cancelled" + exit 0 +fi + +for repo in "${REPOS[@]}"; do + log_info "--- Processing: ${repo} ---" + + # Check if .gitea/workflows/ exists before cloning (avoid unnecessary work) + if ! gitea_api GET "/repos/${GITEA_ORG_NAME}/${repo}/contents/.gitea/workflows" >/dev/null 2>&1; then + log_info "No .gitea/workflows/ in ${repo} — already clean" + continue + fi + + # Clone, remove, commit, push + CLONE_DIR="${TEMP_BASE}/${repo}" + rm -rf "$CLONE_DIR" + + CLONE_URL=$(echo "${GITEA_INTERNAL_URL}" | sed "s|://|://${GITEA_ADMIN_USER}:${GITEA_ADMIN_TOKEN}@|") + git clone -q "${CLONE_URL}/${GITEA_ORG_NAME}/${repo}.git" "$CLONE_DIR" + + if [[ -d "${CLONE_DIR}/.gitea/workflows" ]]; then + rm -rf "${CLONE_DIR}/.gitea/workflows" + cd "$CLONE_DIR" + git config user.name "Gitea Migration" + git config user.email "migration@gitea.local" + git add -A + git commit -q -m "Remove Gitea Actions workflows (teardown)" + git push -q origin HEAD + cd "$SCRIPT_DIR" + log_success "Removed .gitea/workflows/ from ${repo}" + else + log_info ".gitea/workflows/ not found in clone — already clean" + fi +done + +log_success "Phase 5 teardown complete"