name: Run artisan command on liderra.ru # Universal artisan-runner для прод-команд пока прямой SSH с dev-машины # заблокирован YC backbone-фильтром. Заказчик пишет команду строкой в # workflow_dispatch input, workflow проверяет её по whitelist, выполняет на # проде под sudo -u www-data, выводит результат в job summary. # # Whitelist охватывает read-only / dry-run / status команды без подтверждения # плюс несколько mutating команд с обязательным confirm_apply=true. # # Любая команда вне whitelist'а → fail before SSH. # # Использует тот же LIDERRA_SSH_KEY что и deploy.yml/ssh-diagnose.yml. on: workflow_dispatch: inputs: command: description: 'artisan-команда (например: supplier:rekey-orphans --dry-run)' required: true type: string confirm_apply: description: 'Подтверждаю выполнение mutating-команды (обязательно true для команд без --dry-run)' required: false default: false type: boolean jobs: run: name: ${{ github.event.inputs.command }} runs-on: ubuntu-latest timeout-minutes: 15 env: LIDERRA_HOST: 111.88.246.137 LIDERRA_USER: ubuntu CMD: ${{ github.event.inputs.command }} CONFIRM: ${{ github.event.inputs.confirm_apply }} steps: - name: Whitelist check run: | set -euo pipefail CMD_TRIM=$(echo "$CMD" | sed 's/^ *//;s/ *$//') echo "Requested: '$CMD_TRIM'" # Group 1 — read-only / dry-run / inspection: всегда разрешены READ_ONLY_RE='^(migrate:status|route:list|schedule:list|queue:listen --help|about|env:show|config:show|cache:table|view:cache|optimize:status|snapshot:backfill( --date=20[2-9][0-9]-[0-1][0-9]-[0-3][0-9])?|scheduler:check-heartbeats|incidents:watch-failures( --threshold-spike=[0-9]+)?( --threshold-daily=[0-9]+)?( --persistent-hours=[0-9]+)?|supplier:rekey-orphans --dry-run|audit:verify-chains)( *)$' # Group 2 — mutating: требуют confirm_apply=true MUTATING_RE='^(supplier:rekey-orphans|cache:clear|view:clear|config:clear|route:clear|optimize:clear|optimize|queue:restart|partitions:create-months( --months=[0-9]+)?|partitions:drop-old|audit:rebuild-chain --partition=[a-z_0-9]+ --from-id=[0-9]+( --force)?)( *)$' if [[ "$CMD_TRIM" =~ $READ_ONLY_RE ]]; then echo "::notice::Command in read-only whitelist — proceeding." exit 0 fi if [[ "$CMD_TRIM" =~ $MUTATING_RE ]]; then if [[ "$CONFIRM" != "true" ]]; then echo "::error::Mutating command '$CMD_TRIM' requires confirm_apply=true. Re-run with confirm_apply checked." exit 1 fi echo "::warning::Mutating command authorized via confirm_apply=true." exit 0 fi echo "::error::Command '$CMD_TRIM' is NOT in whitelist. Allowed read-only patterns: $READ_ONLY_RE. Allowed mutating: $MUTATING_RE. Add to whitelist if needed." exit 1 - name: Setup SSH key run: | mkdir -p ~/.ssh echo "${{ secrets.LIDERRA_SSH_KEY }}" > ~/.ssh/liderra_deploy chmod 600 ~/.ssh/liderra_deploy ssh-keyscan -H ${{ env.LIDERRA_HOST }} >> ~/.ssh/known_hosts 2>/dev/null - name: Run artisan on prod run: | set -o pipefail CMD_B64=$(printf '%s' "$CMD" | base64 -w0) ssh -i ~/.ssh/liderra_deploy ${{ env.LIDERRA_USER }}@${{ env.LIDERRA_HOST }} \ "CMD_B64='$CMD_B64' bash -s" <<'REMOTE' | tee /tmp/artisan-output.log set +e CMD=$(echo "$CMD_B64" | base64 -d) cd /var/www/liderra/app echo "=== Running: php artisan $CMD on $(hostname) at $(date -u) ===" sudo -u www-data php artisan $CMD 2>&1 RC=$? echo echo "=== Exit code: $RC ===" exit $RC REMOTE - name: Print summary if: always() run: | { echo "## artisan \`$CMD\`" echo echo "- Host: $LIDERRA_HOST" echo "- Confirm: $CONFIRM" echo "- Triggered by: ${{ github.actor }}" echo echo '```' cat /tmp/artisan-output.log 2>/dev/null || echo "(no output captured)" echo '```' } >> "$GITHUB_STEP_SUMMARY" - name: Upload output as artifact if: always() uses: actions/upload-artifact@v4 with: name: artisan-output path: /tmp/artisan-output.log retention-days: 30 - name: Cleanup SSH key if: always() run: rm -f ~/.ssh/liderra_deploy