관리자가 서버에서 컴퓨터로 백업을 정기적으로 다운로드하기 위한 PowerShell 스크립트

최근, 한 친구가 VPS 제공업체가 사전 통보 없이 갑자기 사업을 접는 문제를 겪었습니다. 불행히도, 저렴한 가격을 위해 틈새 제공업체를 선택했는데, 이 업체는 데이터 다운로드 기능조차 제공하지 않았습니다. 더糟糕한 것은, 그녀는 정기적으로 백업을 다운로드하는 습관이 없었습니다. 하루아침에 그녀의 사이트는 모든 데이터와 함께 사라졌습니다.

이 충격적인 경험으로 인해, 저는 추가적인 객체 저장 서비스나 다른 곳에 돈을 쓰지 않고도 서버에서 백업을 자동으로 로컬 컴퓨터로 정기적으로 다운로드하는 PowerShell 스크립트를 작성했습니다. 필요한 것은 정기적으로 사용하며(하드 드라이브 공간이 충분하고) 인터넷에 연결된 Windows 컴퓨터 하나뿐입니다.

이 스크립트는 5일 이전의 백업을 자동으로 정리합니다. 필요에 따라 자동 백업 간격과 로컬에 저장할 가장 오래된 백업 시간을 설정할 수 있습니다.


$ssh_port = 22
$ssh_address = "username@your.site"


Write-Output "Discourse 백업 다운로드 작업 시작..."
Write-Output '------------------------'
Write-Output "최신 백업 파일 가져오는 중..."
Write-Output ''

while ($true) {

    $filename = ''

    while ($true) {
        try {
            Write-Output "> ssh -p $ssh_port $ssh_address 'cd /var/discourse/shared/standalone/backups/default/ && ls -t | head -n 1'"
            Write-Output ''
            $filename = ssh -p $ssh_port "$ssh_address" 'cd /var/discourse/shared/standalone/backups/default/ && ls -t | head -n 1'
            break
        }
        catch {
            $filename = ''
    
            Write-Output "가져오기 실패... 로그는 다음과 같습니다:"
            Write-Output '-------------'
            Write-Output $_
            
            $answer = Read-Host "다시 가져오시겠습니까? (y/N)"
            if ($answer -ne 'y') {
                break
            }
            Write-Output ''
        }
    }
    
    
    if ([String]::IsNullOrEmpty($filename)) {
        Write-Output "오류: 파일 이름 $filename 가져오기 실패"
        Write-Output ''
        $answer = Read-Host '재시도?(y/N)'
  
        if ($answer -eq 'y') {
  
        }
        else {
      
            exit 1
        }
  
    }
    else {

        Write-Output "최신 백업: $filename"
        Write-Output ''
        
        $need_download = $true
        if (Test-Path ".\backups\$filename") {
            $answer = Read-Host ".\backups\$filename 이(가) 이미 존재합니다. 다시 다운로드하시겠습니까?(y/N)"
            Write-Output ''
            if ($answer -ne 'y') {
                $need_download = $false
            }
        }
        if ($need_download) {
            Write-Output "다운로드 시작..."
            Write-Output ''
            
            while ($true) {
                try {
                    Write-Output "scp -p $ssh_port ${ssh_address}:/var/discourse/shared/standalone/backups/default/$filename .\backups\"
                    Write-Output ''

                    scp -p $ssh_port "${ssh_address}:/var/discourse/shared/standalone/backups/default/$filename" .\backups\
                    
                    Write-Output "다운로드 완료"
                    Write-Output ''
                    
                    break
                }
                catch {

                    Write-Output "다운로드 실패 >_<... 로그는 다음과 같습니다:"
                    Write-Output ''

                    Write-Output $_
                    
                    $answer = Read-Host "다시 다운로드하시겠습니까? (y/N)"
                    Write-Output ''
                    if ($answer -ne 'y') {
                        break
                    }
                }
            }

        }
  
        Write-Output "오래된 백업 파일 정리 시도 시작..."
        Write-Output ''

        $count = 0
        $backupfiles = Get-ChildItem -Path .\backups\
  
        foreach ($file in $backupfiles) {
            if ($file.CreationTime -le (Get-Date).AddDays(-5)) {
                try {
                    Write-Output "오래된 백업 파일 $file 삭제 중..."
                    Write-Output ''
                    $file.Delete()
                    $count = $count + 1
                } catch {
                    Write-Output "오래된 백업 파일 $file 삭제 중 오류 발생 >_<"
                    Write-Output '-------------------'
                    Write-Output $_
                    Write-Output '-------------------'
                }
            }
        }

        if ($count -ge 0) {
            Write-Output "$count 개의 오래된 백업 파일 정리 완료"
            Write-Output ''
        }
        else {
            Write-Output '정리할 오래된 백업 파일 없음'
            Write-Output ''
        }

        Pause
  
        exit 0
  
    }
  
  
}


