Signing of update packages.
This commit is contained in:
+2
-3
@@ -11,9 +11,6 @@ set (CMAKE_INSTALL_PREFIX "/usr/")
|
||||
include(CTest)
|
||||
enable_testing()
|
||||
|
||||
# Frameworks
|
||||
# catch
|
||||
|
||||
add_subdirectory("submodules/pipedal_p2pd")
|
||||
|
||||
add_subdirectory("PiPedalCommon")
|
||||
@@ -103,6 +100,8 @@ set(CPACK_PROJECT_VERSION ${PROJECT_VERSION})
|
||||
set(CPACK_DEBIAN_FILE_NAME DEB-DEFAULT)
|
||||
set (CPACK_STRIP_FILES ON)
|
||||
|
||||
set(CPACK_DEBIAN_PACKAGE_SIGN_ALGORITHM "detached")
|
||||
set(CPACK_DEBIAN_PACKAGE_SIGN_TYPE "origin")
|
||||
|
||||
set(CPACK_DEBIAN_PACKAGE_CONTROL_EXTRA "${CMAKE_CURRENT_SOURCE_DIR}/debian/postinst;${CMAKE_CURRENT_SOURCE_DIR}/debian/prerm")
|
||||
|
||||
|
||||
@@ -0,0 +1,41 @@
|
||||
-----BEGIN PGP PUBLIC KEY BLOCK-----
|
||||
|
||||
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
|
||||
QOsNQMsqIgaFJgS/9Yc=
|
||||
=d5rj
|
||||
-----END PGP PUBLIC KEY BLOCK-----
|
||||
@@ -44,13 +44,15 @@ export class UpdateRelease {
|
||||
upgradeVersionDisplayName: string = ""; // display name for the version.
|
||||
assetName: string = ""; // filename only
|
||||
updateUrl: string = ""; // url from which to download the .deb file.
|
||||
gpgSignatureUrl: string = "";
|
||||
|
||||
equals(other: UpdateRelease): boolean {
|
||||
return (this.updateAvailable === other.updateAvailable) &&
|
||||
(this.upgradeVersion === other.upgradeVersion) &&
|
||||
(this.upgradeVersionDisplayName === other.upgradeVersionDisplayName) &&
|
||||
(this.assetName === other.assetName) &&
|
||||
(this.updateUrl === other.updateUrl)
|
||||
(this.updateUrl === other.updateUrl) &&
|
||||
(this.gpgSignatureUrl === other.gpgSignatureUrl)
|
||||
;
|
||||
}
|
||||
};
|
||||
@@ -93,7 +95,7 @@ export class UpdateStatus {
|
||||
switch (this.updatePolicy) {
|
||||
case UpdatePolicyT.Disable: // show available updates in the settings dialog.
|
||||
case UpdatePolicyT.ReleaseOnly:
|
||||
return this.releaseOnlyRelease;
|
||||
return this.releaseOnlyRelease;
|
||||
case UpdatePolicyT.ReleaseOrBeta:
|
||||
return this.releaseOrBetaRelease;
|
||||
case UpdatePolicyT.Development:
|
||||
|
||||
Executable
+10
@@ -0,0 +1,10 @@
|
||||
#!/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 ..
|
||||
@@ -29,6 +29,8 @@
|
||||
#include <sys/prctl.h>
|
||||
#include <sys/wait.h>
|
||||
#include <signal.h>
|
||||
#include "UpdateResults.hpp"
|
||||
#include "Updater.cpp"
|
||||
|
||||
using namespace pipedal;
|
||||
namespace fs = std::filesystem;
|
||||
@@ -100,11 +102,28 @@ void updateLog(const std::string &message)
|
||||
write(1,message.c_str(),message.length());
|
||||
write(1,"\n",1);
|
||||
}
|
||||
|
||||
|
||||
static std::string getVersion(const fs::path path)
|
||||
{
|
||||
std::string fileNameOnly = path.filename();
|
||||
std::vector<std::string> segments = split(path,'_');
|
||||
if (segments.size() != 3) return "Unknown";
|
||||
return SS("v" << segments[1]);
|
||||
}
|
||||
void pipedal::AdminInstallUpdate(const std::filesystem::path path)
|
||||
{
|
||||
|
||||
UpdateResults updateResults;
|
||||
updateResults.updated_ = true;
|
||||
|
||||
updateResults.updateVersion_ = getVersion(path);
|
||||
try
|
||||
{
|
||||
// client needs a disconnect/reconnect even if the update doesn't happen.
|
||||
|
||||
exec("/usr/bin/systemctl stop pipedald");
|
||||
|
||||
fs:create_directories(ROOT_INSTALL_DIRECTORY);
|
||||
Lv2Log::info(SS("Installing " << path));
|
||||
|
||||
@@ -138,20 +157,36 @@ void pipedal::AdminInstallUpdate(const std::filesystem::path path)
|
||||
dup2(fdNull, 0);
|
||||
close(fdNull);
|
||||
|
||||
updateLog("**Stopping pipedald");
|
||||
// verify the signature (again), now that we're running as root and could do real damage
|
||||
// if we install somebody else's package.
|
||||
|
||||
exec("/usr/bin/systemctl stop pipedald");
|
||||
Updater::ValidateSignature(path, SS(path.string() << ".asc")); // errors are thrown.
|
||||
|
||||
updateLog("** Installing update");
|
||||
int retcode = exec(cmd);
|
||||
|
||||
|
||||
if (retcode != EXIT_SUCCESS)
|
||||
{
|
||||
updateResults.updateSuccessful_ = false;
|
||||
updateResults.updateMessage_ = SS("Update failed. See " << INSTALLER_LOG_FILE_PATH.c_str() << " for further details.");
|
||||
} else {
|
||||
updateResults.updateSuccessful_ = true;
|
||||
updateResults.updateMessage_ = SS("Successfully updated to version " << updateResults.updateVersion_ << ".");
|
||||
}
|
||||
|
||||
// in case we didn't actually update for some reason.
|
||||
updateLog("** Starting pipedald");
|
||||
exec("/usr/bin/systemctl start pipedald");
|
||||
}
|
||||
catch (const std::exception &e)
|
||||
{
|
||||
updateLog(e.what());
|
||||
|
||||
updateResults.updated_ = false;
|
||||
updateResults.updateSuccessful_ = false;
|
||||
updateResults.updateMessage_ = e.what();
|
||||
}
|
||||
updateResults.Save();
|
||||
|
||||
// restart pipedal to get the client to remove the "Updating..." message.
|
||||
exec("/usr/bin/systemctl start pipedald");
|
||||
|
||||
}
|
||||
+5
-1
@@ -137,6 +137,8 @@ else()
|
||||
endif()
|
||||
|
||||
set (PIPEDAL_SOURCES
|
||||
UpdateResults.cpp UpdateResults.hpp
|
||||
UpdaterSecurity.hpp
|
||||
Updater.cpp Updater.hpp
|
||||
Lv2PluginChangeMonitor.cpp Lv2PluginChangeMonitor.hpp
|
||||
WebServerConfig.cpp WebServerConfig.hpp
|
||||
@@ -615,12 +617,14 @@ target_link_libraries(capturepresets ${PIPEDAL_LIBS})
|
||||
|
||||
add_executable(pipedal_update
|
||||
UpdateMain.cpp
|
||||
UpdateResults.cpp UpdateResults.hpp
|
||||
Lv2Log.hpp Lv2Log.cpp
|
||||
Lv2SystemdLogger.cpp Lv2SystemdLogger.hpp
|
||||
UpdateResults.cpp UpdateResults.hpp
|
||||
AdminInstallUpdate.cpp AdminInstallUpdate.hpp
|
||||
)
|
||||
|
||||
target_link_libraries(pipedal_update PRIVATE stdc++fs systemd )
|
||||
target_link_libraries(pipedal_update PRIVATE stdc++fs systemd PiPedalCommon )
|
||||
|
||||
|
||||
add_executable(pipedaladmind AdminMain.cpp CommandLineParser.hpp
|
||||
|
||||
+129
-82
@@ -57,7 +57,6 @@ using namespace std;
|
||||
using namespace pipedal;
|
||||
namespace fs = std::filesystem;
|
||||
|
||||
|
||||
#define SERVICE_ACCOUNT_NAME "pipedal_d"
|
||||
#define SERVICE_GROUP_NAME "pipedal_d"
|
||||
#define JACK_SERVICE_ACCOUNT_NAME "jack"
|
||||
@@ -425,9 +424,9 @@ void InstallAudioService()
|
||||
#endif
|
||||
}
|
||||
|
||||
int SudoExec(int argc, char**argv)
|
||||
int SudoExec(int argc, char **argv)
|
||||
{
|
||||
// re-execute with SUDO in order to prompt for SUDO credentials once only.
|
||||
// re-execute with SUDO in order to prompt for SUDO credentials once only.
|
||||
std::vector<char *> args;
|
||||
std::string pkexec = "/usr/bin/sudo"; // staged because "ISO C++ forbids converting a string constant to std::vector<char*>::value_type"(!)
|
||||
args.push_back((char *)(pkexec.c_str()));
|
||||
@@ -471,7 +470,6 @@ void Uninstall()
|
||||
{
|
||||
OnWifiUninstall(true);
|
||||
|
||||
|
||||
StopService();
|
||||
DisableService();
|
||||
|
||||
@@ -584,62 +582,72 @@ static std::string RandomChars(int nchars)
|
||||
return s.str();
|
||||
}
|
||||
|
||||
static std::map<fs::path, fs::perms> sPermissionExceptions
|
||||
({
|
||||
{
|
||||
"/var/pipedal/config/NetworkManagerP2P.json",
|
||||
fs::perms::owner_read | fs::perms::owner_write | fs::perms::group_read | fs::perms::group_write
|
||||
|
||||
}
|
||||
});
|
||||
static std::map<fs::path, fs::perms> sPermissionExceptions({{"/var/pipedal/config/NetworkManagerP2P.json",
|
||||
fs::perms::owner_read | fs::perms::owner_write | fs::perms::group_read | fs::perms::group_write
|
||||
|
||||
}});
|
||||
|
||||
void SetVarPermissions(
|
||||
const fs::path &path,
|
||||
const fs::path &path,
|
||||
fs::perms directoryPermissions,
|
||||
fs::perms filePermissions,
|
||||
uid_t uid, gid_t gid)
|
||||
{
|
||||
using namespace std::filesystem;
|
||||
try {
|
||||
if (fs::exists(path)) {
|
||||
std::ignore = chown(path.c_str(),uid,gid);
|
||||
if (fs::is_directory(path)) {
|
||||
try
|
||||
{
|
||||
if (fs::exists(path))
|
||||
{
|
||||
std::ignore = chown(path.c_str(), uid, gid);
|
||||
if (fs::is_directory(path))
|
||||
{
|
||||
fs::permissions(path, directoryPermissions, fs::perm_options::replace);
|
||||
for (const auto& entry : fs::recursive_directory_iterator(path)) {
|
||||
for (const auto &entry : fs::recursive_directory_iterator(path))
|
||||
{
|
||||
|
||||
if (chown(entry.path().c_str(),uid,gid) != 0)
|
||||
if (chown(entry.path().c_str(), uid, gid) != 0)
|
||||
{
|
||||
std::cout << "Error: failed to set ownership of file " << entry.path() << std::endl;
|
||||
}
|
||||
|
||||
try {
|
||||
try
|
||||
{
|
||||
if (fs::is_directory(entry.path()))
|
||||
{
|
||||
fs::permissions(entry.path(), directoryPermissions, fs::perm_options::replace);
|
||||
} else {
|
||||
fs::permissions(entry.path(),filePermissions, fs::perm_options::replace);
|
||||
}
|
||||
} catch (const std::exception &e)
|
||||
{
|
||||
std::cout << "Error: failed to set permissions on file " << entry.path() << std::endl;
|
||||
else
|
||||
{
|
||||
fs::permissions(entry.path(), filePermissions, fs::perm_options::replace);
|
||||
}
|
||||
}
|
||||
catch (const std::exception &e)
|
||||
{
|
||||
std::cout << "Error: failed to set permissions on file " << entry.path() << std::endl;
|
||||
}
|
||||
|
||||
}
|
||||
} else {
|
||||
if (sPermissionExceptions.contains(path))
|
||||
{
|
||||
fs::permissions(path,sPermissionExceptions[path], fs::perm_options::replace);
|
||||
} else {
|
||||
fs::permissions(path,filePermissions, fs::perm_options::replace);
|
||||
}
|
||||
}
|
||||
} else {
|
||||
std::cout << "Error: Path does not exist: " << path << std::endl;
|
||||
else
|
||||
{
|
||||
if (sPermissionExceptions.contains(path))
|
||||
{
|
||||
fs::permissions(path, sPermissionExceptions[path], fs::perm_options::replace);
|
||||
}
|
||||
else
|
||||
{
|
||||
fs::permissions(path, filePermissions, fs::perm_options::replace);
|
||||
}
|
||||
}
|
||||
}
|
||||
} catch (const fs::filesystem_error& e) {
|
||||
else
|
||||
{
|
||||
std::cout << "Error: Path does not exist: " << path << std::endl;
|
||||
}
|
||||
}
|
||||
catch (const fs::filesystem_error &e)
|
||||
{
|
||||
std::cerr << "Error: " << e.what() << std::endl;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
namespace fs = std::filesystem;
|
||||
@@ -651,17 +659,17 @@ static void FixPermissions()
|
||||
|
||||
fs::create_directories("/var/pipedal");
|
||||
|
||||
fs::perms directoryPermissions =
|
||||
fs::perms directoryPermissions =
|
||||
fs::perms::owner_read | fs::perms::owner_write | fs::perms::owner_exec |
|
||||
fs::perms::group_read | fs::perms::group_write | fs::perms::group_exec |
|
||||
fs::perms::others_read | fs::perms::others_exec |
|
||||
fs::perms::set_gid;
|
||||
|
||||
fs::perms filePermissions =
|
||||
fs::perms::owner_read | fs::perms::owner_write |
|
||||
fs::perms::group_read | fs::perms::group_write |
|
||||
fs::perms::others_read ;
|
||||
|
||||
|
||||
fs::perms filePermissions =
|
||||
fs::perms::owner_read | fs::perms::owner_write |
|
||||
fs::perms::group_read | fs::perms::group_write |
|
||||
fs::perms::others_read;
|
||||
|
||||
uid_t uid;
|
||||
struct passwd *passwd;
|
||||
if ((passwd = getpwnam("pipedal_d")) == nullptr)
|
||||
@@ -671,25 +679,27 @@ static void FixPermissions()
|
||||
}
|
||||
uid = passwd->pw_uid;
|
||||
gid_t gid = passwd->pw_gid;
|
||||
SetVarPermissions("/var/pipedal",directoryPermissions,filePermissions,uid,gid);
|
||||
SetVarPermissions("/var/pipedal", directoryPermissions, filePermissions, uid, gid);
|
||||
|
||||
fs::perms wpa_supplicant_perms =
|
||||
fs::perms wpa_supplicant_perms =
|
||||
fs::perms::owner_read | fs::perms::owner_write |
|
||||
fs::perms::group_read | fs::perms::group_write;
|
||||
|
||||
fs::path wpa_config_path{ "/etc/pipedal/config/wpa_supplicant/wpa_supplicant-pipedal.conf"};
|
||||
try {
|
||||
fs::permissions(wpa_config_path,wpa_supplicant_perms, fs::perm_options::replace);
|
||||
|
||||
struct group*grp = getgrnam("netdev");
|
||||
fs::path wpa_config_path{"/etc/pipedal/config/wpa_supplicant/wpa_supplicant-pipedal.conf"};
|
||||
try
|
||||
{
|
||||
fs::permissions(wpa_config_path, wpa_supplicant_perms, fs::perm_options::replace);
|
||||
|
||||
struct group *grp = getgrnam("netdev");
|
||||
if (grp != nullptr)
|
||||
{
|
||||
if (chown(wpa_config_path.c_str(),0,grp->gr_gid) != 0)
|
||||
if (chown(wpa_config_path.c_str(), 0, grp->gr_gid) != 0)
|
||||
{
|
||||
cout << "Error: Failed to change ownership of " << wpa_config_path << endl;
|
||||
}
|
||||
}
|
||||
} catch (const std::exception&e)
|
||||
}
|
||||
catch (const std::exception &e)
|
||||
{
|
||||
cout << "Error: Failed to set permissions on " << wpa_config_path << ". " << e.what() << endl;
|
||||
}
|
||||
@@ -721,15 +731,17 @@ static void PrepareServiceConfigurationFile(uint16_t portNumber)
|
||||
|
||||
void CopyWpaSupplicantConfigFile()
|
||||
{
|
||||
try {
|
||||
try
|
||||
{
|
||||
const char NM_SUPPLICANT_CONFIG_PATH[] = "/etc/pipedal/config/wpa_supplicant/wpa_supplicant-pipedal.conf";
|
||||
const char NM_SUPPLICANT_CONFIG_SOURCE_PATH[] = "/etc/pipedal/config/templates/wpa_supplicant-pipedal.conf";
|
||||
fs::path destinationPath = NM_SUPPLICANT_CONFIG_PATH;
|
||||
fs::path sourcePath = NM_SUPPLICANT_CONFIG_SOURCE_PATH;
|
||||
|
||||
|
||||
fs::create_directories(destinationPath.parent_path());
|
||||
fs::copy_file(sourcePath,destinationPath,fs::copy_options::overwrite_existing);
|
||||
} catch (const std::exception&e)
|
||||
fs::copy_file(sourcePath, destinationPath, fs::copy_options::overwrite_existing);
|
||||
}
|
||||
catch (const std::exception &e)
|
||||
{
|
||||
cout << "ERROR: " << e.what() << endl;
|
||||
}
|
||||
@@ -743,14 +755,48 @@ void DeployVarConfig()
|
||||
auto directory = varConfig.parent_path();
|
||||
fs::create_directories(directory);
|
||||
fs::path templateFile = "/etc/pipedal/config/templates/var_config.json";
|
||||
try {
|
||||
fs::copy(templateFile,varConfig);
|
||||
} catch (const std::exception &e)
|
||||
try
|
||||
{
|
||||
fs::copy(templateFile, varConfig);
|
||||
}
|
||||
catch (const std::exception &e)
|
||||
{
|
||||
std::cout << "Error: Failed to create " << varConfig << std::endl;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void InstallPgpKey()
|
||||
{
|
||||
fs::path homeDir = "/var/pipedal/config/gpg";
|
||||
fs::path keyPath = "/etc/pipedal/config/updatekey.gpg";
|
||||
|
||||
fs::create_directories(homeDir);
|
||||
std::ignore = chmod(homeDir.c_str(), 0700);
|
||||
|
||||
{
|
||||
std::stringstream s;
|
||||
s << (CHOWN_BIN " " SERVICE_GROUP_NAME ":" SERVICE_GROUP_NAME " ") << homeDir.c_str();
|
||||
sysExec(s.str().c_str());
|
||||
}
|
||||
std::ostringstream ss;
|
||||
ss << "/usr/bin/gpg --homedir " << homeDir.c_str() << " --import " << keyPath.c_str();
|
||||
|
||||
int rc = sysExec(ss.str().c_str());
|
||||
if (rc != EXIT_SUCCESS)
|
||||
{
|
||||
cout << "Error: Failed to create update keyring." << endl;
|
||||
}
|
||||
|
||||
{
|
||||
std::stringstream ss;
|
||||
ss << (CHOWN_BIN " -R " SERVICE_GROUP_NAME ":" SERVICE_GROUP_NAME " ") << homeDir.c_str();
|
||||
std::string cmd = ss.str();
|
||||
cout << cmd << endl;
|
||||
sysExec(cmd.c_str());
|
||||
}
|
||||
cout << "Debug: Finished creating keystring." << endl;
|
||||
}
|
||||
void Install(const fs::path &programPrefix, const std::string endpointAddress)
|
||||
{
|
||||
cout << "Configuring pipedal" << endl;
|
||||
@@ -954,7 +1000,7 @@ void Install(const fs::path &programPrefix, const std::string endpointAddress)
|
||||
|
||||
// Restart WiFi Direct if neccessary.
|
||||
OnWifiReinstall();
|
||||
|
||||
InstallPgpKey();
|
||||
}
|
||||
catch (const std::exception &e)
|
||||
{
|
||||
@@ -968,7 +1014,7 @@ void Install(const fs::path &programPrefix, const std::string endpointAddress)
|
||||
static std::string GetCurrentWebServicePort()
|
||||
{
|
||||
PiPedalConfiguration config;
|
||||
config.Load("/etc/pipedal/config","");
|
||||
config.Load("/etc/pipedal/config", "");
|
||||
|
||||
std::ostringstream ss;
|
||||
ss << config.GetSocketServerPort();
|
||||
@@ -1066,7 +1112,6 @@ static void PrintHelp()
|
||||
<< HangingIndent() << " --list-p2p-channels [<country_code>] \tList valid p2p channels for the current/specified country."
|
||||
<< "\n\n"
|
||||
|
||||
|
||||
// << HangingIndent() << " --enable-legacy-ap\t <country_code> <ssid> <wep_password> <channel>\tEnable a legacy Wi-Fi access point."
|
||||
// << "\n\n"
|
||||
// << "Enable a legacy Wi-Fi access point. \n\n"
|
||||
@@ -1117,17 +1162,21 @@ static void PrintHelp()
|
||||
"therefore preferrable under almost all circumstances.\n\n";
|
||||
}
|
||||
|
||||
static int ListP2PChannels(const std::vector<std::string>&arguments)
|
||||
static int ListP2PChannels(const std::vector<std::string> &arguments)
|
||||
{
|
||||
try {
|
||||
try
|
||||
{
|
||||
std::string country;
|
||||
if (arguments.size() >= 2)
|
||||
if (arguments.size() >= 2)
|
||||
{
|
||||
throw std::runtime_error("Invalid arguments.");
|
||||
} if (arguments.size() == 1)
|
||||
}
|
||||
if (arguments.size() == 1)
|
||||
{
|
||||
country = arguments[0];
|
||||
} else {
|
||||
}
|
||||
else
|
||||
{
|
||||
// use the currently selected country by default.
|
||||
WifiDirectConfigSettings settings;
|
||||
settings.Load();
|
||||
@@ -1137,18 +1186,18 @@ static int ListP2PChannels(const std::vector<std::string>&arguments)
|
||||
}
|
||||
country = settings.countryCode_;
|
||||
}
|
||||
auto channelSelectors = getWifiChannelSelectors(country.c_str(),true);
|
||||
for (const auto &channelSelector: channelSelectors)
|
||||
auto channelSelectors = getWifiChannelSelectors(country.c_str(), true);
|
||||
for (const auto &channelSelector : channelSelectors)
|
||||
{
|
||||
cout << channelSelector.channelName_ << endl;
|
||||
}
|
||||
} catch (const std::exception &e)
|
||||
}
|
||||
catch (const std::exception &e)
|
||||
{
|
||||
cout << "ERROR: " << e.what() << endl;
|
||||
return EXIT_FAILURE;
|
||||
}
|
||||
return EXIT_SUCCESS;
|
||||
|
||||
}
|
||||
|
||||
int main(int argc, char **argv)
|
||||
@@ -1182,13 +1231,12 @@ int main(int argc, char **argv)
|
||||
parser.AddOption("--help", &help);
|
||||
parser.AddOption("--prefix", &prefixOption);
|
||||
parser.AddOption("--port", &portOption);
|
||||
//parser.AddOption("--enable-legacy-ap", &enable_ap);
|
||||
// parser.AddOption("--enable-legacy-ap", &enable_ap);
|
||||
parser.AddOption("--disable-ap", &disable_ap);
|
||||
parser.AddOption("--enable-p2p", &enable_p2p);
|
||||
parser.AddOption("--disable-p2p", &disable_p2p);
|
||||
parser.AddOption("--list-p2p-channels", &list_p2p_channels);
|
||||
parser.AddOption("--fix-permissions", &fix_permissions);
|
||||
|
||||
|
||||
parser.AddOption("--get-current-port", &get_current_port); // private. For debug use only.
|
||||
|
||||
@@ -1197,11 +1245,8 @@ int main(int argc, char **argv)
|
||||
{
|
||||
parser.Parse(argc, (const char **)argv);
|
||||
|
||||
int actionCount =
|
||||
help + get_current_port + install + uninstall + stop + start + enable + disable
|
||||
+ enable_ap + disable_ap + restart + enable_p2p + disable_p2p
|
||||
+ list_p2p_channels + fix_permissions
|
||||
;
|
||||
int actionCount =
|
||||
help + get_current_port + install + uninstall + stop + start + enable + disable + enable_ap + disable_ap + restart + enable_p2p + disable_p2p + list_p2p_channels + fix_permissions;
|
||||
if (actionCount > 1)
|
||||
{
|
||||
throw std::runtime_error("Please provide only one action.");
|
||||
@@ -1260,7 +1305,7 @@ int main(int argc, char **argv)
|
||||
auto uid = getuid();
|
||||
if (uid != 0 && !nosudo)
|
||||
{
|
||||
return SudoExec(argc,argv);
|
||||
return SudoExec(argc, argv);
|
||||
}
|
||||
|
||||
try
|
||||
@@ -1330,7 +1375,8 @@ int main(int argc, char **argv)
|
||||
}
|
||||
else if (enable_p2p)
|
||||
{
|
||||
try {
|
||||
try
|
||||
{
|
||||
auto argv = parser.Arguments();
|
||||
WifiDirectConfigSettings settings;
|
||||
settings.ParseArguments(argv);
|
||||
@@ -1338,7 +1384,8 @@ int main(int argc, char **argv)
|
||||
settings.enable_ = true;
|
||||
SetWifiDirectConfig(settings);
|
||||
RestartService(true); // also have to retart web service so that it gets the correct device name.
|
||||
} catch (const std::exception&e)
|
||||
}
|
||||
catch (const std::exception &e)
|
||||
{
|
||||
cout << "ERROR: " << e.what() << endl;
|
||||
return EXIT_FAILURE;
|
||||
|
||||
@@ -2174,7 +2174,8 @@ UpdateStatus PiPedalModel::GetUpdateStatus()
|
||||
void PiPedalModel::UpdateNow(const std::string &updateUrl)
|
||||
{
|
||||
std::lock_guard<std::recursive_mutex> lock(mutex);
|
||||
auto fileName = updater.DownloadUpdate(updateUrl);
|
||||
std::filesystem::path fileName,signatureName;
|
||||
updater.DownloadUpdate(updateUrl,&fileName,&signatureName);
|
||||
|
||||
adminClient.InstallUpdate(fileName);
|
||||
}
|
||||
|
||||
@@ -35,4 +35,5 @@ int main(int argc, char**argv)
|
||||
}
|
||||
std::filesystem::path path = argv[1];
|
||||
AdminInstallUpdate(path);
|
||||
return EXIT_SUCCESS;
|
||||
}
|
||||
@@ -0,0 +1,60 @@
|
||||
// Copyright (c) 2024 Robin Davies
|
||||
//
|
||||
// Permission is hereby granted, free of charge, to any person obtaining a copy of
|
||||
// this software and associated documentation files (the "Software"), to deal in
|
||||
// the Software without restriction, including without limitation the rights to
|
||||
// use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of
|
||||
// the Software, and to permit persons to whom the Software is furnished to do so,
|
||||
// subject to the following conditions:
|
||||
//
|
||||
// The above copyright notice and this permission notice shall be included in all
|
||||
// copies or substantial portions of the Software.
|
||||
//
|
||||
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
|
||||
// FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
|
||||
// COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER
|
||||
// IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
|
||||
// CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
||||
|
||||
#include "UpdateResults.hpp"
|
||||
#include <filesystem>
|
||||
#include <fstream>
|
||||
#include "Lv2Log.hpp"
|
||||
#include "ss.hpp"
|
||||
|
||||
|
||||
|
||||
using namespace pipedal;
|
||||
namespace fs = std::filesystem;
|
||||
|
||||
const fs::path UPDATE_RESULT_PATH{"/var/pipedal/config"};
|
||||
void UpdateResults::Load()
|
||||
{
|
||||
std::ifstream f(UPDATE_RESULT_PATH);
|
||||
if (f.is_open())
|
||||
{
|
||||
json_reader reader(f);
|
||||
reader.read(this);
|
||||
}
|
||||
}
|
||||
void UpdateResults::Save()
|
||||
{
|
||||
std::ofstream f {UPDATE_RESULT_PATH};
|
||||
if (f.is_open())
|
||||
{
|
||||
json_writer writer(f);
|
||||
writer.write(this);
|
||||
} else {
|
||||
Lv2Log::error(SS("Can't write to " << UPDATE_RESULT_PATH));
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
JSON_MAP_BEGIN(UpdateResults)
|
||||
JSON_MAP_REFERENCE(UpdateResults, updated)
|
||||
JSON_MAP_REFERENCE(UpdateResults, updateSuccessful)
|
||||
JSON_MAP_REFERENCE(UpdateResults, updateVersion)
|
||||
JSON_MAP_REFERENCE(UpdateResults, updateMessage)
|
||||
JSON_MAP_REFERENCE(UpdateResults, installerLog)
|
||||
JSON_MAP_END()
|
||||
@@ -0,0 +1,39 @@
|
||||
// Copyright (c) 2024 Robin Davies
|
||||
//
|
||||
// Permission is hereby granted, free of charge, to any person obtaining a copy of
|
||||
// this software and associated documentation files (the "Software"), to deal in
|
||||
// the Software without restriction, including without limitation the rights to
|
||||
// use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of
|
||||
// the Software, and to permit persons to whom the Software is furnished to do so,
|
||||
// subject to the following conditions:
|
||||
//
|
||||
// The above copyright notice and this permission notice shall be included in all
|
||||
// copies or substantial portions of the Software.
|
||||
//
|
||||
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
|
||||
// FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
|
||||
// COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER
|
||||
// IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
|
||||
// CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "json.hpp"
|
||||
|
||||
namespace pipedal {
|
||||
class UpdateResults {
|
||||
public:
|
||||
bool updated_ = false;
|
||||
bool updateSuccessful_ = false;
|
||||
std::string updateVersion_;
|
||||
std::string updateMessage_;
|
||||
std::string installerLog_;
|
||||
|
||||
void Load();
|
||||
void Save();
|
||||
|
||||
DECLARE_JSON_MAP(UpdateResults);
|
||||
};
|
||||
|
||||
}
|
||||
+211
-47
@@ -32,8 +32,11 @@
|
||||
#include "ss.hpp"
|
||||
#include "Lv2Log.hpp"
|
||||
#include <algorithm>
|
||||
#include "UpdaterSecurity.hpp"
|
||||
#include "SysExec.hpp"
|
||||
|
||||
using namespace pipedal;
|
||||
namespace fs = std::filesystem;
|
||||
|
||||
#define TEST_UPDATE
|
||||
|
||||
@@ -45,12 +48,9 @@ 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";
|
||||
|
||||
static std::string GITHUB_RELEASES_URL = "https://api.github.com/repos/rerdavies/pipedal/releases";
|
||||
|
||||
Updater::clock::duration Updater::updateRate = std::chrono::duration_cast<Updater::clock::duration>(std::chrono::days(1));
|
||||
static std::chrono::system_clock::duration CACHE_DURATION = std::chrono::duration_cast<std::chrono::system_clock::duration>(std::chrono::minutes(30));
|
||||
|
||||
@@ -194,7 +194,8 @@ void Updater::ThreadProc()
|
||||
if (value == CHECK_NOW_EVENT)
|
||||
{
|
||||
CheckForUpdate(true);
|
||||
} else if (value == UNCACHED_CHECK_NOW_EVENT)
|
||||
}
|
||||
else if (value == UNCACHED_CHECK_NOW_EVENT)
|
||||
{
|
||||
CheckForUpdate(false);
|
||||
}
|
||||
@@ -221,6 +222,8 @@ public:
|
||||
GithubRelease(json_variant &v);
|
||||
|
||||
const GithubAsset *GetDownloadForCurrentArchitecture() const;
|
||||
const GithubAsset *GetGpgKeyForAsset(const std::string &name) const;
|
||||
|
||||
bool draft = true;
|
||||
bool prerelease = true;
|
||||
std::string name;
|
||||
@@ -349,8 +352,6 @@ static bool IsCacheValid(const UpdateStatus &updateStatus)
|
||||
return now >= validStart && now < validEnd;
|
||||
}
|
||||
|
||||
|
||||
|
||||
UpdateRelease Updater::getUpdateRelease(
|
||||
const std::vector<GithubRelease> &githubReleases,
|
||||
const std::string ¤tVersion,
|
||||
@@ -361,6 +362,11 @@ UpdateRelease Updater::getUpdateRelease(
|
||||
auto *asset = githubRelease.GetDownloadForCurrentArchitecture();
|
||||
if (!asset)
|
||||
continue;
|
||||
auto *pgpKey = githubRelease.GetGpgKeyForAsset(asset->name);
|
||||
if (!pgpKey)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
if (!predicate(githubRelease))
|
||||
continue;
|
||||
@@ -370,6 +376,7 @@ UpdateRelease Updater::getUpdateRelease(
|
||||
updateRelease.upgradeVersionDisplayName_ = normalizeReleaseName(githubRelease.name);
|
||||
updateRelease.assetName_ = asset->name;
|
||||
updateRelease.updateUrl_ = asset->browser_download_url;
|
||||
updateRelease.gpgSignatureUrl_ = pgpKey->browser_download_url;
|
||||
return updateRelease;
|
||||
}
|
||||
return UpdateRelease();
|
||||
@@ -377,10 +384,10 @@ UpdateRelease Updater::getUpdateRelease(
|
||||
|
||||
void Updater::CheckForUpdate(bool useCache)
|
||||
{
|
||||
UpdateStatus updateResult;
|
||||
UpdateStatus updateResult;
|
||||
|
||||
{
|
||||
std::lock_guard lock {mutex};
|
||||
std::lock_guard lock{mutex};
|
||||
if (useCache && IsCacheValid(cachedUpdateStatus))
|
||||
{
|
||||
this->currentResult = cachedUpdateStatus;
|
||||
@@ -395,7 +402,6 @@ void Updater::CheckForUpdate(bool useCache)
|
||||
}
|
||||
std::string args = SS("-s " << GITHUB_RELEASES_URL);
|
||||
|
||||
|
||||
updateResult.errorMessage_ = "";
|
||||
|
||||
try
|
||||
@@ -569,7 +575,6 @@ void UpdateStatus::ResetCurrentVersion()
|
||||
#endif
|
||||
}
|
||||
|
||||
|
||||
std::chrono::system_clock::time_point UpdateStatus::LastUpdateTime() const
|
||||
{
|
||||
std::chrono::system_clock::duration duration{this->lastUpdateTime_};
|
||||
@@ -582,6 +587,19 @@ void UpdateStatus::LastUpdateTime(const std::chrono::system_clock::time_point &t
|
||||
this->lastUpdateTime_ = timePoint.time_since_epoch().count();
|
||||
}
|
||||
|
||||
const GithubAsset *GithubRelease::GetGpgKeyForAsset(const std::string &name) const
|
||||
{
|
||||
std::string targetName = name + ".asc";
|
||||
|
||||
for (auto &asset : assets)
|
||||
{
|
||||
if (asset.name == targetName)
|
||||
{
|
||||
return &asset;
|
||||
}
|
||||
}
|
||||
return nullptr;
|
||||
}
|
||||
const GithubAsset *GithubRelease::GetDownloadForCurrentArchitecture() const
|
||||
{
|
||||
// deb package names end in {DEBIAN_ARCHITECTURE}.deb
|
||||
@@ -601,14 +619,31 @@ const GithubAsset *GithubRelease::GetDownloadForCurrentArchitecture() const
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
|
||||
UpdateRelease::UpdateRelease()
|
||||
{
|
||||
}
|
||||
|
||||
std::string Updater::GetSignatureUrl(const std::string &url)
|
||||
{
|
||||
|
||||
// partialy whitelisting, partly avoiding having to parse a URL.
|
||||
if (this->currentResult.releaseOnlyRelease_.UpdateUrl() == url)
|
||||
{
|
||||
return this->currentResult.releaseOnlyRelease_.GpgSignatureUrl();
|
||||
}
|
||||
if (this->currentResult.releaseOrBetaRelease_.UpdateUrl() == url)
|
||||
{
|
||||
return this->currentResult.releaseOrBetaRelease_.GpgSignatureUrl();
|
||||
}
|
||||
if (this->currentResult.devRelease_.UpdateUrl() == url)
|
||||
{
|
||||
return this->currentResult.devRelease_.GpgSignatureUrl();
|
||||
}
|
||||
throw std::runtime_error("Permission denied. No signature URL.");
|
||||
}
|
||||
|
||||
std::string Updater::GetUpdateFilename(const std::string &url)
|
||||
{
|
||||
std::lock_guard lock(mutex);
|
||||
|
||||
// partialy whitelisting, partly avoiding having to parse a URL.
|
||||
if (this->currentResult.releaseOnlyRelease_.UpdateUrl() == url)
|
||||
@@ -624,7 +659,6 @@ std::string Updater::GetUpdateFilename(const std::string &url)
|
||||
return this->currentResult.devRelease_.AssetName();
|
||||
}
|
||||
throw std::runtime_error("Permission denied. Invalid url.");
|
||||
|
||||
}
|
||||
static std::string unCRLF(const std::string &text)
|
||||
{
|
||||
@@ -633,9 +667,11 @@ static std::string unCRLF(const std::string &text)
|
||||
{
|
||||
if (c == '\r')
|
||||
continue;
|
||||
if (c == '\n') {
|
||||
if (c == '\n')
|
||||
{
|
||||
ss << '/';
|
||||
} else
|
||||
}
|
||||
else
|
||||
{
|
||||
ss << c;
|
||||
}
|
||||
@@ -645,80 +681,207 @@ static std::string unCRLF(const std::string &text)
|
||||
|
||||
static void removeOldSiblings(int numberToKeep, const std::filesystem::path &fileToKeep)
|
||||
{
|
||||
namespace fs = std::filesystem;
|
||||
|
||||
auto extension = fileToKeep.extension();
|
||||
auto directory = fileToKeep.parent_path();
|
||||
if (directory.empty()) return; // superstition.
|
||||
struct RemoveEntry {
|
||||
if (directory.empty())
|
||||
return; // superstition.
|
||||
struct RemoveEntry
|
||||
{
|
||||
fs::path path;
|
||||
fs::file_time_type time;
|
||||
};
|
||||
std::vector<RemoveEntry> entries;
|
||||
for (const auto&dirEntry : fs::directory_iterator(directory))
|
||||
for (const auto &dirEntry : fs::directory_iterator(directory))
|
||||
{
|
||||
if (dirEntry.is_regular_file())
|
||||
{
|
||||
if (dirEntry.path() != fileToKeep)
|
||||
auto thisPath = dirEntry.path();
|
||||
if (thisPath != fileToKeep)
|
||||
{
|
||||
dirEntry.last_write_time();
|
||||
entries.push_back(RemoveEntry { .path = dirEntry.path(), .time = dirEntry.last_write_time()});
|
||||
if (thisPath.extension() == extension)
|
||||
{
|
||||
entries.push_back(
|
||||
RemoveEntry{
|
||||
.path = thisPath,
|
||||
.time = dirEntry.last_write_time()});
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
std::sort(
|
||||
entries.begin(),entries.end(),
|
||||
[](const RemoveEntry&left, const RemoveEntry&right)
|
||||
entries.begin(), entries.end(),
|
||||
[](const RemoveEntry &left, const RemoveEntry &right)
|
||||
{
|
||||
return left.time > right.time; // by time descending
|
||||
}
|
||||
);
|
||||
});
|
||||
for (size_t i = numberToKeep; i < entries.size(); ++i)
|
||||
{
|
||||
fs::remove(entries[i].path);
|
||||
}
|
||||
}
|
||||
std::filesystem::path Updater::DownloadUpdate(const std::string &url)
|
||||
|
||||
static std::string getKeyId(const std::string&gpgText)
|
||||
{
|
||||
namespace fs = std::filesystem;
|
||||
std::string filename = GetUpdateFilename(url);
|
||||
if (filename.empty())
|
||||
std::string keyPosition = "using RSA key ";
|
||||
size_t nPos = gpgText.find(keyPosition);
|
||||
if (nPos = std::string::npos)
|
||||
{
|
||||
throw std::runtime_error("Permission denied. Invalid url.");
|
||||
}
|
||||
return "";
|
||||
}
|
||||
nPos += keyPosition.size();
|
||||
|
||||
auto start = nPos;
|
||||
|
||||
while (true)
|
||||
{
|
||||
char c = gpgText[nPos];
|
||||
if (nPos >= gpgText.length() || c == ' ' || c == '\r' && c == '\n' && c == '\t')
|
||||
{
|
||||
break;
|
||||
}
|
||||
++nPos;
|
||||
}
|
||||
return gpgText.substr(start,nPos-start);
|
||||
}
|
||||
static std::string getAddress(const std::string&gpgText)
|
||||
{
|
||||
std::string originPosition = "gpg: Good signature from \"";
|
||||
|
||||
size_t nPos = gpgText.find(originPosition);
|
||||
if (nPos == std::string::npos)
|
||||
{
|
||||
return "";
|
||||
}
|
||||
auto start = nPos;
|
||||
while (true)
|
||||
{
|
||||
if (nPos >= gpgText.length() || gpgText[nPos] == '\"' || gpgText[nPos] == '\r' || gpgText[nPos] == '\n')
|
||||
{
|
||||
break;
|
||||
}
|
||||
++nPos;
|
||||
}
|
||||
return gpgText.substr(start,nPos-start);
|
||||
|
||||
}
|
||||
void Updater::ValidateSignature(const std::filesystem::path&file, const std::filesystem::path&signatureFile)
|
||||
{
|
||||
// sudo gpg --home /var/pipedal/config/gpg --verify pipedal_1.2.41_arm64.deb.asc pipedal_1.2.41_arm64.deb
|
||||
// gpg: assuming signed data in 'pipedal_1.2.41_arm64.deb'
|
||||
// gpg: Signature made Tue 27 Aug 2024 09:25:06 PM EDT
|
||||
// gpg: using RSA key 381124E2BB4478D225D2313B2AEF3F7BD53EAA59
|
||||
// gpg: Good signature from "Robin Davies <rerdavies@gmail.com>" [ultimate]
|
||||
std::ostringstream ss;
|
||||
ss << "-home " << PGP_UPDATE_KEYRING_PATH
|
||||
<< " --verify "
|
||||
<< signatureFile << " " << file;
|
||||
|
||||
auto gpgOutput = sysExecForOutput("/usr/bin/gpg",ss.str());
|
||||
if (gpgOutput.exitCode != EXIT_SUCCESS)
|
||||
{
|
||||
throw std::runtime_error("Update file is not correctly signed.");
|
||||
}
|
||||
const std::string&gpgText = gpgOutput.output;
|
||||
|
||||
std::string keyId = getKeyId(gpgText);
|
||||
|
||||
if (keyId != UPDATE_GPG_FINGERPRINT)
|
||||
{
|
||||
throw std::runtime_error("Update signature has the wrong fingerprint.");
|
||||
}
|
||||
std::string origin = getAddress(gpgText);
|
||||
if (origin != UPDATE_GPG_ADDRESS)
|
||||
{
|
||||
throw std::runtime_error("Update signature has an incorrect address.");
|
||||
}
|
||||
}
|
||||
|
||||
static bool badOutput(const std::filesystem::path &filePath)
|
||||
{
|
||||
if (!fs::is_regular_file(filePath))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
return fs::file_size(filePath) != 0;
|
||||
}
|
||||
void Updater::DownloadUpdate(const std::string &url, std::filesystem::path *file, std::filesystem::path *signatureFile)
|
||||
{
|
||||
std::string filename, signatureUrl;
|
||||
{
|
||||
std::lock_guard lock{mutex};
|
||||
filename = GetUpdateFilename(url);
|
||||
signatureUrl = GetSignatureUrl(url);
|
||||
}
|
||||
|
||||
// Only permit downloading of updates from the github releases for the pipedal project.
|
||||
// I don't think this will get past GetUpdateFileName, but defense in depth. We will
|
||||
// not download from anywhere we don't trust.
|
||||
if (!WhitelistDownloadUrl(url))
|
||||
{
|
||||
throw std::runtime_error(SS("Invalid update url. Downloads from this address are not permitted: " << url));
|
||||
}
|
||||
if (!WhitelistDownloadUrl(signatureUrl))
|
||||
{
|
||||
throw std::runtime_error(SS("Invalid update url. Downloads from this address are not permitted: " << signatureUrl));
|
||||
}
|
||||
auto downloadDirectory = WORKING_DIRECTORY / "downloads";
|
||||
std::filesystem::create_directories(downloadDirectory);
|
||||
|
||||
auto downloadPath = downloadDirectory / filename;
|
||||
auto downloadFilePath = downloadDirectory / filename;
|
||||
auto downloadSignaturePath = downloadDirectory / SS(filename << ".asc");
|
||||
|
||||
try {
|
||||
fs::remove(downloadPath);
|
||||
std::string args = SS("-s -L " << url << " -o " << downloadPath << " 2>&1");
|
||||
try
|
||||
{
|
||||
fs::remove(downloadFilePath);
|
||||
fs::remove(downloadSignaturePath);
|
||||
|
||||
std::string args = SS("-s -L " << url << " -o " << downloadFilePath << " 2>&1");
|
||||
auto curlOutput = sysExecForOutput("curl", args);
|
||||
if (curlOutput.exitCode != EXIT_SUCCESS)
|
||||
if (curlOutput.exitCode != EXIT_SUCCESS || badOutput(downloadFilePath))
|
||||
{
|
||||
Lv2Log::error(SS("Update download failed." << unCRLF(curlOutput.output)));
|
||||
throw std::runtime_error("PiPedal server does not have access to the internet.");
|
||||
}
|
||||
if (!fs::exists(downloadPath) || fs::file_size(downloadPath) == 0)
|
||||
|
||||
args = SS("-s -L " << signatureUrl << " -o " << downloadSignaturePath << " 2>&1");
|
||||
|
||||
curlOutput = sysExecForOutput("curl", args);
|
||||
if (curlOutput.exitCode != EXIT_SUCCESS || badOutput(downloadSignaturePath))
|
||||
{
|
||||
throw std::runtime_error("Download failed.");
|
||||
Lv2Log::error(SS("Update download failed." << unCRLF(curlOutput.output)));
|
||||
throw std::runtime_error("PiPedal server does not have access to the internet.");
|
||||
}
|
||||
try {
|
||||
removeOldSiblings(2, downloadPath);
|
||||
} catch (const std::exception&e)
|
||||
|
||||
try
|
||||
{
|
||||
removeOldSiblings(2, downloadFilePath);
|
||||
removeOldSiblings(2, downloadSignaturePath);
|
||||
}
|
||||
catch (const std::exception &e)
|
||||
{
|
||||
Lv2Log::error(SS("Can't remove download siblings" << e.what()));
|
||||
// and carry on.
|
||||
}
|
||||
return downloadPath;
|
||||
} catch (const std::exception &e)
|
||||
|
||||
// Can't only be performed under pipedal_d or root accouts.
|
||||
// This is as far as you can go while debugging.
|
||||
|
||||
// The admin process will actually check the signature again running as root; but this is our last
|
||||
// chance to give a reasonable error message, so do it here as well, because any subsequent errors
|
||||
// can only go to systemd logs.
|
||||
ValidateSignature(downloadFilePath,downloadSignaturePath);
|
||||
*file = downloadFilePath;
|
||||
*signatureFile = downloadSignaturePath;
|
||||
return;
|
||||
}
|
||||
catch (const std::exception &e)
|
||||
{
|
||||
std::filesystem::remove(downloadPath);
|
||||
std::filesystem::remove(downloadFilePath);
|
||||
std::filesystem::remove(downloadSignaturePath);
|
||||
throw;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
///////////////////////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
JSON_MAP_BEGIN(UpdateRelease)
|
||||
@@ -727,6 +890,7 @@ JSON_MAP_REFERENCE(UpdateRelease, upgradeVersion)
|
||||
JSON_MAP_REFERENCE(UpdateRelease, upgradeVersionDisplayName)
|
||||
JSON_MAP_REFERENCE(UpdateRelease, assetName)
|
||||
JSON_MAP_REFERENCE(UpdateRelease, updateUrl)
|
||||
JSON_MAP_REFERENCE(UpdateRelease, gpgSignatureUrl)
|
||||
JSON_MAP_END();
|
||||
|
||||
JSON_MAP_BEGIN(UpdateStatus)
|
||||
|
||||
+7
-1
@@ -52,6 +52,7 @@ namespace pipedal
|
||||
std::string upgradeVersionDisplayName_; // display name for the version.
|
||||
std::string assetName_; // filename only
|
||||
std::string updateUrl_; // url from which to download the .deb file.
|
||||
std::string gpgSignatureUrl_; // url from which to download the .deb.asc file.
|
||||
public:
|
||||
UpdateRelease();
|
||||
|
||||
@@ -60,6 +61,7 @@ namespace pipedal
|
||||
const std::string &UpgradeVersionDisplayName() const { return upgradeVersionDisplayName_; }
|
||||
const std::string &AssetName() const { return assetName_; }
|
||||
const std::string &UpdateUrl() const { return updateUrl_; }
|
||||
const std::string &GpgSignatureUrl() const { return gpgSignatureUrl_;}
|
||||
bool operator==(const UpdateRelease &other) const;
|
||||
DECLARE_JSON_MAP(UpdateRelease);
|
||||
};
|
||||
@@ -108,6 +110,9 @@ namespace pipedal
|
||||
public:
|
||||
Updater();
|
||||
~Updater();
|
||||
|
||||
static void ValidateSignature(const std::filesystem::path&file, const std::filesystem::path&signatureFile);
|
||||
|
||||
using UpdateListener = std::function<void(const UpdateStatus &upateResult)>;
|
||||
|
||||
void SetUpdateListener(UpdateListener &&listener);
|
||||
@@ -117,9 +122,10 @@ namespace pipedal
|
||||
UpdatePolicyT GetUpdatePolicy();
|
||||
void SetUpdatePolicy(UpdatePolicyT updatePolicy);
|
||||
void ForceUpdateCheck();
|
||||
std::filesystem::path DownloadUpdate(const std::string &url);
|
||||
void DownloadUpdate(const std::string &url, std::filesystem::path*file, std::filesystem::path*signatureFile);
|
||||
private:
|
||||
std::string GetUpdateFilename(const std::string &url);
|
||||
std::string GetSignatureUrl(const std::string &url);
|
||||
|
||||
UpdatePolicyT updatePolicy = UpdatePolicyT::ReleaseOrBeta;
|
||||
using UpdateReleasePredicate = std::function<bool(const GithubRelease &githubRelease)>;
|
||||
|
||||
@@ -0,0 +1,61 @@
|
||||
/*
|
||||
* MIT License
|
||||
*
|
||||
* Copyright (c) 2022 Robin E. R. Davies
|
||||
*
|
||||
* Permission is hereby granted, free of charge, to any person obtaining a copy of
|
||||
* this software and associated documentation files (the "Software"), to deal in
|
||||
* the Software without restriction, including without limitation the rights to
|
||||
* use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies
|
||||
* of the Software, and to permit persons to whom the Software is furnished to do
|
||||
* so, subject to the following conditions:
|
||||
*
|
||||
* The above copyright notice and this permission notice shall be included in all
|
||||
* copies or substantial portions of the Software.
|
||||
*
|
||||
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
* SOFTWARE.
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
|
||||
/*
|
||||
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.
|
||||
|
||||
If you don't, PiPedal will prompt to update your installs with the latest
|
||||
installs published on github/rerdavies/pipedal.
|
||||
*/
|
||||
|
||||
#ifndef GITHUB_PROJECT
|
||||
#define GITHUB_PROJECT "rerdavies/pipedal"
|
||||
#endif
|
||||
|
||||
|
||||
// public key info for the private key you used to sign .deb files.
|
||||
|
||||
#ifndef UPDATE_GPG_ADDRESS
|
||||
#define UPDATE_GPG_ADDRESS "Robin Davies <rerdavies@gmail.com>"
|
||||
#endif
|
||||
|
||||
#ifndef UPDATE_GPG_FINGERPRINT
|
||||
#define UPDATE_GPG_FINGERPRINT "381124E2BB4478D225D2313B2AEF3F7BD53EAA59"
|
||||
#endif
|
||||
|
||||
// Configuration for downloading of updates.
|
||||
|
||||
#define GITHUB_DOWNLOAD_PREFIX "https://github.com/" GITHUB_PROJECT "/releases/"
|
||||
|
||||
#define GITHUB_RELEASES_URL "https://api.github.com/repos/" GITHUB_PROJECT "/releases"
|
||||
|
||||
#define PGP_UPDATE_KEYRING_PATH "/var/pipedal/config/gpg"
|
||||
|
||||
inline bool WhitelistDownloadUrl(const std::string &downloadUrl)
|
||||
{
|
||||
return downloadUrl.starts_with(GITHUB_DOWNLOAD_PREFIX);
|
||||
}
|
||||
Reference in New Issue
Block a user