Linux Backup mit der PowerShell

Nach dem SSH und SFTP mit der PowerShell Beitrag, wollte ich euch ein praxisnahes Beispiel vorstellen, ein Linux Backup mit der PowerShell. Wie bereits im Beitrag Backup Raspberry Pi to Windows geht es um die Sicherung von Daten auf einem Linux System, diesmal um die Daten von diesem Blog.

Die Daten werden über SSH aufbereitet, per SFTP abgeholt und dann lokal abgelegt. Auf dem Linux System muss dafür nichts angepasst werden, es wird alles über das PowerShell Skript gesteuert. Das Skript wird bei mir täglich über den Windows Task Scheduler gestartet und es funktioniert bisher sehr zuverlässig, ich kann aber für nichts garantieren! 🙂

Funktion

Zuerst wird das Credential Object Credentials erstellt. Dafür wird, wenn noch nicht vorhanden, einmalig das Kennwort angefordert. Dies wird dann verschlüsselt in das Password File geschrieben und beim nächsten Start des Skripts verwendet. Der erste Start sollte also direkt über die PowerShell Konsole erfolgen!

Das Skript verbindet sich dann per SSH mit dem Webserver und sichert den Content als gezipptes Archiv und die Datenbank über mysqldump in das RemoteBackupPath Verzeichnis. Somit ist schon mal eine lokale Sicherung für den Notfall vorhanden.

Im nächsten Schritt wird eine SFTP Verbindung aufgebaut und die in FilesToBackup definierten Dateien auf Existenz und Alter geprüft. Wenn die Datei älter ist als in MaxBackupAge definiert, wird die Datei vom Backup ausgeschlossen. Dann werden die Dateien auf das lokale System in den LocalBackupPath kopiert, ein Ordner mit dem Datum Today im Format YYYY-MM-TT erstellt und die Dateien in diesen Ordner verschoben. Ist beim Backup ein Fehler aufgetreten, wird an den Ordner ein Failed angehängt. So wird sichergestellt das der Aufräumprozess nicht alle erfolgreichen Sicherungen vernichtet.

Der Aufräumprozess lässt immer eine wie in HistoryDays definierte Anzahl an erfolgreichen Backups stehen. Ausgeschlossen werden monatliche Backups vom ersten Tag des Monats, vorausgesetzt das Backup war an diesem Tag erfolgreich. Wenn alles ohne Fehler durchgelaufen ist und SendOKMail auf $true steht, wird eine E-Mail verschickt die nur ein “OK” enthält. Im Falle eines Fehlers, wird die nicht gesicherte Datei aufgelistet und zusätzlich das Logfile angehängt.

Skript

Das Skript ist aufgeteilt auf zwei Dateien und zusätzlich im Download Bereich zu finden. Ich hoffe die Variablen erklären sich soweit selbst?! Wenn noch Fragen bestehen einfach ein Kommentar hinterlassen!

############################################################
#
# WebServer Backup
#
# Author: Stefan Nikolaus
# Source: www.nikolaus-lueneburg.de
#
############################################################

$ComputerName = "xx.xx.xx.xx"
$Username = "user1"

# Files to backup
$FilesToBackup = @("archive.tar.gz","db_backup.sql")

# Remote backup path
$RemoteBackupPath = "/backup"

# Maximum backup age on remote filesystem in minutes
$MaxBackupAge = 1

# Local backup path
$LocalBackupPath = "c:\temp\backup\"

# How many days backups should be retained
$HistoryDays = 5

# Password file
$PasswordFolder = "C:\temp"
$Extension = ".sec"

# Mail
$SmtpServer = "192.168.1.123" # SmartHost without autentication
$FromAddress = "from@nikolaus-lueneburg.de"
$ToAddress = "to@nikolaus-lueneburg.de"
$Subject = "Backup - www.nikolaus-lueneburg.de"

$SendOKMail = $true # Send mail if everything is OK

# Logfile
$LogfileName = "log.txt"

# Functions filename
$FunctionsFile = "Functions-Backup.ps1"

############################################################

# Load Functions Script
$FunctionsScript = Join-Path $PSScriptRoot $FunctionsFile
. $FunctionsScript

############################################################

$Today = Get-Date -UFormat "%Y-%m-%d"

# Create $LogFile variable with timestamp
$LogFile = Join-Path $PSScriptRoot ($LogfileName.Replace(".","_$Today."))