위 스크립트를 백업을 다운로드하려는 경로에 scriptname.ps1이라는 이름으로 저장하십시오. "PowerShell에서 실행"을 시도해 보세요. 성공하면 다음 단계로 진행할 수 있습니다.

작업 예약하기

  1. "예약된 작업"을 검색합니다.
  2. 예약된 작업 추가를 두 번 클릭합니다. 예약된 작업 마법사가 나타납니다.
  3. 다음을 클릭한 뒤, 찾아보기를 클릭합니다. 예약할 프로그램 선택 대화 상자가 나타납니다.
  4. 만든 스크립트로 이동하여 클릭한 후, 열기를 클릭합니다. 예약된 작업 마법사로 돌아옵니다.
  5. 작업 이름을 지정하거나 기본값인 파일 이름을 유지한 채, 스크립트를 실행할 빈도를 지정하고 다음을 클릭합니다.
  6. 시작 시간과 날짜(일일, 주간, 월간 등을 지정했다면) 및 반복을 지정하고 다음을 클릭합니다. 이 항목은 discourse의 자동 백업 사이클과 일치해야 합니다.
  7. 스크립트를 실행할 계정의 사용자 이름과 비밀번호를 입력하고 다음을 클릭합니다.
  8. 고급 속성을 구성하려면 확인란을 선택한 후, 완료를 클릭합니다.
15개의 좋아요

원격 백업이 얼마나 중요한지 다시 한번 일깨워주는 사례입니다. 몇 달 전 다른 메타 사용자도 동일한 문제를 겪었습니다.

제 인스턴스 중 하나는 내장된 S3 백업 기능을 사용하고, 나머지는 Rclone과 CRON 작업을 사용해 Google Drive에 백업을 전송합니다. 이 방법에 대한 가이드가 있습니다: Use rclone to sync backups to Dropbox or Google Drive

스크립트를 공유해 주셔서 감사합니다, Linca :+1:

3개의 좋아요

정말 좋은 지적입니다. 저는 (여기)에서 업데이트 기능이 백업 파일의 마지막 접근 시간이 너무 오래되었는지 확인하고 경고하도록 제안한 바 있습니다. 우리는 2~3개월에 한 번씩 업데이트를 수행하므로, 해당 체크가 너무 자주 실행되지는 않을 것입니다. 또한 업데이트 시점은 안전한 백업이 있는지 확인하기에 좋은 타이밍이기도 합니다. 백업 파일을 다른 위치로 복사하는 모든 방식은 마지막 접근 타임스탬프를 갱신해야 합니다.

3개의 좋아요

좋은 지적이네요, 스크립트 감사합니다. rsync 버전을 공유해 보겠습니다. :slight_smile:

아래 예시에서는 sql과 첨부파일을 각각 별도로 동기화합니다. 첨부파일을 백업 파일에 포함할 필요가 없으며, 이전 백업을 삭제할 필요도 없습니다.

저는 cwrsync를 설치하기 위해 Chocolatey를 사용합니다. 설치 후에는 cmd 파일 끝에 몇 줄을 추가하고 스케줄을 생성하기만 하면 됩니다:

C:\ProgramData\chocolatey\lib\rsync\tools\cwrsync.cmd

예를 들어:

rsync -rvm --delete --ignore-errors --ignore-existing --size-only --chmod=ugo=rwX -e "ssh -i /cygdrive/c/Users/user1/.ssh/id_rsa" login@host:/var/discourse/shared/standalone/backups/default/ /cygdrive/d/backup/forum/db/

rsync -rvm --delete --ignore-errors --ignore-existing --size-only --chmod=ugo=rwX -e "ssh -i /cygdrive/c/Users/user1/.ssh/id_rsa" login@host:/var/discourse/shared/standalone/uploads/ /cygdrive/d/backup/forum/uploads/

참고 1: C: 대신 /cygdrive/c/를 사용하십시오. 참조 및 문법을 확인하기 위해 cmd 파일을 읽어보십시오.

참고 2: ssh 키(PEM 형식) 대신 비밀번호를 사용하도록 명령을 수정할 수 있습니다.

참고 3: dump/ 폴더 뒤에 슬래시를 넣지 않으면 rsync가 폴더 자체를 복사하지만, 슬래시를 넣으면 폴더의 내용만 복사합니다.

참고 4: chocolatey가 cwrsync를 업그레이드하면 이전 cmd 파일의 백업을 같은 폴더에 생성합니다. 백업을 계속하려면 명령을 새 cmd 파일에 수동으로 복사해야 합니다.

중요: OP가 작성한 것과 동일한 규칙을 스케줄러에 설정하십시오. 스케줄러 행은 다음과 같습니다:

