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