name: deploy-preland # После коммита в main сохраняется legacy-выпуск на Beget. Новые домены ZoomPixel # доставляются отдельными SFTP job: staging — только на stage, main — только на production. # Цель — домен со своим хостингом (список — matrix ниже): реквизиты — секреты репозитория _FTP_HOST, # _FTP_USER, _FTP_PASS (порт 21). Цели независимы: сбой одного хостинга остальные не останавливает. # Заливаются файлы репозитория (git ls-files), кроме служебных (.gitea/, README.md, AGENTS.md, CLAUDE.md, .gitignore) и тех, что живут # только на хостинге: requisites.html (реквизиты заполняют на месте — пустой файл из git их бы затёр). # Рабочий config.json настраивается владельцем на каждом хостинге и является # источником истины. Обычный deploy его не перезаписывает; задача может менять # только прямо согласованные ключи после чтения текущего host-файла. # На хостинге ничего не удаляется: удалённое из репозитория убирать вручную. 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 — префикс секретов _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' runs-on: zoomzilla-production defaults: run: shell: sh env: PRELAND_URL: https://zoomstage.online 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 - name: Checkout staging branch run: git clone --depth 1 --branch staging 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) trap 'rm -f "$known_hosts" "$batch" "$files"' EXIT printf '%s\n' "$SFTP_KNOWN_HOSTS" > "$known_hosts" git ls-files | grep -vE '^(\.gitea/|README\.md$|AGENTS\.md$|CLAUDE\.md$|\.gitignore$|requisites\.html$|config\.json$)' > "$files" if grep -q '[^A-Za-z0-9._/-]' "$files"; then echo "имена с недопустимыми символами:"; grep '[^A-Za-z0-9._/-]' "$files"; exit 1; fi printf '%s\n' "-mkdir $REMOTE_DIR" > "$batch" sed 's#/[^/]*$##' "$files" | grep -v '^$' | sort -u | while read -r dir; do printf '%s\n' "-mkdir $REMOTE_DIR/$dir" >> "$batch"; done while read -r file; do printf '%s\n' "put -p $file $REMOTE_DIR/$file" >> "$batch"; done < "$files" 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; } 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) trap 'rm -f "$known_hosts" "$batch" "$files"' EXIT printf '%s\n' "$SFTP_KNOWN_HOSTS" > "$known_hosts" git ls-files | grep -vE '^(\.gitea/|README\.md$|AGENTS\.md$|CLAUDE\.md$|\.gitignore$|requisites\.html$|config\.json$)' > "$files" if grep -q '[^A-Za-z0-9._/-]' "$files"; then echo "имена с недопустимыми символами:"; grep '[^A-Za-z0-9._/-]' "$files"; exit 1; fi printf '%s\n' "-mkdir $REMOTE_DIR" > "$batch" sed 's#/[^/]*$##' "$files" | grep -v '^$' | sort -u | while read -r dir; do printf '%s\n' "-mkdir $REMOTE_DIR/$dir" >> "$batch"; done while read -r file; do printf '%s\n' "put -p $file $REMOTE_DIR/$file" >> "$batch"; done < "$files" 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; }