mirror of
https://github.com/Nikolay-Shirokov/cc-1c-skills.git
synced 2026-07-26 14:41:02 +03:00
Replace hardcoded Apache zip URL with dynamic parsing of the Apache Lounge download page. Finds the latest Win64 build automatically, so the script won't break when new versions are published. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
399 lines
15 KiB
PowerShell
399 lines
15 KiB
PowerShell
# web-publish v1.2 — Publish 1C infobase via Apache
|
||
# Source: https://github.com/Nikolay-Shirokov/cc-1c-skills
|
||
<#
|
||
.SYNOPSIS
|
||
Публикация информационной базы 1С через Apache HTTP Server
|
||
|
||
.DESCRIPTION
|
||
Генерирует default.vrd и настраивает httpd.conf для веб-доступа
|
||
к информационной базе 1С. При необходимости скачивает portable Apache.
|
||
Идемпотентный — повторный вызов обновляет конфигурацию.
|
||
|
||
.PARAMETER V8Path
|
||
Путь к каталогу bin платформы (для wsap24.dll)
|
||
|
||
.PARAMETER InfoBasePath
|
||
Путь к файловой информационной базе
|
||
|
||
.PARAMETER InfoBaseServer
|
||
Сервер 1С (для серверной базы)
|
||
|
||
.PARAMETER InfoBaseRef
|
||
Имя базы на сервере
|
||
|
||
.PARAMETER UserName
|
||
Имя пользователя 1С
|
||
|
||
.PARAMETER Password
|
||
Пароль пользователя
|
||
|
||
.PARAMETER AppName
|
||
Имя публикации (по умолчанию из имени каталога базы)
|
||
|
||
.PARAMETER ApachePath
|
||
Корень Apache (по умолчанию tools\apache24)
|
||
|
||
.PARAMETER Port
|
||
Порт (по умолчанию 8081)
|
||
|
||
.PARAMETER Manual
|
||
Не скачивать Apache — только проверить и дать инструкцию
|
||
|
||
.EXAMPLE
|
||
.\web-publish.ps1 -InfoBasePath "C:\Bases\MyDB"
|
||
|
||
.EXAMPLE
|
||
.\web-publish.ps1 -InfoBasePath "C:\Bases\MyDB" -AppName "mydb" -Port 9090
|
||
|
||
.EXAMPLE
|
||
.\web-publish.ps1 -InfoBaseServer "srv01" -InfoBaseRef "MyDB" -Manual
|
||
#>
|
||
|
||
[CmdletBinding()]
|
||
param(
|
||
[Parameter(Mandatory=$false)]
|
||
[string]$V8Path,
|
||
|
||
[Parameter(Mandatory=$false)]
|
||
[string]$InfoBasePath,
|
||
|
||
[Parameter(Mandatory=$false)]
|
||
[string]$InfoBaseServer,
|
||
|
||
[Parameter(Mandatory=$false)]
|
||
[string]$InfoBaseRef,
|
||
|
||
[Parameter(Mandatory=$false)]
|
||
[string]$UserName,
|
||
|
||
[Parameter(Mandatory=$false)]
|
||
[string]$Password,
|
||
|
||
[Parameter(Mandatory=$false)]
|
||
[string]$AppName,
|
||
|
||
[Parameter(Mandatory=$false)]
|
||
[string]$ApachePath,
|
||
|
||
[Parameter(Mandatory=$false)]
|
||
[int]$Port = 8081,
|
||
|
||
[Parameter(Mandatory=$false)]
|
||
[switch]$Manual
|
||
)
|
||
|
||
# --- Encoding ---
|
||
$OutputEncoding = [System.Text.Encoding]::UTF8
|
||
[Console]::OutputEncoding = [System.Text.Encoding]::UTF8
|
||
|
||
# --- Resolve V8Path ---
|
||
if (-not $V8Path) {
|
||
$found = Get-ChildItem "C:\Program Files\1cv8\*\bin\1cv8.exe" -ErrorAction SilentlyContinue | Sort-Object FullName -Descending | Select-Object -First 1
|
||
if ($found) {
|
||
$V8Path = Split-Path $found.FullName -Parent
|
||
} else {
|
||
Write-Host "Error: платформа 1С не найдена. Укажите -V8Path" -ForegroundColor Red
|
||
exit 1
|
||
}
|
||
} elseif (Test-Path $V8Path -PathType Leaf) {
|
||
$V8Path = Split-Path $V8Path -Parent
|
||
}
|
||
|
||
# Validate wsap24.dll
|
||
$wsapDll = Join-Path $V8Path "wsap24.dll"
|
||
if (-not (Test-Path $wsapDll)) {
|
||
Write-Host "Error: wsap24.dll не найден в $V8Path" -ForegroundColor Red
|
||
exit 1
|
||
}
|
||
|
||
# --- Validate connection ---
|
||
if (-not $InfoBasePath -and (-not $InfoBaseServer -or -not $InfoBaseRef)) {
|
||
Write-Host "Error: укажите -InfoBasePath или -InfoBaseServer + -InfoBaseRef" -ForegroundColor Red
|
||
exit 1
|
||
}
|
||
|
||
# --- Resolve ApachePath ---
|
||
if (-not $ApachePath) {
|
||
$projectRoot = (Get-Item $PSScriptRoot).Parent.Parent.Parent.Parent.FullName
|
||
$ApachePath = Join-Path $projectRoot "tools\apache24"
|
||
}
|
||
# Ensure absolute path (agent may pass relative like "tools/apache24")
|
||
if (-not [System.IO.Path]::IsPathRooted($ApachePath)) {
|
||
$ApachePath = [System.IO.Path]::GetFullPath((Join-Path (Get-Location).Path $ApachePath))
|
||
}
|
||
|
||
# --- Check / Install Apache ---
|
||
$httpdExe = Join-Path (Join-Path $ApachePath "bin") "httpd.exe"
|
||
|
||
if (-not (Test-Path $httpdExe)) {
|
||
if ($Manual) {
|
||
Write-Host "Apache не найден: $ApachePath" -ForegroundColor Yellow
|
||
Write-Host ""
|
||
Write-Host "Установите Apache вручную:" -ForegroundColor Cyan
|
||
Write-Host " 1. Скачайте Apache Lounge (x64) с https://www.apachelounge.com/download/"
|
||
Write-Host " 2. Распакуйте содержимое Apache24\ в: $ApachePath"
|
||
Write-Host " 3. Запустите скрипт повторно"
|
||
exit 1
|
||
}
|
||
|
||
Write-Host "Apache не найден. Скачиваю..." -ForegroundColor Cyan
|
||
$tmpZip = Join-Path $env:TEMP "apache24.zip"
|
||
$tmpDir = Join-Path $env:TEMP "apache24_extract"
|
||
|
||
try {
|
||
[Net.ServicePointManager]::SecurityProtocol = [Net.SecurityProtocolType]::Tls12
|
||
$wc = New-Object System.Net.WebClient
|
||
|
||
# Parse Apache Lounge download page for latest Win64 zip URL
|
||
$downloadPage = "https://www.apachelounge.com/download/"
|
||
Write-Host "Определяю актуальную версию с $downloadPage ..."
|
||
$html = $wc.DownloadString($downloadPage)
|
||
# Links are typically relative (/download/...), try that first
|
||
$match = [regex]::Match($html, '(?i)href="(/download/[^"]*?httpd-[^"]*?Win64[^"]*?\.zip)"')
|
||
if (-not $match.Success) {
|
||
$match = [regex]::Match($html, '(?i)href="(https://[^"]*?httpd-[^"]*?Win64[^"]*?\.zip)"')
|
||
}
|
||
|
||
if ($match.Success) {
|
||
$zipUrl = $match.Groups[1].Value
|
||
if ($zipUrl.StartsWith('/')) {
|
||
$zipUrl = "https://www.apachelounge.com$zipUrl"
|
||
}
|
||
Write-Host "Найдено: $zipUrl" -ForegroundColor Green
|
||
} else {
|
||
Write-Host "Не удалось определить ссылку автоматически." -ForegroundColor Yellow
|
||
Write-Host "Скачайте вручную: $downloadPage" -ForegroundColor Yellow
|
||
exit 1
|
||
}
|
||
|
||
$wc.DownloadFile($zipUrl, $tmpZip)
|
||
} catch {
|
||
Write-Host "Error: не удалось скачать Apache: $_" -ForegroundColor Red
|
||
Write-Host "Скачайте вручную: https://www.apachelounge.com/download/" -ForegroundColor Yellow
|
||
exit 1
|
||
}
|
||
|
||
Write-Host "Распаковка..."
|
||
if (Test-Path $tmpDir) { Remove-Item $tmpDir -Recurse -Force }
|
||
Expand-Archive -Path $tmpZip -DestinationPath $tmpDir -Force
|
||
|
||
# Move Apache24 contents up to ApachePath
|
||
$innerDir = Join-Path $tmpDir "Apache24"
|
||
if (-not (Test-Path $innerDir)) {
|
||
# Try to find Apache24 in nested folder
|
||
$innerDir = Get-ChildItem $tmpDir -Directory -Recurse -Filter "Apache24" | Select-Object -First 1
|
||
if ($innerDir) { $innerDir = $innerDir.FullName } else {
|
||
Write-Host "Error: каталог Apache24 не найден в архиве" -ForegroundColor Red
|
||
exit 1
|
||
}
|
||
}
|
||
|
||
if (-not (Test-Path $ApachePath)) {
|
||
New-Item -ItemType Directory -Path $ApachePath -Force | Out-Null
|
||
}
|
||
Copy-Item -Path (Join-Path $innerDir "*") -Destination $ApachePath -Recurse -Force
|
||
|
||
# Cleanup
|
||
Remove-Item $tmpZip -Force -ErrorAction SilentlyContinue
|
||
Remove-Item $tmpDir -Recurse -Force -ErrorAction SilentlyContinue
|
||
|
||
# Patch ServerRoot in httpd.conf
|
||
$confFile = Join-Path (Join-Path $ApachePath "conf") "httpd.conf"
|
||
if (Test-Path $confFile) {
|
||
$apachePathFwd = $ApachePath -replace '\\','/'
|
||
$confContent = [System.IO.File]::ReadAllText($confFile)
|
||
$confContent = $confContent -replace '(?m)^Define SRVROOT .*$', "Define SRVROOT `"$apachePathFwd`""
|
||
[System.IO.File]::WriteAllText($confFile, $confContent)
|
||
Write-Host "ServerRoot обновлён: $apachePathFwd" -ForegroundColor Green
|
||
}
|
||
|
||
Write-Host "Apache установлен: $ApachePath" -ForegroundColor Green
|
||
}
|
||
|
||
# --- Derive AppName ---
|
||
if (-not $AppName) {
|
||
if ($InfoBasePath) {
|
||
$AppName = (Split-Path $InfoBasePath -Leaf) -replace '[^\w]',''
|
||
} else {
|
||
$AppName = $InfoBaseRef -replace '[^\w]',''
|
||
}
|
||
$AppName = $AppName.ToLower()
|
||
}
|
||
$AppName = $AppName.ToLower()
|
||
|
||
if (-not $AppName) {
|
||
Write-Host "Error: не удалось определить имя публикации. Укажите -AppName" -ForegroundColor Red
|
||
exit 1
|
||
}
|
||
|
||
Write-Host "Публикация: $AppName" -ForegroundColor Cyan
|
||
|
||
# --- Create publish directory ---
|
||
$publishDir = Join-Path (Join-Path $ApachePath "publish") $AppName
|
||
if (-not (Test-Path $publishDir)) {
|
||
New-Item -ItemType Directory -Path $publishDir -Force | Out-Null
|
||
}
|
||
|
||
# --- Generate default.vrd ---
|
||
$vrdPath = Join-Path $publishDir "default.vrd"
|
||
|
||
$ibParts = @()
|
||
if ($InfoBaseServer -and $InfoBaseRef) {
|
||
$ibParts += "Srvr="$InfoBaseServer""
|
||
$ibParts += "Ref="$InfoBaseRef""
|
||
} else {
|
||
$ibParts += "File="$InfoBasePath""
|
||
}
|
||
if ($UserName) { $ibParts += "Usr="$UserName"" }
|
||
if ($Password) { $ibParts += "Pwd="$Password"" }
|
||
$ibString = ($ibParts -join ";") + ";"
|
||
|
||
$vrdContent = @"
|
||
<?xml version="1.0" encoding="UTF-8"?>
|
||
<point xmlns="http://v8.1c.ru/8.2/virtual-resource-system"
|
||
xmlns:xs="http://www.w3.org/2001/XMLSchema"
|
||
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
|
||
base="/$AppName"
|
||
ib="$ibString"
|
||
enableStandardOdata="true">
|
||
<ws pointEnableCommon="true"/>
|
||
<httpServices publishByDefault="true"/>
|
||
</point>
|
||
"@
|
||
|
||
$utf8Bom = New-Object System.Text.UTF8Encoding($true)
|
||
[System.IO.File]::WriteAllText($vrdPath, $vrdContent, $utf8Bom)
|
||
Write-Host "default.vrd: $vrdPath" -ForegroundColor Green
|
||
|
||
# --- Update httpd.conf ---
|
||
$confFile = Join-Path (Join-Path $ApachePath "conf") "httpd.conf"
|
||
if (-not (Test-Path $confFile)) {
|
||
Write-Host "Error: httpd.conf не найден: $confFile" -ForegroundColor Red
|
||
exit 1
|
||
}
|
||
|
||
$confContent = [System.IO.File]::ReadAllText($confFile)
|
||
$apachePathFwd = $ApachePath -replace '\\','/'
|
||
$wsapDllFwd = $wsapDll -replace '\\','/'
|
||
$publishDirFwd = $publishDir -replace '\\','/'
|
||
$vrdPathFwd = $vrdPath -replace '\\','/'
|
||
|
||
# --- Global block (Listen + LoadModule) ---
|
||
$globalMarkerStart = "# --- 1C: global ---"
|
||
$globalMarkerEnd = "# --- End: global ---"
|
||
$globalBlock = @"
|
||
$globalMarkerStart
|
||
Listen $Port
|
||
LoadModule _1cws_module "$wsapDllFwd"
|
||
$globalMarkerEnd
|
||
"@
|
||
|
||
if ($confContent -match [regex]::Escape($globalMarkerStart)) {
|
||
# Replace existing global block
|
||
$pattern = [regex]::Escape($globalMarkerStart) + '[\s\S]*?' + [regex]::Escape($globalMarkerEnd)
|
||
$confContent = [regex]::Replace($confContent, $pattern, $globalBlock)
|
||
} else {
|
||
# Comment out default Listen to avoid port conflict
|
||
$confContent = $confContent -replace '(?m)^(Listen\s+\d+)', '#$1 # commented by web-publish'
|
||
# Append global block
|
||
$confContent = $confContent.TrimEnd() + "`n`n" + $globalBlock + "`n"
|
||
}
|
||
|
||
# --- Publication block ---
|
||
$pubMarkerStart = "# --- 1C Publication: $AppName ---"
|
||
$pubMarkerEnd = "# --- End: $AppName ---"
|
||
$pubBlock = @"
|
||
$pubMarkerStart
|
||
Alias "/$AppName" "$publishDirFwd"
|
||
<Directory "$publishDirFwd">
|
||
AllowOverride All
|
||
Require all granted
|
||
SetHandler 1c-application
|
||
ManagedApplicationDescriptor "$vrdPathFwd"
|
||
</Directory>
|
||
$pubMarkerEnd
|
||
"@
|
||
|
||
if ($confContent -match [regex]::Escape($pubMarkerStart)) {
|
||
# Replace existing publication block
|
||
$pattern = [regex]::Escape($pubMarkerStart) + '[\s\S]*?' + [regex]::Escape($pubMarkerEnd)
|
||
$confContent = [regex]::Replace($confContent, $pattern, $pubBlock)
|
||
} else {
|
||
# Append publication block
|
||
$confContent = $confContent.TrimEnd() + "`n`n" + $pubBlock + "`n"
|
||
}
|
||
|
||
[System.IO.File]::WriteAllText($confFile, $confContent)
|
||
Write-Host "httpd.conf обновлён" -ForegroundColor Green
|
||
|
||
# --- Helper: filter httpd processes by our ApachePath ---
|
||
function Get-OurHttpd {
|
||
$httpdExeNorm = (Resolve-Path $httpdExe -ErrorAction SilentlyContinue).Path
|
||
Get-Process httpd -ErrorAction SilentlyContinue | Where-Object {
|
||
try { $_.Path -eq $httpdExeNorm } catch { $false }
|
||
}
|
||
}
|
||
|
||
# --- Check port availability ---
|
||
$portCheck = Get-NetTCPConnection -LocalPort $Port -ErrorAction SilentlyContinue | Select-Object -First 1
|
||
if ($portCheck) {
|
||
$ourProc = Get-OurHttpd
|
||
if ($ourProc) {
|
||
# Our Apache holds the port — will restart
|
||
} else {
|
||
$holder = Get-Process -Id $portCheck.OwningProcess -ErrorAction SilentlyContinue
|
||
$holderName = if ($holder) { "$($holder.ProcessName) (PID: $($holder.Id))" } else { "PID $($portCheck.OwningProcess)" }
|
||
Write-Host "Error: порт $Port занят процессом $holderName" -ForegroundColor Red
|
||
Write-Host "Укажите другой порт: -Port 9090" -ForegroundColor Yellow
|
||
exit 1
|
||
}
|
||
}
|
||
|
||
# --- Start Apache if not running ---
|
||
$httpdProc = Get-OurHttpd
|
||
if ($httpdProc) {
|
||
Write-Host "Apache уже запущен (PID: $(($httpdProc | Select-Object -First 1).Id))" -ForegroundColor Yellow
|
||
Write-Host "Перезапуск для применения конфигурации..."
|
||
$httpdProc | Stop-Process -Force -ErrorAction SilentlyContinue
|
||
Start-Sleep -Seconds 1
|
||
} else {
|
||
# Check if a foreign httpd holds the port
|
||
$foreignHttpd = Get-Process httpd -ErrorAction SilentlyContinue
|
||
if ($foreignHttpd) {
|
||
Write-Host "[WARN] Обнаружен сторонний Apache (PID: $(($foreignHttpd | Select-Object -First 1).Id))" -ForegroundColor Yellow
|
||
Write-Host " Наш Apache: $httpdExe" -ForegroundColor Yellow
|
||
}
|
||
}
|
||
|
||
Write-Host "Запуск Apache..."
|
||
Start-Process -FilePath $httpdExe -WorkingDirectory $ApachePath -WindowStyle Hidden
|
||
|
||
Start-Sleep -Seconds 2
|
||
|
||
$httpdCheck = Get-OurHttpd
|
||
if ($httpdCheck) {
|
||
Write-Host "Apache запущен (PID: $(($httpdCheck | Select-Object -First 1).Id))" -ForegroundColor Green
|
||
} else {
|
||
Write-Host "Apache не удалось запустить" -ForegroundColor Red
|
||
# Run config test for diagnostics
|
||
$testResult = & $httpdExe -t 2>&1
|
||
if ($testResult) {
|
||
Write-Host "--- httpd -t ---" -ForegroundColor Yellow
|
||
$testResult | ForEach-Object { Write-Host " $_" }
|
||
}
|
||
$errorLog = Join-Path (Join-Path $ApachePath "logs") "error.log"
|
||
if (Test-Path $errorLog) {
|
||
Write-Host "--- error.log (последние 10 строк) ---" -ForegroundColor Yellow
|
||
Get-Content $errorLog -Tail 10
|
||
}
|
||
exit 1
|
||
}
|
||
|
||
# --- Result ---
|
||
Write-Host ""
|
||
Write-Host "=== Публикация готова ===" -ForegroundColor Green
|
||
Write-Host "URL: http://localhost:$Port/$AppName" -ForegroundColor Cyan
|
||
Write-Host "OData: http://localhost:$Port/$AppName/odata/standard.odata" -ForegroundColor Cyan
|
||
Write-Host "HTTP-сервисы: http://localhost:$Port/$AppName/hs/<RootUrl>/..." -ForegroundColor Cyan
|
||
Write-Host "Web-сервисы: http://localhost:$Port/$AppName/ws/<Имя>?wsdl" -ForegroundColor Cyan
|