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
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>
278 lines
11 KiB
YAML
278 lines
11 KiB
YAML
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"
|