# Modify $MaxBackupAge
$TimeSpan = New-TimeSpan -Minutes $MaxBackupAge
$Limit = (Get-Date) - $TimeSpan

# Set $PasswordFile* variables
$PasswordFilename = $ComputerName + $Extension
$PasswordFilePath = Join-Path $PasswordFolder $PasswordFilename

# Convert $FilesToBackup array
[System.Collections.ArrayList]$FilesToBackup = $FilesToBackup

# Create $MissingFiles array
$MissingFiles = New-Object System.Collections.ArrayList

# Reset error flag
$Global:BackupError = $false

############################################################

# Create Password File if needed
CreatePasswordFile -FilePath $PasswordFilePath

# Create Credential Object
$Credentials = CreateCredentialObject -FilePath $PasswordFilePath -Username $Username

############################################################

# Create SSH Session
Log -text "Create SSH Session" -severity 0
Try
{
    $Session = New-SSHSession -ComputerName $ComputerName -Credential $Credentials -ConnectionTimeout 30 -AcceptKey:$true -ErrorAction Stop # -Verbose
}
Catch
{
Log -text $_.Exception.Message -severity 2
  break
}

# Backup WordPress Site
Log -text "Backup WordPress Site" -severity 0
try
{
    $Command = "tar -zcf /backup/archive.tar.gz /httpdocs/nikolaus-lueneburg"
    #Invoke-SSHCommand -SSHSession $Session -Command $Command -TimeOut 300 | Out-Null
}
catch
{
    Log -text $_.Exception.Message -severity 2
}

# Backup Database
Log -text "Backup Database" -severity 0
try
{
    $Command = "mysqldump -P 3306 -h 10.xx.xx.xx -ustefan -pgeheim db1 > /backup/db_backup.sql"
    Invoke-SSHCommand -SSHSession $Session -Command $Command -TimeOut 300 | Out-Null
}
catch
{
    Log -text $_.Exception.Message -severity 2
}

# Disconnect SSH Session
Remove-SSHSession $Session | Out-Null

############################################################

# Create SFTP Session
Log -text "Create SFTP session" -severity 0
Try
{
    $Session = New-SFTPSession -ComputerName $ComputerName -Credential $Credentials -ConnectionTimeout 30 -AcceptKey:$true -ErrorAction SilentlyContinue #-Verbose
}
Catch
{
  Log -text $_.Exception.Message  -severity 2
  break
}

# Get folder items from SFTP server
$BackupFolderItems = Get-SFTPChildItem -SFTPSession $Session -Path $RemoteBackupPath

# Check file age
foreach ($File in $FilesToBackup)
{
    if ($BackupFolderItems.Name -eq $File)
    {
        if (!(($BackupFolderItems | where-object {$_.Name -eq $File}).LastWriteTime -gt $limit))
        {
            Log -text "$File too old ... skipping file" -severity 2
            $MissingFiles.Add($File) | Out-Null
        }
    }
    else
    {
        Log -text "$File not exist! Backup failed?" -severity 1
        $MissingFiles.Add($File) | Out-Null
    }
}

# Delete file from array $FilesToBackup
foreach ($File in $MissingFiles)
{
    $FilesToBackup.Remove($File)
}

# Get file from SFTP server
foreach ($File in $FilesToBackup)
{
    $SFTPFullPath = "$RemoteBackupPath/$File"
    Log -text "Get file $File from SFTP server" -severity 0
    Get-SFTPFile -SFTPSession $Session -LocalPath $LocalBackupPath -RemoteFile $SFTPFullPath -Overwrite:$true
}

# Disconnect SFTP Session
Remove-SFTPSession $Session | Out-Null

############################################################

# Create local folder $Today
$CreateFolder = New-Item -Path $LocalBackupPath -Name $Today -ItemType directory -ErrorAction SilentlyContinue
if ($CreateFolder -eq $null)
{
    Log -text "Folder $Today not created" -severity 2
}
else
{
    Log -text "Folder $Today created" -severity 0
}

# Copy files to folder $Today
$BackupTodayPath = Join-Path $LocalBackupPath $Today
foreach ($File in $FilesToBackup)
{
    $BackupFile = Join-Path $LocalBackupPath $File
    Log -text "Move $File to folder $BackupTodayPath" -severity 0
    if (Test-Path (Join-Path $BackupTodayPath $File))
    {
        Log -text "$File allready exist!" -severity 1
    }
    else
    {
        Move-Item -Path $BackupFile -Destination $BackupTodayPath
    }
}

