Skip to content

Uptime Monitor

Uptime Monitor #152

name: Uptime Monitor
on:
schedule:
- cron: '*/15 * * * *' # Every 15 minutes
workflow_dispatch:
inputs:
check_all_languages:
description: 'Check all 14 language versions'
type: boolean
default: true
required: false
permissions:
contents: read
issues: write
jobs:
uptime-check:
name: Site Availability Check
runs-on: ubuntu-latest
steps:
- name: Harden Runner
uses: step-security/harden-runner@5ef0c079ce82195b2a36a210272d6b661572d83e # v2.14.2
with:
egress-policy: audit
- name: Check homepage availability
id: homepage
run: |
echo "🔍 Checking homepage availability..."
# Single curl request to capture both HTTP code and response time
CURL_OUTPUT=$(curl -s -o /dev/null -w "%{http_code} %{time_total}" -L --connect-timeout 10 --max-time 30 https://riksdagsmonitor.com)
HTTP_CODE=$(echo "$CURL_OUTPUT" | awk '{print $1}')
RESPONSE_TIME=$(echo "$CURL_OUTPUT" | awk '{print $2}')
echo "http_code=$HTTP_CODE" >> $GITHUB_OUTPUT
echo "response_time=$RESPONSE_TIME" >> $GITHUB_OUTPUT
if [ "$HTTP_CODE" = "200" ]; then
echo "✅ Homepage is UP (HTTP $HTTP_CODE, ${RESPONSE_TIME}s)"
else
echo "❌ Homepage returned HTTP $HTTP_CODE"
exit 1
fi
- name: Check all 14 language versions
id: languages
if: github.event.inputs.check_all_languages != 'false'
continue-on-error: true
run: |
echo "🌐 Checking all 14 language versions..."
LANGUAGES=(en sv da no fi de fr es nl ar he ja ko zh)
FAILED_LANGUAGES=""
SUCCESS_COUNT=0
FAIL_COUNT=0
for lang in "${LANGUAGES[@]}"; do
if [ "$lang" = "en" ]; then
URL="https://riksdagsmonitor.com/index.html"
else
URL="https://riksdagsmonitor.com/index_$lang.html"
fi
HTTP_CODE=$(curl -s -o /dev/null -w "%{http_code}" -L --connect-timeout 10 --max-time 30 "$URL")
if [ "$HTTP_CODE" = "200" ]; then
echo "✅ $lang: HTTP $HTTP_CODE"
SUCCESS_COUNT=$((SUCCESS_COUNT + 1))
else
echo "❌ $lang: HTTP $HTTP_CODE"
FAILED_LANGUAGES="$FAILED_LANGUAGES $lang"
FAIL_COUNT=$((FAIL_COUNT + 1))
fi
done
echo "success_count=$SUCCESS_COUNT" >> $GITHUB_OUTPUT
echo "fail_count=$FAIL_COUNT" >> $GITHUB_OUTPUT
echo "failed_languages=$FAILED_LANGUAGES" >> $GITHUB_OUTPUT
if [ $FAIL_COUNT -gt 0 ]; then
echo "⚠️ $FAIL_COUNT language version(s) failed: $FAILED_LANGUAGES"
echo "Treating as degraded service (not critical failure)"
else
echo "✅ All 14 language versions are UP"
fi
- name: Check critical assets
id: assets
run: |
echo "🎨 Checking critical assets..."
ASSETS=(
"https://riksdagsmonitor.com/styles.css"
"https://riksdagsmonitor.com/manifest.json"
"https://riksdagsmonitor.com/sitemap.xml"
"https://riksdagsmonitor.com/robots.txt"
)
FAILED_ASSETS=""
SUCCESS_COUNT=0
FAIL_COUNT=0
for asset in "${ASSETS[@]}"; do
HTTP_CODE=$(curl -s -o /dev/null -w "%{http_code}" -L --connect-timeout 10 --max-time 30 "$asset")
if [ "$HTTP_CODE" = "200" ]; then
echo "✅ $(basename $asset): HTTP $HTTP_CODE"
SUCCESS_COUNT=$((SUCCESS_COUNT + 1))
else
echo "❌ $(basename $asset): HTTP $HTTP_CODE"
FAILED_ASSETS="$FAILED_ASSETS $(basename $asset)"
FAIL_COUNT=$((FAIL_COUNT + 1))
fi
done
echo "success_count=$SUCCESS_COUNT" >> $GITHUB_OUTPUT
echo "fail_count=$FAIL_COUNT" >> $GITHUB_OUTPUT
echo "failed_assets=$FAILED_ASSETS" >> $GITHUB_OUTPUT
if [ $FAIL_COUNT -gt 0 ]; then
echo "⚠️ $FAIL_COUNT asset(s) failed: $FAILED_ASSETS"
# Don't fail workflow for asset issues
else
echo "✅ All critical assets are accessible"
fi
- name: Check HTTPS and security headers
id: security
run: |
echo "🔒 Checking HTTPS and security headers..."
# Check HTTPS redirect
HTTP_REDIRECT=$(curl -s -o /dev/null -w "%{redirect_url}" --connect-timeout 10 --max-time 30 http://riksdagsmonitor.com)
if [[ "$HTTP_REDIRECT" == https://* ]]; then
echo "✅ HTTPS redirect: Working"
else
echo "⚠️ HTTPS redirect: Not detected"
fi
# Check security headers
HEADERS=$(curl -sI --connect-timeout 10 --max-time 30 https://riksdagsmonitor.com)
# Check for key security headers
if echo "$HEADERS" | grep -qi "strict-transport-security"; then
echo "✅ HSTS header: Present"
else
echo "⚠️ HSTS header: Missing"
fi
if echo "$HEADERS" | grep -qi "x-frame-options"; then
echo "✅ X-Frame-Options: Present"
else
echo "⚠️ X-Frame-Options: Missing"
fi
if echo "$HEADERS" | grep -qi "x-content-type-options"; then
echo "✅ X-Content-Type-Options: Present"
else
echo "⚠️ X-Content-Type-Options: Missing"
fi
if echo "$HEADERS" | grep -qi "content-security-policy"; then
echo "✅ CSP header: Present"
else
echo "⚠️ CSP header: Missing"
fi
- name: Generate uptime summary
if: always()
run: |
echo "## 🚦 Uptime Monitor Report" >> $GITHUB_STEP_SUMMARY
echo "" >> $GITHUB_STEP_SUMMARY
# Homepage status
echo "### Homepage Status" >> $GITHUB_STEP_SUMMARY
echo "" >> $GITHUB_STEP_SUMMARY
echo "- **HTTP Status**: ${{ steps.homepage.outputs.http_code }}" >> $GITHUB_STEP_SUMMARY
echo "- **Response Time**: ${{ steps.homepage.outputs.response_time }}s" >> $GITHUB_STEP_SUMMARY
echo "" >> $GITHUB_STEP_SUMMARY
# Language versions status
if [ -n "${{ steps.languages.outputs.success_count }}" ]; then
echo "### Language Versions" >> $GITHUB_STEP_SUMMARY
echo "" >> $GITHUB_STEP_SUMMARY
echo "- **Total**: 14 languages" >> $GITHUB_STEP_SUMMARY
echo "- **Success**: ✅ ${{ steps.languages.outputs.success_count }}" >> $GITHUB_STEP_SUMMARY
echo "- **Failed**: ❌ ${{ steps.languages.outputs.fail_count }}" >> $GITHUB_STEP_SUMMARY
if [ "${{ steps.languages.outputs.fail_count }}" != "0" ]; then
echo "- **Failed Languages**: ${{ steps.languages.outputs.failed_languages }}" >> $GITHUB_STEP_SUMMARY
fi
echo "" >> $GITHUB_STEP_SUMMARY
fi
# Assets status
if [ -n "${{ steps.assets.outputs.success_count }}" ]; then
echo "### Critical Assets" >> $GITHUB_STEP_SUMMARY
echo "" >> $GITHUB_STEP_SUMMARY
echo "- **Success**: ✅ ${{ steps.assets.outputs.success_count }}" >> $GITHUB_STEP_SUMMARY
echo "- **Failed**: ❌ ${{ steps.assets.outputs.fail_count }}" >> $GITHUB_STEP_SUMMARY
if [ "${{ steps.assets.outputs.fail_count }}" != "0" ]; then
echo "- **Failed Assets**: ${{ steps.assets.outputs.failed_assets }}" >> $GITHUB_STEP_SUMMARY
fi
echo "" >> $GITHUB_STEP_SUMMARY
fi
# Overall status
echo "### 📊 Overall Status" >> $GITHUB_STEP_SUMMARY
echo "" >> $GITHUB_STEP_SUMMARY
# Check homepage, languages, and assets for overall status
LANGUAGES_OK=false
if [ "${{ steps.languages.outcome }}" = "success" ] || [ "${{ steps.languages.outcome }}" = "skipped" ]; then
LANGUAGES_OK=true
fi
ASSETS_OK=true
if [ -n "${{ steps.assets.outputs.fail_count }}" ] && [ "${{ steps.assets.outputs.fail_count }}" != "0" ]; then
ASSETS_OK=false
fi
if [ "${{ steps.homepage.outcome }}" = "success" ] && [ "$LANGUAGES_OK" = "true" ] && [ "$ASSETS_OK" = "true" ]; then
echo "🟢 **ALL SYSTEMS OPERATIONAL**" >> $GITHUB_STEP_SUMMARY
elif [ "${{ steps.homepage.outcome }}" = "success" ]; then
echo "🟡 **DEGRADED SERVICE** - Homepage up, but some issues detected" >> $GITHUB_STEP_SUMMARY
else
echo "🔴 **SERVICE DOWN** - Homepage unreachable" >> $GITHUB_STEP_SUMMARY
fi
echo "" >> $GITHUB_STEP_SUMMARY
echo "---" >> $GITHUB_STEP_SUMMARY
echo "*Check timestamp: $(date -u +"%Y-%m-%d %H:%M:%S UTC")*" >> $GITHUB_STEP_SUMMARY
- name: Create incident issue
if: failure() && steps.homepage.outcome == 'failure'
uses: actions/github-script@ed597411d8f924073f98dfc5c65a23a2325f34cd # v8.0.0
with:
script: |
const httpCode = '${{ steps.homepage.outputs.http_code }}';
const timestamp = new Date().toISOString();
// Check if there's already an open incident
const issues = await github.rest.issues.listForRepo({
owner: context.repo.owner,
repo: context.repo.name,
state: 'open',
labels: 'incident,uptime-monitor'
});
if (issues.data.length === 0) {
// Create new incident issue
await github.rest.issues.create({
owner: context.repo.owner,
repo: context.repo.name,
title: '🚨 Site Down - HTTP ' + httpCode + ' - ' + timestamp,
body: '## Incident Report\n\n**Status**: 🔴 SITE DOWN\n**Timestamp**: ' + timestamp + '\n**HTTP Code**: ' + httpCode + '\n**Detected by**: Uptime Monitor Workflow\n\n### Details\nHomepage is returning HTTP ' + httpCode + ' instead of expected 200.\n\n### Action Required\n1. Check deployment status\n2. Review GitHub Pages configuration\n3. Verify DNS settings\n4. Check CDN status\n\n### Logs\n[Workflow Run](https://github.com/' + context.repo.owner + '/' + context.repo.name + '/actions/runs/' + context.runId + ')\n\n---\nThis issue was automatically created by the uptime monitor workflow',
labels: ['incident', 'uptime-monitor', 'critical']
});
console.log('Incident issue created');
} else {
// Update existing incident
const issueNumber = issues.data[0].number;
await github.rest.issues.createComment({
owner: context.repo.owner,
repo: context.repo.name,
issue_number: issueNumber,
body: 'Update: ' + timestamp + '\n\nSite still down - HTTP ' + httpCode + '\n\n[Latest workflow run](https://github.com/' + context.repo.owner + '/' + context.repo.name + '/actions/runs/' + context.runId + ')'
});
console.log('Incident issue updated');
}
- name: Close resolved incidents
if: success() && steps.homepage.outcome == 'success'
uses: actions/github-script@ed597411d8f924073f98dfc5c65a23a2325f34cd # v8.0.0
with:
script: |
// Find open incident issues
const issues = await github.rest.issues.listForRepo({
owner: context.repo.owner,
repo: context.repo.name,
state: 'open',
labels: 'incident,uptime-monitor'
});
for (const issue of issues.data) {
// Close and comment
await github.rest.issues.createComment({
owner: context.repo.owner,
repo: context.repo.name,
issue_number: issue.number,
body: '### ✅ Incident Resolved\n\nSite is now operational.\n\n**Resolved at**: ' + new Date().toISOString() + '\n**Duration**: From ' + issue.created_at + ' to now\n\n[Verification workflow run](https://github.com/' + context.repo.owner + '/' + context.repo.name + '/actions/runs/' + context.runId + ')'
});
await github.rest.issues.update({
owner: context.repo.owner,
repo: context.repo.name,
issue_number: issue.number,
state: 'closed',
labels: ['incident', 'uptime-monitor', 'resolved']
});
console.log(`Closed incident issue #${issue.number}`);
}