Skip to content

Instantly share code, notes, and snippets.

@koleson
Last active November 6, 2025 05:27
Show Gist options
  • Select an option

  • Save koleson/d54e0cd1f3bce3aedf13be005df99abb to your computer and use it in GitHub Desktop.

Select an option

Save koleson/d54e0cd1f3bce3aedf13be005df99abb to your computer and use it in GitHub Desktop.
PVS System Information Checker - Retrieves system info from SunPower PVS devices via API
#!/bin/bash
#############################################################################
# pvs_vars_check.sh
#
# Description: Retrieves and displays system information from SunPower PVS
# devices via their API, including serial number, model, hardware
# revision, firmware version, system type, and eMMC flashwear.
#
# Usage: ./pvs_vars_check.sh [OPTIONS] <PVS_IP>
# Options:
# -v, --json Output raw JSON response in addition to
# formatted output
#
# Requirements: curl, jq
#
# Author: Kiel Oleson
# Assistance: Claude (Anthropic)
# Date: 5 November 2025
# Version: 1.0.1
#
# Version History:
# 1.0.1 (5 Nov 2025) - Fixed flashwear conversion arithmetic expression
# 1.0 (5 Nov 2025) - Initial release
#
# More PVS Info: https://gist.github.com/koleson/5c719620039e0282976a8263c068e85c
# fcgi_vars info: https://github.com/tjmonk/fcgi_vars
# API docs: https://github.com/SunStrong-Management/pypvs/tree/main/doc
#
#############################################################################
# Parse flags
JSON_OUTPUT=false
PVS_IP=""
while [[ $# -gt 0 ]]; do
case $1 in
-v|--json)
JSON_OUTPUT=true
shift
;;
*)
PVS_IP="$1"
shift
;;
esac
done
# Check for required tools
if ! command -v curl &> /dev/null; then
echo "ERROR: curl is not installed"
echo "Please install curl to use this script"
echo ""
echo "Installation instructions:"
echo " macOS: brew install curl"
echo " Ubuntu: sudo apt-get install curl"
echo " Fedora: sudo dnf install curl"
exit 1
fi
if ! command -v jq &> /dev/null; then
echo "ERROR: jq is not installed"
echo "Please install jq to use this script"
echo ""
echo "Installation instructions:"
echo " macOS: brew install jq"
echo " Ubuntu: sudo apt-get install jq"
echo " Fedora: sudo dnf install jq"
exit 1
fi
if ! command -v base64 &> /dev/null; then
echo "ERROR: base64 is not installed"
echo "The base64 command is required for authentication"
echo "It should be available by default on most Unix systems"
exit 1
fi
# Test that jq can parse JSON
echo '{"test":true}' | jq . &> /dev/null
if [ $? -ne 0 ]; then
echo "ERROR: jq is not working correctly"
echo "jq failed to parse test JSON"
exit 1
fi
# Check for PVS IP parameter
if [ -z "$PVS_IP" ]; then
echo "Usage: $0 [OPTIONS] <PVS_IP>"
echo "Example: $0 192.168.1.101"
echo ""
echo "Options:"
echo " -v, --json Output raw JSON response"
exit 1
fi
# Use HTTPS exclusively (some vars are only available via HTTPS)
PROTOCOL="https"
RESPONSE=$(curl -k -s -m 10 --connect-timeout 5 -w "\n%{http_code}" "${PROTOCOL}://${PVS_IP}/vars?match=/" 2>/dev/null)
# Check if curl succeeded
if [ $? -ne 0 ]; then
echo ""
echo "ERROR: Unable to connect to PVS at ${PVS_IP}"
echo ""
echo "Please check:"
echo " - The IP address or hostname is correct"
echo " - The PVS is powered on and connected to the network"
echo " - You are connected to the same network as the PVS"
exit 1
fi
# Split response into body and HTTP code
HTTP_CODE=$(echo "$RESPONSE" | tail -n1)
BODY=$(echo "$RESPONSE" | sed '$d')
# Check for non-200 response
if [ "$HTTP_CODE" != "200" ]; then
echo ""
echo "ERROR: The PVS responded with an error (HTTP $HTTP_CODE)"
echo "The PVS firmware version may be unexpected or incompatible"
exit 1
fi
# Validate JSON structure
echo "$BODY" | jq . &> /dev/null
if [ $? -ne 0 ]; then
echo ""
echo "ERROR: The PVS response is not valid JSON"
echo "The PVS firmware version may be unexpected or incompatible"
echo ""
echo "Received response:"
echo "$BODY" | head -c 500
exit 1
fi
COUNT=$(echo "$BODY" | jq -r '.count // empty' 2>/dev/null)
if [ -z "$COUNT" ]; then
echo ""
echo "ERROR: The PVS response format was not recognized"
echo "The PVS firmware version may be unexpected or incompatible"
echo ""
echo "Received response:"
echo "$BODY" | head -c 500
exit 1
fi
# Extract serial number
SERIAL=$(echo "$BODY" | jq -r '.values[] | select(.name == "/sys/info/serialnum") | .value' 2>/dev/null)
if [ -z "$SERIAL" ]; then
echo ""
echo "ERROR: Could not find serial number in PVS response"
echo "The PVS firmware version may be unexpected or incompatible"
exit 1
fi
# Validate serial number format (should start with Z and be long enough)
if [[ ! "$SERIAL" =~ ^Z.+ ]]; then
echo ""
echo "ERROR: Serial number format not recognized"
echo "Expected format starting with 'Z', but got: ${SERIAL}"
echo "The PVS firmware version may be unexpected or incompatible"
exit 1
fi
if [ ${#SERIAL} -lt 5 ]; then
echo ""
echo "ERROR: Serial number too short"
echo "Expected at least 5 characters, but got: ${SERIAL} (${#SERIAL} characters)"
exit 1
fi
# Extract last 5 characters of serial number
SN_LAST5="${SERIAL: -5}"
# Authenticate with PVS
# Create cookie file in temp directory
COOKIE_FILE=$(mktemp /tmp/pvs_cookies.XXXXXX 2>/dev/null)
if [ $? -ne 0 ] || [ -z "$COOKIE_FILE" ]; then
echo ""
echo "ERROR: Failed to create temporary cookie file"
echo "Please ensure /tmp is writable"
exit 1
fi
trap "rm -f $COOKIE_FILE" EXIT
# Create base64 auth header
AUTH=$(echo -n "ssm_owner:${SN_LAST5}" | base64)
# Attempt authentication
AUTH_RESPONSE=$(curl -k -s -m 10 --connect-timeout 5 \
-b "$COOKIE_FILE" \
-c "$COOKIE_FILE" \
-w "\n%{http_code}" \
-H "Authorization: basic $AUTH" \
"${PROTOCOL}://${PVS_IP}/auth?login" 2>/dev/null)
# Check if curl succeeded
if [ $? -ne 0 ]; then
echo ""
echo "ERROR: Authentication request failed"
echo "Unable to connect to the PVS authentication endpoint"
exit 1
fi
# Split response into body and HTTP code
AUTH_HTTP_CODE=$(echo "$AUTH_RESPONSE" | tail -n1)
AUTH_BODY=$(echo "$AUTH_RESPONSE" | sed '$d')
if [ "$AUTH_HTTP_CODE" != "200" ]; then
echo ""
echo "ERROR: Authentication failed (HTTP $AUTH_HTTP_CODE)"
echo "The PVS did not accept the credentials"
if [ "$JSON_OUTPUT" = true ]; then
echo ""
echo "Response body:"
echo "$AUTH_BODY"
fi
exit 1
fi
if [ "$JSON_OUTPUT" = true ]; then
echo "Auth response (HTTP $AUTH_HTTP_CODE):"
echo "$AUTH_BODY"
fi
# Retrieve system variables
VARS_URL="${PROTOCOL}://${PVS_IP}/vars?name=/sys/info/serialnum,/sys/info/model,/sys/info/hwrev,/sys/info/sw_rev,/sys/info/sys_type,/sys/pvs/flashwear_type_b"
VARS_RESPONSE=$(curl -k -s -m 10 --connect-timeout 5 \
-b "$COOKIE_FILE" \
-c "$COOKIE_FILE" \
-w "\n%{http_code}" \
"$VARS_URL" 2>/dev/null)
# Check if curl succeeded
if [ $? -ne 0 ]; then
echo ""
echo "ERROR: Failed to retrieve system variables"
echo "Unable to connect to the PVS"
exit 1
fi
# Split response into body and HTTP code
VARS_HTTP_CODE=$(echo "$VARS_RESPONSE" | tail -n1)
VARS_BODY=$(echo "$VARS_RESPONSE" | sed '$d')
if [ "$VARS_HTTP_CODE" != "200" ]; then
echo ""
echo "ERROR: Failed to retrieve system variables (HTTP $VARS_HTTP_CODE)"
# Try to parse error details from JSON response
ERROR_DESC=$(echo "$VARS_BODY" | jq -r '.description // empty' 2>/dev/null)
ERROR_CODE=$(echo "$VARS_BODY" | jq -r '.errorcode // empty' 2>/dev/null)
if [ -n "$ERROR_DESC" ]; then
echo "Error Description: $ERROR_DESC"
fi
if [ -n "$ERROR_CODE" ]; then
echo "Error Code: $ERROR_CODE"
fi
echo "The authenticated request was not successful"
if [ "$JSON_OUTPUT" = true ]; then
echo ""
echo "Request URL:"
echo "$VARS_URL"
echo ""
echo "Response body:"
echo "$VARS_BODY"
fi
exit 1
fi
# Validate JSON structure
echo "$VARS_BODY" | jq . &> /dev/null
if [ $? -ne 0 ]; then
echo ""
echo "ERROR: The PVS response for system variables is not valid JSON"
echo "The PVS firmware version may be unexpected or incompatible"
exit 1
fi
VARS_COUNT=$(echo "$VARS_BODY" | jq -r '.count // empty' 2>/dev/null)
if [ -z "$VARS_COUNT" ]; then
echo ""
echo "ERROR: Unexpected response format when retrieving system variables"
echo "The PVS firmware version may be unexpected or incompatible"
exit 1
fi
# Extract specific values
MODEL=$(echo "$VARS_BODY" | jq -r '.values[] | select(.name == "/sys/info/model") | .value' 2>/dev/null)
HWREV=$(echo "$VARS_BODY" | jq -r '.values[] | select(.name == "/sys/info/hwrev") | .value' 2>/dev/null)
SW_REV=$(echo "$VARS_BODY" | jq -r '.values[] | select(.name == "/sys/info/sw_rev") | .value' 2>/dev/null)
SYS_TYPE=$(echo "$VARS_BODY" | jq -r '.values[] | select(.name == "/sys/info/sys_type") | .value' 2>/dev/null)
FLASHWEAR=$(echo "$VARS_BODY" | jq -r '.values[] | select(.name == "/sys/pvs/flashwear_type_b") | .value' 2>/dev/null)
# Set defaults for missing values
[ -z "$MODEL" ] && MODEL="MISSING"
[ -z "$HWREV" ] && HWREV="MISSING"
[ -z "$SW_REV" ] && SW_REV="MISSING"
[ -z "$SYS_TYPE" ] && SYS_TYPE="MISSING"
# Process flashwear hex value to percentage
if [ -n "$FLASHWEAR" ]; then
# Convert hex to decimal (remove 0x prefix if present)
FLASHWEAR_HEX="${FLASHWEAR#0x}"
# Validate hex format before conversion
if [[ "$FLASHWEAR_HEX" =~ ^[0-9A-Fa-f]+$ ]]; then
# Arithmetic expansion doesn't allow stderr redirection, so check is redundant after regex
FLASHWEAR_DEC=$((16#$FLASHWEAR_HEX))
FLASHWEAR_PCT="${FLASHWEAR_DEC}0% consumed"
else
FLASHWEAR_PCT="INVALID (${FLASHWEAR})"
fi
else
FLASHWEAR_PCT="MISSING"
fi
# Display results
if [ "$JSON_OUTPUT" = true ]; then
echo "=============================="
echo "Raw JSON Response"
echo "=============================="
echo "URL: ${VARS_URL}"
echo ""
echo "$VARS_BODY" | jq '.'
echo ""
fi
# Display formatted output
echo "=============================="
echo "PVS Information"
echo "=============================="
echo "IP Address: ${PVS_IP}"
echo "Serial Number: ${SERIAL}"
echo "Last 5 Characters: ${SN_LAST5} (ssm_owner password)"
echo "PVS Model: ${MODEL}"
echo "PVS Hardware Revision: ${HWREV}"
echo "PVS Firmware Version: ${SW_REV}"
echo "System Type: ${SYS_TYPE}"
echo "eMMC Flashwear: ${FLASHWEAR_PCT}"
echo "=============================="
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment