mongodb-migrate 18 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625
  1. #!/bin/bash
  2. # MongoDB Migration Script from version 3 to 7
  3. # This script handles migration with disk space checks, progress tracking, and error handling
  4. set -e
  5. # Source settings
  6. source $SNAP/bin/wekan-read-settings
  7. # Migration configuration
  8. MIGRATION_LOG="${SNAP_COMMON}/mongodb-migration-log.txt"
  9. MIGRATION_STATUS="${SNAP_COMMON}/mongodb-migration-status.json"
  10. MIGRATION_PROGRESS="${SNAP_COMMON}/mongodb-migration-progress.html"
  11. REVERT_FILE="${SNAP_COMMON}/revert-mongodb-migration.txt"
  12. TEMP_DIR="${SNAP_COMMON}/mongodb-migration-temp"
  13. BACKUP_DIR="${SNAP_COMMON}/mongodb-backup-$(date +%Y%m%d-%H%M%S)"
  14. # MongoDB paths
  15. MONGO3_BIN="/snap/${SNAP_NAME}/current/bin"
  16. MONGO7_BIN="/snap/${SNAP_NAME}/current/usr/bin"
  17. MONGO3_LIB="/snap/${SNAP_NAME}/current/lib"
  18. MONGO7_LIB="/snap/${SNAP_NAME}/current/lib"
  19. # Set up environment for MongoDB 3 tools
  20. export LD_LIBRARY_PATH="${MONGO3_LIB}:${MONGO3_LIB}/x86_64-linux-gnu:${LD_LIBRARY_PATH}"
  21. export PATH="${MONGO3_BIN}:${MONGO7_BIN}:${PATH}"
  22. # Logging functions
  23. log_message() {
  24. local message="$1"
  25. local timestamp=$(date '+%Y-%m-%d %H:%M:%S')
  26. echo "[$timestamp] $message" | tee -a "$MIGRATION_LOG"
  27. }
  28. log_error() {
  29. local message="$1"
  30. log_message "ERROR: $message"
  31. }
  32. log_success() {
  33. local message="$1"
  34. log_message "SUCCESS: $message"
  35. }
  36. # Disk space checking functions
  37. check_disk_space() {
  38. local required_space_gb="$1"
  39. local available_space_gb=$(df "$SNAP_COMMON" | awk 'NR==2 {print int($4/1024/1024)}')
  40. if [ "$available_space_gb" -lt "$required_space_gb" ]; then
  41. log_error "Insufficient disk space. Required: ${required_space_gb}GB, Available: ${available_space_gb}GB"
  42. return 1
  43. fi
  44. log_message "Disk space check passed. Available: ${available_space_gb}GB, Required: ${required_space_gb}GB"
  45. return 0
  46. }
  47. # Progress tracking functions
  48. update_progress() {
  49. local step="$1"
  50. local total_steps="$2"
  51. local description="$3"
  52. local percentage=$((step * 100 / total_steps))
  53. # Update JSON status file
  54. cat > "$MIGRATION_STATUS" << EOF
  55. {
  56. "step": $step,
  57. "total_steps": $total_steps,
  58. "percentage": $percentage,
  59. "description": "$description",
  60. "timestamp": "$(date -Iseconds)",
  61. "status": "running"
  62. }
  63. EOF
  64. # Update HTML progress page
  65. cat > "$MIGRATION_PROGRESS" << EOF
  66. <!DOCTYPE html>
  67. <html>
  68. <head>
  69. <title>MongoDB Migration Progress</title>
  70. <meta http-equiv="refresh" content="5">
  71. <style>
  72. body { font-family: Arial, sans-serif; margin: 40px; }
  73. .progress-bar { width: 100%; background-color: #f0f0f0; border-radius: 5px; }
  74. .progress-fill { height: 30px; background-color: #4CAF50; border-radius: 5px; width: ${percentage}%; }
  75. .status { margin: 20px 0; }
  76. .error { color: red; }
  77. .success { color: green; }
  78. </style>
  79. </head>
  80. <body>
  81. <h1>MongoDB Migration Progress</h1>
  82. <div class="progress-bar">
  83. <div class="progress-fill"></div>
  84. </div>
  85. <div class="status">
  86. <p><strong>Progress:</strong> $step of $total_steps steps ($percentage%)</p>
  87. <p><strong>Current Step:</strong> $description</p>
  88. <p><strong>Last Updated:</strong> $(date)</p>
  89. </div>
  90. <p><em>This page will refresh automatically every 5 seconds.</em></p>
  91. </body>
  92. </html>
  93. EOF
  94. log_message "Progress: $step/$total_steps ($percentage%) - $description"
  95. }
  96. # Estimate completion time
  97. estimate_completion_time() {
  98. local start_time="$1"
  99. local current_step="$2"
  100. local total_steps="$3"
  101. if [ "$current_step" -gt 0 ]; then
  102. local elapsed=$(($(date +%s) - start_time))
  103. local avg_time_per_step=$((elapsed / current_step))
  104. local remaining_steps=$((total_steps - current_step))
  105. local estimated_remaining=$((remaining_steps * avg_time_per_step))
  106. local hours=$((estimated_remaining / 3600))
  107. local minutes=$(((estimated_remaining % 3600) / 60))
  108. local seconds=$((estimated_remaining % 60))
  109. echo "${hours}h ${minutes}m ${seconds}s"
  110. else
  111. echo "Calculating..."
  112. fi
  113. }
  114. # Create backup before migration
  115. create_backup() {
  116. log_message "Creating backup of MongoDB 3 database"
  117. # Check disk space for backup (estimate 2x current database size)
  118. local db_size=$(du -s "${SNAP_COMMON}/wekan" 2>/dev/null | awk '{print $1}' || echo "0")
  119. local required_space=$((db_size * 2 / 1024 / 1024 + 1)) # Convert to GB and add 1GB buffer
  120. if ! check_disk_space "$required_space"; then
  121. log_error "Insufficient disk space for backup"
  122. return 1
  123. fi
  124. # Create backup directory
  125. mkdir -p "$BACKUP_DIR"
  126. # Copy database files
  127. if [ -d "${SNAP_COMMON}/wekan" ]; then
  128. cp -r "${SNAP_COMMON}/wekan" "$BACKUP_DIR/"
  129. log_success "Database backup created at $BACKUP_DIR"
  130. return 0
  131. else
  132. log_error "No database found to backup"
  133. return 1
  134. fi
  135. }
  136. # Check if migration is needed
  137. check_migration_needed() {
  138. if [ -f "$MIGRATION_STATUS" ]; then
  139. local status=$(jq -r '.status' "$MIGRATION_STATUS" 2>/dev/null || echo "unknown")
  140. if [ "$status" = "completed" ]; then
  141. log_message "Migration already completed"
  142. return 1
  143. elif [ "$status" = "running" ]; then
  144. log_message "Migration already in progress"
  145. return 0
  146. fi
  147. fi
  148. # Check if we have MongoDB 3 data (either in wekan directory or raw database files)
  149. if [ -d "${SNAP_COMMON}/wekan" ] && [ ! -f "${SNAP_COMMON}/mongodb-version-7" ]; then
  150. log_message "MongoDB 3 data detected in wekan directory"
  151. return 0
  152. fi
  153. # Check for MongoDB 3 raw database files
  154. if detect_mongodb3_raw_files; then
  155. log_message "MongoDB 3 raw database files detected"
  156. return 0
  157. fi
  158. log_message "No migration needed"
  159. return 1
  160. }
  161. # Detect MongoDB 3 raw database files
  162. detect_mongodb3_raw_files() {
  163. # Look for MongoDB 3 specific files and directories
  164. local mongodb3_indicators=(
  165. "${SNAP_COMMON}/local.0"
  166. "${SNAP_COMMON}/local.ns"
  167. "${SNAP_COMMON}/local.1"
  168. "${SNAP_COMMON}/wekan.0"
  169. "${SNAP_COMMON}/wekan.ns"
  170. "${SNAP_COMMON}/wekan.1"
  171. "${SNAP_COMMON}/storage.bson"
  172. "${SNAP_COMMON}/_tmp"
  173. )
  174. # Check for MongoDB 3 journal files
  175. local journal_files=(
  176. "${SNAP_COMMON}/journal"
  177. "${SNAP_COMMON}/j._0"
  178. )
  179. # Check for MongoDB 3 lock file
  180. if [ -f "${SNAP_COMMON}/mongod.lock" ]; then
  181. log_message "MongoDB lock file found, checking for MongoDB 3 data"
  182. # Check if any MongoDB 3 indicators exist
  183. for indicator in "${mongodb3_indicators[@]}"; do
  184. if [ -e "$indicator" ]; then
  185. log_message "MongoDB 3 indicator found: $indicator"
  186. return 0
  187. fi
  188. done
  189. # Check for journal files
  190. for journal in "${journal_files[@]}"; do
  191. if [ -e "$journal" ]; then
  192. log_message "MongoDB journal file found: $journal"
  193. return 0
  194. fi
  195. done
  196. # Check for any .0, .1, .ns files (MongoDB 3 data files)
  197. if find "${SNAP_COMMON}" -maxdepth 1 -name "*.0" -o -name "*.1" -o -name "*.ns" | grep -q .; then
  198. log_message "MongoDB 3 data files found"
  199. return 0
  200. fi
  201. fi
  202. return 1
  203. }
  204. # Migrate raw MongoDB 3 database files
  205. migrate_raw_database_files() {
  206. log_message "Starting raw MongoDB 3 database files migration"
  207. # Stop any running MongoDB processes
  208. log_message "Stopping any running MongoDB processes"
  209. pkill -f mongod || true
  210. sleep 3
  211. # Start MongoDB 3 with raw files
  212. log_message "Starting MongoDB 3 with raw database files"
  213. local mongo3_pid
  214. mongod --dbpath "${SNAP_COMMON}" --port "${MONGODB_PORT:-27019}" --quiet &
  215. mongo3_pid=$!
  216. # Wait for MongoDB 3 to start
  217. local retry_count=0
  218. while [ $retry_count -lt 30 ]; do
  219. if mongosh --quiet --eval "db.adminCommand('ping')" "mongodb://localhost:${MONGODB_PORT:-27019}/admin" >/dev/null 2>&1; then
  220. log_message "MongoDB 3 started successfully"
  221. break
  222. fi
  223. sleep 1
  224. retry_count=$((retry_count + 1))
  225. done
  226. if [ $retry_count -eq 30 ]; then
  227. log_error "MongoDB 3 failed to start"
  228. kill $mongo3_pid 2>/dev/null || true
  229. return 1
  230. fi
  231. # Dump all databases from MongoDB 3
  232. log_message "Dumping databases from MongoDB 3"
  233. if ! mongodump --port "${MONGODB_PORT:-27019}" --out "$TEMP_DIR"; then
  234. log_error "Failed to dump databases from MongoDB 3"
  235. kill $mongo3_pid 2>/dev/null || true
  236. return 1
  237. fi
  238. # Stop MongoDB 3
  239. log_message "Stopping MongoDB 3"
  240. kill $mongo3_pid 2>/dev/null || true
  241. sleep 3
  242. # Start MongoDB 7
  243. log_message "Starting MongoDB 7"
  244. local mongo7_pid
  245. mongod --dbpath "${SNAP_COMMON}" --port "${MONGODB_PORT:-27019}" --quiet &
  246. mongo7_pid=$!
  247. # Wait for MongoDB 7 to start
  248. retry_count=0
  249. while [ $retry_count -lt 30 ]; do
  250. if mongosh --quiet --eval "db.adminCommand('ping')" "mongodb://localhost:${MONGODB_PORT:-27019}/admin" >/dev/null 2>&1; then
  251. log_message "MongoDB 7 started successfully"
  252. break
  253. fi
  254. sleep 1
  255. retry_count=$((retry_count + 1))
  256. done
  257. if [ $retry_count -eq 30 ]; then
  258. log_error "MongoDB 7 failed to start"
  259. kill $mongo7_pid 2>/dev/null || true
  260. return 1
  261. fi
  262. # Restore databases to MongoDB 7
  263. log_message "Restoring databases to MongoDB 7"
  264. if ! mongorestore --port "${MONGODB_PORT:-27019}" "$TEMP_DIR"; then
  265. log_error "Failed to restore databases to MongoDB 7"
  266. kill $mongo7_pid 2>/dev/null || true
  267. return 1
  268. fi
  269. # Stop MongoDB 7
  270. log_message "Stopping MongoDB 7"
  271. kill $mongo7_pid 2>/dev/null || true
  272. sleep 3
  273. # Clean up old MongoDB 3 files
  274. log_message "Cleaning up old MongoDB 3 files"
  275. find "${SNAP_COMMON}" -maxdepth 1 -name "*.0" -o -name "*.1" -o -name "*.ns" -o -name "j._*" -o -name "mongod.lock" | while read -r file; do
  276. if [ -f "$file" ]; then
  277. rm -f "$file"
  278. log_message "Removed old file: $file"
  279. fi
  280. done
  281. # Remove journal directory if it exists
  282. if [ -d "${SNAP_COMMON}/journal" ]; then
  283. rm -rf "${SNAP_COMMON}/journal"
  284. log_message "Removed journal directory"
  285. fi
  286. log_success "Raw database files migration completed successfully"
  287. return 0
  288. }
  289. # Snap channel detection and switching functions
  290. get_current_snap_channel() {
  291. local snap_name="$1"
  292. snap list "$snap_name" 2>/dev/null | awk 'NR==2 {print $4}' || echo "unknown"
  293. }
  294. is_wekan_snap() {
  295. local snap_name="$1"
  296. case "$snap_name" in
  297. wekan|wekan-gantt-gpl|wekan-ondra)
  298. return 0
  299. ;;
  300. *)
  301. return 1
  302. ;;
  303. esac
  304. }
  305. switch_to_stable_channel() {
  306. local snap_name="$1"
  307. local current_channel=$(get_current_snap_channel "$snap_name")
  308. if [ "$current_channel" != "stable" ] && [ "$current_channel" != "unknown" ]; then
  309. log_message "Switching $snap_name from $current_channel to stable channel"
  310. if snap refresh "$snap_name" --channel=stable; then
  311. log_success "Successfully switched $snap_name to stable channel"
  312. return 0
  313. else
  314. log_error "Failed to switch $snap_name to stable channel"
  315. return 1
  316. fi
  317. else
  318. log_message "$snap_name is already on stable channel or not installed"
  319. return 0
  320. fi
  321. }
  322. switch_all_wekan_snaps_to_stable() {
  323. log_message "Checking for Wekan-related snaps to switch to stable channel"
  324. local wekan_snaps=("wekan" "wekan-gantt-gpl" "wekan-ondra")
  325. local switched_count=0
  326. local failed_count=0
  327. for snap_name in "${wekan_snaps[@]}"; do
  328. if snap list "$snap_name" >/dev/null 2>&1; then
  329. if switch_to_stable_channel "$snap_name"; then
  330. switched_count=$((switched_count + 1))
  331. else
  332. failed_count=$((failed_count + 1))
  333. fi
  334. fi
  335. done
  336. log_message "Channel switching completed: $switched_count successful, $failed_count failed"
  337. if [ "$failed_count" -gt 0 ]; then
  338. return 1
  339. else
  340. return 0
  341. fi
  342. }
  343. # Get database collections
  344. get_collections() {
  345. local mongo_url="$1"
  346. local collections=$(mongosh --quiet --eval "db.getCollectionNames().join('\n')" "$mongo_url" 2>/dev/null | grep -v "^$" || echo "")
  347. echo "$collections"
  348. }
  349. # Migrate a single collection
  350. migrate_collection() {
  351. local collection="$1"
  352. local mongo3_url="$2"
  353. local mongo7_url="$3"
  354. local step="$4"
  355. local total_steps="$5"
  356. log_message "Migrating collection: $collection"
  357. # Check disk space before each collection (estimate 2x collection size)
  358. local collection_size=$(mongosh --quiet --eval "db.$collection.stats().size" "$mongo3_url" 2>/dev/null || echo "0")
  359. local required_space=$((collection_size * 2 / 1024 / 1024 / 1024 + 1)) # Convert to GB and add 1GB buffer
  360. if ! check_disk_space "$required_space"; then
  361. log_error "Insufficient disk space for collection $collection"
  362. return 1
  363. fi
  364. # Dump collection
  365. local dump_file="${TEMP_DIR}/${collection}.bson"
  366. log_message "Dumping collection $collection to $dump_file"
  367. if ! mongodump --db wekan --collection "$collection" --out "$TEMP_DIR" --port "${MONGODB_PORT:-27019}"; then
  368. log_error "Failed to dump collection $collection"
  369. return 1
  370. fi
  371. # Restore collection
  372. log_message "Restoring collection $collection to MongoDB 7"
  373. if ! mongorestore --db wekan --collection "$collection" "$dump_file" --port "${MONGODB_PORT:-27019}"; then
  374. log_error "Failed to restore collection $collection"
  375. return 1
  376. fi
  377. # Update progress
  378. update_progress "$step" "$total_steps" "Migrated collection: $collection"
  379. # Clean up dump file
  380. rm -f "$dump_file"
  381. log_success "Collection $collection migrated successfully"
  382. return 0
  383. }
  384. # Main migration function
  385. perform_migration() {
  386. local start_time=$(date +%s)
  387. log_message "Starting MongoDB migration from version 3 to 7"
  388. # Create backup before migration
  389. log_message "Creating backup before migration"
  390. if ! create_backup; then
  391. log_error "Failed to create backup, aborting migration"
  392. return 1
  393. fi
  394. # Create temporary directory
  395. mkdir -p "$TEMP_DIR"
  396. # Check if we need to migrate raw database files
  397. if detect_mongodb3_raw_files; then
  398. log_message "Raw MongoDB 3 database files detected, starting raw file migration"
  399. if ! migrate_raw_database_files; then
  400. log_error "Failed to migrate raw database files"
  401. return 1
  402. fi
  403. fi
  404. # Get MongoDB connection details
  405. local mongo3_url="mongodb://localhost:${MONGODB_PORT:-27019}/wekan"
  406. local mongo7_url="mongodb://localhost:${MONGODB_PORT:-27019}/wekan"
  407. # Get collections to migrate
  408. log_message "Getting list of collections to migrate"
  409. local collections=$(get_collections "$mongo3_url")
  410. if [ -z "$collections" ]; then
  411. log_error "No collections found to migrate"
  412. return 1
  413. fi
  414. local collection_count=$(echo "$collections" | wc -l)
  415. log_message "Found $collection_count collections to migrate"
  416. # Migrate each collection
  417. local current_step=0
  418. for collection in $collections; do
  419. current_step=$((current_step + 1))
  420. if ! migrate_collection "$collection" "$mongo3_url" "$mongo7_url" "$current_step" "$collection_count"; then
  421. log_error "Migration failed at collection $collection"
  422. return 1
  423. fi
  424. # Update completion time estimate
  425. local estimated_time=$(estimate_completion_time "$start_time" "$current_step" "$collection_count")
  426. log_message "Estimated completion time: $estimated_time"
  427. done
  428. # Mark migration as completed
  429. cat > "$MIGRATION_STATUS" << EOF
  430. {
  431. "step": $collection_count,
  432. "total_steps": $collection_count,
  433. "percentage": 100,
  434. "description": "Migration completed successfully",
  435. "timestamp": "$(date -Iseconds)",
  436. "status": "completed"
  437. }
  438. EOF
  439. # Create MongoDB 7 version marker
  440. touch "${SNAP_COMMON}/mongodb-version-7"
  441. # Clean up temporary files
  442. rm -rf "$TEMP_DIR"
  443. # Switch Wekan snaps to stable channel after successful migration
  444. log_message "Switching Wekan snaps to stable channel after successful migration"
  445. if switch_all_wekan_snaps_to_stable; then
  446. log_success "All Wekan snaps switched to stable channel successfully"
  447. else
  448. log_error "Some Wekan snaps failed to switch to stable channel"
  449. # Don't fail the migration for channel switching issues
  450. fi
  451. log_success "MongoDB migration completed successfully"
  452. return 0
  453. }
  454. # Revert migration
  455. revert_migration() {
  456. log_message "Reverting MongoDB migration"
  457. if [ ! -f "$REVERT_FILE" ]; then
  458. log_error "Revert file not found: $REVERT_FILE"
  459. return 1
  460. fi
  461. # Stop MongoDB 7
  462. log_message "Stopping MongoDB 7"
  463. snapctl stop --disable "${SNAP_NAME}.mongodb"
  464. # Remove MongoDB 7 version marker
  465. rm -f "${SNAP_COMMON}/mongodb-version-7"
  466. # Find the most recent backup directory
  467. local latest_backup=$(ls -td "${SNAP_COMMON}/mongodb-backup-"* 2>/dev/null | head -1)
  468. if [ -n "$latest_backup" ] && [ -d "$latest_backup" ]; then
  469. log_message "Restoring from backup: $latest_backup"
  470. # Stop any running MongoDB processes
  471. pkill -f mongod || true
  472. sleep 2
  473. # Remove current database directory
  474. rm -rf "${SNAP_COMMON}/wekan"
  475. # Restore from backup
  476. cp -r "$latest_backup"/* "${SNAP_COMMON}/"
  477. # Clean up backup directory
  478. rm -rf "$latest_backup"
  479. log_success "Database restored from backup"
  480. else
  481. log_error "No backup found for revert"
  482. return 1
  483. fi
  484. # Remove revert file
  485. rm -f "$REVERT_FILE"
  486. # Clear migration status
  487. rm -f "$MIGRATION_STATUS"
  488. # Start MongoDB 3
  489. log_message "Starting MongoDB 3"
  490. snapctl start --enable "${SNAP_NAME}.mongodb"
  491. log_success "Migration reverted successfully"
  492. return 0
  493. }
  494. # Main execution
  495. main() {
  496. log_message "MongoDB Migration Script started"
  497. # Check if revert is requested
  498. if [ -f "$REVERT_FILE" ]; then
  499. revert_migration
  500. exit $?
  501. fi
  502. # Check if migration is needed
  503. if ! check_migration_needed; then
  504. exit 0
  505. fi
  506. # Perform migration
  507. if perform_migration; then
  508. log_success "Migration completed successfully"
  509. exit 0
  510. else
  511. log_error "Migration failed"
  512. exit 1
  513. fi
  514. }
  515. # Run main function
  516. main "$@"