NALU/.gitea/workflows/deploy-nalu.yml
Ricardo Carneiro ba72b04313
Some checks failed
NALU Deployment Pipeline / Run Tests (push) Successful in 1m6s
NALU Deployment Pipeline / PR Validation (push) Has been skipped
NALU Deployment Pipeline / Build and Push Image (push) Failing after 21s
NALU Deployment Pipeline / Deploy naluai.dev (push) Has been skipped
NALU Deployment Pipeline / Cleanup Old Resources (push) Has been skipped
fix: strip CRLF from SSH key in all workflow steps
Gitea act runner injects secrets with \r\n line endings.
Use printf + tr -d '\r' instead of echo to avoid libcrypto error.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-15 14:08:47 -03:00

278 lines
11 KiB
YAML
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

name: NALU Deployment Pipeline
# ─── Required Gitea Secrets ────────────────────────────────────────────────
# secrets.SSH_PRIVATE_KEY SSH key for ubuntu@ on OCI nodes
# secrets.NALU_MONGODB_CONNECTION MongoDB connection string (with password)
# secrets.NALU_GROQ_API_KEY Groq API key
# secrets.NALU_OPENROUTER_API_KEY OpenRouter API key
# secrets.NALU_GOOGLEAI_API_KEY Google AI (Gemini) API key
# secrets.NALU_STRIPE_SECRET_KEY Stripe secret key
# secrets.NALU_STRIPE_WEBHOOK_SECRET Stripe webhook signing secret
# secrets.NALU_OAUTH_GOOGLE_SECRET Google OAuth client secret
# secrets.NALU_OAUTH_MS_SECRET Microsoft OAuth client secret
# secrets.NALU_OAUTH_GITHUB_SECRET GitHub OAuth client secret
#
# ─── Required Gitea Variables (vars.*) ────────────────────────────────────
# vars.NALU_STRIPE_PUBLISHABLE_KEY
# vars.NALU_OAUTH_GOOGLE_CLIENT_ID
# vars.NALU_OAUTH_MS_CLIENT_ID
# vars.NALU_OAUTH_GITHUB_CLIENT_ID
on:
push:
branches:
- main
pull_request:
branches: [ main ]
types: [opened, synchronize, reopened]
env:
REGISTRY: registry.redecarneir.us
IMAGE_NAME: nalu
SWARM_MANAGER: 141.148.162.114
SWARM_WORKER: 129.146.116.218
jobs:
# ─── Tests ────────────────────────────────────────────────────────────────
test:
name: Run Tests
runs-on: ubuntu-latest
steps:
- name: Checkout code
uses: actions/checkout@v4
- name: Setup .NET 9
uses: actions/setup-dotnet@v4
with:
dotnet-version: '9.0.x'
- name: Cache NuGet packages
uses: actions/cache@v3
with:
path: ~/.nuget/packages
key: ${{ runner.os }}-nuget-${{ hashFiles('**/*.csproj') }}
restore-keys: |
${{ runner.os }}-nuget-
- name: Restore dependencies
run: dotnet restore
- name: Build solution
run: dotnet build --no-restore --configuration Release
- name: Run tests
run: dotnet test --no-build --configuration Release --verbosity normal --filter "FullyQualifiedName!~PipelineIntegration&FullyQualifiedName!~McpServerTests"
# ─── PR Validation ────────────────────────────────────────────────────────
pr-validation:
name: PR Validation
runs-on: ubuntu-latest
needs: [test]
if: github.event_name == 'pull_request'
steps:
- name: PR ready
run: echo "✅ PR validated — tests passed"
# ─── Build & Push ─────────────────────────────────────────────────────────
build-and-push:
name: Build and Push Image
runs-on: ubuntu-latest
needs: [test]
if: github.event_name == 'push' && github.ref_name == 'main' && needs.test.result == 'success'
steps:
- name: Checkout code
uses: actions/checkout@v4
- name: Setup SSH
run: |
mkdir -p ~/.ssh
printf '%s' "${{ secrets.SSH_PRIVATE_KEY }}" | tr -d '\r' > ~/.ssh/id_rsa
echo "" >> ~/.ssh/id_rsa
chmod 600 ~/.ssh/id_rsa
ssh-keyscan -H ${{ env.SWARM_MANAGER }} >> ~/.ssh/known_hosts 2>/dev/null
- name: Build image on ARM server
run: |
echo "🏗️ Syncing source to ARM builder..."
ssh -o StrictHostKeyChecking=no ubuntu@${{ env.SWARM_MANAGER }} 'rm -rf /tmp/nalu-build && mkdir -p /tmp/nalu-build'
rsync -az --delete \
--exclude='.git' \
--exclude='**/bin/' \
--exclude='**/obj/' \
--exclude='**/.vs/' \
-e "ssh -o StrictHostKeyChecking=no" \
./ ubuntu@${{ env.SWARM_MANAGER }}:/tmp/nalu-build/
echo "🔨 Building Docker image natively on ARM64..."
ssh -o StrictHostKeyChecking=no ubuntu@${{ env.SWARM_MANAGER }} << 'EOF'
set -e
cd /tmp/nalu-build
docker build \
--tag ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}:latest \
--tag ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}:${{ github.sha }} \
--progress=plain \
.
docker push ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}:latest
docker push ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}:${{ github.sha }}
rm -rf /tmp/nalu-build
echo "✅ Image pushed"
EOF
# ─── Deploy: naluai.dev ───────────────────────────────────────────────────
deploy:
name: Deploy naluai.dev
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 non-sensitive appsettings
run: |
cat > appsettings.nalu.json << 'CONFIG_EOF'
{
"Logging": {
"LogLevel": {
"Default": "Information",
"Microsoft.AspNetCore": "Warning"
}
},
"AllowedHosts": "*",
"Hangfire": {
"NameImporterCron": "0 3 1 * *"
},
"Groq": {
"BaseUrl": "https://api.groq.com/openai/v1",
"Model": "llama-3.3-70b-versatile",
"MaxTokens": 500,
"Temperature": 0.1
},
"OpenRouter": {
"BaseUrl": "https://openrouter.ai/api/v1",
"Model": "google/gemma-4-31b-it:free",
"MaxTokens": 500,
"Temperature": 0.1
},
"GoogleAi": {
"BaseUrl": "https://generativelanguage.googleapis.com/v1beta/openai/",
"Model": "gemini-2.0-flash"
},
"Plans": {
"free": { "credits_per_month": 3000, "credits_per_day": 100, "price_brl": 0, "price_usd": 0 },
"starter": { "credits_per_month": 15000, "credits_per_day": 0, "price_brl": 2900, "price_usd": 590 },
"indie": { "credits_per_month": 50000, "credits_per_day": 0, "price_brl": 6900, "price_usd": 1390 },
"pro": { "credits_per_month": 250000, "credits_per_day": 0, "price_brl": 19900, "price_usd": 3990 }
},
"RateLimit": {
"PerIpPerMinute": 60,
"PerIpPerHour": 500
},
"Cache": {
"DefaultTtlMinutes": 60
},
"Stripe": {
"PublishableKey": "${{ vars.NALU_STRIPE_PUBLISHABLE_KEY }}"
},
"OAuth": {
"Google": {
"ClientId": "${{ vars.NALU_OAUTH_GOOGLE_CLIENT_ID }}"
},
"Microsoft": {
"ClientId": "${{ vars.NALU_OAUTH_MS_CLIENT_ID }}",
"TenantId": "common"
},
"GitHub": {
"ClientId": "${{ vars.NALU_OAUTH_GITHUB_CLIENT_ID }}"
}
}
}
CONFIG_EOF
echo "✅ appsettings.nalu.json gerado"
- name: Deploy nalu stack
run: |
mkdir -p ~/.ssh
printf '%s' "${{ secrets.SSH_PRIVATE_KEY }}" | tr -d '\r' > ~/.ssh/id_rsa
echo "" >> ~/.ssh/id_rsa
chmod 600 ~/.ssh/id_rsa
ssh-keyscan -H ${{ env.SWARM_MANAGER }} >> ~/.ssh/known_hosts 2>/dev/null
scp -o StrictHostKeyChecking=no appsettings.nalu.json ubuntu@${{ env.SWARM_MANAGER }}:/tmp/
scp -o StrictHostKeyChecking=no deploy/docker-stack-nalu.yml ubuntu@${{ env.SWARM_MANAGER }}:/tmp/
ssh -o StrictHostKeyChecking=no ubuntu@${{ env.SWARM_MANAGER }} << SSHEOF
set -e
# ── Create/update Docker secrets ─────────────────────────────────
update_secret() {
local name=\$1
local value=\$2
docker secret rm "\$name" 2>/dev/null || true
printf '%s' "\$value" | docker secret create "\$name" -
}
update_secret nalu_mongodb_connection '${{ secrets.NALU_MONGODB_CONNECTION }}'
update_secret nalu_groq_api_key '${{ secrets.NALU_GROQ_API_KEY }}'
update_secret nalu_openrouter_api_key '${{ secrets.NALU_OPENROUTER_API_KEY }}'
update_secret nalu_googleai_api_key '${{ secrets.NALU_GOOGLEAI_API_KEY }}'
update_secret nalu_stripe_secret_key '${{ secrets.NALU_STRIPE_SECRET_KEY }}'
update_secret nalu_stripe_webhook_secret '${{ secrets.NALU_STRIPE_WEBHOOK_SECRET }}'
update_secret nalu_oauth_google_secret '${{ secrets.NALU_OAUTH_GOOGLE_SECRET }}'
update_secret nalu_oauth_ms_secret '${{ secrets.NALU_OAUTH_MS_SECRET }}'
update_secret nalu_oauth_github_secret '${{ secrets.NALU_OAUTH_GITHUB_SECRET }}'
# ── Create docker config ──────────────────────────────────────────
docker config rm nalu-appsettings 2>/dev/null || true
CONFIG_NAME="nalu-appsettings-\$(date +%s)"
docker config create "\${CONFIG_NAME}" /tmp/appsettings.nalu.json
sed "s/nalu-appsettings/\${CONFIG_NAME}/g" /tmp/docker-stack-nalu.yml > /tmp/docker-stack-nalu-final.yml
# ── Deploy stack ──────────────────────────────────────────────────
docker stack deploy -c /tmp/docker-stack-nalu-final.yml nalu --with-registry-auth
rm -f /tmp/appsettings.nalu.json /tmp/docker-stack-nalu.yml /tmp/docker-stack-nalu-final.yml
echo "✅ nalu stack atualizado!"
SSHEOF
- name: Health check
run: |
sleep 35
ssh -o StrictHostKeyChecking=no ubuntu@${{ env.SWARM_MANAGER }} \
'curl -sf http://localhost:8084/health && echo "✅ nalu healthy" || (echo "❌ health check failed"; docker service ps nalu_app; exit 1)'
# ─── Cleanup ──────────────────────────────────────────────────────────────
cleanup:
name: Cleanup Old Resources
runs-on: ubuntu-latest
needs: [deploy]
if: always() && needs.deploy.result == 'success'
steps:
- name: Cleanup
run: |
mkdir -p ~/.ssh
printf '%s' "${{ secrets.SSH_PRIVATE_KEY }}" | tr -d '\r' > ~/.ssh/id_rsa
echo "" >> ~/.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
ssh -o StrictHostKeyChecking=no ubuntu@$SERVER << 'EOF'
docker container prune -f
docker image prune -f
# Keep last 3 nalu configs
docker config ls --filter "name=nalu-appsettings" --format "{{.ID}} {{.Name}}" \
| sort -k2 | head -n -3 | awk '{print $1}' \
| xargs -r docker config rm 2>/dev/null || true
EOF
done
echo "✅ Cleanup done"