C:\ProgramData\chocolatey\lib\rsync\tools\cwrsync.cmd >> d:\backup\cwrsync.txt 2>&1

이는 출력을 cwrsync.txt 파일에 추가합니다. 이상입니다.

3개의 좋아요

이런 일이 갑자기 벌어질까 봐 불안한 마음에, rclone을 설정해서 백업을 원격(집에 있는 셀프호스트 NAS)으로 자동으로 복사하는 습관이 있습니다. 이 글은 모든 것을 한 곳에 의존해서는 안 된다는 점을 잘 상기시켜 줍니다.

5개의 좋아요

기회를 틈타 /var/discourse/containers에 있는 모든 것의 사본도 받아 두세요.

4개의 좋아요

좋은 생각입니다! 업데이트를 배포하겠습니다

3개의 좋아요

어디서 “Scheduled Tasks”를 검색해야 하나요? (초보입니다. 조언 부탁드립니다. 감사합니다)

"Scheduled Tasks"는 작업 스케줄러를 의미하며, Windows 10 이상에서는 Win 키를 누른 후 검색창에 입력하면 바로 찾을 수 있습니다.

2개의 좋아요

안녕하세요. 왜 이런 오류가 나는 걸까요?

이 글을 읽고 계신 분들을 위해, 원포스터의 설명을 따라하는 데 조금 어려움을 겪었기 때문에 더 나은/최신화된 방법을 공유합니다:

  1. 이 스크립트를 저장할 폴더를 만들거나 기존 폴더를 사용하세요. 그런 다음, 해당 폴더 안에 backup 이라는 이름의 폴더를 만드세요.

  2. 메모장에 스크립트를 복사/붙여넣기하세요. 참고: VPS를 사용하는 경우, $ssh_address = "username@your.site" 줄에 VPS 사용자 이름과 공개 IP 주소를 사용하세요. anyname.ps1로 다른 이름으로 저장하고, 아래 그림과 같이 드롭다운 상자를 "모든 파일"로 설정하는 것을 잊지 마세요.
    hhhh

  3. Windows 키 ⊞를 누르고 "작업"을 입력한 다음, "작업 스케줄러"를 클릭하세요.

  4. 오른쪽에서 "기본 작업 만들기"를 클릭하세요.

  5. 임의의 이름/설명을 입력하세요.

  6. 일일/주별/연간 등 원하는 주기를 선택하세요. 일일을 권장합니다.

  7. 실행할 날짜와 시간을 설정하세요. 반복은 1로 유지하세요. 반드시 Discourse 사이트의 관리자 설정 > 백업으로 이동하세요. 자동 백업 활성화 옆의 상자를 체크하세요.
    그런 다음, 백업 주기를 원하는 값으로 설정하세요. 1을 권장하지만 여기서 핵심은 작업 스케줄러 설정과 일치하도록 하는 것입니다. 백업 날짜를 일치시키세요. 예: 작업 스케줄러를 일일 백업으로 설정했다면, 관리자 백업의 백업 주기 설정을 1로 설정하세요.

  8. 기본 설정인 "프로그램 시작"을 유지한 다음 다음을 클릭하세요.

  9. 찾아보기를 클릭하고 저장한 스크립트를 찾습니다. 다음을 클릭하세요.

  10. 완료를 클릭하세요.
    테스트를 원하시면 스크립트를 마우스 오른쪽 버튼으로 클릭하고 "PowerShell로 실행"을 선택하세요.

참고: "지정한 파일을 찾을 수 없습니다"라는 오류가 발생하면, PowerShell에 표시된 대로 비밀번호를 입력하면 그래도 찾을 수 있습니다.

1개의 좋아요

PS 스크립트를 저장한 후(단, 2번째 줄에 제 사용자 이름과 IP 주소만 수정함) 마우스 오른쪽 버튼 > PowerShell로 실행을 누르면 아무 일도 일어나지 않습니다.
화면이 깜빡일 뿐입니다. 그리고 이미 Win11에서 터미널이 열려 있다면, 그 터미널이 앞으로 나옵니다. 그 외에는 아무것도 없습니다. 그냥 깜빡일 뿐입니다.

다만, 스크립트에 사용자 이름과 IP 주소를 제공했는데, 어떻게 제 SSH 키를 사용하여 로그인하고 백업을 가져오는지 궁금합니다.

감사합니다.!

OP와 45thj5ej님의 해결책을 모두 시도해 보았지만, 내 환경에서는 둘 다 작동하지 않습니다. PowerShell로 스크립트를 실행하면 "/backups/"와 하위 항목을 찾지 못합니다. 또한 OP가 언급한 사용자 이름과 비밀번호를 입력해야 하는 위치를 찾지 못하겠습니다. 저는 VPS에서 Discourse를 실행하고 있습니다.