88 lines
3.4 KiB
PowerShell
88 lines
3.4 KiB
PowerShell
|
|
[CmdletBinding()]
|
|||
|
|
param(
|
|||
|
|
[Parameter(Mandatory = $true)]
|
|||
|
|
[string]$BackupFileName,
|
|||
|
|
[Parameter(Mandatory = $true)]
|
|||
|
|
[string]$DatabaseName,
|
|||
|
|
[Parameter(Mandatory = $true)]
|
|||
|
|
[string]$DataLogicalName,
|
|||
|
|
[Parameter(Mandatory = $true)]
|
|||
|
|
[string]$LogLogicalName,
|
|||
|
|
[string]$ContainerName = "ys-tplus-sqlserver-dev",
|
|||
|
|
[string]$BackupRoot = "D:\work\app\tplus-sqlserver\backup",
|
|||
|
|
[string]$DataRoot = "D:\work\app\tplus-sqlserver\data",
|
|||
|
|
[string]$LogRoot = "D:\work\app\tplus-sqlserver\log",
|
|||
|
|
[Parameter(Mandatory = $true)]
|
|||
|
|
[SecureString]$SaPassword
|
|||
|
|
)
|
|||
|
|
|
|||
|
|
Set-StrictMode -Version Latest
|
|||
|
|
$ErrorActionPreference = "Stop"
|
|||
|
|
|
|||
|
|
. (Join-Path $PSScriptRoot "container_sql.ps1")
|
|||
|
|
|
|||
|
|
if ($DatabaseName -notmatch '^[A-Za-z_][A-Za-z0-9_]{0,127}$') {
|
|||
|
|
throw "恢复数据库名只能使用字母、数字和下划线。"
|
|||
|
|
}
|
|||
|
|
if ([IO.Path]::GetFileName($BackupFileName) -ne $BackupFileName) {
|
|||
|
|
throw "备份文件名不能包含目录。"
|
|||
|
|
}
|
|||
|
|
foreach ($path in @($BackupRoot, $DataRoot, $LogRoot)) {
|
|||
|
|
if (-not [IO.Path]::GetFullPath($path).StartsWith("D:\", [StringComparison]::OrdinalIgnoreCase)) {
|
|||
|
|
throw "恢复目录必须位于 D 盘。"
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
$mounts = docker inspect $ContainerName --format '{{json .Mounts}}' | ConvertFrom-Json
|
|||
|
|
if ($LASTEXITCODE -ne 0) { throw "无法读取 SQL Server 容器挂载。" }
|
|||
|
|
foreach ($pair in @(
|
|||
|
|
@{ Target="/var/opt/mssql/data"; Source=$DataRoot },
|
|||
|
|
@{ Target="/var/opt/mssql/log"; Source=$LogRoot },
|
|||
|
|
@{ Target="/var/opt/mssql/backup"; Source=$BackupRoot }
|
|||
|
|
)) {
|
|||
|
|
$mount = @($mounts | Where-Object { $_.Destination -eq $pair.Target })
|
|||
|
|
if ($mount.Count -ne 1 -or $mount[0].Type -ne "bind") {
|
|||
|
|
throw "目标目录未明确绑定到宿主机 D 盘:$($pair.Target)"
|
|||
|
|
}
|
|||
|
|
if ([IO.Path]::GetFullPath($mount[0].Source) -ine [IO.Path]::GetFullPath($pair.Source)) {
|
|||
|
|
throw "实际挂载与恢复目录配置不一致:$($pair.Target)"
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
foreach ($path in @($BackupRoot, $DataRoot, $LogRoot)) {
|
|||
|
|
if (-not (Test-Path -LiteralPath $path)) {
|
|||
|
|
New-Item -ItemType Directory -Path $path -Force | Out-Null
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
$backupPath = Join-Path $BackupRoot $BackupFileName
|
|||
|
|
if (-not (Test-Path -LiteralPath $backupPath)) {
|
|||
|
|
throw "找不到备份文件:$backupPath"
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
$containerBackupPath = "/var/opt/mssql/backup/$BackupFileName"
|
|||
|
|
$dataPath = "/var/opt/mssql/data/$DatabaseName.mdf"
|
|||
|
|
$logPath = "/var/opt/mssql/log/$DatabaseName`_log.ldf"
|
|||
|
|
$safeDatabaseIdentifier = $DatabaseName.Replace("]", "]]" )
|
|||
|
|
$safeDatabaseLiteral = $DatabaseName.Replace("'", "''")
|
|||
|
|
$safeDataLogicalName = $DataLogicalName.Replace("'", "''")
|
|||
|
|
$safeLogLogicalName = $LogLogicalName.Replace("'", "''")
|
|||
|
|
$safeBackupPath = $containerBackupPath.Replace("'", "''")
|
|||
|
|
$sql = @"
|
|||
|
|
RESTORE VERIFYONLY FROM DISK = N'$safeBackupPath';
|
|||
|
|
IF DB_ID(N'$safeDatabaseLiteral') IS NOT NULL
|
|||
|
|
BEGIN
|
|||
|
|
THROW 51000, 'Target database exists; refusing to overwrite.', 1;
|
|||
|
|
END;
|
|||
|
|
RESTORE DATABASE [$safeDatabaseIdentifier]
|
|||
|
|
FROM DISK = N'$safeBackupPath'
|
|||
|
|
WITH MOVE N'$safeDataLogicalName' TO N'$dataPath',
|
|||
|
|
MOVE N'$safeLogLogicalName' TO N'$logPath',
|
|||
|
|
RECOVERY, STATS = 5;
|
|||
|
|
ALTER DATABASE [$safeDatabaseIdentifier] SET READ_ONLY;
|
|||
|
|
DBCC CHECKDB (N'$safeDatabaseLiteral') WITH NO_INFOMSGS;
|
|||
|
|
"@
|
|||
|
|
|
|||
|
|
Write-Host "正在校验并恢复备份(密码不会输出)……"
|
|||
|
|
Invoke-TPlusContainerSql -ContainerName $ContainerName -Sql $sql -SaPassword $SaPassword
|
|||
|
|
Write-Host "恢复完成:$DatabaseName;数据库文件位于 D 盘绑定目录。"
|