Sync all scripts from website downloads — 352 scripts total
Includes updated JS challenge scripts with Claude-User whitelist, same-site referer bypass, Blackbox-Exporter allowed bot, and all new exporters, cheat sheets, and automation scripts.
This commit is contained in:
Executable
+455
@@ -0,0 +1,455 @@
|
||||
#!/bin/bash
|
||||
################################################################################
|
||||
# Script Name: dokku-smoke-tests.sh
|
||||
# Version: 1.0
|
||||
# Description: Smoke test suite for Dokku PaaS — validates connectivity,
|
||||
# app deployment lifecycle, plugin health, SSL certificates,
|
||||
# and resource usage via the dokku CLI
|
||||
#
|
||||
# Author: Phil Connor
|
||||
# Contact: contact@mylinux.work
|
||||
# Website: https://mylinux.work
|
||||
# License: MIT
|
||||
#
|
||||
# Prerequisites:
|
||||
# - bash 4+
|
||||
# - dokku binary (run on the Dokku host)
|
||||
# - Root or dokku user access
|
||||
#
|
||||
# Usage:
|
||||
# sudo ./dokku-smoke-tests.sh
|
||||
# sudo ./dokku-smoke-tests.sh --skip-app --skip-ssl
|
||||
# sudo ./dokku-smoke-tests.sh --format tap
|
||||
# sudo ./dokku-smoke-tests.sh --format junit --junit-file results.xml
|
||||
#
|
||||
################################################################################
|
||||
|
||||
set -euo pipefail
|
||||
|
||||
# --- Defaults ---
|
||||
SKIP_APP="${SKIP_APP_LIFECYCLE:-false}"
|
||||
SKIP_SSL="${SKIP_SSL:-false}"
|
||||
OUTPUT_FORMAT="${OUTPUT_FORMAT:-text}"
|
||||
JUNIT_FILE="${JUNIT_FILE:-smoke-results.xml}"
|
||||
DOKKU_DOMAIN="${DOKKU_DOMAIN:-}"
|
||||
VERBOSE=false
|
||||
USE_COLOR=true
|
||||
TEST_APP_NAME=""
|
||||
PASSED=0
|
||||
FAILED=0
|
||||
SKIPPED=0
|
||||
START_TIME=""
|
||||
JUNIT_RESULTS=()
|
||||
TAP_RESULTS=()
|
||||
TEST_NUM=0
|
||||
|
||||
# --- Colors ---
|
||||
RED='\033[0;31m'
|
||||
GREEN='\033[0;32m'
|
||||
YELLOW='\033[0;33m'
|
||||
CYAN='\033[0;36m'
|
||||
BOLD='\033[1m'
|
||||
NC='\033[0m'
|
||||
|
||||
usage() {
|
||||
cat <<EOF
|
||||
Usage: $(basename "$0") [OPTIONS]
|
||||
|
||||
Smoke test suite for Dokku — run on the Dokku host as root or dokku user.
|
||||
|
||||
Options:
|
||||
--skip-app Skip app lifecycle tests
|
||||
--skip-ssl Skip SSL certificate checks
|
||||
--format FORMAT Output: text (default), tap, junit
|
||||
--junit-file FILE JUnit output path (default: smoke-results.xml)
|
||||
--verbose Show debug output
|
||||
--no-color Disable colored output
|
||||
-h, --help Show this help
|
||||
|
||||
Environment:
|
||||
SKIP_APP_LIFECYCLE Skip app lifecycle (same as --skip-app)
|
||||
SKIP_SSL Skip SSL checks (same as --skip-ssl)
|
||||
OUTPUT_FORMAT Output format (same as --format)
|
||||
JUNIT_FILE JUnit output path
|
||||
DOKKU_DOMAIN Dokku global domain (auto-detected if not set)
|
||||
EOF
|
||||
exit 0
|
||||
}
|
||||
|
||||
# --- Argument parsing ---
|
||||
while [[ $# -gt 0 ]]; do
|
||||
case "$1" in
|
||||
--skip-app) SKIP_APP=true; shift ;;
|
||||
--skip-ssl) SKIP_SSL=true; shift ;;
|
||||
--format) OUTPUT_FORMAT="$2"; shift 2 ;;
|
||||
--junit-file) JUNIT_FILE="$2"; shift 2 ;;
|
||||
--verbose) VERBOSE=true; shift ;;
|
||||
--no-color) USE_COLOR=false; shift ;;
|
||||
-h|--help) usage ;;
|
||||
*) echo "Unknown option: $1"; usage ;;
|
||||
esac
|
||||
done
|
||||
|
||||
if [[ "$USE_COLOR" == "false" ]]; then
|
||||
RED="" GREEN="" YELLOW="" CYAN="" BOLD="" NC=""
|
||||
fi
|
||||
|
||||
# --- Helpers ---
|
||||
debug() {
|
||||
if [[ "$VERBOSE" == "true" ]]; then
|
||||
echo -e " ${CYAN}[debug]${NC} $*" >&2
|
||||
fi
|
||||
}
|
||||
|
||||
pass() {
|
||||
local suite="$1" msg="$2"
|
||||
((TEST_NUM++)) || true
|
||||
((PASSED++)) || true
|
||||
case "$OUTPUT_FORMAT" in
|
||||
tap) TAP_RESULTS+=("ok $TEST_NUM - [$suite] $msg") ;;
|
||||
junit) JUNIT_RESULTS+=("<testcase classname=\"$suite\" name=\"$msg\" />") ;;
|
||||
*) echo -e " ${GREEN}✓${NC} $msg" ;;
|
||||
esac
|
||||
}
|
||||
|
||||
fail() {
|
||||
local suite="$1" msg="$2" detail="${3:-}"
|
||||
((TEST_NUM++)) || true
|
||||
((FAILED++)) || true
|
||||
case "$OUTPUT_FORMAT" in
|
||||
tap) TAP_RESULTS+=("not ok $TEST_NUM - [$suite] $msg") ;;
|
||||
junit) JUNIT_RESULTS+=("<testcase classname=\"$suite\" name=\"$msg\"><failure message=\"$detail\">$detail</failure></testcase>") ;;
|
||||
*) echo -e " ${RED}✗${NC} $msg${detail:+ — $detail}" ;;
|
||||
esac
|
||||
}
|
||||
|
||||
skip() {
|
||||
local suite="$1" msg="$2"
|
||||
((TEST_NUM++)) || true
|
||||
((SKIPPED++)) || true
|
||||
case "$OUTPUT_FORMAT" in
|
||||
tap) TAP_RESULTS+=("ok $TEST_NUM - [$suite] $msg # SKIP") ;;
|
||||
junit) JUNIT_RESULTS+=("<testcase classname=\"$suite\" name=\"$msg\"><skipped /></testcase>") ;;
|
||||
*) echo -e " ${YELLOW}⊘${NC} $msg — skipped" ;;
|
||||
esac
|
||||
}
|
||||
|
||||
suite_header() {
|
||||
if [[ "$OUTPUT_FORMAT" == "text" ]]; then
|
||||
echo -e "\n${BOLD}$1${NC}"
|
||||
fi
|
||||
}
|
||||
|
||||
# --- Cleanup ---
|
||||
cleanup() {
|
||||
if [[ -n "$TEST_APP_NAME" ]]; then
|
||||
debug "Cleaning up test app: $TEST_APP_NAME"
|
||||
dokku apps:destroy "$TEST_APP_NAME" --force >/dev/null 2>&1 || true
|
||||
TEST_APP_NAME=""
|
||||
fi
|
||||
}
|
||||
trap cleanup EXIT INT TERM
|
||||
|
||||
# --- Header ---
|
||||
START_TIME=$(date +%s)
|
||||
HOSTNAME_STR=$(hostname -f 2>/dev/null || hostname)
|
||||
|
||||
if [[ "$OUTPUT_FORMAT" == "text" ]]; then
|
||||
echo -e "${BOLD}Dokku Smoke Tests${NC}"
|
||||
echo "Host: $HOSTNAME_STR"
|
||||
echo "Time: $(date -u +%Y-%m-%dT%H:%M:%SZ)"
|
||||
fi
|
||||
|
||||
# =====================================================
|
||||
# Suite 1: Connectivity
|
||||
# =====================================================
|
||||
suite_header "Connectivity"
|
||||
|
||||
# Check dokku binary
|
||||
DOKKU_BIN=$(command -v dokku 2>/dev/null || true)
|
||||
if [[ -n "$DOKKU_BIN" ]]; then
|
||||
pass "Connectivity" "Dokku binary found — $DOKKU_BIN"
|
||||
else
|
||||
fail "Connectivity" "Dokku binary not found" "dokku is not in PATH"
|
||||
echo -e "\n${RED}Cannot continue without dokku binary. Aborting.${NC}" >&2
|
||||
exit 1
|
||||
fi
|
||||
|
||||
# Check Docker daemon
|
||||
if docker info >/dev/null 2>&1; then
|
||||
pass "Connectivity" "Docker daemon running"
|
||||
else
|
||||
fail "Connectivity" "Docker daemon not running" "docker info failed"
|
||||
fi
|
||||
|
||||
# Check dokku version
|
||||
DOKKU_VERSION=$(dokku version 2>/dev/null | grep -oP 'dokku version \K[0-9]+\.[0-9]+\.[0-9]+' || true)
|
||||
if [[ -z "$DOKKU_VERSION" ]]; then
|
||||
# Try alternate format
|
||||
DOKKU_VERSION=$(dokku version 2>/dev/null | grep -oP '[0-9]+\.[0-9]+\.[0-9]+' || true)
|
||||
fi
|
||||
|
||||
if [[ -n "$DOKKU_VERSION" ]]; then
|
||||
pass "Connectivity" "Dokku version — $DOKKU_VERSION"
|
||||
else
|
||||
fail "Connectivity" "Dokku version" "Could not parse version string"
|
||||
fi
|
||||
|
||||
# Auto-detect global domain if not set
|
||||
if [[ -z "$DOKKU_DOMAIN" ]]; then
|
||||
DOKKU_DOMAIN=$(dokku domains:report --global 2>/dev/null | grep -i "global vhosts" | awk '{print $NF}' || true)
|
||||
if [[ -z "$DOKKU_DOMAIN" ]]; then
|
||||
DOKKU_DOMAIN=$(dokku domains:report --global 2>/dev/null | tail -1 | awk '{print $NF}' || true)
|
||||
fi
|
||||
debug "Auto-detected domain: $DOKKU_DOMAIN"
|
||||
fi
|
||||
|
||||
# =====================================================
|
||||
# Suite 2: App Lifecycle
|
||||
# =====================================================
|
||||
if [[ "$SKIP_APP" == "true" ]]; then
|
||||
suite_header "App Lifecycle"
|
||||
skip "App Lifecycle" "Create test app"
|
||||
skip "App Lifecycle" "Deploy image"
|
||||
skip "App Lifecycle" "App responding"
|
||||
skip "App Lifecycle" "Delete test app"
|
||||
else
|
||||
suite_header "App Lifecycle"
|
||||
|
||||
TEST_APP_NAME="dokku-smoke-$(date +%s)"
|
||||
debug "Test app name: $TEST_APP_NAME"
|
||||
|
||||
# Create app
|
||||
create_output=$(dokku apps:create "$TEST_APP_NAME" 2>&1) || true
|
||||
debug "Create output: $create_output"
|
||||
|
||||
if dokku apps:exists "$TEST_APP_NAME" >/dev/null 2>&1; then
|
||||
pass "App Lifecycle" "Create test app — $TEST_APP_NAME"
|
||||
else
|
||||
fail "App Lifecycle" "Create test app" "$create_output"
|
||||
skip "App Lifecycle" "Deploy image"
|
||||
skip "App Lifecycle" "App responding"
|
||||
skip "App Lifecycle" "Delete test app"
|
||||
TEST_APP_NAME=""
|
||||
SKIP_APP=true
|
||||
fi
|
||||
|
||||
if [[ "$SKIP_APP" != "true" ]]; then
|
||||
# Deploy image via git:from-image
|
||||
deploy_output=$(dokku git:from-image "$TEST_APP_NAME" nginxdemos/hello 2>&1) || true
|
||||
debug "Deploy output: $deploy_output"
|
||||
|
||||
# Check if app is running
|
||||
app_running=false
|
||||
for i in $(seq 1 12); do
|
||||
sleep 5
|
||||
debug "Waiting for app to start... attempt $i/12"
|
||||
ps_output=$(dokku ps:report "$TEST_APP_NAME" 2>/dev/null || true)
|
||||
if echo "$ps_output" | grep -qi "running"; then
|
||||
app_running=true
|
||||
break
|
||||
fi
|
||||
# Also check container status directly
|
||||
running_count=$(dokku ps:report "$TEST_APP_NAME" 2>/dev/null | grep -i "running" | wc -l || echo "0")
|
||||
if [[ "$running_count" -gt 0 ]]; then
|
||||
app_running=true
|
||||
break
|
||||
fi
|
||||
done
|
||||
|
||||
if [[ "$app_running" == "true" ]]; then
|
||||
pass "App Lifecycle" "Deploy image — nginxdemos/hello deployed"
|
||||
else
|
||||
fail "App Lifecycle" "Deploy image" "App not running after 60s"
|
||||
fi
|
||||
|
||||
# Verify HTTP response
|
||||
if [[ -n "$DOKKU_DOMAIN" ]]; then
|
||||
app_url="http://${TEST_APP_NAME}.${DOKKU_DOMAIN}"
|
||||
debug "App URL: $app_url"
|
||||
sleep 3
|
||||
|
||||
app_http=$(curl -s -o /dev/null -w "%{http_code}" \
|
||||
--connect-timeout 10 --max-time 30 \
|
||||
"$app_url" 2>/dev/null || echo "000")
|
||||
|
||||
if [[ "$app_http" == "200" ]]; then
|
||||
pass "App Lifecycle" "App responding — HTTP 200 at ${TEST_APP_NAME}.${DOKKU_DOMAIN}"
|
||||
else
|
||||
fail "App Lifecycle" "App responding" "HTTP $app_http at $app_url"
|
||||
fi
|
||||
else
|
||||
# No domain configured — check container port directly
|
||||
debug "No global domain — checking container directly"
|
||||
port=$(dokku proxy:ports "$TEST_APP_NAME" 2>/dev/null | grep -oP ':\K[0-9]+$' | head -1 || true)
|
||||
if [[ -n "$port" ]]; then
|
||||
app_http=$(curl -s -o /dev/null -w "%{http_code}" \
|
||||
--connect-timeout 10 --max-time 30 \
|
||||
"http://localhost:$port" 2>/dev/null || echo "000")
|
||||
if [[ "$app_http" == "200" ]]; then
|
||||
pass "App Lifecycle" "App responding — HTTP 200 on port $port"
|
||||
else
|
||||
fail "App Lifecycle" "App responding" "HTTP $app_http on port $port"
|
||||
fi
|
||||
else
|
||||
skip "App Lifecycle" "App responding"
|
||||
fi
|
||||
fi
|
||||
|
||||
# Delete test app
|
||||
delete_output=$(dokku apps:destroy "$TEST_APP_NAME" --force 2>&1) || true
|
||||
debug "Delete output: $delete_output"
|
||||
|
||||
if ! dokku apps:exists "$TEST_APP_NAME" >/dev/null 2>&1; then
|
||||
pass "App Lifecycle" "Delete test app — cleaned up"
|
||||
TEST_APP_NAME=""
|
||||
else
|
||||
fail "App Lifecycle" "Delete test app" "Manual cleanup may be required"
|
||||
fi
|
||||
fi
|
||||
fi
|
||||
|
||||
# =====================================================
|
||||
# Suite 3: Plugin Health
|
||||
# =====================================================
|
||||
suite_header "Plugins"
|
||||
|
||||
plugin_list=$(dokku plugin:list 2>/dev/null || true)
|
||||
debug "Plugin list: $plugin_list"
|
||||
|
||||
if [[ -n "$plugin_list" ]]; then
|
||||
plugin_count=$(echo "$plugin_list" | grep -c "enabled" || echo "0")
|
||||
pass "Plugins" "Plugin list — $plugin_count plugins installed"
|
||||
else
|
||||
fail "Plugins" "Plugin list" "dokku plugin:list failed"
|
||||
fi
|
||||
|
||||
# Check core plugins
|
||||
CORE_PLUGINS=("nginx-vhosts" "apps" "config" "ps")
|
||||
for plugin in "${CORE_PLUGINS[@]}"; do
|
||||
if echo "$plugin_list" | grep -q "$plugin"; then
|
||||
pass "Plugins" "Core plugin present — $plugin"
|
||||
else
|
||||
fail "Plugins" "Core plugin present — $plugin" "Not found in plugin list"
|
||||
fi
|
||||
done
|
||||
|
||||
# =====================================================
|
||||
# Suite 4: SSL
|
||||
# =====================================================
|
||||
if [[ "$SKIP_SSL" == "true" ]]; then
|
||||
suite_header "SSL"
|
||||
skip "SSL" "Letsencrypt plugin installed"
|
||||
skip "SSL" "TLS certificate valid"
|
||||
else
|
||||
suite_header "SSL"
|
||||
|
||||
# Check if letsencrypt plugin is installed
|
||||
le_installed=false
|
||||
if echo "$plugin_list" | grep -qi "letsencrypt"; then
|
||||
le_installed=true
|
||||
pass "SSL" "Letsencrypt plugin installed"
|
||||
else
|
||||
skip "SSL" "Letsencrypt plugin installed"
|
||||
fi
|
||||
|
||||
# Check global domain certificate
|
||||
if [[ "$le_installed" == "true" && -n "$DOKKU_DOMAIN" ]]; then
|
||||
# Check certificate via openssl if the domain resolves
|
||||
cert_host="$DOKKU_DOMAIN"
|
||||
cert_output=$(echo | openssl s_client -servername "$cert_host" -connect "${cert_host}:443" 2>/dev/null || true)
|
||||
cert_enddate=$(echo "$cert_output" | openssl x509 -noout -enddate 2>/dev/null | cut -d= -f2 || true)
|
||||
|
||||
if [[ -n "$cert_enddate" ]]; then
|
||||
expiry_epoch=$(date -d "$cert_enddate" +%s 2>/dev/null || echo "0")
|
||||
now_epoch=$(date +%s)
|
||||
days_left=$(( (expiry_epoch - now_epoch) / 86400 ))
|
||||
|
||||
if [[ "$days_left" -gt 0 ]]; then
|
||||
pass "SSL" "TLS certificate valid — $days_left days remaining"
|
||||
else
|
||||
fail "SSL" "TLS certificate expired" "$days_left days past expiry"
|
||||
fi
|
||||
else
|
||||
skip "SSL" "TLS certificate valid"
|
||||
fi
|
||||
else
|
||||
skip "SSL" "TLS certificate valid"
|
||||
fi
|
||||
fi
|
||||
|
||||
# =====================================================
|
||||
# Suite 5: Resources
|
||||
# =====================================================
|
||||
suite_header "Resources"
|
||||
|
||||
# Disk usage
|
||||
disk_line=$(df -h / 2>/dev/null | tail -1 || true)
|
||||
if [[ -n "$disk_line" ]]; then
|
||||
disk_pct=$(echo "$disk_line" | awk '{print $5}' | tr -d '%')
|
||||
disk_used=$(echo "$disk_line" | awk '{print $3}')
|
||||
disk_total=$(echo "$disk_line" | awk '{print $2}')
|
||||
pass "Resources" "Disk usage — ${disk_pct}% (${disk_used} / ${disk_total})"
|
||||
else
|
||||
fail "Resources" "Disk usage" "Could not read disk info"
|
||||
fi
|
||||
|
||||
# Docker images
|
||||
image_count=$(docker images -q 2>/dev/null | wc -l || echo "0")
|
||||
pass "Resources" "Docker images — $image_count images"
|
||||
|
||||
# Docker volumes
|
||||
volume_count=$(docker volume ls -q 2>/dev/null | wc -l || echo "0")
|
||||
pass "Resources" "Docker volumes — $volume_count volumes"
|
||||
|
||||
# Docker containers
|
||||
container_count=$(docker ps -q 2>/dev/null | wc -l || echo "0")
|
||||
pass "Resources" "Docker containers — $container_count running"
|
||||
|
||||
# =====================================================
|
||||
# Summary
|
||||
# =====================================================
|
||||
END_TIME=$(date +%s)
|
||||
DURATION=$((END_TIME - START_TIME))
|
||||
|
||||
case "$OUTPUT_FORMAT" in
|
||||
tap)
|
||||
echo "TAP version 13"
|
||||
echo "1..$TEST_NUM"
|
||||
for line in "${TAP_RESULTS[@]}"; do
|
||||
echo "$line"
|
||||
done
|
||||
echo "# passed: $PASSED"
|
||||
echo "# failed: $FAILED"
|
||||
echo "# skipped: $SKIPPED"
|
||||
echo "# duration: ${DURATION}s"
|
||||
;;
|
||||
junit)
|
||||
{
|
||||
echo '<?xml version="1.0" encoding="UTF-8"?>'
|
||||
echo "<testsuite name=\"Dokku Smoke Tests\" tests=\"$TEST_NUM\" failures=\"$FAILED\" skipped=\"$SKIPPED\" time=\"$DURATION\" timestamp=\"$(date -u +%Y-%m-%dT%H:%M:%SZ)\">"
|
||||
echo " <properties>"
|
||||
echo " <property name=\"host\" value=\"$HOSTNAME_STR\" />"
|
||||
echo " </properties>"
|
||||
for result in "${JUNIT_RESULTS[@]}"; do
|
||||
echo " $result"
|
||||
done
|
||||
echo "</testsuite>"
|
||||
} > "$JUNIT_FILE"
|
||||
echo "JUnit results written to $JUNIT_FILE"
|
||||
;;
|
||||
*)
|
||||
echo ""
|
||||
echo "────────────────────────────────────────"
|
||||
echo -e "Summary ${BOLD}$HOSTNAME_STR${NC}"
|
||||
echo -e " ${GREEN}$PASSED passed${NC} ${RED}$FAILED failed${NC} ${YELLOW}$SKIPPED skipped${NC} (${DURATION}s)"
|
||||
echo "────────────────────────────────────────"
|
||||
if [[ "$FAILED" -eq 0 ]]; then
|
||||
echo -e "${GREEN}All tests passed.${NC}"
|
||||
else
|
||||
echo -e "${RED}Some tests failed.${NC}"
|
||||
fi
|
||||
;;
|
||||
esac
|
||||
|
||||
exit $((FAILED > 0 ? 1 : 0))
|
||||
Reference in New Issue
Block a user