Skip to content

Instantly share code, notes, and snippets.

@mujdat
Created June 11, 2025 17:20
Show Gist options
  • Select an option

  • Save mujdat/dee14ae3d99f7812a4fcc9cc0d450270 to your computer and use it in GitHub Desktop.

Select an option

Save mujdat/dee14ae3d99f7812a4fcc9cc0d450270 to your computer and use it in GitHub Desktop.
Portiyer Helper
#!/usr/bin/env bash
# Fail on any error
set -e
# Make sure we have three arguments
if [ $# -ne 3 ]; then
echo "Usage: $0 <SERVER_ID> <ACCESS_TOKEN> <BACKEND_URL>"
exit 1
fi
SERVER_ID="$1"
ACCESS_TOKEN="$2"
BACKEND_URL="$3"
echo "Installing dstat and setting up collector script..."
echo "Server ID: $SERVER_ID"
echo "Backend URL: $BACKEND_URL"
# Ask for confirmation before proceeding
read -p "Do you want to install dstat and set up monitoring? (y/N) " choice
case "$choice" in
y|Y ) echo "Proceeding...";;
* ) echo "Installation aborted."; exit 1;;
esac
# 1) Detect distro and install dstat
if [ -f /etc/debian_version ]; then
echo "Detected Debian/Ubuntu system."
sudo apt-get update -y
sudo apt-get install -y dstat
elif [ -f /etc/redhat-release ]; then
echo "Detected RHEL/CentOS/Fedora system."
sudo yum install -y dstat
else
echo "Unsupported or unknown Linux distribution."
exit 1
fi
# 2) Create portiyer user if not exists
if ! id "portiyer" &>/dev/null; then
sudo useradd -r -s /bin/false portiyer
fi
# 3) Store credentials securely in /etc/portiyer.conf
sudo tee /etc/portiyer.conf > /dev/null <<EOF
SERVER_ID=${SERVER_ID}
ACCESS_TOKEN=${ACCESS_TOKEN}
BACKEND_URL=${BACKEND_URL}
EOF
# 4) Fix permissions for /etc/portiyer.conf
sudo chown portiyer:portiyer /etc/portiyer.conf
sudo chmod 600 /etc/portiyer.conf
# 5) Create a dedicated log directory for Portiyer
LOG_DIR="/var/lib/portiyer"
sudo mkdir -p ${LOG_DIR}
sudo chown portiyer:portiyer ${LOG_DIR}
sudo chmod 755 ${LOG_DIR}
# 6) Update collector.sh with CPU normalization & dstat fixes
COLLECTOR_SCRIPT_PATH="/usr/local/bin/collector.sh"
sudo tee ${COLLECTOR_SCRIPT_PATH} > /dev/null << 'EOF'
#!/usr/bin/env bash
# Fix cron job execution issue by setting PATH manually
export PATH=/usr/local/bin:/usr/bin:/bin
# Load credentials securely
source /etc/portiyer.conf
# Get total CPU cores
CPU_CORES=$(nproc)
# Use a dedicated directory for dstat logs instead of /tmp
LOG_DIR="/var/lib/portiyer"
CSV_FILE="${LOG_DIR}/dstat.csv"
# Ensure the log directory exists and is writable
mkdir -p ${LOG_DIR}
chown portiyer:portiyer ${LOG_DIR}
chmod 755 ${LOG_DIR}
# Ensure the CSV file is writable
touch ${CSV_FILE}
chown portiyer:portiyer ${CSV_FILE}
chmod 644 ${CSV_FILE}
# Run dstat with correct formatting (avoiding empty CSV issue)
dstat --time --cpu --mem --disk --net 1 1 --output ${CSV_FILE} 2>/dev/null
# Extract the last line of dstat output (avoid empty reads)
CSV_LINE=$(tail -n 1 ${CSV_FILE} | tr -d '\r')
# If CSV_LINE is empty, log it and exit (prevent sending bad data)
if [[ -z "$CSV_LINE" ]]; then
echo "[$(date)] WARNING: dstat output was empty!" >> ${LOG_DIR}/collector.log
exit 1
fi
# Escape JSON special characters in CSV_LINE
CSV_LINE_ESCAPED=$(echo ${CSV_LINE} | sed 's/"/\\"/g')
# Get total disk usage stats
DISK_USAGE=$(df -k --output=used,size,pcent / | tail -1 | awk '{print "{ \"used\": " $1 ", \"total\": " $2 ", \"percent\": \"" $3 "\" }"}')
# Get top 10 CPU-intensive processes (now normalized per core)
TOP_CPU=$(ps -eo pid,%cpu,%mem,cmd --sort=-%cpu | head -11 | tail -10 | awk -v cores="$CPU_CORES" '{printf "{ \"pid\": %s, \"cpu\": %.2f, \"mem\": %s, \"cmd\": \"%s\" },", $1, $2/cores, $3, $4}' | sed 's/,$//')
# Get top 10 Memory-intensive processes
TOP_MEM=$(ps -eo pid,%cpu,%mem,cmd --sort=-%mem | head -11 | tail -10 | awk '{printf "{ \"pid\": %s, \"cpu\": %s, \"mem\": %s, \"cmd\": \"%s\" },", $1, $2, $3, $4}' | sed 's/,$//')
# Build final JSON payload
PAYLOAD=$(cat <<EOF_JSON
{
"serverId": "${SERVER_ID}",
"metrics": "${CSV_LINE_ESCAPED}",
"diskUsage": ${DISK_USAGE},
"topProcesses": {
"cpu": [ ${TOP_CPU} ],
"memory": [ ${TOP_MEM} ]
}
}
EOF_JSON
)
# Send metrics via secure POST request
curl -s -X POST -H "Content-Type: application/json" \
-H "Authorization: ${ACCESS_TOKEN}" \
-H "x-server-id: ${SERVER_ID}" \
-d "${PAYLOAD}" ${BACKEND_URL}
# **Clear the file after sending data to keep storage clean**
> ${CSV_FILE}
EOF
# 7) Make the collector script executable
sudo chmod +x "${COLLECTOR_SCRIPT_PATH}"
sudo chown portiyer:portiyer "${COLLECTOR_SCRIPT_PATH}"
# 8) REMOVE OLD CRON JOBS BEFORE ADDING A NEW ONE
if [ -f "/etc/cron.d/portiyer-monitor" ]; then
sudo rm -f /etc/cron.d/portiyer-monitor
fi
# 9) Add a cron job to run the script every 5 seconds (Can be modified for different intervals)
CRON_JOB="* * * * * portiyer ${COLLECTOR_SCRIPT_PATH} > /dev/null 2>&1
* * * * * portiyer sleep 5; ${COLLECTOR_SCRIPT_PATH} > /dev/null 2>&1
* * * * * portiyer sleep 10; ${COLLECTOR_SCRIPT_PATH} > /dev/null 2>&1
* * * * * portiyer sleep 15; ${COLLECTOR_SCRIPT_PATH} > /dev/null 2>&1
* * * * * portiyer sleep 20; ${COLLECTOR_SCRIPT_PATH} > /dev/null 2>&1
* * * * * portiyer sleep 25; ${COLLECTOR_SCRIPT_PATH} > /dev/null 2>&1
* * * * * portiyer sleep 30; ${COLLECTOR_SCRIPT_PATH} > /dev/null 2>&1
* * * * * portiyer sleep 35; ${COLLECTOR_SCRIPT_PATH} > /dev/null 2>&1
* * * * * portiyer sleep 40; ${COLLECTOR_SCRIPT_PATH} > /dev/null 2>&1
* * * * * portiyer sleep 45; ${COLLECTOR_SCRIPT_PATH} > /dev/null 2>&1
* * * * * portiyer sleep 50; ${COLLECTOR_SCRIPT_PATH} > /dev/null 2>&1
* * * * * portiyer sleep 55; ${COLLECTOR_SCRIPT_PATH} > /dev/null 2>&1"
echo "${CRON_JOB}" | sudo tee /etc/cron.d/portiyer-monitor > /dev/null
sudo chmod 644 /etc/cron.d/portiyer-monitor
sudo systemctl restart cron || sudo service cron restart
echo "Installation complete!"
echo "Collector script created at: ${COLLECTOR_SCRIPT_PATH}"
echo
echo "A cron job has been automatically added to run every 5 seconds."
echo "Metrics are being sent to ${BACKEND_URL}."
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment