Auto-build: copilot (python) from 7fa279c

This commit is contained in:
github-actions[bot]
2026-05-17 11:22:33 +00:00
commit bbd2f7a8c1
207 changed files with 98901 additions and 0 deletions
@@ -0,0 +1,398 @@
# 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=&quot;$InfoBaseServer&quot;"
$ibParts += "Ref=&quot;$InfoBaseRef&quot;"
} else {
$ibParts += "File=&quot;$InfoBasePath&quot;"
}
if ($UserName) { $ibParts += "Usr=&quot;$UserName&quot;" }
if ($Password) { $ibParts += "Pwd=&quot;$Password&quot;" }
$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
@@ -0,0 +1,428 @@
#!/usr/bin/env python3
# web-publish v1.2 — Publish 1C infobase via Apache
# Source: https://github.com/Nikolay-Shirokov/cc-1c-skills
"""
Публикация информационной базы 1С через Apache HTTP Server.
Генерирует default.vrd и настраивает httpd.conf для веб-доступа
к информационной базе 1С. При необходимости скачивает portable Apache.
Идемпотентный — повторный вызов обновляет конфигурацию.
"""
import argparse
import glob
import os
import re
import shutil
import subprocess
import sys
import tempfile
import time
import urllib.request
import zipfile
import psutil
def get_our_httpd(httpd_exe_norm):
"""Filter httpd processes by our ApachePath."""
result = []
if not httpd_exe_norm:
return result
for p in psutil.process_iter(['pid', 'name', 'exe']):
try:
if p.info['name'] and 'httpd' in p.info['name'].lower():
if p.info['exe'] and os.path.normcase(os.path.normpath(p.info['exe'])) == httpd_exe_norm:
result.append(p)
except (psutil.NoSuchProcess, psutil.AccessDenied):
pass
return result
def get_all_httpd():
"""Get all httpd processes."""
result = []
for p in psutil.process_iter(['pid', 'name', 'exe']):
try:
if p.info['name'] and 'httpd' in p.info['name'].lower():
result.append(p)
except (psutil.NoSuchProcess, psutil.AccessDenied):
pass
return result
def check_port_in_use(port):
"""Check if a port is in use and return the owning PID, or None."""
for conn in psutil.net_connections(kind='tcp'):
if conn.laddr and conn.laddr.port == port and conn.status == 'LISTEN':
return conn.pid
return None
def main():
sys.stdout.reconfigure(encoding="utf-8")
sys.stderr.reconfigure(encoding="utf-8")
parser = argparse.ArgumentParser(description='Publish 1C infobase via Apache', allow_abbrev=False)
parser.add_argument('-V8Path', type=str, default='', help='Path to 1C platform bin directory (for wsap24.dll)')
parser.add_argument('-InfoBasePath', type=str, default='', help='Path to file infobase')
parser.add_argument('-InfoBaseServer', type=str, default='', help='1C server (for server infobase)')
parser.add_argument('-InfoBaseRef', type=str, default='', help='Infobase name on server')
parser.add_argument('-UserName', type=str, default='', help='1C user name')
parser.add_argument('-Password', type=str, default='', help='1C password')
parser.add_argument('-AppName', type=str, default='', help='Publication name (default: from infobase folder name)')
parser.add_argument('-ApachePath', type=str, default='', help='Apache root (default: tools\\apache24)')
parser.add_argument('-Port', type=int, default=8081, help='Port (default: 8081)')
parser.add_argument('-Manual', action='store_true', help='Do not download Apache — only check and give instructions')
args = parser.parse_args()
# --- Resolve V8Path ---
v8_path = args.V8Path
if not v8_path:
candidates = glob.glob(r'C:\Program Files\1cv8\*\bin\1cv8.exe')
candidates.sort(reverse=True)
if candidates:
v8_path = os.path.dirname(candidates[0])
else:
print('Error: платформа 1С не найдена. Укажите -V8Path', file=sys.stderr)
sys.exit(1)
elif os.path.isfile(v8_path):
v8_path = os.path.dirname(v8_path)
# Validate wsap24.dll
wsap_dll = os.path.join(v8_path, 'wsap24.dll')
if not os.path.exists(wsap_dll):
print(f'Error: wsap24.dll не найден в {v8_path}', file=sys.stderr)
sys.exit(1)
# --- Validate connection ---
if not args.InfoBasePath and (not args.InfoBaseServer or not args.InfoBaseRef):
print('Error: укажите -InfoBasePath или -InfoBaseServer + -InfoBaseRef', file=sys.stderr)
sys.exit(1)
# --- Resolve ApachePath ---
apache_path = args.ApachePath
if not apache_path:
script_dir = os.path.dirname(os.path.abspath(__file__))
project_root = os.path.dirname(os.path.dirname(os.path.dirname(os.path.dirname(script_dir))))
apache_path = os.path.join(project_root, 'tools', 'apache24')
# Ensure absolute path (agent may pass relative like "tools/apache24")
if not os.path.isabs(apache_path):
apache_path = os.path.abspath(apache_path)
port = args.Port
# --- Check / Install Apache ---
httpd_exe = os.path.join(apache_path, 'bin', 'httpd.exe')
if not os.path.exists(httpd_exe):
if args.Manual:
print(f'Apache не найден: {apache_path}')
print('')
print('Установите Apache вручную:')
print(' 1. Скачайте Apache Lounge (x64) с https://www.apachelounge.com/download/')
print(f' 2. Распакуйте содержимое Apache24\\ в: {apache_path}')
print(' 3. Запустите скрипт повторно')
sys.exit(1)
print('Apache не найден. Скачиваю...')
tmp_zip = os.path.join(tempfile.gettempdir(), 'apache24.zip')
tmp_dir = os.path.join(tempfile.gettempdir(), 'apache24_extract')
try:
# Parse Apache Lounge download page for latest Win64 zip URL
download_page = 'https://www.apachelounge.com/download/'
print(f'Определяю актуальную версию с {download_page} ...')
req = urllib.request.Request(download_page, headers={
'User-Agent': 'Mozilla/5.0',
})
with urllib.request.urlopen(req, timeout=30) as resp:
html = resp.read().decode('utf-8', errors='replace')
# Links are typically relative (/download/...), try that first
m = re.search(r'(?i)href="(/download/[^"]*?httpd-[^"]*?Win64[^"]*?\.zip)"', html)
if not m:
m = re.search(r'(?i)href="(https://[^"]*?httpd-[^"]*?Win64[^"]*?\.zip)"', html)
if m:
zip_url = m.group(1)
if zip_url.startswith('/'):
zip_url = f'https://www.apachelounge.com{zip_url}'
print(f'Найдено: {zip_url}')
else:
print('Не удалось определить ссылку автоматически.', file=sys.stderr)
print(f'Скачайте вручную: {download_page}')
sys.exit(1)
urllib.request.urlretrieve(zip_url, tmp_zip)
except SystemExit:
raise
except Exception as e:
print(f'Error: не удалось скачать Apache: {e}', file=sys.stderr)
print('Скачайте вручную: https://www.apachelounge.com/download/')
sys.exit(1)
print('Распаковка...')
if os.path.exists(tmp_dir):
shutil.rmtree(tmp_dir, ignore_errors=True)
with zipfile.ZipFile(tmp_zip, 'r') as zf:
zf.extractall(tmp_dir)
# Move Apache24 contents up to ApachePath
inner_dir = os.path.join(tmp_dir, 'Apache24')
if not os.path.isdir(inner_dir):
# Try to find Apache24 in nested folder
found_inner = None
for root, dirs, files in os.walk(tmp_dir):
if 'Apache24' in dirs:
found_inner = os.path.join(root, 'Apache24')
break
if found_inner:
inner_dir = found_inner
else:
print('Error: каталог Apache24 не найден в архиве', file=sys.stderr)
sys.exit(1)
os.makedirs(apache_path, exist_ok=True)
# Copy contents of inner_dir to apache_path
for item in os.listdir(inner_dir):
src = os.path.join(inner_dir, item)
dst = os.path.join(apache_path, item)
if os.path.isdir(src):
if os.path.exists(dst):
shutil.rmtree(dst)
shutil.copytree(src, dst)
else:
shutil.copy2(src, dst)
# Cleanup
try:
os.remove(tmp_zip)
except OSError:
pass
try:
shutil.rmtree(tmp_dir, ignore_errors=True)
except OSError:
pass
# Patch ServerRoot in httpd.conf
conf_file = os.path.join(apache_path, 'conf', 'httpd.conf')
if os.path.exists(conf_file):
apache_path_fwd = apache_path.replace('\\', '/')
with open(conf_file, 'r', encoding='utf-8-sig') as f:
conf_content = f.read()
conf_content = re.sub(
r'(?m)^Define SRVROOT .*$',
f'Define SRVROOT "{apache_path_fwd}"',
conf_content,
)
with open(conf_file, 'w', encoding='utf-8') as f:
f.write(conf_content)
print(f'ServerRoot обновлён: {apache_path_fwd}')
print(f'Apache установлен: {apache_path}')
# --- Derive AppName ---
app_name = args.AppName
if not app_name:
if args.InfoBasePath:
app_name = re.sub(r'[^\w]', '', os.path.basename(args.InfoBasePath))
else:
app_name = re.sub(r'[^\w]', '', args.InfoBaseRef)
app_name = app_name.lower()
app_name = app_name.lower()
if not app_name:
print('Error: не удалось определить имя публикации. Укажите -AppName', file=sys.stderr)
sys.exit(1)
print(f'Публикация: {app_name}')
# --- Create publish directory ---
publish_dir = os.path.join(apache_path, 'publish', app_name)
os.makedirs(publish_dir, exist_ok=True)
# --- Generate default.vrd ---
vrd_path = os.path.join(publish_dir, 'default.vrd')
ib_parts = []
if args.InfoBaseServer and args.InfoBaseRef:
ib_parts.append(f'Srvr=&quot;{args.InfoBaseServer}&quot;')
ib_parts.append(f'Ref=&quot;{args.InfoBaseRef}&quot;')
else:
ib_parts.append(f'File=&quot;{args.InfoBasePath}&quot;')
if args.UserName:
ib_parts.append(f'Usr=&quot;{args.UserName}&quot;')
if args.Password:
ib_parts.append(f'Pwd=&quot;{args.Password}&quot;')
ib_string = ';'.join(ib_parts) + ';'
vrd_content = f'''<?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="/{app_name}"
ib="{ib_string}"
enableStandardOdata="true">
<ws pointEnableCommon="true"/>
<httpServices publishByDefault="true"/>
</point>'''
with open(vrd_path, 'wb') as f:
f.write(b'\xef\xbb\xbf')
f.write(vrd_content.encode('utf-8'))
print(f'default.vrd: {vrd_path}')
# --- Update httpd.conf ---
conf_file = os.path.join(apache_path, 'conf', 'httpd.conf')
if not os.path.exists(conf_file):
print(f'Error: httpd.conf не найден: {conf_file}', file=sys.stderr)
sys.exit(1)
with open(conf_file, 'r', encoding='utf-8-sig') as f:
conf_content = f.read()
apache_path_fwd = apache_path.replace('\\', '/')
wsap_dll_fwd = wsap_dll.replace('\\', '/')
publish_dir_fwd = publish_dir.replace('\\', '/')
vrd_path_fwd = vrd_path.replace('\\', '/')
# --- Global block (Listen + LoadModule) ---
global_marker_start = '# --- 1C: global ---'
global_marker_end = '# --- End: global ---'
global_block = (
f'{global_marker_start}\n'
f'Listen {port}\n'
f'LoadModule _1cws_module "{wsap_dll_fwd}"\n'
f'{global_marker_end}'
)
if re.search(re.escape(global_marker_start), conf_content):
# Replace existing global block
pattern = re.escape(global_marker_start) + r'[\s\S]*?' + re.escape(global_marker_end)
conf_content = re.sub(pattern, global_block, conf_content)
else:
# Comment out default Listen to avoid port conflict
conf_content = re.sub(r'(?m)^(Listen\s+\d+)', r'#\1 # commented by web-publish', conf_content)
# Append global block
conf_content = conf_content.rstrip() + '\n\n' + global_block + '\n'
# --- Publication block ---
pub_marker_start = f'# --- 1C Publication: {app_name} ---'
pub_marker_end = f'# --- End: {app_name} ---'
pub_block = (
f'{pub_marker_start}\n'
f'Alias "/{app_name}" "{publish_dir_fwd}"\n'
f'<Directory "{publish_dir_fwd}">\n'
f' AllowOverride All\n'
f' Require all granted\n'
f' SetHandler 1c-application\n'
f' ManagedApplicationDescriptor "{vrd_path_fwd}"\n'
f'</Directory>\n'
f'{pub_marker_end}'
)
if re.search(re.escape(pub_marker_start), conf_content):
# Replace existing publication block
pattern = re.escape(pub_marker_start) + r'[\s\S]*?' + re.escape(pub_marker_end)
conf_content = re.sub(pattern, pub_block, conf_content)
else:
# Append publication block
conf_content = conf_content.rstrip() + '\n\n' + pub_block + '\n'
with open(conf_file, 'w', encoding='utf-8') as f:
f.write(conf_content)
print('httpd.conf обновлён')
# --- Normalize httpd_exe for process matching ---
if os.path.exists(httpd_exe):
httpd_exe_norm = os.path.normcase(os.path.normpath(os.path.realpath(httpd_exe)))
else:
httpd_exe_norm = os.path.normcase(os.path.normpath(httpd_exe))
# --- Check port availability ---
holder_pid = check_port_in_use(port)
if holder_pid:
our_proc = get_our_httpd(httpd_exe_norm)
if not our_proc:
# Port is held by someone else
try:
holder_proc = psutil.Process(holder_pid)
holder_name = f'{holder_proc.name()} (PID: {holder_pid})'
except (psutil.NoSuchProcess, psutil.AccessDenied):
holder_name = f'PID {holder_pid}'
print(f'Error: порт {port} занят процессом {holder_name}', file=sys.stderr)
print('Укажите другой порт: -Port 9090')
sys.exit(1)
# --- Start Apache if not running ---
httpd_proc = get_our_httpd(httpd_exe_norm)
if httpd_proc:
first_pid = httpd_proc[0].pid
print(f'Apache уже запущен (PID: {first_pid})')
print('Перезапуск для применения конфигурации...')
for p in httpd_proc:
try:
p.kill()
except (psutil.NoSuchProcess, psutil.AccessDenied):
pass
time.sleep(1)
else:
# Check if a foreign httpd holds the port
foreign_httpd = get_all_httpd()
if foreign_httpd:
print(f'[WARN] Обнаружен сторонний Apache (PID: {foreign_httpd[0].pid})')
print(f' Наш Apache: {httpd_exe}')
print('Запуск Apache...')
subprocess.Popen(
[httpd_exe],
cwd=apache_path,
creationflags=subprocess.CREATE_NO_WINDOW,
)
time.sleep(2)
httpd_check = get_our_httpd(httpd_exe_norm)
if httpd_check:
print(f'Apache запущен (PID: {httpd_check[0].pid})')
else:
print('Apache не удалось запустить', file=sys.stderr)
# Run config test for diagnostics
try:
result = subprocess.run(
[httpd_exe, '-t'],
capture_output=True,
text=True,
timeout=10,
)
test_output = (result.stdout + result.stderr).strip()
if test_output:
print('--- httpd -t ---')
for line in test_output.splitlines():
print(f' {line}')
except Exception:
pass
error_log = os.path.join(apache_path, 'logs', 'error.log')
if os.path.exists(error_log):
print('--- error.log (последние 10 строк) ---')
try:
with open(error_log, 'r', encoding='utf-8-sig', errors='replace') as f:
all_lines = f.readlines()
for line in all_lines[-10:]:
print(line.rstrip())
except Exception:
pass
sys.exit(1)
# --- Result ---
print('')
print('=== Публикация готова ===')
print(f'URL: http://localhost:{port}/{app_name}')
print(f'OData: http://localhost:{port}/{app_name}/odata/standard.odata')
print(f'HTTP-сервисы: http://localhost:{port}/{app_name}/hs/<RootUrl>/...')
print(f'Web-сервисы: http://localhost:{port}/{app_name}/ws/<Имя>?wsdl')
if __name__ == '__main__':
main()