f796b7d1e4
Production regression in 3.9.0: Telegram bot silently dropped every update. WizardDraftRepository.GetActiveAsync was called on every Telegram update (via UpdateRouter -> TryGetWizardContext) and threw System.PlatformNotSupportedException in NativeAOT, because Dapper.AOT 1.0.48 only generates interceptors for the (sql, object?) extension overloads and NOT for the (CommandDefinition) overload. The runtime then fell back to Dapper.SqlMapper.CreateParamInfoGenerator, which uses Reflection.Emit and fails on AOT. TelegramBotService swallowed the exception, so /newsession appeared to start but no button press reached the wizard and no session was created. Two related changes in WizardDraft: 1. Switched WizardDraftRepository.* from 'new CommandDefinition(sql, params, cancellationToken: ct)' to the direct 'connection.Query*(sql, params)' overload, matching the working pattern in JoinSessionHandler. Dapper.AOT now generates CommandFactory30<WizardDraft> + RowFactory17<WizardDraft> + QuerySingleOrDefaultAsync37<WizardDraft> for all four methods. 2. WizardDraft.CreatedAt/UpdatedAt/ExpiresAt are now DateTime (UTC) instead of DateTimeOffset. AOT RowFactory calls reader.GetDateTime() directly and does not perform DateTime -> DateTimeOffset conversion; the previous type raised InvalidCastException on the very first wizard_drafts query. All 588/590 tests pass (2 pre-existing skipped, +5 new AOT regression tests in WizardDraftRepositoryAotShapeTests). dotnet format clean. Bumps: 3.9.0 -> 3.9.1. Note: GetOwnerClubsAsync (Telegram/Discord), DiscordPermissionLookup, and DiscordWizardInteractionModule.GetOwnerClubsAsync still use CommandDefinition and will hit the same Reflection.Emit AOT failure when the user reaches the PickClub visibility step. Follow-up in 3.9.2.
160 lines
5.9 KiB
YAML
160 lines
5.9 KiB
YAML
name: Deploy Telegram Bot
|
|
|
|
on:
|
|
push:
|
|
branches:
|
|
- main
|
|
|
|
env:
|
|
VERSION: 3.9.1
|
|
|
|
jobs:
|
|
# ЧАСТЬ 1: Собираем образы и кладем в Gitea (чтобы делиться с ребятами)
|
|
build-and-push:
|
|
runs-on: ubuntu-latest # Замени на метку твоего раннера, если она другая
|
|
steps:
|
|
- name: Checkout repository
|
|
uses: actions/checkout@v4
|
|
|
|
- name: Login to Gitea Container Registry
|
|
uses: docker/login-action@v3
|
|
with:
|
|
registry: git.codeanddice.ru # НАПРИМЕР: gitea.my-server.com
|
|
username: toutsu
|
|
password: ${{ secrets.GIT_TOKEN }}
|
|
|
|
- name: Build Bot image
|
|
run: |
|
|
docker build \
|
|
--label "org.opencontainers.image.source=https://git.codeanddice.ru/${{ gitea.repository }}" \
|
|
-f src/GmRelay.Bot/Dockerfile \
|
|
-t git.codeanddice.ru/toutsu/gmrelay-bot:latest \
|
|
-t git.codeanddice.ru/toutsu/gmrelay-bot:${{ env.VERSION }} \
|
|
.
|
|
|
|
- name: Push Bot image
|
|
run: |
|
|
docker push git.codeanddice.ru/toutsu/gmrelay-bot:latest
|
|
docker push git.codeanddice.ru/toutsu/gmrelay-bot:${{ env.VERSION }}
|
|
|
|
- name: Build Discord Bot image
|
|
run: |
|
|
docker build \
|
|
--label "org.opencontainers.image.source=https://git.codeanddice.ru/${{ gitea.repository }}" \
|
|
-f src/GmRelay.DiscordBot/Dockerfile \
|
|
-t git.codeanddice.ru/toutsu/gmrelay-discord-bot:latest \
|
|
-t git.codeanddice.ru/toutsu/gmrelay-discord-bot:${{ env.VERSION }} \
|
|
.
|
|
|
|
- name: Push Discord Bot image
|
|
run: |
|
|
docker push git.codeanddice.ru/toutsu/gmrelay-discord-bot:latest
|
|
docker push git.codeanddice.ru/toutsu/gmrelay-discord-bot:${{ env.VERSION }}
|
|
|
|
- name: Build Web image
|
|
run: |
|
|
docker build \
|
|
--label "org.opencontainers.image.source=https://git.codeanddice.ru/${{ gitea.repository }}" \
|
|
-f src/GmRelay.Web/Dockerfile \
|
|
-t git.codeanddice.ru/toutsu/gmrelay-web:latest \
|
|
-t git.codeanddice.ru/toutsu/gmrelay-web:${{ env.VERSION }} \
|
|
.
|
|
|
|
- name: Push Web image
|
|
run: |
|
|
docker push git.codeanddice.ru/toutsu/gmrelay-web:latest
|
|
docker push git.codeanddice.ru/toutsu/gmrelay-web:${{ env.VERSION }}
|
|
|
|
# ЧАСТЬ 1.5: Сканируем собранные образы на уязвимости
|
|
scan-images:
|
|
needs: build-and-push
|
|
runs-on: ubuntu-latest
|
|
steps:
|
|
- name: Install Trivy
|
|
run: |
|
|
curl -sfL https://raw.githubusercontent.com/aquasecurity/trivy/main/contrib/install.sh | sh -s -- -b /usr/local/bin
|
|
|
|
- name: Scan Bot image
|
|
run: |
|
|
trivy image \
|
|
--severity HIGH,CRITICAL \
|
|
--exit-code 1 \
|
|
--format table \
|
|
git.codeanddice.ru/toutsu/gmrelay-bot:${{ env.VERSION }}
|
|
|
|
- name: Scan Discord Bot image
|
|
run: |
|
|
trivy image \
|
|
--severity HIGH,CRITICAL \
|
|
--exit-code 1 \
|
|
--format table \
|
|
git.codeanddice.ru/toutsu/gmrelay-discord-bot:${{ env.VERSION }}
|
|
|
|
- name: Scan Web image
|
|
run: |
|
|
trivy image \
|
|
--severity HIGH,CRITICAL \
|
|
--exit-code 1 \
|
|
--format table \
|
|
git.codeanddice.ru/toutsu/gmrelay-web:${{ env.VERSION }}
|
|
|
|
# ЧАСТЬ 2: Запускаем эти образы на самом сервере
|
|
deploy:
|
|
needs: scan-images
|
|
runs-on: ubuntu-latest # Тот же локальный раннер
|
|
steps:
|
|
- name: Checkout repository
|
|
uses: actions/checkout@v4
|
|
|
|
- name: Create .env file with secrets
|
|
run: |
|
|
echo "TELEGRAM_BOT_TOKEN=${{ secrets.TELEGRAM_BOT_TOKEN }}" > .env
|
|
echo "POSTGRES_PASSWORD=${{ secrets.POSTGRES_PASSWORD }}" >> .env
|
|
echo "DISCORD_BOT_TOKEN=${{ secrets.DISCORD_BOT_TOKEN }}" >> .env
|
|
echo "TELEGRAM_BOT_USERNAME=${{ secrets.TELEGRAM_BOT_USERNAME }}" >> .env
|
|
echo "TELEGRAM_MINI_APP_URL=${{ secrets.TELEGRAM_MINI_APP_URL }}" >> .env
|
|
echo "DISCORD_CLIENT_ID=${{ secrets.DISCORD_CLIENT_ID }}" >> .env
|
|
echo "DISCORD_CLIENT_SECRET=${{ secrets.DISCORD_CLIENT_SECRET }}" >> .env
|
|
echo "DISCORD_REDIRECT_URI=${{ secrets.DISCORD_REDIRECT_URI }}" >> .env
|
|
|
|
- name: Deploy Containers
|
|
run: |
|
|
# Авторизуемся локальным докером в нашей Gitea
|
|
docker login git.codeanddice.ru/ -u toutsu -p ${{ secrets.GIT_TOKEN }}
|
|
|
|
# Pull гарантирует, что мы получили нужную версию.
|
|
docker compose pull bot discord web
|
|
|
|
# Запускаем! Флаг -d оставит их работать в фоне.
|
|
docker compose up -d
|
|
|
|
# Ждём, пока сервисы перейдут в healthy или упадут
|
|
SERVICES="bot discord web"
|
|
MAX_WAIT=40
|
|
INTERVAL=5
|
|
ELAPSED=0
|
|
|
|
while [ $ELAPSED -lt $MAX_WAIT ]; do
|
|
NOT_HEALTHY=0
|
|
for svc in $SERVICES; do
|
|
HEALTH=$(docker compose ps $svc --format="{{.Health}}" 2>/dev/null | head -n1)
|
|
if [ "$HEALTH" != "healthy" ]; then
|
|
STATE=$(docker compose ps $svc --format="{{.State}}" 2>/dev/null | head -n1)
|
|
echo "❌ $svc not healthy yet (state: ${STATE:-unknown})"
|
|
NOT_HEALTHY=$((NOT_HEALTHY + 1))
|
|
fi
|
|
done
|
|
|
|
if [ $NOT_HEALTHY -eq 0 ]; then
|
|
echo "✅ All services are healthy!"
|
|
exit 0
|
|
fi
|
|
|
|
sleep $INTERVAL
|
|
ELAPSED=$((ELAPSED + INTERVAL))
|
|
done
|
|
|
|
echo "⏰ Timed out waiting for services to become healthy"
|
|
docker compose ps
|
|
exit 1
|