# web-publish v1.4 — 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 --- function Find-ProjectV8Path { $dir = (Get-Location).Path while ($dir) { $pf = Join-Path $dir ".v8-project.json" if (Test-Path $pf) { try { $j = Get-Content $pf -Raw -Encoding UTF8 | ConvertFrom-Json if ($j.v8path) { return [string]$j.v8path } } catch {} return $null } $parent = Split-Path $dir -Parent if (-not $parent -or $parent -eq $dir) { break } $dir = $parent } return $null } if (-not $V8Path) { $V8Path = Find-ProjectV8Path } if (-not $V8Path) { $found = Get-ChildItem @("C:\Program Files\1cv8\*\bin\1cv8.exe", "C:\Program Files (x86)\1cv8\*\bin\1cv8.exe") -ErrorAction SilentlyContinue | Sort-Object { try { [version]$_.Directory.Parent.Name } catch { [version]"0.0" } } -Descending | Select-Object -First 1 if ($found) { $V8Path = Split-Path $found.FullName -Parent Write-Host "Auto-selected platform $($found.Directory.Parent.Name): $V8Path" -ForegroundColor Yellow } 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 = @" "@ $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" AllowOverride All Require all granted SetHandler 1c-application ManagedApplicationDescriptor "$vrdPathFwd" $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//..." -ForegroundColor Cyan Write-Host "Web-сервисы: http://localhost:$Port/$AppName/ws/<Имя>?wsdl" -ForegroundColor Cyan