diff --git a/apps/demo-nginx/Jenkinsfile.telegram b/apps/demo-nginx/Jenkinsfile.telegram
new file mode 100644
index 0000000..b0a4d1b
--- /dev/null
+++ b/apps/demo-nginx/Jenkinsfile.telegram
@@ -0,0 +1,727 @@
+pipeline {
+ agent any
+
+ environment {
+ APP_NAME = 'demo-nginx'
+ NAMESPACE = 'demo-app'
+ DOCKER_REGISTRY = 'docker.io'
+ DOCKER_REPO = 'vladcrypto'
+ GITEA_URL = 'http://gitea-http.gitea.svc.cluster.local:3000'
+ GITEA_REPO = 'admin/k3s-gitops'
+ GITEA_BRANCH = 'main'
+ BUILD_TAG = "${env.BUILD_NUMBER}"
+ IMAGE_TAG = "${env.BRANCH_NAME}-${env.BUILD_NUMBER}"
+
+ // Rollback configuration
+ ROLLBACK_ENABLED = 'true'
+ DEPLOYMENT_TIMEOUT = '300s'
+ ARGOCD_SYNC_TIMEOUT = '120'
+ SKIP_HEALTH_CHECK = 'true'
+
+ // Notification configuration
+ TELEGRAM_BOT_TOKEN = credentials('telegram-bot-token')
+ TELEGRAM_CHAT_ID = credentials('telegram-chat-id')
+
+ // Build info
+ BUILD_URL = "${env.BUILD_URL}"
+ GIT_COMMIT_SHORT = ""
+ DEPLOYMENT_START_TIME = ""
+ DEPLOYMENT_END_TIME = ""
+ }
+
+ stages {
+ stage('Initialization') {
+ steps {
+ script {
+ echo "π Starting deployment pipeline..."
+ DEPLOYMENT_START_TIME = sh(script: 'date +%s', returnStdout: true).trim()
+
+ sendTelegramNotification(
+ status: 'STARTED',
+ message: """
+π Deployment Started
+
+Application: ${APP_NAME}
+Build: #${BUILD_NUMBER}
+Branch: ${env.BRANCH_NAME}
+Namespace: ${NAMESPACE}
+Started by: ${env.BUILD_USER ?: 'Jenkins'}
+
+Building and deploying...
+ """,
+ color: 'π΅'
+ )
+ }
+ }
+ }
+
+ stage('Save Current State') {
+ when { branch 'main' }
+ steps {
+ script {
+ echo "πΈ Saving current deployment state for rollback..."
+
+ sh """
+ kubectl get deployment ${APP_NAME} -n ${NAMESPACE} \
+ -o jsonpath='{.spec.template.spec.containers[0].image}' \
+ > /tmp/previous_image_${BUILD_NUMBER}.txt || echo "none" > /tmp/previous_image_${BUILD_NUMBER}.txt
+
+ PREV_IMAGE=\$(cat /tmp/previous_image_${BUILD_NUMBER}.txt)
+ echo "Previous image: \${PREV_IMAGE}"
+ """
+
+ sh """
+ kubectl get deployment ${APP_NAME} -n ${NAMESPACE} \
+ -o jsonpath='{.spec.replicas}' \
+ > /tmp/previous_replicas_${BUILD_NUMBER}.txt || echo "2" > /tmp/previous_replicas_${BUILD_NUMBER}.txt
+ """
+
+ echo "β
Current state saved"
+ }
+ }
+ }
+
+ stage('Checkout Source') {
+ steps {
+ echo "π¦ Creating application artifacts..."
+
+ sh """
+ cat > Dockerfile << 'EOF'
+FROM nginx:1.25.3-alpine
+COPY index.html /usr/share/nginx/html/index.html
+COPY nginx.conf /etc/nginx/nginx.conf
+EXPOSE 80
+CMD ["nginx", "-g", "daemon off;"]
+EOF
+ """
+
+ sh """
+ cat > index.html << EOF
+
+
+
+ Demo Nginx
+
+
+
+
+
π Demo Nginx - Build #${BUILD_NUMBER}
+
Environment: Production
+
Version: ${IMAGE_TAG}
+
+ Image: ${DOCKER_REGISTRY}/${DOCKER_REPO}/${APP_NAME}:${IMAGE_TAG}
+
+
+
+
+EOF
+ """
+
+ sh '''
+ cat > nginx.conf << 'EOF'
+user nginx;
+worker_processes auto;
+error_log /var/log/nginx/error.log warn;
+pid /var/run/nginx.pid;
+events { worker_connections 1024; }
+http {
+ include /etc/nginx/mime.types;
+ default_type application/octet-stream;
+ log_format main '$remote_addr - $remote_user [$time_local] "$request" '
+ '$status $body_bytes_sent "$http_referer" '
+ '"$http_user_agent" "$http_x_forwarded_for"';
+ access_log /var/log/nginx/access.log main;
+ sendfile on;
+ keepalive_timeout 65;
+ server {
+ listen 80;
+ server_name _;
+ location / {
+ root /usr/share/nginx/html;
+ index index.html;
+ }
+ location /health {
+ access_log off;
+ return 200 "healthy\n";
+ add_header Content-Type text/plain;
+ }
+ }
+}
+EOF
+ '''
+ }
+ }
+
+ stage('Build Docker Image') {
+ steps {
+ script {
+ echo "ποΈ Building Docker image..."
+
+ sendTelegramNotification(
+ status: 'BUILDING',
+ message: """
+ποΈ Building Docker Image
+
+Image: ${DOCKER_REGISTRY}/${DOCKER_REPO}/${APP_NAME}:${IMAGE_TAG}
+Stage: Build
+ """,
+ color: 'π΅'
+ )
+
+ sh """
+ docker build \
+ -t ${DOCKER_REGISTRY}/${DOCKER_REPO}/${APP_NAME}:${IMAGE_TAG} \
+ -t ${DOCKER_REGISTRY}/${DOCKER_REPO}/${APP_NAME}:latest \
+ .
+ """
+ echo "β
Image built successfully!"
+ }
+ }
+ }
+
+ stage('Push to Registry') {
+ when { branch 'main' }
+ steps {
+ script {
+ echo "π€ Pushing image to registry..."
+
+ sendTelegramNotification(
+ status: 'PUSHING',
+ message: """
+π€ Pushing to Registry
+
+Registry: ${DOCKER_REGISTRY}
+Image: ${DOCKER_REPO}/${APP_NAME}:${IMAGE_TAG}
+ """,
+ color: 'π΅'
+ )
+
+ withCredentials([usernamePassword(
+ credentialsId: 'docker-registry-credentials',
+ usernameVariable: 'DOCKER_USER',
+ passwordVariable: 'DOCKER_PASS'
+ )]) {
+ sh """
+ echo "\${DOCKER_PASS}" | docker login ${DOCKER_REGISTRY} -u "\${DOCKER_USER}" --password-stdin
+ docker push ${DOCKER_REGISTRY}/${DOCKER_REPO}/${APP_NAME}:${IMAGE_TAG}
+ docker push ${DOCKER_REGISTRY}/${DOCKER_REPO}/${APP_NAME}:latest
+ docker logout ${DOCKER_REGISTRY}
+ """
+ }
+ echo "β
Image pushed successfully!"
+ }
+ }
+ }
+
+ stage('Update GitOps Manifests') {
+ when { branch 'main' }
+ steps {
+ script {
+ echo "π Updating Kubernetes manifests..."
+
+ sendTelegramNotification(
+ status: 'UPDATING',
+ message: """
+π Updating GitOps Manifests
+
+Repository: ${GITEA_REPO}
+Branch: ${GITEA_BRANCH}
+File: apps/demo-nginx/deployment.yaml
+ """,
+ color: 'π΅'
+ )
+
+ withCredentials([usernamePassword(
+ credentialsId: 'gitea-credentials',
+ usernameVariable: 'GIT_USER',
+ passwordVariable: 'GIT_PASS'
+ )]) {
+ sh """
+ rm -rf k3s-gitops || true
+ git clone http://\${GIT_USER}:\${GIT_PASS}@gitea-http.gitea.svc.cluster.local:3000/admin/k3s-gitops.git
+ cd k3s-gitops
+ git config user.name "Jenkins"
+ git config user.email "jenkins@thedevops.dev"
+
+ # Save current commit for rollback
+ git rev-parse HEAD > /tmp/previous_commit_${BUILD_NUMBER}.txt
+
+ sed -i 's|image: .*|image: ${DOCKER_REGISTRY}/${DOCKER_REPO}/${APP_NAME}:${IMAGE_TAG}|' apps/demo-nginx/deployment.yaml
+ git add apps/demo-nginx/deployment.yaml
+ git commit -m "chore(demo-nginx): Update image to ${IMAGE_TAG}" || echo "No changes"
+ git push origin main
+ """
+ }
+ echo "β
Manifests updated!"
+ }
+ }
+ }
+
+ stage('Wait for ArgoCD Sync') {
+ when { branch 'main' }
+ steps {
+ script {
+ echo "β³ Waiting for ArgoCD to sync..."
+
+ sendTelegramNotification(
+ status: 'SYNCING',
+ message: """
+β³ ArgoCD Syncing
+
+Application: ${APP_NAME}
+Namespace: argocd
+Timeout: ${ARGOCD_SYNC_TIMEOUT}s
+ """,
+ color: 'π΅'
+ )
+
+ def syncSuccess = false
+ def attempts = 0
+ def maxAttempts = Integer.parseInt(env.ARGOCD_SYNC_TIMEOUT) / 10
+
+ while (!syncSuccess && attempts < maxAttempts) {
+ attempts++
+ echo "ArgoCD sync check attempt ${attempts}/${maxAttempts}..."
+
+ def syncStatus = sh(
+ script: """
+ kubectl get application ${APP_NAME} -n argocd \
+ -o jsonpath='{.status.sync.status}'
+ """,
+ returnStdout: true
+ ).trim()
+
+ def currentImage = sh(
+ script: """
+ kubectl get deployment ${APP_NAME} -n ${NAMESPACE} \
+ -o jsonpath='{.spec.template.spec.containers[0].image}'
+ """,
+ returnStdout: true
+ ).trim()
+
+ echo "ArgoCD sync status: ${syncStatus}"
+ echo "Current deployment image: ${currentImage}"
+
+ if (syncStatus == 'Synced' && currentImage.contains(env.IMAGE_TAG)) {
+ syncSuccess = true
+ echo "β
ArgoCD synced successfully!"
+ break
+ }
+
+ if (attempts < maxAttempts) {
+ sleep 10
+ }
+ }
+
+ if (!syncSuccess) {
+ error "β ArgoCD sync timeout!"
+ }
+ }
+ }
+ }
+
+ stage('Wait for Deployment') {
+ when { branch 'main' }
+ steps {
+ script {
+ echo "β³ Waiting for deployment to complete..."
+
+ sendTelegramNotification(
+ status: 'DEPLOYING',
+ message: """
+π Deploying to Kubernetes
+
+Deployment: ${APP_NAME}
+Namespace: ${NAMESPACE}
+Timeout: ${DEPLOYMENT_TIMEOUT}
+ """,
+ color: 'π΅'
+ )
+
+ try {
+ sh """
+ kubectl rollout status deployment/${APP_NAME} -n ${NAMESPACE} --timeout=${DEPLOYMENT_TIMEOUT}
+ """
+ echo "β
Deployment rolled out successfully!"
+ } catch (Exception e) {
+ echo "β Deployment failed: ${e.message}"
+ throw e
+ }
+ }
+ }
+ }
+
+ stage('Verify Deployment') {
+ when { branch 'main' }
+ steps {
+ script {
+ echo "β
Verifying deployment..."
+
+ try {
+ def verifyResult = sh(script: """#!/bin/bash
+ set -e
+
+ # Check pod status
+ READY_PODS=\$(kubectl get deployment ${APP_NAME} -n ${NAMESPACE} -o jsonpath='{.status.readyReplicas}')
+ DESIRED_PODS=\$(kubectl get deployment ${APP_NAME} -n ${NAMESPACE} -o jsonpath='{.spec.replicas}')
+
+ echo "Ready pods: \${READY_PODS}/\${DESIRED_PODS}"
+
+ if [ "\${READY_PODS}" != "\${DESIRED_PODS}" ]; then
+ echo "β Not all pods are ready!"
+ exit 1
+ fi
+
+ # Verify image version
+ DEPLOYED_IMAGE=\$(kubectl get deployment ${APP_NAME} -n ${NAMESPACE} -o jsonpath='{.spec.template.spec.containers[0].image}')
+ echo "Deployed image: \${DEPLOYED_IMAGE}"
+
+ if [[ "\${DEPLOYED_IMAGE}" != *"${IMAGE_TAG}"* ]]; then
+ echo "β Image version mismatch!"
+ exit 1
+ fi
+
+ echo "β
All verification checks passed!"
+ """, returnStdout: true).trim()
+
+ echo verifyResult
+ echo "β
Deployment verified successfully!"
+
+ } catch (Exception e) {
+ echo "β Deployment verification failed: ${e.message}"
+ throw e
+ }
+ }
+ }
+ }
+ }
+
+ post {
+ success {
+ script {
+ DEPLOYMENT_END_TIME = sh(script: 'date +%s', returnStdout: true).trim()
+ def duration = (DEPLOYMENT_END_TIME.toInteger() - DEPLOYMENT_START_TIME.toInteger())
+ def durationMin = duration / 60
+ def durationSec = duration % 60
+
+ // Get deployment details
+ def podStatus = sh(
+ script: """
+ kubectl get pods -n ${NAMESPACE} -l app=${APP_NAME} \
+ -o jsonpath='{range .items[*]}{.metadata.name}{" "}{.status.phase}{" "}{.status.containerStatuses[0].restartCount}{"\\n"}{end}'
+ """,
+ returnStdout: true
+ ).trim()
+
+ def deployedImage = sh(
+ script: """
+ kubectl get deployment ${APP_NAME} -n ${NAMESPACE} \
+ -o jsonpath='{.spec.template.spec.containers[0].image}'
+ """,
+ returnStdout: true
+ ).trim()
+
+ def replicas = sh(
+ script: """
+ kubectl get deployment ${APP_NAME} -n ${NAMESPACE} \
+ -o jsonpath='{.status.replicas}'
+ """,
+ returnStdout: true
+ ).trim()
+
+ echo """
+ β
DEPLOYMENT SUCCESS!
+
+ Application: ${APP_NAME}
+ Image: ${deployedImage}
+ Namespace: ${NAMESPACE}
+ Build: #${BUILD_NUMBER}
+ Duration: ${durationMin}m ${durationSec}s
+
+ All checks passed! β¨
+ """
+
+ // Send detailed success notification
+ sendTelegramNotification(
+ status: 'SUCCESS',
+ message: """
+β
Deployment Successful!
+
+ββββββββββββββββββββββ
+π¦ Application Details
+Name: ${APP_NAME}
+Build: #${BUILD_NUMBER}
+Branch: ${env.BRANCH_NAME}
+Namespace: ${NAMESPACE}
+
+ββββββββββββββββββββββ
+π³ Docker Image
+Registry: ${DOCKER_REGISTRY}
+Repository: ${DOCKER_REPO}/${APP_NAME}
+Tag: ${IMAGE_TAG}
+Full Image: ${deployedImage}
+
+ββββββββββββββββββββββ
+βΈοΈ Kubernetes Status
+Replicas: ${replicas}/${replicas} Ready
+Rollout: Completed β
+Health: All pods healthy
+
+ββββββββββββββββββββββ
+β±οΈ Deployment Metrics
+Duration: ${durationMin}m ${durationSec}s
+Started: ${new Date(DEPLOYMENT_START_TIME.toLong() * 1000).format('HH:mm:ss')}
+Completed: ${new Date(DEPLOYMENT_END_TIME.toLong() * 1000).format('HH:mm:ss')}
+
+ββββββββββββββββββββββ
+π Links
+Jenkins Build
+GitOps Repository
+
+Deployed by Jenkins CI/CD Pipeline π
+ """,
+ color: 'β
'
+ )
+
+ // Cleanup rollback files
+ sh """
+ rm -f /tmp/previous_image_${BUILD_NUMBER}.txt
+ rm -f /tmp/previous_replicas_${BUILD_NUMBER}.txt
+ rm -f /tmp/previous_commit_${BUILD_NUMBER}.txt
+ """
+ }
+ }
+
+ failure {
+ script {
+ def errorDetails = ""
+ def previousImage = "unknown"
+
+ try {
+ errorDetails = sh(
+ script: """
+ kubectl get events -n ${NAMESPACE} --sort-by='.lastTimestamp' | tail -10
+ """,
+ returnStdout: true
+ ).trim()
+ } catch (Exception e) {
+ errorDetails = "Could not fetch events: ${e.message}"
+ }
+
+ if (env.BRANCH_NAME == 'main' && env.ROLLBACK_ENABLED == 'true') {
+ echo """
+ β DEPLOYMENT FAILED - INITIATING ROLLBACK!
+
+ Rolling back to previous version...
+ """
+
+ sendTelegramNotification(
+ status: 'ROLLING_BACK',
+ message: """
+π Deployment Failed - Rolling Back
+
+Application: ${APP_NAME}
+Failed Build: #${BUILD_NUMBER}
+Image: ${IMAGE_TAG}
+
+Initiating automatic rollback...
+ """,
+ color: 'β οΈ'
+ )
+
+ try {
+ previousImage = sh(
+ script: "cat /tmp/previous_image_${BUILD_NUMBER}.txt 2>/dev/null || echo 'none'",
+ returnStdout: true
+ ).trim()
+
+ if (previousImage != 'none' && previousImage != '') {
+ echo "π Rolling back to: ${previousImage}"
+
+ sh """
+ kubectl rollout undo deployment/${APP_NAME} -n ${NAMESPACE}
+ kubectl rollout status deployment/${APP_NAME} -n ${NAMESPACE} --timeout=180s
+ """
+
+ withCredentials([usernamePassword(
+ credentialsId: 'gitea-credentials',
+ usernameVariable: 'GIT_USER',
+ passwordVariable: 'GIT_PASS'
+ )]) {
+ sh """
+ cd k3s-gitops || git clone http://\${GIT_USER}:\${GIT_PASS}@gitea-http.gitea.svc.cluster.local:3000/admin/k3s-gitops.git
+ cd k3s-gitops
+ git config user.name "Jenkins"
+ git config user.email "jenkins@thedevops.dev"
+ git revert --no-edit HEAD || true
+ git push origin main || true
+ """
+ }
+
+ sendTelegramNotification(
+ status: 'ROLLBACK_SUCCESS',
+ message: """
+β
Rollback Completed
+
+ββββββββββββββββββββββ
+π¦ Application
+Name: ${APP_NAME}
+Failed Build: #${BUILD_NUMBER}
+Rolled Back To: ${previousImage}
+
+ββββββββββββββββββββββ
+π Rollback Status
+Status: Success β
+Method: kubectl rollout undo
+Git: Commit reverted
+
+ββββββββββββββββββββββ
+π Recent Events
+${errorDetails.take(500)}
+
+ββββββββββββββββββββββ
+β οΈ Action Required
+Please review logs and fix issues before redeploying
+
+View Build Logs
+ """,
+ color: 'β
'
+ )
+
+ } else {
+ sendTelegramNotification(
+ status: 'ROLLBACK_FAILED',
+ message: """
+β Rollback Failed
+
+Application: ${APP_NAME}
+Build: #${BUILD_NUMBER}
+
+Error: No previous version found
+
+β οΈ MANUAL INTERVENTION REQUIRED
+kubectl rollout undo deployment/${APP_NAME} -n ${NAMESPACE}
+
+View Build Logs
+ """,
+ color: 'π΄'
+ )
+ }
+
+ } catch (Exception e) {
+ sendTelegramNotification(
+ status: 'ROLLBACK_FAILED',
+ message: """
+β Rollback Failed
+
+Application: ${APP_NAME}
+Build: #${BUILD_NUMBER}
+
+Error: ${e.message}
+
+β οΈ MANUAL ROLLBACK REQUIRED
+kubectl rollout undo deployment/${APP_NAME} -n ${NAMESPACE}
+
+View Build Logs
+ """,
+ color: 'π΄'
+ )
+ }
+
+ } else {
+ sendTelegramNotification(
+ status: 'FAILED',
+ message: """
+β Deployment Failed
+
+ββββββββββββββββββββββ
+π¦ Application
+Name: ${APP_NAME}
+Build: #${BUILD_NUMBER}
+Branch: ${env.BRANCH_NAME}
+Image: ${IMAGE_TAG}
+
+ββββββββββββββββββββββ
+π Recent Events
+${errorDetails.take(500)}
+
+ββββββββββββββββββββββ
+π Links
+View Console Output
+Build Details
+
+Rollback: ${env.ROLLBACK_ENABLED == 'true' ? 'Enabled but not on main branch' : 'Disabled'}
+ """,
+ color: 'π΄'
+ )
+ }
+ }
+ }
+
+ always {
+ sh """
+ docker rmi ${DOCKER_REGISTRY}/${DOCKER_REPO}/${APP_NAME}:${IMAGE_TAG} || true
+ docker rmi ${DOCKER_REGISTRY}/${DOCKER_REPO}/${APP_NAME}:latest || true
+ docker stop test-${BUILD_NUMBER} 2>/dev/null || true
+ docker rm test-${BUILD_NUMBER} 2>/dev/null || true
+ """
+ cleanWs()
+ }
+ }
+}
+
+// Telegram notification function
+def sendTelegramNotification(Map args) {
+ def status = args.status
+ def message = args.message
+ def color = args.color ?: 'π΅'
+
+ try {
+ // HTML formatting for Telegram
+ def formattedMessage = message.trim()
+
+ sh """
+ curl -s -X POST "https://api.telegram.org/bot${TELEGRAM_BOT_TOKEN}/sendMessage" \
+ -d chat_id="${TELEGRAM_CHAT_ID}" \
+ -d parse_mode="HTML" \
+ -d disable_web_page_preview=true \
+ -d text="${formattedMessage}"
+ """
+
+ echo "π± Telegram notification sent: ${status}"
+ } catch (Exception e) {
+ echo "β οΈ Failed to send Telegram notification: ${e.message}"
+ // Don't fail the pipeline if notification fails
+ }
+}
\ No newline at end of file