#!/bin/sh #set -x # genailogs - feed some log files to Googles Gemini AI and output the results # # default usage sends the output to a userXX.asp file in /tmp/var/wwwext for viewing on # the Addons tab for Asuswrt-Merlin based routers # You can also send the output to standard out. # # Default output is first the question (log file sent) then the analysis from Gemini # # You can output just the analysis by using the "result" option # # readonly PROGNAME=genailogs readonly PROGDIR=/jffs/scripts readonly SCRIPTDIR=/jffs/addons/$PROGNAME readonly SCRIPTLOGDIR=/jffs/addons/$PROGNAME/logs readonly CONFIG=$PROGNAME.conf readonly VERSION=0.4.2 # text formatting readonly BOLD="\\e[1m" readonly SETTING="${BOLD}\\e[36m" readonly CLEARFORMAT="\\e[0m" # Debug and verbose flags debug=0 results=0 verbose=0 noweb=0 cronmode=0 chatmode=0 debughtml=0 watchlogs=0 # Worker functions showhelp() { printf "\\ngenailogs (Ver $VERSION) - send one or more log files (or chat) to Google Gemini AI\\n" printf " and display the results in an Addon web page and/or the terminal\\n" printf "\\nUsage: genailogs [help] [install] [uninstall] [update] [results] [noweb] [verbose] [cron add|del|run] [chat]\\n\\n" printf "\\thelp - show this message\\n" printf "\\tresults - just show the response/results from Gemini AI\\n" printf "\\tnoweb - don't create the web page, just show the results\\n" printf "\\tverbose - both create the web page and show the results\\n" printf "\\twatch - send logs/get results every SLDELAY seconds in a loop\\n" printf " press any key to exit\\n" printf "\\tcron [add|del|run] - cron job - log the last $NUMLOGS\\n" printf "\\t additional argument: add - add a cron entry\\n" printf "\\t del - delete the cron job\\n" printf "\\t run - run the cron job\\n" printf "\\tchat - start an interactive chat session with GeminiAI\\n" printf "\\tinstall - install genailogs and create addon dir and config file\\n" printf "\\tuninstall - remove genailogs and its directory and config file\\n" printf "\\tupdate - check for and optionally update $PROGRAME\\n" printf "\\n" } dprint() { if [ $debug = 1 ]; then printf "\\n%s\\n" "$1" fi } waitsome() { if [ $1 = 1 ]; then printf "\\nPress Enter to Continue..." read -r fi } checkexit() { if $(read -r -t $SLDELAY); then printf "\\n" cleanupfiles exit fi } doinstall() { printf "\\nInstall $PROGNAME\\n" printf "\\nAre you sure you want to install $PROGNAME and it's required apps (fold and jq) and config file (Y|N) ?" read -r answr case $answr in Y|y) printf "\\nInstalling..." ;; N|n) printf "\\nOk, not installing\\n" exit ;; *) printf "Enter either Y or N...exiting\\n" exit ;; esac # make sure it was downloaded to the correct place if [ ! -f /jffs/scripts/$PROGNAME ]; then cp ./$PROGNAME.sh /jffs/scripts/$PROGNAME rm -f ./$PROGNAME.sh fi # check for entware, bail if not there printf "\\n\\nInstalling $PROGNAME\\n\\n" if [ ! -x /opt/bin/opkg ]; then printf "\\nEntware not deteted (and needed)\\n" printf "Install using AMTM and try again.\\n" exit 1 fi # check for required helper apps if [ ! -x /opt/bin/fold ] || [ ! -x /opt/bin/jq ]; then printf "\\nThe applications fold and jq are needed.\\n" printf " Check and install now (Y|N) ? " read -r answr case $answr in Y|y) printf "\\nInstalling..." /opt/bin/opkg update if [ ! -x /opt/bin/fold ]; then /opt/bin/opkg install coreutils-fold if [ ! -x /opt/bin/fold ]; then printf "\\nError - can't seem to install fold...\\n" printf " Exiting\\n" exit 1 fi fi if [ ! -x /opt/bin/jq ]; then /opt/bin/opkg install jq if [ ! -x /opt/bin/jq ]; then printf "\\nError - can't seem to install jq...\\n" printf " Exiting\\n" exit 1 fi fi printf "\\nSuccess\\n" ;; N|n) printf "This scripts requires fold and jq...exiting\\n" exit 1 ;; *) printf "Enter either Y or N...exiting\\n" exit 1 ;; esac fi # setup addon dir and config file mkdir -p $SCRIPTDIR # find a spare addon web slot # thanks to JackYaz USERASP="none" for i in 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20; do page="/www/user/user$i.asp" if [ -f "$page" ] && grep -q "GenAI Analyze Logs" "$page"; then USERASP="user$i.asp" USERTTL="user$i.title" elif [ "$USERASP" = "none" ] && [ ! -f "$page" ]; then USERASP="user$i.asp" USERTTL="user$i.title" fi done cat < $SCRIPTDIR/$CONFIG # # $PROGNAME conf file # API_KEY="PutYourAPIKeyHere" # Put here ;-) LOGFILES="/tmp/syslog.log" # log files to analyze - always check syslog # add more logs seperated by a space like this (i.e add diversion log) # LOGFILES="/tmp/syslog.log /opt/var/log/dnsmasq.log" etc. USERASP="$USERASP" # /tmp/var/wwwext output file. Shouldnt need to change NUMLINES="40" # last number of lines from log file to analyze # less give less info, more can take some time and be chatty SLDELAY="300" # time in secods between analysis for watch mode # (default=300 - 5 mins) NUMLOGS="5" # number of logs to save - for cron job SCHEDULEHRS="*" # hour of day to run genailogs in cron mode (default * - once per hour) SCHEDULEMIN="01" # minutes of hour to run genailogs in cron mode EOF # add menu to Addons tab # code taken from JackYaz which was forked from Dave sed -i "\\~$USERASP~d" /tmp/menuTree.js if ! grep -q 'menuName: "Addons"' /tmp/menuTree.js ; then echo "no addons" read a lineinsbefore="$(( $(grep -n "exclude:" /tmp/menuTree.js | cut -f1 -d':') - 1))" sed -i "$lineinsbefore"'i,\n{\nmenuName: "Addons",\nindex: "menu_Addons",\ntab: [\n{url: "javascript:var helpwindow=window.open('"'"'/ext/shared-jy/redirect.htm'"'"')", tabName: "Help & Support"},\n{url: "NULL", tabName: "__INHERIT__"}\n]\n}' /tmp/menuTree.js fi sed -i "/url: \"javascript:var helpwindow=window.open('\/ext\/shared-jy\/redirect.htm'/i {url: \"$USERASP\", tabName: \"GenAI Analyze Logs\"}," /tmp/menuTree.js echo "GenAI Analyze Logs" > /www/user/$USERTTL umount /www/require/modules/menuTree.js 2>/dev/null mount -o bind /tmp/menuTree.js /www/require/modules/menuTree.js # now make link to app printf "\\nInstalling $PROGNAME in /opt/sbin\\n" if [ -d "/opt/sbin" ] && [ ! -L "/opt/sbin/$PROGNAME" ]; then ln -s "$PROGDIR/$PROGNAME" "/opt/sbin/$PROGNAME" chmod 0755 "/opt/sbin/$PROGNAME" fi printf "\\n$SCRIPTDIR/$CONFIG created and $PROGNAME installed in /opt/sbin\\n" printf "\\nBefore running $PROGNAME you will need to get an API key from https://aistudio.google.com/app/apikey\\n" printf "Once you get the key, edit $SCRIPTDIR/$CONFIG and change the API_KEY line\\n\\n" printf "\\n\\nDo you want to add a cron job to have $PROGNAME run every hour and store the last 5 analysis\\n" printf " (this can be changed by editting $SCRIPTDIR/$CONFIG) (Y|N)? " read -r answr case $answr in Y|y) printf "\\nSetting up cron job..." addcronjob printf "\\n\\n${BOLD} Remember to add your API key to $SCRIPTDIR/$CONFIG - the cron job won't run without this ${CLEARFORMAT}n\\n" ;; N|n) printf "\\nOk, not adding - you can always add it by executing: $PROGNAME cron add\\n" ;; *) printf "Enter either Y or N...exiting\\n" ;; esac printf "\\nInstall done.\\n" exit } douninstall() { if [ ! -x $PROGDIR/$PROGNAME ] || [ ! -d $SCRIPTDIR ]; then printf "\\n$PROGNAME doesn't seem to be installed\\n" exit fi source $SCRIPTDIR/$CONFIG printf "\\nUninstall $PROGNAME\\n" printf "\\nAre you sure you want to remove $PROGNAME and it's config file (Y|N) ?" read -r answr case $answr in Y|y) printf "\\nUninstalling..." delcronjob rm -rf $SCRIPTDIR rm -f $PROGDIR/$PROGNAME if [ -L /opt/sbin/$PROGNAME ]; then rm -f /opt/sbin/$PROGNAME fi rm -f "/tmp/var/wwwext/$USERASP" sed -i "\\~$USERASP~d" /tmp/menuTree.js printf "\\nSuccess - $PROGNAME uninstalled.\\n" ;; N|n) printf "\\nok, exiting...\\n" exit ;; *) printf "Enter either Y or N...exiting\\n" exit ;; esac } doupdate() { if [ -x "$PROGDIR/$PROGNAME" ] && [ -d "$SCRIPTDIR" ]; then curl --retry 3 --silent "https://raw.githubusercontent.com/JGrana01/genailogs/master/SCRIPTVER" -o "$SCRIPTDIR/VERSION.new" if [ -z "$SCRIPTDIR/VERSION.new" ]; then echo "genailogs: Could not retrieve version number from github. Exiting." exit fi oldwas=$(grep -m 1 "VERSION=" "$PROGDIR/$PROGNAME" | sed 's/readonly VERSION\=//g') newis=$(grep -m 1 "VERSION=" "$SCRIPTDIR/VERSION.new" | sed 's/VERSION\=//g') rm -f "$SCRIPTDIR/VERSION.new" if [ "$oldwas" = "$newis" ]; then printf "\\n\\ngenailogs is up to date\\n\\n" exit fi printf "New version ($newis) of genailogs found.\\n" printf "\\nDownload and install the latest version of genailogs (Y/N)? " read a if [ "$a" = "n" ] || [ "$a" = "N" ]; then exit else printf "\\nOk, downloading genailogs\\n" curl --retry 3 --silent "https://raw.githubusercontent.com/JGrana01/genailogs/master/genailogs" -o /jffs/scripts/genailogs && chmod 0755 /jffs/scripts/genailogs printf "\\n\\nDone.\\n" printf "\\nUpdated to version %s\\n\\n" $newis fi else printf "\\n\\n$PROGNAME doesnt appear to be installed!!!\\n" fi } # Function to call GenAI genaiqa() { # only look at logs for todays date and only NUMLINES long if [ $chatmode = 0 ]; then if [ ! -f $1 ]; then printf "\\ngenailogs: Error - $1 not found\\n" output="" return fi todayis=$(date +"%b %d") logis=$(basename $1) grep "$todayis" "$1" > $logis # snapshot time for log report timestamp=$(date +"%c") # now send off... d=$(tail -n "$NUMLINES" "$1") question="Please analyze the following log file for issues: '${d}'" else question="Please answer the following: ${1}?" printf "\\nWorking..." fi # Call GenAI API curl -sSX POST \ -H 'Content-Type: application/json' \ -H "x-goog-api-key: ${API_KEY}" \ -o "genresp" \ -d '{ "contents": [ { "role": "user", "parts": [ { "text": "'"$question"'" } ] } ] }' \ "https://generativelanguage.googleapis.com/v1/models/gemini-pro:generateContent" if [ $chatmode = 0 ]; then if [ -s genresp ]; then output=$(jq -r '.candidates[0].content.parts[0].text' genresp | tr -d "'") else output="Unable to get any answers from Google AI" fi else if [ -s genresp ]; then output=$(jq -r '.candidates[0].content.parts[0].text' genresp) else output="Unable to get any answers from Google AI" fi printf "\\r${BOLD} %s ${CLEARFORMAT}\\n\\n" "$output" fi rm -f $logis } # Function to start HTML file starthtmlfile() { if [ $noweb = 0 ]; then echo "" > $1 echo "" >> $1 echo '' >> $1 echo "" >> $1 echo " GeminiAI Log Analyze " >> $1 echo "" >> $1 fi } # Function to close HTML file closehtmlfile() { if [ $noweb = 0 ]; then echo '' >> $1 echo "" >> $1 fi } # Function to prepare output prepareoutput() { timestamp=$(date +"%c") request="Please analyze the following log file ($log) for issues:

