Basic AppImage Updater

This commit is contained in:
TheBrokenRail 2025-01-03 15:03:30 -05:00
parent b055980103
commit 00fee9c410
9 changed files with 166 additions and 7 deletions

View File

@ -76,6 +76,9 @@ if(BUILD_ARM_COMPONENTS)
endif()
cmake_language(EVAL CODE "${COMPILE_FLAGS_SETUP}")
# Build Dependencies
add_subdirectory(dependencies)
# Fast Math
add_compile_options(-ffast-math)
@ -91,9 +94,6 @@ if(CMAKE_C_COMPILER_ID STREQUAL "GNU")
endif()
endif()
# Build Dependencies
add_subdirectory(dependencies)
# Build libreborn
add_subdirectory(libreborn)

View File

@ -291,10 +291,12 @@ void ConfigurationUI::draw_about() {
Updater *updater = Updater::instance;
if (updater) {
ImGui::Separator();
ImGui::BeginDisabled(!updater->can_start());
draw_right_aligned_buttons({updater->get_status().c_str()}, [&updater](__attribute__((unused)) int id, const bool was_clicked) {
if (was_clicked) {
updater->start();
}
}, true);
ImGui::EndDisabled();
}
}

View File

@ -9,6 +9,7 @@
#include "logger/logger.h"
#include "util/util.h"
#include "client/configuration.h"
#include "updater/updater.h"
// Bind Options To Environmental Variable
static void bind_to_env(const char *env, const bool value) {
@ -39,12 +40,34 @@ static void setup_environment(const options_t &options) {
// Non-Launch Commands
static void handle_non_launch_commands(const options_t &options) {
// SDK
if (options.copy_sdk) {
const std::string binary_directory = get_binary_directory();
copy_sdk(binary_directory, true);
fflush(stdout);
exit(EXIT_SUCCESS);
}
// Updater
if (options.run_update) {
Updater *updater = Updater::instance;
if (updater) {
updater->update();
if (updater->status == ERROR) {
ERR("Unable To Update");
} else if (updater->status == UP_TO_DATE) {
INFO("Already Up-To-Date");
} else {
if (updater->status != RESTART_NEEDED) {
IMPOSSIBLE();
}
INFO("Update Completed");
}
} else {
ERR("Built-In Updater Unavailable, Use System Package Manager");
}
fflush(stdout);
exit(EXIT_SUCCESS);
}
}
// Start The Game

View File

@ -10,4 +10,5 @@ OPTION(only_generate, "only-generate", -8, "Generate World And Exit (Server-Mode
OPTION(force_headless, "force-headless", -9, "Force Disable Game Rendering")
OPTION(force_non_headless, "force-non-headless", -10, "Force Enable Game Rendering")
OPTION(server_mode, "server", -11, "Run In Server-Mode")
OPTION(skip_pagesize_check, "skip-pagesize-check", -12, "Skip Page-Size Check (Not Recommended)")
OPTION(skip_pagesize_check, "skip-pagesize-check", -12, "Skip Page-Size Check (Not Recommended)")
OPTION(run_update, "update", -13, "Run Updater (If Available)")

View File

@ -0,0 +1,122 @@
#include <cstdlib>
#include <optional>
#include <utility>
#include <unistd.h>
#include <sys/stat.h>
#include <libreborn/log.h>
#include <libreborn/util/exec.h>
#include <libreborn/config.h>
#include "updater.h"
// Implement
struct AppImageUpdater final : Updater {
void update() override;
void restart() override;
static AppImageUpdater instance;
};
AppImageUpdater AppImageUpdater::instance;
// Update
template <typename... Args>
static std::optional<std::string> run_wget(Args... args) {
int status = 0;
const char *const command[] = {"wget", "-O", std::forward<Args>(args)..., nullptr};
const std::vector<unsigned char> *output = run_command(command, &status);
std::string output_str = (const char *) output->data();
delete output;
if (!is_exit_status_success(status)) {
return std::nullopt;
} else {
return output_str;
}
}
static std::string extract_from_json(const std::string &json_str, const std::string &key) {
std::string::size_type pos = json_str.find(key);
std::array<std::string::size_type, 3> indices = {};
unsigned int i = 0;
while (true) {
if (pos == std::string::npos) {
return "";
}
if (i >= indices.size()) {
break;
}
pos = json_str.find('"', pos) + 1;
indices[i++] = pos;
}
const std::string::size_type start = indices[1];
const std::string::size_type end = indices[2];
return json_str.substr(start, end - start - 1);
}
static const char *get_appimage_path() {
const char *path = getenv("APPIMAGE");
if (path == nullptr) {
IMPOSSIBLE();
}
return path;
}
void AppImageUpdater::update() {
// Check
if (status != CHECKING) {
IMPOSSIBLE();
}
const std::optional<std::string> json = run_wget("-", MCPI_APPIMAGE_JSON_URL);
if (!json.has_value()) {
status = ERROR;
return;
}
const std::string tag_name = extract_from_json(json.value(), "tag_name");
// Check Version
if (tag_name == MCPI_VERSION) {
status = UP_TO_DATE;
return;
}
// Get URL
std::string url = MCPI_APPIMAGE_DOWNLOAD_URL;
while (true) {
const std::string placeholder = MCPI_APPIMAGE_VERSION_PLACEHOLDER;
const std::string::size_type pos = url.find(placeholder);
if (pos == std::string::npos) {
break;
}
url.replace(pos, placeholder.size(), tag_name);
}
// Get Path
const char *appimage_path = get_appimage_path();
const std::string new_appimage_path_base = std::string(appimage_path) + ".new";
std::string new_appimage_path = new_appimage_path_base;
int num = 1;
while (access(new_appimage_path.c_str(), F_OK) != -1) {
new_appimage_path = new_appimage_path_base + '.' + std::to_string(num++);
}
// Download
status = DOWNLOADING;
const std::optional<std::string> out = run_wget(new_appimage_path.c_str(), url.c_str());
bool ret = out.has_value();
if (ret) {
ret = chmod(new_appimage_path.c_str(), S_IRWXU | S_IRGRP | S_IXGRP | S_IROTH | S_IXOTH) == 0;
}
if (ret) {
ret = rename(new_appimage_path.c_str(), appimage_path) == 0;
}
if (!ret) {
unlink(new_appimage_path.c_str());
status = ERROR;
return;
}
// Done
status = RESTART_NEEDED;
}
// Restart
void AppImageUpdater::restart() {
const char *const command[] = {get_appimage_path(), nullptr};
safe_execvpe(command, environ);
}

View File

@ -20,6 +20,7 @@ std::string Updater::get_status() const {
case CHECKING: return "Checking...";
case UP_TO_DATE: return "Up-To-Date";
case DOWNLOADING: return "Downloading...";
case ERROR: return "Error";
default: return "";
}
}
@ -33,6 +34,7 @@ static void *update_thread(void *data) {
void Updater::start() {
switch (status) {
case NOT_STARTED: {
status = CHECKING;
pthread_t thread;
pthread_create(&thread, nullptr, update_thread, this);
break;

View File

@ -8,7 +8,8 @@ enum UpdateStatus {
CHECKING,
UP_TO_DATE,
DOWNLOADING,
RESTART_NEEDED
RESTART_NEEDED,
ERROR
};
// Updater

View File

@ -142,7 +142,7 @@ std::vector<unsigned char> *run_command(const char *const command[], int *exit_s
// Child Process
reborn_debug_tag = CHILD_PROCESS_TAG;
// Run
safe_execvpe(command, (const char *const *) environ);
safe_execvpe(command, environ);
} else {
// Read stdout
std::vector<unsigned char> *output = new std::vector<unsigned char>;

View File

@ -49,7 +49,15 @@ export function getDebianVersion() {
// Make File Executable
export function makeExecutable(path) {
fs.chmodSync(path, 0o755);
fs.chmodSync(path,
fs.constants.S_IRUSR |
fs.constants.S_IWUSR |
fs.constants.S_IXUSR |
fs.constants.S_IRGRP |
fs.constants.S_IXGRP |
fs.constants.S_IROTH |
fs.constants.S_IXOTH
);
}
// Get Scripts Directory