All checks were successful
BCards Multi-Tenant Deployment Pipeline / Run Tests (push) Successful in 6s
BCards Multi-Tenant Deployment Pipeline / PR Validation (push) Has been skipped
BCards Multi-Tenant Deployment Pipeline / Run Tests (pull_request) Successful in 5s
BCards Multi-Tenant Deployment Pipeline / PR Validation (pull_request) Successful in 0s
BCards Multi-Tenant Deployment Pipeline / Build and Push Image (push) Successful in 10m8s
BCards Multi-Tenant Deployment Pipeline / Build and Push Image (pull_request) Has been skipped
BCards Multi-Tenant Deployment Pipeline / Deploy bcards.site (pull_request) Has been skipped
BCards Multi-Tenant Deployment Pipeline / Deploy spicylinks.site (pull_request) Has been skipped
BCards Multi-Tenant Deployment Pipeline / Deploy luslinks.site (pull_request) Has been skipped
BCards Multi-Tenant Deployment Pipeline / Deploy to Release Swarm (ARM) (pull_request) Has been skipped
BCards Multi-Tenant Deployment Pipeline / Cleanup Old Resources (pull_request) Has been skipped
BCards Multi-Tenant Deployment Pipeline / Deploy bcards.site (push) Has been skipped
BCards Multi-Tenant Deployment Pipeline / Deploy spicylinks.site (push) Has been skipped
BCards Multi-Tenant Deployment Pipeline / Deploy luslinks.site (push) Has been skipped
BCards Multi-Tenant Deployment Pipeline / Deployment Summary (pull_request) Successful in 0s
BCards Multi-Tenant Deployment Pipeline / Deploy to Release Swarm (ARM) (push) Successful in 15s
BCards Multi-Tenant Deployment Pipeline / Cleanup Old Resources (push) Has been skipped
BCards Multi-Tenant Deployment Pipeline / Deployment Summary (push) Successful in 0s
727 lines
44 KiB
YAML
727 lines
44 KiB
YAML
name: BCards Multi-Tenant Deployment Pipeline
|
||
|
||
# ─── Required Gitea Secrets ────────────────────────────────────────────────
|
||
# secrets.SSH_PRIVATE_KEY – SSH key for ubuntu@ on both OCI nodes
|
||
# secrets.STRIPE_SECRET_KEY – Shared Stripe secret key
|
||
# secrets.STRIPE_WEBHOOK_SECRET – Shared Stripe webhook secret
|
||
# secrets.GOOGLE_CLIENT_SECRET – Google OAuth client secret
|
||
# secrets.MICROSOFT_CLIENT_SECRET – Microsoft OAuth client secret
|
||
# secrets.SENDGRID_API_KEY – SendGrid API key
|
||
#
|
||
# ─── Required Gitea Variables (vars.*) ────────────────────────────────────
|
||
# vars.STRIPE_PUBLISHABLE_KEY
|
||
# vars.STRIPE_ENVIRONMENT (default: test)
|
||
# vars.GOOGLE_CLIENT_ID
|
||
# vars.MICROSOFT_CLIENT_ID
|
||
# vars.OPENSEARCH_URL (default: http://localhost:9201)
|
||
# vars.MODERATOR_EMAIL
|
||
# vars.MODERATOR_EMAIL_1
|
||
# vars.MODERATOR_EMAIL_2
|
||
#
|
||
# ─── Per-Tenant Variables (optional, have defaults) ───────────────────────
|
||
# vars.SPICYLINKS_FROM_EMAIL (default: noreply@spicylinks.site)
|
||
# vars.SPICYLINKS_FROM_NAME (default: SpicyLinks)
|
||
# vars.LUSLINKS_FROM_EMAIL (default: noreply@luslinks.site)
|
||
# vars.LUSLINKS_FROM_NAME (default: LusLinks)
|
||
|
||
on:
|
||
push:
|
||
branches:
|
||
- main
|
||
- 'Release/*'
|
||
pull_request:
|
||
branches: [ main ]
|
||
types: [opened, synchronize, reopened]
|
||
|
||
env:
|
||
REGISTRY: registry.redecarneir.us
|
||
IMAGE_NAME: bcards
|
||
SWARM_MANAGER: 141.148.162.114
|
||
SWARM_WORKER: 129.146.116.218
|
||
|
||
jobs:
|
||
# ─── Tests ────────────────────────────────────────────────────────────────
|
||
test:
|
||
name: Run Tests
|
||
runs-on: ubuntu-latest
|
||
|
||
steps:
|
||
- name: Test info
|
||
run: |
|
||
echo "🧪 Executando testes para ${{ github.ref_name }}"
|
||
SKIP_TESTS="${{ github.event.inputs.skip_tests || vars.SKIP_TESTS }}"
|
||
if [ "$SKIP_TESTS" == "true" ]; then
|
||
echo "⚠️ Testes PULADOS"
|
||
echo "TESTS_SKIPPED=true" >> $GITHUB_ENV
|
||
else
|
||
echo "✅ Executando testes"
|
||
echo "TESTS_SKIPPED=false" >> $GITHUB_ENV
|
||
fi
|
||
|
||
- name: Checkout code
|
||
if: env.TESTS_SKIPPED == 'false'
|
||
uses: actions/checkout@v4
|
||
|
||
- name: Setup .NET 8
|
||
if: env.TESTS_SKIPPED == 'false'
|
||
uses: actions/setup-dotnet@v4
|
||
with:
|
||
dotnet-version: '8.0.x'
|
||
|
||
- name: Cache dependencies
|
||
if: env.TESTS_SKIPPED == 'false'
|
||
uses: actions/cache@v3
|
||
with:
|
||
path: ~/.nuget/packages
|
||
key: ${{ runner.os }}-nuget-${{ hashFiles('**/*.csproj') }}
|
||
restore-keys: |
|
||
${{ runner.os }}-nuget-
|
||
|
||
- name: Restore dependencies
|
||
if: env.TESTS_SKIPPED == 'false'
|
||
run: dotnet restore
|
||
|
||
- name: Build solution
|
||
if: env.TESTS_SKIPPED == 'false'
|
||
run: dotnet build --no-restore --configuration Release
|
||
|
||
- name: Run unit tests
|
||
if: env.TESTS_SKIPPED == 'false'
|
||
run: dotnet test --no-build --configuration Release --verbosity normal --collect:"XPlat Code Coverage"
|
||
|
||
# ─── PR Validation (no deploy) ────────────────────────────────────────────
|
||
pr-validation:
|
||
name: PR Validation
|
||
runs-on: ubuntu-latest
|
||
needs: [test]
|
||
if: github.event_name == 'pull_request'
|
||
|
||
steps:
|
||
- name: PR Validation Summary
|
||
run: |
|
||
echo "✅ Pull Request Validation Summary"
|
||
echo "🎯 Target: ${{ github.base_ref }}"
|
||
echo "📂 Source: ${{ github.head_ref }}"
|
||
echo "🧪 Tests: ${{ needs.test.result }}"
|
||
echo "✨ PR está pronto para merge!"
|
||
|
||
# ─── Build & Push (single image, all tenants share it) ───────────────────
|
||
build-and-push:
|
||
name: Build and Push Image
|
||
runs-on: [self-hosted, arm64, bcards]
|
||
needs: [test]
|
||
if: github.event_name == 'push' && (needs.test.result == 'success' || needs.test.result == 'skipped')
|
||
|
||
steps:
|
||
- name: Checkout code
|
||
uses: actions/checkout@v4
|
||
|
||
- name: Set up Docker Buildx
|
||
uses: docker/setup-buildx-action@v3
|
||
with:
|
||
platforms: linux/arm64
|
||
|
||
- name: Determine build settings
|
||
id: settings
|
||
run: |
|
||
BRANCH_NAME="${{ github.ref_name }}"
|
||
if [ "$BRANCH_NAME" = "main" ]; then
|
||
echo "tag=latest" >> $GITHUB_OUTPUT
|
||
echo "environment=Production" >> $GITHUB_OUTPUT
|
||
echo "deploy_target=production" >> $GITHUB_OUTPUT
|
||
elif [[ "$BRANCH_NAME" == Release/* ]]; then
|
||
VERSION_RAW=${BRANCH_NAME#Release/}
|
||
VERSION=$(echo "$VERSION_RAW" | sed 's/^[Vv]\([0-9]\)/\1/')
|
||
[ -z "$VERSION" ] && VERSION="0.0.1"
|
||
echo "tag=$VERSION" >> $GITHUB_OUTPUT
|
||
echo "environment=Testing" >> $GITHUB_OUTPUT
|
||
echo "deploy_target=testing" >> $GITHUB_OUTPUT
|
||
echo "version=$VERSION" >> $GITHUB_OUTPUT
|
||
fi
|
||
SHORT_COMMIT=${GITHUB_SHA:0:7}
|
||
echo "commit=$SHORT_COMMIT" >> $GITHUB_OUTPUT
|
||
|
||
- name: Build and push image
|
||
run: |
|
||
echo "🏗️ Building image: ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}:${{ steps.settings.outputs.tag }}"
|
||
|
||
if [ "${{ steps.settings.outputs.deploy_target }}" = "production" ]; then
|
||
docker buildx build \
|
||
--platform linux/arm64 \
|
||
--file Dockerfile \
|
||
--tag ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}:${{ steps.settings.outputs.tag }} \
|
||
--push \
|
||
--progress=plain \
|
||
.
|
||
else
|
||
docker buildx build \
|
||
--platform linux/arm64 \
|
||
--file Dockerfile \
|
||
--tag ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}:${{ steps.settings.outputs.tag }} \
|
||
--push \
|
||
--build-arg BUILD_CONFIGURATION=Testing \
|
||
--build-arg VERSION=${{ steps.settings.outputs.version || 'latest' }} \
|
||
--build-arg COMMIT=${{ steps.settings.outputs.commit }} \
|
||
--progress=plain \
|
||
.
|
||
fi
|
||
|
||
# ─── Deploy: bcards.site ──────────────────────────────────────────────────
|
||
deploy-bcards:
|
||
name: Deploy bcards.site
|
||
runs-on: ubuntu-latest
|
||
needs: [build-and-push]
|
||
if: github.ref_name == 'main'
|
||
environment: production
|
||
|
||
steps:
|
||
- name: Checkout code
|
||
uses: actions/checkout@v4
|
||
|
||
- name: Generate appsettings for bcards
|
||
run: |
|
||
cat > appsettings.bcards.json << 'CONFIG_EOF'
|
||
{
|
||
"Logging": {
|
||
"LogLevel": {
|
||
"Default": "Information",
|
||
"Microsoft.AspNetCore": "Warning",
|
||
"BCards": "Information"
|
||
}
|
||
},
|
||
"AllowedHosts": "*",
|
||
"Stripe": {
|
||
"PublishableKey": "${{ vars.STRIPE_PUBLISHABLE_KEY }}",
|
||
"SecretKey": "${{ secrets.STRIPE_SECRET_KEY }}",
|
||
"WebhookSecret": "${{ secrets.STRIPE_WEBHOOK_SECRET }}",
|
||
"Environment": "${{ vars.STRIPE_ENVIRONMENT || 'test' }}"
|
||
},
|
||
"Authentication": {
|
||
"Google": {
|
||
"ClientId": "${{ vars.GOOGLE_CLIENT_ID }}",
|
||
"ClientSecret": "${{ secrets.GOOGLE_CLIENT_SECRET }}"
|
||
},
|
||
"Microsoft": {
|
||
"ClientId": "${{ vars.MICROSOFT_CLIENT_ID }}",
|
||
"ClientSecret": "${{ secrets.MICROSOFT_CLIENT_SECRET }}"
|
||
}
|
||
},
|
||
"SendGrid": {
|
||
"ApiKey": "${{ secrets.SENDGRID_API_KEY }}",
|
||
"FromEmail": "${{ vars.SENDGRID_FROM_EMAIL || 'ricardo.carneiro@jobmaker.com.br' }}",
|
||
"FromName": "${{ vars.SENDGRID_FROM_NAME || 'Ricardo Carneiro' }}"
|
||
},
|
||
"Plans": {
|
||
"Basic": { "Name": "Básico", "PriceId": "price_1RycPaBMIadsOxJVKioZZofK", "Price": 12.90, "MaxPages": 3, "MaxLinks": 8, "AllowPremiumThemes": false, "AllowProductLinks": false, "AllowAnalytics": true, "AllowDocumentUpload": false, "MaxDocuments": 0, "Features": [ "URL Personalizada", "20 temas básicos", "Analytics simples" ], "Interval": "month" },
|
||
"Professional": { "Name": "Profissional", "PriceId": "price_1RycQmBMIadsOxJVGqjVMaOj", "Price": 25.90, "MaxPages": 5, "MaxLinks": 20, "AllowPremiumThemes": false, "AllowProductLinks": false, "AllowAnalytics": true, "AllowDocumentUpload": false, "MaxDocuments": 0, "Features": [ "URL Personalizada", "20 temas básicos", "Analytics avançado" ], "Interval": "month" },
|
||
"Premium": { "Name": "Premium", "PriceId": "price_1RycRUBMIadsOxJVkxGOh3uu", "Price": 29.90, "MaxPages": 15, "MaxLinks": -1, "AllowPremiumThemes": true, "AllowProductLinks": false, "AllowAnalytics": true, "AllowDocumentUpload": true, "MaxDocuments": 5, "Features": [ "URL Personalizada", "40 temas (básicos + premium)", "Suporte prioritário", "Links ilimitados", "Upload de PDFs (até 5 arquivos)" ], "Interval": "month" },
|
||
"PremiumAffiliate": { "Name": "Premium+Afiliados", "PriceId": "price_1RycTaBMIadsOxJVeDLseXQq", "Price": 34.90, "MaxPages": 15, "MaxLinks": -1, "AllowPremiumThemes": true, "AllowProductLinks": true, "AllowAnalytics": true, "AllowDocumentUpload": true, "MaxDocuments": 10, "Features": [ "Tudo do Premium", "Links de produto", "Moderação especial", "Até 10 links afiliados", "Upload de PDFs (até 10 arquivos)" ], "Interval": "month" },
|
||
"BasicYearly": { "Name": "Básico Anual", "PriceId": "price_1RycWgBMIadsOxJVGdtEeoMS", "Price": 129.00, "MaxPages": 3, "MaxLinks": 8, "AllowPremiumThemes": false, "AllowProductLinks": false, "AllowAnalytics": true, "AllowDocumentUpload": false, "MaxDocuments": 0, "Features": [ "URL Personalizada", "20 temas básicos", "Analytics simples", "Economize R$ 11,80 (2 meses grátis)" ], "Interval": "year" },
|
||
"ProfessionalYearly": { "Name": "Profissional Anual", "PriceId": "price_1RycXdBMIadsOxJV5cNX7dHm", "Price": 259.00, "MaxPages": 5, "MaxLinks": 20, "AllowPremiumThemes": false, "AllowProductLinks": false, "AllowAnalytics": true, "AllowDocumentUpload": false, "MaxDocuments": 0, "Features": [ "URL Personalizada", "20 temas básicos", "Analytics avançado", "Economize R$ 25,80 (2 meses grátis)" ], "Interval": "year" },
|
||
"PremiumYearly": { "Name": "Premium Anual", "PriceId": "price_1RycYnBMIadsOxJVPdKmzy4m", "Price": 299.00, "MaxPages": 15, "MaxLinks": -1, "AllowPremiumThemes": true, "AllowProductLinks": false, "AllowAnalytics": true, "AllowDocumentUpload": true, "MaxDocuments": 5, "Features": [ "URL Personalizada", "40 temas (básicos + premium)", "Suporte prioritário", "Links ilimitados", "Upload de PDFs (até 5 arquivos)", "Economize R$ 39,80 (2 meses grátis)" ], "Interval": "year" },
|
||
"PremiumAffiliateYearly": { "Name": "Premium+Afiliados Anual", "PriceId": "price_1RycaEBMIadsOxJVEhsdB2Y1", "Price": 349.00, "MaxPages": 15, "MaxLinks": -1, "AllowPremiumThemes": true, "AllowProductLinks": true, "AllowAnalytics": true, "AllowDocumentUpload": true, "MaxDocuments": 10, "Features": [ "Tudo do Premium", "Links de produto", "Moderação especial", "Até 10 links afiliados", "Upload de PDFs (até 10 arquivos)", "Economize R$ 59,80 (2 meses grátis)" ], "Interval": "year" }
|
||
},
|
||
"Moderation": {
|
||
"PriorityTimeframes": { "Trial": "7.00:00:00", "Basic": "7.00:00:00", "Professional": "3.00:00:00", "Premium": "1.00:00:00" },
|
||
"MaxAttempts": 3,
|
||
"ModeratorEmail": "${{ vars.MODERATOR_EMAIL || 'ricardo.carneiro@jobmaker.com.br' }}",
|
||
"ModeratorEmails": [ "${{ vars.MODERATOR_EMAIL_1 || 'rrcgoncalves@gmail.com' }}", "${{ vars.MODERATOR_EMAIL_2 || 'rirocarneiro@gmail.com' }}" ]
|
||
},
|
||
"MongoDb": {
|
||
"ConnectionString": "mongodb://admin:c4rn31r0@129.146.116.218:27017,141.148.162.114:27017/BCardsDB?replicaSet=rs0&authSource=admin",
|
||
"DatabaseName": "BCardsDB"
|
||
},
|
||
"BaseUrl": "https://bcards.site",
|
||
"Tenant": {
|
||
"SiteName": "BCards",
|
||
"SiteDescription": "Crie sua página profissional com links organizados. A melhor alternativa para ter sua bio / links. Criada para profissionais e empresas no Brasil.",
|
||
"Tagline": "Sua bio de links profissional",
|
||
"SupportEmail": "suporte@bcards.site",
|
||
"ContentFolder": "bcards",
|
||
"AgeGated": false,
|
||
"UrlExample": "bcards.site/corretor/seu-nome",
|
||
"DpoEmail": "dpo@bcards.site",
|
||
"HeroHeadline": "Sua presença digital profissional em um só lugar",
|
||
"HeroDescription": "Organize seus links, portfólio, contatos e redes sociais em uma página única e elegante. Criado para profissionais e empresas brasileiras que querem ser encontrados.",
|
||
"HeroCtaText": "Criar Minha Página Grátis",
|
||
"FeaturesHeadline": "Por que profissionais escolhem o {SiteName}?",
|
||
"Features": [
|
||
{ "Icon": "🎨", "Title": "Temas Profissionais", "Description": "Mais de 40 temas para sua área: corretores, advogados, médicos, consultores e muito mais." },
|
||
{ "Icon": "📊", "Title": "Analytics de Verdade", "Description": "Saiba quantas pessoas acessaram sua página, quais links clicaram e de onde vieram." },
|
||
{ "Icon": "🔗", "Title": "URLs com Credibilidade", "Description": "Sua URL tem contexto profissional: bcards.site/corretor/seu-nome — transmite autoridade instantânea." }
|
||
],
|
||
"CtaHeadline": "Pronto para se destacar?",
|
||
"CtaDescription": "Junte-se a milhares de profissionais que já têm sua presença digital organizada no BCards.",
|
||
"CtaButtonText": "Criar Minha Página Grátis",
|
||
"MetaKeywords": "cartão digital, página de links, bio links, linktree brasil, página profissional, corretor, advogado, médico, consultor",
|
||
"FooterTagline": "Sua presença digital profissional, simplificada."
|
||
},
|
||
"Support": {
|
||
"TelegramUrl": "https://t.me/jobmakerbr",
|
||
"FormspreeUrl": "https://formspree.io/f/xpwynqpj",
|
||
"EnableTelegramForPlans": [ "Premium", "PremiumAffiliate" ],
|
||
"EnableFormForPlans": [ "Basic", "Professional", "Premium", "PremiumAffiliate" ],
|
||
"EnableRatingForAllUsers": true
|
||
},
|
||
"Serilog": {
|
||
"OpenSearchUrl": "${{ vars.OPENSEARCH_URL || 'http://141.148.162.114:19201' }}"
|
||
}
|
||
}
|
||
CONFIG_EOF
|
||
echo "✅ appsettings.bcards.json gerado"
|
||
|
||
- name: Deploy bcards stack
|
||
run: |
|
||
mkdir -p ~/.ssh
|
||
echo "${{ secrets.SSH_PRIVATE_KEY }}" > ~/.ssh/id_rsa
|
||
chmod 600 ~/.ssh/id_rsa
|
||
ssh-keyscan -H ${{ env.SWARM_MANAGER }} >> ~/.ssh/known_hosts 2>/dev/null
|
||
ssh-keyscan -H ${{ env.SWARM_WORKER }} >> ~/.ssh/known_hosts 2>/dev/null
|
||
|
||
# ── Sync Content to both nodes ──────────────────────────────────────
|
||
for NODE in ${{ env.SWARM_MANAGER }} ${{ env.SWARM_WORKER }}; do
|
||
echo "📂 Syncing content to $NODE..."
|
||
ssh -o StrictHostKeyChecking=no ubuntu@$NODE 'sudo mkdir -p /opt/bcards-content/bcards && sudo chown -R ubuntu:ubuntu /opt/bcards-content'
|
||
scp -o StrictHostKeyChecking=no -r src/BCards.Web/Content/Tenants/bcards/. ubuntu@$NODE:/opt/bcards-content/bcards/
|
||
done
|
||
|
||
# ── Deploy stack on manager ─────────────────────────────────────────
|
||
scp -o StrictHostKeyChecking=no appsettings.bcards.json ubuntu@${{ env.SWARM_MANAGER }}:/tmp/
|
||
scp -o StrictHostKeyChecking=no deploy/docker-stack-bcards.yml ubuntu@${{ env.SWARM_MANAGER }}:/tmp/
|
||
|
||
ssh -o StrictHostKeyChecking=no ubuntu@${{ env.SWARM_MANAGER }} << 'EOF'
|
||
set -e
|
||
echo "🔄 Updating bcards stack..."
|
||
|
||
docker config rm bcards-appsettings 2>/dev/null || true
|
||
CONFIG_NAME="bcards-appsettings-$(date +%s)"
|
||
docker config create ${CONFIG_NAME} /tmp/appsettings.bcards.json
|
||
|
||
sed "s/bcards-appsettings/${CONFIG_NAME}/g" /tmp/docker-stack-bcards.yml > /tmp/docker-stack-bcards-final.yml
|
||
|
||
docker stack deploy -c /tmp/docker-stack-bcards-final.yml bcards --with-registry-auth
|
||
|
||
rm -f /tmp/appsettings.bcards.json /tmp/docker-stack-bcards.yml /tmp/docker-stack-bcards-final.yml
|
||
echo "✅ bcards stack atualizado!"
|
||
EOF
|
||
|
||
- name: Health Check bcards
|
||
run: |
|
||
sleep 30
|
||
ssh -o StrictHostKeyChecking=no ubuntu@${{ env.SWARM_MANAGER }} \
|
||
'curl -sf http://localhost:8080/health && echo "✅ bcards healthy" || echo "⚠️ bcards health check failed"'
|
||
|
||
# ─── Deploy: spicylinks.site ──────────────────────────────────────────────
|
||
deploy-spicylinks:
|
||
name: Deploy spicylinks.site
|
||
runs-on: ubuntu-latest
|
||
needs: [build-and-push]
|
||
if: github.ref_name == 'main'
|
||
environment: production
|
||
|
||
steps:
|
||
- name: Checkout code
|
||
uses: actions/checkout@v4
|
||
|
||
- name: Generate appsettings for spicylinks
|
||
run: |
|
||
cat > appsettings.spicylinks.json << 'CONFIG_EOF'
|
||
{
|
||
"Logging": {
|
||
"LogLevel": {
|
||
"Default": "Information",
|
||
"Microsoft.AspNetCore": "Warning",
|
||
"BCards": "Information"
|
||
}
|
||
},
|
||
"AllowedHosts": "*",
|
||
"Stripe": {
|
||
"PublishableKey": "${{ vars.STRIPE_PUBLISHABLE_KEY }}",
|
||
"SecretKey": "${{ secrets.STRIPE_SECRET_KEY }}",
|
||
"WebhookSecret": "${{ secrets.STRIPE_WEBHOOK_SECRET }}",
|
||
"Environment": "${{ vars.STRIPE_ENVIRONMENT || 'test' }}"
|
||
},
|
||
"Authentication": {
|
||
"Google": {
|
||
"ClientId": "${{ vars.GOOGLE_CLIENT_ID }}",
|
||
"ClientSecret": "${{ secrets.GOOGLE_CLIENT_SECRET }}"
|
||
},
|
||
"Microsoft": {
|
||
"ClientId": "${{ vars.MICROSOFT_CLIENT_ID }}",
|
||
"ClientSecret": "${{ secrets.MICROSOFT_CLIENT_SECRET }}"
|
||
}
|
||
},
|
||
"SendGrid": {
|
||
"ApiKey": "${{ secrets.SENDGRID_API_KEY }}",
|
||
"FromEmail": "${{ vars.SPICYLINKS_FROM_EMAIL || 'noreply@spicylinks.site' }}",
|
||
"FromName": "${{ vars.SPICYLINKS_FROM_NAME || 'SpicyLinks' }}"
|
||
},
|
||
"Plans": {
|
||
"Basic": { "Name": "Básico", "PriceId": "price_1RycPaBMIadsOxJVKioZZofK", "Price": 12.90, "MaxPages": 3, "MaxLinks": 8, "AllowPremiumThemes": false, "AllowProductLinks": false, "AllowAnalytics": true, "AllowDocumentUpload": false, "MaxDocuments": 0, "Features": [ "URL Personalizada", "20 temas básicos", "Analytics simples" ], "Interval": "month" },
|
||
"Professional": { "Name": "Profissional", "PriceId": "price_1RycQmBMIadsOxJVGqjVMaOj", "Price": 25.90, "MaxPages": 5, "MaxLinks": 20, "AllowPremiumThemes": false, "AllowProductLinks": false, "AllowAnalytics": true, "AllowDocumentUpload": false, "MaxDocuments": 0, "Features": [ "URL Personalizada", "20 temas básicos", "Analytics avançado" ], "Interval": "month" },
|
||
"Premium": { "Name": "Premium", "PriceId": "price_1RycRUBMIadsOxJVkxGOh3uu", "Price": 29.90, "MaxPages": 15, "MaxLinks": -1, "AllowPremiumThemes": true, "AllowProductLinks": false, "AllowAnalytics": true, "AllowDocumentUpload": true, "MaxDocuments": 5, "Features": [ "URL Personalizada", "40 temas (básicos + premium)", "Suporte prioritário", "Links ilimitados" ], "Interval": "month" },
|
||
"PremiumAffiliate": { "Name": "Premium+Afiliados", "PriceId": "price_1RycTaBMIadsOxJVeDLseXQq", "Price": 34.90, "MaxPages": 15, "MaxLinks": -1, "AllowPremiumThemes": true, "AllowProductLinks": true, "AllowAnalytics": true, "AllowDocumentUpload": true, "MaxDocuments": 10, "Features": [ "Tudo do Premium", "Links de produto", "Moderação especial", "Até 10 links afiliados" ], "Interval": "month" },
|
||
"BasicYearly": { "Name": "Básico Anual", "PriceId": "price_1RycWgBMIadsOxJVGdtEeoMS", "Price": 129.00, "MaxPages": 3, "MaxLinks": 8, "AllowPremiumThemes": false, "AllowProductLinks": false, "AllowAnalytics": true, "AllowDocumentUpload": false, "MaxDocuments": 0, "Features": [ "URL Personalizada", "20 temas básicos", "Analytics simples", "Economize R$ 11,80 (2 meses grátis)" ], "Interval": "year" },
|
||
"ProfessionalYearly": { "Name": "Profissional Anual", "PriceId": "price_1RycXdBMIadsOxJV5cNX7dHm", "Price": 259.00, "MaxPages": 5, "MaxLinks": 20, "AllowPremiumThemes": false, "AllowProductLinks": false, "AllowAnalytics": true, "AllowDocumentUpload": false, "MaxDocuments": 0, "Features": [ "URL Personalizada", "20 temas básicos", "Analytics avançado", "Economize R$ 25,80 (2 meses grátis)" ], "Interval": "year" },
|
||
"PremiumYearly": { "Name": "Premium Anual", "PriceId": "price_1RycYnBMIadsOxJVPdKmzy4m", "Price": 299.00, "MaxPages": 15, "MaxLinks": -1, "AllowPremiumThemes": true, "AllowProductLinks": false, "AllowAnalytics": true, "AllowDocumentUpload": true, "MaxDocuments": 5, "Features": [ "URL Personalizada", "40 temas", "Suporte prioritário", "Links ilimitados", "Economize R$ 39,80 (2 meses grátis)" ], "Interval": "year" },
|
||
"PremiumAffiliateYearly": { "Name": "Premium+Afiliados Anual", "PriceId": "price_1RycaEBMIadsOxJVEhsdB2Y1", "Price": 349.00, "MaxPages": 15, "MaxLinks": -1, "AllowPremiumThemes": true, "AllowProductLinks": true, "AllowAnalytics": true, "AllowDocumentUpload": true, "MaxDocuments": 10, "Features": [ "Tudo do Premium", "Links de produto", "Moderação especial", "Até 10 links afiliados", "Economize R$ 59,80 (2 meses grátis)" ], "Interval": "year" }
|
||
},
|
||
"Moderation": {
|
||
"PriorityTimeframes": { "Trial": "7.00:00:00", "Basic": "7.00:00:00", "Professional": "3.00:00:00", "Premium": "1.00:00:00" },
|
||
"MaxAttempts": 3,
|
||
"ModeratorEmail": "${{ vars.MODERATOR_EMAIL || 'ricardo.carneiro@jobmaker.com.br' }}",
|
||
"ModeratorEmails": [ "${{ vars.MODERATOR_EMAIL_1 || 'rrcgoncalves@gmail.com' }}", "${{ vars.MODERATOR_EMAIL_2 || 'rirocarneiro@gmail.com' }}" ]
|
||
},
|
||
"MongoDb": {
|
||
"ConnectionString": "mongodb://admin:c4rn31r0@129.146.116.218:27017,141.148.162.114:27017/SpicyLinksDB?replicaSet=rs0&authSource=admin",
|
||
"DatabaseName": "SpicyLinksDB"
|
||
},
|
||
"BaseUrl": "https://spicylinks.site",
|
||
"Tenant": {
|
||
"SiteName": "SpicyLinks",
|
||
"SiteDescription": "A plataforma discreta e segura para criadores de conteúdo adulto. Reúna suas assinaturas, lista de desejos, redes sociais e conteúdo exclusivo em uma única bio.",
|
||
"Tagline": "Seu conteúdo exclusivo, um link só",
|
||
"SupportEmail": "suporte@spicylinks.site",
|
||
"ContentFolder": "spicylinks",
|
||
"AgeGated": true,
|
||
"UrlExample": "spicylinks.site/modelo/seu-nome",
|
||
"DpoEmail": "dpo@spicylinks.site",
|
||
"HeroHeadline": "Seu conteúdo exclusivo, um link só",
|
||
"HeroDescription": "A plataforma discreta e segura para criadores de conteúdo adulto. Reúna suas assinaturas, lista de desejos, redes e conteúdo exclusivo em uma bio elegante.",
|
||
"HeroCtaText": "Criar Minha Bio",
|
||
"FeaturesHeadline": "Por que criadores escolhem o {SiteName}?",
|
||
"Features": [
|
||
{ "Icon": "❤️", "Title": "Tudo num Só Link", "Description": "Instagram, Twitter/X, OnlyFans, lista de desejos e mais — tudo em uma bio única, elegante e fácil de compartilhar." },
|
||
{ "Icon": "🔒", "Title": "Verificação de Idade", "Description": "Acesso protegido com verificação de idade automática. Plataforma segura, discreta e responsável." },
|
||
{ "Icon": "📊", "Title": "Saiba Quem Te Visita", "Description": "Analytics detalhado de cliques, visualizações e origem do tráfego para otimizar suas conversões." }
|
||
],
|
||
"CtaHeadline": "Pronta para monetizar seu conteúdo?",
|
||
"CtaDescription": "Milhares de criadoras já centralizam seus links e aumentam suas conversões com o SpicyLinks.",
|
||
"CtaButtonText": "Criar Minha Bio",
|
||
"MetaKeywords": "bio links criadora, creator bio, linktree conteudo adulto, links onlyfans, bio instagram criadora",
|
||
"FooterTagline": "Seu conteúdo, sua identidade."
|
||
},
|
||
"Support": {
|
||
"TelegramUrl": "https://t.me/jobmakerbr",
|
||
"FormspreeUrl": "https://formspree.io/f/xpwynqpj",
|
||
"EnableTelegramForPlans": [ "Premium", "PremiumAffiliate" ],
|
||
"EnableFormForPlans": [ "Basic", "Professional", "Premium", "PremiumAffiliate" ],
|
||
"EnableRatingForAllUsers": true
|
||
},
|
||
"Serilog": {
|
||
"OpenSearchUrl": "${{ vars.OPENSEARCH_URL || 'http://141.148.162.114:19201' }}"
|
||
}
|
||
}
|
||
CONFIG_EOF
|
||
echo "✅ appsettings.spicylinks.json gerado"
|
||
|
||
- name: Deploy spicylinks stack
|
||
run: |
|
||
mkdir -p ~/.ssh
|
||
echo "${{ secrets.SSH_PRIVATE_KEY }}" > ~/.ssh/id_rsa
|
||
chmod 600 ~/.ssh/id_rsa
|
||
ssh-keyscan -H ${{ env.SWARM_MANAGER }} >> ~/.ssh/known_hosts 2>/dev/null
|
||
ssh-keyscan -H ${{ env.SWARM_WORKER }} >> ~/.ssh/known_hosts 2>/dev/null
|
||
|
||
# ── Sync Content to both nodes ──────────────────────────────────────
|
||
for NODE in ${{ env.SWARM_MANAGER }} ${{ env.SWARM_WORKER }}; do
|
||
echo "📂 Syncing spicylinks content to $NODE..."
|
||
ssh -o StrictHostKeyChecking=no ubuntu@$NODE 'sudo mkdir -p /opt/bcards-content/spicylinks && sudo chown -R ubuntu:ubuntu /opt/bcards-content'
|
||
scp -o StrictHostKeyChecking=no -r src/BCards.Web/Content/Tenants/spicylinks/. ubuntu@$NODE:/opt/bcards-content/spicylinks/
|
||
done
|
||
|
||
# ── Deploy stack on manager ─────────────────────────────────────────
|
||
scp -o StrictHostKeyChecking=no appsettings.spicylinks.json ubuntu@${{ env.SWARM_MANAGER }}:/tmp/
|
||
scp -o StrictHostKeyChecking=no deploy/docker-stack-spicylinks.yml ubuntu@${{ env.SWARM_MANAGER }}:/tmp/
|
||
|
||
ssh -o StrictHostKeyChecking=no ubuntu@${{ env.SWARM_MANAGER }} << 'EOF'
|
||
set -e
|
||
echo "🔄 Updating spicylinks stack..."
|
||
|
||
docker config rm spicylinks-appsettings 2>/dev/null || true
|
||
CONFIG_NAME="spicylinks-appsettings-$(date +%s)"
|
||
docker config create ${CONFIG_NAME} /tmp/appsettings.spicylinks.json
|
||
|
||
sed "s/spicylinks-appsettings/${CONFIG_NAME}/g" /tmp/docker-stack-spicylinks.yml > /tmp/docker-stack-spicylinks-final.yml
|
||
|
||
docker stack deploy -c /tmp/docker-stack-spicylinks-final.yml spicylinks --with-registry-auth
|
||
|
||
rm -f /tmp/appsettings.spicylinks.json /tmp/docker-stack-spicylinks.yml /tmp/docker-stack-spicylinks-final.yml
|
||
echo "✅ spicylinks stack atualizado!"
|
||
EOF
|
||
|
||
- name: Health Check spicylinks
|
||
run: |
|
||
sleep 30
|
||
ssh -o StrictHostKeyChecking=no ubuntu@${{ env.SWARM_MANAGER }} \
|
||
'curl -sf http://localhost:8082/health && echo "✅ spicylinks healthy" || echo "⚠️ spicylinks health check failed"'
|
||
|
||
# ─── Deploy: luslinks.site ────────────────────────────────────────────────
|
||
deploy-luslinks:
|
||
name: Deploy luslinks.site
|
||
runs-on: ubuntu-latest
|
||
needs: [build-and-push]
|
||
if: github.ref_name == 'main'
|
||
environment: production
|
||
|
||
steps:
|
||
- name: Checkout code
|
||
uses: actions/checkout@v4
|
||
|
||
- name: Generate appsettings for luslinks
|
||
run: |
|
||
cat > appsettings.luslinks.json << 'CONFIG_EOF'
|
||
{
|
||
"Logging": {
|
||
"LogLevel": {
|
||
"Default": "Information",
|
||
"Microsoft.AspNetCore": "Warning",
|
||
"BCards": "Information"
|
||
}
|
||
},
|
||
"AllowedHosts": "*",
|
||
"Stripe": {
|
||
"PublishableKey": "${{ vars.STRIPE_PUBLISHABLE_KEY }}",
|
||
"SecretKey": "${{ secrets.STRIPE_SECRET_KEY }}",
|
||
"WebhookSecret": "${{ secrets.STRIPE_WEBHOOK_SECRET }}",
|
||
"Environment": "${{ vars.STRIPE_ENVIRONMENT || 'test' }}"
|
||
},
|
||
"Authentication": {
|
||
"Google": {
|
||
"ClientId": "${{ vars.GOOGLE_CLIENT_ID }}",
|
||
"ClientSecret": "${{ secrets.GOOGLE_CLIENT_SECRET }}"
|
||
},
|
||
"Microsoft": {
|
||
"ClientId": "${{ vars.MICROSOFT_CLIENT_ID }}",
|
||
"ClientSecret": "${{ secrets.MICROSOFT_CLIENT_SECRET }}"
|
||
}
|
||
},
|
||
"SendGrid": {
|
||
"ApiKey": "${{ secrets.SENDGRID_API_KEY }}",
|
||
"FromEmail": "${{ vars.LUSLINKS_FROM_EMAIL || 'noreply@luslinks.site' }}",
|
||
"FromName": "${{ vars.LUSLINKS_FROM_NAME || 'LusLinks' }}"
|
||
},
|
||
"Plans": {
|
||
"Basic": { "Name": "Básico", "PriceId": "price_1RycPaBMIadsOxJVKioZZofK", "Price": 12.90, "MaxPages": 3, "MaxLinks": 8, "AllowPremiumThemes": false, "AllowProductLinks": false, "AllowAnalytics": true, "AllowDocumentUpload": false, "MaxDocuments": 0, "Features": [ "URL Personalizada", "20 temas básicos", "Analytics simples" ], "Interval": "month" },
|
||
"Professional": { "Name": "Profissional", "PriceId": "price_1RycQmBMIadsOxJVGqjVMaOj", "Price": 25.90, "MaxPages": 5, "MaxLinks": 20, "AllowPremiumThemes": false, "AllowProductLinks": false, "AllowAnalytics": true, "AllowDocumentUpload": false, "MaxDocuments": 0, "Features": [ "URL Personalizada", "20 temas básicos", "Analytics avançado" ], "Interval": "month" },
|
||
"Premium": { "Name": "Premium", "PriceId": "price_1RycRUBMIadsOxJVkxGOh3uu", "Price": 29.90, "MaxPages": 15, "MaxLinks": -1, "AllowPremiumThemes": true, "AllowProductLinks": false, "AllowAnalytics": true, "AllowDocumentUpload": true, "MaxDocuments": 5, "Features": [ "URL Personalizada", "40 temas", "Suporte prioritário", "Links ilimitados" ], "Interval": "month" },
|
||
"PremiumAffiliate": { "Name": "Premium+Afiliados", "PriceId": "price_1RycTaBMIadsOxJVeDLseXQq", "Price": 34.90, "MaxPages": 15, "MaxLinks": -1, "AllowPremiumThemes": true, "AllowProductLinks": true, "AllowAnalytics": true, "AllowDocumentUpload": true, "MaxDocuments": 10, "Features": [ "Tudo do Premium", "Links de produto", "Moderação especial", "Até 10 links afiliados" ], "Interval": "month" },
|
||
"BasicYearly": { "Name": "Básico Anual", "PriceId": "price_1RycWgBMIadsOxJVGdtEeoMS", "Price": 129.00, "MaxPages": 3, "MaxLinks": 8, "AllowPremiumThemes": false, "AllowProductLinks": false, "AllowAnalytics": true, "AllowDocumentUpload": false, "MaxDocuments": 0, "Features": [ "URL Personalizada", "20 temas básicos", "Analytics simples", "Economize R$ 11,80 (2 meses grátis)" ], "Interval": "year" },
|
||
"ProfessionalYearly": { "Name": "Profissional Anual", "PriceId": "price_1RycXdBMIadsOxJV5cNX7dHm", "Price": 259.00, "MaxPages": 5, "MaxLinks": 20, "AllowPremiumThemes": false, "AllowProductLinks": false, "AllowAnalytics": true, "AllowDocumentUpload": false, "MaxDocuments": 0, "Features": [ "URL Personalizada", "20 temas básicos", "Analytics avançado", "Economize R$ 25,80 (2 meses grátis)" ], "Interval": "year" },
|
||
"PremiumYearly": { "Name": "Premium Anual", "PriceId": "price_1RycYnBMIadsOxJVPdKmzy4m", "Price": 299.00, "MaxPages": 15, "MaxLinks": -1, "AllowPremiumThemes": true, "AllowProductLinks": false, "AllowAnalytics": true, "AllowDocumentUpload": true, "MaxDocuments": 5, "Features": [ "URL Personalizada", "40 temas", "Suporte prioritário", "Links ilimitados", "Economize R$ 39,80 (2 meses grátis)" ], "Interval": "year" },
|
||
"PremiumAffiliateYearly": { "Name": "Premium+Afiliados Anual", "PriceId": "price_1RycaEBMIadsOxJVEhsdB2Y1", "Price": 349.00, "MaxPages": 15, "MaxLinks": -1, "AllowPremiumThemes": true, "AllowProductLinks": true, "AllowAnalytics": true, "AllowDocumentUpload": true, "MaxDocuments": 10, "Features": [ "Tudo do Premium", "Links de produto", "Moderação especial", "Até 10 links afiliados", "Economize R$ 59,80 (2 meses grátis)" ], "Interval": "year" }
|
||
},
|
||
"Moderation": {
|
||
"PriorityTimeframes": { "Trial": "7.00:00:00", "Basic": "7.00:00:00", "Professional": "3.00:00:00", "Premium": "1.00:00:00" },
|
||
"MaxAttempts": 3,
|
||
"ModeratorEmail": "${{ vars.MODERATOR_EMAIL || 'ricardo.carneiro@jobmaker.com.br' }}",
|
||
"ModeratorEmails": [ "${{ vars.MODERATOR_EMAIL_1 || 'rrcgoncalves@gmail.com' }}", "${{ vars.MODERATOR_EMAIL_2 || 'rirocarneiro@gmail.com' }}" ]
|
||
},
|
||
"MongoDb": {
|
||
"ConnectionString": "mongodb://admin:c4rn31r0@129.146.116.218:27017,141.148.162.114:27017/LusLinksDB?replicaSet=rs0&authSource=admin",
|
||
"DatabaseName": "LusLinksDB"
|
||
},
|
||
"BaseUrl": "https://luslinks.site",
|
||
"Tenant": {
|
||
"SiteName": "LusLinks",
|
||
"SiteDescription": "A plataforma para pastores, padres, líderes religiosos e ministérios. Reúna seus estudos bíblicos, eventos, lives e canal em uma única página de fé.",
|
||
"Tagline": "Conecte sua comunidade em um único link",
|
||
"SupportEmail": "suporte@luslinks.site",
|
||
"ContentFolder": "luslinks",
|
||
"AgeGated": false,
|
||
"UrlExample": "luslinks.site/pastor/seu-nome",
|
||
"DpoEmail": "dpo@luslinks.site",
|
||
"HeroHeadline": "Conecte sua comunidade em um único link",
|
||
"HeroDescription": "A plataforma ideal para pastores, padres, líderes e ministérios. Reúna seus estudos bíblicos, agenda de cultos, canal e dízimos em uma só página.",
|
||
"HeroCtaText": "Criar Minha Bio de Fé",
|
||
"FeaturesHeadline": "Por que líderes religiosos usam o {SiteName}?",
|
||
"Features": [
|
||
{ "Icon": "📖", "Title": "Conteúdo Espiritual Organizado", "Description": "Concentre seus estudos bíblicos, séries de pregações, agenda de cultos e canal do YouTube em um só link." },
|
||
{ "Icon": "🙏", "Title": "Facilite Dízimos e Ofertas", "Description": "Link direto para doações do seu ministério. Simplifique as ofertas e dízimos da sua congregação." },
|
||
{ "Icon": "📅", "Title": "Agenda e Eventos", "Description": "Compartilhe retiros, cultos especiais e eventos com toda a comunidade de forma simples e organizada." }
|
||
],
|
||
"CtaHeadline": "Compartilhe sua mensagem com o mundo",
|
||
"CtaDescription": "Líderes de toda denominação já usam o LusLinks para alcançar mais pessoas com sua mensagem de fé.",
|
||
"CtaButtonText": "Criar Minha Bio de Fé",
|
||
"MetaKeywords": "bio links pastor, página ministério, linktree cristão, links religiosos, página iglesia, bio pastor, links igreja",
|
||
"FooterTagline": "Conectando fé e comunidade."
|
||
},
|
||
"Support": {
|
||
"TelegramUrl": "https://t.me/jobmakerbr",
|
||
"FormspreeUrl": "https://formspree.io/f/xpwynqpj",
|
||
"EnableTelegramForPlans": [ "Premium", "PremiumAffiliate" ],
|
||
"EnableFormForPlans": [ "Basic", "Professional", "Premium", "PremiumAffiliate" ],
|
||
"EnableRatingForAllUsers": true
|
||
},
|
||
"Serilog": {
|
||
"OpenSearchUrl": "${{ vars.OPENSEARCH_URL || 'http://141.148.162.114:19201' }}"
|
||
}
|
||
}
|
||
CONFIG_EOF
|
||
echo "✅ appsettings.luslinks.json gerado"
|
||
|
||
- name: Deploy luslinks stack
|
||
run: |
|
||
mkdir -p ~/.ssh
|
||
echo "${{ secrets.SSH_PRIVATE_KEY }}" > ~/.ssh/id_rsa
|
||
chmod 600 ~/.ssh/id_rsa
|
||
ssh-keyscan -H ${{ env.SWARM_MANAGER }} >> ~/.ssh/known_hosts 2>/dev/null
|
||
ssh-keyscan -H ${{ env.SWARM_WORKER }} >> ~/.ssh/known_hosts 2>/dev/null
|
||
|
||
# ── Sync Content to both nodes ──────────────────────────────────────
|
||
for NODE in ${{ env.SWARM_MANAGER }} ${{ env.SWARM_WORKER }}; do
|
||
echo "📂 Syncing luslinks content to $NODE..."
|
||
ssh -o StrictHostKeyChecking=no ubuntu@$NODE 'sudo mkdir -p /opt/bcards-content/luslinks && sudo chown -R ubuntu:ubuntu /opt/bcards-content'
|
||
scp -o StrictHostKeyChecking=no -r src/BCards.Web/Content/Tenants/luslinks/. ubuntu@$NODE:/opt/bcards-content/luslinks/
|
||
done
|
||
|
||
# ── Deploy stack on manager ─────────────────────────────────────────
|
||
scp -o StrictHostKeyChecking=no appsettings.luslinks.json ubuntu@${{ env.SWARM_MANAGER }}:/tmp/
|
||
scp -o StrictHostKeyChecking=no deploy/docker-stack-luslinks.yml ubuntu@${{ env.SWARM_MANAGER }}:/tmp/
|
||
|
||
ssh -o StrictHostKeyChecking=no ubuntu@${{ env.SWARM_MANAGER }} << 'EOF'
|
||
set -e
|
||
echo "🔄 Updating luslinks stack..."
|
||
|
||
docker config rm luslinks-appsettings 2>/dev/null || true
|
||
CONFIG_NAME="luslinks-appsettings-$(date +%s)"
|
||
docker config create ${CONFIG_NAME} /tmp/appsettings.luslinks.json
|
||
|
||
sed "s/luslinks-appsettings/${CONFIG_NAME}/g" /tmp/docker-stack-luslinks.yml > /tmp/docker-stack-luslinks-final.yml
|
||
|
||
docker stack deploy -c /tmp/docker-stack-luslinks-final.yml luslinks --with-registry-auth
|
||
|
||
rm -f /tmp/appsettings.luslinks.json /tmp/docker-stack-luslinks.yml /tmp/docker-stack-luslinks-final.yml
|
||
echo "✅ luslinks stack atualizado!"
|
||
EOF
|
||
|
||
- name: Health Check luslinks
|
||
run: |
|
||
sleep 30
|
||
ssh -o StrictHostKeyChecking=no ubuntu@${{ env.SWARM_MANAGER }} \
|
||
'curl -sf http://localhost:8083/health && echo "✅ luslinks healthy" || echo "⚠️ luslinks health check failed"'
|
||
|
||
# ─── Release branch deploy (test swarm) ───────────────────────────────────
|
||
deploy-test:
|
||
name: Deploy to Release Swarm (ARM)
|
||
runs-on: [self-hosted, arm64, bcards]
|
||
needs: [build-and-push]
|
||
if: startsWith(github.ref_name, 'Release/')
|
||
|
||
steps:
|
||
- name: Checkout code
|
||
uses: actions/checkout@v4
|
||
|
||
- name: Extract version
|
||
id: version
|
||
run: |
|
||
BRANCH_NAME="${{ github.ref_name }}"
|
||
VERSION_RAW=${BRANCH_NAME#Release/}
|
||
VERSION=$(echo "$VERSION_RAW" | sed 's/^[Vv]\([0-9]\)/\1/')
|
||
[ -z "$VERSION" ] && VERSION="0.0.1"
|
||
echo "version=$VERSION" >> $GITHUB_OUTPUT
|
||
echo "📦 Deploying version: $VERSION"
|
||
|
||
- name: Prepare release stack manifest
|
||
run: |
|
||
mkdir -p artifacts
|
||
BCARDS_IMAGE=${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}:${{ steps.version.outputs.version }}
|
||
sed "s|\${BCARDS_IMAGE}|${BCARDS_IMAGE}|g" deploy/docker-stack.release.yml > artifacts/docker-stack.release.yml
|
||
echo "🔧 Generated manifest with image: ${BCARDS_IMAGE}"
|
||
|
||
- name: Deploy to release swarm
|
||
run: |
|
||
docker stack deploy -c artifacts/docker-stack.release.yml bcards-release
|
||
|
||
- name: Await release service readiness
|
||
run: |
|
||
echo "⏳ Aguardando serviço bcards-release estabilizar..."
|
||
ATTEMPTS=30
|
||
while [ $ATTEMPTS -gt 0 ]; do
|
||
REPLICAS=$(docker service ls --filter name=bcards-release_bcards-release --format '{{.Replicas}}')
|
||
if [ "$REPLICAS" = "1/1" ]; then
|
||
echo "✅ Serviço com $REPLICAS réplica"
|
||
break
|
||
fi
|
||
echo "Atual: ${REPLICAS:-N/A}; aguardando..."
|
||
sleep 5
|
||
ATTEMPTS=$((ATTEMPTS-1))
|
||
done
|
||
if [ "$REPLICAS" != "1/1" ]; then
|
||
echo "❌ Serviço não atingiu 1/1 réplica"
|
||
docker service ps bcards-release_bcards-release
|
||
exit 1
|
||
fi
|
||
docker service ps bcards-release_bcards-release
|
||
|
||
# ─── Cleanup ──────────────────────────────────────────────────────────────
|
||
cleanup:
|
||
name: Cleanup Old Resources
|
||
runs-on: ubuntu-latest
|
||
needs: [deploy-bcards, deploy-spicylinks, deploy-luslinks, deploy-test]
|
||
if: always() && (needs.deploy-bcards.result == 'success' || needs.deploy-spicylinks.result == 'success' || needs.deploy-luslinks.result == 'success' || needs.deploy-test.result == 'success')
|
||
|
||
steps:
|
||
- name: Cleanup containers and images
|
||
run: |
|
||
echo "🧹 Limpando recursos antigos..."
|
||
|
||
if [ "${{ github.ref_name }}" = "main" ]; then
|
||
mkdir -p ~/.ssh
|
||
echo "${{ secrets.SSH_PRIVATE_KEY }}" > ~/.ssh/id_rsa
|
||
chmod 600 ~/.ssh/id_rsa
|
||
ssh-keyscan -H ${{ env.SWARM_MANAGER }} >> ~/.ssh/known_hosts 2>/dev/null
|
||
ssh-keyscan -H ${{ env.SWARM_WORKER }} >> ~/.ssh/known_hosts 2>/dev/null
|
||
|
||
for SERVER in ${{ env.SWARM_MANAGER }} ${{ env.SWARM_WORKER }}; do
|
||
echo "🧹 Limpando $SERVER..."
|
||
ssh -o StrictHostKeyChecking=no ubuntu@$SERVER << 'EOF'
|
||
docker container prune -f
|
||
docker image prune -f
|
||
docker network prune -f
|
||
# Remove stale swarm configs (keep last 3 per tenant)
|
||
for TENANT in bcards spicylinks luslinks; do
|
||
docker config ls --filter "name=${TENANT}-appsettings" --format "{{.ID}} {{.Name}}" \
|
||
| sort -k2 | head -n -3 | awk '{print $1}' \
|
||
| xargs -r docker config rm 2>/dev/null || true
|
||
done
|
||
EOF
|
||
done
|
||
else
|
||
echo "ℹ️ Release branch: limpeza remota ignorada."
|
||
fi
|
||
|
||
echo "✅ Limpeza concluída!"
|
||
|
||
# ─── Summary ──────────────────────────────────────────────────────────────
|
||
deployment-summary:
|
||
name: Deployment Summary
|
||
runs-on: ubuntu-latest
|
||
needs: [deploy-bcards, deploy-spicylinks, deploy-luslinks, deploy-test]
|
||
if: always()
|
||
|
||
steps:
|
||
- name: Summary
|
||
run: |
|
||
echo "📋 DEPLOYMENT SUMMARY"
|
||
echo "===================="
|
||
echo "🎯 Branch: ${{ github.ref_name }}"
|
||
echo "🔑 Commit: ${{ github.sha }}"
|
||
echo "🏗️ Registry: ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}"
|
||
|
||
if [ "${{ github.ref_name }}" = "main" ]; then
|
||
echo "🌍 Environment: Production (Multi-Tenant)"
|
||
echo "🖥️ Manager: ${{ env.SWARM_MANAGER }}"
|
||
echo "🖥️ Worker: ${{ env.SWARM_WORKER }}"
|
||
echo ""
|
||
echo " bcards.site → :8080 [${{ needs.deploy-bcards.result }}]"
|
||
echo " spicylinks.site → :8082 [${{ needs.deploy-spicylinks.result }}]"
|
||
echo " luslinks.site → :8083 [${{ needs.deploy-luslinks.result }}]"
|
||
else
|
||
echo "🌍 Environment: Release (Test Swarm)"
|
||
echo "🔗 Status: ${{ needs.deploy-test.result }}"
|
||
fi
|
||
|
||
echo "===================="
|
||
echo "✅ Pipeline completed!"
|