From dfbb086e2b131a084f3de5b0f92c9cd69616c01a Mon Sep 17 00:00:00 2001 From: Panfilov Konstantin Date: Sat, 17 Oct 2026 07:00:16 +0300 Subject: [PATCH] chore: version single preland config --- .gitea/workflows/deploy.yml | 13 ++--- .gitignore | 1 - AGENTS.md | 8 +-- CLAUDE.md | 8 +-- README.md | 25 ++++----- config.json.example => config.json | 36 ++++++------- tests/check-versioned-stage-config.php | 74 ++++++++++++++++++++++++++ 7 files changed, 120 insertions(+), 45 deletions(-) rename config.json.example => config.json (67%) create mode 100644 tests/check-versioned-stage-config.php diff --git a/.gitea/workflows/deploy.yml b/.gitea/workflows/deploy.yml index b157166..6e00daa 100644 --- a/.gitea/workflows/deploy.yml +++ b/.gitea/workflows/deploy.yml @@ -5,8 +5,9 @@ name: deploy-preland # Цель — домен со своим хостингом (список — matrix ниже): реквизиты — секреты репозитория _FTP_HOST, # _FTP_USER, _FTP_PASS (порт 21). Цели независимы: сбой одного хостинга остальные не останавливает. # Заливаются файлы репозитория (git ls-files), кроме служебных (.gitea/, README.md, AGENTS.md, CLAUDE.md, .gitignore) и тех, что живут -# только на хостинге: config.json (в git его нет) и requisites.html (реквизиты заполняют на месте — пустой файл -# из git их бы затёр). На хостинге ничего не удаляется: удалённое из репозитория убирать вручную. +# только на хостинге: requisites.html (реквизиты заполняют на месте — пустой файл из git их бы затёр). +# Рабочий config.json единственный и версионируется: он должен попадать в каждую доставку. +# На хостинге ничего не удаляется: удалённое из репозитория убирать вручную. on: push: @@ -26,7 +27,7 @@ jobs: include: # name — имя цели, url — адрес преленда (для проверки), prefix — префикс секретов _FTP_*, # dir — каталог сайта на FTP ("" — корень FTP, как на beget). Новый домен — новая запись и три секрета; - # config.json (и requisites.html) создают на хостинге цели — без него проверка страницы не пройдёт. + # requisites.html создают на хостинге цели. Рабочий config.json приходит из Git. - name: barbariskatest url: https://preland.anton.barbariskatest.ru prefix: PRELAND @@ -61,7 +62,7 @@ jobs: netrc=$(mktemp); list=$(mktemp) trap 'rm -f "$netrc" "$list"' EXIT printf 'machine %s login %s password %s\n' "$FTP_HOST" "$FTP_USER" "$FTP_PASS" > "$netrc" - git ls-files | grep -vE '^(\.gitea/|README\.md$|AGENTS\.md$|CLAUDE\.md$|\.gitignore$|config\.json$|requisites\.html$)' > files.txt + git ls-files | grep -vE '^(\.gitea/|README\.md$|AGENTS\.md$|CLAUDE\.md$|\.gitignore$|requisites\.html$)' > files.txt # имя уходит в адрес ftp:// как есть — пробелы и прочее экранирование не поддерживаем if grep -q '[^A-Za-z0-9._/-]' files.txt; then echo "имена с недопустимыми символами:"; grep '[^A-Za-z0-9._/-]' files.txt; exit 1; fi # один curl на все файлы — одно соединение FTP+TLS вместо соединения на файл @@ -118,7 +119,7 @@ jobs: files=$(mktemp) trap 'rm -f "$known_hosts" "$batch" "$files"' EXIT printf '%s\n' "$SFTP_KNOWN_HOSTS" > "$known_hosts" - git ls-files | grep -vE '^(\.gitea/|README\.md$|AGENTS\.md$|CLAUDE\.md$|\.gitignore$|config\.json$|requisites\.html$)' > "$files" + git ls-files | grep -vE '^(\.gitea/|README\.md$|AGENTS\.md$|CLAUDE\.md$|\.gitignore$|requisites\.html$)' > "$files" if grep -q '[^A-Za-z0-9._/-]' "$files"; then echo "имена с недопустимыми символами:"; grep '[^A-Za-z0-9._/-]' "$files"; exit 1; fi printf '%s\n' "-mkdir $REMOTE_DIR" > "$batch" sed 's#/[^/]*$##' "$files" | grep -v '^$' | sort -u | while read -r dir; do printf '%s\n' "-mkdir $REMOTE_DIR/$dir" >> "$batch"; done @@ -177,7 +178,7 @@ jobs: files=$(mktemp) trap 'rm -f "$known_hosts" "$batch" "$files"' EXIT printf '%s\n' "$SFTP_KNOWN_HOSTS" > "$known_hosts" - git ls-files | grep -vE '^(\.gitea/|README\.md$|AGENTS\.md$|CLAUDE\.md$|\.gitignore$|config\.json$|requisites\.html$)' > "$files" + git ls-files | grep -vE '^(\.gitea/|README\.md$|AGENTS\.md$|CLAUDE\.md$|\.gitignore$|requisites\.html$)' > "$files" if grep -q '[^A-Za-z0-9._/-]' "$files"; then echo "имена с недопустимыми символами:"; grep '[^A-Za-z0-9._/-]' "$files"; exit 1; fi printf '%s\n' "-mkdir $REMOTE_DIR" > "$batch" sed 's#/[^/]*$##' "$files" | grep -v '^$' | sort -u | while read -r dir; do printf '%s\n' "-mkdir $REMOTE_DIR/$dir" >> "$batch"; done diff --git a/.gitignore b/.gitignore index 0cffcb3..e69de29 100644 --- a/.gitignore +++ b/.gitignore @@ -1 +0,0 @@ -config.json \ No newline at end of file diff --git a/AGENTS.md b/AGENTS.md index ea912ae..3abb684 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -8,7 +8,7 @@ ## Перед работой - Прочитать [README.md](README.md), [заметку проекта](../.known-base/Проекты/Преленд%20—%20anton-preland.md), - а для выката — `.gitea/workflows/deploy.yml` и `config.json.example`. + а для выката — `.gitea/workflows/deploy.yml` и единственный `config.json`. - Проверить состояние ветки и чужие изменения в `preland/`; изменения API сайта согласовать с его контрактом и инструкциями `api/`. @@ -21,8 +21,10 @@ переход напрямую на `/public/upscale` не заменяет передачу владельца через claim. - `host` задаёт сайт назначения; HTTPS необходим для `crypto.randomUUID()`. Не обходить серверные правила доступа к загрузке и результату обработки. -- Рабочий `config.json` каждой цели и `requisites.html` живут на хостинге: не добавлять - их в git, не перезаписывать и не удалять при деплое. В репозитории — только образец конфига. +- Рабочий `config.json` единственный для всех прилендов: это точная проверенная + конфигурация `zoomstage.online`, и каждый FTP/SFTP-deploy обязан доставлять её на + stage и production. `requisites.html` по-прежнему живёт только на хостинге и не + перезаписывается. Не создавать target-specific конфиги или `config.json.example`. - Коммит в `main` запускает FTP-автодеплой на все цели workflow; откат через `git revert` также запускает выкат. Коммит, push и изменения хостинга — только по прямому поручению. - Проверки кода и браузера проводить в Docker без установки ПО на хост. diff --git a/CLAUDE.md b/CLAUDE.md index e353f51..9bf5ebf 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -8,7 +8,7 @@ ## Перед работой - Прочитать [README.md](README.md), [заметку проекта](../.known-base/Проекты/Преленд%20—%20anton-preland.md), - а для выката — `.gitea/workflows/deploy.yml` и `config.json.example`. + а для выката — `.gitea/workflows/deploy.yml` и единственный `config.json`. - Проверить состояние ветки и чужие изменения в `preland/`; изменения API сайта согласовать с его контрактом и инструкциями `api/`. @@ -21,8 +21,10 @@ переход напрямую на `/public/upscale` не заменяет передачу владельца через claim. - `host` задаёт сайт назначения; HTTPS необходим для `crypto.randomUUID()`. Не обходить серверные правила доступа к загрузке и результату обработки. -- Рабочий `config.json` каждой цели и `requisites.html` живут на хостинге: не добавлять - их в git, не перезаписывать и не удалять при деплое. В репозитории — только образец конфига. +- Рабочий `config.json` единственный для всех прилендов: это точная проверенная + конфигурация `zoomstage.online`, и каждый FTP/SFTP-deploy обязан доставлять её на + stage и production. `requisites.html` по-прежнему живёт только на хостинге и не + перезаписывается. Не создавать target-specific конфиги или `config.json.example`. - Коммит в `main` запускает FTP-автодеплой на все цели workflow; откат через `git revert` также запускает выкат. Коммит, push и изменения хостинга — только по прямому поручению. - Проверки кода и браузера проводить в Docker без установки ПО на хост. diff --git a/README.md b/README.md index 45f41b6..bd6c7d9 100644 --- a/README.md +++ b/README.md @@ -61,7 +61,7 @@ | `requisites.html` | реквизиты для подвала (HTML), если в `config.json` нет `requisites`. Без обоих — **заглушка «ИП Иванов Иван Иванович»** из `template.php` | | `images/`, `fonts/`, `favicon/`, `*.css` | статика; `style.min.css` встраивается в страницу | | `.htaccess` | только сроки кэширования статики (Apache) | -| `config.json.example` | образец конфига; рабочий `config.json` в git не хранится | +| `config.json` | единственный версионируемый рабочий конфиг всех прилендов: точная проверенная конфигурация stage `zoomstage.online`; каждый deploy доставляет его на целевой хост | ### `config.json` @@ -87,12 +87,10 @@ git clone http://git.dira89kv.beget.tech:3000/anton/preland.git && cd preland # дальше — загрузка на хостинг: rsync/scp по SSH или FTP-клиент ``` -3. **Конфиг и реквизиты.** На сервере, рядом с `index.php`; реквизиты — ключ `requisites` (иначе в подвале заглушка): - ```bash - cp config.json.example config.json # host — домен сайта, метки — по кампании - ``` - `config.json` не в git: при обновлении файлов **не удалять** его (не синхронизировать каталог «зеркалом» без - исключения `config.json`). 2026-10-05 после заливки он пропал, и преленд слал загрузку на собственный домен. +3. **Конфиг и реквизиты.** Рабочий `config.json` уже лежит в репозитории и обязателен к доставке на каждый + приленд. Его не заменяют локальным вариантом и не исключают из синхронизации. Реквизиты задаются ключом + `requisites` в нём либо runtime-файлом `requisites.html`. 2026-10-05 конфиг пропал после заливки, + и преленд слал загрузку на собственный домен; это больше не допускается. 4. **nginx без Apache** (если хостинг не обрабатывает `.htaccess`): ```nginx server { @@ -139,14 +137,13 @@ 1. Домен и HTTPS — как в «Деплой на произвольный домен», шаг 1. 2. Секреты `<ПРЕФИКС>_FTP_HOST`, `<ПРЕФИКС>_FTP_USER`, `<ПРЕФИКС>_FTP_PASS` (Gitea → настройки репозитория → Actions → секреты; порт 21, FTP с TLS). В коде реквизитов нет. -3. На хостинге, в каталоге сайта, — `config.json` из `config.json.example`: свой `host`, метки, `requisites`. - В репозитории только образец: `config.json` у каждого домена свой и живёт на его хостинге. -4. Запись в `matrix` — коммит в `main`, автодеплой выкатит все цели. Пока на новом хостинге нет `config.json`, - его проверка падает: загрузка ушла бы на сам преленд. +3. Никаких ручных конфигов не создавать: единственный `config.json` приезжает вместе с кодом. +4. Запись в `matrix` — коммит в `main`, автодеплой выкатит все цели вместе с конфигом. -**Что заливается.** Файлы репозитория (`git ls-files`), кроме служебных (`.gitea/`, `README.md`, `.gitignore`) -и живущих на хостинге `config.json` и `requisites.html`. На хостинге ничего не удаляется: файлы, убранные -из репозитория, удалять вручную. +**Что заливается.** Каждый deploy получает файлы репозитория (`git ls-files`), кроме служебных +(`.gitea/`, `README.md`, `.gitignore`) и живущего на хостинге `requisites.html`. Единственный +версионируемый `config.json` всегда доставляется вместе с кодом. +На хостинге ничего не удаляется: файлы, убранные из репозитория, удалять вручную. **Проверка после заливки.** На странице цели должен быть адрес загрузки на сайте, а не на самом преленде (иначе нет `config.json` с `host`), и переход на `claim`. Иначе прогон этой цели падает. diff --git a/config.json.example b/config.json similarity index 67% rename from config.json.example rename to config.json index fb9914e..62063b9 100644 --- a/config.json.example +++ b/config.json @@ -11,52 +11,52 @@ "utm": { "indpic": { "weights": { - "1": 0.2, - "2": 0.3 + "1": 1, + "2": 0 }, "default": "1" }, "theme": { "weights": { - "light": 0.5, - "dark": 0.5 + "light": 0, + "dark": 1 }, "default": "dark" }, "degr": { "weights": { - "1": 0.7, - "2": 0.3 + "1": 1, + "2": 0 }, "default": "1" }, "registration": { "weights": { - "before": 0.7, - "after": 0.3 + "before": 0, + "after": 1 }, - "default": "before" + "default": "after" }, "bchoice": { "weights": { - "a": 0.7, - "b": 0.3 + "a": 1, + "b": 0 }, "default": "a" }, "chb": { "weights": { - "a": 0.7, - "b": 0.3 + "a": 1, + "b": 0 }, - "default": "a" + "default": "b" }, "tp": { "weights": { - "1": 0.7, - "2": 0.3 + "1": 0, + "2": 1 }, - "default": "2" + "default": "1" } } -} \ No newline at end of file +} diff --git a/tests/check-versioned-stage-config.php b/tests/check-versioned-stage-config.php new file mode 100644 index 0000000..6664953 --- /dev/null +++ b/tests/check-versioned-stage-config.php @@ -0,0 +1,74 @@ + 'zoomzilla.ru', + 'indpic' => ['1' => 1, '2' => 0, 'default' => '1'], + 'theme' => ['light' => 0, 'dark' => 1, 'default' => 'dark'], + 'degr' => ['1' => 1, '2' => 0, 'default' => '1'], + 'registration' => ['before' => 0, 'after' => 1, 'default' => 'after'], + 'bchoice' => ['a' => 1, 'b' => 0, 'default' => 'a'], + 'chb' => ['a' => 1, 'b' => 0, 'default' => 'b'], + 'tp' => ['1' => 0, '2' => 1, 'default' => '1'], + ]; + + if (($config['host'] ?? null) !== $expected['host']) { + $failures[] = 'Рабочий config.json обязан направлять загрузку на zoomzilla.ru.'; + } + + foreach (array_diff_key($expected, ['host' => true]) as $key => $settings) { + $actual = $config['utm'][$key] ?? []; + if (($actual['weights'] ?? null) !== array_diff_key($settings, ['default' => true]) + || ($actual['default'] ?? null) !== $settings['default']) { + $failures[] = "Рабочий config.json должен сохранять stage-настройку UTM {$key}."; + } + } +} + +$workflow = (string) file_get_contents($root . '/.gitea/workflows/deploy.yml'); +$legacyStart = strpos($workflow, ' deploy:'); +$stageStart = strpos($workflow, 'deploy-zoomstage:'); +$productionStart = strpos($workflow, 'deploy-zoompixel-production:'); +$legacyBlock = $legacyStart === false || $stageStart === false + ? '' + : substr($workflow, $legacyStart, $stageStart - $legacyStart); +$stageBlock = $stageStart === false || $productionStart === false + ? '' + : substr($workflow, $stageStart, $productionStart - $stageStart); +$productionBlock = $productionStart === false ? '' : substr($workflow, $productionStart); + +foreach ([ + 'legacy FTP' => $legacyBlock, + 'deploy-zoomstage' => $stageBlock, + 'deploy-zoompixel-production' => $productionBlock, +] as $delivery => $block) { + if ($block === '' || str_contains($block, 'config\\.json$')) { + $failures[] = "{$delivery} обязан доставлять единый версионируемый config.json."; + } +} + +if (is_file($root . '/config.json.example')) { + $failures[] = 'config.json.example не должен существовать: рабочий config.json единственный.'; +} + +if ($failures !== []) { + fwrite(STDERR, "FAILED\n- " . implode("\n- ", $failures) . "\n"); + exit(1); +} + +fwrite(STDOUT, "OK: versioned stage config and ZoomPixel delivery\n");