'"${d}"'" prettyout="$(printf "%s" "$output" | fold -w 80 -s)" if [ $noweb = 1 ]; then return fi dprint "DEBUG: $request" dprint "DEBUG: $prettyout" waitsome $debug # Save the log content and analysis result to a file echo "

GenAiLogs ($log - $timestamp):

" >> $1 if [ $results = 0 ]; then echo "
 $request
" >> $1 fi echo "

GenAI Analysis Result ($log):

" >> $1 echo "
$prettyout
" >> $1 } # Function to print request and answer to screen if verbose or noweb mode on showverbose() { if [ $noweb = 1 ] || [ $verbose = 1 ]; then printf "\\nHere is the analysis ($log - $timestamp):\\n" if [ $results = 0 ]; then if [ -t 1 ]; then # runing from terminal or script? printf "%s\\n" "$question" | more echo read -rp "Press Enter to show analysis..." else printf "%s\\n" "$question" fi fi printf "\\n${BOLD}Analysis:${CLEARFORMAT}\\n" printf "\\n%s\\n\\n" "$prettyout" | more fi } docronjob() { # make sure were installed and load configs checkenv setconfigs mkdir -p $SCRIPTLOGDIR #output_file_path=/tmp/tstgen # do a LIFO on the existing logs # and create if they are not yet there. i=$NUMLOGS while [ $i -gt 1 ]; do if [ ! -f $SCRIPTLOGDIR/genlog$i ]; then echo "
No analysis for log $i created yet
" > $SCRIPTLOGDIR/genlog$i else cp $SCRIPTLOGDIR/genlog"$((i - 1))" $SCRIPTLOGDIR/genlog$i fi i="$((i - 1))" done # process the logs and save a copy processlogs /tmp/genlogsout cp /tmp/genlogsout $SCRIPTLOGDIR/genlog1 # rebuild the web page cp /tmp/genlogsout $output_file_path rm -f /tmp/genlogsout i=2 while [ $i -le $NUMLOGS ]; do echo "
" >> $output_file_path echo "genailog$i" >> $output_file_path echo "
" >> $output_file_path cat $SCRIPTLOGDIR/genlog$i >> $output_file_path i="$((i + 1))" done logger -t $PROGNAME "genailogs cron job ran successful" } dochat() { checkenv setconfigs printf "\\n\\nGeminiAI Chat mode. Enter a line of text and get a response.\\n" printf " Enter "q" to exit this mode\\n\\n" while true; do read -p "chat> " input if [[ "$input" == "q" ]]; then echo "Exiting..." break fi # Call GenAI API if [ ! -z "$input" ]; then cleanout=$(echo $input | sed "s/\"/'/g") genaiqa "$cleanout" fi done } # # add/del genailogs to cron and install in services-start # # majority of code from BACKUPMON (@Victor Jaep) # addcronjob() { source $SCRIPTDIR/$CONFIG # get config info if [ -f /jffs/scripts/services-start ]; then if ! grep -q -F "sh /jffs/scripts/genailogs cron run" /jffs/scripts/services-start; then printf "\\nAdding genilogs to services-start and cron..." echo 'cru a GenAiLogs "'"$SCHEDULEMIN $SCHEDULEHRS * * * sh /jffs/scripts/genailogs cron run"'"' >> /jffs/scripts/services-start cru a GenAiLogs "$SCHEDULEMIN $SCHEDULEHRS * * * sh /jffs/scripts/genailogs cron run" printf "Done\\n" else printf "\\ngenailogs already setup as cron job, re-adding..." #delete and re-add if it already exists in case there's a time change sed -i '/GenAiLogs/d' /jffs/scripts/services-start cru d GenAiLogs echo 'cru a GenAiLogs "'"$SCHEDULEMIN $SCHEDULEHRS * * * sh /jffs/scripts/genailogs cron run"'"' >> /jffs/scripts/services-start cru a GenAiLogs "$SCHEDULEMIN $SCHEDULEHRS * * * sh /jffs/scripts/genailogs cron run" printf "Done\\n" fi else printf "\\nCreating services-start and adding genilogs and cron..." echo "#!/bin/sh" > /jffs/scripts/services-start echo 'cru a GenAiLogs "'"$SCHEDULEMIN $SCHEDULEHRS * * * sh /jffs/scripts/genailogs cron run"'"' >> /jffs/scripts/services-start chmod 755 /jffs/scripts/services-start cru a GenAiLogs "$SCHEDULEMIN $SCHEDULEHRS * * * sh /jffs/scripts/genailogs cron run" printf "Done\\n" fi } delcronjob() { source $SCRIPTDIR/$CONFIG # get config info if [ -f /jffs/scripts/services-start ]; then if grep -q -F "sh /jffs/scripts/genailogs cron run" /jffs/scripts/services-start; then printf "\\nRemoving genailogs from services-start and cron..." sed -i '/GenAiLogs/d' /jffs/scripts/services-start printf "Done\\n" else printf "\\ngenailogs not in services-start... nothing to remove\\n" cru d GenAiLogs # just to be safe fi else printf "\\nNo services-start file!!! Nothing to remove\\n" fi cru d GenAiLogs # just to be safe } # Main Function to check for options and installed needed apps # and build the log file and response dogenai() { # look for any command line options if [ ! "$#" -eq 0 ]; then while [ "$#" -gt 0 ]; do case $1 in install) doinstall exit ;; uninstall) douninstall exit ;; help) showhelp exit ;; results) results=1 ;; noweb) noweb=1 dprint "No Web On" ;; watch) watchlogs=1 dprint "Watch Logs On" ;; verbose) verbose=1 dprint "Verbose On" ;; watchlog) watchlog=1 noweb=1 # insure no web dprint "Watch Log On" ;; cron) shift case $1 in add) addcronjob exit ;; del) delcronjob exit ;; run) doweb=0 # override if set cronmode=1 # set in cron mode docronjob exit ;; *) printf "\\nwrong argument for cron function: $1\\n" exit ;; esac ;; chat) chatmode=1 dochat exit ;; update) doupdate exit ;; debug) debug=1 printf "\\nDebug On\\n" ;; debughtml) debughtml=1 ;; *) printf "\\ngenailogs: Invalid argument $1\\n" showhelp exit ;; esac shift done fi # check environment and load confgis checkenv setconfigs } checkenv() { if [ ! -f $SCRIPTDIR/$CONFIG ]; then printf "\\n$SCRIPTDIR/$CONFIG not detected, please run install\\n" exit fi if grep -q "PutYourAPIKeyHere" $SCRIPTDIR/$CONFIG; then if [ $cronmode = 0 ]; then printf "\\n API Key not detected in $SCRIPTDIR/$CONFIG\\n" printf "\\nGet a key from https://aistudio.google.com/app/apikey and add it\\n" printf "to $SCRIPTDIR/$CONFIG.\\n" exit else logger -t genailogs "genailogs cron job not run - API key not detected" exit fi fi } setconfigs() { # ok, load config info source $SCRIPTDIR/$CONFIG # Set output file path based on debug mode if [ $debughtml = 1 ]; then output_file_path="/tmp/$USERASP" printf "\\nNote: Using debug asp file - $output_file_path\\n" else output_file_path="/tmp/var/wwwext/$USERASP" fi } processlogs() { starthtmlfile $1 for log in $LOGFILES do genaiqa "$log" if [ ! -z "$output" ]; then prepareoutput $1 if [ $noweb = 1 ] || [ $watchlogs = 1 ]; then printf "\\n${BOLD}GenAI analysis result for "$log" ($timestamp)${CLEARFORMAT}\\n" elif [ $cronmode = 0 ]; then printf "\\nQuestion, Log content, and GenAI analysis result for "$log" saved to $1\\n" fi showverbose fi done closehtmlfile $1 } # remove temp files cleanupfiles() { rm -f genresp } # Main script dogenai "$@" if [ $watchlogs = 1 ]; then debug=0 # override interactive things verbose=0 results=1 printf "\\nWatching logs every $SLDELAY seconds\\n" printf "\\n Press any key to exit\\n" while true do processlogs "$output_file_path" printf "\\n%s\\n\\n" "$prettyout" checkexit done else processlogs "$output_file_path" fi cleanupfiles exit