Compare commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
ba33a76a34 | ||
|
|
791761a782 | ||
|
|
4a6683f335 | ||
|
|
2970f990e0 | ||
|
|
875549a5af | ||
|
|
01f83375a7 | ||
|
|
469bec1595 | ||
|
|
f7d1792e23 | ||
|
|
1993f7b4b1 | ||
|
|
0d350ed14b | ||
|
|
94619432a3 | ||
|
|
941a4f01aa | ||
|
|
47405b27d8 | ||
|
|
a22519e754 | ||
|
|
00fbef9ede | ||
|
|
dd6d221dd7 | ||
|
|
84f70b1c71 | ||
|
|
f2097d4800 | ||
|
|
4fc4fd8162 | ||
|
|
6ec813bc99 | ||
|
|
522aaf8554 | ||
|
|
d04b40519e | ||
|
|
2cfa2e2d2e | ||
|
|
48c71f3300 | ||
|
|
9515c1c62b | ||
|
|
5b6b2d2053 | ||
|
|
b3b14ec193 | ||
|
|
847c642ca6 | ||
|
|
a1aa25671f | ||
|
|
965eb2e6a5 | ||
|
|
08febba368 | ||
|
|
81e189ca67 | ||
|
|
f30742c2fb | ||
|
|
f4ce5afdc4 | ||
|
|
89cc16eb4a | ||
|
|
188b78a115 | ||
|
|
f87bef2443 | ||
|
|
0e6611da73 | ||
|
|
198ba573f9 | ||
|
|
01933546a7 | ||
|
|
ad8c8eb849 | ||
|
|
5ffb0f37e9 | ||
|
|
c6b2b227c0 | ||
|
|
9c06977e03 | ||
|
|
3740839321 | ||
|
|
ec087ea636 | ||
|
|
a2c3197a16 | ||
|
|
57196e32a9 | ||
|
|
f2ad7ecb4c | ||
|
|
e9a2a0d568 | ||
|
|
144dda6d88 | ||
|
|
0c9a406d68 | ||
|
|
75ccf14889 | ||
|
|
6a2532cb87 | ||
|
|
4c5d3de7bc | ||
|
|
5afa71ec84 | ||
|
|
d70d396670 | ||
|
|
1a20ce17d5 | ||
|
|
f2e6877013 | ||
|
|
7eaf1c12b0 | ||
|
|
c30e3239a0 | ||
|
|
8274946610 | ||
|
|
d2637e8f61 | ||
|
|
0f06a18622 | ||
|
|
0274d5d921 | ||
|
|
dcb914a429 | ||
|
|
ab84dc1629 | ||
|
|
029cd16d46 | ||
|
|
3319499d5e | ||
|
|
953c7dd8f2 | ||
|
|
21f8fbb6a4 | ||
|
|
3ad7be0db3 | ||
|
|
b4dedcfea5 | ||
|
|
19547d1797 | ||
|
|
56456bb0cb | ||
|
|
78b24c91f8 | ||
|
|
be030f25a0 | ||
|
|
498e2df7b9 | ||
|
|
97fe9646b7 | ||
|
|
d646f98aa7 | ||
|
|
35b6a334ba | ||
|
|
a0dee79b22 | ||
|
|
730042385d | ||
|
|
a86978a499 | ||
|
|
ac4cc21361 | ||
|
|
f14ce88cc6 | ||
|
|
9835bb0ef8 | ||
|
|
2180d9e37b |
No files matched your search
@@ -0,0 +1,3 @@
|
||||
components/theme-client.php text eol=lf
|
||||
components/theme-switch.php text eol=lf
|
||||
components/theme-style.php text eol=lf
|
||||
@@ -1,118 +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 настраивается владельцем на каждом хостинге. Деплой не
|
||||
# перезаписывает его: он добавляет только отсутствующие обязательные UTM-ключи.
|
||||
# Единственное контролируемое удаление — завершённая миграция legacy images/demo* после
|
||||
# smoke новых корневых наборов /1, /2. Прочие файлы на хостинге не удаляются.
|
||||
|
||||
# Решение владельца 19.10.2026, preland#4: только stage.
|
||||
# Production выкаты временно отключены; Барбариска больше не используется.
|
||||
on:
|
||||
push:
|
||||
branches:
|
||||
- main
|
||||
- staging
|
||||
|
||||
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:
|
||||
name: deploy zoomstage.online
|
||||
if: github.ref == 'refs/heads/staging'
|
||||
@@ -125,12 +20,29 @@ jobs:
|
||||
REMOTE_DIR: domains/zoomstage.online
|
||||
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
|
||||
run: apt-get update && DEBIAN_FRONTEND=noninteractive apt-get install -y --no-install-recommends ca-certificates git curl openssh-client sshpass php-cli php-imagick
|
||||
|
||||
- name: Checkout staging branch
|
||||
run: git clone --depth 1 --branch staging http://git.dira89kv.beget.tech:3000/anton/preland.git .
|
||||
env:
|
||||
RELEASE_SHA: ${{ github.sha }}
|
||||
run: |
|
||||
set -eu
|
||||
git clone --depth 1 --branch staging http://git.dira89kv.beget.tech:3000/anton/preland.git .
|
||||
git fetch --depth 1 origin "$RELEASE_SHA"
|
||||
git checkout --detach "$RELEASE_SHA"
|
||||
[ "$(git rev-parse HEAD)" = "$RELEASE_SHA" ]
|
||||
|
||||
- name: Preserve host config and add focus if absent
|
||||
- name: Validate release and current stage sets before upload
|
||||
run: |
|
||||
set -eu
|
||||
php bin/release-assets.php --check
|
||||
for test in tests/check-*.php; do php "$test"; done
|
||||
php bin/release-assets.php --primary
|
||||
# preland#22: /N/ — runtime. Проверяем config и декодируем все 90
|
||||
# обязательных изображений используемых наборов, не сверяем с Git SHA.
|
||||
php bin/release-assets.php --stage-sets "$PRELAND_URL"
|
||||
|
||||
- name: Apply approved domain brand only if needed
|
||||
env:
|
||||
SFTP_HOST: ${{ secrets.ZOOMPIXEL_SFTP_HOST }}
|
||||
SFTP_USER: ${{ secrets.ZOOMPIXEL_SFTP_USER }}
|
||||
@@ -138,22 +50,24 @@ 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; }
|
||||
status=0
|
||||
php bin/release-brand.php --status "$PRELAND_URL" || status=$?
|
||||
case "$status" in 0) exit 0 ;; 10) ;; *) exit "$status" ;; esac
|
||||
# Только точная ранее согласованная пара заменяется по preland#12.
|
||||
# Обычные выпуски с правильным брендом сюда не доходят.
|
||||
umask 077
|
||||
known_hosts=$(mktemp); batch=$(mktemp); config=$(mktemp)
|
||||
trap 'rm -f "$known_hosts" "$batch" "$config"' EXIT
|
||||
known_hosts=$(mktemp); batch=$(mktemp); files=$(mktemp)
|
||||
trap 'rm -f "$known_hosts" "$batch" "$files"' 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
|
||||
|
||||
php bin/release-brand.php --files "$PRELAND_URL" > "$files"
|
||||
revision=$(git rev-parse HEAD)
|
||||
while read -r source target; do
|
||||
# Оборванный put не должен оставлять усечённый действующий SVG.
|
||||
printf '%s\n' "put -p $source $REMOTE_DIR/$target.upload-$revision" >> "$batch"
|
||||
printf '%s\n' "rename $REMOTE_DIR/$target.upload-$revision $REMOTE_DIR/$target" >> "$batch"
|
||||
done < "$files"
|
||||
SSHPASS="$SFTP_PASS" sshpass -e sftp -oBatchMode=no -oStrictHostKeyChecking=yes -oUserKnownHostsFile="$known_hosts" -b "$batch" "$SFTP_USER@$SFTP_HOST"
|
||||
php bin/release-brand.php --status "$PRELAND_URL"
|
||||
- name: Upload over SFTP
|
||||
env:
|
||||
SFTP_HOST: ${{ secrets.ZOOMPIXEL_SFTP_HOST }}
|
||||
@@ -170,7 +84,10 @@ jobs:
|
||||
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 — редактируемый контент. Кодовая доставка их
|
||||
# намеренно не перезаписывает. Обновление набора через админку не требует Git.
|
||||
# Положительный список: admin/tests/bin/планы/секреты не публикуются.
|
||||
php bin/release-assets.php --files | grep -vE '^(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"
|
||||
while read -r file; do
|
||||
@@ -185,9 +102,36 @@ jobs:
|
||||
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"
|
||||
SSHPASS="$SFTP_PASS" sshpass -e sftp -oBatchMode=no -oStrictHostKeyChecking=yes -oUserKnownHostsFile="$known_hosts" -b "$batch" "$SFTP_USER@$SFTP_HOST"
|
||||
echo "залито файлов: $(wc -l < "$files")"
|
||||
|
||||
# api#326/#327: сначала совместимый PHP-код, затем точечная миграция UTM.
|
||||
# chob→banch меняет только токен имени; веса/default владельца сохраняются.
|
||||
# Остальные поля владельца и их JSON-типы сохраняются.
|
||||
- name: Migrate numeric UTM in preserved host config
|
||||
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
|
||||
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" -b "$batch" "$SFTP_USER@$SFTP_HOST"
|
||||
changed=0
|
||||
php bin/ensure-utm-config.php "$config" || changed=$?
|
||||
case "$changed" in
|
||||
0) ;;
|
||||
10) revision=$(git rev-parse HEAD)
|
||||
printf '%s\n' "put -p $config $REMOTE_DIR/config.json.upload-$revision" "rename $REMOTE_DIR/config.json.upload-$revision $REMOTE_DIR/config.json" > "$batch"
|
||||
SSHPASS="$SFTP_PASS" sshpass -e sftp -oBatchMode=no -oStrictHostKeyChecking=yes -oUserKnownHostsFile="$known_hosts" -b "$batch" "$SFTP_USER@$SFTP_HOST" ;;
|
||||
*) exit "$changed" ;;
|
||||
esac
|
||||
|
||||
- name: Check the page
|
||||
run: |
|
||||
set -eu
|
||||
@@ -196,10 +140,29 @@ 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; }
|
||||
# Список наборов берётся из runtime config, а не фиксируется как /1/ и /2/.
|
||||
# Полная проверка изображений ниже; одиночный HTTP200 недостаточен.
|
||||
php bin/release-assets.php --remote "$PRELAND_URL"
|
||||
php bin/release-brand.php --status "$PRELAND_URL"
|
||||
|
||||
- name: Remove previously published internal files
|
||||
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
|
||||
umask 077
|
||||
known_hosts=$(mktemp); batch=$(mktemp)
|
||||
trap 'rm -f "$known_hosts" "$batch"' EXIT
|
||||
printf '%s\n' "$SFTP_KNOWN_HOSTS" > "$known_hosts"
|
||||
# Только точно известные служебные файлы прежнего deploy, без рекурсии.
|
||||
printf '%s\n' "-rm $REMOTE_DIR/requisites.html" > "$batch"
|
||||
git ls-files tests bin docs/superpowers | while read -r file; do
|
||||
printf '%s\n' "-rm $REMOTE_DIR/$file" >> "$batch"
|
||||
done
|
||||
SSHPASS="$SFTP_PASS" sshpass -e sftp -oBatchMode=no -oStrictHostKeyChecking=yes -oUserKnownHostsFile="$known_hosts" -b "$batch" "$SFTP_USER@$SFTP_HOST"
|
||||
|
||||
- name: Remove obsolete demo directories
|
||||
env:
|
||||
@@ -223,98 +186,4 @@ jobs:
|
||||
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"
|
||||
|
||||
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; }
|
||||
SSHPASS="$SFTP_PASS" sshpass -e sftp -oBatchMode=no -oStrictHostKeyChecking=yes -oUserKnownHostsFile="$known_hosts" -b "$batch" "$SFTP_USER@$SFTP_HOST"
|
||||
|
Before Width: | Height: | Size: 35 KiB After Width: | Height: | Size: 21 KiB |
|
Before Width: | Height: | Size: 132 KiB After Width: | Height: | Size: 139 KiB |
|
Before Width: | Height: | Size: 28 KiB After Width: | Height: | Size: 34 KiB |
|
Before Width: | Height: | Size: 90 KiB After Width: | Height: | Size: 44 KiB |
|
Before Width: | Height: | Size: 170 KiB After Width: | Height: | Size: 121 KiB |
|
Before Width: | Height: | Size: 62 KiB After Width: | Height: | Size: 65 KiB |
|
Before Width: | Height: | Size: 18 KiB After Width: | Height: | Size: 5.7 KiB |
|
Before Width: | Height: | Size: 79 KiB After Width: | Height: | Size: 72 KiB |
|
Before Width: | Height: | Size: 14 KiB After Width: | Height: | Size: 15 KiB |
|
Before Width: | Height: | Size: 33 KiB After Width: | Height: | Size: 4.0 KiB |
|
After Width: | Height: | Size: 11 KiB |
|
After Width: | Height: | Size: 4.9 KiB |
|
Before Width: | Height: | Size: 49 KiB After Width: | Height: | Size: 6.8 KiB |
|
After Width: | Height: | Size: 18 KiB |
|
After Width: | Height: | Size: 10 KiB |
|
Before Width: | Height: | Size: 5.6 KiB After Width: | Height: | Size: 1.5 KiB |
|
After Width: | Height: | Size: 2.2 KiB |
|
After Width: | Height: | Size: 1.6 KiB |
|
Before Width: | Height: | Size: 523 KiB After Width: | Height: | Size: 51 KiB |
|
Before Width: | Height: | Size: 719 KiB After Width: | Height: | Size: 309 KiB |
|
Before Width: | Height: | Size: 158 KiB After Width: | Height: | Size: 73 KiB |
|
Before Width: | Height: | Size: 148 KiB After Width: | Height: | Size: 71 KiB |
|
Before Width: | Height: | Size: 298 KiB After Width: | Height: | Size: 226 KiB |
|
Before Width: | Height: | Size: 100 KiB After Width: | Height: | Size: 99 KiB |
|
Before Width: | Height: | Size: 16 KiB After Width: | Height: | Size: 6.0 KiB |
|
Before Width: | Height: | Size: 71 KiB After Width: | Height: | Size: 69 KiB |
|
Before Width: | Height: | Size: 13 KiB After Width: | Height: | Size: 16 KiB |
|
Before Width: | Height: | Size: 5.9 KiB After Width: | Height: | Size: 9.6 KiB |
|
After Width: | Height: | Size: 26 KiB |
|
After Width: | Height: | Size: 16 KiB |
|
Before Width: | Height: | Size: 76 KiB After Width: | Height: | Size: 9.9 KiB |
|
After Width: | Height: | Size: 27 KiB |
|
After Width: | Height: | Size: 17 KiB |
|
Before Width: | Height: | Size: 4.7 KiB After Width: | Height: | Size: 1.3 KiB |
|
After Width: | Height: | Size: 2.0 KiB |
|
After Width: | Height: | Size: 1.4 KiB |
|
Before Width: | Height: | Size: 94 KiB After Width: | Height: | Size: 33 KiB |
|
Before Width: | Height: | Size: 303 KiB After Width: | Height: | Size: 234 KiB |
|
Before Width: | Height: | Size: 64 KiB After Width: | Height: | Size: 45 KiB |
|
Before Width: | Height: | Size: 163 KiB After Width: | Height: | Size: 41 KiB |
|
Before Width: | Height: | Size: 344 KiB After Width: | Height: | Size: 151 KiB |
|
Before Width: | Height: | Size: 118 KiB After Width: | Height: | Size: 56 KiB |
|
Before Width: | Height: | Size: 18 KiB After Width: | Height: | Size: 4.8 KiB |
|
Before Width: | Height: | Size: 78 KiB After Width: | Height: | Size: 64 KiB |
|
Before Width: | Height: | Size: 15 KiB After Width: | Height: | Size: 14 KiB |
|
Before Width: | Height: | Size: 64 KiB After Width: | Height: | Size: 5.5 KiB |
|
After Width: | Height: | Size: 14 KiB |
|
After Width: | Height: | Size: 7.3 KiB |
|
Before Width: | Height: | Size: 69 KiB After Width: | Height: | Size: 6.7 KiB |
|
After Width: | Height: | Size: 18 KiB |
|
After Width: | Height: | Size: 9.5 KiB |
|
Before Width: | Height: | Size: 5.1 KiB After Width: | Height: | Size: 1.4 KiB |
|
After Width: | Height: | Size: 2.1 KiB |
|
After Width: | Height: | Size: 1.4 KiB |
|
Before Width: | Height: | Size: 325 KiB After Width: | Height: | Size: 218 KiB |
|
Before Width: | Height: | Size: 533 KiB After Width: | Height: | Size: 716 KiB |
|
Before Width: | Height: | Size: 77 KiB After Width: | Height: | Size: 188 KiB |
|
Before Width: | Height: | Size: 144 KiB After Width: | Height: | Size: 57 KiB |
|
Before Width: | Height: | Size: 332 KiB After Width: | Height: | Size: 186 KiB |
|
Before Width: | Height: | Size: 93 KiB After Width: | Height: | Size: 81 KiB |
|
Before Width: | Height: | Size: 15 KiB After Width: | Height: | Size: 5.0 KiB |
|
Before Width: | Height: | Size: 70 KiB After Width: | Height: | Size: 66 KiB |
|
Before Width: | Height: | Size: 13 KiB After Width: | Height: | Size: 14 KiB |
|
Before Width: | Height: | Size: 5.4 KiB After Width: | Height: | Size: 7.2 KiB |
|
After Width: | Height: | Size: 17 KiB |
|
After Width: | Height: | Size: 9.8 KiB |
|
Before Width: | Height: | Size: 79 KiB After Width: | Height: | Size: 7.9 KiB |
|
After Width: | Height: | Size: 21 KiB |
|
After Width: | Height: | Size: 12 KiB |
|
Before Width: | Height: | Size: 5.0 KiB After Width: | Height: | Size: 1.4 KiB |
|
After Width: | Height: | Size: 2.1 KiB |
|
After Width: | Height: | Size: 1.4 KiB |
|
Before Width: | Height: | Size: 60 KiB After Width: | Height: | Size: 127 KiB |
|
Before Width: | Height: | Size: 224 KiB After Width: | Height: | Size: 535 KiB |
|
Before Width: | Height: | Size: 39 KiB After Width: | Height: | Size: 302 KiB |
|
Before Width: | Height: | Size: 91 KiB After Width: | Height: | Size: 58 KiB |
|
Before Width: | Height: | Size: 233 KiB After Width: | Height: | Size: 197 KiB |
|
Before Width: | Height: | Size: 66 KiB After Width: | Height: | Size: 74 KiB |
|
Before Width: | Height: | Size: 13 KiB After Width: | Height: | Size: 5.0 KiB |
|
Before Width: | Height: | Size: 68 KiB After Width: | Height: | Size: 65 KiB |
|
Before Width: | Height: | Size: 12 KiB After Width: | Height: | Size: 14 KiB |
|
Before Width: | Height: | Size: 4.1 KiB After Width: | Height: | Size: 6.6 KiB |
|
After Width: | Height: | Size: 17 KiB |
|
After Width: | Height: | Size: 9.3 KiB |
|
Before Width: | Height: | Size: 45 KiB After Width: | Height: | Size: 7.5 KiB |
|
After Width: | Height: | Size: 22 KiB |
|
After Width: | Height: | Size: 12 KiB |
|
Before Width: | Height: | Size: 4.5 KiB After Width: | Height: | Size: 1.4 KiB |
|
After Width: | Height: | Size: 2.0 KiB |
|
After Width: | Height: | Size: 1.4 KiB |
@@ -1,5 +1,18 @@
|
||||
# Инструкции агентов для `preland`
|
||||
|
||||
## Поручение 25.10.2026 — preland#21
|
||||
|
||||
Боевые цели теперь `zoompixel.ru` на основном сервере и `zoomila.ru`/`zoomika.ru`
|
||||
на двух REG.RU-хостингах. Владелец явно разрешил первоначальное размещение этих трёх
|
||||
целей; прежняя заморозка #4 для этой задачи снята. Единственный активный `.online` —
|
||||
`zoomstage.online` (stage). `zoompixel.online`, `zoomika.online`, `zoomila.online` отключены,
|
||||
каталоги убраны из рабочих FTP-папок с резервным сохранением; не включать без поручения.
|
||||
На обоих REG.RU-хостингах владельцу нужен SFTP или FTP; существующие доступы описаны
|
||||
в актуальном разделе README, пароли остаются в REG.RU. Доменные готовые SVG — в
|
||||
`brands/`, существующий runtime-config сохранять. Обычный автодеплой остаётся только
|
||||
на stage; новое поручение не включает production-автодеплой. При расхождении со старыми
|
||||
разделами ниже действует это датированное решение и задача #21.
|
||||
|
||||
Область действия — репозиторий `preland/`. Общие правила рабочей области находятся в
|
||||
[AGENTS.md](../AGENTS.md) и [CLAUDE.md](../CLAUDE.md); канонический процесс и каталог ролей —
|
||||
[в общей политике](../.known-base/Соглашения/Процесс%20командной%20разработки%20и%20каталог%20ролей.md).
|
||||
@@ -30,7 +43,11 @@
|
||||
такого выпуска фактический stage-config следует забрать и зафиксировать в Git.
|
||||
`requisites.html` также живёт только на хостинге и не перезаписывается. Не
|
||||
создавать target-specific конфиги или `config.json.example`.
|
||||
- Коммит в `main` запускает FTP-автодеплой на все цели workflow; откат через `git revert`
|
||||
- Корневые числовые наборы примеров `/N/` на `zoomstage.online` — редактируемый
|
||||
контент stage. Обычный push в `staging` должен исключать `[0-9]+/` из SFTP-заливки:
|
||||
актуальные примеры нельзя перезаписывать Git-версией. Снимок можно сохранить в
|
||||
Git по задаче; #22 не требует этого для обычного изменения runtime-набора.
|
||||
- Коммит в `staging` запускает SFTP-автодеплой только на zoomstage.online; откат через `git revert`
|
||||
также запускает выкат. Коммит, push и изменения хостинга — только по прямому поручению.
|
||||
- Проверки кода и браузера проводить в Docker без установки ПО на хост.
|
||||
Проверка загрузки по боевому адресу вызывает реальную обработку; выполнять её только
|
||||
@@ -38,3 +55,57 @@
|
||||
|
||||
План, журнал и база знаний ведутся в корне рабочей области по общим правилам. Ссылки на
|
||||
политику и документацию не подключают MCP и не дают разрешения на публикацию или выкат.
|
||||
|
||||
## Актуальная маршрутизация (19.10.2026, preland#4)
|
||||
До отдельного поручения владельца выкатывать только staging → zoomstage.online. Production zoompixel.ru/.online не обновлять. preland.anton.barbariskatest.ru больше не используется, не является целью и не должен упоминаться как требующий выпуска. CI запускает только stage. Сохранение runtime config и stage-наборов /N/ обязательно. Работа и решения — через Gitea.
|
||||
|
||||
## Актуальные ресурсы (21.10.2026, preland#11)
|
||||
|
||||
- Git main и staging содержат одно проверенное дерево. Источники: текущая zoomzilla.ru — два логотипа Zoomzilla, четыре PDF, favicon и реквизиты; zoomstage.online — все файлы наборов /1 и /2. История Git не переписывается; известная старая заглушка запрещена в рабочей поставке.
|
||||
- release/assets.json фиксирует SHA-256 всех статических ресурсов (текстовые SVG/CSS/HTML/webmanifest нормализуют CRLF→LF). До выпуска выполнить девять tests/check-*.php, php bin/release-assets.php --check, --primary и --stage-sets https://zoomstage.online. Если источник изменился, сначала синхронизировать актуальные файлы и manifest по задаче, затем повторить проверки. Не отключать guard ради выката.
|
||||
- --primary читает именно текущую страницу основного сайта и проверяет её актуальные URL/реквизиты: старый сохранённый asset URL не доказывает актуальность. Новые логотипы должны пройти визуальную проверку обеих тем/шапки/подвала. Cache version в components/asset-url.php зависит от содержимого.
|
||||
- По прямому выбору владельца21.10 requisites.html теперь входит в каноническую поставку. Единственное новое разрешённое изменение runtime-config — ключ requisites из release/requisites.json через bin/ensure-requisites-config.php; остальные байты полей сохраняются, повторный запуск ничего не меняет. Это явное исключение к прежнему host-only правилу реквизитов, а не разрешение заменять весь config.json. Git config.json хранит согласованный снимок; после выпуска сверять runtime в памяти, не сохранять потенциальные секреты в логи.
|
||||
- /N/ не перезаписывается кодовой доставкой. Требование #11 о сверке runtime-наборов с Git заменено решением #22 от 25.10.2026 (см. ниже).
|
||||
- SFTP берёт положительный список из bin/release-assets.php --files: никакие tests/bin/release/plans не публикуются. Старые известные служебные файлы удаляются только по точному списку git ls-files, без рекурсивной очистки. После выпуска --remote сверяет все184ресурса.
|
||||
- Production-преленды остаются замороженными по preland#4. Push main ничего не публикует; staging обновляет только zoomstage.online из точного event SHA.
|
||||
|
||||
## Доменный бренд и минимальный подвал (21.10.2026, preland#12)
|
||||
|
||||
Новое поручение владельца заменяет часть требований #11: в подвале остаются логотип и копирайт, без реквизитов и платёжных значков. Копирайт использует текущий HTTP_HOST без порта/trailing-dot, отдельно от config.host API; непроверенные Host и X-Forwarded-Host не выводятся.
|
||||
|
||||
Логотипы — готовые SVG-контуры, не текст на лету. Для zoomstage.online утверждено имя ZoomStage, слитно без зоны, обе темы. Источники лежат в brands/zoomstage.online/light.svg и dark.svg; release/brands.json содержит явную цель, имя и SHA. В docs/logo.svg и logo-alt.svg хранятся правильные canonical stage-копии для полного переноса. Их размеры182×47 совпадают с viewBox, чтобы загрузка SVG не меняла геометрию.
|
||||
|
||||
Обычный bin/release-assets.php --files исключает оба logo-alias из записи; runtime cache-version рассчитывается по реальному файлу хоста. Отдельный шаг release-brand.php --status разрешает только заранее согласованную миграцию #12:0 — оба SVG уже правильные, записи нет;10 — каждый файл имеет свой известный старый или новый SHA, можно закончить миграцию (включая частичный put);1 — неизвестный домен/хеш, остановка без подмены. --files выдаёт ровно утверждённые source→target. Иные логотипы не перезаписывать общим выкатом. Новые production-домены требуют отдельного добавления готовых пар и явного первоначального размещения; текущий stage-процесс их не активирует.
|
||||
|
||||
primary guard теперь сверяет PDF/favicon, а не заставляет возвращать Zoomzilla и реквизиты на преленд. Реквизиты не отображаются и больше автоматически не синхронизируются; хостовый config и его существующие значения не меняются этим поручением. Устаревшие requisites.html и patch-скрипт удалены из поставки. Статический manifest183, отдельный каталог брендов проверяется по собственным SHA. История #11 сохранена; её прежние инструкции о видимом бренде/реквизитах заменены этим датированным решением.
|
||||
|
||||
Перед выпуском: девять tests/check-*.php и lint, --check/--primary/--stage-sets, оба бренда шапки/подвала на desktop/mobile, перенос Host, отсутствие карт/реквизитов, холодная/медленная загрузка, реальные локальные Preland→Tour→Join/error/retry и геометрия. Production-преленды по #4 не включать; выпускать только staging→zoomstage.online. API продуктовый код не меняется.
|
||||
|
||||
## Runtime indpic и отдельная админка (25.10.2026, preland#22)
|
||||
|
||||
Это решение явно заменяет требование #11 о предварительном импорте изменённых
|
||||
наборов `/N/` в Git. Кодовая доставка сохраняет runtime `config.json` и `/N/`;
|
||||
обновление контента админкой не требует commit/push. Исторические записи `/N/`
|
||||
в `release/assets.json` остаются снимком, не источником истины runtime.
|
||||
|
||||
`php bin/release-assets.php --check` сохраняет строгую проверку состава и SHA
|
||||
поставляемой статики, включая запрет старого бренда. `--stage-sets URL` и
|
||||
`--remote URL` читают фактический config и проверяют default, номера/веса indpic,
|
||||
затем декодируют все 90 обязательных изображений каждого используемого набора,
|
||||
проверяют формат и размеры по контракту API `IndpicSetStorage` (591a67cc8302).
|
||||
Проверяются также ссылки с нулевым весом: их разрешает явный UTM и legacy random.
|
||||
Отсутствующий indpic/default использует штатный fallback `/1/`; явно неверный
|
||||
номер/default блокируется. Конфиг перечитывается после проверки для обнаружения
|
||||
его изменения. Одиночный HTTP200 не считается подтверждением валидного набора.
|
||||
|
||||
HTTP не предоставляет inventory каталога: лишние файлы удалённого набора этим
|
||||
режимом не исключаются. Для строгой проверки ровно 90 файлов без симлинков,
|
||||
вложенных каталогов и лишних элементов выполнить на фактическом webroot
|
||||
`php bin/check-runtime-sets.php /path/to/webroot` либо проверку через транспорт
|
||||
админки. Локальная проверка и HTTP требуют PHP Imagick с AVIF/WebP/JPEG.
|
||||
|
||||
`releaseFiles()` — положительный список публичных кодовых/статических ресурсов.
|
||||
`admin/`, его состояние/ключи/резервы, `bin/`, тесты и планы не публикуются обычной
|
||||
доставкой прелендинга. Админка имеет отдельный образ и процедуру выпуска.
|
||||
Проверки перед выпуском: все `tests/check-*.php`, manifest `--check`, `--primary`,
|
||||
`--stage-sets`; после — `--remote` и проверка бренда. Не ослаблять guard ради выпуска.
|
||||
@@ -1,9 +1,22 @@
|
||||
# Инструкции агентов для `preland`
|
||||
|
||||
## Поручение 25.10.2026 — preland#21
|
||||
|
||||
Боевые цели теперь `zoompixel.ru` на основном сервере и `zoomila.ru`/`zoomika.ru`
|
||||
на двух REG.RU-хостингах. Владелец явно разрешил первоначальное размещение этих трёх
|
||||
целей; прежняя заморозка #4 для этой задачи снята. Единственный активный `.online` —
|
||||
`zoomstage.online` (stage). `zoompixel.online`, `zoomika.online`, `zoomila.online` отключены,
|
||||
каталоги убраны из рабочих FTP-папок с резервным сохранением; не включать без поручения.
|
||||
На обоих REG.RU-хостингах владельцу нужен SFTP или FTP; существующие доступы описаны
|
||||
в актуальном разделе README, пароли остаются в REG.RU. Доменные готовые SVG — в
|
||||
`brands/`, существующий runtime-config сохранять. Обычный автодеплой остаётся только
|
||||
на stage; новое поручение не включает production-автодеплой. При расхождении со старыми
|
||||
разделами ниже действует это датированное решение и задача #21.
|
||||
|
||||
Область действия — репозиторий `preland/`. Общие правила рабочей области находятся в
|
||||
[AGENTS.md](../AGENTS.md) и [CLAUDE.md](../CLAUDE.md); канонический процесс и каталог ролей —
|
||||
[в общей политике](../.known-base/Соглашения/Процесс%20командной%20разработки%20и%20каталог%20ролей.md).
|
||||
Этот файл и [AGENTS.md](AGENTS.md) содержат одинаковые локальные правила; обновлять их вместе.
|
||||
Этот файл и [CLAUDE.md](CLAUDE.md) содержат одинаковые локальные правила; обновлять их вместе.
|
||||
|
||||
## Перед работой
|
||||
|
||||
@@ -21,10 +34,19 @@
|
||||
переход напрямую на `/public/upscale` не заменяет передачу владельца через claim.
|
||||
- `host` задаёт сайт назначения; HTTPS необходим для `crypto.randomUUID()`.
|
||||
Не обходить серверные правила доступа к загрузке и результату обработки.
|
||||
- Рабочий `config.json` единственный для всех прилендов: это точная проверенная
|
||||
конфигурация `zoomstage.online`, и каждый FTP/SFTP-deploy обязан доставлять её на
|
||||
stage и production. `requisites.html` по-прежнему живёт только на хостинге и не
|
||||
перезаписывается. Не создавать target-specific конфиги или `config.json.example`.
|
||||
- Рабочий `config.json` на каждом хостинге настраивает владелец. Деплой не имеет
|
||||
права перезаписывать его значений. При появлении новой обязательной UTM workflow
|
||||
получает текущий хостовый конфиг и меняет только ключи, которые прямо
|
||||
согласованы в задаче. Иные значения, включая уже настроенные UTM, остаются
|
||||
байт-в-байт прежними. Для #274 `bin/ensure-focus-config.php` — явное
|
||||
исключение: он задаёт только новый `focus=face` (default и rnd). После каждого
|
||||
такого выпуска фактический stage-config следует забрать и зафиксировать в Git.
|
||||
`requisites.html` также живёт только на хостинге и не перезаписывается. Не
|
||||
создавать target-specific конфиги или `config.json.example`.
|
||||
- Корневые числовые наборы примеров `/N/` на `zoomstage.online` — редактируемый
|
||||
контент stage. Обычный push в `staging` должен исключать `[0-9]+/` из SFTP-заливки:
|
||||
актуальные примеры нельзя перезаписывать Git-версией. Снимок можно сохранить в
|
||||
Git по задаче; #22 не требует этого для обычного изменения runtime-набора.
|
||||
- Коммит в `main` запускает FTP-автодеплой на все цели workflow; откат через `git revert`
|
||||
также запускает выкат. Коммит, push и изменения хостинга — только по прямому поручению.
|
||||
- Проверки кода и браузера проводить в Docker без установки ПО на хост.
|
||||
@@ -33,3 +55,54 @@
|
||||
|
||||
План, журнал и база знаний ведутся в корне рабочей области по общим правилам. Ссылки на
|
||||
политику и документацию не подключают MCP и не дают разрешения на публикацию или выкат.
|
||||
|
||||
## Актуальные ресурсы (21.10.2026, preland#11)
|
||||
|
||||
- Git main и staging содержат одно проверенное дерево. Источники: текущая zoomzilla.ru — два логотипа Zoomzilla, четыре PDF, favicon и реквизиты; zoomstage.online — все файлы наборов /1 и /2. История Git не переписывается; известная старая заглушка запрещена в рабочей поставке.
|
||||
- release/assets.json фиксирует SHA-256 всех статических ресурсов (текстовые SVG/CSS/HTML/webmanifest нормализуют CRLF→LF). До выпуска выполнить девять tests/check-*.php, php bin/release-assets.php --check, --primary и --stage-sets https://zoomstage.online. Если источник изменился, сначала синхронизировать актуальные файлы и manifest по задаче, затем повторить проверки. Не отключать guard ради выката.
|
||||
- --primary читает именно текущую страницу основного сайта и проверяет её актуальные URL/реквизиты: старый сохранённый asset URL не доказывает актуальность. Новые логотипы должны пройти визуальную проверку обеих тем/шапки/подвала. Cache version в components/asset-url.php зависит от содержимого.
|
||||
- По прямому выбору владельца21.10 requisites.html теперь входит в каноническую поставку. Единственное новое разрешённое изменение runtime-config — ключ requisites из release/requisites.json через bin/ensure-requisites-config.php; остальные байты полей сохраняются, повторный запуск ничего не меняет. Это явное исключение к прежнему host-only правилу реквизитов, а не разрешение заменять весь config.json. Git config.json хранит согласованный снимок; после выпуска сверять runtime в памяти, не сохранять потенциальные секреты в логи.
|
||||
- /N/ не перезаписывается кодовой доставкой. Требование #11 о сверке runtime-наборов с Git заменено решением #22 от 25.10.2026 (см. ниже).
|
||||
- SFTP берёт положительный список из bin/release-assets.php --files: никакие tests/bin/release/plans не публикуются. Старые известные служебные файлы удаляются только по точному списку git ls-files, без рекурсивной очистки. После выпуска --remote сверяет все184ресурса.
|
||||
- Production-преленды остаются замороженными по preland#4. Push main ничего не публикует; staging обновляет только zoomstage.online из точного event SHA.
|
||||
|
||||
## Доменный бренд и минимальный подвал (21.10.2026, preland#12)
|
||||
|
||||
Новое поручение владельца заменяет часть требований #11: в подвале остаются логотип и копирайт, без реквизитов и платёжных значков. Копирайт использует текущий HTTP_HOST без порта/trailing-dot, отдельно от config.host API; непроверенные Host и X-Forwarded-Host не выводятся.
|
||||
|
||||
Логотипы — готовые SVG-контуры, не текст на лету. Для zoomstage.online утверждено имя ZoomStage, слитно без зоны, обе темы. Источники лежат в brands/zoomstage.online/light.svg и dark.svg; release/brands.json содержит явную цель, имя и SHA. В docs/logo.svg и logo-alt.svg хранятся правильные canonical stage-копии для полного переноса. Их размеры182×47 совпадают с viewBox, чтобы загрузка SVG не меняла геометрию.
|
||||
|
||||
Обычный bin/release-assets.php --files исключает оба logo-alias из записи; runtime cache-version рассчитывается по реальному файлу хоста. Отдельный шаг release-brand.php --status разрешает только заранее согласованную миграцию #12:0 — оба SVG уже правильные, записи нет;10 — каждый файл имеет свой известный старый или новый SHA, можно закончить миграцию (включая частичный put);1 — неизвестный домен/хеш, остановка без подмены. --files выдаёт ровно утверждённые source→target. Иные логотипы не перезаписывать общим выкатом. Новые production-домены требуют отдельного добавления готовых пар и явного первоначального размещения; текущий stage-процесс их не активирует.
|
||||
|
||||
primary guard теперь сверяет PDF/favicon, а не заставляет возвращать Zoomzilla и реквизиты на преленд. Реквизиты не отображаются и больше автоматически не синхронизируются; хостовый config и его существующие значения не меняются этим поручением. Устаревшие requisites.html и patch-скрипт удалены из поставки. Статический manifest183, отдельный каталог брендов проверяется по собственным SHA. История #11 сохранена; её прежние инструкции о видимом бренде/реквизитах заменены этим датированным решением.
|
||||
|
||||
Перед выпуском: девять tests/check-*.php и lint, --check/--primary/--stage-sets, оба бренда шапки/подвала на desktop/mobile, перенос Host, отсутствие карт/реквизитов, холодная/медленная загрузка, реальные локальные Preland→Tour→Join/error/retry и геометрия. Production-преленды по #4 не включать; выпускать только staging→zoomstage.online. API продуктовый код не меняется.
|
||||
|
||||
## Runtime indpic и отдельная админка (25.10.2026, preland#22)
|
||||
|
||||
Это решение явно заменяет требование #11 о предварительном импорте изменённых
|
||||
наборов `/N/` в Git. Кодовая доставка сохраняет runtime `config.json` и `/N/`;
|
||||
обновление контента админкой не требует commit/push. Исторические записи `/N/`
|
||||
в `release/assets.json` остаются снимком, не источником истины runtime.
|
||||
|
||||
`php bin/release-assets.php --check` сохраняет строгую проверку состава и SHA
|
||||
поставляемой статики, включая запрет старого бренда. `--stage-sets URL` и
|
||||
`--remote URL` читают фактический config и проверяют default, номера/веса indpic,
|
||||
затем декодируют все 90 обязательных изображений каждого используемого набора,
|
||||
проверяют формат и размеры по контракту API `IndpicSetStorage` (591a67cc8302).
|
||||
Проверяются также ссылки с нулевым весом: их разрешает явный UTM и legacy random.
|
||||
Отсутствующий indpic/default использует штатный fallback `/1/`; явно неверный
|
||||
номер/default блокируется. Конфиг перечитывается после проверки для обнаружения
|
||||
его изменения. Одиночный HTTP200 не считается подтверждением валидного набора.
|
||||
|
||||
HTTP не предоставляет inventory каталога: лишние файлы удалённого набора этим
|
||||
режимом не исключаются. Для строгой проверки ровно 90 файлов без симлинков,
|
||||
вложенных каталогов и лишних элементов выполнить на фактическом webroot
|
||||
`php bin/check-runtime-sets.php /path/to/webroot` либо проверку через транспорт
|
||||
админки. Локальная проверка и HTTP требуют PHP Imagick с AVIF/WebP/JPEG.
|
||||
|
||||
`releaseFiles()` — положительный список публичных кодовых/статических ресурсов.
|
||||
`admin/`, его состояние/ключи/резервы, `bin/`, тесты и планы не публикуются обычной
|
||||
доставкой прелендинга. Админка имеет отдельный образ и процедуру выпуска.
|
||||
Проверки перед выпуском: все `tests/check-*.php`, manifest `--check`, `--primary`,
|
||||
`--stage-sets`; после — `--remote` и проверка бренда. Не ослаблять guard ради выпуска.
|
||||
@@ -1,39 +1,79 @@
|
||||
# Преленд zoomzilla
|
||||
|
||||
## Боевые домены — поручение 25.10.2026, preland#21
|
||||
|
||||
Три боевые цели: `zoompixel.ru` на основном сервере, `zoomila.ru` и `zoomika.ru`
|
||||
на двух отдельных хостингах REG.RU. Это отдельное разрешение владельца на первоначальное
|
||||
размещение/обновление этих целей после заморозки #4. Единственный активный `.online` —
|
||||
`zoomstage.online`, staging прелендингов. `zoompixel.online`, `zoomika.online` и
|
||||
`zoomila.online` отключены по дополнительному решению владельца; их регистрация сохранена.
|
||||
Рабочие FTP/SFTP-каталоги не должны содержать директории отключённых `.online`.
|
||||
|
||||
| Домен | Размещение | Пользователь SFTP/FTP | Каталог сайта |
|
||||
|---|---|---|---|
|
||||
| `zoomika.ru` | `server197.hosting.reg.ru`, `31.31.196.183` | `u3695919` | `/www/zoomika.ru` относительно домашнего каталога |
|
||||
| `zoomila.ru` | `server124.hosting.reg.ru`, `37.140.192.195` | `u3695920` | `/www/zoomila.ru` относительно домашнего каталога |
|
||||
| `zoompixel.ru` | основной сервер, `/srv/preland/domains/zoompixel.ru` | существующий административный доступ | отдельный контейнер `preland-zoompixel-ru-1` |
|
||||
|
||||
REG.RU выдаёт SFTP/SSH и FTP отдельно: SFTP использует порт 22 и пароль из блока
|
||||
«Доступ в панель управления, SSH, SFTP»; FTP — порт 21 и отдельный пароль из блока
|
||||
«Доступ к FTP». Пароли смотреть в кабинете REG.RU → нужный хостинг → «Доступы»;
|
||||
не помещать в Git и журналы. Оба аккаунта уже созданы провайдером, сбрасывать пароли
|
||||
для размещения не требуется. Полные домашние каталоги — `/var/www/<пользователь>/data`.
|
||||
В `www` каждого REG.RU-аккаунта оставлен только соответствующий `.ru`. Сайты `.online`
|
||||
выключены через ispmanager, их исходные файлы и установочные архивы сохранены вне webroot
|
||||
в скрытом `.preland21-backup/` (каталог `site`, файлы `release.zip`, `initial-index.html`).
|
||||
На основном сервере `zoompixel.online` отвечает HTTP/HTTPS 410; его контейнер удалён,
|
||||
сервис исключён из Compose, исходники сохранены в `/srv/preland/backups/preland21-disabled-online/site`.
|
||||
|
||||
Логотипы для обеих тем — готовые SVG в `brands/<домен>/`, реестр — `release/brands.json`.
|
||||
При первоначальном размещении копировать только положительный список `releaseFiles()`
|
||||
и проверенные статические ресурсы (включая `/1`, `/2`), затем доменную пару в
|
||||
`docs/logo.svg`/`docs/logo-alt.svg`. Служебные `bin/`, `tests/`, `.git/`, планы и
|
||||
установочные архивы в webroot не входят. Архивы REG.RU хранить вне `/www`.
|
||||
У новых пустых сайтов исходный config — проверенный stage-снимок; существующий
|
||||
`zoompixel.ru/config.json` сохраняется байт-в-байт, старые UTM нормализуются в памяти.
|
||||
|
||||
Перед трафиком обязательны доверенный TLS (самоподписанный сертификат REG.RU не подходит),
|
||||
HTTP→HTTPS, проверка двух тем/ширин, примеров, upload→claim→Tour→Join с UTM,
|
||||
видимой ошибки и повторной попытки. Точный состав, коммиты, результаты и ограничения — #21.
|
||||
Обычная автоматическая доставка по-прежнему только `staging` → `zoomstage.online`;
|
||||
данное первоначальное размещение не включает production-автодеплой по push `main`.
|
||||
Исторические разделы ниже описывают прежние цели; при расхождении действует этот раздел.
|
||||
|
||||
Одностраничный лендинг перед основным сайтом (`zoomzilla.ru`): посетитель с рекламы выбирает фото прямо на
|
||||
преленде, файл сразу уходит в API сайта, а сам посетитель переходит на сайт — на страницу обработки этого фото.
|
||||
Варианты страницы и сайта (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=e9st1mm12b&theme=e9st1mm12b°r=e9st2mm12b®=e9st2mm12b&banch=e9st2mm12b&tp=e9st1mm12b&utm_source=test&subid=CLICK1`.
|
||||
|
||||
## Как работает
|
||||
|
||||
### Метки
|
||||
### Метки (api#326)
|
||||
|
||||
`index.php` разбирает параметры адреса по разделу `utm` из `config.json`. Для каждой настроенной метки:
|
||||
Из публичного значения учитывается только пятый символ: цифра1–9 выбирает вариант,0 на Preland выбирает его по прежним `weights`. Отсутствующий, короткий, массивный и невалидный ввод получает `default`. Открытые `1`, `rnd`, `dark`, старые имена `registration/bchoice/chob/chb` больше не выбирают варианты. Точная длина входной строки не ограничена; Unicode-шум не изменяет позицию управляющего символа.
|
||||
|
||||
- значение из адреса принимается, если оно есть в `weights`;
|
||||
- `rnd` — случайное значение по весам (`weights`), так делится трафик между вариантами;
|
||||
- неизвестное значение отбрасывается, отсутствующее — заменяется на `default`.
|
||||
| Метка | Числовые значения |
|
||||
|---|---|
|
||||
| `indpic` | номер настроенного набора1–9 (`/1/`, `/2/` и т.д.) |
|
||||
| `theme` |1 dark,2 light |
|
||||
| `degr` | номер настроенной группы1–9 |
|
||||
| `reg` |1 before (PIN до оплаты),2 after |
|
||||
| `banch` |1 без попапа банка,2 с попапом |
|
||||
| `tp` | номер настроенной тарифной группы1–9 |
|
||||
| `cons` | номер группы согласий1–9; существование проверяет API |
|
||||
| `focus` |1 face,2 center |
|
||||
| `part` |1 показывать ссылку «Для бизнеса» на сайте,2 скрывать; Preland ссылку не показывает |
|
||||
|
||||
Ненастроенные параметры (`utm_source`, `keyword`, `subid` и любые другие) проходят как есть. Перед переходом
|
||||
Preland всегда формирует полный resolved-набор своих восьми меток: явное допустимое значение остаётся, `rnd`
|
||||
разрешается по весам, отсутствующее получает `default`. Вместе с внешними рекламными параметрами этот набор
|
||||
уходит в `claim`, а затем сайт переносит его в `upscale`; ни один из семи параметров не должен пропадать только
|
||||
потому, что был назначен default.
|
||||
`config.json` хранит публичные числовые `weights/default`. Внутри PHP сохраняются прежние semantic enum. Runtime-конфиг мигрируется `bin/ensure-utm-config.php`: только согласованные UTM, с сохранением весов, default и JSON-типов соседних полей. Скрипт идемпотентен; старые focus/cons patch-скрипты больше не вызываются доставкой.
|
||||
|
||||
| Метка | Что задаёт | Где |
|
||||
|---|---|---|
|
||||
| `indpic` | картинка-пример на преленде | корневой каталог сета `/1/`, `/2/` и т. д. |
|
||||
| `theme` | `light` / `dark` | преленд и сайт |
|
||||
| `degr` | пресет ухудшения превью «До» | сайт (#42) |
|
||||
| `registration` | `before` — PIN до оплаты, `after` — после | сайт, страница результата |
|
||||
| `bchoice` | попап выбора банка (`a` — показать) | сайт, оплата (#44) |
|
||||
| `chb` | галочки согласия на странице оплаты (`no` — не обязательны) | сайт, оплата |
|
||||
| `tp` | группа тарифов | сайт, cpanel «Группы тарифов (tp)» |
|
||||
| `focus` | первоначальная область лупы Join: `center`/`face` | сайт, cpanel после `tp` |
|
||||
В api#327 публичное `chob` заменено на `banch`; внутренний `bchoice` сохраняется. Мигратор переименовывает текущий host-ключ токенно, сохраняя все остальные байты, включая веса и default. Одновременные `banch/chob/bchoice` блокируют patch без записи. До миграции код читает прежний host-config; сохранённые recovery-записи с `utmDigits.chob` или `redirectUtm` продолжают выбранный вариант и выпускают уже `banch`.
|
||||
|
||||
Перед каждым переходом Preland создаёт для всех девяти меток новый шум из10 ASCII букв/цифр, с уже выбранной цифрой в пятой позиции. Retry и recovery меняют только шум; вариант повторно не выбирается. Выбранные цифры сохраняются в upload record. Старые записи IndexedDB адаптируются только на пути recovery, без публичных alias старого URL.
|
||||
|
||||
Чужие параметры (`utm_source`, `keyword`, `subid` и любые другие) сохраняют значения после обычного URL-кодирования. Технические `key/id/stage/shown_theme` не маскируются; внешний `key` не может подменить одноразовый ключ загрузки. `shown_theme` берётся из фактически показанной темы непосредственно при переходе.
|
||||
|
||||
### Загрузка и переход на сайт
|
||||
|
||||
@@ -76,7 +116,7 @@ Preland всегда формирует полный resolved-набор сво
|
||||
| `requisites` | нет | реквизиты в подвале — список строк, каждая отдельным абзацем (текст экранируется). Важнее `requisites.html`; без обоих — заглушка «ИП Иванов» |
|
||||
| `assetsUriPrefix` | нет | откуда брать статику. Пусто — из своего каталога; если на хостинге нет каталога `images`, статика берётся с сайта (`https://<host>`) |
|
||||
| `fontsUrl`, `faviconUrl`, `spritePath` | нет | адреса шрифтов, иконок и SVG-спрайта, если они не в `assetsUriPrefix` |
|
||||
| `logoAltExt` | нет | расширение второго логотипа (`svg` по умолчанию) |
|
||||
| `logoAltExt` | устарело | После preland#11 обе темы используют проверенные SVG Zoomzilla; расширение не меняется runtime-конфигом. |
|
||||
| `title` | нет | заголовок страницы («Улучшение изображения») |
|
||||
| `default_language` | нет | сейчас не действует: язык всегда `ru` |
|
||||
|
||||
@@ -99,10 +139,16 @@ Preland хранит наборы ровно как Index, поэтому их
|
||||
N/05_...
|
||||
```
|
||||
|
||||
`indpic=N` выбирает корневой каталог `/N/`. Набор копируется с Index в корень
|
||||
`indpic=abcdNefghi` выбирает корневой каталог `/N/`. Набор копируется с Index в корень
|
||||
домена целиком: без переименования, вложения в `images/` и промежуточных
|
||||
каталогов. Устаревшие `images/demo*` не являются частью поставки.
|
||||
|
||||
Для `zoomstage.online` обычный deploy ветки `staging` намеренно **не загружает**
|
||||
корневые числовые каталоги `/N/`: это редактируемый контент stage, а не артефакт
|
||||
кодовой доставки. По #22 workflow проверяет их полный контракт по фактическому
|
||||
config через runtime guard. Импорт изменённого набора в Git не требуется;
|
||||
следующая заливка не перетрёт его старой ревизией.
|
||||
|
||||
## Деплой на произвольный домен
|
||||
|
||||
**Требования.** PHP 8.0+ (боевой хостинг — 8.2), веб-сервер с `index.php` в роли индексного файла и **HTTPS**:
|
||||
@@ -114,10 +160,7 @@ N/05_...
|
||||
git clone http://git.dira89kv.beget.tech:3000/anton/preland.git && cd preland
|
||||
# дальше — загрузка на хостинг: rsync/scp по SSH или FTP-клиент
|
||||
```
|
||||
3. **Конфиг и реквизиты.** Рабочий `config.json` уже лежит в репозитории и обязателен к доставке на каждый
|
||||
приленд. Его не заменяют локальным вариантом и не исключают из синхронизации. Реквизиты задаются ключом
|
||||
`requisites` в нём либо runtime-файлом `requisites.html`. 2026-10-05 конфиг пропал после заливки,
|
||||
и преленд слал загрузку на собственный домен; это больше не допускается.
|
||||
3. **Конфиг и реквизиты.** Рабочий `config.json` на хостинге — источник истины. При первичном создании домена его настраивают отдельно; обычный deploy не заменяет его репозиторным снимком и не перезаписывает `requisites.html`. Менять можно только явно согласованные ключи после чтения текущего файла.
|
||||
4. **nginx без Apache** (если хостинг не обрабатывает `.htaccess`):
|
||||
```nginx
|
||||
server {
|
||||
@@ -142,43 +185,88 @@ N/05_...
|
||||
curl -s -o /dev/null -w '%{http_code} %{redirect_url}\n' 'https://<host>/api/images/public/claim?key=x'
|
||||
# 302 на https://<host>/ — точка передачи на сайте есть
|
||||
```
|
||||
В браузере: открыть `https://<домен>/?indpic=1&theme=dark&subid=TEST`, выбрать фото — адрес сменится на
|
||||
`https://<host>/public/upscale?id=…&indpic=1&theme=dark&subid=TEST…` (без `key=`), пойдёт обработка.
|
||||
В браузере: открыть `https://<домен>/?indpic=abcd1efghi&theme=abcd1efghi&subid=TEST`, выбрать фото — адрес сменится на
|
||||
`https://<host>/public/upscale?id=…&indpic=<новый шум с1>&theme=<новый шум с1>&subid=TEST…` (без `key=`), пойдёт обработка.
|
||||
Это настоящая загрузка и обработка на сайте.
|
||||
|
||||
## Автодеплой (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. Никаких ручных конфигов не создавать: единственный `config.json` приезжает вместе с кодом.
|
||||
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` всегда доставляется вместе с кодом.
|
||||
На хостинге ничего не удаляется: файлы, убранные из репозитория, удалять вручную.
|
||||
Рабочие config.json/requisites.html на хостинге не перезаписываются. Корневые наборы
|
||||
/N/ принадлежат stage и исключены из кодовой доставки. Родительские каталоги создаются
|
||||
штатным SFTP-генератором; smoke проверяет страницу и assets наборов1/2.
|
||||
Откат — отдельный согласованный revert в staging, который обновляет только stage.
|
||||
|
||||
**Проверка после заливки.** На странице цели должен быть адрес загрузки на сайте, а не на самом преленде (иначе нет
|
||||
`config.json` с `host`), и переход на `claim`. Иначе прогон этой цели падает.
|
||||
## 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 не меняются.
|
||||
|
||||
**Откат** — `git revert` проблемного коммита в `main`: автодеплой зальёт прежнюю версию на все цели.
|
||||
## Актуальные ресурсы (21.10.2026, preland#11)
|
||||
|
||||
**SFTP.** Сейчас только FTP с TLS. Хостинг без FTP — поле `protocol` в цели и `lftp` вместо curl в шаге заливки.
|
||||
- Git main и staging содержат одно проверенное дерево. Источники: текущая zoomzilla.ru — два логотипа Zoomzilla, четыре PDF, favicon и реквизиты; zoomstage.online — все файлы наборов /1 и /2. История Git не переписывается; известная старая заглушка запрещена в рабочей поставке.
|
||||
- release/assets.json фиксирует SHA-256 всех статических ресурсов (текстовые SVG/CSS/HTML/webmanifest нормализуют CRLF→LF). До выпуска выполнить девять tests/check-*.php, php bin/release-assets.php --check, --primary и --stage-sets https://zoomstage.online. Если источник изменился, сначала синхронизировать актуальные файлы и manifest по задаче, затем повторить проверки. Не отключать guard ради выката.
|
||||
- --primary читает именно текущую страницу основного сайта и проверяет её актуальные URL/реквизиты: старый сохранённый asset URL не доказывает актуальность. Новые логотипы должны пройти визуальную проверку обеих тем/шапки/подвала. Cache version в components/asset-url.php зависит от содержимого.
|
||||
- По прямому выбору владельца21.10 requisites.html теперь входит в каноническую поставку. Единственное новое разрешённое изменение runtime-config — ключ requisites из release/requisites.json через bin/ensure-requisites-config.php; остальные байты полей сохраняются, повторный запуск ничего не меняет. Это явное исключение к прежнему host-only правилу реквизитов, а не разрешение заменять весь config.json. Git config.json хранит согласованный снимок; после выпуска сверять runtime в памяти, не сохранять потенциальные секреты в логи.
|
||||
- /N/ не перезаписывается кодовой доставкой. Требование #11 о сверке runtime-наборов с Git заменено решением #22 от 25.10.2026 (см. ниже).
|
||||
- SFTP берёт положительный список из bin/release-assets.php --files: никакие tests/bin/release/plans не публикуются. Старые известные служебные файлы удаляются только по точному списку git ls-files, без рекурсивной очистки. После выпуска --remote сверяет все184ресурса.
|
||||
- Production-преленды остаются замороженными по preland#4. Push main ничего не публикует; staging обновляет только zoomstage.online из точного event SHA.
|
||||
|
||||
**Откат вручную.** Перед заменой файлов — копия изменяемых файлов (`index.php`, `components/*`) с хостинга; вернуть их той же
|
||||
загрузкой. Новый преленд требует на сайте `/api/images/public/claim` (api с 2026-10-05): со старым api переход
|
||||
по ключу отдаст 404.
|
||||
Повторная проверка выпуска: php bin/release-assets.php --stage-requisites https://zoomstage.online проверяет именно runtime config; успешная загрузка requisites.html сама по себе недостаточна. Все обязательные SFTP-команды выполняются через batch -b и прекращают выкат при ошибке put.
|
||||
|
||||
## Доменный бренд и минимальный подвал (21.10.2026, preland#12)
|
||||
|
||||
Новое поручение владельца заменяет часть требований #11: в подвале остаются логотип и копирайт, без реквизитов и платёжных значков. Копирайт использует текущий HTTP_HOST без порта/trailing-dot, отдельно от config.host API; непроверенные Host и X-Forwarded-Host не выводятся.
|
||||
|
||||
Логотипы — готовые SVG-контуры, не текст на лету. Для zoomstage.online утверждено имя ZoomStage, слитно без зоны, обе темы. Источники лежат в brands/zoomstage.online/light.svg и dark.svg; release/brands.json содержит явную цель, имя и SHA. В docs/logo.svg и logo-alt.svg хранятся правильные canonical stage-копии для полного переноса. Их размеры182×47 совпадают с viewBox, чтобы загрузка SVG не меняла геометрию.
|
||||
|
||||
Обычный bin/release-assets.php --files исключает оба logo-alias из записи; runtime cache-version рассчитывается по реальному файлу хоста. Отдельный шаг release-brand.php --status разрешает только заранее согласованную миграцию #12:0 — оба SVG уже правильные, записи нет;10 — каждый файл имеет свой известный старый или новый SHA, можно закончить миграцию (включая частичный put);1 — неизвестный домен/хеш, остановка без подмены. --files выдаёт ровно утверждённые source→target. Иные логотипы не перезаписывать общим выкатом. Новые production-домены требуют отдельного добавления готовых пар и явного первоначального размещения; текущий stage-процесс их не активирует.
|
||||
|
||||
primary guard теперь сверяет PDF/favicon, а не заставляет возвращать Zoomzilla и реквизиты на преленд. Реквизиты не отображаются и больше автоматически не синхронизируются; хостовый config и его существующие значения не меняются этим поручением. Устаревшие requisites.html и patch-скрипт удалены из поставки. Статический manifest183, отдельный каталог брендов проверяется по собственным SHA. История #11 сохранена; её прежние инструкции о видимом бренде/реквизитах заменены этим датированным решением.
|
||||
|
||||
Перед выпуском: девять tests/check-*.php и lint, --check/--primary/--stage-sets, оба бренда шапки/подвала на desktop/mobile, перенос Host, отсутствие карт/реквизитов, холодная/медленная загрузка, реальные локальные Preland→Tour→Join/error/retry и геометрия. Production-преленды по #4 не включать; выпускать только staging→zoomstage.online. API продуктовый код не меняется.
|
||||
|
||||
Дополнение приёмки #12: tests/e2e/brand-footer.cjs проверяет именно видимый SVG по центру шапки, а не только headerbox. PUBLISHED=1 использует zoomstage.online (или PRELAND_URL); без него — изолированный pre301.localhost QA proxy. Запускать в имеющемся Docker Playwright с /candidate=репозиторий,/qa=артефакты; POST/PUT блокируются. Пропущенное при compact CSS правило Bootstrap navbar>.container восстановлено во всех3CSS: API уже имеет его из полного Bootstrap. Это устраняет переход logo y0→13.5px, который рамочный тест не замечал.
|
||||
|
||||
## Runtime indpic и отдельная админка (25.10.2026, preland#22)
|
||||
|
||||
Это решение явно заменяет требование #11 о предварительном импорте изменённых
|
||||
наборов `/N/` в Git. Кодовая доставка сохраняет runtime `config.json` и `/N/`;
|
||||
обновление контента админкой не требует commit/push. Исторические записи `/N/`
|
||||
в `release/assets.json` остаются снимком, не источником истины runtime.
|
||||
|
||||
`php bin/release-assets.php --check` сохраняет строгую проверку состава и SHA
|
||||
поставляемой статики, включая запрет старого бренда. `--stage-sets URL` и
|
||||
`--remote URL` читают фактический config и проверяют default, номера/веса indpic,
|
||||
затем декодируют все 90 обязательных изображений каждого используемого набора,
|
||||
проверяют формат и размеры по контракту API `IndpicSetStorage` (591a67cc8302).
|
||||
Проверяются также ссылки с нулевым весом: их разрешает явный UTM и legacy random.
|
||||
Отсутствующий indpic/default использует штатный fallback `/1/`; явно неверный
|
||||
номер/default блокируется. Конфиг перечитывается после проверки для обнаружения
|
||||
его изменения. Одиночный HTTP200 не считается подтверждением валидного набора.
|
||||
|
||||
HTTP не предоставляет inventory каталога: лишние файлы удалённого набора этим
|
||||
режимом не исключаются. Для строгой проверки ровно 90 файлов без симлинков,
|
||||
вложенных каталогов и лишних элементов выполнить на фактическом webroot
|
||||
`php bin/check-runtime-sets.php /path/to/webroot` либо проверку через транспорт
|
||||
админки. Локальная проверка и HTTP требуют PHP Imagick с AVIF/WebP/JPEG.
|
||||
|
||||
`releaseFiles()` — положительный список публичных кодовых/статических ресурсов.
|
||||
`admin/`, его состояние/ключи/резервы, `bin/`, тесты и планы не публикуются обычной
|
||||
доставкой прелендинга. Админка имеет отдельный образ и процедуру выпуска.
|
||||
Проверки перед выпуском: все `tests/check-*.php`, manifest `--check`, `--primary`,
|
||||
`--stage-sets`; после — `--remote` и проверка бренда. Не ослаблять guard ради выпуска.
|
||||
|
||||
api#347: part — девятая UTM, пятый символ1/2, default1. Preland только передаёт её на сайт и никогда не показывает ссылку «Для бизнеса». Отсутствующая группа/веса сохраняют fallback1; случайный выбор использует существующий порядок и настроенные веса. Runtime config автоматически не дополняется. Старые сохранённые ссылки конструктора получают part1 без изменения шума остальных меток; старые заметки читаются с пустой заметкой part.
|
||||
@@ -0,0 +1,6 @@
|
||||
tests
|
||||
vendor
|
||||
state
|
||||
secret.env
|
||||
.env
|
||||
*.log
|
||||
@@ -0,0 +1,5 @@
|
||||
/vendor/
|
||||
/state/
|
||||
/secret.env
|
||||
/.env
|
||||
/*.log
|
||||
@@ -0,0 +1,23 @@
|
||||
FROM php:8.3-apache-bookworm@sha256:cbd3b3d9011668443d54ab40ec31db56c73d91b4c8198935d639aa80d2d815c1
|
||||
|
||||
RUN apt-get update \
|
||||
&& apt-get install -y --no-install-recommends libmagickwand-dev libzip-dev unzip ca-certificates git \
|
||||
&& docker-php-ext-install zip \
|
||||
&& printf '\n' | pecl install imagick-3.8.0 \
|
||||
&& docker-php-ext-enable imagick \
|
||||
&& a2enmod rewrite headers \
|
||||
&& groupmod -g 987 www-data && usermod -u 997 -g 987 www-data \
|
||||
&& rm -rf /var/lib/apt/lists/*
|
||||
|
||||
COPY --from=composer:2@sha256:b6dcd72ca98fa19d0e5b8476de179859704891fad06dd9e0b96833d4c430a5fc /usr/bin/composer /usr/local/bin/composer
|
||||
RUN docker-php-ext-install pcntl
|
||||
WORKDIR /app
|
||||
COPY composer.* ./
|
||||
RUN COMPOSER_IPRESOLVE=4 composer install --no-dev --no-interaction --prefer-dist --no-progress --optimize-autoloader
|
||||
COPY . /app
|
||||
COPY deploy/apache.conf /etc/apache2/sites-available/000-default.conf
|
||||
COPY deploy/php.ini /usr/local/etc/php/conf.d/preland-admin.ini
|
||||
RUN mkdir -p /var/lib/preland-admin && chown 997:987 /var/lib/preland-admin
|
||||
ENV ADMIN_STATE_DIR=/var/lib/preland-admin
|
||||
EXPOSE 80
|
||||
CMD ["apache2-foreground"]
|
||||
@@ -0,0 +1,273 @@
|
||||
# Админка прелендингов — preland#22
|
||||
|
||||
Отдельное PHP-приложение для `admin.zoomstage.online`. Формы редактируют фактические
|
||||
`config.json` в общей таблице; сохранённые настройки можно скопировать в выбранные домены. ZIP indpic
|
||||
проверяется по контракту CPanel и доставляется на выбранные хостинги. Изменение контента
|
||||
не требует Git/push/deploy; `admin/` не входит в публичную поставку прелендингов.
|
||||
|
||||
Реквизиты и устаревший `utm.chb` скрыты и защищены от записи. Неизвестные поля показаны только для чтения и сохраняются.
|
||||
|
||||
Спецификация и план — `docs/superpowers/{specs,plans}/2026-10-25-preland22-*` в репозитории,
|
||||
копии и фактический статус — в базе знаний, основной реестр — preland#22.
|
||||
|
||||
## Сводка и конструктор ссылки
|
||||
|
||||
Первый экран — редактор фактических конфигов всех подключений. Основной сайт,
|
||||
веса и default редактируются в соответствующей колонке. Веса хранятся в процентах:
|
||||
0..100 с двумя знаками; сумма группы — 100% либо все веса — 0%. Default выбирается radio обычного
|
||||
начертания. Отсутствующая группа добавляется только явно. «Сохранить» и «Отмена»
|
||||
работают отдельно для каждого домена; ошибка и конфликт сохраняют черновик.
|
||||
Перечитывание другой колонки сохраняет DOM, фокус и курсор. После конфликта нужно
|
||||
явно загрузить сохранённое либо отменить черновик. Версии CAS остаются внутренними;
|
||||
даты заметок и версии конфигов в таблице не показываются. Дата последнего чтения с хостинга видна у каждого домена.
|
||||
|
||||
Под таблицей — копирование сохранённых host и девяти канонических UTM из одного
|
||||
источника в несколько получателей. Источник исключён из получателей. Черновики
|
||||
источника и выбранных получателей сначала нужно сохранить или отменить. Неизвестные
|
||||
поля, реквизиты, заметки и файлы ZIP не копируются. Отсутствующая группа источника
|
||||
оставляет группу получателя. Источник и каждая цель проверяются по своей версии;
|
||||
сохранение/копирование считаются завершёнными после результата задания и перечитывания.
|
||||
Ошибки отдельных целей видны в колонке и журнале; успешные цели не повторяются.
|
||||
Конструктор работает отдельно: значения или «Рандом» в девяти списках, затем
|
||||
«Сформировать и сохранить». Последний выбор и точный URL сохраняются на сервере
|
||||
отдельно по ID домена, восстанавливаются после перезагрузки. Изменение списка скрывает
|
||||
старую ссылку до явного сохранения. Значащая цифра стоит на пятой позиции метки,
|
||||
`0` означает выбор по весам. Конструктор не меняет config, default, веса или наборы,
|
||||
не создаёт задания; сохранённый выбор доступен и при недоступном хостинге.
|
||||
|
||||
`GET /api/link-builder?target=ID` читает выбор, `POST /api/link-builder` сохраняет
|
||||
`{target,selection}` с ровно девятью каноническими UTM-ключами. Действуют общий вход
|
||||
и CSRF. Состояние находится в приватном `state/link-builder-ID.json` с режимом0600.
|
||||
При переименовании домена меняется origin ссылки, выбор и сохранённые метки остаются.
|
||||
Неизвестный в текущем config номер сопровождается предупреждением;
|
||||
конструктор не исправляет конфигурацию автоматически.
|
||||
|
||||
## Компактная сводка и заметки — preland#23 / #24
|
||||
|
||||
С версии preland#25 ячейки содержат inline-редактор, описанный выше.
|
||||
|
||||
В блоке каждой UTM верхняя строка содержит название один раз и общую заметку
|
||||
в объединённой ячейке доменов. Ниже — пустая первая ячейка и значения доменов.
|
||||
Устаревший `utm.chb` скрыт в сводке и редакторе; остальные неизвестные поля видны.
|
||||
Редактор заметки —
|
||||
растягиваемая по высоте textarea на всю ширину объединённой ячейки доменов; сохранение явное.
|
||||
Черновик сохраняется при обновлении колонок или ошибке запроса. При конфликте
|
||||
сохранения нужно явно загрузить серверный текст; чужой текст молча не заменяется.
|
||||
|
||||
`GET /api/utm-notes` читает девять заметок, `POST /api/utm-notes` принимает
|
||||
`{marker,text,version}`. Авторизация/CSRF общие; версия отдельная у каждой заметки,
|
||||
запись защищена CAS. Plain text до10000 Unicode-символов, пустой текст очищает
|
||||
заметку. Файл `state/utm-notes.json` приватный0600, хранится независимо от
|
||||
config.json, подключений, конструктора ссылок и заданий. Заметки не доставляются
|
||||
на хостинги и сохраняются после замены контейнера.
|
||||
|
||||
## Состав и доступы
|
||||
|
||||
`compose.yaml` запускает web и отдельный worker из одного неизменяемого образа. Нужны
|
||||
PHP8.3, ZIP, Imagick с AVIF/WebP/JPEG и phpseclib3 из зафиксированного Composer lock.
|
||||
На Windows ничего устанавливать не требуется: сборка и проверки выполняются Docker.
|
||||
|
||||
На сервере `/srv/preland-admin`: `compose.yaml`, `.env` с `ADMIN_IMAGE`, закрытый
|
||||
`secret.env`, `state/`, `secrets/` и `deploy/`. В `secret.env` задаются `ADMIN_USERNAME`
|
||||
и `ADMIN_PASSWORD_HASH`; bcrypt-значение заключается в одинарные кавычки, чтобы Compose
|
||||
не интерполировал `$`. Пароль хешируется через `bin/password-hash.php` со stdin.
|
||||
В HTTP Secure-cookie включена по умолчанию; `ADMIN_COOKIE_SECURE=0` разрешена только
|
||||
на изолированном HTTP-стенде.
|
||||
|
||||
PHP-сессии хранятся в `state/sessions` с режимом0700, файлы имеют режим0600.
|
||||
Пересоздание web-контейнера сохраняет вход; срок бездействия остаётся3600секунд.
|
||||
Хранилище сессий не допускает относительный путь, symlink или публичный каталог.
|
||||
При первом переходе со старого ephemeral backing существующие session-файлы
|
||||
нужно перенести приватно до пересоздания; сам код не мигрирует старый `/tmp`.
|
||||
|
||||
Необязательная галочка «Запомнить меня на 90 дней» создаёт persistent cookie
|
||||
`preland_admin` с HttpOnly/Secure/SameSite=Strict. Срок фиксируется при входе,
|
||||
чтения его не продлевают. Данные такого входа лежат отдельно в
|
||||
`state/remember-sessions`0700, файлы0600. Без галочки остаются session-only cookie
|
||||
и прежний час бездействия. Анонимные обращения не создают долгоживущих файлов.
|
||||
«Выйти» удаляет серверный сеанс и cookie немедленно; пароль не сохраняется.
|
||||
|
||||
UID/GID приложения — 997:987. Приложение получает приватный state и ровно два local
|
||||
mount: `/targets/zoompixel.ru`, `/targets/zoomstage.online`. Весь `/srv/preland`, Docker
|
||||
socket, root SSH key и база основного сайта в приложение не передаются. Права публикации
|
||||
на каждом корне проверяются реальным временным файлом/rename при проверке подключения;
|
||||
нельзя включать запись до успешной проверки.
|
||||
|
||||
REG.RU ограничивает размер SSH exec-команды: длинный inline helper разрывал
|
||||
соединение после успешного входа. Доверенный helper передаётся сжатым zlib;
|
||||
финальная команда ограничена7000байт, большие payload идут в закрытый SFTP-inbox.
|
||||
На удалённом PHP требуется `gzinflate`; отсутствие возвращает понятную ошибку.
|
||||
Содержимое inbox удаляется и при нормальном завершении, и резервно при обрыве.
|
||||
|
||||
Два REG.RU-хостинга подключаются SFTP/SSH с их обычными ограниченными учётными записями.
|
||||
Нужны абсолютный корень, host key либо SHA256 fingerprint, пароль или приватный ключ,
|
||||
доступный через stdin CLI `bin/target-import.php`. Секрет хранится 0600 в приватном state,
|
||||
по API не возвращается. На удалённом хосте нужен `php` CLI >=8.1 с `fsync`; удалённый
|
||||
Imagick не нужен. Замена реквизитов сбрасывает проверку подключения.
|
||||
|
||||
## Работа через формы
|
||||
|
||||
1. Изменить основной сайт или UTM в нужной колонке, проверить проценты и default,
|
||||
нажать «Сохранить» под колонкой. Остальные поля сохраняются; ошибка оставляет черновик.
|
||||
2. Для копирования выбрать источник и получателей под таблицей. Сначала сохранить
|
||||
либо отменить их черновики, затем нажать кнопку с числом получателей.
|
||||
3. Проверить итог каждой цели в колонках и журнале. При конфликте явно перечитать
|
||||
сохранённые настройки; неизвестный результат требует проверки журнала и фактического состояния.
|
||||
4. Раздел indpic имеет собственные «Открытый домен» и checkbox целей ZIP/проверки.
|
||||
Выбор копирования настроек на эти цели не влияет. Резерв относится к открытому домену indpic.
|
||||
5. Загрузить ZIP или проверить SFTP-папку, при необходимости назначить новый default
|
||||
в основной таблице: его номер должен присутствовать в весах, допускается вес0.
|
||||
Номера — 1–9, ZIP содержит папку номера и ровно90 изображений установленных форматов/
|
||||
размеров; лимит8МиБ на файл,64МиБ распакованного набора. При нулевой сумме весов rnd
|
||||
использует первый ключ weights по порядку config.json; только cons использует default.
|
||||
Имена numeric/legacy UTM адаптируются отдельно для каждой цели, конфликтующие aliases
|
||||
блокируются. Удаление набора из реестра оставляет файлы; используемый номер удалить нельзя.
|
||||
|
||||
## Ручные варианты и независимость — preland#30
|
||||
|
||||
Во всех девяти UTM ссылка «Добавить вариант» создаёт пустую строку: номер, вес и
|
||||
неотмеченный default. До нажатия пустых строк нет. Существующие номера показаны
|
||||
текстом; новые вводятся вручную (1–9, для part только 1 или 2). После сохранения новый номер тоже становится
|
||||
обычным текстом. Нулевой вес сохраняется. Отсутствующую группу можно создать через
|
||||
ту же ссылку; для неё default выбирается явно. «Отмена» возвращает последний прочитанный конфиг.
|
||||
|
||||
Редактор не обращается к каталогу основной админки и не проверяет реализацию номера
|
||||
на сайте: можно записать theme=3 или indpic=9 независимо от наличия темы/файлов.
|
||||
Соответствие номера содержимому сайта обеспечивает пользователь. Формат номера,
|
||||
дубликаты, aliases, проценты и CAS проверяются; конфиг не нормализуется автоматически.
|
||||
Сохранение, копирование и восстановление одинаково независимы от каталога/наборов.
|
||||
|
||||
Новый сет загружается отдельно: выбрать домены, вручную указать номер, выбрать ZIP,
|
||||
нажать «Загрузить сет». Для существующего номера нужно подтвердить замену.
|
||||
Проверка ZIP и изображений сохраняется, как и проверка файлов, загруженных по SFTP.
|
||||
Загрузка не меняет config.json. После неё пользователь сам добавляет вариант indpic,
|
||||
заполняет вес/default и сохраняет настройки. Автоматической связи нет.
|
||||
|
||||
## Приватное состояние и публикация наборов
|
||||
|
||||
Резервы и journal локального транспорта хранятся в приватном state. Для exact bind mount
|
||||
папка подготовки `.preland-admin-staging` создаётся внутри корня с0700; публичный веб-сервер
|
||||
другого UID обязан получать403 и через внешний origin, и через прямой loopback Apache.
|
||||
При наличии nginx-правила deny оно служит дополнительной защитой. Одной надежды на
|
||||
`.htaccess` недостаточно: AllowOverride может быть выключен. Каталог активного `/N/` —0755,
|
||||
файлы0644. SFTP использует приватную sibling-папку вне webroot.
|
||||
|
||||
Переключение каталогов имеет два rename и durable journal. При аварии upload-child
|
||||
supervisor сразу пытается восстановить полный прежний/новый набор под тем же lock;
|
||||
при полном перезапуске это делает следующая операция транспорта. Неподтверждённый
|
||||
результат остаётся unknown. Это восстановление после прерывания, а не обещание отсутствия
|
||||
краткого окна между двумя rename. При недоступном SSH восстановление нельзя подтвердить.
|
||||
|
||||
## Сборка, проверки и выпуск
|
||||
|
||||
```sh
|
||||
docker build -t preland-admin:<git-sha> -f admin/Dockerfile admin
|
||||
docker run --rm --entrypoint composer -v "$PWD:/work:ro" -w /work/admin preland-admin:<git-sha> test
|
||||
```
|
||||
|
||||
Дополнительные Docker проверки: `tests/backend-sftp.Dockerfile` — настоящий SSH/SFTP,
|
||||
`tests/backend-worker.php` — цикл с паузой65секунд, `tests/ui-*.cjs` — реальные browser
|
||||
сценарии. Последние выполняются только на disposable целях; они изменяют и восстанавливают
|
||||
настройки. `NODE_PATH=/e2e/node_modules`, `CHROMIUM_PATH` задаёт браузер имеющегося e2e-образа.
|
||||
|
||||
Для production подготовить отдельный TLS/nginx по `deploy/nginx.conf`, импортировать
|
||||
проверенные подключения, сохранить резерв состояния, указать точный image в `.env`, затем
|
||||
`docker compose up -d`. Проверить web/worker health, вход, четыре чтения, безопасный реальный
|
||||
цикл сохранения/перечитывания, ZIP и публичные изображения. Не заменять runtime-конфиги
|
||||
Git-снимком. Для отката приложения вернуть прежний `ADMIN_IMAGE`; state и контент сохраняются.
|
||||
Возврат контента выполняется отдельно через резерв и CAS, не через старый Docker-образ.
|
||||
|
||||
Фактические production-команды, версии, ограничения, свидетельства UI и статус подключения
|
||||
четырёх доменов фиксируются в preland#22 и KB; этот README сам не доказывает завершение выпуска.
|
||||
|
||||
## Browser проверки preland#25
|
||||
|
||||
`ui-matrix.cjs` выполняет полный цикл на четырёх disposable local-целях a/b/c/d:
|
||||
inline input/default/add/remove/missing, Save/Cancel, параллельные черновики,
|
||||
задержанный GET, настоящий CAS-конфликт и offline/recovery, copy двух получателей,
|
||||
partial failure/source changed, aliases/unknown/requisites, независимые ZIP-цели,
|
||||
ошибочный ZIP, конструктор/заметки и1440/390. Скрипту нужны writable mount fixture
|
||||
в `/fixture` и output в `/output`; он возвращает исходные config-файлы в finally.
|
||||
`ui-regression.cjs` — совместимая точка входа этого же сценария, отдельно повторять её
|
||||
не требуется. `ui-utm.cjs` отдельно проверяет настоящую ZIP-доставку в a+b, variant3
|
||||
с весом0/default и обратное восстановление через controls; нужен `VALID_ZIP` набора3.
|
||||
`ui-notes.cjs`, `ui-link-builder.cjs`, `ui-verified-refresh.cjs`, `ui-remember.cjs`,
|
||||
`ui-requisites.cjs` и `ui-smoke.cjs` сохраняют отдельные сценарии своих подсистем.
|
||||
Ни один browser write-test не предназначен для production.
|
||||
|
||||
|
||||
Отдельный `ui-session-jobs.cjs` удерживает ответ настоящего завершённого Save/Copy
|
||||
через выход и повторный вход: старое продолжение не сбрасывает новый черновик,
|
||||
не подменяет результаты/notice и не снимает busy новой операции. Серверные задания
|
||||
при выходе продолжаются; отменяются только устаревшие UI-продолжения.
|
||||
|
||||
## Скрытие, удаление и порядок доменов (preland#26)
|
||||
|
||||
Значок перечёркнутого глаза справа от домена временно убирает его из таблицы и
|
||||
рабочих списков. Подключение, секрет, проверка соединения, сайт, config.json и наборы
|
||||
сохраняются. В «Скрытых доменах» доступны возврат и удаление подключения. Сервер
|
||||
запрещает удалять видимый домен и домен с незавершённым заданием; сайт и файлы
|
||||
удаление подключения не затрагивает. История сохраняется, результаты скрытых целей
|
||||
не выводятся в рабочих списках.
|
||||
|
||||
Маркер слева от названия позволяет перетащить колонку мышью; стрелки на маркере
|
||||
позволяют изменить порядок с клавиатуры. Порядок автоматически сохраняется на сервере
|
||||
и применяется к таблице и спискам выбора для всех входов. Скрытый домен сохраняет
|
||||
свою позицию, новый добавляется в конец. Черновики настроек и заметок при перестановке
|
||||
сохраняются; перед скрытием изменённого домена нужно сохранить или отменить его правки.
|
||||
|
||||
API с обычными авторизацией и CSRF: `POST /api/targets/visibility` принимает
|
||||
`{id,hidden}` (hidden — boolean), `POST /api/targets/order` принимает `{ids}` —
|
||||
ровно все видимые ID в нужном порядке; оба возвращают актуальные `targets`.
|
||||
Неверный/устаревший набор ID отвергается, скрытые позиции не перезаписываются.
|
||||
`GET /api/state` содержит все подключения, включая hidden; интерфейс отдельно
|
||||
фильтрует рабочие списки. Скрытие не отменяет уже запущенное задание.
|
||||
|
||||
Ответы API401 не отправляют Set-Cookie: задержанный ответ старого запроса не может
|
||||
перезаписать cookie нового успешного входа. Истёкший сеанс отзывается на сервере,
|
||||
а явный выход (200) удаляет cookie. UI также отбрасывает ответы прежнего сеанса.
|
||||
Проверка `ui-target-login-race.cjs` включает следующий авторизованный запрос и
|
||||
сохранение порядка после задержанного401, а не только видимость рабочей области.
|
||||
|
||||
## Номера вариантов и чтение с хостинга (#29)
|
||||
|
||||
В строках UTM редактируются только вес и default; номера отображаются текстом,
|
||||
добавление/удаление вариантов и создание отсутствующей группы из таблицы убраны.
|
||||
Копирование сохранённых групп остаётся доступно. API config возвращает read_at UTC
|
||||
как метаданные успешного Transport-чтения, не записывая его в config.json.
|
||||
Колонка показывает время последнего успешного чтения МСК с секундами, отдельные
|
||||
loading/error/retry состояния и «Записано на хостинг» после подтверждённых Save/Copy.
|
||||
Ошибка сохраняет прошлую дату; ввод во время чтения сохраняет черновик, но обновляет
|
||||
метаданные чтения. Сохранение блокируется до завершения чтения.
|
||||
|
||||
## Процентный контракт (#28)
|
||||
|
||||
В `config.json` веса известных UTM хранятся непосредственно в процентах: числа
|
||||
0–100 с точностью до двух знаков; сумма группы — 100 либо все значения равны 0.
|
||||
Поле «Вес, %» показывает эти же числа, без пересчёта и перераспределения ввода.
|
||||
Номер варианта и default не меняются при изменении веса. Промежуточная неверная
|
||||
сумма допускается в черновике и блокирует сохранение с понятной причиной.
|
||||
|
||||
Config/Transport проверяют единый контракт для Save, Copy, Restore и проверки
|
||||
нового подключения. Старые относительные конфиги и резервные копии не публикуются;
|
||||
сами архивы сохраняются. Совместимость со старой единицей весов не поддерживается
|
||||
по прямому решению владельца. Не путать это с неизменными alias/defaulttypes.
|
||||
Публичный выбор варианта остаётся прежним: масштабирование до процентов сохраняет
|
||||
доли; поведение полностью нулевой группы не заменяется новым fallback.
|
||||
|
||||
`PercentMigration` применяется только явно при миграции: заменяет числовые токены
|
||||
известных weights, сохраняя остальные байты. Доли округляются независимо до сотых;
|
||||
если сумма округлений не равна 100, операция останавливается до записи всех целей.
|
||||
Остаток автоматически не распределяется. Приватный CLI с планом/receipt/CAS делает
|
||||
резервные копии и проверяет точные bytes после каждой записи; в public его нет.
|
||||
|
||||
## Независимые UTM-наборы — preland#32
|
||||
|
||||
Дискета перед глазиком создаёт снимок текущих UTM-полей таблицы, включая ещё не опубликованные правки. Сохранение набора не пишет на хостинг. Обязательно собственное название; дубликат без учёта регистра отклоняется. Приватный utm-presets.json содержит только id/name/utm: ни домен, ни ID подключения, ни происхождение не сохраняются. Host, заметки, реквизиты и файлы сетов не входят в набор.
|
||||
|
||||
GET/POST/DELETE /api/utm-presets требуют вход; записи требуют CSRF. POST принимает только name и utm (канонические группы, только default/weights). DELETE принимает только id. Копирование POST /api/copy принимает preset и targets с ID/версией каждого получателя; прежний источник-домен больше не поддерживается. Очередь фиксирует собственный UTM-снимок, поэтому удаление набора или любого подключения-источника не влияет на задание. Получателем может быть любой доступный домен. Основной сайт и заметки сохраняются, отсутствующая группа набора не удаляет группу получателя. CAS, резерв и подтверждённое перечитывание сохранены.
|
||||
|
||||
Проверки: tests/utm-presets.php, обновлённый tests/copy.php и ui-utm-presets.cjs; helper preset-ui-helper.cjs адаптирует прежние copy-регрессии к независимому источнику. UI покрывает черновик без публикации, имена/дубликаты, два получателя, reload, гонку list GET/delete, 401/relogin, отмену удаления и API-удаление, мобильную ширину. Тестовые удаления выполняются только над созданными этим тестом наборами.
|
||||
|
||||
|
||||
api#347: part — девятая UTM, пятый символ1/2, default1. Поддерживается редактором, именованными UTM-наборами, копированием, заметками и конструктором. Preland только передаёт метку, ссылка «Для бизнеса» там не добавляется. Runtime config автоматически не дополняется. Старые сохранённые ссылки получают независимый part1 без изменения остальных query-шумов; recovery использует общий приоритет сохранённого snapshot перед текущим resolved fallback.
|
||||
@@ -0,0 +1,4 @@
|
||||
<?php
|
||||
declare(strict_types=1);
|
||||
// Пароль читается только из stdin; не передавать его через аргументы командной строки.
|
||||
$password=rtrim(stream_get_contents(STDIN),"\r\n");if(strlen($password)<12){fwrite(STDERR,"Пароль должен содержать не менее 12 символов.\n");exit(1);}echo password_hash($password,PASSWORD_DEFAULT)."\n";
|
||||