chore(ci): только stage, Барбариска выведена (refs #4)
deploy-preland / deploy zoomstage.online (push) Successful in 35s
deploy-preland / deploy zoomstage.online (push) Successful in 35s
This commit is contained in:
1 parent
0f06a18622
commit
d2637e8f61
5 files changed
+37
-262
No files matched your search
+2
-201
@@ -1,118 +1,13 @@
|
|||||||
name: deploy-preland
|
name: deploy-preland
|
||||||
|
|
||||||
# После коммита в main сохраняется legacy-выпуск на Beget. Новые домены ZoomPixel
|
# Решение владельца 19.10.2026, preland#4: только stage.
|
||||||
# доставляются отдельными SFTP job: staging — только на stage, main — только на production.
|
# Production выкаты временно отключены; Барбариска больше не используется.
|
||||||
# Цель — домен со своим хостингом (список — matrix ниже): реквизиты — секреты репозитория <prefix>_FTP_HOST,
|
|
||||||
# <prefix>_FTP_USER, <prefix>_FTP_PASS (порт 21). Цели независимы: сбой одного хостинга остальные не останавливает.
|
|
||||||
# Заливаются файлы репозитория (git ls-files), кроме служебных (.gitea/, README.md, AGENTS.md, CLAUDE.md, .gitignore) и тех, что живут
|
|
||||||
# только на хостинге: requisites.html (реквизиты заполняют на месте — пустой файл из git их бы затёр).
|
|
||||||
# Рабочий config.json настраивается владельцем на каждом хостинге. Деплой не
|
|
||||||
# перезаписывает его: он добавляет только отсутствующие обязательные UTM-ключи.
|
|
||||||
# Единственное контролируемое удаление — завершённая миграция legacy images/demo* после
|
|
||||||
# smoke новых корневых наборов /1, /2. Прочие файлы на хостинге не удаляются.
|
|
||||||
|
|
||||||
on:
|
on:
|
||||||
push:
|
push:
|
||||||
branches:
|
branches:
|
||||||
- main
|
|
||||||
- staging
|
- staging
|
||||||
|
|
||||||
jobs:
|
jobs:
|
||||||
deploy:
|
|
||||||
name: deploy ${{ matrix.name }}
|
|
||||||
if: github.ref == 'refs/heads/main'
|
|
||||||
runs-on: ubuntu-latest
|
|
||||||
|
|
||||||
strategy:
|
|
||||||
fail-fast: false
|
|
||||||
matrix:
|
|
||||||
include:
|
|
||||||
# name — имя цели, url — адрес преленда (для проверки), prefix — префикс секретов <prefix>_FTP_*,
|
|
||||||
# dir — каталог сайта на FTP ("" — корень FTP, как на beget). Новый домен — новая запись и три секрета;
|
|
||||||
# requisites.html создают на хостинге цели. Рабочий config.json приходит из Git.
|
|
||||||
- name: barbariskatest
|
|
||||||
url: https://preland.anton.barbariskatest.ru
|
|
||||||
prefix: PRELAND
|
|
||||||
dir: ""
|
|
||||||
|
|
||||||
defaults:
|
|
||||||
run:
|
|
||||||
shell: sh
|
|
||||||
|
|
||||||
env:
|
|
||||||
PRELAND_URL: ${{ matrix.url }}
|
|
||||||
REMOTE_DIR: ${{ matrix.dir }}
|
|
||||||
|
|
||||||
steps:
|
|
||||||
- name: Install tools (Alpine)
|
|
||||||
run: apk add --no-cache git curl php83-cli
|
|
||||||
|
|
||||||
- name: Checkout repository
|
|
||||||
uses: actions/checkout@v4
|
|
||||||
|
|
||||||
- name: Preserve host config and add focus if absent
|
|
||||||
env:
|
|
||||||
FTP_HOST: ${{ secrets[format('{0}_FTP_HOST', matrix.prefix)] }}
|
|
||||||
FTP_USER: ${{ secrets[format('{0}_FTP_USER', matrix.prefix)] }}
|
|
||||||
FTP_PASS: ${{ secrets[format('{0}_FTP_PASS', matrix.prefix)] }}
|
|
||||||
run: |
|
|
||||||
set -eu
|
|
||||||
[ -n "$FTP_HOST" ] && [ -n "$FTP_USER" ] && [ -n "$FTP_PASS" ] || { echo "нет секретов ${{ matrix.prefix }}_FTP_*"; exit 1; }
|
|
||||||
dir=$(printf '%s' "$REMOTE_DIR" | sed 's#^/*##; s#/*$##')
|
|
||||||
umask 077
|
|
||||||
netrc=$(mktemp); config=$(mktemp)
|
|
||||||
trap 'rm -f "$netrc" "$config"' EXIT
|
|
||||||
printf 'machine %s login %s password %s\n' "$FTP_HOST" "$FTP_USER" "$FTP_PASS" > "$netrc"
|
|
||||||
curl -sS --ssl-reqd --netrc-file "$netrc" --fail --retry 3 \
|
|
||||||
"ftp://$FTP_HOST/${dir:+$dir/}config.json" -o "$config"
|
|
||||||
changed=0
|
|
||||||
php bin/ensure-focus-config.php "$config" || changed=$?
|
|
||||||
case "$changed" in
|
|
||||||
0) ;;
|
|
||||||
10) curl -sS --ssl-reqd --netrc-file "$netrc" --ftp-create-dirs --retry 3 --fail \
|
|
||||||
-T "$config" "ftp://$FTP_HOST/${dir:+$dir/}config.json" ;;
|
|
||||||
*) exit "$changed" ;;
|
|
||||||
esac
|
|
||||||
|
|
||||||
- name: Upload over FTP (TLS)
|
|
||||||
env:
|
|
||||||
FTP_HOST: ${{ secrets[format('{0}_FTP_HOST', matrix.prefix)] }}
|
|
||||||
FTP_USER: ${{ secrets[format('{0}_FTP_USER', matrix.prefix)] }}
|
|
||||||
FTP_PASS: ${{ secrets[format('{0}_FTP_PASS', matrix.prefix)] }}
|
|
||||||
run: |
|
|
||||||
set -eu
|
|
||||||
[ -n "$FTP_HOST" ] && [ -n "$FTP_USER" ] && [ -n "$FTP_PASS" ] || { echo "нет секретов ${{ matrix.prefix }}_FTP_*"; exit 1; }
|
|
||||||
dir=$(printf '%s' "$REMOTE_DIR" | sed 's#^/*##; s#/*$##')
|
|
||||||
case "$dir" in *[!A-Za-z0-9._/-]*) echo "недопустимый dir: $dir"; exit 1 ;; esac
|
|
||||||
umask 077
|
|
||||||
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$|requisites\.html$|config\.json$)' > 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 вместо соединения на файл
|
|
||||||
while read -r f; do printf 'upload-file = "%s"\nurl = "ftp://%s/%s%s"\n' "$f" "$FTP_HOST" "${dir:+$dir/}" "$f"; done < files.txt > "$list"
|
|
||||||
curl -sS --ssl-reqd --netrc-file "$netrc" --ftp-create-dirs --retry 3 --fail-early -K "$list"
|
|
||||||
echo "залито файлов: $(wc -l < files.txt)"
|
|
||||||
|
|
||||||
- name: Check the page
|
|
||||||
run: |
|
|
||||||
set -eu
|
|
||||||
page=$(curl -fsS --max-time 30 "$PRELAND_URL/?nc=$(date +%s)")
|
|
||||||
api=$(printf '%s' "$page" | grep -o 'const createApiUrl = "[^"]*"' || true)
|
|
||||||
echo "$api"
|
|
||||||
case "$api" in
|
|
||||||
*'\/api\/images\/public\/create'*) ;;
|
|
||||||
*) echo "на странице нет адреса загрузки"; exit 1 ;;
|
|
||||||
esac
|
|
||||||
own=$(printf '%s' "$PRELAND_URL" | sed -E 's#^https?://([^/]+).*#\1#')
|
|
||||||
case "$api" in
|
|
||||||
*"$own"*) echo "загрузка уходит на сам преленд: на хостинге нет config.json с host"; exit 1 ;;
|
|
||||||
esac
|
|
||||||
printf '%s' "$page" | grep -q 'claim?key=' || { echo "переход не на claim — залит старый код?"; exit 1; }
|
|
||||||
echo "страница в порядке"
|
|
||||||
|
|
||||||
deploy-zoomstage:
|
deploy-zoomstage:
|
||||||
name: deploy zoomstage.online
|
name: deploy zoomstage.online
|
||||||
if: github.ref == 'refs/heads/staging'
|
if: github.ref == 'refs/heads/staging'
|
||||||
@@ -227,97 +122,3 @@ jobs:
|
|||||||
printf '%s\n' "-rmdir $REMOTE_DIR/images/$set" >> "$batch"
|
printf '%s\n' "-rmdir $REMOTE_DIR/images/$set" >> "$batch"
|
||||||
done
|
done
|
||||||
SSHPASS="$SFTP_PASS" sshpass -e sftp -oBatchMode=no -oStrictHostKeyChecking=yes -oUserKnownHostsFile="$known_hosts" "$SFTP_USER@$SFTP_HOST" < "$batch"
|
SSHPASS="$SFTP_PASS" sshpass -e sftp -oBatchMode=no -oStrictHostKeyChecking=yes -oUserKnownHostsFile="$known_hosts" "$SFTP_USER@$SFTP_HOST" < "$batch"
|
||||||
|
|
||||||
deploy-zoompixel-production:
|
|
||||||
name: deploy ${{ matrix.name }}
|
|
||||||
if: github.ref == 'refs/heads/main'
|
|
||||||
runs-on: zoomzilla-production
|
|
||||||
strategy:
|
|
||||||
fail-fast: false
|
|
||||||
matrix:
|
|
||||||
include:
|
|
||||||
- name: zoompixel.ru
|
|
||||||
url: https://zoompixel.ru
|
|
||||||
dir: domains/zoompixel.ru
|
|
||||||
- name: zoompixel.online
|
|
||||||
url: https://zoompixel.online
|
|
||||||
dir: domains/zoompixel.online
|
|
||||||
defaults:
|
|
||||||
run:
|
|
||||||
shell: sh
|
|
||||||
env:
|
|
||||||
PRELAND_URL: ${{ matrix.url }}
|
|
||||||
REMOTE_DIR: ${{ matrix.dir }}
|
|
||||||
steps:
|
|
||||||
- name: Install tools
|
|
||||||
run: apt-get update && DEBIAN_FRONTEND=noninteractive apt-get install -y --no-install-recommends ca-certificates git curl openssh-client sshpass php-cli
|
|
||||||
|
|
||||||
- name: Checkout production branch
|
|
||||||
run: git clone --depth 1 --branch main http://git.dira89kv.beget.tech:3000/anton/preland.git .
|
|
||||||
|
|
||||||
- name: Preserve host config and add focus if absent
|
|
||||||
env:
|
|
||||||
SFTP_HOST: ${{ secrets.ZOOMPIXEL_SFTP_HOST }}
|
|
||||||
SFTP_USER: ${{ secrets.ZOOMPIXEL_SFTP_USER }}
|
|
||||||
SFTP_PASS: ${{ secrets.ZOOMPIXEL_SFTP_PASS }}
|
|
||||||
SFTP_KNOWN_HOSTS: ${{ secrets.ZOOMPIXEL_SFTP_KNOWN_HOSTS }}
|
|
||||||
run: |
|
|
||||||
set -eu
|
|
||||||
[ -n "$SFTP_HOST" ] && [ -n "$SFTP_USER" ] && [ -n "$SFTP_PASS" ] && [ -n "$SFTP_KNOWN_HOSTS" ] || { echo "не заданы секреты ZOOMPIXEL_SFTP_*"; exit 1; }
|
|
||||||
umask 077
|
|
||||||
known_hosts=$(mktemp); batch=$(mktemp); config=$(mktemp)
|
|
||||||
trap 'rm -f "$known_hosts" "$batch" "$config"' EXIT
|
|
||||||
printf '%s\n' "$SFTP_KNOWN_HOSTS" > "$known_hosts"
|
|
||||||
printf '%s\n' "get -p $REMOTE_DIR/config.json $config" > "$batch"
|
|
||||||
SSHPASS="$SFTP_PASS" sshpass -e sftp -oBatchMode=no -oStrictHostKeyChecking=yes -oUserKnownHostsFile="$known_hosts" "$SFTP_USER@$SFTP_HOST" < "$batch"
|
|
||||||
changed=0
|
|
||||||
php bin/ensure-focus-config.php "$config" || changed=$?
|
|
||||||
case "$changed" in
|
|
||||||
0) ;;
|
|
||||||
10) printf '%s\n' "put -p $config $REMOTE_DIR/config.json" > "$batch"
|
|
||||||
SSHPASS="$SFTP_PASS" sshpass -e sftp -oBatchMode=no -oStrictHostKeyChecking=yes -oUserKnownHostsFile="$known_hosts" "$SFTP_USER@$SFTP_HOST" < "$batch" ;;
|
|
||||||
*) exit "$changed" ;;
|
|
||||||
esac
|
|
||||||
|
|
||||||
- name: Upload over SFTP
|
|
||||||
env:
|
|
||||||
SFTP_HOST: ${{ secrets.ZOOMPIXEL_SFTP_HOST }}
|
|
||||||
SFTP_USER: ${{ secrets.ZOOMPIXEL_SFTP_USER }}
|
|
||||||
SFTP_PASS: ${{ secrets.ZOOMPIXEL_SFTP_PASS }}
|
|
||||||
SFTP_KNOWN_HOSTS: ${{ secrets.ZOOMPIXEL_SFTP_KNOWN_HOSTS }}
|
|
||||||
run: |
|
|
||||||
set -eu
|
|
||||||
[ -n "$SFTP_HOST" ] && [ -n "$SFTP_USER" ] && [ -n "$SFTP_PASS" ] && [ -n "$SFTP_KNOWN_HOSTS" ] || { echo "не заданы секреты ZOOMPIXEL_SFTP_*"; exit 1; }
|
|
||||||
umask 077
|
|
||||||
known_hosts=$(mktemp)
|
|
||||||
batch=$(mktemp)
|
|
||||||
files=$(mktemp)
|
|
||||||
dirs=$(mktemp)
|
|
||||||
trap 'rm -f "$known_hosts" "$batch" "$files" "$dirs"' EXIT
|
|
||||||
printf '%s\n' "$SFTP_KNOWN_HOSTS" > "$known_hosts"
|
|
||||||
git ls-files | grep -vE '^(\.gitea/|README\.md$|AGENTS\.md$|CLAUDE\.md$|\.gitignore$|requisites\.html$|config\.json$)' > "$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"
|
|
||||||
while read -r file; do
|
|
||||||
case "$file" in
|
|
||||||
*/*) dir=${file%/*} ;;
|
|
||||||
*) continue ;;
|
|
||||||
esac
|
|
||||||
while [ -n "$dir" ]; do
|
|
||||||
printf '%s\n' "$dir" >> "$dirs"
|
|
||||||
case "$dir" in */*) dir=${dir%/*} ;; *) break ;; esac
|
|
||||||
done
|
|
||||||
done < "$files"
|
|
||||||
sort -u "$dirs" | while read -r dir; do printf '%s\n' "-mkdir $REMOTE_DIR/$dir" >> "$batch"; done
|
|
||||||
while read -r file; do printf '%s\n' "put -p $file $REMOTE_DIR/$file" >> "$batch"; done < "$files"
|
|
||||||
SSHPASS="$SFTP_PASS" sshpass -e sftp -oBatchMode=no -oStrictHostKeyChecking=yes -oUserKnownHostsFile="$known_hosts" "$SFTP_USER@$SFTP_HOST" < "$batch"
|
|
||||||
echo "залито файлов: $(wc -l < "$files")"
|
|
||||||
|
|
||||||
- name: Check the page
|
|
||||||
run: |
|
|
||||||
set -eu
|
|
||||||
page=$(curl -fsS --max-time 30 "$PRELAND_URL/?nc=$(date +%s)")
|
|
||||||
api=$(printf '%s' "$page" | grep -o 'const createApiUrl = "[^"]*"' || true)
|
|
||||||
echo "$api"
|
|
||||||
case "$api" in *'zoomzilla.ru\/api\/images\/public\/create'*) ;; *) echo "на странице нет адреса загрузки Zoomzilla"; exit 1 ;; esac
|
|
||||||
printf '%s' "$page" | grep -q 'claim?key=' || { echo "переход не на claim"; exit 1; }
|
|
||||||
@@ -34,7 +34,7 @@
|
|||||||
контент stage. Обычный push в `staging` должен исключать `[0-9]+/` из SFTP-заливки:
|
контент stage. Обычный push в `staging` должен исключать `[0-9]+/` из SFTP-заливки:
|
||||||
актуальные примеры нельзя перезаписывать Git-версией. Снимок stage фиксируется в
|
актуальные примеры нельзя перезаписывать Git-версией. Снимок stage фиксируется в
|
||||||
Git отдельной синхронизацией по задаче.
|
Git отдельной синхронизацией по задаче.
|
||||||
- Коммит в `main` запускает FTP-автодеплой на все цели workflow; откат через `git revert`
|
- Коммит в `staging` запускает SFTP-автодеплой только на zoomstage.online; откат через `git revert`
|
||||||
также запускает выкат. Коммит, push и изменения хостинга — только по прямому поручению.
|
также запускает выкат. Коммит, push и изменения хостинга — только по прямому поручению.
|
||||||
- Проверки кода и браузера проводить в Docker без установки ПО на хост.
|
- Проверки кода и браузера проводить в Docker без установки ПО на хост.
|
||||||
Проверка загрузки по боевому адресу вызывает реальную обработку; выполнять её только
|
Проверка загрузки по боевому адресу вызывает реальную обработку; выполнять её только
|
||||||
@@ -42,3 +42,6 @@
|
|||||||
|
|
||||||
План, журнал и база знаний ведутся в корне рабочей области по общим правилам. Ссылки на
|
План, журнал и база знаний ведутся в корне рабочей области по общим правилам. Ссылки на
|
||||||
политику и документацию не подключают MCP и не дают разрешения на публикацию или выкат.
|
политику и документацию не подключают MCP и не дают разрешения на публикацию или выкат.
|
||||||
|
|
||||||
|
## Актуальная маршрутизация (19.10.2026, preland#4)
|
||||||
|
До отдельного поручения владельца выкатывать только staging → zoomstage.online. Production zoompixel.ru/.online не обновлять. preland.anton.barbariskatest.ru больше не используется, не является целью и не должен упоминаться как требующий выпуска. CI запускает только stage. Сохранение runtime config и stage-наборов /N/ обязательно. Работа и решения — через Gitea.
|
||||||
@@ -5,8 +5,8 @@
|
|||||||
Варианты страницы и сайта (A/B) задаются метками в адресе. Код — обычный PHP без фреймворка и сборки; переводы —
|
Варианты страницы и сайта (A/B) задаются метками в адресе. Код — обычный PHP без фреймворка и сборки; переводы —
|
||||||
маленькая заглушка `Yii::t` в `i18n.php`.
|
маленькая заглушка `Yii::t` в `i18n.php`.
|
||||||
|
|
||||||
Боевой экземпляр: `https://preland.anton.barbariskatest.ru` (хостинг beget). Пример ссылки входа:
|
Активные production: `https://zoompixel.ru`, `https://zoompixel.online`; текущая цель выката — только `https://zoomstage.online`. Пример ссылки входа:
|
||||||
`https://preland.anton.barbariskatest.ru/?indpic=1&theme=dark°r=2®istration=after&bchoice=a&tp=1&utm_source=test&subid=CLICK1`.
|
`https://zoomstage.online/?indpic=1&theme=dark°r=2®istration=after&bchoice=a&tp=1&utm_source=test&subid=CLICK1`.
|
||||||
|
|
||||||
## Как работает
|
## Как работает
|
||||||
|
|
||||||
@@ -120,10 +120,7 @@ N/05_...
|
|||||||
git clone http://git.dira89kv.beget.tech:3000/anton/preland.git && cd preland
|
git clone http://git.dira89kv.beget.tech:3000/anton/preland.git && cd preland
|
||||||
# дальше — загрузка на хостинг: rsync/scp по SSH или FTP-клиент
|
# дальше — загрузка на хостинг: rsync/scp по SSH или FTP-клиент
|
||||||
```
|
```
|
||||||
3. **Конфиг и реквизиты.** Рабочий `config.json` уже лежит в репозитории и обязателен к доставке на каждый
|
3. **Конфиг и реквизиты.** Рабочий `config.json` на хостинге — источник истины. При первичном создании домена его настраивают отдельно; обычный deploy не заменяет его репозиторным снимком и не перезаписывает `requisites.html`. Менять можно только явно согласованные ключи после чтения текущего файла.
|
||||||
приленд. Его не заменяют локальным вариантом и не исключают из синхронизации. Реквизиты задаются ключом
|
|
||||||
`requisites` в нём либо runtime-файлом `requisites.html`. 2026-10-05 конфиг пропал после заливки,
|
|
||||||
и преленд слал загрузку на собственный домен; это больше не допускается.
|
|
||||||
4. **nginx без Apache** (если хостинг не обрабатывает `.htaccess`):
|
4. **nginx без Apache** (если хостинг не обрабатывает `.htaccess`):
|
||||||
```nginx
|
```nginx
|
||||||
server {
|
server {
|
||||||
@@ -154,41 +151,25 @@ N/05_...
|
|||||||
|
|
||||||
## Автодеплой (Gitea Actions)
|
## Автодеплой (Gitea Actions)
|
||||||
|
|
||||||
`.gitea/workflows/deploy.yml`: после каждого коммита в `main` раннер заливает файлы репозитория на хостинги **всех
|
Решение владельца от 19.10.2026, задача preland#4: сейчас выкатываем **только stage**.
|
||||||
целей** по FTP с TLS и проверяет страницу каждой. Цель — домен со своим хостингом; список целей — `matrix` в workflow:
|
|
||||||
|
|
||||||
```yaml
|
| Экземпляр | Назначение | Выкат |
|
||||||
- name: barbariskatest # имя цели (видно в списке прогонов: deploy barbariskatest)
|
|---|---|---|
|
||||||
url: https://preland.anton.barbariskatest.ru # адрес преленда — для проверки после заливки
|
| https://zoomstage.online | Тестовый стенд | Push в staging, SFTP |
|
||||||
prefix: PRELAND # секреты PRELAND_FTP_HOST, PRELAND_FTP_USER, PRELAND_FTP_PASS
|
| https://zoompixel.ru | Production | Отключён до отдельного поручения |
|
||||||
dir: "" # каталог сайта на FTP: "" — корень FTP (beget), иначе, например, public_html
|
| https://zoompixel.online | Production | Отключён до отдельного поручения |
|
||||||
```
|
|
||||||
|
|
||||||
Цели выкатываются независимо (`fail-fast: false`): сбой одного хостинга не останавливает остальные.
|
Прежний preland.anton.barbariskatest.ru выведен из использования и исключён из workflow.
|
||||||
|
Публикация изменений самого workflow в main не запускает доставку: триггер оставлен только для staging.
|
||||||
|
|
||||||
**Новый домен на своём хостинге:**
|
Workflow `.gitea/workflows/deploy.yml` содержит единственную job deploy-zoomstage на
|
||||||
1. Домен и HTTPS — как в «Деплой на произвольный домен», шаг 1.
|
выделенном runner zoomzilla-production. Production возвращается только отдельной задачей
|
||||||
2. Секреты `<ПРЕФИКС>_FTP_HOST`, `<ПРЕФИКС>_FTP_USER`, `<ПРЕФИКС>_FTP_PASS` (Gitea → настройки репозитория →
|
и прямым поручением владельца. Ошибки старого FTP больше не являются блокером выпуска.
|
||||||
Actions → секреты; порт 21, FTP с TLS). В коде реквизитов нет.
|
|
||||||
3. Никаких ручных конфигов не создавать: единственный `config.json` приезжает вместе с кодом.
|
|
||||||
4. Запись в `matrix` — коммит в `main`, автодеплой выкатит все цели вместе с конфигом.
|
|
||||||
|
|
||||||
**Что заливается.** Каждый deploy получает файлы репозитория (`git ls-files`), кроме служебных
|
Рабочие config.json/requisites.html на хостинге не перезаписываются. Корневые наборы
|
||||||
(`.gitea/`, `README.md`, `.gitignore`) и живущего на хостинге `requisites.html`. Единственный
|
/N/ принадлежат stage и исключены из кодовой доставки. Родительские каталоги создаются
|
||||||
версионируемый `config.json` всегда доставляется вместе с кодом.
|
штатным SFTP-генератором; smoke проверяет страницу и assets наборов1/2.
|
||||||
На хостинге ничего не удаляется: файлы, убранные из репозитория, удалять вручную.
|
Откат — отдельный согласованный revert в staging, который обновляет только stage.
|
||||||
|
|
||||||
**Проверка после заливки.** На странице цели должен быть адрес загрузки на сайте, а не на самом преленде (иначе нет
|
|
||||||
`config.json` с `host`), и переход на `claim`. Иначе прогон этой цели падает.
|
|
||||||
|
|
||||||
**Откат** — `git revert` проблемного коммита в `main`: автодеплой зальёт прежнюю версию на все цели.
|
|
||||||
|
|
||||||
**SFTP.** Сейчас только FTP с TLS. Хостинг без FTP — поле `protocol` в цели и `lftp` вместо curl в шаге заливки.
|
|
||||||
|
|
||||||
**Откат вручную.** Перед заменой файлов — копия изменяемых файлов (`index.php`, `components/*`) с хостинга; вернуть их той же
|
|
||||||
загрузкой. Новый преленд требует на сайте `/api/images/public/claim` (api с 2026-10-05): со старым api переход
|
|
||||||
по ключу отдаст 404.
|
|
||||||
|
|
||||||
## Android: фото и файлы (2026-10-19)
|
## Android: фото и файлы (2026-10-19)
|
||||||
Основная кнопка выбирает image/*; отдельная ссылка «Выбрать файлы (PDF и др.)» открывает общий picker без ограничения image/*. PDF в подписи скрывается по runtime API-policy. iOS/iPad/Desktop сохраняют прежний общий выбор. +много сохраняется в одной компактной mobile-строке. components/browser-upload.php и CSS upload-picker-links.php — лёгкие inline-копии соответствующих компонентов API: отдельный PHP-хост не использует Yii AssetManager, новый внешний запрос не добавляется. При изменении общего контракта синхронизировать обе копии; API mobile-picker.cjs покрывает обе реальные страницы. Android byte-copy FileProvider сохраняет исходные байты/имя/MIME и ограничен max_file_size_bytes; до ответа policy используется консервативный15MB предел копирования. UTM/client_key/claim/config.json не меняются.
|
Основная кнопка выбирает image/*; отдельная ссылка «Выбрать файлы (PDF и др.)» открывает общий picker без ограничения image/*. PDF в подписи скрывается по runtime API-policy. iOS/iPad/Desktop сохраняют прежний общий выбор. +много сохраняется в одной компактной mobile-строке. components/browser-upload.php и CSS upload-picker-links.php — лёгкие inline-копии соответствующих компонентов API: отдельный PHP-хост не использует Yii AssetManager, новый внешний запрос не добавляется. При изменении общего контракта синхронизировать обе копии; API mobile-picker.cjs покрывает обе реальные страницы. Android byte-copy FileProvider сохраняет исходные байты/имя/MIME и ограничен max_file_size_bytes; до ответа policy используется консервативный15MB предел копирования. UTM/client_key/claim/config.json не меняются.
|
||||||
|
|
||||||
@@ -64,7 +64,7 @@ if (!str_contains($workflow, '|[0-9]+/)')) {
|
|||||||
$failures[] = 'Stage-деплой не должен перезаписывать корневые наборы /N/: stage владеет их содержимым.';
|
$failures[] = 'Stage-деплой не должен перезаписывать корневые наборы /N/: stage владеет их содержимым.';
|
||||||
}
|
}
|
||||||
|
|
||||||
if (substr_count($workflow, 'while [ -n "$dir" ]; do') < 2) {
|
if (substr_count($workflow, 'while [ -n "$dir" ]; do') < 1) {
|
||||||
$failures[] = 'SFTP-деплой должен создавать все родительские каталоги для новых наборов assets.';
|
$failures[] = 'SFTP-деплой должен создавать все родительские каталоги для новых наборов assets.';
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -36,30 +36,20 @@ if (!is_array($config)) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
$workflow = (string) file_get_contents($root . '/.gitea/workflows/deploy.yml');
|
$workflow = (string) file_get_contents($root . '/.gitea/workflows/deploy.yml');
|
||||||
$legacyStart = strpos($workflow, ' deploy:');
|
|
||||||
$stageStart = strpos($workflow, ' deploy-zoomstage:');
|
$stageStart = strpos($workflow, ' deploy-zoomstage:');
|
||||||
$productionStart = strpos($workflow, 'deploy-zoompixel-production:');
|
$stageBlock = $stageStart === false ? '' : substr($workflow, $stageStart);
|
||||||
$legacyBlock = $legacyStart === false || $stageStart === false
|
if ($stageBlock === '' || !str_contains($stageBlock, 'config\\.json$')
|
||||||
? ''
|
|| !str_contains($stageBlock, 'ensure-focus-config.php')
|
||||||
: substr($workflow, $legacyStart, $stageStart - $legacyStart);
|
|| !str_contains($stageBlock, 'Preserve host config and add focus if absent')) {
|
||||||
$stageBlock = $stageStart === false || $productionStart === false
|
$failures[] = 'Stage обязан сохранять хостовый config.json и согласованный focus-контракт.';
|
||||||
? ''
|
|
||||||
: 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$')
|
|
||||||
|| !str_contains($block, 'ensure-focus-config.php')
|
|
||||||
|| !str_contains($block, 'Preserve host config and add focus if absent')) {
|
|
||||||
$failures[] = "{$delivery} обязан сохранять хостовый config.json и менять только явно согласованный focus.";
|
|
||||||
}
|
}
|
||||||
|
if (preg_match_all('/^ [a-z][a-z0-9-]*:/m', substr($workflow, strpos($workflow, 'jobs:')), $jobs) !== 1
|
||||||
|
|| !str_contains($workflow, " - staging")
|
||||||
|
|| str_contains($workflow, " - main")
|
||||||
|
|| str_contains($workflow, 'deploy-zoompixel-production:')
|
||||||
|
|| str_contains($workflow, 'barbariskatest')) {
|
||||||
|
$failures[] = 'По решению владельца автодеплой должен содержать только staging/zoomstage.online.';
|
||||||
}
|
}
|
||||||
|
|
||||||
if (is_file($root . '/config.json.example')) {
|
if (is_file($root . '/config.json.example')) {
|
||||||
$failures[] = 'config.json.example не должен существовать: рабочий config.json единственный.';
|
$failures[] = 'config.json.example не должен существовать: рабочий config.json единственный.';
|
||||||
}
|
}
|
||||||
@@ -69,4 +59,4 @@ if ($failures !== []) {
|
|||||||
exit(1);
|
exit(1);
|
||||||
}
|
}
|
||||||
|
|
||||||
fwrite(STDOUT, "OK: versioned stage config and ZoomPixel delivery\n");
|
fwrite(STDOUT, "OK: host config preserved; deploy targets stage only\n");
|
||||||
Reference in new issue
Block a user