A small program for web hosts, or self hosters, that reads TOML config files, generates reports with GoAccess, and emails them to config'd recipients. The user can schedule with cron, systemd-timers, etc.
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
GoAccessReporter/goaccess_reporter.nim

177 lines
6.5 KiB

2 weeks ago
#[Copyright 2024 ITwrx.
This file is part of GoAccessReporter.
GoAccessReporter is released under the GNU Affero General Public License 3.0.
See COPYING or <https://www.gnu.org/licenses/> for details.]#
import parsetoml, smtp, mime, std/[logging, times, dirs, paths, os, osproc]
let dt = now()
let nowDT = dt.format("M-d-YYYY h:mm:ss tt")
let nowDTNoSpaces = dt.format("M-d-YYYY-h-mm-ss-tt")
discard existsOrCreateDir(Path expandTilde("~/.goaccess-reporter"))
discard existsOrCreateDir(Path expandTilde("~/.goaccess-reporter/reports"))
var logger = newFileLogger(expandTilde("~/.goaccess-reporter/goaccess-reporter.log"))
var logMsg: string
proc logKeyParseError(key: string, tableName: string, tomlFile: string) =
logMsg = nowDT & ": unable to parse '" & key & "' key from table: " & tableName & " in " & tomlFile & ".toml."
logger.log(lvlError, logMsg)
var tomlErrors = false
var tomlSitesTable, tomlEmailTable: TomlTableRef
try:
let tomlSitesData = parsetoml.parseFile("/etc/goaccess-reporter/sites.toml")
tomlSitesTable = tomlSitesData.getTable()
except TomlError:
logMsg = nowDT & ": Unable to parse '/etc/goaccess-reporter/sites.toml'"
logger.log(lvlError, logMsg)
echo "Error: unable to parse toml sites config file"
quit()
try:
let tomlEmailData = parsetoml.parseFile("/etc/goaccess-reporter/email.toml")
tomlEmailTable = tomlEmailData.getTable()
except TomlError:
logMsg = nowDT & ": Unable to parse '/etc/goaccess-reporter/email.toml'"
logger.log(lvlError, logMsg)
echo "Error: unable to parse toml email config file"
quit()
type
Site = object
name, logFile, output: string
appendDate, deleteAfter: bool
emailAddresses: seq[string]
type
Email = object
hostname, senderAddress, senderPassword: string
starttlsPort: int
var sites: seq[Site]
var site: Site
var email: Email
var emailAddresses: seq[string]
var logFile, output, outputPath, origOutputLocation, outputLocation, outputFilename: string
var appendDate, deleteAfter: bool
var emails: seq[TomlValueRef]
var goaccessRun, tarRun: tuple[output: string, exitCode: int]
for key, val in tomlEmailTable.pairs:
if val.kind == TomlValueKind.Table:
let tableName = key
let table = val.getTable()
try:
email.hostname = table["hostname"].getStr()
except KeyError:
tomlErrors = true
logKeyParseError("hostname", tableName, "email")
try:
email.starttlsPort = table["starttls-port"].getInt()
except KeyError:
tomlErrors = true
logKeyParseError("starttls-port", tableName, "email")
try:
email.senderAddress = table["sender-address"].getStr()
except KeyError:
tomlErrors = true
logKeyParseError("sender-address", tableName, "email")
try:
email.senderPassword = table["sender-password"].getStr()
except KeyError:
tomlErrors = true
logKeyParseError("sender-password", tableName, "email")
for key, val in tomlSitesTable.pairs:
if val.kind == TomlValueKind.Table:
let tableName = key
site.name = tableName
let table = val.getTable()
try:
logFile = table["log-file"].getStr()
site.logFile = logFile
except KeyError:
tomlErrors = true
logKeyParseError("log-file", tableName, "sites")
try:
output = table["output"].getStr()
site.output = output
except KeyError:
tomlErrors = true
logKeyParseError("output", tableName, "sites")
try:
appendDate = table["append-date"].getBool()
site.appendDate = appendDate
except KeyError:
tomlErrors = true
logKeyParseError("append-date", tableName, "sites")
try:
deleteAfter = table["delete-after"].getBool()
site.deleteAfter = deleteAfter
except KeyError:
tomlErrors = true
logKeyParseError("delete-after", tableName, "sites")
try:
emails = table["email-addresses"].getElems()
for email in emails:
emailAddresses.add($email)
site.emailAddresses = emailAddresses
except KeyError:
tomlErrors = true
logKeyParseError("email-addresses", tableName, "sites")
sites.add(site)
emailAddresses = @[]
if not tomlErrors:
outputPath = expandTilde("~/.goaccess-reporter/reports")
for site in sites:
if site.appendDate:
origOutputLocation = expandTilde("~/.goaccess-reporter/reports/" & site.name & "-report-" & nowDTNoSpaces & ".html")
outputFilename = site.name & "-report-" & nowDTNoSpaces & ".html"
else:
origOutputLocation = expandTilde("~/.goaccess-reporter/reports/" & site.name & "-report.html")
outputFilename = site.name & "-report.html"
goaccessRun = execCmdEx( "goaccess " & site.logFile & " -o " & origOutputLocation & " --geoip-database=/var/www/goaccess-geolite2-city.mmdb")
if goaccessRun.exitCode == 1:
echo goaccessRun.output
logMsg = goaccessRun.output
logger.log(lvlError, logMsg)
quit()
tarRun = execCmdEx("tar -zcf " & outputPath & "/" & outputFilename.changeFileExt("tar.gz") & " -C " & outputPath & " " & outputFilename)
if tarRun.exitCode == 1:
echo tarRun.output
logMsg = tarRun.output
logger.log(lvlError, logMsg)
quit()
else:
outputLocation = origOutputLocation.changeFileExt("tar.gz")
outputFilename = outputFilename.changeFileExt("tar.gz")
#send the reports via email. Using the mime module for sending with attachment, so some things are different from using smtp only.
var tarFile = newAttachment(readFile(outputLocation), filename = outputFilename)
tarFile.encodeBase64()
var multi = newMimeMessage()
# Main email data
multi.body = "I have no idea what goes here. a message that their client is not compat?"
multi.header["bcc"] = site.emailAddresses.mimeList
multi.header["from"] = email.senderAddress
multi.header["subject"] = "GoAccess/SEO Report for " & site.name
# Add text to email body
var first = newMimeMessage()
first.header["Content-Type"] = "text/plain"
first.body = "Please see attached SEO/GoAccess report."
multi.parts.add first
multi.parts.add tarFile
let smtpConn = newSmtp(debug=false)
smtpConn.connect(email.hostname, Port email.starttlsPort)
smtpConn.startTls()
smtpConn.auth(email.senderAddress, email.senderPassword)
smtpConn.sendmail(email.hostname, site.emailAddresses, $multi.finalize())
smtpConn.close()
if site.deleteAfter:
removeFile(origOutputLocation)
removeFile(outputLocation)
else:
echo "Errors in toml config file. Please see '~/.goaccess-reporter/goaccess-reporter.log'"
quit()