gitea-migrate-repos.sh raw

   1  #!/usr/bin/env bash
   2  set -euo pipefail
   3  
   4  # Gitea Repository Migration Script
   5  # Migrates Git repositories from local directory to Gitea server
   6  
   7  # Configuration (edit these values)
   8  GITEA_URL="${GITEA_URL:-http://localhost:3000}"  # Gitea URL
   9  GITEA_TOKEN="${GITEA_TOKEN:-}"                    # Gitea API token (required)
  10  SOURCE_DIR="${SOURCE_DIR:-/home/mleku/Documents/github}"
  11  VPS_HOST="${VPS_HOST:-}"                          # SSH host for VPS (e.g., user@vps.example.com)
  12  USE_SSH="${USE_SSH:-false}"                       # Set to true to use SSH instead of HTTP for push
  13  DRY_RUN="${DRY_RUN:-false}"                       # Set to true for dry run
  14  
  15  # Colors
  16  GREEN='\033[0;32m'
  17  YELLOW='\033[1;33m'
  18  RED='\033[0;31m'
  19  BLUE='\033[0;34m'
  20  NC='\033[0m'
  21  
  22  # Stats
  23  TOTAL_REPOS=0
  24  CREATED_REPOS=0
  25  PUSHED_REPOS=0
  26  SKIPPED_REPOS=0
  27  FAILED_REPOS=0
  28  
  29  echo -e "${GREEN}=== Gitea Repository Migration Script ===${NC}"
  30  echo ""
  31  
  32  # Validate configuration
  33  if [ -z "$GITEA_TOKEN" ]; then
  34      echo -e "${RED}Error: GITEA_TOKEN is required${NC}"
  35      echo ""
  36      echo "To generate a Gitea API token:"
  37      echo "1. Log in to Gitea at ${GITEA_URL}"
  38      echo "2. Go to Settings -> Applications -> Generate New Token"
  39      echo "3. Give it a name (e.g., 'migration') and select all scopes"
  40      echo "4. Copy the token and run:"
  41      echo "   export GITEA_TOKEN='your-token-here'"
  42      echo ""
  43      exit 1
  44  fi
  45  
  46  if [ ! -d "$SOURCE_DIR" ]; then
  47      echo -e "${RED}Error: Source directory does not exist: ${SOURCE_DIR}${NC}"
  48      exit 1
  49  fi
  50  
  51  echo "Configuration:"
  52  echo "  Gitea URL: ${GITEA_URL}"
  53  echo "  Source directory: ${SOURCE_DIR}"
  54  echo "  VPS Host: ${VPS_HOST:-<local installation>}"
  55  echo "  Push method: $([ "$USE_SSH" = "true" ] && echo "SSH" || echo "HTTP with token")"
  56  echo "  Dry run: ${DRY_RUN}"
  57  echo ""
  58  
  59  # Function to check if Gitea is accessible
  60  check_gitea() {
  61      echo -e "${YELLOW}Checking Gitea accessibility...${NC}"
  62  
  63      if ! curl -sf "${GITEA_URL}/api/v1/version" > /dev/null; then
  64          echo -e "${RED}Error: Cannot connect to Gitea at ${GITEA_URL}${NC}"
  65          echo "Make sure Gitea is running and accessible."
  66          exit 1
  67      fi
  68  
  69      # Verify token
  70      if ! curl -sf -H "Authorization: token ${GITEA_TOKEN}" "${GITEA_URL}/api/v1/user" > /dev/null; then
  71          echo -e "${RED}Error: Invalid Gitea token${NC}"
  72          exit 1
  73      fi
  74  
  75      GITEA_USER=$(curl -sf -H "Authorization: token ${GITEA_TOKEN}" "${GITEA_URL}/api/v1/user" | grep -o '"login":"[^"]*"' | cut -d'"' -f4)
  76      echo -e "${GREEN}✓ Connected to Gitea as: ${GITEA_USER}${NC}"
  77  }
  78  
  79  # Function to find all Git repositories
  80  find_repos() {
  81      echo -e "${YELLOW}Scanning for Git repositories...${NC}"
  82      mapfile -t REPOS < <(find "${SOURCE_DIR}" -maxdepth 2 -name .git -type d | sed 's|/.git$||')
  83      TOTAL_REPOS=${#REPOS[@]}
  84      echo -e "${GREEN}✓ Found ${TOTAL_REPOS} repositories${NC}"
  85      echo ""
  86  }
  87  
  88  # Function to get repository name from path
  89  get_repo_name() {
  90      basename "$1"
  91  }
  92  
  93  # Function to get repository description from Git config
  94  get_repo_description() {
  95      local repo_path="$1"
  96      local desc=""
  97  
  98      # Try to get description from .git/description
  99      if [ -f "${repo_path}/.git/description" ]; then
 100          desc=$(cat "${repo_path}/.git/description" | grep -v "^Unnamed repository" || true)
 101      fi
 102  
 103      # If empty, try README
 104      if [ -z "$desc" ] && [ -f "${repo_path}/README.md" ]; then
 105          desc=$(head -n 1 "${repo_path}/README.md" | sed 's/^#*\s*//')
 106      fi
 107  
 108      echo "$desc"
 109  }
 110  
 111  # Function to check if repo exists in Gitea
 112  repo_exists() {
 113      local repo_name="$1"
 114      curl -sf -H "Authorization: token ${GITEA_TOKEN}" \
 115          "${GITEA_URL}/api/v1/repos/${GITEA_USER}/${repo_name}" > /dev/null
 116  }
 117  
 118  # Function to create repository in Gitea
 119  create_repo() {
 120      local repo_name="$1"
 121      local description="$2"
 122  
 123      if [ "$DRY_RUN" = "true" ]; then
 124          echo -e "${BLUE}[DRY RUN]${NC} Would create: ${repo_name}"
 125          return 0
 126      fi
 127  
 128      local json_data=$(cat <<EOF
 129  {
 130      "name": "${repo_name}",
 131      "description": "${description}",
 132      "private": false,
 133      "auto_init": false
 134  }
 135  EOF
 136  )
 137  
 138      if curl -sf -X POST \
 139          -H "Authorization: token ${GITEA_TOKEN}" \
 140          -H "Content-Type: application/json" \
 141          -d "$json_data" \
 142          "${GITEA_URL}/api/v1/user/repos" > /dev/null; then
 143          return 0
 144      else
 145          return 1
 146      fi
 147  }
 148  
 149  # Function to push repository to Gitea
 150  push_repo() {
 151      local repo_path="$1"
 152      local repo_name="$2"
 153  
 154      if [ "$DRY_RUN" = "true" ]; then
 155          echo -e "${BLUE}[DRY RUN]${NC} Would push: ${repo_name}"
 156          return 0
 157      fi
 158  
 159      cd "$repo_path"
 160  
 161      # Check if gitea remote already exists
 162      if git remote | grep -q "^gitea$"; then
 163          git remote remove gitea 2>/dev/null || true
 164      fi
 165  
 166      # Build Git URL based on SSH or HTTP preference
 167      local git_url
 168      if [ "$USE_SSH" = "true" ]; then
 169          # Use SSH URL
 170          if [ -n "$VPS_HOST" ]; then
 171              # Extract host from VPS_HOST (format: user@host or just host)
 172              local ssh_host=$(echo "$VPS_HOST" | grep -oP '@\K.*' || echo "$VPS_HOST")
 173              git_url="git@${ssh_host}:${GITEA_USER}/${repo_name}.git"
 174          else
 175              # Use local host
 176              local gitea_host=$(echo "$GITEA_URL" | sed -E 's|https?://||' | cut -d':' -f1)
 177              git_url="git@${gitea_host}:${GITEA_USER}/${repo_name}.git"
 178          fi
 179      else
 180          # Use HTTP URL with token authentication (more reliable for automation)
 181          # Extract the base URL (http:// or https://)
 182          local protocol=$(echo "$GITEA_URL" | grep -oP '^https?')
 183          local url_without_protocol=$(echo "$GITEA_URL" | sed -E 's|^https?://||')
 184  
 185          # Build authenticated URL: http://username:token@host:port/username/repo.git
 186          git_url="${protocol}://${GITEA_USER}:${GITEA_TOKEN}@${url_without_protocol}/${GITEA_USER}/${repo_name}.git"
 187      fi
 188  
 189      # Add Gitea remote with URL
 190      git remote add gitea "$git_url"
 191  
 192      # Push all branches and tags
 193      local push_success=true
 194  
 195      # Push all branches
 196      if ! git push gitea --all 2>&1; then
 197          push_success=false
 198      fi
 199  
 200      # Push all tags
 201      if ! git push gitea --tags 2>&1; then
 202          push_success=false
 203      fi
 204  
 205      # Clean up - remove the remote to avoid storing token in git config (HTTP only)
 206      if [ "$USE_SSH" != "true" ]; then
 207          git remote remove gitea 2>/dev/null || true
 208      fi
 209  
 210      if [ "$push_success" = true ]; then
 211          return 0
 212      else
 213          return 1
 214      fi
 215  }
 216  
 217  # Main migration function
 218  migrate_repos() {
 219      echo -e "${GREEN}Starting migration...${NC}"
 220      echo ""
 221  
 222      local count=0
 223      for repo_path in "${REPOS[@]}"; do
 224          count=$((count + 1))
 225          local repo_name=$(get_repo_name "$repo_path")
 226  
 227          echo -e "${BLUE}[${count}/${TOTAL_REPOS}]${NC} Processing: ${repo_name}"
 228  
 229          # Check if repo already exists
 230          if repo_exists "$repo_name"; then
 231              echo -e "  ${YELLOW}⚠${NC}  Repository already exists in Gitea"
 232              SKIPPED_REPOS=$((SKIPPED_REPOS + 1))
 233  
 234              # Ask if user wants to push updates
 235              if [ "$DRY_RUN" = "false" ]; then
 236                      if push_repo "$repo_path" "$repo_name"; then
 237                          echo -e "  ${GREEN}✓${NC}  Pushed updates"
 238                          PUSHED_REPOS=$((PUSHED_REPOS + 1))
 239                      else
 240                          echo -e "  ${RED}✗${NC}  Failed to push"
 241                          FAILED_REPOS=$((FAILED_REPOS + 1))
 242                  fi
 243              fi
 244              echo ""
 245              continue
 246          fi
 247  
 248          # Get description
 249          local description=$(get_repo_description "$repo_path")
 250          if [ -n "$description" ]; then
 251              echo -e "  Description: ${description}"
 252          fi
 253  
 254          # Create repository
 255          echo -e "  Creating repository in Gitea..."
 256          if create_repo "$repo_name" "$description"; then
 257              echo -e "  ${GREEN}✓${NC}  Repository created"
 258              CREATED_REPOS=$((CREATED_REPOS + 1))
 259  
 260              # Push repository
 261              echo -e "  Pushing repository data..."
 262              if push_repo "$repo_path" "$repo_name"; then
 263                  echo -e "  ${GREEN}✓${NC}  Repository pushed"
 264                  PUSHED_REPOS=$((PUSHED_REPOS + 1))
 265              else
 266                  echo -e "  ${RED}✗${NC}  Failed to push"
 267                  FAILED_REPOS=$((FAILED_REPOS + 1))
 268              fi
 269          else
 270              echo -e "  ${RED}✗${NC}  Failed to create repository"
 271              FAILED_REPOS=$((FAILED_REPOS + 1))
 272          fi
 273  
 274          echo ""
 275      done
 276  }
 277  
 278  # Print summary
 279  print_summary() {
 280      echo ""
 281      echo -e "${GREEN}=== Migration Summary ===${NC}"
 282      echo "Total repositories: ${TOTAL_REPOS}"
 283      echo "Created: ${CREATED_REPOS}"
 284      echo "Pushed: ${PUSHED_REPOS}"
 285      echo "Skipped: ${SKIPPED_REPOS}"
 286      echo "Failed: ${FAILED_REPOS}"
 287      echo ""
 288  
 289      if [ "$DRY_RUN" = "true" ]; then
 290          echo -e "${YELLOW}This was a dry run. No changes were made.${NC}"
 291          echo "Run without DRY_RUN=true to perform actual migration."
 292      elif [ $FAILED_REPOS -eq 0 ]; then
 293          echo -e "${GREEN}✓ Migration completed successfully!${NC}"
 294          echo ""
 295          echo "Visit your Gitea instance at: ${GITEA_URL}"
 296      else
 297          echo -e "${YELLOW}⚠ Migration completed with some failures${NC}"
 298          echo "Check the output above for details on failed repositories."
 299      fi
 300  }
 301  
 302  # Main execution
 303  check_gitea
 304  find_repos
 305  
 306  # Confirm before proceeding
 307  if [ "$DRY_RUN" = "false" ]; then
 308      echo -e "${YELLOW}Ready to migrate ${TOTAL_REPOS} repositories.${NC}"
 309      read -p "Continue? (y/N) " -n 1 -r
 310      echo
 311      if [[ ! $REPLY =~ ^[Yy]$ ]]; then
 312          echo "Migration cancelled."
 313          exit 0
 314      fi
 315      echo ""
 316  fi
 317  
 318  migrate_repos
 319  print_summary
 320