From 4b8e5c2b2b193cfe1ff389104d4347f5b5b0afe7 Mon Sep 17 00:00:00 2001 From: William C Bonner Date: Tue, 1 Oct 2024 12:57:42 -0700 Subject: [PATCH] Runas goveebttemplogger (#73) * running as goveebttemplogger * modified: CMakeLists.txt modified: goveebttemplogger.service modified: postinst * modified: CMakeLists.txt modified: goveebttemplogger.service modified: postinst * modified: README.md * move all handling of old format filename to gvh-organizelogs * modified: CMakeLists.txt modified: postinst --------- Co-authored-by: William C Bonner --- CMakeLists.txt | 5 +- README.md | 23 ++-- goveebttemplogger.cpp | 30 +----- goveebttemplogger.service | 14 ++- gvh-organizelogs.cpp | 220 ++++++++++++++++++++------------------ postinst | 21 +++- postrm | 2 +- prerm | 2 +- 8 files changed, 165 insertions(+), 152 deletions(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index dd5971d..1fecdea 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -21,7 +21,7 @@ if (POLICY CMP0115) endif() project (GoveeBTTempLogger - VERSION 3.20240925.0 + VERSION 3.20241001.0 DESCRIPTION "Listen and log Govee Thermometer Bluetooth Low Energy Advertisments via BlueZ and DBus" HOMEPAGE_URL https://github.com/wcbonner/GoveeBTTempLogger ) @@ -55,7 +55,7 @@ if (CMAKE_VERSION VERSION_GREATER 3.12) endif() target_include_directories(goveebttemplogger PUBLIC - "${PROJECT_BINARY_DIR}" + "${PROJECT_BINARY_DIR}" ${EXTRA_INCLUDES} ${dbus_INCLUDE_DIRS} ) @@ -108,7 +108,6 @@ set(CPACK_DEBIAN_PACKAGE_MAINTAINER "William C Bonner <${CPACK_PACKAGE_CONTACT}> include(InstallRequiredSystemLibraries) set(CPACK_RESOURCE_FILE_LICENSE "${CMAKE_CURRENT_SOURCE_DIR}/LICENSE") set(CPACK_DEBIAN_FILE_NAME DEB-DEFAULT) -# set(CPACK_DEBIAN_PACKAGE_RELEASE ${CMAKE_PROJECT_VERSION_PATCH}) set(CPACK_DEBIAN_PACKAGE_SECTION custom) set(CPACK_DEBIAN_PACKAGE_CONTROL_EXTRA "${CMAKE_CURRENT_SOURCE_DIR}/postinst" "${CMAKE_CURRENT_SOURCE_DIR}/prerm" "${CMAKE_CURRENT_SOURCE_DIR}/postrm") set(CPACK_STRIP_FILES YES) diff --git a/README.md b/README.md index 15b51ea..9f3215f 100644 --- a/README.md +++ b/README.md @@ -78,10 +78,19 @@ cmake --build GoveeBTTempLogger/build pushd GoveeBTTempLogger/build && cpack . && popd ``` -The install package will install a systemd unit `goveebttemplogger.service` which will automatically start GoveeBTTempLogger. The service can be configured using environment variables via +The install package will creates a systemd unit `goveebttemplogger.service` which will automatically start GoveeBTTempLogger. The service can be configured via the `systemctl edit goveebttemplogger.service` command. By default, it writes logs to `/var/log/goveebttemplogger` and writes SVG files to `/var/www/html/goveebttemplogger`. -There are several `ExecStartPre` lines to confirm the directories used exist. To use different directories, both this location and the parameter on the ExecStart line need to be changed. + +The [postinst](https://github.com/wcbonner/GoveeBTTempLogger/blob/master/postinst) install routine creates a user and three directories. +It also will change the permissions on those dirctories to be owned by and writable by the newly created user. +``` +adduser --system --ingroup www-data goveebttemplogger +mkdir --verbose --mode 0755 --parents /var/log/goveebttemplogger /var/cache/goveebttemplogger /var/www/html/goveebttemplogger +chown --changes --recursive goveebttemplogger:www-data /var/log/goveebttemplogger /var/cache/goveebttemplogger /var/www/html/goveebttemplogger +chmod --changes --recursive 0644 /var/log/goveebttemplogger/* /var/cache/goveebttemplogger/* /var/www/html/goveebttemplogger/* +sudo setcap 'cap_net_raw,cap_net_admin+eip' /usr/local/bin/goveebttemplogger +``` The systemd unit file section `ExecStart` to start the service has been broken into several lines for clarity. @@ -89,17 +98,15 @@ The systemd unit file section `ExecStart` to start the service has been broken i [Service] Type=simple Restart=always -RestartSec=5 -ExecStartPre=/bin/mkdir -p /var/log/goveebttemplogger -ExecStartPre=/bin/mkdir -p /var/www/html/goveebttemplogger -ExecStartPre=/bin/mkdir -p /var/cache/goveebttemplogger +RestartSec=30 +User=goveebttemplogger +Group=www-data ExecStart=/usr/local/bin/goveebttemplogger \ --verbose 0 \ --log /var/log/goveebttemplogger \ --time 60 \ --svg /var/www/html/goveebttemplogger --battery 8 --minmax 8 \ - --cache /var/cache/goveebttemplogger \ - --download + --cache /var/cache/goveebttemplogger KillSignal=SIGINT ``` diff --git a/goveebttemplogger.cpp b/goveebttemplogger.cpp index 2038eac..5b35c23 100644 --- a/goveebttemplogger.cpp +++ b/goveebttemplogger.cpp @@ -1146,7 +1146,8 @@ std::filesystem::path GenerateLogFileName(const bdaddr_t &a, time_t timer = 0) //OutputFilename << "gvh507x_"; //OutputFilename << std::hex << std::uppercase << std::setw(2) << std::setfill('0') << int(a.b[1]); //OutputFilename << std::hex << std::uppercase << std::setw(2) << std::setfill('0') << int(a.b[0]); - OutputFilename << "gvh507x_"; + // The New Format Log File Name includes the entire Bluetooth Address, making it much easier to recognize and add to MRTG config files. + OutputFilename << "gvh-"; std::string btAddress(ba2string(a)); for (auto pos = btAddress.find(':'); pos != std::string::npos; pos = btAddress.find(':')) btAddress.erase(pos, 1); @@ -1158,34 +1159,7 @@ std::filesystem::path GenerateLogFileName(const bdaddr_t &a, time_t timer = 0) if (!((UTC.tm_year == 70) && (UTC.tm_mon == 0) && (UTC.tm_mday == 1))) OutputFilename << "-" << std::dec << UTC.tm_year + 1900 << "-" << std::setw(2) << std::setfill('0') << UTC.tm_mon + 1; OutputFilename << ".txt"; - std::filesystem::path OldFormatFileName(LogDirectory / OutputFilename.str()); - - // The New Format Log File Name includes the entire Bluetooth Address, making it much easier to recognize and add to MRTG config files. - OutputFilename.str(""); - OutputFilename << "gvh-"; - OutputFilename << btAddress; - if (!((UTC.tm_year == 70) && (UTC.tm_mon == 0) && (UTC.tm_mday == 1))) - OutputFilename << "-" << std::dec << UTC.tm_year + 1900 << "-" << std::setw(2) << std::setfill('0') << UTC.tm_mon + 1; - OutputFilename << ".txt"; std::filesystem::path NewFormatFileName(LogDirectory / OutputFilename.str()); - - // This is a temporary hack to transparently change log file name formats - std::ifstream OldFile(OldFormatFileName); - if (OldFile.is_open()) - { - OldFile.close(); - try - { - std::filesystem::rename(OldFormatFileName, NewFormatFileName); - std::cerr << "[ ] Renamed " << OldFormatFileName << " to " << NewFormatFileName << std::endl; - } - catch (const std::filesystem::filesystem_error& ia) - { - std::cerr << "[ ] " << ia.what() << std::endl; - std::cerr << "[ ] Unable to Rename " << OldFormatFileName << " to " << NewFormatFileName << std::endl; - } - } - return(NewFormatFileName); } bool GenerateLogFile(std::map> &AddressTemperatureMap, std::map &PersistenceData) diff --git a/goveebttemplogger.service b/goveebttemplogger.service index 9d32c36..28df161 100644 --- a/goveebttemplogger.service +++ b/goveebttemplogger.service @@ -1,4 +1,5 @@ # Contents of /etc/systemd/system/goveebttemplogger.service + [Unit] Description=GoveeBTTempLogger service After=bluetooth.target dbus-org.bluez.service network-online.target @@ -6,13 +7,20 @@ Requires=bluetooth.target StartLimitBurst=5 StartLimitIntervalSec=33 +# The user and directories are created by the postinst script with the following commands: +# adduser --system --ingroup www-data goveebttemplogger +# mkdir --verbose --mode 0755 --parents /var/log/goveebttemplogger /var/cache/goveebttemplogger /var/www/html/goveebttemplogger +# chown --changes --recursive goveebttemplogger:www-data /var/log/goveebttemplogger /var/cache/goveebttemplogger /var/www/html/goveebttemplogger +# chmod --changes --recursive 0644 /var/log/goveebttemplogger/* /var/cache/goveebttemplogger/* /var/www/html/goveebttemplogger/* +# +# They are run as root during the installation of the service. The service runs as the newly crteated user. + [Service] Type=simple Restart=always RestartSec=30 -ExecStartPre=/bin/mkdir --parents /var/log/goveebttemplogger -ExecStartPre=/bin/mkdir --parents /var/www/html/goveebttemplogger -ExecStartPre=/bin/mkdir --parents /var/cache/goveebttemplogger +User=goveebttemplogger +Group=www-data ExecStart=/usr/local/bin/goveebttemplogger \ --verbose 0 \ --log /var/log/goveebttemplogger \ diff --git a/gvh-organizelogs.cpp b/gvh-organizelogs.cpp index 68225c9..024b3f9 100644 --- a/gvh-organizelogs.cpp +++ b/gvh-organizelogs.cpp @@ -158,8 +158,39 @@ bool operator ==(const bdaddr_t& a, const bdaddr_t& b) return(A == B); } ///////////////////////////////////////////////////////////////////////////// -std::string ba2string(const bdaddr_t& a) { char addr_str[18]; ba2str(&a, addr_str); std::string rVal(addr_str); return(rVal); } -bdaddr_t string2ba(const std::string& a) { std::string ssBTAddress(a);if (ssBTAddress.length() == 12)for (auto index = ssBTAddress.length() - 2; index > 0; index -= 2)ssBTAddress.insert(index, ":");bdaddr_t TheBlueToothAddress({ 0 });if (ssBTAddress.length() == 17)str2ba(ssBTAddress.c_str(), &TheBlueToothAddress);return(TheBlueToothAddress); } +std::string ba2string(const bdaddr_t& TheBlueToothAddress) +{ + std::ostringstream oss; + for (auto i = 5; i >= 0; i--) + { + oss << std::hex << std::uppercase << std::setw(2) << std::setfill('0') << static_cast(TheBlueToothAddress.b[i]); + if (i > 0) + oss << ":"; + } + return (oss.str()); +} +const std::regex BluetoothAddressRegex("((([[:xdigit:]]{2}:){5}))[[:xdigit:]]{2}"); +bdaddr_t string2ba(const std::string& TheBlueToothAddressString) +{ + bdaddr_t TheBlueToothAddress({ 0 }); + if (TheBlueToothAddressString.length() == 12) + { + std::string ssBTAddress(TheBlueToothAddressString); + for (auto index = ssBTAddress.length() - 2; index > 0; index -= 2) + ssBTAddress.insert(index, ":"); + TheBlueToothAddress = string2ba(ssBTAddress); // Recursive call + } + else if (std::regex_match(TheBlueToothAddressString, BluetoothAddressRegex)) + { + std::stringstream ss(TheBlueToothAddressString); + std::string byteString; + int index(5); + // Because I've verified the string format with regex I can safely run this loop knowing it'll get 6 bytes + while (std::getline(ss, byteString, ':')) + TheBlueToothAddress.b[index--] = std::stoi(byteString, nullptr, 16); + } + return(TheBlueToothAddress); +} ///////////////////////////////////////////////////////////////////////////// // Create a standardized logfile name for this program based on a Bluetooth address and the global parameter of the log file directory. std::filesystem::path GenerateLogFileName(const bdaddr_t& a, time_t timer = 0) @@ -171,7 +202,8 @@ std::filesystem::path GenerateLogFileName(const bdaddr_t& a, time_t timer = 0) //OutputFilename << "gvh507x_"; //OutputFilename << std::hex << std::uppercase << std::setw(2) << std::setfill('0') << int(a.b[1]); //OutputFilename << std::hex << std::uppercase << std::setw(2) << std::setfill('0') << int(a.b[0]); - OutputFilename << "gvh507x_"; + // The New Format Log File Name includes the entire Bluetooth Address, making it much easier to recognize and add to MRTG config files. + OutputFilename << "gvh-"; std::string btAddress(ba2string(a)); for (auto pos = btAddress.find(':'); pos != std::string::npos; pos = btAddress.find(':')) btAddress.erase(pos, 1); @@ -183,34 +215,7 @@ std::filesystem::path GenerateLogFileName(const bdaddr_t& a, time_t timer = 0) if (!((UTC.tm_year == 70) && (UTC.tm_mon == 0) && (UTC.tm_mday == 1))) OutputFilename << "-" << std::dec << UTC.tm_year + 1900 << "-" << std::setw(2) << std::setfill('0') << UTC.tm_mon + 1; OutputFilename << ".txt"; - std::filesystem::path OldFormatFileName(LogDirectory / OutputFilename.str()); - - // The New Format Log File Name includes the entire Bluetooth Address, making it much easier to recognize and add to MRTG config files. - OutputFilename.str(""); - OutputFilename << "gvh-"; - OutputFilename << btAddress; - if (!((UTC.tm_year == 70) && (UTC.tm_mon == 0) && (UTC.tm_mday == 1))) - OutputFilename << "-" << std::dec << UTC.tm_year + 1900 << "-" << std::setw(2) << std::setfill('0') << UTC.tm_mon + 1; - OutputFilename << ".txt"; std::filesystem::path NewFormatFileName(LogDirectory / OutputFilename.str()); - - // This is a temporary hack to transparently change log file name formats - std::ifstream OldFile(OldFormatFileName); - if (OldFile.is_open()) - { - OldFile.close(); - try - { - std::filesystem::rename(OldFormatFileName, NewFormatFileName); - std::cerr << "[ ] Renamed " << OldFormatFileName << " to " << NewFormatFileName << std::endl; - } - catch (const std::filesystem::filesystem_error& ia) - { - std::cerr << "[ ] " << ia.what() << std::endl; - std::cerr << "[ ] Unable to Rename " << OldFormatFileName << " to " << NewFormatFileName << std::endl; - } - } - return(NewFormatFileName); } ///////////////////////////////////////////////////////////////////////////// @@ -279,7 +284,7 @@ int main(int argc, char** argv) usage(argc, argv); else { - const std::regex LogFileRegex("gvh-[[:xdigit:]]{12}-[[:digit:]]{4}-[[:digit:]]{2}.txt"); + const std::regex LogFileRegex("(gvh507x_|gvh-)[[:xdigit:]]{12}-[[:digit:]]{4}-[[:digit:]]{2}.txt"); // 2024-10-01 Both old and new format recognized std::deque files; for (auto const& dir_entry : std::filesystem::directory_iterator{ LogDirectory }) if (dir_entry.is_regular_file()) @@ -300,98 +305,101 @@ int main(int argc, char** argv) { std::deque DataLines; std::cout << "[" << getTimeISO8601() << "] Reading: " << FQFileName; - int count(0); - std::string ssBTAddress; - if (FQFileName.stem().string().find("gvh-") != std::string::npos) - ssBTAddress = FQFileName.stem().string().substr(4, 12); // new filename format (2023-04-03) - bdaddr_t TheBlueToothAddress(string2ba(ssBTAddress)); - // Read All Data From Existing Log, removing nulls if possible - std::string TheLine; - while (std::getline(TheFile, TheLine)) - { - // erase any nulls from the data. these are occasionally in the log file when the platform crashed during a write to the logfile. - for (auto pos = TheLine.find('\000'); pos != std::string::npos; pos = TheLine.find('\000')) - TheLine.erase(pos); - DataLines.push_back(TheLine); - count++; - } - std::cout << " (" << count << " lines)"; - TheFile.close(); - // Rename Existing Log to backup. - bool bBackedUp = false; - try - { - std::filesystem::rename(FQFileName, BackupName); - std::cout << " Renamed " << FQFileName << " to " << BackupName << std::endl; - bBackedUp = true; - } - catch (const std::filesystem::filesystem_error& ia) + const std::regex ModifiedBluetoothAddressRegex("[[:xdigit:]]{12}"); + std::smatch BluetoothAddressInFilename; + std::string Stem(FQFileName.stem()); + if (std::regex_search(Stem, BluetoothAddressInFilename, ModifiedBluetoothAddressRegex)) { - std::cout << " " << ia.what() << std::endl; - std::cout << " Unable to Rename " << FQFileName << " to " << BackupName << std::endl; - } + bdaddr_t TheBlueToothAddress(string2ba(BluetoothAddressInFilename.str())); + int count(0); + // Read All Data From Existing Log, removing nulls if possible + std::string TheLine; + while (std::getline(TheFile, TheLine)) + { + // erase any nulls from the data. these are occasionally in the log file when the platform crashed during a write to the logfile. + for (auto pos = TheLine.find('\000'); pos != std::string::npos; pos = TheLine.find('\000')) + TheLine.erase(pos); + DataLines.push_back(TheLine); + count++; + } + std::cout << " (" << count << " lines)"; + TheFile.close(); + // Rename Existing Log to backup. + bool bBackedUp = false; + try + { + std::filesystem::rename(FQFileName, BackupName); + std::cout << " Renamed " << FQFileName << " to " << BackupName << std::endl; + bBackedUp = true; + } + catch (const std::filesystem::filesystem_error& ia) + { + std::cout << " " << ia.what() << std::endl; + std::cout << " Unable to Rename " << FQFileName << " to " << BackupName << std::endl; + } - if (bBackedUp) - { - // Sort all Data - sort(DataLines.begin(), DataLines.end()); - // Distribute Data to appropriate new Log Files - std::ofstream LogFile; - time_t TheTime(0), LastTime(0); - int LastYear(0), LastMonth(0); - std::filesystem::path LastFileName; - std::string LastLine; - while (!DataLines.empty()) + if (bBackedUp) { - if (DataLines.begin()->length() > 18) // line is longer than an ISO8601 String + // Sort all Data + sort(DataLines.begin(), DataLines.end()); + // Distribute Data to appropriate new Log Files + std::ofstream LogFile; + time_t TheTime(0), LastTime(0); + int LastYear(0), LastMonth(0); + std::filesystem::path LastFileName; + std::string LastLine; + while (!DataLines.empty()) { - if (0 != LastLine.compare(*DataLines.begin())) // line is unique + if (DataLines.begin()->length() > 18) // line is longer than an ISO8601 String { - TheTime = ISO8601totime(*DataLines.begin()); - struct tm UTC; - if (nullptr != gmtime_r(&TheTime, &UTC)) + if (0 != LastLine.compare(*DataLines.begin())) // line is unique { - if ((UTC.tm_year != LastYear) || (UTC.tm_mon != LastMonth)) + TheTime = ISO8601totime(*DataLines.begin()); + struct tm UTC; + if (nullptr != gmtime_r(&TheTime, &UTC)) { - LastYear = UTC.tm_year; - LastMonth = UTC.tm_mon; - if (LogFile.is_open()) + if ((UTC.tm_year != LastYear) || (UTC.tm_mon != LastMonth)) { - std::cout << " (" << count << " lines)" << std::endl; - LogFile.close(); - if (!LastFileName.empty()) + LastYear = UTC.tm_year; + LastMonth = UTC.tm_mon; + if (LogFile.is_open()) { - struct utimbuf ut; - ut.actime = LastTime; - ut.modtime = LastTime; - utime(LastFileName.c_str(), &ut); + std::cout << " (" << count << " lines)" << std::endl; + LogFile.close(); + if (!LastFileName.empty()) + { + struct utimbuf ut; + ut.actime = LastTime; + ut.modtime = LastTime; + utime(LastFileName.c_str(), &ut); + } } + LastFileName = GenerateLogFileName(TheBlueToothAddress, TheTime); + LogFile.open(LastFileName, std::ios_base::out | std::ios_base::app); + std::cout << "[" << getTimeISO8601() << "] Writing: " << LastFileName; + count = 0; } - LastFileName = GenerateLogFileName(TheBlueToothAddress, TheTime); - LogFile.open(LastFileName, std::ios_base::out | std::ios_base::app); - std::cout << "[" << getTimeISO8601() << "] Writing: " << LastFileName; - count = 0; + LogFile << *DataLines.begin() << std::endl; + LastTime = TheTime; + count++; } - LogFile << *DataLines.begin() << std::endl; - LastTime = TheTime; - count++; + LastLine = *DataLines.begin(); } - LastLine = *DataLines.begin(); } + DataLines.pop_front(); + } + std::cout << " (" << count << " lines)" << std::endl; + LogFile.close(); + if (!LastFileName.empty()) + { + struct utimbuf ut; + ut.actime = LastTime; + ut.modtime = LastTime; + utime(LastFileName.c_str(), &ut); } - DataLines.pop_front(); - } - std::cout << " (" << count << " lines)" << std::endl; - LogFile.close(); - if (!LastFileName.empty()) - { - struct utimbuf ut; - ut.actime = LastTime; - ut.modtime = LastTime; - utime(LastFileName.c_str(), &ut); } + files.pop_front(); } - files.pop_front(); } } } diff --git a/postinst b/postinst index bd2478d..b0bb3f6 100755 --- a/postinst +++ b/postinst @@ -1,9 +1,26 @@ #!/bin/sh # POSTINST script for goveebttemplogger -echo "\033[36m HI I'M A POSTINST SCRIPT `date +"%s"` \033[39m" +#simple function to deal with multiple files existing and glob +exists() { + [ -e "$1" ] +} + +echo "\033[36m HI I'M A POSTINST SCRIPT `date --rfc-3339='seconds'` running as \033[91m`whoami`\033[39m" +adduser --system --ingroup www-data goveebttemplogger +mkdir --verbose --mode 0755 --parents /var/log/goveebttemplogger /var/cache/goveebttemplogger /var/www/html/goveebttemplogger +chown --changes --recursive goveebttemplogger:www-data /var/log/goveebttemplogger /var/cache/goveebttemplogger /var/www/html/goveebttemplogger +chmod --changes --recursive 0644 /var/log/goveebttemplogger/* /var/cache/goveebttemplogger/* /var/www/html/goveebttemplogger/* +sudo setcap 'cap_net_raw,cap_net_admin+eip' /usr/local/bin/goveebttemplogger +# the next line doesn't handle multiple files existing, I created the function above that handles multiple files +# if [ -f /var/log/goveebttemplogger/gvh507x_*.txt ]; then +if exists /var/log/goveebttemplogger/gvh507x_*.txt; then + mkdir --verbose --mode 0755 --parents /var/log/goveebttemplogger/backup + /usr/local/bin/gvh-organizelogs --log /var/log/goveebttemplogger/ --backup /var/log/goveebttemplogger/backup/ + chown --recursive goveebttemplogger:www-data /var/log/goveebttemplogger +fi systemctl daemon-reload systemctl enable goveebttemplogger.service systemctl start goveebttemplogger.service -exit 0 \ No newline at end of file +exit 0 diff --git a/postrm b/postrm index e5296fc..15095f9 100755 --- a/postrm +++ b/postrm @@ -1,7 +1,7 @@ #!/bin/sh # POSTRM script for goveebttemplogger -echo "\033[36m HI I'M A POSTRM SCRIPT `date +"%s"` \033[39m" +echo "\033[36m HI I'M A POSTRM SCRIPT `date --rfc-3339='seconds'` running as \033[91m`whoami`\033[39m" systemctl daemon-reload exit 0 \ No newline at end of file diff --git a/prerm b/prerm index e143f5c..55463e3 100755 --- a/prerm +++ b/prerm @@ -1,7 +1,7 @@ #!/bin/sh # PRERM script for goveebttemplogger -echo "\033[36m HI I'M A PRERM SCRIPT `date +"%s"` \033[39m" +echo "\033[36m HI I'M A PRERM SCRIPT `date --rfc-3339='seconds'` running as \033[91m`whoami`\033[39m" systemctl stop goveebttemplogger.service systemctl disable goveebttemplogger.service