# Rename folder if backup failed
if ($Global:BackupError)
{
    Log -text "Due to errors, the folder will be renamed" -severity 1
    Rename-Item -path $BackupTodayPath -newName ($Today + "_FAILED")
    $BackupTodayPath = $BackupTodayPath + "_FAILED"
}

# Folder CleanUp (Exclude monthly Backup 01 Folder)
Log -text "Starting folder cleanup" -severity 0
$Folder = Get-ChildItem -Path $LocalBackupPath | ? { $_.PsIsContainer -and $_.Name -notmatch '^\d{4}[-|\s]\d{2}[-|\s]01$' -and $_.Name -match "^\d{4}[-|\s]\d{2}[-|\s]\d{2}$"}
if ($Folder.Count -gt $HistoryDays)
{
    $DeleteFolder = $Folder | Sort Name | Select-Object -First($Folder.Count-$HistoryDays)
    if ($DeleteFolder -ne $null)
    {
        Log -text "Deleting the following folders" -severity 0
        Log -text $DeleteFolder.Name
        Remove-Item $DeleteFolder.FullName -Recurse
    }
}

Log -text "Finished - Sending E-Mail" -severity 0

if ($Global:BackupError)
{
    $body = "<b>Error!</b><br><br>Following files were not backed up:<br>$MissingFiles"
    Mail -body $body -attachments $Logfile
}
else
{
    if ($SendOKMail)
    {
    $body = "OK"
    Mail -body $body
    }
}

# Cleanup
Move-Item $Logfile $BackupTodayPath

Im folgenden Skript (Functions-Backup.ps1) sind ein paar Funktionen ausgelagert.

############################################################
#
# Functions Script
#
# Author: Stefan Nikolaus
# Source: www.nikolaus-lueneburg.de
#
############################################################

function CreatePasswordFile ($FilePath)
{
    if (Test-Path $FilePath)
    {
        Write-Host "Password file allready exist" -ForegroundColor Green
    }
    else
    {
        Try
        {
            Read-Host -assecurestring | ConvertFrom-SecureString | Out-File ($FilePath)
        }
        Catch
        {
          Write-Host $_.Exception.Message
          break
        }
        Write-Host "Writing password file to" $FilePath -ForegroundColor Green
    }
}

function CreateCredentialObject ($FilePath, $Username)
{
    if (Test-Path $FilePath)
    {
        # Load Password File
        $Password = cat $FilePath | ConvertTo-SecureString

        # Create Credential Object
        Write-Host "Creating credential object" -ForegroundColor Green
        $Cred = New-Object -typename System.Management.Automation.PSCredential -ArgumentList $Username, $Password
        Return $Cred
    }
    else
    {
        Write-Host "Password file allready exist" -ForegroundColor Red
    }
}

function Log($text,$severity)
{
    $Time = Get-Date –f [yyyy-MM-dd_HHmmss]
    switch ($severity)
    {
        0 {
            Write-Host $text -ForegroundColor green
            "[INF] " + $time + " " + $text | Out-File -Append $Logfile
        }

        1 {
            $Global:BackupError = $true
            Write-Host $text -ForegroundColor Yellow
            "[WAR] " + $time + " " + $text | Out-File -Append $Logfile
        }

        2 {
            $Global:BackupError = $true
            Write-Host $text -ForegroundColor Red
            "[ERR] " + $time + " " + $text | Out-File -Append $Logfile
        }

        default {
            Write-Host $text
            "[---] " + $time + " " + $text | Out-File -Append $Logfile
        }
    }
}

function Mail($body,$attachments)
{
    if ($attachments)
    {
        Send-MailMessage -SmtpServer $SmtpServer -From $FromAddress -To $ToAddress -Subject $Subject -Body $body -BodyAsHtml -Attachments $attachments
    }
    else
    {
        Send-MailMessage -SmtpServer $SmtpServer -From $FromAddress -To $ToAddress -Subject $Subject -Body $body -BodyAsHtml
    }
}

Schreibe einen Kommentar

Deine E-Mail-Adresse wird nicht veröffentlicht. Erforderliche Felder sind mit * markiert