chore(ci): отключить production и legacy автодеплой (refs #4)
This commit is contained in:
1 parent
029cd16d46
commit
8274946610
4 files changed
+68
-274
No files matched your search
+34
-213
@@ -1,151 +1,13 @@
|
||||
name: deploy-preland
|
||||
|
||||
# После коммита в main сохраняется legacy-выпуск на Beget. Новые домены ZoomPixel
|
||||
# доставляются отдельными SFTP job: staging — только на stage, main — только на 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 настраивается владельцем на каждом хостинге и является
|
||||
# источником истины. Обычный deploy его не перезаписывает; задача может менять
|
||||
# только прямо согласованные ключи после чтения текущего host-файла.
|
||||
# На хостинге ничего не удаляется: удалённое из репозитория убирать вручную.
|
||||
|
||||
# Решение владельца 19.10.2026, preland#4: только stage.
|
||||
# Production выкаты временно отключены; Барбариска больше не используется.
|
||||
on:
|
||||
push:
|
||||
branches:
|
||||
- main
|
||||
- staging
|
||||
|
||||
jobs:
|
||||
deploy:
|
||||
name: deploy ${{ matrix.name }}
|
||||
if: github.ref == 'refs/heads/main'
|
||||
# Legacy — тоже production-выпуск: выполняем на выделенном runner, а не
|
||||
# на общем Alpine-пуле, чтобы не зависеть от внешних apk mirrors.
|
||||
runs-on: zoomzilla-production
|
||||
|
||||
strategy:
|
||||
fail-fast: false
|
||||
matrix:
|
||||
include:
|
||||
# name — имя цели, url — адрес преленда (для проверки), prefix — префикс секретов <prefix>_FTP_*,
|
||||
# dir — каталог сайта на FTP ("" — корень FTP, как на beget). Новый домен — новая запись и три секрета;
|
||||
# requisites.html создают на хостинге цели. Рабочий config.json остаётся
|
||||
# на хостинге и обычным deploy не заменяется.
|
||||
- name: barbariskatest
|
||||
url: https://preland.anton.barbariskatest.ru
|
||||
prefix: PRELAND
|
||||
dir: ""
|
||||
|
||||
defaults:
|
||||
run:
|
||||
shell: sh
|
||||
|
||||
env:
|
||||
PRELAND_URL: ${{ matrix.url }}
|
||||
REMOTE_DIR: ${{ matrix.dir }}
|
||||
# Последний успешно опубликованный legacy-снимок. На нестабильный FTP
|
||||
# отправляем только изменения приложения после него, а не весь набор
|
||||
# уже присутствующих медиафайлов.
|
||||
LEGACY_DEPLOY_BASE: dfbb086e2b131a084f3de5b0f92c9cd69616c01a
|
||||
|
||||
steps:
|
||||
- name: Install tools
|
||||
run: apt-get update && DEBIAN_FRONTEND=noninteractive apt-get install -y --no-install-recommends ca-certificates git curl lftp php-cli
|
||||
|
||||
# На production runner образ задания не содержит Node.js, поэтому
|
||||
# actions/checkout не запускается. Клонируем тот же main напрямую:
|
||||
# это не меняет содержимое host config.json.
|
||||
- name: Checkout production branch
|
||||
run: git clone --branch main http://git.dira89kv.beget.tech:3000/anton/preland.git .
|
||||
|
||||
- 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
|
||||
script=$(mktemp)
|
||||
trap 'rm -f "$script"' EXIT
|
||||
git diff --name-only "$LEGACY_DEPLOY_BASE"..HEAD > changed-files.txt
|
||||
grep -vE '^(\.gitea/|README\.md$|AGENTS\.md$|CLAUDE\.md$|\.gitignore$|requisites\.html$|config\.json$|bin/|tests/)' \
|
||||
changed-files.txt > files.txt || true
|
||||
# имя уходит в адрес ftp:// как есть — пробелы и прочее экранирование не поддерживаем
|
||||
if grep -q '[^A-Za-z0-9._/-]' files.txt; then echo "имена с недопустимыми символами:"; grep '[^A-Za-z0-9._/-]' files.txt; exit 1; fi
|
||||
{
|
||||
echo 'set cmd:fail-exit yes'
|
||||
echo 'set ftp:ssl-force true'
|
||||
echo 'set ftp:ssl-protect-data true'
|
||||
echo 'set ssl:verify-certificate yes'
|
||||
echo 'set net:max-retries 3'
|
||||
echo 'set net:timeout 30'
|
||||
} > "$script"
|
||||
# Сервер legacy обрывает бинарные загрузки curl с 426. lftp ведёт
|
||||
# каждую передачу отдельной FTPS-командой, не ослабляя TLS.
|
||||
while read -r f; do
|
||||
echo "загрузка: $f"
|
||||
remote_dir=${f%/*}
|
||||
[ "$remote_dir" = "$f" ] && remote_dir='.'
|
||||
if [ "$remote_dir" != '.' ]; then
|
||||
# Legacy-сервер возвращает 550 и для уже существующего каталога.
|
||||
# Ошибку mkdir гасим, но put по-прежнему завершит выпуск ошибкой,
|
||||
# если каталог действительно нельзя использовать.
|
||||
printf 'mkdir -p %s%s || echo\n' "${dir:+$dir/}" "$remote_dir" >> "$script"
|
||||
fi
|
||||
printf 'put -O %s%s %s\n' \
|
||||
"${dir:+$dir/}" "$remote_dir" "$f" >> "$script"
|
||||
done < files.txt
|
||||
printf 'bye\n' >> "$script"
|
||||
lftp -u "$FTP_USER","$FTP_PASS" -e "source $script" "ftp://$FTP_HOST"
|
||||
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:
|
||||
name: deploy zoomstage.online
|
||||
if: github.ref == 'refs/heads/staging'
|
||||
@@ -200,12 +62,26 @@ jobs:
|
||||
known_hosts=$(mktemp)
|
||||
batch=$(mktemp)
|
||||
files=$(mktemp)
|
||||
trap 'rm -f "$known_hosts" "$batch" "$files"' EXIT
|
||||
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"
|
||||
# Наборы `/N/` на stage — редактируемый контент. Кодовая доставка их
|
||||
# намеренно не перезаписывает: актуальное содержимое stage фиксируют в Git
|
||||
# отдельной синхронизацией по задаче.
|
||||
git ls-files | grep -vE '^(\.gitea/|README\.md$|AGENTS\.md$|CLAUDE\.md$|\.gitignore$|requisites\.html$|config\.json$|[0-9]+/)' > "$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
|
||||
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")"
|
||||
@@ -218,35 +94,12 @@ jobs:
|
||||
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; }
|
||||
curl -fsS --max-time 30 "$PRELAND_URL/1/01_desk_1-source.avif" -o /dev/null || { echo "не опубликован первый набор примеров"; exit 1; }
|
||||
curl -fsS --max-time 30 "$PRELAND_URL/2/01_desk_1-source.avif" -o /dev/null || { echo "не опубликован второй набор примеров"; exit 1; }
|
||||
page2=$(curl -fsS --max-time 30 "$PRELAND_URL/?indpic=2&nc=$(date +%s)")
|
||||
printf '%s' "$page2" | grep -q '2/01_mob_1-source.avif' || { echo "indpic=2 не использует второй набор примеров"; exit 1; }
|
||||
|
||||
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
|
||||
- name: Remove obsolete demo directories
|
||||
env:
|
||||
SFTP_HOST: ${{ secrets.ZOOMPIXEL_SFTP_HOST }}
|
||||
SFTP_USER: ${{ secrets.ZOOMPIXEL_SFTP_USER }}
|
||||
@@ -254,50 +107,18 @@ jobs:
|
||||
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)
|
||||
trap 'rm -f "$known_hosts" "$batch" "$files"' EXIT
|
||||
trap 'rm -f "$known_hosts" "$batch"' 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"
|
||||
sed 's#/[^/]*$##' "$files" | grep -v '^$' | sort -u | 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"
|
||||
for set in demo demo-2; do
|
||||
for example in 1 2 3 4 5; do
|
||||
for file in source.avif source.jpg source.webp result.avif result.jpg result.webp thumb.avif thumb.jpg thumb.webp source_mob.avif result_mob.avif thumb_mob.avif; do
|
||||
printf '%s\n' "-rm $REMOTE_DIR/images/$set/$example/$file" >> "$batch"
|
||||
done
|
||||
printf '%s\n' "-rmdir $REMOTE_DIR/images/$set/$example" >> "$batch"
|
||||
done
|
||||
printf '%s\n' "-rmdir $REMOTE_DIR/images/$set" >> "$batch"
|
||||
done
|
||||
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; }
|
||||
@@ -30,7 +30,7 @@
|
||||
такого выпуска фактический stage-config следует забрать и зафиксировать в Git.
|
||||
`requisites.html` также живёт только на хостинге и не перезаписывается. Не
|
||||
создавать target-specific конфиги или `config.json.example`.
|
||||
- Коммит в `main` запускает FTP-автодеплой на все цели workflow; откат через `git revert`
|
||||
- Коммит в `staging` запускает SFTP-автодеплой только на zoomstage.online; откат через `git revert`
|
||||
также запускает выкат. Коммит, push и изменения хостинга — только по прямому поручению.
|
||||
- Проверки кода и браузера проводить в Docker без установки ПО на хост.
|
||||
Проверка загрузки по боевому адресу вызывает реальную обработку; выполнять её только
|
||||
@@ -38,3 +38,6 @@
|
||||
|
||||
План, журнал и база знаний ведутся в корне рабочей области по общим правилам. Ссылки на
|
||||
политику и документацию не подключают 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 без фреймворка и сборки; переводы —
|
||||
маленькая заглушка `Yii::t` в `i18n.php`.
|
||||
|
||||
Боевой экземпляр: `https://preland.anton.barbariskatest.ru` (хостинг beget). Пример ссылки входа:
|
||||
`https://preland.anton.barbariskatest.ru/?indpic=1&theme=dark°r=2®istration=after&bchoice=a&tp=1&utm_source=test&subid=CLICK1`.
|
||||
Активные production: `https://zoompixel.ru`, `https://zoompixel.online`; текущая цель выката — только `https://zoomstage.online`. Пример ссылки входа:
|
||||
`https://zoomstage.online/?indpic=1&theme=dark°r=2®istration=after&bchoice=a&tp=1&utm_source=test&subid=CLICK1`.
|
||||
|
||||
## Как работает
|
||||
|
||||
@@ -91,10 +91,7 @@ Preland всегда формирует полный resolved-набор сво
|
||||
git clone http://git.dira89kv.beget.tech:3000/anton/preland.git && cd preland
|
||||
# дальше — загрузка на хостинг: rsync/scp по SSH или FTP-клиент
|
||||
```
|
||||
3. **Конфиг и реквизиты.** Runtime `config.json` создаётся при первичной настройке хоста и не заменяется
|
||||
обычной синхронизацией. Менять в нём можно только ключи, явно согласованные в задаче; после такого выпуска
|
||||
актуальный stage-снимок фиксируется в Git. Реквизиты задаются ключом `requisites` либо runtime-файлом
|
||||
`requisites.html`.
|
||||
3. **Конфиг и реквизиты.** Рабочий `config.json` на хостинге — источник истины. При первичном создании домена его настраивают отдельно; обычный deploy не заменяет его репозиторным снимком и не перезаписывает `requisites.html`. Менять можно только явно согласованные ключи после чтения текущего файла.
|
||||
4. **nginx без Apache** (если хостинг не обрабатывает `.htaccess`):
|
||||
```nginx
|
||||
server {
|
||||
@@ -125,42 +122,25 @@ Preland всегда формирует полный resolved-набор сво
|
||||
|
||||
## Автодеплой (Gitea Actions)
|
||||
|
||||
`.gitea/workflows/deploy.yml`: после каждого коммита в `main` раннер заливает файлы репозитория на хостинги **всех
|
||||
целей** по FTP с TLS и проверяет страницу каждой. Цель — домен со своим хостингом; список целей — `matrix` в workflow:
|
||||
Решение владельца от 19.10.2026, задача preland#4: сейчас выкатываем **только stage**.
|
||||
|
||||
```yaml
|
||||
- name: barbariskatest # имя цели (видно в списке прогонов: deploy barbariskatest)
|
||||
url: https://preland.anton.barbariskatest.ru # адрес преленда — для проверки после заливки
|
||||
prefix: PRELAND # секреты PRELAND_FTP_HOST, PRELAND_FTP_USER, PRELAND_FTP_PASS
|
||||
dir: "" # каталог сайта на FTP: "" — корень FTP (beget), иначе, например, public_html
|
||||
```
|
||||
| Экземпляр | Назначение | Выкат |
|
||||
|---|---|---|
|
||||
| https://zoomstage.online | Тестовый стенд | Push в staging, SFTP |
|
||||
| https://zoompixel.ru | Production | Отключён до отдельного поручения |
|
||||
| https://zoompixel.online | Production | Отключён до отдельного поручения |
|
||||
|
||||
Цели выкатываются независимо (`fail-fast: false`): сбой одного хостинга не останавливает остальные.
|
||||
Прежний preland.anton.barbariskatest.ru выведен из использования и исключён из workflow.
|
||||
Публикация изменений самого workflow в main не запускает доставку: триггер оставлен только для staging.
|
||||
|
||||
**Новый домен на своём хостинге:**
|
||||
1. Домен и HTTPS — как в «Деплой на произвольный домен», шаг 1.
|
||||
2. Секреты `<ПРЕФИКС>_FTP_HOST`, `<ПРЕФИКС>_FTP_USER`, `<ПРЕФИКС>_FTP_PASS` (Gitea → настройки репозитория →
|
||||
Actions → секреты; порт 21, FTP с TLS). В коде реквизитов нет.
|
||||
3. Один раз создать runtime `config.json` с нужными значениями: последующие обычные deploy его не перезапишут.
|
||||
4. Запись в `matrix` — коммит в `main`, автодеплой выкатит код на все цели, сохраняя их конфиги.
|
||||
Workflow `.gitea/workflows/deploy.yml` содержит единственную job deploy-zoomstage на
|
||||
выделенном runner zoomzilla-production. Production возвращается только отдельной задачей
|
||||
и прямым поручением владельца. Ошибки старого FTP больше не являются блокером выпуска.
|
||||
|
||||
**Что заливается.** Каждый deploy получает файлы репозитория (`git ls-files`), кроме служебных
|
||||
(`.gitea/`, `README.md`, `.gitignore`), а также живущих на хостинге `requisites.html` и `config.json`.
|
||||
Перед разрешённым изменением конкретного конфигурационного ключа workflow считывает текущий host-файл и
|
||||
патчит только этот ключ.
|
||||
На хостинге ничего не удаляется: файлы, убранные из репозитория, удалять вручную.
|
||||
|
||||
**Проверка после заливки.** На странице цели должен быть адрес загрузки на сайте, а не на самом преленде (иначе нет
|
||||
`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.
|
||||
Рабочие config.json/requisites.html на хостинге не перезаписываются. Корневые наборы
|
||||
/N/ принадлежат stage и исключены из кодовой доставки. Родительские каталоги создаются
|
||||
штатным SFTP-генератором; smoke проверяет страницу и assets наборов1/2.
|
||||
Откат — отдельный согласованный revert в staging, который обновляет только stage.
|
||||
|
||||
## 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 не меняются.
|
||||
|
||||
@@ -36,30 +36,20 @@ if (!is_array($config)) {
|
||||
}
|
||||
|
||||
$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$')
|
||||
|| !str_contains($block, 'ensure-focus-config.php')
|
||||
|| !str_contains($block, 'Preserve host config and add focus if absent')) {
|
||||
$failures[] = "{$delivery} обязан сохранять хостовый config.json и менять только явно согласованный focus.";
|
||||
$stageBlock = $stageStart === false ? '' : substr($workflow, $stageStart);
|
||||
if ($stageBlock === '' || !str_contains($stageBlock, 'config\\.json$')
|
||||
|| !str_contains($stageBlock, 'ensure-focus-config.php')
|
||||
|| !str_contains($stageBlock, 'Preserve host config and add focus if absent')) {
|
||||
$failures[] = 'Stage обязан сохранять хостовый 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')) {
|
||||
$failures[] = 'config.json.example не должен существовать: рабочий config.json единственный.';
|
||||
}
|
||||
@@ -69,4 +59,4 @@ if ($failures !== []) {
|
||||
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