|
7 | 7 | "os" |
8 | 8 | "path/filepath" |
9 | 9 | "strings" |
| 10 | +"syscall" |
10 | 11 |
|
11 | 12 | "github.com/0xJacky/Nginx-UI/internal/nginx" |
12 | 13 | "github.com/0xJacky/Nginx-UI/settings" |
@@ -345,9 +346,9 @@ func verifyHashes(restoreDir, nginxUIZipPath, nginxZipPath string) (bool, error) |
345 | 346 | // parseHashInfo parses hash info from content string |
346 | 347 | funcparseHashInfo(contentstring)HashInfo { |
347 | 348 | info:=HashInfo{} |
348 | | -lines:=strings.Split(content,"\n") |
| 349 | +lines:=strings.SplitSeq(content,"\n") |
349 | 350 |
|
350 | | -for_,line:=rangelines { |
| 351 | +forline:=rangelines { |
351 | 352 | line=strings.TrimSpace(line) |
352 | 353 | ifline=="" { |
353 | 354 | continue |
@@ -383,35 +384,146 @@ func restoreNginxConfigs(nginxBackupDir string) error { |
383 | 384 | returnErrNginxConfigDirEmpty |
384 | 385 | } |
385 | 386 |
|
| 387 | +logger.Infof("Starting Nginx config restore from %s to %s",nginxBackupDir,destDir) |
| 388 | + |
386 | 389 | // Recursively clean destination directory preserving the directory structure |
| 390 | +logger.Info("Cleaning destination directory before restore") |
387 | 391 | iferr:=cleanDirectoryPreservingStructure(destDir);err!=nil { |
| 392 | +logger.Errorf("Failed to clean directory %s: %v",destDir,err) |
388 | 393 | returncosy.WrapErrorWithParams(ErrCopyNginxConfigDir,"failed to clean directory: "+err.Error()) |
389 | 394 | } |
390 | 395 |
|
391 | 396 | // Copy files from backup to nginx config directory |
| 397 | +logger.Infof("Copying backup files to destination: %s",destDir) |
392 | 398 | iferr:=copyDirectory(nginxBackupDir,destDir);err!=nil { |
| 399 | +logger.Errorf("Failed to copy backup files: %v",err) |
393 | 400 | returnerr |
394 | 401 | } |
395 | 402 |
|
| 403 | +logger.Info("Nginx config restore completed successfully") |
396 | 404 | returnnil |
397 | 405 | } |
398 | 406 |
|
399 | | -// cleanDirectoryPreservingStructure removes all files andsymlinks in a directory |
400 | | -// but preserves the directory structure itself |
| 407 | +// cleanDirectoryPreservingStructure removes all files andsubdirectories in a directory |
| 408 | +// but preserves the directory structure itself and handles mount points correctly. |
401 | 409 | funccleanDirectoryPreservingStructure(dirstring)error { |
| 410 | +logger.Infof("Cleaning directory: %s",dir) |
| 411 | + |
402 | 412 | entries,err:=os.ReadDir(dir) |
403 | 413 | iferr!=nil { |
404 | 414 | returnerr |
405 | 415 | } |
406 | 416 |
|
407 | 417 | for_,entry:=rangeentries { |
408 | 418 | path:=filepath.Join(dir,entry.Name()) |
409 | | -err=os.RemoveAll(path) |
410 | | -iferr!=nil { |
| 419 | + |
| 420 | +iferr:=removeOrClearPath(path,entry.IsDir());err!=nil { |
411 | 421 | returnerr |
412 | 422 | } |
413 | 423 | } |
414 | 424 |
|
| 425 | +logger.Infof("Successfully cleaned directory: %s",dir) |
| 426 | +returnnil |
| 427 | +} |
| 428 | + |
| 429 | +// removeOrClearPath removes a path or clears it if it's a mount point |
| 430 | +funcremoveOrClearPath(pathstring,isDirbool)error { |
| 431 | +// Try to remove the path first |
| 432 | +err:=os.RemoveAll(path) |
| 433 | +iferr==nil { |
| 434 | +returnnil |
| 435 | +} |
| 436 | + |
| 437 | +// Handle removal failures |
| 438 | +if!isDeviceBusyError(err) { |
| 439 | +returnfmt.Errorf("failed to remove %s: %w",path,err) |
| 440 | +} |
| 441 | + |
| 442 | +// Device busy - check if it's a mount point or directory |
| 443 | +if!isDir { |
| 444 | +returnfmt.Errorf("file is busy and cannot be removed: %s: %w",path,err) |
| 445 | +} |
| 446 | + |
| 447 | +logger.Warnf("Path is busy (mount point): %s, clearing contents only",path) |
| 448 | +returnclearDirectoryContents(path) |
| 449 | +} |
| 450 | + |
| 451 | +// isMountPoint checks if a path is a mount point by comparing device IDs |
| 452 | +// or checking /proc/mounts on Linux systems |
| 453 | +funcisMountPoint(pathstring)bool { |
| 454 | +ifisDeviceDifferent(path) { |
| 455 | +returntrue |
| 456 | +} |
| 457 | + |
| 458 | +returnisInMountTable(path) |
| 459 | +} |
| 460 | + |
| 461 | +// isDeviceDifferent and isInMountTable are implemented in platform-specific files: |
| 462 | +// - restore_unix.go for Linux/Unix systems |
| 463 | +// - restore_windows.go for Windows systems |
| 464 | + |
| 465 | +// unescapeOctal converts octal escape sequences like \040 to their character equivalents |
| 466 | +funcunescapeOctal(sstring)string { |
| 467 | +varresult strings.Builder |
| 468 | + |
| 469 | +fori:=0;i<len(s);i++ { |
| 470 | +ifchar,skip:=tryParseOctal(s,i);skip>0 { |
| 471 | +result.WriteByte(char) |
| 472 | +i+=skip-1// -1 because loop will increment |
| 473 | +continue |
| 474 | +} |
| 475 | +result.WriteByte(s[i]) |
| 476 | +} |
| 477 | + |
| 478 | +returnresult.String() |
| 479 | +} |
| 480 | + |
| 481 | +// tryParseOctal attempts to parse octal sequence at position i |
| 482 | +// returns (char, skip) where skip > 0 if successful |
| 483 | +functryParseOctal(sstring,iint) (byte,int) { |
| 484 | +ifs[i]!='\\'||i+3>=len(s) { |
| 485 | +return0,0 |
| 486 | +} |
| 487 | + |
| 488 | +varcharbyte |
| 489 | +if_,err:=fmt.Sscanf(s[i:i+4],"\\%03o",&char);err==nil { |
| 490 | +returnchar,4 |
| 491 | +} |
| 492 | + |
| 493 | +return0,0 |
| 494 | +} |
| 495 | + |
| 496 | +// isDeviceBusyError checks if an error is a "device or resource busy" error |
| 497 | +funcisDeviceBusyError(errerror)bool { |
| 498 | +iferr==nil { |
| 499 | +returnfalse |
| 500 | +} |
| 501 | + |
| 502 | +iferrno,ok:=err.(syscall.Errno);ok&&errno==syscall.EBUSY { |
| 503 | +returntrue |
| 504 | +} |
| 505 | + |
| 506 | +errMsg:=err.Error() |
| 507 | +returnstrings.Contains(errMsg,"device or resource busy")|| |
| 508 | +strings.Contains(errMsg,"resource busy") |
| 509 | +} |
| 510 | + |
| 511 | +// clearDirectoryContents removes all files and subdirectories within a directory |
| 512 | +// but preserves the directory itself. This is useful for cleaning mount points. |
| 513 | +funcclearDirectoryContents(dirstring)error { |
| 514 | +entries,err:=os.ReadDir(dir) |
| 515 | +iferr!=nil { |
| 516 | +returnerr |
| 517 | +} |
| 518 | + |
| 519 | +for_,entry:=rangeentries { |
| 520 | +path:=filepath.Join(dir,entry.Name()) |
| 521 | + |
| 522 | +iferr:=removeOrClearPath(path,entry.IsDir());err!=nil { |
| 523 | +logger.Warnf("Failed to clear %s: %v, continuing",path,err) |
| 524 | +} |
| 525 | +} |
| 526 | + |
415 | 527 | returnnil |
416 | 528 | } |
417 | 529 |
|
|