Compare commits

..

7 Commits

Author SHA1 Message Date
Lion Kortlepel a665e6042b fix postfix formatting 2024-01-08 11:52:17 +01:00
Lion Kortlepel eda78f7e57 leave out version if not exists 2024-01-08 11:50:26 +01:00
Lion Kortlepel f0861f7927 adjust update to use new binary naming format 2024-01-08 11:25:23 +01:00
Lion Kortlepel 5efbbaa901 fixed for self update 2023-12-29 00:26:24 +01:00
Lion Kortlepel 38494bb33a remove .delete_me file on startup if it exists
this is an artifact from the --update process on windows
2023-12-29 00:02:56 +01:00
Lion Kortlepel a89b51ca50 add self-update option via --update 2023-12-29 00:00:43 +01:00
Lion Kortlepel b4d6f90012 remove custom ip functionality
because it never worked
2023-12-28 17:20:31 +01:00
8 changed files with 281 additions and 19 deletions
+2
View File
@@ -48,6 +48,7 @@ set(PRJ_HEADERS
include/TScopedTimer.h
include/TServer.h
include/VehicleData.h
include/Update.h
)
# add all source files (.cpp) to this, except the one with main()
set(PRJ_SOURCES
@@ -70,6 +71,7 @@ set(PRJ_SOURCES
src/TScopedTimer.cpp
src/TServer.cpp
src/VehicleData.cpp
src/Update.cpp
)
find_package(Lua REQUIRED)
+4 -6
View File
@@ -52,22 +52,20 @@ You can find precompiled binaries under [Releases](https://github.com/BeamMP/Bea
On Linux, you need some dependencies to **build** the server (on Windows, you don't):
For Debian, Ubuntu and others you can find scripts that do this in `scripts/`.
```
liblua5.3-dev curl zip unzip tar cmake make git g++
```
You can install these with your distribution's package manager. You will need sudo or need root for ONLY this step.
The names of each package may change depending on your platform.
If you are building for ARM (like aarch64), you need to run `export VCPKG_FORCE_SYSTEM_BINARIES=1` before the following commands.
The names of each package may change depending on your platform. See in `scripts/` or use a search engine to find out what they're called for you.
You can build on **Windows, Linux** or other platforms by following these steps:
1. Check out the repository with git: `git clone --recursive https://github.com/BeamMP/BeamMP-Server`.
2. Go into the directory `cd BeamMP-Server`.
3. Run CMake `cmake -S . -B bin -DCMAKE_BUILD_TYPE=Release` - this can take a few minutes and may take a lot of disk space and bandwidth.
4. Build via `cmake --build bin --parallel --config Release -t BeamMP-Server`.
4. Build via `cmake --build bin --parallel -t BeamMP-Server`.
5. Your executable can be found in `bin/`.
When you make changes to the code, you only have to run step 4 again.
+1 -1
View File
@@ -134,7 +134,7 @@ private:
static inline std::mutex mShutdownHandlersMutex {};
static inline std::deque<TShutdownHandler> mShutdownHandlers {};
static inline Version mVersion { 3, 2, 1 };
static inline Version mVersion { 3, 2, 0 };
};
void SplitString(std::string const& str, const char delim, std::vector<std::string>& out);
+9
View File
@@ -0,0 +1,9 @@
#pragma once
#include <string>
namespace Update {
[[noreturn]] void PerformUpdate(const std::string& InvokedAs);
}
-11
View File
@@ -80,17 +80,6 @@ TEST_CASE("GetPidVid") {
TServer::TServer(const std::vector<std::string_view>& Arguments) {
beammp_info("BeamMP Server v" + Application::ServerVersionString());
Application::SetSubsystemStatus("Server", Application::Status::Starting);
if (Arguments.size() > 1) {
Application::Settings.CustomIP = Arguments[0];
size_t n = std::count(Application::Settings.CustomIP.begin(), Application::Settings.CustomIP.end(), '.');
auto p = Application::Settings.CustomIP.find_first_not_of(".0123456789");
if (p != std::string::npos || n != 3 || Application::Settings.CustomIP.substr(0, 3) == "127") {
Application::Settings.CustomIP.clear();
beammp_warn("IP Specified is invalid! Ignoring");
} else {
beammp_info("server started with custom IP");
}
}
Application::SetSubsystemStatus("Server", Application::Status::Good);
}
+249
View File
@@ -0,0 +1,249 @@
#include "Update.h"
#include "Common.h"
#include <algorithm>
#include <boost/predef.h>
#include <cerrno>
#include <cstdio>
#include <cstdlib>
#include <filesystem>
#include <httplib.h>
#include <nlohmann/json.hpp>
#if defined(__linux)
#include <unistd.h>
#elif defined(WIN32)
#include <windows.h>
#endif
static bool ProgressReport(uint64_t current, uint64_t total) {
if (current == total) {
fmt::print("100% ({:.2f} / {:.2f} KiB)\n", float(current) / 1024.0f, float(total) / 1024.0f);
}
if (total != 0) {
auto percent = (float(current) / float(total)) * 100.0f;
fmt::print("{:3.0f}% ({:.2f} / {:.2f} KiB)\r", percent, float(current) / 1024.0f, float(total) / 1024.0f);
}
return true;
}
void Update::PerformUpdate(const std::string& InvokedAs) {
using json = nlohmann::json;
namespace http = httplib;
constexpr auto GH = "api.github.com";
std::string ReleasesAPI = "/repos/BeamMP/BeamMP-Server/releases";
http::Headers APIHeaders {};
APIHeaders.emplace("X-GitHub-Api-Version", "2022-11-28");
APIHeaders.emplace("Accept", "application/vnd.github+json");
http::SSLClient c(GH);
c.set_read_timeout(std::chrono::seconds(30));
beammp_infof("Checking for latest release...");
// check if there is a new release
auto Res = c.Get(ReleasesAPI + "/latest", APIHeaders);
if (!Res || Res->status < 200 || Res->status >= 300) {
beammp_errorf("Failed to fetch latest release: {}", to_string(Res.error()));
std::exit(1);
}
Version NewVersion { 0, 0, 0 };
json ReleaseInfo;
try {
ReleaseInfo = json::parse(Res->body);
std::string TagName = ReleaseInfo["tag_name"].get<std::string>();
if (!TagName.starts_with("v") || std::count(TagName.begin(), TagName.end(), '.') != 2) {
beammp_errorf("Invalid version provided by GitHub: '{}', exiting", TagName);
std::exit(1);
}
TagName = TagName.substr(1);
auto Tag = Application::VersionStrToInts(TagName);
NewVersion = Version { Tag[0], Tag[1], Tag[2] };
beammp_infof("Latest release is v{}", NewVersion.AsString());
} catch (const std::exception& e) {
beammp_errorf("Failed to fetch latest release info from GitHub");
beammp_errorf("Error: {}", e.what());
std::exit(1);
}
// check if the new release is patch, minor or major
auto Current = Application::ServerVersion();
bool MajorOutdated = NewVersion.major > Current.major;
bool MinorOutdated = !MajorOutdated && Application::IsOutdated(Current, NewVersion);
if (!MajorOutdated && !MinorOutdated) {
beammp_infof("BeamMP-Server is already the latest version (v{})!", Current.AsString());
std::exit(0);
} else {
beammp_infof("New update available, updating from v{} to v{}", Current.AsString(), NewVersion.AsString());
}
std::string Arch = "";
#if BOOST_ARCH_ARM && BOOST_ARCH_WORD_BITS == 64
Arch = "arm64";
#elif BOOST_ARCH_X86_64
Arch = "x86_64";
#else
beammp_errorf("The current architecture is not supported, please update manually.");
std::exit(1);
#endif
std::string Postfix = {};
// figure out current platform
#if WIN32
beammp_infof("Current platform is Windows");
Postfix = ".exe";
#elif __linux
beammp_infof("Current platform is Linux, checking distribution");
const std::string OsReleasePath = "/etc/os-release";
std::string DistroID = "";
std::string DistroVersion = "";
if (fs::exists(OsReleasePath)) {
std::ifstream OsRelease(OsReleasePath);
std::string Line {};
while (std::getline(OsRelease, Line)) {
if (Line.starts_with("ID=")) {
DistroID = Line.substr(3);
} else if (Line.starts_with("VERSION_ID=\"")) {
DistroVersion = Line.substr(strlen("VERSION_ID=\""));
// skip closing quote
DistroVersion = DistroVersion.substr(0, DistroVersion.size() - 1);
} else if (Line.starts_with("VERSION_ID=")) {
DistroVersion = Line.substr(strlen("VERSION_ID="));
}
}
}
beammp_infof("Distribution: {} {}", DistroID, DistroVersion);
std::string Distro = {};
if (DistroVersion.empty()) {
Distro = DistroID;
} else {
Distro = DistroID + "." + DistroVersion;
}
Postfix = fmt::format(".{}.{}", Distro, Arch);
#else
beammp_infof("BeamMP doesn't provide binaries for this OS, please update manually");
std::exit(1);
#endif
beammp_infof("Looking for BeamMP-Server{}", Postfix);
// check if the release exists for that platform
std::string DownloadURL = "";
try {
for (const auto& Asset : ReleaseInfo.at("assets")) {
if (Asset.at("name").get<std::string>() == "BeamMP-Server" + Postfix) {
DownloadURL = Asset.at("browser_download_url");
break;
}
}
} catch (const std::exception& e) {
beammp_errorf("Failed to parse GitHub API's release assets: {}", e.what());
std::exit(1);
}
if (DownloadURL.empty()) {
beammp_infof("BeamMP doesn't provide binaries for this platform or distribution (release 'BeamMP-Server{}' not found in the release assets), please update manually", Postfix);
std::exit(1);
}
// download urls exist, ask if the user wants to do a major update
if (MajorOutdated) {
beammp_warnf("The update from v{} to v{} is a major update, which is likely to *break* any Lua Plugins. Please make sure you have read the release notes at {} before proceeding!", Current.AsString(), NewVersion.AsString(), ReleaseInfo.at("html_url").get<std::string>());
#if !defined(WIN32)
if (!isatty(STDIN_FILENO)) {
beammp_errorf("Refusing to do a major version update non-interactively. Run this again in a TTY or update manually");
std::exit(1);
}
#endif
fmt::print("\n");
int ch;
do {
fmt::print("Do you wish to proceed with this update? [y/n] ");
std::string Input;
std::getline(std::cin, Input);
if (Input.empty()) {
continue;
}
ch = Input.at(0);
} while (tolower(ch) != 'y' && tolower(ch) != 'n');
if (tolower(ch) == 'n') {
beammp_error("Cancelling update");
std::exit(2);
}
}
beammp_info("Downloading latest release from github.com...");
http::SSLClient DlClient("github.com");
DlClient.set_follow_location(true);
auto ReleaseRes = DlClient.Get(DownloadURL.substr(strlen("https://github.com")), ProgressReport);
if (!ReleaseRes || ReleaseRes->status < 200 || ReleaseRes->status >= 300) {
beammp_errorf("Failed to fetch binary: {}. Please update manually", to_string(ReleaseRes.error()));
std::exit(1);
}
beammp_info("Download complete!");
auto Temp = InvokedAs + ".temp";
beammp_infof("Creating '{}'", Temp);
FILE* Out = std::fopen(Temp.c_str(), "w+");
if (!Out) {
beammp_errorf("Failed to update executable, because a temporary file couldn't be created: {}", std::strerror(errno));
std::exit(1);
}
auto n = std::fwrite(ReleaseRes->body.data(), 1, ReleaseRes->body.size(), Out);
if (n != ReleaseRes->body.size()) {
beammp_errorf("Failed to update executable, because a temporary file couldn't be written to: {}", std::strerror(errno));
std::fclose(Out);
std::exit(1);
}
std::fclose(Out);
#if defined(__linux)
beammp_infof("Removing '{}'", InvokedAs);
struct stat st;
if (stat(InvokedAs.c_str(), &st) != 0) {
// shouldn't happen at this point
beammp_errorf("Failed to stat original executable: {}", std::strerror(errno));
std::exit(1);
}
auto Ret = unlink(InvokedAs.c_str());
if (Ret != 0) {
beammp_errorf("Failed to remove executable: {}", std::strerror(errno));
std::exit(1);
}
beammp_infof("Replacing '{}' with '{}'", InvokedAs, Temp);
fs::rename(Temp, InvokedAs);
if (chmod(InvokedAs.c_str(), st.st_mode) != 0) {
beammp_warnf("Failed to set file mode to 0{:o}: {}. File may not be executable.", st.st_mode, std::strerror(errno));
}
#elif defined(WIN32)
auto DeleteMe = InvokedAs + ".delete_me";
std::filesystem::rename(InvokedAs, DeleteMe);
std::filesystem::rename(Temp, InvokedAs);
int Attr = GetFileAttributesA(DeleteMe.c_str());
if ((Attr & FILE_ATTRIBUTE_HIDDEN) == 0) {
SetFileAttributesA(DeleteMe.c_str(), Attr | FILE_ATTRIBUTE_HIDDEN);
}
#else
beammp_error("Not implemented");
std::exit(4);
#endif
// make sure the user knows that it was a success, on windows wait for return???
beammp_infof("SUCCESSFULLY UPDATED to v{}. Please launch the server manually to start the new version!", NewVersion.AsString());
std::exit(0);
}
+15
View File
@@ -11,7 +11,9 @@
#include "TPluginMonitor.h"
#include "TResourceManager.h"
#include "TServer.h"
#include "Update.h"
#include <filesystem>
#include <iostream>
#include <thread>
@@ -33,6 +35,9 @@ ARGUMENTS:
including the path given in --config.
--version
Prints version info and exits.
--update
Starts an interactive update to the newest
version of BeamMP-Server.
EXAMPLES:
BeamMP-Server --config=../MyWestCoastServerConfig.toml
@@ -72,6 +77,7 @@ int BeamMPServerMain(MainArguments Arguments) {
ArgsParser Parser;
Parser.RegisterArgument({ "help" }, ArgsParser::NONE);
Parser.RegisterArgument({ "version" }, ArgsParser::NONE);
Parser.RegisterArgument({ "update" }, ArgsParser::NONE);
Parser.RegisterArgument({ "config" }, ArgsParser::HAS_VALUE);
Parser.RegisterArgument({ "working-directory" }, ArgsParser::HAS_VALUE);
Parser.Parse(Arguments.List);
@@ -86,6 +92,15 @@ int BeamMPServerMain(MainArguments Arguments) {
Application::Console().WriteRaw("BeamMP-Server v" + Application::ServerVersionString());
return 0;
}
if (Parser.FoundArgument({ "update" })) {
Update::PerformUpdate(Arguments.InvokedAs);
return 123;
}
// check that there is no old update file
auto OldFile = Arguments.InvokedAs + ".delete_me";
if (fs::exists(OldFile)) {
fs::remove(OldFile);
}
std::string ConfigPath = "ServerConfig.toml";
if (Parser.FoundArgument({ "config" })) {