mongodb-migrate 27 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867
  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. #
  5. # IMPORTANT: All operations are contained within SNAP_COMMON directory
  6. # This is the only writable directory in a snap environment
  7. set -e
  8. # Source settings
  9. source $SNAP/bin/wekan-read-settings
  10. # Migration configuration
  11. MIGRATION_LOG="${SNAP_COMMON}/mongodb-migration-log.txt"
  12. MIGRATION_STATUS="${SNAP_COMMON}/mongodb-migration-status.json"
  13. MIGRATION_PROGRESS="${SNAP_COMMON}/mongodb-migration-progress.html"
  14. REVERT_FILE="${SNAP_COMMON}/revert-mongodb-migration.txt"
  15. TEMP_DIR="${SNAP_COMMON}/mongodb-migration-temp"
  16. BACKUP_DIR="${SNAP_COMMON}/mongodb-backup-$(date +%Y%m%d-%H%M%S)"
  17. # MongoDB paths
  18. MONGO3_BIN="/snap/${SNAP_NAME}/current/migratemongo/bin"
  19. MONGO7_BIN="/snap/${SNAP_NAME}/current/bin"
  20. MONGO3_LIB="/snap/${SNAP_NAME}/current/migratemongo/lib"
  21. MONGO7_LIB="/snap/${SNAP_NAME}/current/usr/lib"
  22. # Set up environment for MongoDB 3 tools
  23. export LD_LIBRARY_PATH="${MONGO3_LIB}:${MONGO3_LIB}/x86_64-linux-gnu:${LD_LIBRARY_PATH}"
  24. export PATH="${MONGO3_BIN}:${MONGO7_BIN}:${PATH}"
  25. # Set MongoDB log destination to snapcommon for log file detection
  26. export MONGO_LOG_DESTINATION="snapcommon"
  27. # Validate that all operations are within SNAP_COMMON
  28. validate_snap_common_path() {
  29. local path="$1"
  30. local description="$2"
  31. if [[ "$path" != "${SNAP_COMMON}"* ]]; then
  32. log_error "Path outside SNAP_COMMON detected: $path ($description)"
  33. log_error "SNAP_COMMON: $SNAP_COMMON"
  34. return 1
  35. fi
  36. return 0
  37. }
  38. # Validate all critical paths
  39. validate_all_paths() {
  40. log_message "Validating all paths are within SNAP_COMMON"
  41. validate_snap_common_path "$MIGRATION_LOG" "Migration log" || return 1
  42. validate_snap_common_path "$MIGRATION_STATUS" "Migration status" || return 1
  43. validate_snap_common_path "$MIGRATION_PROGRESS" "Migration progress" || return 1
  44. validate_snap_common_path "$REVERT_FILE" "Revert file" || return 1
  45. validate_snap_common_path "$TEMP_DIR" "Temporary directory" || return 1
  46. validate_snap_common_path "$BACKUP_DIR" "Backup directory" || return 1
  47. log_success "All paths validated within SNAP_COMMON"
  48. return 0
  49. }
  50. # Logging functions
  51. log_message() {
  52. local message="$1"
  53. local timestamp=$(date '+%Y-%m-%d %H:%M:%S')
  54. echo "[$timestamp] $message" | tee -a "$MIGRATION_LOG"
  55. }
  56. log_error() {
  57. local message="$1"
  58. log_message "ERROR: $message"
  59. }
  60. log_success() {
  61. local message="$1"
  62. log_message "SUCCESS: $message"
  63. }
  64. log_warning() {
  65. local message="$1"
  66. log_message "WARNING: $message"
  67. }
  68. # Disk space checking functions
  69. check_disk_space() {
  70. local required_space_gb="$1"
  71. local available_space_gb=$(df "$SNAP_COMMON" | awk 'NR==2 {print int($4/1024/1024)}')
  72. if [ "$available_space_gb" -lt "$required_space_gb" ]; then
  73. log_error "Insufficient disk space. Required: ${required_space_gb}GB, Available: ${available_space_gb}GB"
  74. return 1
  75. fi
  76. log_message "Disk space check passed. Available: ${available_space_gb}GB, Required: ${required_space_gb}GB"
  77. return 0
  78. }
  79. # Progress tracking functions
  80. update_progress() {
  81. local step="$1"
  82. local total_steps="$2"
  83. local description="$3"
  84. local percentage=$((step * 100 / total_steps))
  85. # Update JSON status file
  86. cat > "$MIGRATION_STATUS" << EOF
  87. {
  88. "step": $step,
  89. "total_steps": $total_steps,
  90. "percentage": $percentage,
  91. "description": "$description",
  92. "timestamp": "$(date -Iseconds)",
  93. "status": "running"
  94. }
  95. EOF
  96. # Update HTML progress page
  97. cat > "$MIGRATION_PROGRESS" << EOF
  98. <!DOCTYPE html>
  99. <html>
  100. <head>
  101. <title>MongoDB Migration Progress</title>
  102. <meta http-equiv="refresh" content="5">
  103. <style>
  104. body { font-family: Arial, sans-serif; margin: 40px; }
  105. .progress-bar { width: 100%; background-color: #f0f0f0; border-radius: 5px; }
  106. .progress-fill { height: 30px; background-color: #4CAF50; border-radius: 5px; width: ${percentage}%; }
  107. .status { margin: 20px 0; }
  108. .error { color: red; }
  109. .success { color: green; }
  110. </style>
  111. </head>
  112. <body>
  113. <h1>MongoDB Migration Progress</h1>
  114. <div class="progress-bar">
  115. <div class="progress-fill"></div>
  116. </div>
  117. <div class="status">
  118. <p><strong>Progress:</strong> $step of $total_steps steps ($percentage%)</p>
  119. <p><strong>Current Step:</strong> $description</p>
  120. <p><strong>Last Updated:</strong> $(date)</p>
  121. </div>
  122. <p><em>This page will refresh automatically every 5 seconds.</em></p>
  123. </body>
  124. </html>
  125. EOF
  126. log_message "Progress: $step/$total_steps ($percentage%) - $description"
  127. }
  128. # Estimate completion time
  129. estimate_completion_time() {
  130. local start_time="$1"
  131. local current_step="$2"
  132. local total_steps="$3"
  133. if [ "$current_step" -gt 0 ]; then
  134. local elapsed=$(($(date +%s) - start_time))
  135. local avg_time_per_step=$((elapsed / current_step))
  136. local remaining_steps=$((total_steps - current_step))
  137. local estimated_remaining=$((remaining_steps * avg_time_per_step))
  138. local hours=$((estimated_remaining / 3600))
  139. local minutes=$(((estimated_remaining % 3600) / 60))
  140. local seconds=$((estimated_remaining % 60))
  141. echo "${hours}h ${minutes}m ${seconds}s"
  142. else
  143. echo "Calculating..."
  144. fi
  145. }
  146. # Create backup before migration
  147. create_backup() {
  148. log_message "Creating backup of MongoDB 3 database"
  149. # Check disk space for backup (estimate 2x current database size)
  150. local db_size=$(du -s "${SNAP_COMMON}/wekan" 2>/dev/null | awk '{print $1}' || echo "0")
  151. local required_space=$((db_size * 2 / 1024 / 1024 + 1)) # Convert to GB and add 1GB buffer
  152. if ! check_disk_space "$required_space"; then
  153. log_error "Insufficient disk space for backup"
  154. return 1
  155. fi
  156. # Create backup directory
  157. mkdir -p "$BACKUP_DIR"
  158. # Copy database files
  159. if [ -d "${SNAP_COMMON}/wekan" ]; then
  160. cp -r "${SNAP_COMMON}/wekan" "$BACKUP_DIR/"
  161. log_success "Database backup created at $BACKUP_DIR"
  162. return 0
  163. else
  164. log_error "No database found to backup"
  165. return 1
  166. fi
  167. }
  168. # Check if migration is needed
  169. check_migration_needed() {
  170. if [ -f "$MIGRATION_STATUS" ]; then
  171. local status=$(jq -r '.status' "$MIGRATION_STATUS" 2>/dev/null || echo "unknown")
  172. if [ "$status" = "completed" ]; then
  173. log_message "Migration already completed"
  174. return 1
  175. elif [ "$status" = "running" ]; then
  176. log_message "Migration already in progress"
  177. return 0
  178. fi
  179. fi
  180. # Check if we have MongoDB 3 data (either in wekan directory or raw database files)
  181. if [ -d "${SNAP_COMMON}/wekan" ] && [ ! -f "${SNAP_COMMON}/mongodb-version-7" ]; then
  182. log_message "MongoDB 3 data detected in wekan directory"
  183. return 0
  184. fi
  185. # Check for MongoDB upgrade needed by examining log file
  186. if detect_mongodb_upgrade_needed; then
  187. log_message "MongoDB upgrade needed detected from log file"
  188. return 0
  189. fi
  190. log_message "No migration needed"
  191. return 1
  192. }
  193. # Display MongoDB log content for debugging
  194. display_mongodb_log_content() {
  195. local mongodb_log="${SNAP_COMMON}/mongodb.log"
  196. if [ ! -f "$mongodb_log" ]; then
  197. log_message "MongoDB log file not found: $mongodb_log"
  198. return 1
  199. fi
  200. log_message "MongoDB log file content (last 50 lines):"
  201. if [ -r "$mongodb_log" ]; then
  202. tail -50 "$mongodb_log" | while read -r line; do
  203. log_message "LOG: $line"
  204. done
  205. else
  206. log_message "MongoDB log file not readable, trying with sudo"
  207. sudo tail -50 "$mongodb_log" 2>/dev/null | while read -r line; do
  208. log_message "LOG: $line"
  209. done
  210. fi
  211. }
  212. # Detect if MongoDB upgrade is needed by checking log file
  213. detect_mongodb_upgrade_needed() {
  214. local mongodb_log="${SNAP_COMMON}/mongodb.log"
  215. # Check if MongoDB log file exists
  216. if [ ! -f "$mongodb_log" ]; then
  217. log_message "MongoDB log file not found: $mongodb_log"
  218. return 1
  219. fi
  220. # Display log content for debugging
  221. display_mongodb_log_content
  222. # Check file permissions and try to read with appropriate method
  223. if [ ! -r "$mongodb_log" ]; then
  224. log_message "MongoDB log file not readable, trying with sudo"
  225. # Try to read with sudo if not readable
  226. if ! sudo grep -q "too recent to start up on the existing data files" "$mongodb_log" 2>/dev/null; then
  227. log_message "No MongoDB upgrade needed detected in log file (via sudo)"
  228. return 1
  229. fi
  230. else
  231. # Check for various error messages that indicate upgrade is needed
  232. # The exact message may vary between MongoDB versions
  233. local upgrade_patterns=(
  234. "This version of MongoDB is too recent to start up on the existing data files"
  235. "too recent to start up on the existing data files"
  236. "Try MongoDB 4.2 or earlier"
  237. "unsupported format version"
  238. "data files are incompatible"
  239. "database files are incompatible"
  240. "version too new"
  241. "version too recent"
  242. )
  243. local found_upgrade_needed=false
  244. for pattern in "${upgrade_patterns[@]}"; do
  245. if grep -q "$pattern" "$mongodb_log" 2>/dev/null; then
  246. log_message "Found upgrade pattern in log: '$pattern'"
  247. found_upgrade_needed=true
  248. break
  249. fi
  250. done
  251. if [ "$found_upgrade_needed" = false ]; then
  252. log_message "No MongoDB upgrade needed detected in log file"
  253. return 1
  254. fi
  255. fi
  256. log_message "MongoDB upgrade needed detected in log file"
  257. return 0
  258. }
  259. # Log rotation function for MongoDB logs
  260. rotate_mongodb_logs() {
  261. local mongodb_log="${SNAP_COMMON}/mongodb.log"
  262. local max_size_mb=100
  263. local keep_copies=10
  264. # Check if log file exists and is large enough to rotate
  265. if [ ! -f "$mongodb_log" ]; then
  266. log_message "MongoDB log file not found, skipping rotation"
  267. return 0
  268. fi
  269. # Get log file size in MB
  270. local log_size_mb=$(du -m "$mongodb_log" | cut -f1)
  271. if [ "$log_size_mb" -lt "$max_size_mb" ]; then
  272. log_message "MongoDB log size (${log_size_mb}MB) is below rotation threshold (${max_size_mb}MB)"
  273. return 0
  274. fi
  275. log_message "Rotating MongoDB log file (size: ${log_size_mb}MB)"
  276. # Create rotated log file with timestamp
  277. local timestamp=$(date +%Y%m%d-%H%M%S)
  278. local rotated_log="${mongodb_log}.${timestamp}"
  279. # Copy current log to rotated file
  280. if cp "$mongodb_log" "$rotated_log"; then
  281. log_message "Created rotated log file: $rotated_log"
  282. # Truncate original log file
  283. if > "$mongodb_log"; then
  284. log_message "Truncated original log file"
  285. else
  286. log_error "Failed to truncate original log file"
  287. return 1
  288. fi
  289. # Compress rotated log file
  290. if gzip "$rotated_log"; then
  291. log_message "Compressed rotated log file: ${rotated_log}.gz"
  292. else
  293. log_warning "Failed to compress rotated log file"
  294. fi
  295. # Clean up old rotated logs (keep only specified number)
  296. local old_logs=$(ls -t "${mongodb_log}".* 2>/dev/null | tail -n +$((keep_copies + 1)))
  297. if [ -n "$old_logs" ]; then
  298. echo "$old_logs" | xargs rm -f
  299. log_message "Cleaned up old rotated log files"
  300. fi
  301. log_success "MongoDB log rotation completed successfully"
  302. return 0
  303. else
  304. log_error "Failed to create rotated log file"
  305. return 1
  306. fi
  307. }
  308. # Enhanced log rotation function for migration logs
  309. rotate_migration_logs() {
  310. local migration_log="${SNAP_COMMON}/mongodb-migration-log.txt"
  311. local max_size_mb=50
  312. local keep_copies=5
  313. # Check if migration log file exists and is large enough to rotate
  314. if [ ! -f "$migration_log" ]; then
  315. log_message "Migration log file not found, skipping rotation"
  316. return 0
  317. fi
  318. # Get log file size in MB
  319. local log_size_mb=$(du -m "$migration_log" | cut -f1)
  320. if [ "$log_size_mb" -lt "$max_size_mb" ]; then
  321. log_message "Migration log size (${log_size_mb}MB) is below rotation threshold (${max_size_mb}MB)"
  322. return 0
  323. fi
  324. log_message "Rotating migration log file (size: ${log_size_mb}MB)"
  325. # Create rotated log file with timestamp
  326. local timestamp=$(date +%Y%m%d-%H%M%S)
  327. local rotated_log="${migration_log}.${timestamp}"
  328. # Copy current log to rotated file
  329. if cp "$migration_log" "$rotated_log"; then
  330. log_message "Created rotated migration log file: $rotated_log"
  331. # Truncate original log file
  332. if > "$migration_log"; then
  333. log_message "Truncated original migration log file"
  334. else
  335. log_error "Failed to truncate original migration log file"
  336. return 1
  337. fi
  338. # Compress rotated log file
  339. if gzip "$rotated_log"; then
  340. log_message "Compressed rotated migration log file: ${rotated_log}.gz"
  341. else
  342. log_warning "Failed to compress rotated migration log file"
  343. fi
  344. # Clean up old rotated logs (keep only specified number)
  345. local old_logs=$(ls -t "${migration_log}".* 2>/dev/null | tail -n +$((keep_copies + 1)))
  346. if [ -n "$old_logs" ]; then
  347. echo "$old_logs" | xargs rm -f
  348. log_message "Cleaned up old rotated migration log files"
  349. fi
  350. log_success "Migration log rotation completed successfully"
  351. return 0
  352. else
  353. log_error "Failed to create rotated migration log file"
  354. return 1
  355. fi
  356. }
  357. # Reset MONGO_LOG_DESTINATION to devnull after successful migration
  358. reset_mongo_log_destination() {
  359. log_message "Resetting MONGO_LOG_DESTINATION to devnull after successful migration"
  360. # Use snap set to change the setting back to devnull
  361. if snap set wekan mongo-log-destination="devnull" 2>/dev/null; then
  362. log_success "MONGO_LOG_DESTINATION reset to devnull successfully"
  363. else
  364. log_error "Failed to reset MONGO_LOG_DESTINATION to devnull"
  365. # Don't fail the migration for this setting issue
  366. fi
  367. }
  368. # Migrate raw MongoDB 3 database files
  369. migrate_raw_database_files() {
  370. log_message "Starting raw MongoDB 3 database files migration"
  371. # Validate paths are within SNAP_COMMON
  372. if ! validate_snap_common_path "${SNAP_COMMON}" "Database path"; then
  373. log_error "Database path validation failed"
  374. return 1
  375. fi
  376. # Stop any running MongoDB processes
  377. log_message "Stopping any running MongoDB processes"
  378. pkill -f mongod || true
  379. sleep 3
  380. # Start MongoDB 3 with raw files
  381. log_message "Starting MongoDB 3 with raw database files"
  382. local mongo3_pid
  383. mongod --dbpath "${SNAP_COMMON}" --port "${MONGODB_PORT:-27019}" --quiet &
  384. mongo3_pid=$!
  385. # Wait for MongoDB 3 to start
  386. local retry_count=0
  387. while [ $retry_count -lt 30 ]; do
  388. if mongosh --quiet --eval "db.adminCommand('ping')" "mongodb://localhost:${MONGODB_PORT:-27019}/admin" >/dev/null 2>&1; then
  389. log_message "MongoDB 3 started successfully"
  390. break
  391. fi
  392. sleep 1
  393. retry_count=$((retry_count + 1))
  394. done
  395. if [ $retry_count -eq 30 ]; then
  396. log_error "MongoDB 3 failed to start"
  397. kill $mongo3_pid 2>/dev/null || true
  398. return 1
  399. fi
  400. # Dump all databases from MongoDB 3
  401. log_message "Dumping databases from MongoDB 3"
  402. if ! mongodump --port "${MONGODB_PORT:-27019}" --out "$TEMP_DIR" --dbpath "${SNAP_COMMON}"; then
  403. log_error "Failed to dump databases from MongoDB 3"
  404. kill $mongo3_pid 2>/dev/null || true
  405. return 1
  406. fi
  407. # Stop MongoDB 3
  408. log_message "Stopping MongoDB 3"
  409. kill $mongo3_pid 2>/dev/null || true
  410. sleep 3
  411. # Start MongoDB 7
  412. log_message "Starting MongoDB 7"
  413. local mongo7_pid
  414. mongod --dbpath "${SNAP_COMMON}" --port "${MONGODB_PORT:-27019}" --quiet &
  415. mongo7_pid=$!
  416. # Wait for MongoDB 7 to start
  417. retry_count=0
  418. while [ $retry_count -lt 30 ]; do
  419. if mongosh --quiet --eval "db.adminCommand('ping')" "mongodb://localhost:${MONGODB_PORT:-27019}/admin" >/dev/null 2>&1; then
  420. log_message "MongoDB 7 started successfully"
  421. break
  422. fi
  423. sleep 1
  424. retry_count=$((retry_count + 1))
  425. done
  426. if [ $retry_count -eq 30 ]; then
  427. log_error "MongoDB 7 failed to start"
  428. kill $mongo7_pid 2>/dev/null || true
  429. return 1
  430. fi
  431. # Restore databases to MongoDB 7
  432. log_message "Restoring databases to MongoDB 7"
  433. if ! mongorestore --port "${MONGODB_PORT:-27019}" --dbpath "${SNAP_COMMON}" "$TEMP_DIR"; then
  434. log_error "Failed to restore databases to MongoDB 7"
  435. kill $mongo7_pid 2>/dev/null || true
  436. return 1
  437. fi
  438. # Stop MongoDB 7
  439. log_message "Stopping MongoDB 7"
  440. kill $mongo7_pid 2>/dev/null || true
  441. sleep 3
  442. # Clean up old MongoDB 3 files
  443. log_message "Cleaning up old MongoDB 3 files"
  444. 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
  445. if [ -f "$file" ]; then
  446. rm -f "$file"
  447. log_message "Removed old file: $file"
  448. fi
  449. done
  450. # Remove journal directory if it exists
  451. if [ -d "${SNAP_COMMON}/journal" ]; then
  452. rm -rf "${SNAP_COMMON}/journal"
  453. log_message "Removed journal directory"
  454. fi
  455. log_success "Raw database files migration completed successfully"
  456. # Reset MONGO_LOG_DESTINATION to devnull after successful migration
  457. reset_mongo_log_destination
  458. return 0
  459. }
  460. # Snap channel detection and switching functions
  461. get_current_snap_channel() {
  462. local snap_name="$1"
  463. snap list "$snap_name" 2>/dev/null | awk 'NR==2 {print $4}' || echo "unknown"
  464. }
  465. is_wekan_snap() {
  466. local snap_name="$1"
  467. case "$snap_name" in
  468. wekan|wekan-gantt-gpl|wekan-ondra)
  469. return 0
  470. ;;
  471. *)
  472. return 1
  473. ;;
  474. esac
  475. }
  476. switch_to_stable_channel() {
  477. local snap_name="$1"
  478. local current_channel=$(get_current_snap_channel "$snap_name")
  479. if [ "$current_channel" != "stable" ] && [ "$current_channel" != "unknown" ]; then
  480. log_message "Switching $snap_name from $current_channel to stable channel"
  481. if snap refresh "$snap_name" --channel=stable; then
  482. log_success "Successfully switched $snap_name to stable channel"
  483. return 0
  484. else
  485. log_error "Failed to switch $snap_name to stable channel"
  486. return 1
  487. fi
  488. else
  489. log_message "$snap_name is already on stable channel or not installed"
  490. return 0
  491. fi
  492. }
  493. switch_all_wekan_snaps_to_stable() {
  494. log_message "Checking for Wekan-related snaps to switch to stable channel"
  495. local wekan_snaps=("wekan" "wekan-gantt-gpl" "wekan-ondra")
  496. local switched_count=0
  497. local failed_count=0
  498. for snap_name in "${wekan_snaps[@]}"; do
  499. if snap list "$snap_name" >/dev/null 2>&1; then
  500. if switch_to_stable_channel "$snap_name"; then
  501. switched_count=$((switched_count + 1))
  502. else
  503. failed_count=$((failed_count + 1))
  504. fi
  505. fi
  506. done
  507. log_message "Channel switching completed: $switched_count successful, $failed_count failed"
  508. if [ "$failed_count" -gt 0 ]; then
  509. return 1
  510. else
  511. return 0
  512. fi
  513. }
  514. # Get database collections
  515. get_collections() {
  516. local mongo_url="$1"
  517. local collections=$(mongosh --quiet --eval "db.getCollectionNames().join('\n')" "$mongo_url" 2>/dev/null | grep -v "^$" || echo "")
  518. echo "$collections"
  519. }
  520. # Migrate a single collection
  521. migrate_collection() {
  522. local collection="$1"
  523. local mongo3_url="$2"
  524. local mongo7_url="$3"
  525. local step="$4"
  526. local total_steps="$5"
  527. log_message "Migrating collection: $collection"
  528. # Check disk space before each collection (estimate 2x collection size)
  529. local collection_size=$(mongosh --quiet --eval "db.$collection.stats().size" "$mongo3_url" 2>/dev/null || echo "0")
  530. local required_space=$((collection_size * 2 / 1024 / 1024 / 1024 + 1)) # Convert to GB and add 1GB buffer
  531. if ! check_disk_space "$required_space"; then
  532. log_error "Insufficient disk space for collection $collection"
  533. return 1
  534. fi
  535. # Dump collection
  536. local dump_file="${TEMP_DIR}/${collection}.bson"
  537. log_message "Dumping collection $collection to $dump_file"
  538. if ! mongodump --db wekan --collection "$collection" --out "$TEMP_DIR" --port "${MONGODB_PORT:-27019}" --dbpath "${SNAP_COMMON}"; then
  539. log_error "Failed to dump collection $collection"
  540. return 1
  541. fi
  542. # Restore collection
  543. log_message "Restoring collection $collection to MongoDB 7"
  544. if ! mongorestore --db wekan --collection "$collection" "$dump_file" --port "${MONGODB_PORT:-27019}" --dbpath "${SNAP_COMMON}"; then
  545. log_error "Failed to restore collection $collection"
  546. return 1
  547. fi
  548. # Update progress
  549. update_progress "$step" "$total_steps" "Migrated collection: $collection"
  550. # Clean up dump file
  551. rm -f "$dump_file"
  552. log_success "Collection $collection migrated successfully"
  553. return 0
  554. }
  555. # Main migration function
  556. perform_migration() {
  557. local start_time=$(date +%s)
  558. log_message "Starting MongoDB migration from version 3 to 7"
  559. # Rotate MongoDB logs before migration if needed
  560. log_message "Checking if MongoDB log rotation is needed"
  561. if ! rotate_mongodb_logs; then
  562. log_warning "MongoDB log rotation failed, continuing with migration"
  563. fi
  564. # Rotate migration logs before migration if needed
  565. log_message "Checking if migration log rotation is needed"
  566. if ! rotate_migration_logs; then
  567. log_warning "Migration log rotation failed, continuing with migration"
  568. fi
  569. # Create backup before migration
  570. log_message "Creating backup before migration"
  571. if ! create_backup; then
  572. log_error "Failed to create backup, aborting migration"
  573. return 1
  574. fi
  575. # Create temporary directory
  576. mkdir -p "$TEMP_DIR"
  577. # Check if we need to migrate raw database files
  578. if detect_mongodb_upgrade_needed; then
  579. log_message "MongoDB upgrade needed detected, starting raw file migration"
  580. if ! migrate_raw_database_files; then
  581. log_error "Failed to migrate raw database files"
  582. return 1
  583. fi
  584. fi
  585. # Get MongoDB connection details
  586. local mongo3_url="mongodb://localhost:${MONGODB_PORT:-27019}/wekan"
  587. local mongo7_url="mongodb://localhost:${MONGODB_PORT:-27019}/wekan"
  588. # Get collections to migrate
  589. log_message "Getting list of collections to migrate"
  590. local collections=$(get_collections "$mongo3_url")
  591. if [ -z "$collections" ]; then
  592. log_error "No collections found to migrate"
  593. return 1
  594. fi
  595. local collection_count=$(echo "$collections" | wc -l)
  596. log_message "Found $collection_count collections to migrate"
  597. # Migrate each collection
  598. local current_step=0
  599. for collection in $collections; do
  600. current_step=$((current_step + 1))
  601. if ! migrate_collection "$collection" "$mongo3_url" "$mongo7_url" "$current_step" "$collection_count"; then
  602. log_error "Migration failed at collection $collection"
  603. return 1
  604. fi
  605. # Update completion time estimate
  606. local estimated_time=$(estimate_completion_time "$start_time" "$current_step" "$collection_count")
  607. log_message "Estimated completion time: $estimated_time"
  608. done
  609. # Mark migration as completed
  610. cat > "$MIGRATION_STATUS" << EOF
  611. {
  612. "step": $collection_count,
  613. "total_steps": $collection_count,
  614. "percentage": 100,
  615. "description": "Migration completed successfully",
  616. "timestamp": "$(date -Iseconds)",
  617. "status": "completed"
  618. }
  619. EOF
  620. # Create MongoDB 7 version marker
  621. touch "${SNAP_COMMON}/mongodb-version-7"
  622. # Clean up temporary files
  623. rm -rf "$TEMP_DIR"
  624. # Switch Wekan snaps to stable channel after successful migration
  625. log_message "Switching Wekan snaps to stable channel after successful migration"
  626. if switch_all_wekan_snaps_to_stable; then
  627. log_success "All Wekan snaps switched to stable channel successfully"
  628. else
  629. log_error "Some Wekan snaps failed to switch to stable channel"
  630. # Don't fail the migration for channel switching issues
  631. fi
  632. log_success "MongoDB migration completed successfully"
  633. # Rotate MongoDB logs after successful migration
  634. log_message "Rotating MongoDB logs after successful migration"
  635. if ! rotate_mongodb_logs; then
  636. log_warning "MongoDB log rotation after migration failed"
  637. fi
  638. # Rotate migration logs after successful migration
  639. log_message "Rotating migration logs after successful migration"
  640. if ! rotate_migration_logs; then
  641. log_warning "Migration log rotation after migration failed"
  642. fi
  643. # Reset MONGO_LOG_DESTINATION to devnull after successful migration
  644. reset_mongo_log_destination
  645. return 0
  646. }
  647. # Revert migration
  648. revert_migration() {
  649. log_message "Reverting MongoDB migration"
  650. if [ ! -f "$REVERT_FILE" ]; then
  651. log_error "Revert file not found: $REVERT_FILE"
  652. return 1
  653. fi
  654. # Stop MongoDB 7
  655. log_message "Stopping MongoDB 7"
  656. snapctl stop --disable "${SNAP_NAME}.mongodb"
  657. # Remove MongoDB 7 version marker
  658. rm -f "${SNAP_COMMON}/mongodb-version-7"
  659. # Find the most recent backup directory
  660. local latest_backup=$(ls -td "${SNAP_COMMON}/mongodb-backup-"* 2>/dev/null | head -1)
  661. if [ -n "$latest_backup" ] && [ -d "$latest_backup" ]; then
  662. log_message "Restoring from backup: $latest_backup"
  663. # Stop any running MongoDB processes
  664. pkill -f mongod || true
  665. sleep 2
  666. # Remove current database directory
  667. rm -rf "${SNAP_COMMON}/wekan"
  668. # Restore from backup
  669. cp -r "$latest_backup"/* "${SNAP_COMMON}/"
  670. # Clean up backup directory
  671. rm -rf "$latest_backup"
  672. log_success "Database restored from backup"
  673. else
  674. log_error "No backup found for revert"
  675. return 1
  676. fi
  677. # Remove revert file
  678. rm -f "$REVERT_FILE"
  679. # Clear migration status
  680. rm -f "$MIGRATION_STATUS"
  681. # Start MongoDB 3
  682. log_message "Starting MongoDB 3"
  683. snapctl start --enable "${SNAP_NAME}.mongodb"
  684. log_success "Migration reverted successfully"
  685. return 0
  686. }
  687. # Main execution
  688. main() {
  689. log_message "MongoDB Migration Script started"
  690. # Validate all paths are within SNAP_COMMON
  691. if ! validate_all_paths; then
  692. log_error "Path validation failed - aborting migration"
  693. exit 1
  694. fi
  695. # Check if revert is requested
  696. if [ -f "$REVERT_FILE" ]; then
  697. revert_migration
  698. exit $?
  699. fi
  700. # Check if migration is needed
  701. if ! check_migration_needed; then
  702. exit 0
  703. fi
  704. # Perform migration
  705. if perform_migration; then
  706. log_success "Migration completed successfully"
  707. exit 0
  708. else
  709. log_error "Migration failed"
  710. exit 1
  711. fi
  712. }
  713. # Run main function
  714. main "$@"