diff --git a/.vscode/launch.json b/.vscode/launch.json index ad4204b..f75aadd 100644 --- a/.vscode/launch.json +++ b/.vscode/launch.json @@ -66,6 +66,32 @@ } ] }, + { + "name": "(gdb) makeRelease", + "type": "cppdbg", + "request": "launch", + // Resolved by CMake Tools: + "program": "${command:cmake.launchTargetPath}", + "args": [ ], + "stopAtEntry": false, + "cwd": "${workspaceFolder}/../..", + "environment": [ + { + "name": "PATH", + "value": "$PATH:${command:cmake.launchTargetDirectory}" + } + ], + "externalConsole": true, + "MIMode": "gdb", + "setupCommands": [ + { + "description": "Enable pretty-printing for gdb", + "text": "-enable-pretty-printing", + "ignoreFailures": true + } + ] + }, + { "name": "(gdb) pipedal_nm_p2pd", "type": "cppdbg", diff --git a/CMakeLists.txt b/CMakeLists.txt index dc5b55d..64bdd88 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -4,7 +4,7 @@ project(pipedal DESCRIPTION "PiPedal Guitar Effect Pedal For Raspberry Pi" HOMEPAGE_URL "https://rerdavies.github.io/pipedal" ) -set (DISPLAY_VERSION "PiPedal v1.2.49-Release") +set (DISPLAY_VERSION "PiPedal v1.2.49-Development") set (PACKAGE_ARCHITECTURE "arm64") set (CMAKE_INSTALL_PREFIX "/usr/") @@ -22,7 +22,6 @@ add_subdirectory("src") # Replaced with hotspot. #add_subdirectory("NetworkManagerP2P") - # install (TARGETS pipedal_p2pd DESTINATION ${CMAKE_INSTALL_PREFIX}/sbin # ) @@ -56,7 +55,7 @@ install( #) install ( - FILES ${PROJECT_SOURCE_DIR}/src/polkit-1/rules/55-pipedal-networkmanager.rule DESTINATION /etc/polkit-1/rules.d/ + FILES ${PROJECT_SOURCE_DIR}/src/polkit-1/rules/10-pipedal-networkmanager.rules DESTINATION /etc/polkit-1/rules.d/ ) install ( DIRECTORY ${REACT_BUILD_DIRECTORY} DESTINATION /etc/pipedal/react diff --git a/react/public/var/config.json b/react/public/var/config.json index f808ca9..5711fff 100644 --- a/react/public/var/config.json +++ b/react/public/var/config.json @@ -4,5 +4,6 @@ "debug": true, "max_upload_size": 536870912, "fakeAndroid": false, - "ui_plugins": [] + "ui_plugins": [], + "enable_auto_update": true } \ No newline at end of file diff --git a/signPackage.sh b/signPackage.sh index 04fef7f..c26d857 100755 --- a/signPackage.sh +++ b/signPackage.sh @@ -1,10 +1,14 @@ #!/bin/bash - -#sign a package with rerdavies@gmail.com's private key. -cd build -for filename in *.deb; do - echo gpg --armor --output "$filename".asc -b "$filename" - - gpg --armor --output "$filename".asc -b "$filename" -done -cd .. \ No newline at end of file +# Sign the built .deb package with the private key used in the Auto-update process. +# +# Won't work for you, because you don't have MY private key. +# +# Auto-update requires that .deb files be distributed from the GitHub rerdavies/pipedal project. +# And auto-updates WILL update installs of your forked binary unless you take steps to prevent that. +# +# If you are distributing a fork, you should see the file UpdaterSecurity.hpp for details on how to +# modify pipedal to perform updates from an alternate github site. Or you could modify your fork +# to NOT do auto-updating (probably best). The build doesn't currently have a procedure for +# knocking out Auto-Update from UpdaterSecurity.hpp. But I would happily accept a pull request +# to bring such a feature back into mainline if you do it. +build/src/makeRelease diff --git a/src/AvahiService.cpp b/src/AvahiService.cpp index e0eefbf..026445a 100644 --- a/src/AvahiService.cpp +++ b/src/AvahiService.cpp @@ -45,6 +45,12 @@ void AvahiService::Announce( const std::string &mdnsName, bool addTestGroup) { + // We should update an existing group if the name doesn't change. + // but in practice, the name is the only thing that's going to change without restarting the server. + if (this->name && strcmp(name.c_str(),this->name) == 0) + { + return; + } Unannounce(); this->portNumber = portNumber; @@ -162,7 +168,7 @@ void AvahiService::create_group(AvahiClient *c) if ((ret = avahi_entry_group_add_service( group, AVAHI_IF_UNSPEC, - AVAHI_PROTO_UNSPEC, // IPv4 for now, until we figure out why dnsqmasq borks IPv6 routing.) + AVAHI_PROTO_INET, // IPv4 for now, until we figure out why dnsqmasq borks IPv6 routing.) (AvahiPublishFlags)0, name, PIPEDAL_SERVICE_TYPE, @@ -307,41 +313,32 @@ void AvahiService::Start() while (true) { /* Allocate main loop object */ - if (!(this->threadedPoll = avahi_threaded_poll_new())) + if (!this->threadedPoll) { - Lv2Log::error("Failed to create Avahi poll object."); - goto fail; + if (!(this->threadedPoll = avahi_threaded_poll_new())) + { + Lv2Log::error("Failed to create Avahi poll object."); + goto fail; + } + avahi_threaded_poll_start(threadedPoll); } - /* Allocate a new client */ - client = avahi_client_new(avahi_threaded_poll_get(threadedPoll), AvahiClientFlags::AVAHI_CLIENT_NO_FAIL, client_callback, (void *)this, &error); - /* Check wether creating the client object succeeded */ if (!client) { - Lv2Log::error(SS("Failed to create client: " << avahi_strerror(error))); - goto fail; + /* Allocate a new client */ + client = avahi_client_new(avahi_threaded_poll_get(threadedPoll), AvahiClientFlags::AVAHI_CLIENT_NO_FAIL, client_callback, (void *)this, &error); + /* Check wether creating the client object succeeded */ + if (!client) + { + Lv2Log::error(SS("Failed to create client: " << avahi_strerror(error))); + goto fail; + } } - avahi_threaded_poll_start(threadedPoll); ret = 0; return; fail: - /* Cleanup things */ - if (client) - { - avahi_client_free(client); - client = nullptr; - } - if (group) - { - avahi_entry_group_reset(group); - group = nullptr; - } - if (threadedPoll) - { - avahi_threaded_poll_free(threadedPoll); - threadedPoll = nullptr; - } + Stop(); } return; } @@ -349,27 +346,23 @@ void AvahiService::Start() void AvahiService::Stop() { - if (threadedPoll) + /* Cleanup things */ + if (group) { - avahi_threaded_poll_stop(threadedPoll); + avahi_entry_group_reset(group); + avahi_entry_group_free(group); + group = nullptr; } - /* Cleanup things */ - // client owns the group? - // if (group) - // { - // avahi_entry_group_reset(group); - // group = nullptr; - // }` - if (client) { avahi_client_free(client); client = nullptr; } + if (threadedPoll) { + avahi_threaded_poll_stop(threadedPoll); avahi_threaded_poll_free(threadedPoll); threadedPoll = nullptr; } - group = nullptr; } diff --git a/src/AvahiService.hpp b/src/AvahiService.hpp index e02db9e..60dafff 100644 --- a/src/AvahiService.hpp +++ b/src/AvahiService.hpp @@ -43,7 +43,6 @@ namespace pipedal { int portNumber, const std::string &name, const std::string &instanceId, const std::string&mdnsName,bool addTestGroup = false); void Unannounce(); - private: void Start(); void Stop(); @@ -56,8 +55,6 @@ namespace pipedal { void create_group(AvahiClient *c); - void threadProc(); - int clientErrno = 0; int portNumber = -1; diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt index d8abe0f..7841428 100644 --- a/src/CMakeLists.txt +++ b/src/CMakeLists.txt @@ -301,6 +301,9 @@ add_executable(hotspotManagerTest target_link_libraries(hotspotManagerTest PRIVATE ${PIPEDAL_LIBS}) +set_target_properties(hotspotManagerTest PROPERTIES EXCLUDE_FROM_ALL true) + + add_executable(pipedaltest testMain.cpp @@ -329,6 +332,8 @@ target_link_libraries(pipedaltest PRIVATE ${PIPEDAL_LIBS}) target_include_directories(pipedaltest PRIVATE ${PIPEDAL_INCLUDES} ) +set_target_properties(pipedaltest PROPERTIES EXCLUDE_FROM_ALL true) + if (NOT GITHUB_ACTIONS) # Google perftools are not available on Github action servers. @@ -339,6 +344,9 @@ if (NOT GITHUB_ACTIONS) # Google perftools are not available on Github action se target_include_directories(profilePlugin PRIVATE ${PIPEDAL_INCLUDES} ) + + set_target_properties(profilePlugin PROPERTIES EXCLUDE_FROM_ALL true) + endif() add_executable(jsonTest testMain.cpp @@ -356,7 +364,12 @@ target_link_libraries(jsonTest PRIVATE PiPedalCommon) target_include_directories(jsonTest PRIVATE ${PIPEDAL_INCLUDES} ) +set_target_properties(jsonTest PROPERTIES EXCLUDE_FROM_ALL true) + + target_link_libraries(jsonTest PRIVATE pthread) + + add_test(NAME jsonTest COMMAND jsonTest) @@ -561,18 +574,18 @@ if(ENABLE_VST3) ) - add_executable(vst3test Vst3test.cpp + # add_executable(vst3test Vst3test.cpp - MemDebug.cpp - MemDebug.hpp + # MemDebug.cpp + # MemDebug.hpp - Vst3SdkRepro.cpp + # Vst3SdkRepro.cpp - ${VST3_FILES} + # ${VST3_FILES} - #vst3/Vst3PresetFile.hpp Vst3PresetFile.cpp - asan_options.cpp - ) + # #vst3/Vst3PresetFile.hpp Vst3PresetFile.cpp + # asan_options.cpp + # ) target_include_directories(vst3test PRIVATE ${PIPEDAL_INCLUDES} ) @@ -642,6 +655,17 @@ target_include_directories(capturepresets PRIVATE ${PIPEDAL_INCLUDES}) target_link_libraries(capturepresets ${PIPEDAL_LIBS}) +add_executable(makeRelease + makeReleaseMain.cpp + CommandLineParser.hpp +) +target_include_directories(makeRelease PRIVATE ${PIPEDAL_INCLUDES}) + +target_link_libraries(makeRelease ${PIPEDAL_LIBS}) + + + + add_executable(pipedal_update UpdateMain.cpp diff --git a/src/HotspotManager.cpp b/src/HotspotManager.cpp index 1d0612c..c680422 100644 --- a/src/HotspotManager.cpp +++ b/src/HotspotManager.cpp @@ -66,7 +66,6 @@ namespace pipedal::impl { Initial, WaitingForNetworkManager, - Disabled, Monitoring, HotspotConnecting, HotspotConnected, @@ -82,7 +81,6 @@ namespace pipedal::impl void onDevicesChanged(); void onDisconnect(); void onReload(); - void onDisableHotspot(); void DisableHotspot(); void EnableHotspot(); @@ -169,7 +167,7 @@ void HotspotManagerImpl::Open() void HotspotManagerImpl::onClose() { - onDisableHotspot(); + StopHotspot(); ReleaseNetworkManager(); Lv2Log::debug("HotspotManager: state=Closed"); @@ -201,14 +199,7 @@ void HotspotManagerImpl::onInitialize() { wifiConfigSettings.Load(); - if (wifiConfigSettings.valid_ && wifiConfigSettings.IsEnabled()) - { - onStartMonitoring(); - } - else - { - onDisableHotspot(); - } + onStartMonitoring(); } catch (const std::exception &e) { @@ -431,17 +422,6 @@ void HotspotManagerImpl::WaitForNetworkManager() ReleaseNetworkManager(); StartWaitForNetworkManagerTimer(); } -void HotspotManagerImpl::onDisableHotspot() -{ - if (this->state != State::Disabled) - { - ReleaseNetworkManager(); - - Lv2Log::debug("HotspotManager: state=Disabled"); - SetState(State::Disabled); - Lv2Log::info("HotspotManager: Hotspot disabled."); - } -} void HotspotManagerImpl::onReload() { if (closed) @@ -453,17 +433,12 @@ void HotspotManagerImpl::onReload() switch (state) { case State::Initial: + case State::Error: // ignore. return; - case State::Disabled: - if (wifiConfigSettings.valid_ && wifiConfigSettings.IsEnabled()) - { - onStartMonitoring(); - } + default: + MaybeStartHotspot(); return; - default: - this->onDisableHotspot(); - break; } } @@ -726,10 +701,12 @@ static std::vector> GetAccessPointSsids(std::vectorstate == State::Error) return; + if (this->closed) return; if (!wlanDevice || !wlanWirelessDevice) { // devices are transitioning. Do nothing. + return; } std::vector allAccessPoints = GetAllAccessPoints(); std::vector> allAccessPointSsids = GetAccessPointSsids(allAccessPoints); @@ -787,6 +764,7 @@ Connection::ptr HotspotManagerImpl::FindExistingConnection() } } } + return nullptr; } diff --git a/src/PiPedalModel.cpp b/src/PiPedalModel.cpp index 22f8ed7..358d7b7 100644 --- a/src/PiPedalModel.cpp +++ b/src/PiPedalModel.cpp @@ -71,6 +71,7 @@ PiPedalModel::PiPedalModel() atomConverter(pluginHost.GetMapFeature()) { this->updater = Updater::Create(); + this->updater->Start(); this->currentUpdateStatus = updater->GetCurrentStatus(); this->pedalboard = Pedalboard::MakeDefault(); #if JACK_HOST @@ -980,6 +981,7 @@ void PiPedalModel::SetWifiConfigSettings(const WifiConfigSettings &wifiConfigSet { this->hotspotManager->Reload(); } + this->UpdateDnsSd(); } #else this->storage.SetWifiConfigSettings(wifiConfigSettings); @@ -1020,7 +1022,7 @@ static std::string GetP2pdName() void PiPedalModel::UpdateDnsSd() { - avahiService.Unannounce(); + // avahiService.Unannounce(); let Announce decide whether it wants to unannounce or update. ServiceConfiguration deviceIdFile; deviceIdFile.Load(); diff --git a/src/Updater.cpp b/src/Updater.cpp index 3511436..d4a9037 100644 --- a/src/Updater.cpp +++ b/src/Updater.cpp @@ -48,7 +48,8 @@ namespace fs = std::filesystem; #undef TEST_UPDATE // do NOT leat this leak into a production build! #endif -namespace pipedal { +namespace pipedal +{ class GithubResponseHeaders { public: @@ -62,8 +63,8 @@ namespace pipedal { uint64_t ratelimit_used_ = 0; std::string ratelimit_resource_; bool limit_exceeded() const { return code_ != 200 && ratelimit_limit_ != 0 && ratelimit_limit_ == ratelimit_used_; } - void Load(); - void Save(); + void Load(const std::filesystem::path &filename); + void Save(const std::filesystem::path &filename); DECLARE_JSON_MAP(GithubResponseHeaders); private: @@ -74,24 +75,37 @@ namespace pipedal { class pipedal::UpdaterImpl : public Updater { public: - UpdaterImpl(); + UpdaterImpl(const std::filesystem::path &workingDirectory); + UpdaterImpl() : UpdaterImpl("/var/pipedal/updates") {} + virtual ~UpdaterImpl() noexcept; virtual void SetUpdateListener(UpdateListener &&listener) override; + virtual void Start() override; + virtual void Stop() override; + virtual void CheckNow() override; - virtual void Stop(); virtual UpdatePolicyT GetUpdatePolicy() override; virtual void SetUpdatePolicy(UpdatePolicyT updatePolicy) override; virtual void ForceUpdateCheck() override; virtual void DownloadUpdate(const std::string &url, std::filesystem::path *file, std::filesystem::path *signatureFile) override; virtual UpdateStatus GetCurrentStatus() const override { return this->currentResult; } + virtual UpdateStatus GetReleaseGeneratorStatus() override; private: + std::filesystem::path workingDirectory; + std::filesystem::path updateStatusCacheFile; + std::filesystem::path githubResponseHeaderFilename; + GithubResponseHeaders githubResponseHeaders; using clock = std::chrono::steady_clock; + void SetCachedUpdateStatus(UpdateStatus &updateStatus); + + UpdateStatus GetCachedUpdateStatus(); + void RetryAfter(clock::duration delay); template @@ -120,6 +134,7 @@ private: int event_reader = -1; int event_writer = -1; void ThreadProc(); + UpdateStatus DoUpdate(bool forReleaseGenerator); void CheckForUpdate(bool useCache); UpdateListener listener; @@ -135,26 +150,27 @@ Updater::ptr Updater::Create() { return std::make_unique(); } +Updater::ptr Updater::Create(const std::filesystem::path &workingDirectory) +{ + return std::make_unique(workingDirectory); +} static constexpr uint64_t CLOSE_EVENT = 0; static constexpr uint64_t CHECK_NOW_EVENT = 1; static constexpr uint64_t UNCACHED_CHECK_NOW_EVENT = 2; -static std::filesystem::path WORKING_DIRECTORY = "/var/pipedal/updates"; -static std::filesystem::path UPDATE_STATUS_CACHE_FILE = WORKING_DIRECTORY / "updateStatus.json"; - UpdaterImpl::clock::duration UpdaterImpl::updateRate = std::chrono::duration_cast(std::chrono::days(1)); static std::chrono::system_clock::duration CACHE_DURATION = std::chrono::duration_cast(std::chrono::minutes(30)); std::mutex cacheMutex; -static UpdateStatus GetCachedUpdateStatus() +UpdateStatus UpdaterImpl::GetCachedUpdateStatus() { std::lock_guard lock{cacheMutex}; try { - if (std::filesystem::exists(UPDATE_STATUS_CACHE_FILE)) + if (std::filesystem::exists(updateStatusCacheFile)) { - std::ifstream f{UPDATE_STATUS_CACHE_FILE}; + std::ifstream f{updateStatusCacheFile}; if (f.is_open()) { json_reader reader(f); @@ -174,13 +190,13 @@ static UpdateStatus GetCachedUpdateStatus() return UpdateStatus(); } -static void SetCachedUpdateStatus(UpdateStatus &updateStatus) +void UpdaterImpl::SetCachedUpdateStatus(UpdateStatus &updateStatus) { std::lock_guard lock{cacheMutex}; updateStatus.LastUpdateTime(std::chrono::system_clock::now()); try { - pipedal::ofstream_synced f{UPDATE_STATUS_CACHE_FILE}; + pipedal::ofstream_synced f{updateStatusCacheFile}; json_writer writer{f}; writer.write(updateStatus); } @@ -189,13 +205,22 @@ static void SetCachedUpdateStatus(UpdateStatus &updateStatus) Lv2Log::error(SS("Unable to write cached UpdateStatus. " << e.what())); } } -UpdaterImpl::UpdaterImpl() + +UpdaterImpl::UpdaterImpl(const std::filesystem::path &workingDirectory) + : workingDirectory(workingDirectory), + updateStatusCacheFile(workingDirectory / "updateStatus.json"), + githubResponseHeaderFilename(workingDirectory / "githubHeaders.json") { - this->githubResponseHeaders.Load(); + this->githubResponseHeaders.Load(githubResponseHeaderFilename); cachedUpdateStatus = GetCachedUpdateStatus(); this->updatePolicy = cachedUpdateStatus.UpdatePolicy(); currentResult = cachedUpdateStatus; - +} +void UpdaterImpl::Start() +{ + #if !ENABLE_AUTO_UPDATE + return; + #endif int fds[2]; int rc = pipe(fds); if (rc != 0) @@ -225,6 +250,9 @@ void UpdaterImpl::Stop() { return; } + #if !ENABLE_AUTO_UPDATE + return; + #endif stopped = true; if (event_writer != -1) @@ -563,8 +591,7 @@ JSON_MAP_REFERENCE(GithubResponseHeaders, ratelimit_used) JSON_MAP_REFERENCE(GithubResponseHeaders, ratelimit_resource) JSON_MAP_END(); -std::filesystem::path GithubResponseHeaders::FILENAME = WORKING_DIRECTORY / "githubHeaders.json"; -void GithubResponseHeaders::Load() +void GithubResponseHeaders::Load(const std::filesystem::path &FILENAME) { std::ifstream f(FILENAME); if (f.is_open()) @@ -581,9 +608,9 @@ void GithubResponseHeaders::Load() } } } -void GithubResponseHeaders::Save() +void GithubResponseHeaders::Save(const std::filesystem::path &FILENAME) { - std::filesystem::create_directories(WORKING_DIRECTORY); + std::filesystem::create_directories(FILENAME.parent_path()); ofstream_synced f(FILENAME); if (!f.is_open()) { @@ -622,7 +649,7 @@ GithubResponseHeaders::GithubResponseHeaders(const std::filesystem::path path) auto trimPos = line.find_last_not_of("\r\n"); if (trimPos != std::string::npos) { - line = line.substr(0,trimPos + 1); + line = line.substr(0, trimPos + 1); } if (!f) @@ -675,6 +702,12 @@ GithubResponseHeaders::GithubResponseHeaders(const std::filesystem::path path) } } +UpdateStatus UpdaterImpl::GetReleaseGeneratorStatus() +{ + UpdateStatus result = DoUpdate(true); + return result; +} + void UpdaterImpl::CheckForUpdate(bool useCache) { UpdateStatus updateResult; @@ -702,133 +735,11 @@ void UpdaterImpl::CheckForUpdate(bool useCache) } } - // set default next retry time. github failures may postpone even further. - - Lv2Log::info("Checking for updates."); - // const std::string responseOption = "-w \"%{response_code}\""; -#ifdef WIN32 - responseOption = "-w \"%%{response_code}\""; // windows shell requires doubling of the %%. -#endif - - TemporaryFile headerFile{WORKING_DIRECTORY}; - std::string args = SS("-s -L " << GITHUB_RELEASES_URL << " -D " << headerFile.str()); - - updateResult.errorMessage_ = ""; try { - auto result = sysExecForOutput("curl", args); - if (result.exitCode != EXIT_SUCCESS) - { - RetryAfter(std::chrono::duration_cast(std::chrono::hours(4))); - throw std::runtime_error("Server has no internet access."); - } - else - { - // hard throttling for github. - RetryAfter(std::chrono::duration_cast(std::chrono::hours(8))); - GithubResponseHeaders githubHeaders{headerFile.Path()}; - this->githubResponseHeaders = githubHeaders; - this->githubResponseHeaders.Save(); - - if (githubHeaders.code_ != 200) - { - if ( - githubHeaders.ratelimit_limit_ != 0 && - githubHeaders.ratelimit_limit_ == githubHeaders.ratelimit_used_) - { - std::time_t time = std::chrono::system_clock::to_time_t(githubHeaders.ratelimit_reset_); - std::string strTime = std::ctime(&time); - - this->RetryAfter(githubHeaders.ratelimit_reset_ - githubHeaders.date_); - throw std::runtime_error(SS("Github API rate limit exceeded. Retrying at " << strTime)); - } - } - - if (result.output.length() == 0) - { - throw std::runtime_error("Server has no internet access."); - } - std::stringstream ss(result.output); - json_reader reader(ss); - json_variant vResult(reader); - - if (vResult.is_object()) - { - // an HTML error. - updateResult.isOnline_ = false; - auto o = vResult.as_object(); - std::string message = "Unknown error."; - if (o->at("message").is_string()) - { - message = o->at("message").as_string(); - } - throw std::runtime_error(SS("Github Service error: " << message)); - } - else if (!vResult.is_array()) - { - throw std::runtime_error("Invalid file format error."); - } - else - { - json_variant::array_ptr vArray = vResult.as_array(); - - std::vector releases; - for (size_t i = 0; i < vArray->size(); ++i) - { - auto &el = vArray->at(i); - GithubRelease release{el}; - if (!release.draft && release.GetDownloadForCurrentArchitecture() != nullptr) - { - if (release.name.find("Experimental") == std::string::npos) // experimental releases do not participate in auto-updates (not even for dev stream) - { - releases.push_back(std::move(release)); - } - } - } - std::sort( - releases.begin(), - releases.end(), - [](const GithubRelease &left, const GithubRelease &right) - { - return left.published_at > right.published_at; // latest date first. - }); - updateResult.releaseOnlyRelease_ = getUpdateRelease( - releases, - updateResult.currentVersion_, - [](const GithubRelease &githubRelease) - { - return !githubRelease.prerelease && - githubRelease.name.find("Release") != std::string::npos; - }); - - updateResult.releaseOrBetaRelease_ = getUpdateRelease( - releases, - updateResult.currentVersion_, - [](const GithubRelease &githubRelease) - { - return !githubRelease.prerelease && - (githubRelease.name.find("Release") != std::string::npos || - githubRelease.name.find("Beta") != std::string::npos); - }); - updateResult.devRelease_ = getUpdateRelease( - releases, - updateResult.currentVersion_, - [](const GithubRelease &githubRelease) - { - return true; - }); -#ifdef TEST_UPDATE - updateResult.releaseOrBetaRelease_.upgradeVersionDisplayName_ = "PiPedal v1.2.41-Beta"; - updateResult.devRelease_.upgradeVersionDisplayName_ = "PiPedal v1.2.39-Experimental"; - updateResult.devRelease_.upgradeVersion_ = "1.2.39"; - updateResult.devRelease_.updateAvailable_ = false; -#endif - updateResult.isValid_ = true; - updateResult.isOnline_ = true; - } - } + updateResult = DoUpdate(false); } catch (const std::exception &e) { @@ -851,6 +762,142 @@ void UpdaterImpl::CheckForUpdate(bool useCache) } } } + +UpdateStatus UpdaterImpl::DoUpdate(bool forReleaseGenerator) +{ + UpdateStatus updateResult; + if (forReleaseGenerator) + { + updateResult.currentVersion_ = "0.0.0"; + } + + // set default next retry time. github failures may postpone even further. + + Lv2Log::info("Checking for updates."); + // const std::string responseOption = "-w \"%{response_code}\""; +#ifdef WIN32 + responseOption = "-w \"%%{response_code}\""; // windows shell requires doubling of the %%. +#endif + + TemporaryFile headerFile{workingDirectory}; + std::string args = SS("-s -L " << GITHUB_RELEASES_URL << " -D " << headerFile.str()); + + updateResult.errorMessage_ = ""; + + auto result = sysExecForOutput("curl", args); + if (result.exitCode != EXIT_SUCCESS) + { + RetryAfter(std::chrono::duration_cast(std::chrono::hours(4))); + throw std::runtime_error("Server has no internet access."); + } + else + { + // hard throttling for github. + RetryAfter(std::chrono::duration_cast(std::chrono::hours(8))); + + GithubResponseHeaders githubHeaders{headerFile.Path()}; + this->githubResponseHeaders = githubHeaders; + this->githubResponseHeaders.Save(githubResponseHeaderFilename); + + if (githubHeaders.code_ != 200) + { + if ( + githubHeaders.ratelimit_limit_ != 0 && + githubHeaders.ratelimit_limit_ == githubHeaders.ratelimit_used_) + { + std::time_t time = std::chrono::system_clock::to_time_t(githubHeaders.ratelimit_reset_); + std::string strTime = std::ctime(&time); + + this->RetryAfter(githubHeaders.ratelimit_reset_ - githubHeaders.date_); + throw std::runtime_error(SS("Github API rate limit exceeded. Retrying at " << strTime)); + } + } + + if (result.output.length() == 0) + { + throw std::runtime_error("Server has no internet access."); + } + std::stringstream ss(result.output); + json_reader reader(ss); + json_variant vResult(reader); + + if (vResult.is_object()) + { + // an HTML error. + updateResult.isOnline_ = false; + auto o = vResult.as_object(); + std::string message = "Unknown error."; + if (o->at("message").is_string()) + { + message = o->at("message").as_string(); + } + throw std::runtime_error(SS("Github Service error: " << message)); + } + else if (!vResult.is_array()) + { + throw std::runtime_error("Invalid file format error."); + } + else + { + json_variant::array_ptr vArray = vResult.as_array(); + + std::vector releases; + for (size_t i = 0; i < vArray->size(); ++i) + { + auto &el = vArray->at(i); + GithubRelease release{el}; + if (!release.draft && release.GetDownloadForCurrentArchitecture() != nullptr) + { + if (release.name.find("Experimental") == std::string::npos) // experimental releases do not participate in auto-updates (not even for dev stream) + { + releases.push_back(std::move(release)); + } + } + } + std::sort( + releases.begin(), + releases.end(), + [](const GithubRelease &left, const GithubRelease &right) + { + return left.published_at > right.published_at; // latest date first. + }); + updateResult.releaseOnlyRelease_ = getUpdateRelease( + releases, + updateResult.currentVersion_, + [](const GithubRelease &githubRelease) + { + return !githubRelease.prerelease && + githubRelease.name.find("Release") != std::string::npos; + }); + + updateResult.releaseOrBetaRelease_ = getUpdateRelease( + releases, + updateResult.currentVersion_, + [](const GithubRelease &githubRelease) + { + return !githubRelease.prerelease && + (githubRelease.name.find("Release") != std::string::npos || + githubRelease.name.find("Beta") != std::string::npos); + }); + updateResult.devRelease_ = getUpdateRelease( + releases, + updateResult.currentVersion_, + [](const GithubRelease &githubRelease) + { + return true; + }); +#ifdef TEST_UPDATE + updateResult.releaseOrBetaRelease_.upgradeVersionDisplayName_ = "PiPedal v1.2.41-Beta"; + updateResult.devRelease_.upgradeVersionDisplayName_ = "PiPedal v1.2.39-Experimental"; + updateResult.devRelease_.upgradeVersion_ = "1.2.39"; + updateResult.devRelease_.updateAvailable_ = false; +#endif + updateResult.isValid_ = true; + updateResult.isOnline_ = true; + } + } + return updateResult; +} bool UpdateRelease::operator==(const UpdateRelease &other) const { return (updateAvailable_ == other.updateAvailable_) && @@ -1216,7 +1263,7 @@ void UpdaterImpl::RetryAfter(clock::duration delay) } void UpdaterImpl::SaveRetryTime(const std::chrono::system_clock::time_point &time) { - fs::path retryTimePath = WORKING_DIRECTORY / "retryTime.json"; + fs::path retryTimePath = workingDirectory / "retryTime.json"; pipedal::ofstream_synced f{retryTimePath}; if (f.is_open()) { @@ -1229,7 +1276,7 @@ UpdaterImpl::clock::time_point UpdaterImpl::LoadRetryTime() { using namespace std::chrono; - fs::path retryTimePath = WORKING_DIRECTORY / "retryTime.json"; + fs::path retryTimePath = workingDirectory / "retryTime.json"; std::ifstream f{retryTimePath}; if (f.is_open()) { @@ -1273,7 +1320,7 @@ void UpdaterImpl::DownloadUpdate(const std::string &url, std::filesystem::path * { throw std::runtime_error(SS("Invalid update url. Downloads from this address are not permitted: " << signatureUrl)); } - auto downloadDirectory = WORKING_DIRECTORY / "downloads"; + auto downloadDirectory = workingDirectory / "downloads"; std::filesystem::create_directories(downloadDirectory); auto downloadFilePath = downloadDirectory / filename; @@ -1336,6 +1383,14 @@ void UpdaterImpl::DownloadUpdate(const std::string &url, std::filesystem::path * throw; } } + + +void UpdateStatus::AddRelease(const std::string&packageName) +{ + this->isValid_ = true; + this->errorMessage_ = ""; +} + /////////////////////////////////////////////////////////////////////////////////////////////// JSON_MAP_BEGIN(UpdateRelease) diff --git a/src/Updater.hpp b/src/Updater.hpp index f5d7cdf..201422b 100644 --- a/src/Updater.hpp +++ b/src/Updater.hpp @@ -47,18 +47,22 @@ namespace pipedal using ptr = std::unique_ptr; static Updater::ptr Create(); + static Updater::ptr Create(const std::filesystem::path & workingDirectory); static void ValidateSignature(const std::filesystem::path&file, const std::filesystem::path&signatureFile); using UpdateListener = std::function; virtual void SetUpdateListener(UpdateListener &&listener) = 0; - virtual void CheckNow() = 0; + virtual void Start() = 0; virtual void Stop() = 0; + virtual void CheckNow() = 0; + virtual UpdatePolicyT GetUpdatePolicy() = 0; virtual void SetUpdatePolicy(UpdatePolicyT updatePolicy) = 0; virtual void ForceUpdateCheck() = 0; virtual void DownloadUpdate(const std::string &url, std::filesystem::path*file, std::filesystem::path*signatureFile) = 0; - UpdateStatus virtual GetCurrentStatus() const = 0; + virtual UpdateStatus GetCurrentStatus() const = 0; + virtual UpdateStatus GetReleaseGeneratorStatus() = 0; // only used when generating releases. Absolutely no caching. }; diff --git a/src/UpdaterSecurity.hpp b/src/UpdaterSecurity.hpp index a27bd0b..3fac951 100644 --- a/src/UpdaterSecurity.hpp +++ b/src/UpdaterSecurity.hpp @@ -24,6 +24,7 @@ #pragma once +#include /* PiPedal uses whitelisting and GPG signatures to verify that updates are valid. You must modify these defines appropriately if you want to host a fork. @@ -32,6 +33,9 @@ installs published on github/rerdavies/pipedal. */ +#ifndef ENABLE_AUTO_UPDATE +#define ENABLE_AUTO_UPDATE 1 +#endif #ifndef GITHUB_PROJECT #define GITHUB_PROJECT "rerdavies/pipedal" #endif @@ -43,7 +47,7 @@ #define UPDATE_GPG_ADDRESS "Robin Davies " #endif #ifndef UPDATE_GPG_ADDRESS2 -#define UPDATE_GPG_ADDRESS2 "PiPedal Project " +#define UPDATE_GPG_ADDRESS2 "PiPedal Project " #endif diff --git a/src/UpdaterStatus.hpp b/src/UpdaterStatus.hpp index 94fab81..4d50326 100644 --- a/src/UpdaterStatus.hpp +++ b/src/UpdaterStatus.hpp @@ -92,6 +92,8 @@ namespace pipedal { const UpdateRelease &DevRelease() const { return devRelease_; } bool operator==(const UpdateStatus &other) const; + void AddRelease(const std::string&packageName); // Release Generator ONLY + DECLARE_JSON_MAP(UpdateStatus); }; diff --git a/src/UpdaterTest.cpp b/src/UpdaterTest.cpp index 94113bb..2590492 100644 --- a/src/UpdaterTest.cpp +++ b/src/UpdaterTest.cpp @@ -43,6 +43,8 @@ TEST_CASE( "updater test", "[updater]" ) { ++nCalls; } ); + updater->Start(); + } REQUIRE(nCalls == 1); diff --git a/src/WebServerConfig.cpp b/src/WebServerConfig.cpp index 05f085b..45566b1 100644 --- a/src/WebServerConfig.cpp +++ b/src/WebServerConfig.cpp @@ -29,6 +29,7 @@ #include "ZipFile.hpp" #include "PiPedalUI.hpp" #include "ofstream_synced.hpp" +#include "UpdaterSecurity.hpp" #define PRESET_EXTENSION ".piPreset" @@ -524,8 +525,10 @@ public: std::stringstream s; s << "{ \"socket_server_port\": " << portNumber - << ", \"socket_server_address\": \"" << webSocketAddress << - "\", \"ui_plugins\": [ ], \"max_upload_size\": " << maxUploadSize << " }"; + << ", \"socket_server_address\": \"" << webSocketAddress + << "\", \"ui_plugins\": [ ], \"max_upload_size\": " << maxUploadSize + << ", \"enable_auto_update\": " << (ENABLE_AUTO_UPDATE ? " true": "false") + << " }"; return s.str(); } diff --git a/src/polkit-1/55-org.pipedal.d/55-org.freedesktop.NetworkManager.pkla b/src/polkit-1/55-org.pipedal.d/55-org.freedesktop.NetworkManager.pkla deleted file mode 100644 index f1172b4..0000000 --- a/src/polkit-1/55-org.pipedal.d/55-org.freedesktop.NetworkManager.pkla +++ /dev/null @@ -1,6 +0,0 @@ -[Adding or changing system-wide NetworkManager connections] -Identity=unix-group:pipedal_d -Action=org.freedesktop.NetworkManager.settings.modify.system -ResultAny=yes -ResultInactive=yes -ResultActive=yes diff --git a/src/polkit-1/rules/55-pipedal-networkmanager.rule b/src/polkit-1/rules/10-pipedal-networkmanager.rules similarity index 82% rename from src/polkit-1/rules/55-pipedal-networkmanager.rule rename to src/polkit-1/rules/10-pipedal-networkmanager.rules index 2c4ea65..f5fe4d2 100644 --- a/src/polkit-1/rules/55-pipedal-networkmanager.rule +++ b/src/polkit-1/rules/10-pipedal-networkmanager.rules @@ -1,5 +1,5 @@ // grant NetworkManager dbus permissions (no-prompts) to the pipedald systemd service. -polpolkit.addRule(function(action, subject) { +polkit.addRule(function(action, subject) { if (action.id.indexOf("org.freedesktop.NetworkManager.") == 0 && subject.isInGroup("pipedal_d")) { return polkit.Result.YES; } diff --git a/todo.txt b/todo.txt index 528828a..e1a64c1 100644 --- a/todo.txt +++ b/todo.txt @@ -1,7 +1,11 @@ +- X test reconnect sequence in client. + +-X remove 10-pipedal-networkmanager.rules before testing installer. + +- new signing procedure. - verify that scanning doesn't cause overruns. -- address change handlers in client (and browser?) -- Keybaord escape for hotspotManager. +- verify address change behaviour in client - verify stripping in installer. - verify update with no keyring. -verify clean removal of dhcpcd and nm_p2p2d @@ -14,12 +18,11 @@ Localisation: support non-UTF8 code pages. - unicode commandline arguments. - review unicode filenames (this is probably ok) - - -- make app use the same theme settings as the website. +- make app use the same theme settings as the website. (make the website use the same theme as the app) - BUG: gcs when we have an animated output control - versioning. -- do we want to take a second crack at establishin multi-p2p connections with the driver_param? + +