Scert/deploy/install-windows.ps1

192 lines
7.5 KiB
PowerShell
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

[CmdletBinding()]
param(
[string]$SourceDirectory = "",
[string]$InstallDirectory = (Join-Path $env:ProgramFiles "Scert"),
[string]$DataDirectory = (Join-Path $env:ProgramData "Scert"),
[string]$CertificateDirectory = (Join-Path $env:ProgramData "Scert\ssl"),
[ValidatePattern("^[A-Za-z0-9_.-]+$")]
[string]$ServiceName = "Scert",
[ValidatePattern("^[A-Za-z0-9_.-]+$")]
[string]$AdminServiceName = "Scert-admin"
)
Set-StrictMode -Version Latest
$ErrorActionPreference = "Stop"
$scriptDirectory = Split-Path -Parent $MyInvocation.MyCommand.Definition
if ([string]::IsNullOrWhiteSpace($SourceDirectory)) {
$SourceDirectory = $scriptDirectory
}
function Test-Administrator {
$identity = [Security.Principal.WindowsIdentity]::GetCurrent()
$principal = [Security.Principal.WindowsPrincipal]::new($identity)
return $principal.IsInRole([Security.Principal.WindowsBuiltInRole]::Administrator)
}
function Resolve-RequiredDirectory {
param(
[Parameter(Mandatory = $true)]
[string]$Path,
[Parameter(Mandatory = $true)]
[string]$Label
)
if ([string]::IsNullOrWhiteSpace($Path)) {
throw "$Label 不能为空。"
}
$resolved = [System.IO.Path]::GetFullPath($Path)
$root = [System.IO.Path]::GetPathRoot($resolved)
if ($resolved -eq $root) {
throw "$Label 不能使用磁盘根目录:$resolved"
}
return $resolved.TrimEnd([System.IO.Path]::DirectorySeparatorChar)
}
function Invoke-Sc {
param(
[Parameter(Mandatory = $true)]
[string[]]$Arguments,
[switch]$AllowFailure
)
$output = & sc.exe @Arguments 2>&1
if ($LASTEXITCODE -ne 0 -and -not $AllowFailure) {
throw "Windows 服务命令执行失败:sc.exe $($Arguments -join ' ')`n$($output -join "`n")"
}
return $output
}
function Test-ServiceExists {
param([Parameter(Mandatory = $true)][string]$Name)
& sc.exe query $Name *> $null
return $LASTEXITCODE -eq 0
}
function Install-OrUpdateService {
param(
[Parameter(Mandatory = $true)][string]$Name,
[Parameter(Mandatory = $true)][string]$DisplayName,
[Parameter(Mandatory = $true)][string]$Description,
[Parameter(Mandatory = $true)][string]$BinaryPath,
[Parameter(Mandatory = $true)][ValidateSet("auto", "demand")][string]$StartMode
)
if (Test-ServiceExists -Name $Name) {
Invoke-Sc -Arguments @("config", $Name, "binPath=", $BinaryPath, "start=", $StartMode, "obj=", "NT AUTHORITY\LocalService", "DisplayName=", $DisplayName) | Out-Null
}
else {
Invoke-Sc -Arguments @("create", $Name, "binPath=", $BinaryPath, "start=", $StartMode, "obj=", "NT AUTHORITY\LocalService", "DisplayName=", $DisplayName) | Out-Null
}
Invoke-Sc -Arguments @("description", $Name, $Description) | Out-Null
}
function Set-DirectoryAcl {
param(
[Parameter(Mandatory = $true)][string]$Path,
[Parameter(Mandatory = $true)][ValidateSet("read", "modify")][string]$Mode
)
$localServicePermission = if ($Mode -eq "modify") { "(OI)(CI)(M)" } else { "(OI)(CI)(RX)" }
& icacls.exe $Path /inheritance:r /grant:r `
"BUILTIN\Administrators:(OI)(CI)(F)" `
"NT AUTHORITY\SYSTEM:(OI)(CI)(F)" `
"NT AUTHORITY\LOCAL SERVICE:$localServicePermission" *> $null
if ($LASTEXITCODE -ne 0) {
throw "设置目录访问控制失败:$Path"
}
}
if ([Environment]::OSVersion.Platform -ne [PlatformID]::Win32NT) {
throw "该安装器只能在 Windows 上运行。"
}
if (-not (Test-Administrator)) {
throw "请使用管理员权限运行 Scert Windows 安装器。"
}
if (-not (Get-Command sc.exe -ErrorAction SilentlyContinue)) {
throw "系统中没有找到 sc.exe,无法注册 Windows 服务。"
}
if (-not (Get-Command icacls.exe -ErrorAction SilentlyContinue)) {
throw "系统中没有找到 icacls.exe,无法设置安全权限。"
}
$source = Resolve-RequiredDirectory -Path $SourceDirectory -Label "安装产物目录"
$install = Resolve-RequiredDirectory -Path $InstallDirectory -Label "程序安装目录"
$data = Resolve-RequiredDirectory -Path $DataDirectory -Label "数据目录"
$certificate = Resolve-RequiredDirectory -Path $CertificateDirectory -Label "证书目录"
$sourceScert = Join-Path $source "scert.exe"
$sourceAdmin = Join-Path $source "scert-admin.exe"
if (-not (Test-Path -LiteralPath $sourceScert -PathType Leaf)) {
throw "安装产物目录缺少 scert.exe:$sourceScert"
}
if (-not (Test-Path -LiteralPath $sourceAdmin -PathType Leaf)) {
throw "安装产物目录缺少 scert-admin.exe:$sourceAdmin"
}
$configDirectory = Join-Path $data "config"
$databaseDirectory = Join-Path $data "data"
$logDirectory = Join-Path $data "logs"
$backupDirectory = Join-Path $data "backups"
$docsDirectory = Join-Path $data "docs"
$runtimeDirectory = Join-Path $data "runtime"
foreach ($directory in @($install, $data, $configDirectory, $databaseDirectory, $logDirectory, $backupDirectory, $docsDirectory, $runtimeDirectory, $certificate)) {
New-Item -ItemType Directory -Path $directory -Force | Out-Null
}
$installedScert = Join-Path $install "scert.exe"
$installedAdmin = Join-Path $install "scert-admin.exe"
Copy-Item -LiteralPath $sourceScert -Destination $installedScert -Force
Copy-Item -LiteralPath $sourceAdmin -Destination $installedAdmin -Force
Set-DirectoryAcl -Path $install -Mode read
foreach ($directory in @($data, $configDirectory, $databaseDirectory, $logDirectory, $backupDirectory, $runtimeDirectory, $certificate)) {
Set-DirectoryAcl -Path $directory -Mode modify
}
Set-DirectoryAcl -Path $docsDirectory -Mode read
$configurationPath = Join-Path $configDirectory "scert.json"
$resetCodePath = Join-Path $runtimeDirectory "reset-code"
$mainBinaryPath = '"{0}" --config "{1}" serve' -f $installedScert, $configurationPath
$adminBinaryPath = '"{0}" --config "{1}" --code-output "{2}"' -f $installedAdmin, $configurationPath, $resetCodePath
Install-OrUpdateService `
-Name $ServiceName `
-DisplayName "Scert SSL 证书管理服务" `
-Description "Scert SSL 证书申请、续期和部署服务" `
-BinaryPath $mainBinaryPath `
-StartMode auto
Install-OrUpdateService `
-Name $AdminServiceName `
-DisplayName "Scert-admin 临时密码重置服务" `
-Description "仅在管理员手动启动时临时激活 Scert 密码重置入口" `
-BinaryPath $adminBinaryPath `
-StartMode demand
$metadata = [ordered]@{
installed_at = [DateTimeOffset]::Now.ToString("o")
install_directory = $install
data_directory = $data
config_directory = $configDirectory
certificate_root = $certificate
service_name = $ServiceName
admin_service_name = $AdminServiceName
service_account = "NT AUTHORITY\LocalService"
}
$metadataPath = Join-Path $configDirectory "installation.json"
$metadata | ConvertTo-Json -Depth 4 | Set-Content -LiteralPath $metadataPath -Encoding UTF8
Write-Host "Scert Windows 文件安装和服务注册完成。"
Write-Host "核心程序:$installedScert"
Write-Host "密码重置程序:$installedAdmin"
Write-Host "配置目录:$configDirectory"
Write-Host "数据目录:$databaseDirectory"
Write-Host "日志目录:$logDirectory"
Write-Host "备份目录:$backupDirectory"
Write-Host "证书目录:$certificate"
Write-Host "安装记录:$metadataPath"
Write-Host "Scert 主服务已注册为自动启动,但本安装器没有启动或重启服务。"
Write-Host "Scert-admin 已注册为手动启动,本安装器不会启动它,也不会创建公网防火墙规则。"
Write-Host "首次初始化时请使用配置文件路径:$configurationPath"