feat(deploy): pilonuxt 19a4a84 — structured-data (description/brand/return-policy) live; close redeploy-pilonuxt-gsc-structured-data
Перекат stack 16 на registry.kzntsv.site/pilonuxt:19a4a84 (build на workstation, push, Portainer PUT pullImage). Smoke на проде через --resolve: description непустой, brand=Пилорама 98, offers→ hasMerchantReturnPolicy/returnPolicyCategory=MerchantReturnNotPermitted, прежние offers-поля целы, регрессий нет. Отчёт в inbox pilonuxt. Wiki ingest: portainer-stack-management-vds — раздел Stack redeploy (новый тег) + gotcha #9 (PS 5.1 Invoke-RestMethod декодит /file как ISO-8859-1 → mojibake кириллицы → PUT падает YAML; fix байты+UTF8) + #10 (пустой env). index/log обновлены. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -3,6 +3,7 @@ title: Portainer stack management on vds-kzntsv — canonical pattern
|
||||
status: live
|
||||
tags: [vds, portainer, docker-compose, ops]
|
||||
related: [[vds-kzntsv]], [[mssql-on-vds]], [[minio-imgproxy-on-vds]]
|
||||
updated: 2026-06-17
|
||||
---
|
||||
|
||||
# Portainer stack management on vds-kzntsv
|
||||
@@ -77,6 +78,30 @@ curl -ksS -X POST "$PORTAINER_URL/api/stacks/create/standalone/string?endpointId
|
||||
6. **`com.docker.compose.project.working_dir`** меняется с `/opt/stacks/<name>` (ad-hoc) на `/data/compose/<id>` (Portainer-managed). Сохранённые `/opt/stacks/<name>/.env` остаются на disk — лишь для backup/reference, не используются compose runtime.
|
||||
7. **Volumes preserved across migration** — named volumes (`docker compose down` без `-v`) и binds (`/opt/stacks/<name>/data`) сохраняются. Data layer не теряется при ad-hoc→Portainer переезде.
|
||||
8. **Management plane skip** — `traefik` и `portainer` сами через себя нельзя безопасно recreate (теряется access). Оставлены ad-hoc; их docker-compose.yml в `/opt/stacks/{traefik,portainer}/`. Future: bootstrap script для cold-start ставит их first перед всем остальным.
|
||||
9. **PowerShell 5.1 коррапит кириллицу при round-trip stack-file через API** — `Invoke-RestMethod` на `GET /api/stacks/<id>/file` декодит тело как **ISO-8859-1** (нет `charset` в Content-Type ответа), UTF-8 кириллица в комментариях compose превращается в mojibake (`Боевой`→`Боевой`). Обратный `PUT` шлёт mojibake → docker compose загрузчик падает `yaml: line N: could not find expected ':'` на строке с битым комментарием. На диске исходный файл корректен — портит именно round-trip. **Fix (PS 5.1):** качать байтами `Invoke-WebRequest -OutFile $tmp` → `Get-Content $tmp -Raw -Encoding UTF8 | ConvertFrom-Json`, а тело PUT слать UTF-8-байтами: `$bytes=[Text.Encoding]::UTF8.GetBytes($json); Invoke-RestMethod -Body $bytes -ContentType 'application/json; charset=utf-8'`. (`pilorama98`/pilonuxt redeploy 2026-06-17.)
|
||||
10. **Пустой `env` в PUT-payload** — Portainer ждёт `env: []` (`[]portainer.Pair`). PS `@{env=@()}|ConvertTo-Json` схлопывает пустой массив → `Invalid request payload`. Fix: подставить literal через плейсхолдер — `(... | ConvertTo-Json) -replace '"__ENV__"','[]'`.
|
||||
|
||||
## Stack redeploy (existing stack, новый image tag)
|
||||
|
||||
Перекат уже-managed стека на новый тег образа (НЕ создание). Применялось для `pilonuxt` (stack Id 16) на `redeploy-pilonuxt-gsc-*` 2026-06-16/17. Образ собирается/пушится на workstation (≈400 МБ влезает; multi-GB → собирать на VDS, см. memory `registry-large-push-499-build-on-vds`), затем:
|
||||
|
||||
```powershell
|
||||
# 1. JWT
|
||||
$jwt = (Invoke-RestMethod -Method Post -Uri 'https://portainer.vds.kzntsv.site/api/auth' `
|
||||
-ContentType 'application/json' -Body (@{username='vitya';password='<vds-kzntsv/full-env>'}|ConvertTo-Json)).jwt
|
||||
$h = @{ Authorization = "Bearer $jwt" }
|
||||
# 2. GET текущий compose БАЙТАМИ (gotcha #9), подменить тег
|
||||
$tmp = "$env:TEMP\stack.json"
|
||||
Invoke-WebRequest -Headers $h -Uri 'https://portainer.vds.kzntsv.site/api/stacks/16/file' -OutFile $tmp
|
||||
$new = ((Get-Content $tmp -Raw -Encoding UTF8 | ConvertFrom-Json).StackFileContent) -replace 'pilonuxt:OLD','pilonuxt:NEW'
|
||||
# 3. PUT с pullImage:true (форсит свежий pull нового тега), env=[] через плейсхолдер, тело UTF-8-байтами
|
||||
$json = (@{stackFileContent=$new; env='__ENV__'; prune=$false; pullImage=$true}|ConvertTo-Json -Depth 10) -replace '"__ENV__"','[]'
|
||||
Invoke-RestMethod -Method Put -Headers $h -ContentType 'application/json; charset=utf-8' `
|
||||
-Body ([Text.Encoding]::UTF8.GetBytes($json)) -Uri 'https://portainer.vds.kzntsv.site/api/stacks/16?endpointId=1'
|
||||
```
|
||||
|
||||
Verify: контейнер на новом образе — `ops.docker.ps name=<stack>` (vds-ops MCP) показывает `image: registry.kzntsv.site/<name>:NEW`. Прод-smoke мимо LAN-DNS воркстейшна — `curl --resolve <host>:443:89.253.255.94` (иначе резолвится локальная копия, см. memory `workstation-lan-dns-serves-local-cms-copy`); краулить реальные nav-страницы + data-endpoint, не 2 роута ([[smoke-crawl-real-pages-not-two-routes]]).
|
||||
**Предусловие pull:** registry.kzntsv.site зарегистрирован в Portainer как Custom registry Id 1 (иначе `pull access denied` / `no basic auth`). См. memory `portainer-vds-needs-registry-registered`.
|
||||
|
||||
## Stack inventory (2026-05-22, Portainer Ids)
|
||||
|
||||
|
||||
Reference in New Issue
Block a user