diff --git a/config/config.dist.sh b/config/config.dist.sh new file mode 100644 index 0000000..5a9edc6 --- /dev/null +++ b/config/config.dist.sh @@ -0,0 +1,85 @@ +#!/bin/bash +# +# Example configuration file for dashboard. +# +# Copy this file to config.sh and edit it to your needs. +# +# --- GitHub ------------------------------------------------------------------- +# +# Your GitHub username. +# Required by: modules/github.sh +# +GITHUB_USER="" + +# +# An array of your repository names to fetch stats for. +# Example: REPOS=("repo1" "repo2") +# Required by: modules/github.sh +# +REPOS=() + +# +# Your GitHub Personal Access Token (PAT). +# Required for modules/github-sponsors.sh +# Optional for modules/github.sh (to avoid rate limiting). +# Create a token at: https://github.com/settings/tokens +# +GITHUB_TOKEN="" + + +# --- Hacker News -------------------------------------------------------------- +# +# Your Hacker News username. +# Required by: modules/hackernews.sh +# +HN_USER="" + + +# --- Discord ------------------------------------------------------------------ +# +# Your Discord server ID. +# To get this, right-click your server icon in Discord and select "Copy Server ID". +# You may need to enable Developer Mode in your Discord settings first. +# Required by: modules/discord.sh +# +DISCORD_SERVER_ID="" + +# +# Note on Discord Stats: +# The current implementation only fetches the number of online users via the +# server's public widget. To get more detailed stats like total member count, +# you would need to create a Discord Bot, give it the "Server Members Intent", +# and use its token to make authenticated API calls. This would require +# modifying the modules/discord.sh script. +# + + +# --- Crypto Donations --------------------------------------------------------- +# +# Your cryptocurrency wallet addresses. +# The script will try to fetch balances for any address you provide here. +# The variable name should be `CRYPTO_WALLET_{TICKER}`. +# +# Examples: +# CRYPTO_WALLET_BTC="bc1q..." +# CRYPTO_WALLET_ETH="0x..." +# CRYPTO_WALLET_LTC="ltc1q..." +# CRYPTO_WALLET_DOGE="D..." +# CRYPTO_WALLET_DASH="X..." + +# +# You can also specify a provider for a given ticker. +# By default, BTC, ETH, LTC, DOGE, and DASH use 'blockcypher'. +# Other EVM-compatible chains can use 'covalent'. +# +# Examples: +# CRYPTO_MATIC_PROVIDER="covalent" +# CRYPTO_WALLET_MATIC="0x..." +# CRYPTO_AVAX_PROVIDER="covalent" +# CRYPTO_WALLET_AVAX="0x..." + +# +# API Keys for crypto providers (optional, but recommended). +# +BLOCKCYPHER_TOKEN="" +COVALENT_API_KEY="" diff --git a/dashboard.sh b/dashboard.sh index ac653f5..76e0d3c 100755 --- a/dashboard.sh +++ b/dashboard.sh @@ -1,4 +1,4 @@ -#!/bin/bash +#!/usr/bin/env bash # # dashboard.sh # @@ -41,14 +41,19 @@ _error() { printf '[ERROR] %s\n' "$1" >&2 } +AGGREGATE=0 + usage() { echo "Usage: $(basename "$0") [options] [module]" echo "Options:" + echo " -a, --aggregate Generate a trend report from all .tsv files in the reports/ directory." echo " -f, --format Set the output format." echo " Supported formats: ${VALID_FORMATS[*]}" - echo " -o, --output Set the output file or directory." echo " -h, --help Display this help message." echo + echo "To save a report, redirect the output to a file. Example:" + echo " ./dashboard.sh > reports/my_report.tsv" + echo echo "Available modules:" local modules=() for module in "${SCRIPT_DIR}/modules"/*; do @@ -65,18 +70,17 @@ _debug "$DASHBOARD_NAME v$DASHBOARD_VERSION" _debug 'parsing command-line arguments' -OUTPUT_PATH="" while [[ $# -gt 0 ]]; do key="$1" case $key in + -a|--aggregate) + AGGREGATE=1 + shift + ;; -f|--format) FORMAT="$2" shift 2 ;; - -o|--output) - OUTPUT_PATH="$2" - shift 2 - ;; -h|--help) usage exit 0 @@ -128,49 +132,6 @@ if ! command -v jq &> /dev/null; then fi -_debug 'Get list of modules to run' -MODULES_DIR="${SCRIPT_DIR}/modules" -MODULES_TO_RUN=() -if [ -n "$MODULE_TO_RUN" ]; then - # If the user provides 'github', check for 'github.sh' - if [[ ! "$MODULE_TO_RUN" == *.sh ]]; then - MODULE_TO_RUN="${MODULE_TO_RUN}.sh" - fi - MODULE_PATH="${MODULES_DIR}/${MODULE_TO_RUN}" - if [ ! -x "$MODULE_PATH" ]; then - # If 'github.sh' is not found, try without the extension for backward compatibility - MODULE_TO_RUN_NO_EXT="${MODULE_TO_RUN%.sh}" - MODULE_PATH_NO_EXT="${MODULES_DIR}/${MODULE_TO_RUN_NO_EXT}" - if [ -x "$MODULE_PATH_NO_EXT" ]; then - MODULE_PATH="$MODULE_PATH_NO_EXT" - MODULE_TO_RUN="$MODULE_TO_RUN_NO_EXT" - else - _error "Error: Module '${MODULE_TO_RUN}' not found or not executable." - exit 1 - fi - fi - MODULES_TO_RUN+=("$MODULE_TO_RUN") -else - # Find all executable files in the modules directory, with or without .sh - for module in "${MODULES_DIR}"/*; do - if [ -x "$module" ]; then - MODULES_TO_RUN+=("$(basename "$module")") - fi - done -fi - -_debug "MODULES_TO_RUN: ${MODULES_TO_RUN[*]}" - -_debug 'Collect output from all modules' -OUTPUTS=() -for module_name in "${MODULES_TO_RUN[@]}"; do - _debug "Calling $module_name" - module_output=$("$MODULES_DIR/$module_name" "$MODULE_EXEC_FORMAT") - if [ -n "$module_output" ]; then - _debug "Saving output from $module_name: $(echo "$module_output" | wc -c | tr -d ' ') bytes" - OUTPUTS+=("$module_output") - fi -done generate_report() { case "$FORMAT" in @@ -264,31 +225,105 @@ generate_report() { esac } -FINAL_OUTPUT_FILE="" -if [ -n "$OUTPUT_PATH" ]; then - if [ -d "$OUTPUT_PATH" ]; then - # User provided a directory - REPORTS_DIR="$OUTPUT_PATH" - TIMESTAMP=$(date +"%Y-%m-%d-%H-%M") - FINAL_OUTPUT_FILE="${REPORTS_DIR}/${TIMESTAMP}.${FORMAT}" - else - # User provided a file path - FINAL_OUTPUT_FILE="$OUTPUT_PATH" +aggregate_reports() { + local reports_dir="${SCRIPT_DIR}/reports" + _debug "Aggregating reports from ${reports_dir}" + if ! command -v awk &> /dev/null; then + _error "'awk' command not found, which is required for aggregation." + exit 1 fi -else - # Default behavior - REPORTS_DIR="${SCRIPT_DIR}/reports" - mkdir -p "$REPORTS_DIR" - TIMESTAMP=$(date +"%Y-%m-%d-%H-%M") - FINAL_OUTPUT_FILE="${REPORTS_DIR}/${TIMESTAMP}.${FORMAT}" -fi -_debug "Assemble the final report: FORMAT: $FORMAT" -_debug "Output file: $FINAL_OUTPUT_FILE" + local report_files + report_files=$(find "$reports_dir" -name "*.tsv" 2>/dev/null | sort) + if [ -z "$report_files" ]; then + _warn "No .tsv reports found in ${reports_dir} to aggregate." + return + fi + + # Use awk to process the tsv files + # We pass the report files to awk, which will process them in alphabetical order. + # Since the filenames start with a timestamp, this will process them in chronological order. + awk ' + BEGIN { + FS="\t"; + OFS="\t"; + print "Metric\tFirst Value\tLast Value\tChange"; + print "------\t-----------\t----------\t------"; + } + FNR == 1 { next; } # Skip header row of each file + { + metric = $2 OFS $3 OFS $4; # module, channel, namespace + value = $5; + if (!(metric in first_value)) { + first_value[metric] = value; + } + last_value[metric] = value; + } + END { + for (metric in last_value) { + change = last_value[metric] - first_value[metric]; + # Add a plus sign for positive changes + if (change > 0) { + change_str = "+" change; + } else { + change_str = change; + } + print metric, first_value[metric], last_value[metric], change_str; + } + }' $report_files +} + +# --- Main Execution Flow ---------------------------------------------------- -if [ -n "$FINAL_OUTPUT_FILE" ]; then - generate_report > "$FINAL_OUTPUT_FILE" +if [ "$AGGREGATE" -eq 1 ]; then + aggregate_reports else + # --- Module Data Collection --------------------------------------------- + _debug 'Get list of modules to run' + MODULES_DIR="${SCRIPT_DIR}/modules" + MODULES_TO_RUN=() + if [ -n "$MODULE_TO_RUN" ]; then + # If the user provides 'github', check for 'github.sh' + if [[ ! "$MODULE_TO_RUN" == *.sh ]]; then + MODULE_TO_RUN="${MODULE_TO_RUN}.sh" + fi + MODULE_PATH="${MODULES_DIR}/${MODULE_TO_RUN}" + if [ ! -x "$MODULE_PATH" ]; then + # If 'github.sh' is not found, try without the extension for backward compatibility + MODULE_TO_RUN_NO_EXT="${MODULE_TO_RUN%.sh}" + MODULE_PATH_NO_EXT="${MODULES_DIR}/${MODULE_TO_RUN_NO_EXT}" + if [ -x "$MODULE_PATH_NO_EXT" ]; then + MODULE_PATH="$MODULE_PATH_NO_EXT" + MODULE_TO_RUN="$MODULE_TO_RUN_NO_EXT" + else + _error "Error: Module '${MODULE_TO_RUN}' not found or not executable." + exit 1 + fi + fi + MODULES_TO_RUN+=("$MODULE_TO_RUN") + else + # Defined order of execution + ORDERED_MODULES=("github.sh" "hackernews.sh" "discord.sh" "github-sponsors.sh" "crypto.sh") + for module in "${ORDERED_MODULES[@]}"; do + if [ -x "${MODULES_DIR}/${module}" ]; then + MODULES_TO_RUN+=("$module") + fi + done + fi + + _debug "MODULES_TO_RUN: ${MODULES_TO_RUN[*]}" + + _debug 'Collect output from all modules' + OUTPUTS=() + for module_name in "${MODULES_TO_RUN[@]}"; do + _debug "Calling $module_name" + module_output=$("$MODULES_DIR/$module_name" "$MODULE_EXEC_FORMAT") + if [ -n "$module_output" ]; then + _debug "Saving output from $module_name: $(echo "$module_output" | wc -c | tr -d ' ') bytes" + OUTPUTS+=("$module_output") + fi + done + generate_report fi diff --git a/docs/render.md b/docs/render.md deleted file mode 100644 index 89b95d8..0000000 --- a/docs/render.md +++ /dev/null @@ -1,23 +0,0 @@ -# Deploying to Render.com - -This repository is configured for continuous deployment to [Render.com](https://render.com/) on their free tier. - -## How It Works - -The deployment is defined using infrastructure-as-code in the `render.yaml` file at the root of the repository. This file tells Render to: - -1. **Create a Web Service:** Define a service of type `web`. -2. **Use Docker:** Use the `docker` environment to build and deploy the service. -3. **Use the Free Plan:** Deploy the service on the free instance type. -4. **Configure Health Checks:** Use the `/` path to perform health checks, ensuring the service is running correctly. - -## Getting Started - -To deploy this repository to your own Render account: - -1. Create a new "Blueprint" service in the Render dashboard. -2. Connect the GitHub repository you created from this template. -3. Render will automatically detect and use the `render.yaml` file. -4. Approve the plan, and Render will build and deploy the service. - -Any subsequent pushes to your `main` branch will automatically trigger a new deployment on Render. diff --git a/modules/crypto.sh b/modules/crypto.sh index 3e1105e..6f93b83 100755 --- a/modules/crypto.sh +++ b/modules/crypto.sh @@ -1,4 +1,4 @@ -#!/bin/bash +#!/usr/bin/env bash # # modules/crypto.sh # diff --git a/modules/discord.sh b/modules/discord.sh index 1c1e8cd..712ba0d 100755 --- a/modules/discord.sh +++ b/modules/discord.sh @@ -1,4 +1,4 @@ -#!/bin/bash +#!/usr/bin/env bash # # modules/discord.sh # diff --git a/modules/github-sponsors.sh b/modules/github-sponsors.sh index 5500f88..9465ecd 100755 --- a/modules/github-sponsors.sh +++ b/modules/github-sponsors.sh @@ -1,4 +1,4 @@ -#!/bin/bash +#!/usr/bin/env bash # # modules/github-sponsors # diff --git a/modules/github.sh b/modules/github.sh index b5fbb67..327f751 100755 --- a/modules/github.sh +++ b/modules/github.sh @@ -1,4 +1,4 @@ -#!/bin/bash +#!/usr/bin/env bash # # modules/github # diff --git a/modules/hackernews.sh b/modules/hackernews.sh index 621fe5d..d98da28 100755 --- a/modules/hackernews.sh +++ b/modules/hackernews.sh @@ -1,4 +1,4 @@ -#!/bin/bash +#!/usr/bin/env bash # # modules/hackernews # diff --git a/render.yaml b/render.yaml deleted file mode 100644 index 4dc466e..0000000 --- a/render.yaml +++ /dev/null @@ -1,6 +0,0 @@ -services: - - type: web - name: base-template-app - env: docker - plan: free - healthCheckPath: / diff --git a/reports/.gitignore b/reports/.gitignore new file mode 100644 index 0000000..4aabeef --- /dev/null +++ b/reports/.gitignore @@ -0,0 +1,5 @@ +# Ignore everything in this directory +* + +# Except for this file +!.gitignore