Deploy to Production / Build & Verify (push) Failing after 5m56s
Ping Search Engines / Notify Search Engines (push) Successful in 2s
Deploy to Production / Pre-Deploy Tests (push) Has been skipped
Deploy to Production / Deploy to Railway (push) Has been skipped
Deploy to Production / Deploy to Render (push) Has been skipped
Deploy to Production / Deploy to VPS (PM2) (push) Has been skipped
Deploy to Production / Deploy to Fly.io (push) Has been skipped
Deploy to Production / Post-Deploy Verification (push) Has been skipped
Deploy to Production / Notify on Failure (push) Successful in 2s
E2E Test Suite / Critical User Journeys (push) Has been skipped
E2E Test Suite / API Integration Tests (push) Has been skipped
E2E Test Suite / Smoke Tests (P0) (push) Failing after 11m26s
E2E Test Suite / Form Interaction Tests (push) Failing after 11m42s
E2E Test Suite / Destructive & Chaos Tests (push) Failing after 12m2s
E2E Test Suite / Cross-Browser Regression (chromium) (push) Failing after 16m14s
E2E Test Suite / Cross-Browser Regression (webkit) (push) Failing after 17m45s
E2E Test Suite / Cross-Browser Regression (firefox) (push) Failing after 25m23s
E2E Test Suite / Security Header Tests (push) Failing after 7m55s
E2E Test Suite / Test Report Summary (push) Failing after 20s
E2E Test Suite / Mobile Device Tests (push) Failing after 2h49m9s
Uptime Monitor / Health & Response Time (push) Failing after 2s
Uptime Monitor / SSL Certificate (push) Successful in 2s
Uptime Monitor / Send Alerts (push) Failing after 3s
Uptime Monitor / Record Uptime Success (push) Has been skipped
295 lines
11 KiB
YAML
295 lines
11 KiB
YAML
name: Uptime Monitor
|
|
|
|
on:
|
|
schedule:
|
|
# Run every 5 minutes
|
|
- cron: '*/5 * * * *'
|
|
workflow_dispatch:
|
|
inputs:
|
|
verbose:
|
|
description: 'Verbose output'
|
|
required: false
|
|
default: false
|
|
type: boolean
|
|
|
|
# Prevent concurrent monitoring runs
|
|
concurrency:
|
|
group: uptime-monitor
|
|
cancel-in-progress: true
|
|
|
|
env:
|
|
BASE_URL: https://workroot.in
|
|
RESPONSE_TIME_THRESHOLD_MS: 3000
|
|
# SSL check: days before expiry to alert
|
|
SSL_ALERT_DAYS: 14
|
|
|
|
jobs:
|
|
# ============================================================
|
|
# Job 1: Health & Response Time Check
|
|
# ============================================================
|
|
health-check:
|
|
name: Health & Response Time
|
|
runs-on: ubuntu-latest
|
|
timeout-minutes: 5
|
|
|
|
outputs:
|
|
health-status: ${{ steps.health.outputs.status }}
|
|
response-time: ${{ steps.health.outputs.response_time }}
|
|
is-slow: ${{ steps.health.outputs.is_slow }}
|
|
|
|
steps:
|
|
- name: Check health endpoint
|
|
id: health
|
|
run: |
|
|
START=$(date +%s%3N)
|
|
HTTP_RESPONSE=$(curl \
|
|
--silent \
|
|
--max-time 10 \
|
|
--write-out "\n%{http_code}" \
|
|
"${{ env.BASE_URL }}/api/health.json" \
|
|
)
|
|
END=$(date +%s%3N)
|
|
RESPONSE_TIME=$((END - START))
|
|
|
|
HTTP_BODY=$(echo "$HTTP_RESPONSE" | head -n -1)
|
|
HTTP_CODE=$(echo "$HTTP_RESPONSE" | tail -n 1)
|
|
|
|
echo "HTTP status: $HTTP_CODE"
|
|
echo "Response time: ${RESPONSE_TIME}ms"
|
|
echo "Body: $HTTP_BODY"
|
|
|
|
# Outputs
|
|
echo "response_time=${RESPONSE_TIME}" >> $GITHUB_OUTPUT
|
|
|
|
if [ "$HTTP_CODE" = "200" ]; then
|
|
STATUS=$(echo "$HTTP_BODY" | python3 -c "import sys,json; d=json.load(sys.stdin); print(d.get('status','unknown'))" 2>/dev/null || echo "parse-error")
|
|
echo "status=${STATUS}" >> $GITHUB_OUTPUT
|
|
else
|
|
echo "status=down-${HTTP_CODE}" >> $GITHUB_OUTPUT
|
|
fi
|
|
|
|
# Slow response check
|
|
if [ "$RESPONSE_TIME" -gt "${{ env.RESPONSE_TIME_THRESHOLD_MS }}" ]; then
|
|
echo "is_slow=true" >> $GITHUB_OUTPUT
|
|
echo "WARNING: Response time ${RESPONSE_TIME}ms exceeds threshold ${{ env.RESPONSE_TIME_THRESHOLD_MS }}ms"
|
|
else
|
|
echo "is_slow=false" >> $GITHUB_OUTPUT
|
|
fi
|
|
|
|
- name: Check critical pages
|
|
id: pages
|
|
run: |
|
|
FAILED_PAGES=""
|
|
PAGES="/ /services /portfolio /contact /about"
|
|
|
|
for PAGE in $PAGES; do
|
|
START=$(date +%s%3N)
|
|
HTTP_CODE=$(curl --silent --max-time 10 -o /dev/null -w "%{http_code}" "${{ env.BASE_URL }}${PAGE}")
|
|
END=$(date +%s%3N)
|
|
RT=$((END - START))
|
|
|
|
if [ "$HTTP_CODE" != "200" ]; then
|
|
FAILED_PAGES="${FAILED_PAGES} ${PAGE}(${HTTP_CODE})"
|
|
echo "FAIL: ${PAGE} → HTTP $HTTP_CODE"
|
|
else
|
|
echo "OK: ${PAGE} → HTTP $HTTP_CODE (${RT}ms)"
|
|
fi
|
|
done
|
|
|
|
if [ -n "$FAILED_PAGES" ]; then
|
|
echo "Failed pages:${FAILED_PAGES}"
|
|
exit 1
|
|
fi
|
|
|
|
- name: Check sitemap and robots.txt
|
|
run: |
|
|
for RESOURCE in "/sitemap.xml" "/robots.txt"; do
|
|
HTTP_CODE=$(curl --silent --max-time 10 -o /dev/null -w "%{http_code}" "${{ env.BASE_URL }}${RESOURCE}")
|
|
if [ "$HTTP_CODE" != "200" ]; then
|
|
echo "FAIL: ${RESOURCE} returned HTTP $HTTP_CODE"
|
|
exit 1
|
|
fi
|
|
echo "OK: ${RESOURCE} → HTTP $HTTP_CODE"
|
|
done
|
|
|
|
# ============================================================
|
|
# Job 2: SSL Certificate Check
|
|
# ============================================================
|
|
ssl-check:
|
|
name: SSL Certificate
|
|
runs-on: ubuntu-latest
|
|
timeout-minutes: 5
|
|
|
|
outputs:
|
|
days-until-expiry: ${{ steps.ssl.outputs.days_until_expiry }}
|
|
ssl-valid: ${{ steps.ssl.outputs.ssl_valid }}
|
|
|
|
steps:
|
|
- name: Check SSL certificate
|
|
id: ssl
|
|
run: |
|
|
DOMAIN="workroot.in"
|
|
|
|
# Get certificate expiry date
|
|
EXPIRY=$(echo | openssl s_client -servername "$DOMAIN" -connect "${DOMAIN}:443" 2>/dev/null \
|
|
| openssl x509 -noout -enddate 2>/dev/null \
|
|
| cut -d= -f2)
|
|
|
|
if [ -z "$EXPIRY" ]; then
|
|
echo "ssl_valid=false" >> $GITHUB_OUTPUT
|
|
echo "days_until_expiry=0" >> $GITHUB_OUTPUT
|
|
echo "ERROR: Could not retrieve SSL certificate"
|
|
exit 1
|
|
fi
|
|
|
|
EXPIRY_EPOCH=$(date -d "$EXPIRY" +%s 2>/dev/null || date -j -f "%b %d %H:%M:%S %Y %Z" "$EXPIRY" +%s 2>/dev/null)
|
|
NOW_EPOCH=$(date +%s)
|
|
DAYS_LEFT=$(( (EXPIRY_EPOCH - NOW_EPOCH) / 86400 ))
|
|
|
|
echo "ssl_valid=true" >> $GITHUB_OUTPUT
|
|
echo "days_until_expiry=${DAYS_LEFT}" >> $GITHUB_OUTPUT
|
|
|
|
echo "SSL certificate expires: $EXPIRY"
|
|
echo "Days until expiry: $DAYS_LEFT"
|
|
|
|
if [ "$DAYS_LEFT" -le "${{ env.SSL_ALERT_DAYS }}" ]; then
|
|
echo "WARNING: SSL certificate expires in $DAYS_LEFT days!"
|
|
exit 1
|
|
fi
|
|
|
|
echo "SSL OK: $DAYS_LEFT days remaining"
|
|
|
|
# ============================================================
|
|
# Job 3: Alert on Downtime or Issues
|
|
# ============================================================
|
|
alert:
|
|
name: Send Alerts
|
|
runs-on: ubuntu-latest
|
|
timeout-minutes: 5
|
|
needs: [health-check, ssl-check]
|
|
if: |
|
|
always() &&
|
|
(
|
|
needs.health-check.result == 'failure' ||
|
|
needs.ssl-check.result == 'failure' ||
|
|
needs.health-check.outputs.health-status != 'ok' ||
|
|
needs.health-check.outputs.is-slow == 'true'
|
|
)
|
|
|
|
steps:
|
|
- name: Build alert summary
|
|
id: alert-content
|
|
run: |
|
|
HEALTH_STATUS="${{ needs.health-check.outputs.health-status }}"
|
|
RESPONSE_TIME="${{ needs.health-check.outputs.response-time }}"
|
|
IS_SLOW="${{ needs.health-check.outputs.is-slow }}"
|
|
SSL_DAYS="${{ needs.ssl-check.outputs.days-until-expiry }}"
|
|
SSL_VALID="${{ needs.ssl-check.outputs.ssl-valid }}"
|
|
|
|
# Determine alert type
|
|
if [ "${{ needs.health-check.result }}" = "failure" ]; then
|
|
ALERT_TYPE="DOWNTIME"
|
|
SEVERITY="critical"
|
|
elif [ "$IS_SLOW" = "true" ]; then
|
|
ALERT_TYPE="SLOW_RESPONSE"
|
|
SEVERITY="warning"
|
|
elif [ "${{ needs.ssl-check.result }}" = "failure" ]; then
|
|
ALERT_TYPE="SSL_EXPIRY"
|
|
SEVERITY="warning"
|
|
else
|
|
ALERT_TYPE="DEGRADED"
|
|
SEVERITY="warning"
|
|
fi
|
|
|
|
echo "alert_type=${ALERT_TYPE}" >> $GITHUB_OUTPUT
|
|
echo "severity=${SEVERITY}" >> $GITHUB_OUTPUT
|
|
|
|
# GitHub Step Summary
|
|
echo "## Alert: ${ALERT_TYPE} [${SEVERITY^^}]" >> $GITHUB_STEP_SUMMARY
|
|
echo "" >> $GITHUB_STEP_SUMMARY
|
|
echo "| Check | Status |" >> $GITHUB_STEP_SUMMARY
|
|
echo "|-------|--------|" >> $GITHUB_STEP_SUMMARY
|
|
echo "| Health Status | ${HEALTH_STATUS} |" >> $GITHUB_STEP_SUMMARY
|
|
echo "| Response Time | ${RESPONSE_TIME}ms |" >> $GITHUB_STEP_SUMMARY
|
|
echo "| Response Slow | ${IS_SLOW} |" >> $GITHUB_STEP_SUMMARY
|
|
echo "| SSL Valid | ${SSL_VALID} |" >> $GITHUB_STEP_SUMMARY
|
|
echo "| SSL Days Left | ${SSL_DAYS} |" >> $GITHUB_STEP_SUMMARY
|
|
echo "" >> $GITHUB_STEP_SUMMARY
|
|
echo "**Time:** $(date -u '+%Y-%m-%d %H:%M:%S UTC')" >> $GITHUB_STEP_SUMMARY
|
|
echo "**Site:** ${{ env.BASE_URL }}" >> $GITHUB_STEP_SUMMARY
|
|
echo "" >> $GITHUB_STEP_SUMMARY
|
|
echo "[View Health Endpoint](${{ env.BASE_URL }}/api/health.json)" >> $GITHUB_STEP_SUMMARY
|
|
|
|
# ── Slack notification (enable by adding SLACK_WEBHOOK_URL secret) ──
|
|
# - name: Notify Slack
|
|
# if: secrets.SLACK_WEBHOOK_URL != ''
|
|
# run: |
|
|
# ALERT_TYPE="${{ steps.alert-content.outputs.alert_type }}"
|
|
# SEVERITY="${{ steps.alert-content.outputs.severity }}"
|
|
# COLOR=$([ "$SEVERITY" = "critical" ] && echo "danger" || echo "warning")
|
|
# EMOJI=$([ "$SEVERITY" = "critical" ] && echo ":red_circle:" || echo ":warning:")
|
|
#
|
|
# curl -s -X POST "${{ secrets.SLACK_WEBHOOK_URL }}" \
|
|
# -H 'Content-type: application/json' \
|
|
# --data "{
|
|
# \"text\": \"${EMOJI} WorkRoot Alert: ${ALERT_TYPE}\",
|
|
# \"attachments\": [{
|
|
# \"color\": \"${COLOR}\",
|
|
# \"fields\": [
|
|
# {\"title\": \"Alert\", \"value\": \"${ALERT_TYPE}\", \"short\": true},
|
|
# {\"title\": \"Site\", \"value\": \"${{ env.BASE_URL }}\", \"short\": true},
|
|
# {\"title\": \"Response Time\", \"value\": \"${{ needs.health-check.outputs.response-time }}ms\", \"short\": true},
|
|
# {\"title\": \"SSL Days Left\", \"value\": \"${{ needs.ssl-check.outputs.days-until-expiry }}\", \"short\": true}
|
|
# ],
|
|
# \"footer\": \"WorkRoot Monitor | $(date -u '+%Y-%m-%d %H:%M UTC')\"
|
|
# }]
|
|
# }"
|
|
|
|
# ── Email/PagerDuty notification (via webhook) ──
|
|
# - name: Notify via webhook
|
|
# if: secrets.ALERT_WEBHOOK_URL != ''
|
|
# run: |
|
|
# curl -s -X POST "${{ secrets.ALERT_WEBHOOK_URL }}" \
|
|
# -H 'Content-type: application/json' \
|
|
# --data '{
|
|
# "event": "${{ steps.alert-content.outputs.alert_type }}",
|
|
# "severity": "${{ steps.alert-content.outputs.severity }}",
|
|
# "site": "${{ env.BASE_URL }}",
|
|
# "health_status": "${{ needs.health-check.outputs.health-status }}",
|
|
# "response_time_ms": "${{ needs.health-check.outputs.response-time }}",
|
|
# "ssl_days_left": "${{ needs.ssl-check.outputs.days-until-expiry }}",
|
|
# "timestamp": "'"$(date -u +%Y-%m-%dT%H:%M:%SZ)"'"
|
|
# }'
|
|
|
|
- name: Fail workflow to make alert visible
|
|
run: |
|
|
echo "Alert triggered: ${{ steps.alert-content.outputs.alert_type }}"
|
|
echo "Severity: ${{ steps.alert-content.outputs.severity }}"
|
|
exit 1
|
|
|
|
# ============================================================
|
|
# Job 4: Record Success (for uptime tracking)
|
|
# ============================================================
|
|
record-success:
|
|
name: Record Uptime Success
|
|
runs-on: ubuntu-latest
|
|
timeout-minutes: 2
|
|
needs: [health-check, ssl-check]
|
|
if: |
|
|
always() &&
|
|
needs.health-check.result == 'success' &&
|
|
needs.ssl-check.result == 'success'
|
|
|
|
steps:
|
|
- name: Log success
|
|
run: |
|
|
echo "## Uptime Check Passed" >> $GITHUB_STEP_SUMMARY
|
|
echo "" >> $GITHUB_STEP_SUMMARY
|
|
echo "| Check | Result |" >> $GITHUB_STEP_SUMMARY
|
|
echo "|-------|--------|" >> $GITHUB_STEP_SUMMARY
|
|
echo "| Health | ${{ needs.health-check.outputs.health-status }} |" >> $GITHUB_STEP_SUMMARY
|
|
echo "| Response Time | ${{ needs.health-check.outputs.response-time }}ms |" >> $GITHUB_STEP_SUMMARY
|
|
echo "| SSL Days Left | ${{ needs.ssl-check.outputs.days-until-expiry }} |" >> $GITHUB_STEP_SUMMARY
|
|
echo "" >> $GITHUB_STEP_SUMMARY
|
|
echo "**Checked at:** $(date -u '+%Y-%m-%d %H:%M:%S UTC')" >> $GITHUB_STEP_SUMMARY
|