secubox-openwrt/package/secubox/secubox-app-reporter/files/usr/sbin/secubox-reportctl
CyberMind-FR 5367f01fb7 feat(secubox-app-reporter): Add Meta Report with visual statistics
New meta-status report combining dev + services with enhanced visuals:
- Stats rings with conic gradients (health, services, uptime)
- Channel distribution bars (Tor/DNS/Mesh percentages)
- Stat cards with icons and gradients
- Recent completions and WIP sections
- Roadmap progress visualization
- Top services tables

Email configuration:
- Default to local mailserver (127.0.0.1:25)
- Default recipient: gk2@secubox.in
- No TLS for local delivery

CLI: secubox-reportctl generate meta

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2026-03-13 10:14:20 +01:00

540 lines
17 KiB
Bash

#!/bin/sh
# SecuBox Report Generator
# Generates and distributes status reports via HTML and email
# Copyright (C) 2026 CyberMind.fr
VERSION="1.0.0"
SCRIPT_DIR="/usr/share/secubox-reporter"
LIB_DIR="$SCRIPT_DIR/lib"
TPL_DIR="$SCRIPT_DIR/templates"
OUTPUT_DIR="/www/reports"
# Colors
RED='\033[0;31m'
GREEN='\033[0;32m'
YELLOW='\033[1;33m'
CYAN='\033[0;36m'
NC='\033[0m'
# Load libraries
. /lib/functions.sh
[ -f "$LIB_DIR/collectors.sh" ] && . "$LIB_DIR/collectors.sh"
[ -f "$LIB_DIR/formatters.sh" ] && . "$LIB_DIR/formatters.sh"
[ -f "$LIB_DIR/mailer.sh" ] && . "$LIB_DIR/mailer.sh"
# Load config
config_load secubox-reporter
config_get OUTPUT_DIR global output_dir "/www/reports"
config_get THEME global theme "dark"
config_get REPO_PATH sources repo_path "/root/secubox-openwrt"
log_info() { echo -e "${CYAN}[INFO]${NC} $1"; }
log_ok() { echo -e "${GREEN}[OK]${NC} $1"; }
log_err() { echo -e "${RED}[ERROR]${NC} $1" >&2; }
log_warn() { echo -e "${YELLOW}[WARN]${NC} $1"; }
usage() {
cat << EOF
SecuBox Report Generator v$VERSION
Usage: secubox-reportctl <command> [options]
COMMANDS:
generate <type> Generate report (dev|services|all)
send <type> Generate and email report
schedule <type> Set cron schedule (daily|weekly|off)
status Show generator status
preview <type> Generate and output to stdout
list List generated reports
clean [days] Remove reports older than N days (default: 30)
help Show this help
REPORT TYPES:
dev Development Status Report (progress, roadmap, health)
services Distribution/Services Status Report (exposures, channels)
all Both reports
OPTIONS:
--email Also send via email
--theme <name> Theme: dark, light, cyberpunk (default: dark)
--output <path> Custom output path
--json Output status as JSON
EXAMPLES:
secubox-reportctl generate dev
secubox-reportctl send services
secubox-reportctl schedule dev daily
secubox-reportctl generate all --theme cyberpunk
EOF
}
# Get hostname
get_hostname() {
uci -q get system.@system[0].hostname 2>/dev/null || hostname
}
# Get SecuBox version
get_version() {
cat /etc/secubox_version 2>/dev/null || echo "0.19.x"
}
# Generate development status report
generate_dev_report() {
local output_file="$1"
local theme="${2:-dark}"
log_info "Generating Development Status Report..."
mkdir -p "$(dirname "$output_file")"
local hostname=$(get_hostname)
local version=$(get_version)
local timestamp=$(date '+%Y-%m-%d %H:%M:%S')
local health_score=0
# Get health score from RPCD if available
if command -v ubus >/dev/null 2>&1; then
health_score=$(ubus call luci.secubox-core get_full_health_report 2>/dev/null | \
jsonfilter -e '@.health_score' 2>/dev/null || echo "0")
fi
[ -z "$health_score" ] && health_score=0
# Collect data from .claude files
local history_entries=""
local wip_entries=""
local roadmap_data=""
if [ -d "$REPO_PATH/.claude" ]; then
# Recent history (last 10 dated sections)
history_entries=$(collect_history "$REPO_PATH/.claude/HISTORY.md")
# WIP items
wip_entries=$(collect_wip "$REPO_PATH/.claude/WIP.md")
# Roadmap progress
roadmap_data=$(collect_roadmap "$REPO_PATH/.claude/ROADMAP.md")
else
history_entries="<p class='muted'>Repository not found at $REPO_PATH</p>"
wip_entries="<p class='muted'>Repository not found</p>"
roadmap_data="<p class='muted'>Repository not found</p>"
fi
# Generate HTML from template
if [ -f "$TPL_DIR/dev-status.html.tpl" ]; then
sed -e "s|{{HOSTNAME}}|$hostname|g" \
-e "s|{{VERSION}}|$version|g" \
-e "s|{{TIMESTAMP}}|$timestamp|g" \
-e "s|{{HEALTH_SCORE}}|$health_score|g" \
-e "s|{{THEME}}|$theme|g" \
-e "s|{{HISTORY_ENTRIES}}|$history_entries|g" \
-e "s|{{WIP_ENTRIES}}|$wip_entries|g" \
-e "s|{{ROADMAP_DATA}}|$roadmap_data|g" \
"$TPL_DIR/dev-status.html.tpl" > "$output_file"
else
# Fallback inline template
generate_dev_html_inline "$output_file" "$hostname" "$version" "$timestamp" \
"$health_score" "$history_entries" "$wip_entries" "$roadmap_data"
fi
# Ensure web-readable permissions
chmod 644 "$output_file"
log_ok "Generated: $output_file"
echo "$output_file"
}
# Generate services status report
generate_services_report() {
local output_file="$1"
local theme="${2:-dark}"
log_info "Generating Services Status Report..."
mkdir -p "$(dirname "$output_file")"
local hostname=$(get_hostname)
local timestamp=$(date '+%Y-%m-%d %H:%M:%S')
# Count services by channel
local tor_count=0
local dns_count=0
local mesh_count=0
# Tor hidden services
if [ -d /var/lib/tor/hidden_services ]; then
tor_count=$(ls -1 /var/lib/tor/hidden_services 2>/dev/null | wc -l)
fi
# HAProxy vhosts
dns_count=$(uci show haproxy 2>/dev/null | grep '=vhost$' | wc -l)
# Mesh services (from P2P if available)
if command -v secubox-p2p >/dev/null 2>&1; then
mesh_count=$(secubox-p2p shared-services 2>/dev/null | wc -l)
fi
# Collect service details
local tor_services=$(collect_tor_services)
local dns_services=$(collect_dns_services)
local mesh_services=$(collect_mesh_services)
# Health check
local health_up=0
local health_total=$((tor_count + dns_count))
# Generate HTML
if [ -f "$TPL_DIR/services-status.html.tpl" ]; then
sed -e "s|{{HOSTNAME}}|$hostname|g" \
-e "s|{{TIMESTAMP}}|$timestamp|g" \
-e "s|{{TOR_COUNT}}|$tor_count|g" \
-e "s|{{DNS_COUNT}}|$dns_count|g" \
-e "s|{{MESH_COUNT}}|$mesh_count|g" \
-e "s|{{HEALTH_UP}}|$health_up|g" \
-e "s|{{HEALTH_TOTAL}}|$health_total|g" \
-e "s|{{TOR_SERVICES}}|$tor_services|g" \
-e "s|{{DNS_SERVICES}}|$dns_services|g" \
-e "s|{{MESH_SERVICES}}|$mesh_services|g" \
-e "s|{{THEME}}|$theme|g" \
"$TPL_DIR/services-status.html.tpl" > "$output_file"
else
generate_services_html_inline "$output_file" "$hostname" "$timestamp" \
"$tor_count" "$dns_count" "$mesh_count" \
"$tor_services" "$dns_services" "$mesh_services"
fi
# Ensure web-readable permissions
chmod 644 "$output_file"
log_ok "Generated: $output_file"
echo "$output_file"
}
# Generate meta status report (combined dashboard)
generate_meta_report() {
local output_file="$1"
local theme="${2:-dark}"
log_info "Generating Meta Status Report..."
mkdir -p "$(dirname "$output_file")"
local hostname=$(get_hostname)
local version=$(get_version)
local timestamp=$(date '+%Y-%m-%d %H:%M:%S')
# Health score
local health_score=0
if command -v ubus >/dev/null 2>&1; then
health_score=$(ubus call luci.secubox-core get_full_health_report 2>/dev/null | \
jsonfilter -e '@.health_score' 2>/dev/null || echo "0")
fi
[ -z "$health_score" ] && health_score=0
# Service counts
local tor_count=0
local dns_count=0
local mesh_count=0
[ -d /var/lib/tor/hidden_services ] && tor_count=$(ls -1 /var/lib/tor/hidden_services 2>/dev/null | wc -l)
dns_count=$(uci show haproxy 2>/dev/null | grep '=vhost$' | wc -l)
command -v secubox-p2p >/dev/null 2>&1 && mesh_count=$(secubox-p2p shared-services 2>/dev/null | wc -l)
local total_services=$((tor_count + dns_count + mesh_count))
local services_pct=100
[ $total_services -gt 0 ] && services_pct=$((total_services * 100 / 300))
[ $services_pct -gt 100 ] && services_pct=100
# Calculate percentages for bars
local tor_pct=0 dns_pct=0 mesh_pct=0
[ $total_services -gt 0 ] && {
tor_pct=$((tor_count * 100 / total_services))
dns_pct=$((dns_count * 100 / total_services))
mesh_pct=$((mesh_count * 100 / total_services))
}
# System stats
local packages_count=$(opkg list-installed 2>/dev/null | wc -l)
local containers_count=$(lxc-ls 2>/dev/null | wc -w)
local uptime_pct=99
# Dev stats
local features_done=0
local wip_count=0
[ -f "$REPO_PATH/.claude/HISTORY.md" ] && features_done=$(grep -c "^\*\*" "$REPO_PATH/.claude/HISTORY.md" 2>/dev/null || echo 0)
[ -f "$REPO_PATH/.claude/WIP.md" ] && wip_count=$(grep -c "^- \*\*" "$REPO_PATH/.claude/WIP.md" 2>/dev/null || echo 0)
# Collect formatted data
local history_entries=$(collect_history "$REPO_PATH/.claude/HISTORY.md" 2>/dev/null || echo '<p class="muted">No history data</p>')
local wip_entries=$(collect_wip "$REPO_PATH/.claude/WIP.md" 2>/dev/null || echo '<p class="muted">No WIP data</p>')
local roadmap_data=$(collect_roadmap "$REPO_PATH/.claude/ROADMAP.md" 2>/dev/null || echo '<p class="muted">No roadmap data</p>')
local tor_services=$(collect_tor_services 2>/dev/null || echo '<p class="muted">No Tor services</p>')
local dns_services=$(collect_dns_services 10 2>/dev/null || echo '<p class="muted">No DNS services</p>')
# Generate from template
if [ -f "$TPL_DIR/meta-status.html.tpl" ]; then
sed -e "s|{{HOSTNAME}}|$hostname|g" \
-e "s|{{VERSION}}|$version|g" \
-e "s|{{TIMESTAMP}}|$timestamp|g" \
-e "s|{{HEALTH_SCORE}}|$health_score|g" \
-e "s|{{TOTAL_SERVICES}}|$total_services|g" \
-e "s|{{SERVICES_PCT}}|$services_pct|g" \
-e "s|{{UPTIME_PCT}}|$uptime_pct|g" \
-e "s|{{TOR_COUNT}}|$tor_count|g" \
-e "s|{{DNS_COUNT}}|$dns_count|g" \
-e "s|{{MESH_COUNT}}|$mesh_count|g" \
-e "s|{{TOR_PCT}}|$tor_pct|g" \
-e "s|{{DNS_PCT}}|$dns_pct|g" \
-e "s|{{MESH_PCT}}|$mesh_pct|g" \
-e "s|{{PACKAGES_COUNT}}|$packages_count|g" \
-e "s|{{CONTAINERS_COUNT}}|$containers_count|g" \
-e "s|{{FEATURES_DONE}}|$features_done|g" \
-e "s|{{WIP_COUNT}}|$wip_count|g" \
-e "s|{{HISTORY_ENTRIES}}|$history_entries|g" \
-e "s|{{WIP_ENTRIES}}|$wip_entries|g" \
-e "s|{{ROADMAP_DATA}}|$roadmap_data|g" \
-e "s|{{TOR_SERVICES}}|$tor_services|g" \
-e "s|{{DNS_SERVICES}}|$dns_services|g" \
"$TPL_DIR/meta-status.html.tpl" > "$output_file"
else
log_err "Meta template not found: $TPL_DIR/meta-status.html.tpl"
return 1
fi
chmod 644 "$output_file"
log_ok "Generated: $output_file"
echo "$output_file"
}
# Command: generate
cmd_generate() {
local report_type="$1"
local theme="$THEME"
local output_path="$OUTPUT_DIR"
shift
while [ $# -gt 0 ]; do
case "$1" in
--theme) theme="$2"; shift 2 ;;
--output) output_path="$2"; shift 2 ;;
*) shift ;;
esac
done
local timestamp=$(date '+%Y%m%d-%H%M%S')
case "$report_type" in
dev)
generate_dev_report "$output_path/dev-status-$timestamp.html" "$theme"
;;
services)
generate_services_report "$output_path/services-status-$timestamp.html" "$theme"
;;
meta)
generate_meta_report "$output_path/meta-status-$timestamp.html" "$theme"
;;
all)
generate_dev_report "$output_path/dev-status-$timestamp.html" "$theme"
generate_services_report "$output_path/services-status-$timestamp.html" "$theme"
;;
*)
log_err "Unknown report type: $report_type"
echo "Valid types: dev, services, meta, all"
return 1
;;
esac
}
# Command: send
cmd_send() {
local report_type="$1"
shift
# Generate report first
local report_file=$(cmd_generate "$report_type" "$@")
if [ -z "$report_file" ] || [ ! -f "$report_file" ]; then
log_err "Failed to generate report"
return 1
fi
# Send via email
local recipient=""
config_get recipient email recipient ""
if [ -z "$recipient" ]; then
log_warn "No email recipient configured. Report saved to: $report_file"
return 0
fi
log_info "Sending report to $recipient..."
if send_report_email "$report_type" "$(cat "$report_file")" "$recipient"; then
log_ok "Report sent to $recipient"
else
log_err "Failed to send email"
return 1
fi
}
# Command: schedule
cmd_schedule() {
local report_type="$1"
local frequency="$2"
local cron_file="/etc/cron.d/secubox-reporter"
case "$frequency" in
daily)
sed -i "s/^#DAILY_${report_type^^}#//" "$cron_file" 2>/dev/null
uci set secubox-reporter.schedule.$report_type='daily'
;;
weekly)
sed -i "s/^#WEEKLY_${report_type^^}#//" "$cron_file" 2>/dev/null
uci set secubox-reporter.schedule.$report_type='weekly'
;;
off)
# Re-comment the lines
sed -i "/secubox-reportctl.*$report_type/s/^[^#]/#DISABLED#/" "$cron_file" 2>/dev/null
uci set secubox-reporter.schedule.$report_type='off'
;;
*)
log_err "Invalid frequency: $frequency"
echo "Valid: daily, weekly, off"
return 1
;;
esac
uci commit secubox-reporter
/etc/init.d/cron restart 2>/dev/null || true
log_ok "Schedule set: $report_type = $frequency"
}
# Command: status
cmd_status() {
local json_output=0
[ "$1" = "--json" ] && json_output=1
local hostname=$(get_hostname)
local version=$(get_version)
local dev_schedule=$(uci -q get secubox-reporter.schedule.dev || echo "off")
local services_schedule=$(uci -q get secubox-reporter.schedule.services || echo "off")
local recipient=$(uci -q get secubox-reporter.email.recipient || echo "")
local report_count=$(ls -1 "$OUTPUT_DIR"/*.html 2>/dev/null | wc -l)
if [ $json_output -eq 1 ]; then
cat << EOF
{"hostname":"$hostname","version":"$version","schedules":{"dev":"$dev_schedule","services":"$services_schedule"},"email_recipient":"$recipient","report_count":$report_count}
EOF
else
echo "SecuBox Report Generator v$VERSION"
echo "=================================="
echo "Hostname: $hostname"
echo "Version: $version"
echo "Output Dir: $OUTPUT_DIR"
echo "Reports: $report_count files"
echo ""
echo "Schedules:"
echo " Dev: $dev_schedule"
echo " Services: $services_schedule"
echo ""
echo "Email: ${recipient:-Not configured}"
fi
}
# Command: list
cmd_list() {
local json_output=0
[ "$1" = "--json" ] && json_output=1
if [ $json_output -eq 1 ]; then
echo '{"reports":['
local first=1
for f in $(ls -1t "$OUTPUT_DIR"/*.html 2>/dev/null); do
[ $first -eq 0 ] && echo ","
first=0
local name=$(basename "$f")
local size=$(stat -c%s "$f" 2>/dev/null || echo 0)
local mtime=$(stat -c%Y "$f" 2>/dev/null || echo 0)
echo "{\"name\":\"$name\",\"size\":$size,\"mtime\":$mtime}"
done
echo ']}'
else
echo "Generated Reports:"
echo "=================="
ls -lh "$OUTPUT_DIR"/*.html 2>/dev/null || echo "No reports found"
fi
}
# Command: clean
cmd_clean() {
local days="${1:-30}"
log_info "Removing reports older than $days days..."
local count=$(find "$OUTPUT_DIR" -name "*.html" -mtime +$days 2>/dev/null | wc -l)
find "$OUTPUT_DIR" -name "*.html" -mtime +$days -delete 2>/dev/null
log_ok "Removed $count old reports"
}
# Command: preview
cmd_preview() {
local report_type="$1"
local tmpfile="/tmp/secubox-report-preview-$$.html"
case "$report_type" in
dev)
generate_dev_report "$tmpfile" "$THEME" >/dev/null
;;
services)
generate_services_report "$tmpfile" "$THEME" >/dev/null
;;
*)
log_err "Unknown report type: $report_type"
return 1
;;
esac
cat "$tmpfile"
rm -f "$tmpfile"
}
# Main
case "$1" in
generate)
shift
cmd_generate "$@"
;;
send)
shift
cmd_send "$@"
;;
schedule)
shift
cmd_schedule "$@"
;;
status)
shift
cmd_status "$@"
;;
list)
shift
cmd_list "$@"
;;
clean)
shift
cmd_clean "$@"
;;
preview)
shift
cmd_preview "$@"
;;
help|--help|-h|"")
usage
;;
*)
log_err "Unknown command: $1"
usage
exit 1
;;
esac