From bc4778d6cc94490bd017535552d231ddb5dd2920 Mon Sep 17 00:00:00 2001 From: Nick Shirokov Date: Sun, 22 Feb 2026 16:28:22 +0300 Subject: [PATCH 01/11] feat(web): add web publishing skills (web-publish, web-info, web-stop, web-unpublish) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Phase 1 of web publishing: publish 1C infobases via portable Apache HTTP Server. Closes the feedback loop: edit → load → update → open in browser. Co-Authored-By: Claude Opus 4.6 --- .claude/skills/web-info/SKILL.md | 62 ++++ .claude/skills/web-info/scripts/web-info.ps1 | 127 +++++++ .claude/skills/web-publish/SKILL.md | 79 +++++ .../web-publish/scripts/web-publish.ps1 | 328 ++++++++++++++++++ .claude/skills/web-stop/SKILL.md | 46 +++ .claude/skills/web-stop/scripts/web-stop.ps1 | 75 ++++ .claude/skills/web-unpublish/SKILL.md | 51 +++ .../web-unpublish/scripts/web-unpublish.ps1 | 109 ++++++ docs/web-guide.md | 117 +++++++ docs/web-spec.md | 184 ++++++++++ 10 files changed, 1178 insertions(+) create mode 100644 .claude/skills/web-info/SKILL.md create mode 100644 .claude/skills/web-info/scripts/web-info.ps1 create mode 100644 .claude/skills/web-publish/SKILL.md create mode 100644 .claude/skills/web-publish/scripts/web-publish.ps1 create mode 100644 .claude/skills/web-stop/SKILL.md create mode 100644 .claude/skills/web-stop/scripts/web-stop.ps1 create mode 100644 .claude/skills/web-unpublish/SKILL.md create mode 100644 .claude/skills/web-unpublish/scripts/web-unpublish.ps1 create mode 100644 docs/web-guide.md create mode 100644 docs/web-spec.md diff --git a/.claude/skills/web-info/SKILL.md b/.claude/skills/web-info/SKILL.md new file mode 100644 index 00000000..51382845 --- /dev/null +++ b/.claude/skills/web-info/SKILL.md @@ -0,0 +1,62 @@ +--- +name: web-info +description: Статус веб-публикации 1С — Apache, опубликованные базы, ошибки. Используй когда пользователь спрашивает про статус веб-сервера, опубликованные базы, работает ли Apache +argument-hint: "" +allowed-tools: + - Bash + - Read + - Glob +--- + +# /web-info — Статус Apache и публикаций 1С + +Показывает состояние Apache HTTP Server, список опубликованных баз и последние ошибки. + +## Usage + +``` +/web-info +``` + +## Параметры подключения + +Прочитай `.v8-project.json` из корня проекта. Если задан `webPath` — используй как `-ApachePath`. +По умолчанию `tools/apache24` от корня проекта. + +## Команда + +```powershell +powershell.exe -NoProfile -File .claude/skills/web-info/scripts/web-info.ps1 <параметры> +``` + +### Параметры скрипта + +| Параметр | Обязательный | Описание | +|----------|:------------:|----------| +| `-ApachePath <путь>` | нет | Корень Apache (по умолчанию `tools/apache24`) | + +## Формат вывода + +``` +=== Apache Web Server === +Status: Запущен (PID: 12345) +Path: C:\...\tools\apache24 +Port: 8080 +Module: C:/Program Files/1cv8/8.3.24.1691/bin/wsap24.dll + +=== Опубликованные базы === + mydb http://localhost:8080/mydb File="C:\Bases\MyDB"; + +=== Последние ошибки === +(пусто) +``` + +## Примеры + +```powershell +# Статус по умолчанию +powershell.exe -NoProfile -File .claude/skills/web-info/scripts/web-info.ps1 + +# Указать путь к Apache +powershell.exe -NoProfile -File .claude/skills/web-info/scripts/web-info.ps1 -ApachePath "C:\tools\apache24" +``` diff --git a/.claude/skills/web-info/scripts/web-info.ps1 b/.claude/skills/web-info/scripts/web-info.ps1 new file mode 100644 index 00000000..75f8efdb --- /dev/null +++ b/.claude/skills/web-info/scripts/web-info.ps1 @@ -0,0 +1,127 @@ +# web-info v1.0 — Apache & 1C publication status +# Source: https://github.com/Nikolay-Shirokov/cc-1c-skills +<# +.SYNOPSIS + Статус Apache и публикаций 1С + +.DESCRIPTION + Показывает состояние Apache HTTP Server, список опубликованных баз + и последние ошибки из error.log. + +.PARAMETER ApachePath + Корень Apache (по умолчанию tools\apache24) + +.EXAMPLE + .\web-info.ps1 + +.EXAMPLE + .\web-info.ps1 -ApachePath "C:\tools\apache24" +#> + +[CmdletBinding()] +param( + [Parameter(Mandatory=$false)] + [string]$ApachePath +) + +$OutputEncoding = [System.Text.Encoding]::UTF8 +[Console]::OutputEncoding = [System.Text.Encoding]::UTF8 + +# --- Resolve ApachePath --- +if (-not $ApachePath) { + $projectRoot = (Get-Item $PSScriptRoot).Parent.Parent.Parent.Parent.FullName + $ApachePath = Join-Path $projectRoot "tools\apache24" +} + +# --- Check Apache installation --- +$httpdExe = Join-Path (Join-Path $ApachePath "bin") "httpd.exe" + +Write-Host "=== Apache Web Server ===" -ForegroundColor Cyan + +if (-not (Test-Path $httpdExe)) { + Write-Host "Status: Не установлен" -ForegroundColor Red + Write-Host "Path: $ApachePath (не найден)" + Write-Host "" + Write-Host "Используйте /web-publish для установки Apache." -ForegroundColor Yellow + exit 0 +} + +# --- Check process --- +$httpdProc = Get-Process httpd -ErrorAction SilentlyContinue +if ($httpdProc) { + $pids = ($httpdProc | ForEach-Object { $_.Id }) -join ", " + Write-Host "Status: Запущен (PID: $pids)" -ForegroundColor Green +} else { + Write-Host "Status: Остановлен" -ForegroundColor Yellow +} + +Write-Host "Path: $ApachePath" + +# --- Parse httpd.conf --- +$confFile = Join-Path (Join-Path $ApachePath "conf") "httpd.conf" +if (-not (Test-Path $confFile)) { + Write-Host "Config: httpd.conf не найден" -ForegroundColor Red + exit 0 +} + +$confContent = [System.IO.File]::ReadAllText($confFile) + +# Extract port from global block +$port = "—" +if ($confContent -match '(?m)^Listen\s+(\d+)') { + $port = $Matches[1] +} +Write-Host "Port: $port" + +# Extract wsap24 path +if ($confContent -match 'LoadModule\s+_1cws_module\s+"([^"]+)"') { + Write-Host "Module: $($Matches[1])" +} + +# --- Publications --- +Write-Host "" +Write-Host "=== Опубликованные базы ===" -ForegroundColor Cyan + +$pubPattern = '# --- 1C Publication: (.+?) ---' +$pubMatches = [regex]::Matches($confContent, $pubPattern) + +if ($pubMatches.Count -eq 0) { + Write-Host "(нет публикаций)" -ForegroundColor Yellow +} else { + foreach ($match in $pubMatches) { + $appName = $match.Groups[1].Value + + # Read default.vrd for this publication + $vrdPath = Join-Path (Join-Path (Join-Path $ApachePath "publish") $appName) "default.vrd" + $ibInfo = "—" + if (Test-Path $vrdPath) { + $vrdContent = [System.IO.File]::ReadAllText($vrdPath) + if ($vrdContent -match 'ib="([^"]*)"') { + $ibInfo = $Matches[1] -replace '"','"' + } + } + + $url = "http://localhost:$port/$appName" + Write-Host " $appName" -ForegroundColor White -NoNewline + Write-Host " $url" -ForegroundColor Gray -NoNewline + Write-Host " $ibInfo" -ForegroundColor DarkGray + } +} + +# --- Error log --- +Write-Host "" +Write-Host "=== Последние ошибки ===" -ForegroundColor Cyan + +$errorLog = Join-Path (Join-Path $ApachePath "logs") "error.log" +if (Test-Path $errorLog) { + $lines = Get-Content $errorLog -Tail 5 -ErrorAction SilentlyContinue + if ($lines -and $lines.Count -gt 0) { + foreach ($line in $lines) { + Write-Host " $line" -ForegroundColor DarkGray + } + } else { + Write-Host "(пусто)" -ForegroundColor Green + } +} else { + Write-Host "(нет файла)" -ForegroundColor Green +} diff --git a/.claude/skills/web-publish/SKILL.md b/.claude/skills/web-publish/SKILL.md new file mode 100644 index 00000000..fac54379 --- /dev/null +++ b/.claude/skills/web-publish/SKILL.md @@ -0,0 +1,79 @@ +--- +name: web-publish +description: Публикация информационной базы 1С через Apache. Используй когда пользователь просит опубликовать базу, настроить веб-доступ, веб-клиент, открыть в браузере +argument-hint: "[database]" +allowed-tools: + - Bash + - Read + - Glob + - AskUserQuestion +--- + +# /web-publish — Публикация 1С через Apache + +Генерирует `default.vrd`, настраивает `httpd.conf` и запускает Apache HTTP Server для веб-доступа к информационной базе. При необходимости скачивает portable Apache. Идемпотентный — повторный вызов обновляет конфигурацию. + +## Usage + +``` +/web-publish [database] +/web-publish dev +/web-publish dev --manual +/web-publish dev --port 9090 +``` + +## Параметры подключения + +Прочитай `.v8-project.json` из корня проекта. Возьми `v8path` (путь к платформе) и разреши базу: +1. Если пользователь указал параметры подключения (путь, сервер) — используй напрямую +2. Если указал базу по имени — ищи по id / alias / name в `.v8-project.json` +3. Если не указал — сопоставь текущую ветку Git с `databases[].branches` +4. Если ветка не совпала — используй `default` +Если `v8path` не задан — автоопределение. +Если в `.v8-project.json` задан `webPath` — используй как `-ApachePath`. +Если файла нет — предложи `/db-list add`. + +## Команда + +```powershell +powershell.exe -NoProfile -File .claude/skills/web-publish/scripts/web-publish.ps1 <параметры> +``` + +### Параметры скрипта + +| Параметр | Обязательный | Описание | +|----------|:------------:|----------| +| `-V8Path <путь>` | нет | Каталог bin платформы (для wsap24.dll) | +| `-InfoBasePath <путь>` | * | Файловая база | +| `-InfoBaseServer <сервер>` | * | Сервер 1С (для серверной базы) | +| `-InfoBaseRef <имя>` | * | Имя базы на сервере | +| `-UserName <имя>` | нет | Имя пользователя | +| `-Password <пароль>` | нет | Пароль | +| `-AppName <имя>` | нет | Имя публикации (по умолчанию из имени каталога базы) | +| `-ApachePath <путь>` | нет | Корень Apache (по умолчанию `tools/apache24`) | +| `-Port <порт>` | нет | Порт (по умолчанию `8080`) | +| `-Manual` | нет | Не скачивать — только проверить и дать инструкцию | + +> `*` — нужен либо `-InfoBasePath`, либо пара `-InfoBaseServer` + `-InfoBaseRef` + +## После выполнения + +1. Сообщи URL: `http://localhost:{Port}/{AppName}` +2. Предложи открыть в браузере +3. Если база не зарегистрирована — предложи `/db-list add` + +## Примеры + +```powershell +# Файловая база +powershell.exe -NoProfile -File .claude/skills/web-publish/scripts/web-publish.ps1 -InfoBasePath "C:\Bases\MyDB" -UserName "Admin" + +# С явным именем публикации и портом +powershell.exe -NoProfile -File .claude/skills/web-publish/scripts/web-publish.ps1 -InfoBasePath "C:\Bases\MyDB" -AppName "mydb" -Port 9090 + +# Серверная база +powershell.exe -NoProfile -File .claude/skills/web-publish/scripts/web-publish.ps1 -InfoBaseServer "srv01" -InfoBaseRef "MyDB" -UserName "Admin" -Password "secret" + +# Ручной режим (только инструкция) +powershell.exe -NoProfile -File .claude/skills/web-publish/scripts/web-publish.ps1 -InfoBasePath "C:\Bases\MyDB" -Manual +``` diff --git a/.claude/skills/web-publish/scripts/web-publish.ps1 b/.claude/skills/web-publish/scripts/web-publish.ps1 new file mode 100644 index 00000000..eac97545 --- /dev/null +++ b/.claude/skills/web-publish/scripts/web-publish.ps1 @@ -0,0 +1,328 @@ +# web-publish v1.0 — 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 + Порт (по умолчанию 8080) + +.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 = 8080, + + [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" +} + +# --- 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 VS17) с https://www.apachelounge.com/download/" + Write-Host " 2. Распакуйте содержимое Apache24\ в: $ApachePath" + Write-Host " 3. Запустите скрипт повторно" + exit 1 + } + + Write-Host "Apache не найден. Скачиваю..." -ForegroundColor Cyan + $zipUrl = "https://www.apachelounge.com/download/VS17/binaries/httpd-2.4.62-240904-win64-VS17.zip" + $tmpZip = Join-Path $env:TEMP "apache24.zip" + $tmpDir = Join-Path $env:TEMP "apache24_extract" + + try { + [Net.ServicePointManager]::SecurityProtocol = [Net.SecurityProtocolType]::Tls12 + Invoke-WebRequest -Uri $zipUrl -OutFile $tmpZip -UseBasicParsing + } catch { + Write-Host "Error: не удалось скачать Apache: $_" -ForegroundColor Red + Write-Host "Скачайте вручную: $zipUrl" -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 { + # 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 + +# --- Start Apache if not running --- +$httpdProc = Get-Process httpd -ErrorAction SilentlyContinue +if ($httpdProc) { + Write-Host "Apache уже запущен (PID: $(($httpdProc | Select-Object -First 1).Id))" -ForegroundColor Yellow + Write-Host "Перезапуск для применения конфигурации..." + # Portable Apache: stop + start + $httpdProc | Stop-Process -Force -ErrorAction SilentlyContinue + Start-Sleep -Seconds 1 +} + +Write-Host "Запуск Apache..." +Start-Process -FilePath $httpdExe -WorkingDirectory $ApachePath -WindowStyle Hidden + +Start-Sleep -Seconds 2 + +$httpdCheck = Get-Process httpd -ErrorAction SilentlyContinue +if ($httpdCheck) { + Write-Host "Apache запущен (PID: $(($httpdCheck | Select-Object -First 1).Id))" -ForegroundColor Green +} else { + Write-Host "Apache не удалось запустить" -ForegroundColor Red + $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 diff --git a/.claude/skills/web-stop/SKILL.md b/.claude/skills/web-stop/SKILL.md new file mode 100644 index 00000000..69b0b8f0 --- /dev/null +++ b/.claude/skills/web-stop/SKILL.md @@ -0,0 +1,46 @@ +--- +name: web-stop +description: Остановка Apache HTTP Server. Используй когда пользователь просит остановить веб-сервер, Apache, прекратить веб-публикацию +argument-hint: "" +allowed-tools: + - Bash + - Read + - Glob +--- + +# /web-stop — Остановка Apache + +Останавливает Apache HTTP Server. Публикации сохраняются — при следующем `/web-publish` сервер запустится снова. + +## Usage + +``` +/web-stop +``` + +## Параметры подключения + +Прочитай `.v8-project.json` из корня проекта. Если задан `webPath` — используй как `-ApachePath`. +По умолчанию `tools/apache24` от корня проекта. + +## Команда + +```powershell +powershell.exe -NoProfile -File .claude/skills/web-stop/scripts/web-stop.ps1 <параметры> +``` + +### Параметры скрипта + +| Параметр | Обязательный | Описание | +|----------|:------------:|----------| +| `-ApachePath <путь>` | нет | Корень Apache (по умолчанию `tools/apache24`) | + +## Примеры + +```powershell +# Остановить Apache +powershell.exe -NoProfile -File .claude/skills/web-stop/scripts/web-stop.ps1 + +# С указанием пути +powershell.exe -NoProfile -File .claude/skills/web-stop/scripts/web-stop.ps1 -ApachePath "C:\tools\apache24" +``` diff --git a/.claude/skills/web-stop/scripts/web-stop.ps1 b/.claude/skills/web-stop/scripts/web-stop.ps1 new file mode 100644 index 00000000..bf5aa66e --- /dev/null +++ b/.claude/skills/web-stop/scripts/web-stop.ps1 @@ -0,0 +1,75 @@ +# web-stop v1.0 — Stop Apache HTTP Server +# Source: https://github.com/Nikolay-Shirokov/cc-1c-skills +<# +.SYNOPSIS + Остановка Apache HTTP Server + +.DESCRIPTION + Останавливает Apache HTTP Server. Сначала пытается graceful shutdown, + при неудаче — принудительная остановка. + +.PARAMETER ApachePath + Корень Apache (по умолчанию tools\apache24) + +.EXAMPLE + .\web-stop.ps1 + +.EXAMPLE + .\web-stop.ps1 -ApachePath "C:\tools\apache24" +#> + +[CmdletBinding()] +param( + [Parameter(Mandatory=$false)] + [string]$ApachePath +) + +$OutputEncoding = [System.Text.Encoding]::UTF8 +[Console]::OutputEncoding = [System.Text.Encoding]::UTF8 + +# --- Resolve ApachePath --- +if (-not $ApachePath) { + $projectRoot = (Get-Item $PSScriptRoot).Parent.Parent.Parent.Parent.FullName + $ApachePath = Join-Path $projectRoot "tools\apache24" +} + +# --- Check process --- +$httpdProc = Get-Process httpd -ErrorAction SilentlyContinue +if (-not $httpdProc) { + Write-Host "Apache не запущен" -ForegroundColor Yellow + exit 0 +} + +$pids = ($httpdProc | ForEach-Object { $_.Id }) -join ", " +Write-Host "Останавливаю Apache (PID: $pids)..." + +# --- Stop processes --- +$httpdProc | Stop-Process -Force -ErrorAction SilentlyContinue + +# --- Wait for shutdown --- +$maxWait = 5 +$elapsed = 0 +while ($elapsed -lt $maxWait) { + Start-Sleep -Seconds 1 + $elapsed++ + $check = Get-Process httpd -ErrorAction SilentlyContinue + if (-not $check) { + Write-Host "Apache остановлен" -ForegroundColor Green + exit 0 + } +} + +# --- Fallback: force kill --- +$remaining = Get-Process httpd -ErrorAction SilentlyContinue +if ($remaining) { + Write-Host "Принудительная остановка..." -ForegroundColor Yellow + $remaining | Stop-Process -Force -ErrorAction SilentlyContinue + Start-Sleep -Seconds 1 + $final = Get-Process httpd -ErrorAction SilentlyContinue + if ($final) { + Write-Host "Error: не удалось остановить Apache" -ForegroundColor Red + exit 1 + } +} + +Write-Host "Apache остановлен" -ForegroundColor Green diff --git a/.claude/skills/web-unpublish/SKILL.md b/.claude/skills/web-unpublish/SKILL.md new file mode 100644 index 00000000..677ad0fb --- /dev/null +++ b/.claude/skills/web-unpublish/SKILL.md @@ -0,0 +1,51 @@ +--- +name: web-unpublish +description: Удаление веб-публикации 1С. Используй когда пользователь просит убрать публикацию, удалить веб-доступ к базе +argument-hint: "" +allowed-tools: + - Bash + - Read + - Glob + - AskUserQuestion +--- + +# /web-unpublish — Удаление публикации 1С + +Удаляет публикацию из httpd.conf и каталог `publish/{appname}`. Если других публикаций не осталось — удаляет глобальный блок 1C и останавливает Apache. + +## Usage + +``` +/web-unpublish +/web-unpublish bpdemo +``` + +## Параметры подключения + +Прочитай `.v8-project.json` из корня проекта. Если задан `webPath` — используй как `-ApachePath`. +По умолчанию `tools/apache24` от корня проекта. + +Если пользователь не указал `appname`, выполни `/web-info` чтобы показать список публикаций и спроси какую удалить. + +## Команда + +```powershell +powershell.exe -NoProfile -File .claude/skills/web-unpublish/scripts/web-unpublish.ps1 <параметры> +``` + +### Параметры скрипта + +| Параметр | Обязательный | Описание | +|----------|:------------:|----------| +| `-AppName <имя>` | да | Имя публикации | +| `-ApachePath <путь>` | нет | Корень Apache (по умолчанию `tools/apache24`) | + +## Примеры + +```powershell +# Удалить публикацию +powershell.exe -NoProfile -File .claude/skills/web-unpublish/scripts/web-unpublish.ps1 -AppName "bpdemo" + +# С указанием пути +powershell.exe -NoProfile -File .claude/skills/web-unpublish/scripts/web-unpublish.ps1 -AppName "mydb" -ApachePath "C:\tools\apache24" +``` diff --git a/.claude/skills/web-unpublish/scripts/web-unpublish.ps1 b/.claude/skills/web-unpublish/scripts/web-unpublish.ps1 new file mode 100644 index 00000000..c4265596 --- /dev/null +++ b/.claude/skills/web-unpublish/scripts/web-unpublish.ps1 @@ -0,0 +1,109 @@ +# web-unpublish v1.0 — Remove 1C web publication +# Source: https://github.com/Nikolay-Shirokov/cc-1c-skills +<# +.SYNOPSIS + Удаление публикации 1С из Apache + +.DESCRIPTION + Удаляет маркерный блок из httpd.conf и каталог публикации. + Если Apache запущен — перезапускает для применения. + +.PARAMETER AppName + Имя публикации (обязательный) + +.PARAMETER ApachePath + Корень Apache (по умолчанию tools\apache24) + +.EXAMPLE + .\web-unpublish.ps1 -AppName "mydb" + +.EXAMPLE + .\web-unpublish.ps1 -AppName "bpdemo" -ApachePath "C:\tools\apache24" +#> + +[CmdletBinding()] +param( + [Parameter(Mandatory=$true)] + [string]$AppName, + + [Parameter(Mandatory=$false)] + [string]$ApachePath +) + +$OutputEncoding = [System.Text.Encoding]::UTF8 +[Console]::OutputEncoding = [System.Text.Encoding]::UTF8 + +# --- Resolve ApachePath --- +if (-not $ApachePath) { + $projectRoot = (Get-Item $PSScriptRoot).Parent.Parent.Parent.Parent.FullName + $ApachePath = Join-Path $projectRoot "tools\apache24" +} + +# --- Remove marker block from 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) +$pubMarkerStart = "# --- 1C Publication: $AppName ---" +$pubMarkerEnd = "# --- End: $AppName ---" + +if ($confContent -match [regex]::Escape($pubMarkerStart)) { + $pattern = '\r?\n?' + [regex]::Escape($pubMarkerStart) + '[\s\S]*?' + [regex]::Escape($pubMarkerEnd) + '\r?\n?' + $confContent = [regex]::Replace($confContent, $pattern, "`n") + [System.IO.File]::WriteAllText($confFile, $confContent) + Write-Host "httpd.conf: блок публикации '$AppName' удалён" -ForegroundColor Green +} else { + Write-Host "Публикация '$AppName' не найдена в httpd.conf" -ForegroundColor Yellow +} + +# --- Check if any publications remain; if not, remove global block --- +$remainingPubs = [regex]::Matches($confContent, '# --- 1C Publication: .+? ---') +if ($remainingPubs.Count -eq 0) { + $globalMarkerStart = "# --- 1C: global ---" + $globalMarkerEnd = "# --- End: global ---" + if ($confContent -match [regex]::Escape($globalMarkerStart)) { + $globalPattern = '\r?\n?' + [regex]::Escape($globalMarkerStart) + '[\s\S]*?' + [regex]::Escape($globalMarkerEnd) + '\r?\n?' + $confContent = [regex]::Replace($confContent, $globalPattern, "`n") + [System.IO.File]::WriteAllText($confFile, $confContent) + Write-Host "httpd.conf: глобальный блок 1C удалён (нет публикаций)" -ForegroundColor Green + } +} + +# --- Remove publish directory --- +$publishDir = Join-Path (Join-Path $ApachePath "publish") $AppName +if (Test-Path $publishDir) { + Remove-Item $publishDir -Recurse -Force + Write-Host "Каталог удалён: $publishDir" -ForegroundColor Green +} else { + Write-Host "Каталог не найден: $publishDir" -ForegroundColor Yellow +} + +# --- Restart Apache if running --- +$httpdProc = Get-Process httpd -ErrorAction SilentlyContinue +if ($httpdProc) { + Write-Host "Перезапуск Apache..." + $httpdExe = Join-Path (Join-Path $ApachePath "bin") "httpd.exe" + $httpdProc | Stop-Process -Force -ErrorAction SilentlyContinue + Start-Sleep -Seconds 1 + + # Only restart if there are remaining publications + if ($remainingPubs.Count -gt 0) { + Start-Process -FilePath $httpdExe -WorkingDirectory $ApachePath -WindowStyle Hidden + Start-Sleep -Seconds 2 + $check = Get-Process httpd -ErrorAction SilentlyContinue + if ($check) { + Write-Host "Apache перезапущен" -ForegroundColor Green + } else { + Write-Host "Error: Apache не удалось перезапустить" -ForegroundColor Red + exit 1 + } + } else { + Write-Host "Публикаций не осталось — Apache остановлен" -ForegroundColor Green + } +} + +Write-Host "" +Write-Host "Публикация '$AppName' удалена" -ForegroundColor Green diff --git a/docs/web-guide.md b/docs/web-guide.md new file mode 100644 index 00000000..26f47015 --- /dev/null +++ b/docs/web-guide.md @@ -0,0 +1,117 @@ +# Веб-публикация 1С + +Навыки группы `/web-*` позволяют публиковать информационные базы 1С через Apache HTTP Server для доступа из браузера (веб-клиент). Это замыкает цикл разработки: правка исходников → загрузка → обновление БД → **открытие в браузере**. + +## Навыки + +| Навык | Скрипт | Описание | +|-------|:------:|----------| +| `/web-publish` | `.ps1` | Публикация базы (VRD + httpd.conf + запуск Apache) | +| `/web-info` | `.ps1` | Статус Apache и список публикаций | +| `/web-stop` | `.ps1` | Остановка Apache | +| `/web-unpublish` | `.ps1` | Удаление публикации | + +## Рабочий цикл + +``` +правки в исходниках → /db-load-xml → /db-update → /web-publish → браузер + ↑ + /web-info ← статус | + /web-stop ← остановка| + /web-unpublish ← удаление +``` + +### Типичный цикл разработки + +1. **Настройка** — `/db-list add` зарегистрировать базу в `.v8-project.json` +2. **Загрузка** — `/db-load-xml` загрузить конфигурацию +3. **Обновление** — `/db-update` применить к БД +4. **Публикация** — `/web-publish` опубликовать базу (при первом запуске скачает Apache) +5. **Проверка** — открыть `http://localhost:8080/{appname}` в браузере +6. **Правка** — изменить исходники +7. **Синхронизация** — `/db-load-git` + `/db-update` +8. **Проверка** — обновить страницу в браузере +9. **Завершение** — `/web-stop` остановить Apache + +## Конфигурация `webPath` в `.v8-project.json` + +Поле `webPath` указывает путь к каталогу Apache. Если не задано, используется `tools/apache24` от корня проекта. + +```json +{ + "v8path": "C:\\Program Files\\1cv8\\8.3.24.1691\\bin", + "webPath": "C:\\tools\\apache24", + "databases": [ + { + "id": "dev", + "name": "Разработка", + "type": "file", + "path": "C:\\Bases\\MyApp_Dev", + "user": "Admin" + } + ], + "default": "dev" +} +``` + +> `webPath` — опционально. Если не задан, все навыки `/web-*` ищут Apache в `tools/apache24`. + +## Сценарии использования + +### Опубликовать базу + +``` +> Опубликуй базу bp-demo +``` + +Claude вызовет `/web-publish bp-demo` → скачает Apache (если нет) → сгенерирует VRD → настроит httpd.conf → запустит Apache → выдаст URL. + +### Проверить статус + +``` +> Что с веб-сервером? +``` + +Claude вызовет `/web-info` → покажет состояние Apache, порт, список публикаций, последние ошибки. + +### Опубликовать на другом порту + +``` +> Опубликуй ERP на порту 9090 +``` + +Claude вызовет `/web-publish erp --port 9090`. + +### Остановить сервер + +``` +> Останови Apache +``` + +Claude вызовет `/web-stop`. + +### Удалить публикацию + +``` +> Убери публикацию bpdemo +``` + +Claude вызовет `/web-unpublish bpdemo` → удалит блок из httpd.conf → удалит каталог → перезапустит Apache (если есть другие публикации). + +## Ручная установка Apache + +Если автоматическая загрузка невозможна (прокси, firewall), используйте флаг `--manual`: + +``` +> Опубликуй базу --manual +``` + +Скрипт выдаст инструкцию: +1. Скачайте Apache Lounge (x64 VS17) с https://www.apachelounge.com/download/ +2. Распакуйте содержимое `Apache24/` в `tools/apache24` (или путь из `webPath`) +3. Запустите команду повторно + +## Спецификации + +- [web-spec.md](web-spec.md) — техническая спецификация (VRD, httpd.conf, wsap24.dll, portable Apache) +- [build-spec.md](build-spec.md) — пакетный режим конфигуратора 1С diff --git a/docs/web-spec.md b/docs/web-spec.md new file mode 100644 index 00000000..16d327df --- /dev/null +++ b/docs/web-spec.md @@ -0,0 +1,184 @@ +# Веб-публикация 1С — техническая спецификация + +Описание артефактов, необходимых для публикации информационной базы 1С через Apache HTTP Server. + +## default.vrd + +Дескриптор виртуального ресурса. XML-файл, описывающий подключение к информационной базе. + +### Формат + +```xml + + + +``` + +### Атрибут `base` + +URL-путь публикации. Должен начинаться с `/`, совпадает с `Alias` в httpd.conf. + +### Атрибут `ib` + +Строка подключения к информационной базе. + +**Файловая база:** +``` +File="C:\Bases\MyDB"; +``` + +**Серверная база:** +``` +Srvr="server01";Ref="MyDB"; +``` + +**С авторизацией:** +``` +File="C:\Bases\MyDB";Usr="Admin";Pwd="123"; +``` + +> Кавычки внутри значения `ib` экранируются как `"` (XML-сущность). + +### Расположение + +`{ApachePath}/publish/{AppName}/default.vrd` + +## httpd.conf для 1С + +### LoadModule + +Apache загружает модуль расширения 1С: + +```apache +LoadModule _1cws_module "C:/Program Files/1cv8/8.3.24.1691/bin/wsap24.dll" +``` + +- Модуль `wsap24.dll` — 64-разрядный, требует x64-версию Apache +- Путь использует forward slashes + +### Listen + +```apache +Listen 8080 +``` + +Порт для веб-клиента. По умолчанию `8080` (стандартный `80` может быть занят). + +### Alias + Directory + +Для каждой публикации добавляется блок: + +```apache +Alias "/appname" "C:/path/to/apache/publish/appname" + + AllowOverride All + Require all granted + SetHandler 1c-application + ManagedApplicationDescriptor "C:/path/to/apache/publish/appname/default.vrd" + +``` + +- `Alias` — URL-путь → физический каталог +- `SetHandler 1c-application` — делегирование обработки запросов модулю wsap24 +- `ManagedApplicationDescriptor` — путь к default.vrd + +### Маркерный подход + +Скрипты используют маркерные комментарии для идемпотентного управления блоками: + +```apache +# --- 1C: global --- +Listen 8080 +LoadModule _1cws_module "C:/Program Files/1cv8/8.3.24.1691/bin/wsap24.dll" +# --- End: global --- + +# --- 1C Publication: mydb --- +Alias "/mydb" "C:/tools/apache24/publish/mydb" + + AllowOverride All + Require all granted + SetHandler 1c-application + ManagedApplicationDescriptor "C:/tools/apache24/publish/mydb/default.vrd" + +# --- End: mydb --- +``` + +При повторном запуске блок между маркерами заменяется целиком. + +## wsap24.dll + +Модуль расширения Apache для 1С:Предприятие 8.3. + +- Расположение: `{V8Path}/wsap24.dll` (в каталоге `bin` платформы) +- Архитектура: x64 (Apache тоже должен быть x64) +- Имя модуля: `_1cws_module` + +## Portable Apache + +### Дистрибутив + +Apache Lounge — Windows-сборка Apache HTTP Server (x64): +- Сайт: `https://www.apachelounge.com/download/` +- Прямая ссылка (2.4.62, VS17): `https://www.apachelounge.com/download/VS17/binaries/httpd-2.4.62-240904-win64-VS17.zip` +- Внутри ZIP: каталог `Apache24/` с полной структурой + +### Структура после установки + +``` +tools/apache24/ +├── bin/ +│ ├── httpd.exe +│ └── ... +├── conf/ +│ ├── httpd.conf +│ └── ... +├── logs/ +│ ├── error.log +│ └── access.log +├── modules/ +│ └── ... +└── publish/ + └── {appname}/ + └── default.vrd +``` + +### Пост-распаковка + +1. `Expand-Archive` распаковывает ZIP во временный каталог +2. Содержимое `Apache24/` перемещается в `{ApachePath}` +3. В `httpd.conf` патчится `ServerRoot`: + +```apache +Define SRVROOT "C:/path/to/apache24" +ServerRoot "${SRVROOT}" +``` + +Путь `SRVROOT` — абсолютный, с forward slashes. + +### Запуск + +``` +httpd.exe # foreground (для отладки) +httpd.exe -k start # фоновый запуск (не работает без установки сервиса) +``` + +> Portable Apache запускается напрямую через `Start-Process httpd.exe` без установки Windows-сервиса. + +### Остановка + +``` +httpd.exe -k stop # graceful shutdown (требует сервис) +Stop-Process -Name httpd # принудительная остановка (portable) +``` + +### Перезагрузка + +``` +httpd.exe -k restart # graceful restart (требует сервис) +``` + +Для portable варианта: остановка + запуск. From a3e9e3c90741c86529c91dec3aa2abf0523fc251 Mon Sep 17 00:00:00 2001 From: Nick Shirokov Date: Sun, 22 Feb 2026 16:29:34 +0300 Subject: [PATCH 02/11] chore: add tools/ to .gitignore for portable Apache Co-Authored-By: Claude Opus 4.6 --- .gitignore | 3 +++ 1 file changed, 3 insertions(+) diff --git a/.gitignore b/.gitignore index 2d82609f..79321f31 100644 --- a/.gitignore +++ b/.gitignore @@ -13,5 +13,8 @@ test-tmp/ # Локальные настройки Claude Code .claude/settings.local.json +# Инструменты (portable Apache и т.д.) +tools/ + # Локальный реестр баз данных 1С .v8-project.json From e2f765fcc073df8f838e21ae26ad2f520609b840 Mon Sep 17 00:00:00 2001 From: Nick Shirokov Date: Sun, 22 Feb 2026 16:34:43 +0300 Subject: [PATCH 03/11] fix(web-publish): use WebClient for download, comment default Listen 80 - PS 5.1 Invoke-WebRequest fails on 308 redirects; WebClient handles them - Updated Apache URL to 2.4.66 VS18 (current release) - Comment out default Listen 80 when adding marker block to avoid port conflict Co-Authored-By: Claude Opus 4.6 --- .claude/skills/web-publish/scripts/web-publish.ps1 | 12 ++++++++---- 1 file changed, 8 insertions(+), 4 deletions(-) diff --git a/.claude/skills/web-publish/scripts/web-publish.ps1 b/.claude/skills/web-publish/scripts/web-publish.ps1 index eac97545..2f5f7063 100644 --- a/.claude/skills/web-publish/scripts/web-publish.ps1 +++ b/.claude/skills/web-publish/scripts/web-publish.ps1 @@ -126,23 +126,25 @@ if (-not (Test-Path $httpdExe)) { Write-Host "Apache не найден: $ApachePath" -ForegroundColor Yellow Write-Host "" Write-Host "Установите Apache вручную:" -ForegroundColor Cyan - Write-Host " 1. Скачайте Apache Lounge (x64 VS17) с https://www.apachelounge.com/download/" + 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 - $zipUrl = "https://www.apachelounge.com/download/VS17/binaries/httpd-2.4.62-240904-win64-VS17.zip" + $zipUrl = "https://www.apachelounge.com/download/VS18/binaries/httpd-2.4.66-260131-Win64-VS18.zip" $tmpZip = Join-Path $env:TEMP "apache24.zip" $tmpDir = Join-Path $env:TEMP "apache24_extract" try { [Net.ServicePointManager]::SecurityProtocol = [Net.SecurityProtocolType]::Tls12 - Invoke-WebRequest -Uri $zipUrl -OutFile $tmpZip -UseBasicParsing + # WebClient works better than Invoke-WebRequest in PS 5.1 (handles redirects) + $wc = New-Object System.Net.WebClient + $wc.DownloadFile($zipUrl, $tmpZip) } catch { Write-Host "Error: не удалось скачать Apache: $_" -ForegroundColor Red - Write-Host "Скачайте вручную: $zipUrl" -ForegroundColor Yellow + Write-Host "Скачайте вручную: https://www.apachelounge.com/download/" -ForegroundColor Yellow exit 1 } @@ -263,6 +265,8 @@ if ($confContent -match [regex]::Escape($globalMarkerStart)) { $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" } From b68013b2f2daf2158f48aa8865705c4c33ac0f72 Mon Sep 17 00:00:00 2001 From: Nick Shirokov Date: Sun, 22 Feb 2026 16:38:53 +0300 Subject: [PATCH 04/11] fix(web): port check, process isolation, startup diagnostics - web-publish: check port availability before starting, show which process holds it; run httpd -t on startup failure for diagnostics - All scripts: filter httpd processes by path (Resolve-Path match) to avoid killing or misidentifying a global Apache installation - web-info: warn about foreign httpd processes - web-stop: only stop our Apache instance Co-Authored-By: Claude Opus 4.6 --- .claude/skills/web-info/scripts/web-info.ps1 | 19 +++++++-- .../web-publish/scripts/web-publish.ps1 | 41 +++++++++++++++++-- .claude/skills/web-stop/scripts/web-stop.ps1 | 29 +++++++++---- .../web-unpublish/scripts/web-unpublish.ps1 | 13 ++++-- 4 files changed, 84 insertions(+), 18 deletions(-) diff --git a/.claude/skills/web-info/scripts/web-info.ps1 b/.claude/skills/web-info/scripts/web-info.ps1 index 75f8efdb..615fcd43 100644 --- a/.claude/skills/web-info/scripts/web-info.ps1 +++ b/.claude/skills/web-info/scripts/web-info.ps1 @@ -46,14 +46,25 @@ if (-not (Test-Path $httpdExe)) { exit 0 } -# --- Check process --- -$httpdProc = Get-Process httpd -ErrorAction SilentlyContinue -if ($httpdProc) { - $pids = ($httpdProc | ForEach-Object { $_.Id }) -join ", " +# --- Check process (only our Apache) --- +$httpdExeNorm = (Resolve-Path $httpdExe -ErrorAction SilentlyContinue).Path +$ourProc = Get-Process httpd -ErrorAction SilentlyContinue | Where-Object { + try { $_.Path -eq $httpdExeNorm } catch { $false } +} +$foreignProc = Get-Process httpd -ErrorAction SilentlyContinue | Where-Object { + try { $_.Path -ne $httpdExeNorm } catch { $true } +} +if ($ourProc) { + $pids = ($ourProc | ForEach-Object { $_.Id }) -join ", " Write-Host "Status: Запущен (PID: $pids)" -ForegroundColor Green } else { Write-Host "Status: Остановлен" -ForegroundColor Yellow } +if ($foreignProc) { + $fpid = ($foreignProc | Select-Object -First 1).Id + $fpath = try { ($foreignProc | Select-Object -First 1).Path } catch { "?" } + Write-Host "[WARN] Обнаружен сторонний Apache (PID: $fpid, $fpath)" -ForegroundColor Yellow +} Write-Host "Path: $ApachePath" diff --git a/.claude/skills/web-publish/scripts/web-publish.ps1 b/.claude/skills/web-publish/scripts/web-publish.ps1 index 2f5f7063..deb2c45a 100644 --- a/.claude/skills/web-publish/scripts/web-publish.ps1 +++ b/.claude/skills/web-publish/scripts/web-publish.ps1 @@ -298,14 +298,43 @@ if ($confContent -match [regex]::Escape($pubMarkerStart)) { [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-Process httpd -ErrorAction SilentlyContinue +$httpdProc = Get-OurHttpd if ($httpdProc) { Write-Host "Apache уже запущен (PID: $(($httpdProc | Select-Object -First 1).Id))" -ForegroundColor Yellow Write-Host "Перезапуск для применения конфигурации..." - # Portable Apache: stop + start $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..." @@ -313,11 +342,17 @@ Start-Process -FilePath $httpdExe -WorkingDirectory $ApachePath -WindowStyle Hid Start-Sleep -Seconds 2 -$httpdCheck = Get-Process httpd -ErrorAction SilentlyContinue +$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 diff --git a/.claude/skills/web-stop/scripts/web-stop.ps1 b/.claude/skills/web-stop/scripts/web-stop.ps1 index bf5aa66e..949b0789 100644 --- a/.claude/skills/web-stop/scripts/web-stop.ps1 +++ b/.claude/skills/web-stop/scripts/web-stop.ps1 @@ -33,17 +33,32 @@ if (-not $ApachePath) { $ApachePath = Join-Path $projectRoot "tools\apache24" } -# --- Check process --- -$httpdProc = Get-Process httpd -ErrorAction SilentlyContinue +# --- Helper: filter httpd processes by our ApachePath --- +$httpdExe = Join-Path (Join-Path $ApachePath "bin") "httpd.exe" +$httpdExeNorm = (Resolve-Path $httpdExe -ErrorAction SilentlyContinue).Path +function Get-OurHttpd { + Get-Process httpd -ErrorAction SilentlyContinue | Where-Object { + try { $_.Path -eq $httpdExeNorm } catch { $false } + } +} + +# --- Check process (only our Apache) --- +$httpdProc = Get-OurHttpd if (-not $httpdProc) { - Write-Host "Apache не запущен" -ForegroundColor Yellow + $foreign = Get-Process httpd -ErrorAction SilentlyContinue + if ($foreign) { + Write-Host "Наш Apache не запущен" -ForegroundColor Yellow + Write-Host "[WARN] Обнаружен сторонний Apache (PID: $(($foreign | Select-Object -First 1).Id))" -ForegroundColor Yellow + } else { + Write-Host "Apache не запущен" -ForegroundColor Yellow + } exit 0 } $pids = ($httpdProc | ForEach-Object { $_.Id }) -join ", " Write-Host "Останавливаю Apache (PID: $pids)..." -# --- Stop processes --- +# --- Stop our processes --- $httpdProc | Stop-Process -Force -ErrorAction SilentlyContinue # --- Wait for shutdown --- @@ -52,7 +67,7 @@ $elapsed = 0 while ($elapsed -lt $maxWait) { Start-Sleep -Seconds 1 $elapsed++ - $check = Get-Process httpd -ErrorAction SilentlyContinue + $check = Get-OurHttpd if (-not $check) { Write-Host "Apache остановлен" -ForegroundColor Green exit 0 @@ -60,12 +75,12 @@ while ($elapsed -lt $maxWait) { } # --- Fallback: force kill --- -$remaining = Get-Process httpd -ErrorAction SilentlyContinue +$remaining = Get-OurHttpd if ($remaining) { Write-Host "Принудительная остановка..." -ForegroundColor Yellow $remaining | Stop-Process -Force -ErrorAction SilentlyContinue Start-Sleep -Seconds 1 - $final = Get-Process httpd -ErrorAction SilentlyContinue + $final = Get-OurHttpd if ($final) { Write-Host "Error: не удалось остановить Apache" -ForegroundColor Red exit 1 diff --git a/.claude/skills/web-unpublish/scripts/web-unpublish.ps1 b/.claude/skills/web-unpublish/scripts/web-unpublish.ps1 index c4265596..d07651d9 100644 --- a/.claude/skills/web-unpublish/scripts/web-unpublish.ps1 +++ b/.claude/skills/web-unpublish/scripts/web-unpublish.ps1 @@ -81,11 +81,14 @@ if (Test-Path $publishDir) { Write-Host "Каталог не найден: $publishDir" -ForegroundColor Yellow } -# --- Restart Apache if running --- -$httpdProc = Get-Process httpd -ErrorAction SilentlyContinue +# --- Restart Apache if running (only our instance) --- +$httpdExe = Join-Path (Join-Path $ApachePath "bin") "httpd.exe" +$httpdExeNorm = (Resolve-Path $httpdExe -ErrorAction SilentlyContinue).Path +$httpdProc = Get-Process httpd -ErrorAction SilentlyContinue | Where-Object { + try { $_.Path -eq $httpdExeNorm } catch { $false } +} if ($httpdProc) { Write-Host "Перезапуск Apache..." - $httpdExe = Join-Path (Join-Path $ApachePath "bin") "httpd.exe" $httpdProc | Stop-Process -Force -ErrorAction SilentlyContinue Start-Sleep -Seconds 1 @@ -93,7 +96,9 @@ if ($httpdProc) { if ($remainingPubs.Count -gt 0) { Start-Process -FilePath $httpdExe -WorkingDirectory $ApachePath -WindowStyle Hidden Start-Sleep -Seconds 2 - $check = Get-Process httpd -ErrorAction SilentlyContinue + $check = Get-Process httpd -ErrorAction SilentlyContinue | Where-Object { + try { $_.Path -eq $httpdExeNorm } catch { $false } + } if ($check) { Write-Host "Apache перезапущен" -ForegroundColor Green } else { From 96b400ddb979e329a33377a0ffc80e3283a89222 Mon Sep 17 00:00:00 2001 From: Nick Shirokov Date: Sun, 22 Feb 2026 16:40:33 +0300 Subject: [PATCH 05/11] chore(web): change default port from 8080 to 8081 8080 is too commonly used (Tomcat, Jenkins, dev servers). 8081 is less likely to conflict in dev environments. Co-Authored-By: Claude Opus 4.6 --- .claude/skills/web-info/SKILL.md | 4 ++-- .claude/skills/web-publish/SKILL.md | 2 +- .claude/skills/web-publish/scripts/web-publish.ps1 | 2 +- docs/web-guide.md | 2 +- docs/web-spec.md | 6 +++--- 5 files changed, 8 insertions(+), 8 deletions(-) diff --git a/.claude/skills/web-info/SKILL.md b/.claude/skills/web-info/SKILL.md index 51382845..56599d83 100644 --- a/.claude/skills/web-info/SKILL.md +++ b/.claude/skills/web-info/SKILL.md @@ -41,11 +41,11 @@ powershell.exe -NoProfile -File .claude/skills/web-info/scripts/web-info.ps1 <п === Apache Web Server === Status: Запущен (PID: 12345) Path: C:\...\tools\apache24 -Port: 8080 +Port: 8081 Module: C:/Program Files/1cv8/8.3.24.1691/bin/wsap24.dll === Опубликованные базы === - mydb http://localhost:8080/mydb File="C:\Bases\MyDB"; + mydb http://localhost:8081/mydb File="C:\Bases\MyDB"; === Последние ошибки === (пусто) diff --git a/.claude/skills/web-publish/SKILL.md b/.claude/skills/web-publish/SKILL.md index fac54379..c9ee3cc1 100644 --- a/.claude/skills/web-publish/SKILL.md +++ b/.claude/skills/web-publish/SKILL.md @@ -51,7 +51,7 @@ powershell.exe -NoProfile -File .claude/skills/web-publish/scripts/web-publish.p | `-Password <пароль>` | нет | Пароль | | `-AppName <имя>` | нет | Имя публикации (по умолчанию из имени каталога базы) | | `-ApachePath <путь>` | нет | Корень Apache (по умолчанию `tools/apache24`) | -| `-Port <порт>` | нет | Порт (по умолчанию `8080`) | +| `-Port <порт>` | нет | Порт (по умолчанию `8081`) | | `-Manual` | нет | Не скачивать — только проверить и дать инструкцию | > `*` — нужен либо `-InfoBasePath`, либо пара `-InfoBaseServer` + `-InfoBaseRef` diff --git a/.claude/skills/web-publish/scripts/web-publish.ps1 b/.claude/skills/web-publish/scripts/web-publish.ps1 index deb2c45a..7658088f 100644 --- a/.claude/skills/web-publish/scripts/web-publish.ps1 +++ b/.claude/skills/web-publish/scripts/web-publish.ps1 @@ -76,7 +76,7 @@ param( [string]$ApachePath, [Parameter(Mandatory=$false)] - [int]$Port = 8080, + [int]$Port = 8081, [Parameter(Mandatory=$false)] [switch]$Manual diff --git a/docs/web-guide.md b/docs/web-guide.md index 26f47015..b840d115 100644 --- a/docs/web-guide.md +++ b/docs/web-guide.md @@ -27,7 +27,7 @@ 2. **Загрузка** — `/db-load-xml` загрузить конфигурацию 3. **Обновление** — `/db-update` применить к БД 4. **Публикация** — `/web-publish` опубликовать базу (при первом запуске скачает Apache) -5. **Проверка** — открыть `http://localhost:8080/{appname}` в браузере +5. **Проверка** — открыть `http://localhost:8081/{appname}` в браузере 6. **Правка** — изменить исходники 7. **Синхронизация** — `/db-load-git` + `/db-update` 8. **Проверка** — обновить страницу в браузере diff --git a/docs/web-spec.md b/docs/web-spec.md index 16d327df..edd6456f 100644 --- a/docs/web-spec.md +++ b/docs/web-spec.md @@ -63,10 +63,10 @@ LoadModule _1cws_module "C:/Program Files/1cv8/8.3.24.1691/bin/wsap24.dll" ### Listen ```apache -Listen 8080 +Listen 8081 ``` -Порт для веб-клиента. По умолчанию `8080` (стандартный `80` может быть занят). +Порт для веб-клиента. По умолчанию `8081` (стандартный `80` может быть занят). ### Alias + Directory @@ -92,7 +92,7 @@ Alias "/appname" "C:/path/to/apache/publish/appname" ```apache # --- 1C: global --- -Listen 8080 +Listen 8081 LoadModule _1cws_module "C:/Program Files/1cv8/8.3.24.1691/bin/wsap24.dll" # --- End: global --- From b535dff60946a7c49ec265b6651cf8c79e280c71 Mon Sep 17 00:00:00 2001 From: Nick Shirokov Date: Sun, 22 Feb 2026 17:37:02 +0300 Subject: [PATCH 06/11] fix(web): improve skills after haiku agent testing MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - web-unpublish: add -All flag to remove all publications at once - web-stop: add hint about /web-unpublish after stopping - web-publish SKILL.md: emphasize mandatory -V8Path/-UserName params - Fix Port default comment (8080 → 8081) - Clean up descriptions: semantic matching only, no technical details - web-guide: add "delete all" scenario Co-Authored-By: Claude Opus 4.6 --- .claude/skills/web-info/SKILL.md | 2 +- .claude/skills/web-publish/SKILL.md | 13 +- .../web-publish/scripts/web-publish.ps1 | 2 +- .claude/skills/web-stop/scripts/web-stop.ps1 | 2 + .claude/skills/web-unpublish/SKILL.md | 25 ++-- .../web-unpublish/scripts/web-unpublish.ps1 | 119 ++++++++++++------ docs/web-guide.md | 10 +- 7 files changed, 122 insertions(+), 51 deletions(-) diff --git a/.claude/skills/web-info/SKILL.md b/.claude/skills/web-info/SKILL.md index 56599d83..e8b13d98 100644 --- a/.claude/skills/web-info/SKILL.md +++ b/.claude/skills/web-info/SKILL.md @@ -1,6 +1,6 @@ --- name: web-info -description: Статус веб-публикации 1С — Apache, опубликованные базы, ошибки. Используй когда пользователь спрашивает про статус веб-сервера, опубликованные базы, работает ли Apache +description: Статус Apache и веб-публикаций 1С — запущен ли сервер, какие базы опубликованы, ошибки. Используй когда пользователь спрашивает про статус веб-сервера, опубликованные базы, работает ли Apache argument-hint: "" allowed-tools: - Bash diff --git a/.claude/skills/web-publish/SKILL.md b/.claude/skills/web-publish/SKILL.md index c9ee3cc1..fdea6696 100644 --- a/.claude/skills/web-publish/SKILL.md +++ b/.claude/skills/web-publish/SKILL.md @@ -24,14 +24,19 @@ allowed-tools: ## Параметры подключения -Прочитай `.v8-project.json` из корня проекта. Возьми `v8path` (путь к платформе) и разреши базу: +Прочитай `.v8-project.json` из корня проекта и разреши базу: 1. Если пользователь указал параметры подключения (путь, сервер) — используй напрямую 2. Если указал базу по имени — ищи по id / alias / name в `.v8-project.json` 3. Если не указал — сопоставь текущую ветку Git с `databases[].branches` 4. Если ветка не совпала — используй `default` -Если `v8path` не задан — автоопределение. -Если в `.v8-project.json` задан `webPath` — используй как `-ApachePath`. -Если файла нет — предложи `/db-list add`. + +**ОБЯЗАТЕЛЬНО передавай все найденные параметры:** +- **`-V8Path`** — из `v8path` в `.v8-project.json`. Если не передать, скрипт автоопределит версию платформы, что может выбрать не ту версию +- **`-UserName`** — из поля `user` найденной записи базы (если есть) +- **`-Password`** — из поля `password` найденной записи базы (если есть) +- **`-ApachePath`** — из `webPath` в `.v8-project.json` (если есть) + +Если файла `.v8-project.json` нет — предложи `/db-list add`. ## Команда diff --git a/.claude/skills/web-publish/scripts/web-publish.ps1 b/.claude/skills/web-publish/scripts/web-publish.ps1 index 7658088f..b4cd6143 100644 --- a/.claude/skills/web-publish/scripts/web-publish.ps1 +++ b/.claude/skills/web-publish/scripts/web-publish.ps1 @@ -34,7 +34,7 @@ Корень Apache (по умолчанию tools\apache24) .PARAMETER Port - Порт (по умолчанию 8080) + Порт (по умолчанию 8081) .PARAMETER Manual Не скачивать Apache — только проверить и дать инструкцию diff --git a/.claude/skills/web-stop/scripts/web-stop.ps1 b/.claude/skills/web-stop/scripts/web-stop.ps1 index 949b0789..43b77b64 100644 --- a/.claude/skills/web-stop/scripts/web-stop.ps1 +++ b/.claude/skills/web-stop/scripts/web-stop.ps1 @@ -70,6 +70,7 @@ while ($elapsed -lt $maxWait) { $check = Get-OurHttpd if (-not $check) { Write-Host "Apache остановлен" -ForegroundColor Green + Write-Host "Публикации сохранены. Для удаления: /web-unpublish <имя> или /web-unpublish --all" -ForegroundColor Gray exit 0 } } @@ -88,3 +89,4 @@ if ($remaining) { } Write-Host "Apache остановлен" -ForegroundColor Green +Write-Host "Публикации сохранены. Для удаления: /web-unpublish <имя> или /web-unpublish --all" -ForegroundColor Gray diff --git a/.claude/skills/web-unpublish/SKILL.md b/.claude/skills/web-unpublish/SKILL.md index 677ad0fb..89aebdbe 100644 --- a/.claude/skills/web-unpublish/SKILL.md +++ b/.claude/skills/web-unpublish/SKILL.md @@ -1,7 +1,7 @@ --- name: web-unpublish -description: Удаление веб-публикации 1С. Используй когда пользователь просит убрать публикацию, удалить веб-доступ к базе -argument-hint: "" +description: Удаление веб-публикации 1С из Apache. Используй когда пользователь просит убрать публикацию, удалить веб-доступ к базе +argument-hint: "" allowed-tools: - Bash - Read @@ -9,15 +9,18 @@ allowed-tools: - AskUserQuestion --- -# /web-unpublish — Удаление публикации 1С +# /web-unpublish — Удаление веб-публикации 1С из Apache -Удаляет публикацию из httpd.conf и каталог `publish/{appname}`. Если других публикаций не осталось — удаляет глобальный блок 1C и останавливает Apache. +Удаляет блок публикации из `httpd.conf` и каталог `publish/{appname}` внутри Apache. Если других публикаций не осталось — удаляет глобальный блок 1C и останавливает Apache. С флагом `--all` удаляет все публикации разом. + +> **Внимание:** этот навык управляет только веб-публикациями в Apache (блоки в `httpd.conf` + каталог `publish/`). Он **НЕ** удаляет каталоги проекта, `upload/`, базы данных или исходники. ## Usage ``` /web-unpublish /web-unpublish bpdemo +/web-unpublish --all ``` ## Параметры подключения @@ -25,7 +28,9 @@ allowed-tools: Прочитай `.v8-project.json` из корня проекта. Если задан `webPath` — используй как `-ApachePath`. По умолчанию `tools/apache24` от корня проекта. -Если пользователь не указал `appname`, выполни `/web-info` чтобы показать список публикаций и спроси какую удалить. +Если пользователь не указал `appname` и не указал `--all`, выполни `/web-info` чтобы показать список публикаций и спроси какую удалить. + +Если пользователь просит удалить **все** публикации — используй `-All`. ## Команда @@ -37,15 +42,21 @@ powershell.exe -NoProfile -File .claude/skills/web-unpublish/scripts/web-unpubli | Параметр | Обязательный | Описание | |----------|:------------:|----------| -| `-AppName <имя>` | да | Имя публикации | +| `-AppName <имя>` | * | Имя публикации | +| `-All` | * | Удалить все публикации | | `-ApachePath <путь>` | нет | Корень Apache (по умолчанию `tools/apache24`) | +> `*` — нужен либо `-AppName`, либо `-All` + ## Примеры ```powershell -# Удалить публикацию +# Удалить одну публикацию powershell.exe -NoProfile -File .claude/skills/web-unpublish/scripts/web-unpublish.ps1 -AppName "bpdemo" +# Удалить все публикации +powershell.exe -NoProfile -File .claude/skills/web-unpublish/scripts/web-unpublish.ps1 -All + # С указанием пути powershell.exe -NoProfile -File .claude/skills/web-unpublish/scripts/web-unpublish.ps1 -AppName "mydb" -ApachePath "C:\tools\apache24" ``` diff --git a/.claude/skills/web-unpublish/scripts/web-unpublish.ps1 b/.claude/skills/web-unpublish/scripts/web-unpublish.ps1 index d07651d9..479ec642 100644 --- a/.claude/skills/web-unpublish/scripts/web-unpublish.ps1 +++ b/.claude/skills/web-unpublish/scripts/web-unpublish.ps1 @@ -2,32 +2,42 @@ # Source: https://github.com/Nikolay-Shirokov/cc-1c-skills <# .SYNOPSIS - Удаление публикации 1С из Apache + Удаление веб-публикации 1С из Apache .DESCRIPTION Удаляет маркерный блок из httpd.conf и каталог публикации. Если Apache запущен — перезапускает для применения. + С флагом -All удаляет все публикации и останавливает Apache. .PARAMETER AppName - Имя публикации (обязательный) + Имя публикации (обязательный, если не указан -All) .PARAMETER ApachePath Корень Apache (по умолчанию tools\apache24) +.PARAMETER All + Удалить все публикации + .EXAMPLE .\web-unpublish.ps1 -AppName "mydb" +.EXAMPLE + .\web-unpublish.ps1 -All + .EXAMPLE .\web-unpublish.ps1 -AppName "bpdemo" -ApachePath "C:\tools\apache24" #> [CmdletBinding()] param( - [Parameter(Mandatory=$true)] + [Parameter(Mandatory=$false)] [string]$AppName, [Parameter(Mandatory=$false)] - [string]$ApachePath + [string]$ApachePath, + + [Parameter(Mandatory=$false)] + [switch]$All ) $OutputEncoding = [System.Text.Encoding]::UTF8 @@ -39,7 +49,13 @@ if (-not $ApachePath) { $ApachePath = Join-Path $projectRoot "tools\apache24" } -# --- Remove marker block from httpd.conf --- +# --- Validate params --- +if (-not $All -and -not $AppName) { + Write-Host "Error: укажите -AppName или -All" -ForegroundColor Red + exit 1 +} + +# --- Read httpd.conf --- $confFile = Join-Path (Join-Path $ApachePath "conf") "httpd.conf" if (-not (Test-Path $confFile)) { Write-Host "Error: httpd.conf не найден: $confFile" -ForegroundColor Red @@ -47,16 +63,43 @@ if (-not (Test-Path $confFile)) { } $confContent = [System.IO.File]::ReadAllText($confFile) -$pubMarkerStart = "# --- 1C Publication: $AppName ---" -$pubMarkerEnd = "# --- End: $AppName ---" -if ($confContent -match [regex]::Escape($pubMarkerStart)) { - $pattern = '\r?\n?' + [regex]::Escape($pubMarkerStart) + '[\s\S]*?' + [regex]::Escape($pubMarkerEnd) + '\r?\n?' - $confContent = [regex]::Replace($confContent, $pattern, "`n") - [System.IO.File]::WriteAllText($confFile, $confContent) - Write-Host "httpd.conf: блок публикации '$AppName' удалён" -ForegroundColor Green +# --- Helper: our httpd process --- +$httpdExe = Join-Path (Join-Path $ApachePath "bin") "httpd.exe" +$httpdExeNorm = (Resolve-Path $httpdExe -ErrorAction SilentlyContinue).Path +function Get-OurHttpd { + Get-Process httpd -ErrorAction SilentlyContinue | Where-Object { + try { $_.Path -eq $httpdExeNorm } catch { $false } + } +} + +# --- Collect app names to remove --- +if ($All) { + $pubPattern = '# --- 1C Publication: (.+?) ---' + $pubMatches = [regex]::Matches($confContent, $pubPattern) + if ($pubMatches.Count -eq 0) { + Write-Host "Нет публикаций для удаления" -ForegroundColor Yellow + exit 0 + } + $appNames = @() + foreach ($m in $pubMatches) { $appNames += $m.Groups[1].Value } + Write-Host "Удаление всех публикаций: $($appNames -join ', ')" -ForegroundColor Cyan } else { - Write-Host "Публикация '$AppName' не найдена в httpd.conf" -ForegroundColor Yellow + $appNames = @($AppName) +} + +# --- Remove marker blocks --- +foreach ($name in $appNames) { + $pubMarkerStart = "# --- 1C Publication: $name ---" + $pubMarkerEnd = "# --- End: $name ---" + + if ($confContent -match [regex]::Escape($pubMarkerStart)) { + $pattern = '\r?\n?' + [regex]::Escape($pubMarkerStart) + '[\s\S]*?' + [regex]::Escape($pubMarkerEnd) + '\r?\n?' + $confContent = [regex]::Replace($confContent, $pattern, "`n") + Write-Host "httpd.conf: блок публикации '$name' удалён" -ForegroundColor Green + } else { + Write-Host "Публикация '$name' не найдена в httpd.conf" -ForegroundColor Yellow + } } # --- Check if any publications remain; if not, remove global block --- @@ -67,38 +110,33 @@ if ($remainingPubs.Count -eq 0) { if ($confContent -match [regex]::Escape($globalMarkerStart)) { $globalPattern = '\r?\n?' + [regex]::Escape($globalMarkerStart) + '[\s\S]*?' + [regex]::Escape($globalMarkerEnd) + '\r?\n?' $confContent = [regex]::Replace($confContent, $globalPattern, "`n") - [System.IO.File]::WriteAllText($confFile, $confContent) Write-Host "httpd.conf: глобальный блок 1C удалён (нет публикаций)" -ForegroundColor Green } } -# --- Remove publish directory --- -$publishDir = Join-Path (Join-Path $ApachePath "publish") $AppName -if (Test-Path $publishDir) { - Remove-Item $publishDir -Recurse -Force - Write-Host "Каталог удалён: $publishDir" -ForegroundColor Green -} else { - Write-Host "Каталог не найден: $publishDir" -ForegroundColor Yellow +[System.IO.File]::WriteAllText($confFile, $confContent) + +# --- Remove publish directories --- +foreach ($name in $appNames) { + $publishDir = Join-Path (Join-Path $ApachePath "publish") $name + if (Test-Path $publishDir) { + Remove-Item $publishDir -Recurse -Force + Write-Host "Каталог удалён: $publishDir" -ForegroundColor Green + } else { + Write-Host "Каталог не найден: $publishDir" -ForegroundColor Yellow + } } -# --- Restart Apache if running (only our instance) --- -$httpdExe = Join-Path (Join-Path $ApachePath "bin") "httpd.exe" -$httpdExeNorm = (Resolve-Path $httpdExe -ErrorAction SilentlyContinue).Path -$httpdProc = Get-Process httpd -ErrorAction SilentlyContinue | Where-Object { - try { $_.Path -eq $httpdExeNorm } catch { $false } -} +# --- Restart/Stop Apache if running (only our instance) --- +$httpdProc = Get-OurHttpd if ($httpdProc) { - Write-Host "Перезапуск Apache..." - $httpdProc | Stop-Process -Force -ErrorAction SilentlyContinue - Start-Sleep -Seconds 1 - - # Only restart if there are remaining publications if ($remainingPubs.Count -gt 0) { + Write-Host "Перезапуск Apache..." + $httpdProc | Stop-Process -Force -ErrorAction SilentlyContinue + Start-Sleep -Seconds 1 Start-Process -FilePath $httpdExe -WorkingDirectory $ApachePath -WindowStyle Hidden Start-Sleep -Seconds 2 - $check = Get-Process httpd -ErrorAction SilentlyContinue | Where-Object { - try { $_.Path -eq $httpdExeNorm } catch { $false } - } + $check = Get-OurHttpd if ($check) { Write-Host "Apache перезапущен" -ForegroundColor Green } else { @@ -106,9 +144,16 @@ if ($httpdProc) { exit 1 } } else { - Write-Host "Публикаций не осталось — Apache остановлен" -ForegroundColor Green + Write-Host "Публикаций не осталось — останавливаю Apache..." + $httpdProc | Stop-Process -Force -ErrorAction SilentlyContinue + Start-Sleep -Seconds 1 + Write-Host "Apache остановлен" -ForegroundColor Green } } Write-Host "" -Write-Host "Публикация '$AppName' удалена" -ForegroundColor Green +if ($All) { + Write-Host "Все публикации удалены ($($appNames.Count) шт.)" -ForegroundColor Green +} else { + Write-Host "Публикация '$AppName' удалена" -ForegroundColor Green +} diff --git a/docs/web-guide.md b/docs/web-guide.md index b840d115..002ba4cf 100644 --- a/docs/web-guide.md +++ b/docs/web-guide.md @@ -9,7 +9,7 @@ | `/web-publish` | `.ps1` | Публикация базы (VRD + httpd.conf + запуск Apache) | | `/web-info` | `.ps1` | Статус Apache и список публикаций | | `/web-stop` | `.ps1` | Остановка Apache | -| `/web-unpublish` | `.ps1` | Удаление публикации | +| `/web-unpublish` | `.ps1` | Удаление публикации (одной или всех `--all`) | ## Рабочий цикл @@ -98,6 +98,14 @@ Claude вызовет `/web-stop`. Claude вызовет `/web-unpublish bpdemo` → удалит блок из httpd.conf → удалит каталог → перезапустит Apache (если есть другие публикации). +### Удалить все публикации + +``` +> Удали все веб-публикации +``` + +Claude вызовет `/web-unpublish --all` → удалит все блоки из httpd.conf → удалит все каталоги → остановит Apache. + ## Ручная установка Apache Если автоматическая загрузка невозможна (прокси, firewall), используйте флаг `--manual`: From a110c17066bbc8e13f1ed6352a735c916665c4e4 Mon Sep 17 00:00:00 2001 From: Nick Shirokov Date: Sun, 22 Feb 2026 17:48:59 +0300 Subject: [PATCH 07/11] docs(web): add multi-user publishing scenarios - SKILL.md: add "multiple users" section with replace vs parallel rules - web-guide: add scenarios for user switch and parallel publications Co-Authored-By: Claude Opus 4.6 --- .claude/skills/web-publish/SKILL.md | 12 ++++++++++++ docs/web-guide.md | 16 ++++++++++++++++ 2 files changed, 28 insertions(+) diff --git a/.claude/skills/web-publish/SKILL.md b/.claude/skills/web-publish/SKILL.md index fdea6696..ce6376c4 100644 --- a/.claude/skills/web-publish/SKILL.md +++ b/.claude/skills/web-publish/SKILL.md @@ -61,6 +61,18 @@ powershell.exe -NoProfile -File .claude/skills/web-publish/scripts/web-publish.p > `*` — нужен либо `-InfoBasePath`, либо пара `-InfoBaseServer` + `-InfoBaseRef` +## Несколько пользователей одной базы + +Повторный вызов с тем же AppName **заменяет** публикацию (идемпотентность). Это используется для: +- смены пользователя: «опубликуй под Ивановым» → тот же AppName, новый `-UserName` +- перезапуска после `/web-stop`: тот же вызов поднимает Apache обратно + +Если пользователь просит **параллельную** публикацию под другим пользователем (для тестирования разных наборов прав), добавь суффикс к AppName: +- база `bpdemo`, пользователь `Иванов` → `-AppName bpdemo-ivanov` +- база `bpdemo`, пользователь `Admin` → `-AppName bpdemo-admin` (или просто `bpdemo`) + +Ключевые слова: «ещё одну публикацию», «дополнительно», «параллельно», «под другим пользователем не убирая текущую». + ## После выполнения 1. Сообщи URL: `http://localhost:{Port}/{AppName}` diff --git a/docs/web-guide.md b/docs/web-guide.md index 002ba4cf..168acb90 100644 --- a/docs/web-guide.md +++ b/docs/web-guide.md @@ -82,6 +82,22 @@ Claude вызовет `/web-info` → покажет состояние Apache, Claude вызовет `/web-publish erp --port 9090`. +### Сменить пользователя публикации + +``` +> Переключи публикацию bp-demo на пользователя Иванов +``` + +Claude вызовет `/web-publish bp-demo` с `-UserName "Иванов"` — перезапишет VRD с новым пользователем. + +### Параллельные публикации под разными пользователями + +``` +> Добавь ещё одну публикацию bp-demo под Ивановым, Admin оставь +``` + +Claude вызовет `/web-publish bp-demo --appname bpdemo-ivanov --user Иванов` — создаст вторую публикацию. Обе будут доступны одновременно по разным URL. + ### Остановить сервер ``` From 31debfb61b2abba41292a70b998b05ad691fc237 Mon Sep 17 00:00:00 2001 From: Nick Shirokov Date: Sun, 22 Feb 2026 18:08:28 +0300 Subject: [PATCH 08/11] fix(web-stop): add restart hint and "after execution" section MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - Script: hint now mentions both restart (/web-publish) and delete (/web-unpublish) - SKILL.md: add "После выполнения" section with restart/delete options Co-Authored-By: Claude Opus 4.6 --- .claude/skills/web-stop/SKILL.md | 6 ++++++ .claude/skills/web-stop/scripts/web-stop.ps1 | 4 ++-- 2 files changed, 8 insertions(+), 2 deletions(-) diff --git a/.claude/skills/web-stop/SKILL.md b/.claude/skills/web-stop/SKILL.md index 69b0b8f0..73d3ca6c 100644 --- a/.claude/skills/web-stop/SKILL.md +++ b/.claude/skills/web-stop/SKILL.md @@ -35,6 +35,12 @@ powershell.exe -NoProfile -File .claude/skills/web-stop/scripts/web-stop.ps1 <п |----------|:------------:|----------| | `-ApachePath <путь>` | нет | Корень Apache (по умолчанию `tools/apache24`) | +## После выполнения + +Предложи пользователю: +- **Перезапуск** — `/web-publish <база>` (повторный вызов поднимет Apache с существующими публикациями) +- **Удаление публикаций** — `/web-unpublish <имя>` или `/web-unpublish --all` + ## Примеры ```powershell diff --git a/.claude/skills/web-stop/scripts/web-stop.ps1 b/.claude/skills/web-stop/scripts/web-stop.ps1 index 43b77b64..f8be0efd 100644 --- a/.claude/skills/web-stop/scripts/web-stop.ps1 +++ b/.claude/skills/web-stop/scripts/web-stop.ps1 @@ -70,7 +70,7 @@ while ($elapsed -lt $maxWait) { $check = Get-OurHttpd if (-not $check) { Write-Host "Apache остановлен" -ForegroundColor Green - Write-Host "Публикации сохранены. Для удаления: /web-unpublish <имя> или /web-unpublish --all" -ForegroundColor Gray + Write-Host "Публикации сохранены. Перезапуск: /web-publish <база> Удаление: /web-unpublish --all" -ForegroundColor Gray exit 0 } } @@ -89,4 +89,4 @@ if ($remaining) { } Write-Host "Apache остановлен" -ForegroundColor Green -Write-Host "Публикации сохранены. Для удаления: /web-unpublish <имя> или /web-unpublish --all" -ForegroundColor Gray +Write-Host "Публикации сохранены. Перезапуск: /web-publish <база> Удаление: /web-unpublish --all" -ForegroundColor Gray From 028c5292d74ada13861392b1e8c8f69e05222eee Mon Sep 17 00:00:00 2001 From: Nick Shirokov Date: Sun, 22 Feb 2026 18:38:55 +0300 Subject: [PATCH 09/11] feat(web): publish WS, HTTP services and OData by default VRD now includes , , elements so all service types are available out of the box. web-info shows [WS HTTP OData] tags per publication. Docs updated with service URL patterns and scenarios. Co-Authored-By: Claude Opus 4.6 --- .claude/skills/web-info/scripts/web-info.ps1 | 12 +++++++++- .claude/skills/web-publish/SKILL.md | 9 ++++++-- .../web-publish/scripts/web-publish.ps1 | 8 ++++++- docs/web-guide.md | 23 +++++++++++++++++++ docs/web-spec.md | 14 +++++++++++ 5 files changed, 62 insertions(+), 4 deletions(-) diff --git a/.claude/skills/web-info/scripts/web-info.ps1 b/.claude/skills/web-info/scripts/web-info.ps1 index 615fcd43..02ec4b8a 100644 --- a/.claude/skills/web-info/scripts/web-info.ps1 +++ b/.claude/skills/web-info/scripts/web-info.ps1 @@ -112,10 +112,20 @@ if ($pubMatches.Count -eq 0) { } } + # Detect published services + $svcTags = @() + if (Test-Path $vrdPath) { + if ($vrdContent -match '/...` + - Web-сервисы: `http://localhost:{Port}/{AppName}/ws/<Имя>?wsdl` 2. Предложи открыть в браузере -3. Если база не зарегистрирована — предложи `/db-list add` +3. Если нужно протестировать сервис — помоги составить запрос +4. Если база не зарегистрирована — предложи `/db-list add` ## Примеры diff --git a/.claude/skills/web-publish/scripts/web-publish.ps1 b/.claude/skills/web-publish/scripts/web-publish.ps1 index b4cd6143..40ad5e45 100644 --- a/.claude/skills/web-publish/scripts/web-publish.ps1 +++ b/.claude/skills/web-publish/scripts/web-publish.ps1 @@ -230,6 +230,9 @@ $vrdContent = @" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" base="/$AppName" ib="$ibString"> + + + "@ @@ -364,4 +367,7 @@ if ($httpdCheck) { # --- Result --- Write-Host "" Write-Host "=== Публикация готова ===" -ForegroundColor Green -Write-Host "URL: http://localhost:$Port/$AppName" -ForegroundColor Cyan +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 diff --git a/docs/web-guide.md b/docs/web-guide.md index 168acb90..49d2e83d 100644 --- a/docs/web-guide.md +++ b/docs/web-guide.md @@ -122,6 +122,29 @@ Claude вызовет `/web-unpublish bpdemo` → удалит блок из htt Claude вызовет `/web-unpublish --all` → удалит все блоки из httpd.conf → удалит все каталоги → остановит Apache. +## Работа с сервисами + +### Протестировать HTTP-сервис +``` +> Вызови HTTP-сервис ОбменДанными метод ПолучитьСтатус +``` + +Claude составит curl/Invoke-WebRequest к `http://localhost:8081/appname/hs/exchange/status` + +### Проверить OData +``` +> Покажи список справочников через OData +``` + +Claude вызовет `http://localhost:8081/appname/odata/standard.odata/$metadata` + +### Получить WSDL web-сервиса +``` +> Покажи WSDL сервиса ОбменСВнешнимиСистемами +``` + +Claude откроет `http://localhost:8081/appname/ws/ExchangeWithExternalSystems?wsdl` + ## Ручная установка Apache Если автоматическая загрузка невозможна (прокси, firewall), используйте флаг `--manual`: diff --git a/docs/web-spec.md b/docs/web-spec.md index edd6456f..d8c46635 100644 --- a/docs/web-spec.md +++ b/docs/web-spec.md @@ -43,6 +43,20 @@ File="C:\Bases\MyDB";Usr="Admin";Pwd="123"; > Кавычки внутри значения `ib` экранируются как `"` (XML-сущность). +### Дочерние элементы + +#### `` +Публикация SOAP web-сервисов. `publishByDefault="true"` публикует все сервисы из конфигурации. +URL: `/{AppName}/ws/{WebServiceName}?wsdl` + +#### `` +Публикация HTTP-сервисов. `publishByDefault="true"` публикует все сервисы из конфигурации. +URL: `/{AppName}/hs/{RootUrl}/...` + +#### `` +Стандартный OData-интерфейс платформы. `enable="true"` открывает REST-доступ ко всем объектам. +URL: `/{AppName}/odata/standard.odata` + ### Расположение `{ApachePath}/publish/{AppName}/default.vrd` From fe8fa4bc3f0b0148d575ea7d74c0eeab58ff8e5e Mon Sep 17 00:00:00 2001 From: Nick Shirokov Date: Sun, 22 Feb 2026 18:46:35 +0300 Subject: [PATCH 10/11] fix(web): correct VRD service element format for 1C 8.3 ws uses pointEnableCommon (not publishByDefault), OData is attribute enableStandardOdata on (not child element). Verified against live BP-demo: WSDL returns 200, SOAP call succeeds. Co-Authored-By: Claude Opus 4.6 --- .claude/skills/web-info/scripts/web-info.ps1 | 2 +- .../skills/web-publish/scripts/web-publish.ps1 | 6 +++--- docs/web-spec.md | 15 +++++++++------ 3 files changed, 13 insertions(+), 10 deletions(-) diff --git a/.claude/skills/web-info/scripts/web-info.ps1 b/.claude/skills/web-info/scripts/web-info.ps1 index 02ec4b8a..b8ddff73 100644 --- a/.claude/skills/web-info/scripts/web-info.ps1 +++ b/.claude/skills/web-info/scripts/web-info.ps1 @@ -117,7 +117,7 @@ if ($pubMatches.Count -eq 0) { if (Test-Path $vrdPath) { if ($vrdContent -match ' - + ib="$ibString" + enableStandardOdata="true"> + - "@ diff --git a/docs/web-spec.md b/docs/web-spec.md index d8c46635..4f22ef7a 100644 --- a/docs/web-spec.md +++ b/docs/web-spec.md @@ -14,7 +14,10 @@ xmlns:xs="http://www.w3.org/2001/XMLSchema" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" base="/appname" - ib="connection-string"> + ib="connection-string" + enableStandardOdata="true"> + + ``` @@ -45,18 +48,18 @@ File="C:\Bases\MyDB";Usr="Admin";Pwd="123"; ### Дочерние элементы +#### `enableStandardOdata` (атрибут ``) +Стандартный OData-интерфейс платформы. `enableStandardOdata="true"` открывает REST-доступ ко всем объектам. +URL: `/{AppName}/odata/standard.odata` + #### `` -Публикация SOAP web-сервисов. `publishByDefault="true"` публикует все сервисы из конфигурации. +Публикация SOAP web-сервисов. `pointEnableCommon="true"` публикует все сервисы из конфигурации. URL: `/{AppName}/ws/{WebServiceName}?wsdl` #### `` Публикация HTTP-сервисов. `publishByDefault="true"` публикует все сервисы из конфигурации. URL: `/{AppName}/hs/{RootUrl}/...` -#### `` -Стандартный OData-интерфейс платформы. `enable="true"` открывает REST-доступ ко всем объектам. -URL: `/{AppName}/odata/standard.odata` - ### Расположение `{ApachePath}/publish/{AppName}/default.vrd` From 9d5cbaab9f4f85c44829ee35961d943303d4658f Mon Sep 17 00:00:00 2001 From: Nick Shirokov Date: Sun, 22 Feb 2026 18:47:55 +0300 Subject: [PATCH 11/11] docs(readme): add web skills to groups table, specs and repo structure Co-Authored-By: Claude Opus 4.6 --- .claude/skills/web-publish/SKILL.md | 2 +- README.md | 10 +++++++++- 2 files changed, 10 insertions(+), 2 deletions(-) diff --git a/.claude/skills/web-publish/SKILL.md b/.claude/skills/web-publish/SKILL.md index 7e3f5d67..7e9c2110 100644 --- a/.claude/skills/web-publish/SKILL.md +++ b/.claude/skills/web-publish/SKILL.md @@ -1,6 +1,6 @@ --- name: web-publish -description: Публикация информационной базы 1С через Apache. Используй когда пользователь просит опубликовать базу, настроить веб-доступ, веб-клиент, открыть в браузере +description: Публикация информационной базы 1С через Apache. Используй когда пользователь просит опубликовать базу, сервисы, настроить веб-доступ, веб-клиент, открыть в браузере argument-hint: "[database]" allowed-tools: - Bash diff --git a/README.md b/README.md index 6f4f372f..300b7298 100644 --- a/README.md +++ b/README.md @@ -34,6 +34,7 @@ | Подсистемы (Subsystem) | 4 навыка `/subsystem-*` | Анализ, создание, редактирование, валидация подсистем конфигурации | [Подробнее](docs/subsystem-guide.md) | | Командный интерфейс (CI) | 2 навыка `/interface-*` | Редактирование и валидация CommandInterface.xml подсистем | [Подробнее](docs/subsystem-guide.md) | | Базы данных (DB) | 9 навыков `/db-*` | Создание баз, загрузка/выгрузка конфигураций, обновление БД, загрузка из Git | [Подробнее](docs/db-guide.md) | +| Веб-публикация (Web) | 4 навыка `/web-*` | Публикация баз через Apache, статус, остановка, удаление публикаций | [Подробнее](docs/web-guide.md) | | Утилиты | `/img-grid` | Наложение сетки на изображение для определения пропорций колонок | — | ## Требования @@ -61,6 +62,7 @@ - [Подсистемы и командный интерфейс](docs/1c-subsystem-spec.md) — XML-формат подсистем, CommandInterface.xml, секции видимости/размещения/порядка - [Корневая конфигурация](docs/1c-configuration-spec.md) — XML-формат Configuration.xml, ConfigDumpInfo.xml, Languages/, 44 типа ChildObjects - [Расширения конфигурации (CFE)](docs/1c-extension-spec.md) — XML-формат выгрузки расширений конфигурации +- [Веб-публикация 1С](docs/web-spec.md) — VRD, httpd.conf, wsap24.dll, portable Apache ## Структура репозитория @@ -127,6 +129,10 @@ ├── db-update/ # Обновление конфигурации БД ├── db-run/ # Запуск 1С:Предприятие ├── db-load-git/ # Загрузка изменений из Git +├── web-publish/ # Публикация базы через Apache +├── web-info/ # Статус Apache и публикаций +├── web-stop/ # Остановка Apache +├── web-unpublish/ # Удаление публикации └── img-grid/ # Сетка для анализа изображений docs/ ├── epf-guide.md # Гайд: внешние обработки и отчёты @@ -139,6 +145,7 @@ docs/ ├── cfe-guide.md # Гайд: расширения конфигурации (CFE) ├── subsystem-guide.md # Гайд: подсистемы и командный интерфейс ├── db-guide.md # Гайд: базы данных 1С +├── web-guide.md # Гайд: веб-публикация через Apache ├── 1c-epf-spec.md # Спецификация XML-формата (EPF) ├── 1c-erf-spec.md # Спецификация XML-формата (ERF) ├── 1c-form-spec.md # Спецификация управляемых форм @@ -154,5 +161,6 @@ docs/ ├── skd-dsl-spec.md # Спецификация SKD DSL ├── role-dsl-spec.md # Спецификация Role DSL ├── 1c-extension-spec.md # Спецификация расширений конфигурации (CFE) -└── 1c-subsystem-spec.md # Спецификация подсистем и командного интерфейса +├── 1c-subsystem-spec.md # Спецификация подсистем и командного интерфейса +└── web-spec.md # Спецификация веб-публикации (VRD, httpd.conf, Apache) ```