diff --git a/es-app/CMakeLists.txt b/es-app/CMakeLists.txt
index eec608187..3900481b6 100644
--- a/es-app/CMakeLists.txt
+++ b/es-app/CMakeLists.txt
@@ -29,6 +29,7 @@ set(ES_HEADERS
 
     # GUIs
     ${CMAKE_CURRENT_SOURCE_DIR}/src/guis/GuiAlternativeEmulators.h
+    ${CMAKE_CURRENT_SOURCE_DIR}/src/guis/GuiApplicationUpdater.h
     ${CMAKE_CURRENT_SOURCE_DIR}/src/guis/GuiCollectionSystemsOptions.h
     ${CMAKE_CURRENT_SOURCE_DIR}/src/guis/GuiGamelistFilter.h
     ${CMAKE_CURRENT_SOURCE_DIR}/src/guis/GuiGamelistOptions.h
@@ -79,6 +80,7 @@ set(ES_SOURCES
 
     # GUIs
     ${CMAKE_CURRENT_SOURCE_DIR}/src/guis/GuiAlternativeEmulators.cpp
+    ${CMAKE_CURRENT_SOURCE_DIR}/src/guis/GuiApplicationUpdater.cpp
     ${CMAKE_CURRENT_SOURCE_DIR}/src/guis/GuiCollectionSystemsOptions.cpp
     ${CMAKE_CURRENT_SOURCE_DIR}/src/guis/GuiGamelistFilter.cpp
     ${CMAKE_CURRENT_SOURCE_DIR}/src/guis/GuiGamelistOptions.cpp
diff --git a/es-app/src/ApplicationUpdater.cpp b/es-app/src/ApplicationUpdater.cpp
index a6a306d12..841f46cf7 100644
--- a/es-app/src/ApplicationUpdater.cpp
+++ b/es-app/src/ApplicationUpdater.cpp
@@ -396,6 +396,8 @@ void ApplicationUpdater::compareVersions()
                     mPackage = package;
             }
 
+            mPackage.version = releaseType->version;
+
             // Cut the message to 280 characters so we don't make the message box exceedingly large.
             mPackage.message = mPackage.message.substr(0, 280);
 
@@ -410,13 +412,12 @@ void ApplicationUpdater::compareVersions()
 
             mResults.append("New ")
                 .append(releaseType == &mStableRelease ? "release " : "prerelease ")
-                .append("available!\n")
-                .append(releaseType->version)
-                .append(" (")
-                .append(releaseType->date)
-                .append(")\n")
-                .append("can now be downloaded from\n")
-                .append("https://es-de.org/");
+                .append("available: ")
+                .append(releaseType->version);
+
+            if (mPackage.name != "LinuxAppImage" && mPackage.name != "LinuxSteamDeckAppImage")
+                mResults.append("\nIt can be downloaded from\n").append("https://es-de.org");
+            // mResults.append("\nFor more information visit\n").append("https://es-de.org");
 
             if (mPackage.message != "")
                 mResults.append("\n").append(mPackage.message);
diff --git a/es-app/src/ApplicationUpdater.h b/es-app/src/ApplicationUpdater.h
index 0778cf883..3bb7d9542 100644
--- a/es-app/src/ApplicationUpdater.h
+++ b/es-app/src/ApplicationUpdater.h
@@ -34,6 +34,7 @@ public:
 
     struct Package {
         std::string name;
+        std::string version;
         std::string filename;
         std::string url;
         std::string md5;
diff --git a/es-app/src/guis/GuiApplicationUpdater.cpp b/es-app/src/guis/GuiApplicationUpdater.cpp
new file mode 100644
index 000000000..1f99bc925
--- /dev/null
+++ b/es-app/src/guis/GuiApplicationUpdater.cpp
@@ -0,0 +1,434 @@
+//  SPDX-License-Identifier: MIT
+//
+//  EmulationStation Desktop Edition
+//  GuiApplicationUpdater.cpp
+//
+//  Installs application updates.
+//
+
+#include "GuiApplicationUpdater.h"
+
+#include "EmulationStation.h"
+#include "utils/PlatformUtil.h"
+
+#include <filesystem>
+
+GuiApplicationUpdater::GuiApplicationUpdater()
+    : mRenderer {Renderer::getInstance()}
+    , mBackground {":/graphics/frame.svg"}
+    , mGrid {glm::ivec2 {4, 11}}
+    , mAbortDownload {false}
+    , mDownloading {false}
+    , mReadyToInstall {false}
+    , mHasDownloaded {false}
+    , mInstalling {false}
+    , mHasInstalled {false}
+{
+    addChild(&mBackground);
+    addChild(&mGrid);
+
+    mPackage = ApplicationUpdater::getInstance().getPackageInfo();
+    assert(mPackage.url != "");
+
+    LOG(LogInfo) << "Starting Application Updater";
+
+    // Set up grid.
+    mTitle = std::make_shared<TextComponent>("APPLICATION UPDATER", Font::get(FONT_SIZE_LARGE),
+                                             mMenuColorTitle, ALIGN_CENTER);
+    mGrid.setEntry(mTitle, glm::ivec2 {0, 0}, false, true, glm::ivec2 {4, 1},
+                   GridFlags::BORDER_BOTTOM);
+
+    mStatusHeader = std::make_shared<TextComponent>(
+        "INSTALLATION STEPS:", Font::get(FONT_SIZE_MINI), mMenuColorPrimary, ALIGN_LEFT);
+    mGrid.setEntry(mStatusHeader, glm::ivec2 {1, 1}, false, true, glm::ivec2 {2, 1});
+
+    mProcessStep1 = std::make_shared<TextComponent>(
+        "DOWNLOAD NEW RELEASE", Font::get(FONT_SIZE_MEDIUM), mMenuColorPrimary, ALIGN_LEFT);
+    mGrid.setEntry(mProcessStep1, glm::ivec2 {1, 2}, false, true, glm::ivec2 {2, 1});
+
+    mProcessStep2 = std::make_shared<TextComponent>("INSTALL PACKAGE", Font::get(FONT_SIZE_MEDIUM),
+                                                    mMenuColorPrimary, ALIGN_LEFT);
+    mGrid.setEntry(mProcessStep2, glm::ivec2 {1, 3}, false, true, glm::ivec2 {2, 1});
+
+    mProcessStep3 =
+        std::make_shared<TextComponent>("QUIT AND MANUALLY RESTART ES-DE",
+                                        Font::get(FONT_SIZE_MEDIUM), mMenuColorPrimary, ALIGN_LEFT);
+    mGrid.setEntry(mProcessStep3, glm::ivec2 {1, 4}, false, true, glm::ivec2 {2, 1});
+
+    mStatusMessageHeader = std::make_shared<TextComponent>(
+        "STATUS MESSAGE:", Font::get(FONT_SIZE_MINI), mMenuColorPrimary, ALIGN_LEFT);
+    mGrid.setEntry(mStatusMessageHeader, glm::ivec2 {1, 6}, false, true, glm::ivec2 {2, 1});
+
+    mStatusMessage = std::make_shared<TextComponent>("", Font::get(FONT_SIZE_SMALL),
+                                                     mMenuColorPrimary, ALIGN_LEFT);
+    mGrid.setEntry(mStatusMessage, glm::ivec2 {1, 7}, false, true, glm::ivec2 {2, 1});
+
+    mChangelogMessage = std::make_shared<TextComponent>("", Font::get(FONT_SIZE_SMALL),
+                                                        mMenuColorPrimary, ALIGN_LEFT);
+    mGrid.setEntry(mChangelogMessage, glm::ivec2 {1, 8}, false, true, glm::ivec2 {2, 1});
+
+    // Buttons.
+    std::vector<std::shared_ptr<ButtonComponent>> buttons;
+
+    mButton1 = std::make_shared<ButtonComponent>("DOWNLOAD", "download new release", [this]() {
+        if (!mDownloading) {
+            mMessage = "";
+            mStatusMessage->setText(mMessage);
+            mDownloading = true;
+            if (mThread) {
+                mThread->join();
+                mThread.reset();
+            }
+            mThread = std::make_unique<std::thread>(&GuiApplicationUpdater::downloadPackage, this);
+        }
+    });
+
+    buttons.push_back(mButton1);
+
+    mButton2 = std::make_shared<ButtonComponent>("CANCEL", "cancel", [this]() {
+        mAbortDownload = true;
+        if (mDownloading) {
+            mWindow->pushGui(
+                new GuiMsgBox(getHelpStyle(), "DOWNLOAD ABORTED\nNO PACKAGE SAVED TO DISK", "OK",
+                              nullptr, "", nullptr, "", nullptr, true, true,
+                              (mRenderer->getIsVerticalOrientation() ?
+                                   0.70f :
+                                   0.45f * (1.778f / mRenderer->getScreenAspectRatio()))));
+        }
+        else if (mHasDownloaded && !mHasInstalled) {
+            mWindow->pushGui(new GuiMsgBox(
+                getHelpStyle(), "PACKAGE WAS DOWNLOADED AND\nCAN BE MANUALLY INSTALLED", "OK",
+                nullptr, "", nullptr, "", nullptr, true, true,
+                (mRenderer->getIsVerticalOrientation() ?
+                     0.70f :
+                     0.45f * (1.778f / mRenderer->getScreenAspectRatio()))));
+        }
+        delete this;
+    });
+
+    buttons.push_back(mButton2);
+
+    mButtons = MenuComponent::makeButtonGrid(buttons);
+    mGrid.setEntry(mButtons, glm::ivec2 {0, 10}, true, false, glm::ivec2 {4, 1},
+                   GridFlags::BORDER_TOP);
+
+    // Limit the width of the GUI on ultrawide monitors. The 1.778 aspect ratio value is
+    // the 16:9 reference.
+    const float aspectValue {1.778f / Renderer::getScreenAspectRatio()};
+    const float width {glm::clamp(0.70f * aspectValue, 0.55f,
+                                  (mRenderer->getIsVerticalOrientation() ? 0.95f : 0.85f)) *
+                       mRenderer->getScreenWidth()};
+    setSize(width,
+            mTitle->getSize().y +
+                (FONT_SIZE_MEDIUM * 1.5f * (mRenderer->getIsVerticalOrientation() ? 8.0f : 7.0f)) +
+                mButtons->getSize().y);
+
+    setPosition((mRenderer->getScreenWidth() - mSize.x) / 2.0f,
+                (mRenderer->getScreenHeight() - mSize.y) / 2.0f);
+
+    setPosition((mRenderer->getScreenWidth() - mSize.x) / 2.0f,
+                std::round(mRenderer->getScreenHeight() * 0.13f));
+
+    mBusyAnim.setSize(glm::vec2 {mRenderer->getScreenWidth(),
+                                 mRenderer->getScreenHeight() *
+                                     (mRenderer->getIsVerticalOrientation() ? 0.80f : 1.0f)});
+    mBusyAnim.setText("DOWNLOADING");
+    mBusyAnim.onSizeChanged();
+}
+
+GuiApplicationUpdater::~GuiApplicationUpdater()
+{
+    mAbortDownload = true;
+
+    if (mThread)
+        mThread->join();
+}
+
+bool GuiApplicationUpdater::downloadPackage()
+{
+    mStatus = ASYNC_IN_PROGRESS;
+    mRequest = std::unique_ptr<HttpReq>(std::make_unique<HttpReq>(mPackage.url));
+    LOG(LogDebug) << "GuiApplicationUpdater::downloadPackage(): Starting download of \""
+                  << mPackage.filename << "\"";
+
+    while (!mAbortDownload) {
+        HttpReq::Status reqStatus {mRequest->status()};
+        if (reqStatus == HttpReq::REQ_SUCCESS) {
+            mStatus = ASYNC_DONE;
+            break;
+        }
+        else if (reqStatus != HttpReq::REQ_IN_PROGRESS) {
+            std::string errorMessage {"Network error (status: "};
+            errorMessage.append(std::to_string(reqStatus))
+                .append(") - ")
+                .append(mRequest->getErrorMsg());
+            mRequest.reset();
+            std::unique_lock<std::mutex> lock {mMutex};
+            mMessage = errorMessage;
+            return true;
+        }
+    }
+
+    if (mAbortDownload) {
+        if (mAbortDownload) {
+            LOG(LogInfo) << "Aborted package download";
+        }
+        mRequest.reset();
+        return false;
+    }
+
+    std::string fileContents {mRequest->getContent()};
+    mRequest.reset();
+
+    if (Utils::Math::md5Hash(fileContents) != mPackage.md5) {
+        const std::string errorMessage {"Downloaded file does not match expected MD5 checksum"};
+        LOG(LogError) << errorMessage;
+        std::unique_lock<std::mutex> lock {mMutex};
+        mMessage = "Error: " + errorMessage;
+        return true;
+    }
+
+    const std::string packageTempFile {
+        Utils::FileSystem::getParent(Utils::FileSystem::getEsBinary()) + "/" + mPackage.filename +
+        "_" + mPackage.version};
+    LOG(LogDebug)
+        << "GuiApplicationUpdater::downloadPackage(): Package downloaded, writing it to \""
+        << packageTempFile << "\"";
+
+    if (Utils::FileSystem::isRegularFile(packageTempFile)) {
+        LOG(LogInfo) << "Temporary package file already exists, deleting it";
+        Utils::FileSystem::removeFile(packageTempFile);
+        if (Utils::FileSystem::exists(packageTempFile)) {
+            const std::string errorMessage {
+                "Couldn't delete temporary package file, permission problems?"};
+            LOG(LogError) << errorMessage;
+            std::unique_lock<std::mutex> lock {mMutex};
+            mMessage = "Error: " + errorMessage;
+            return true;
+        }
+    }
+
+    std::ofstream writeFile;
+    writeFile.open(packageTempFile.c_str(), std::ofstream::binary);
+
+    if (writeFile.fail()) {
+        const std::string errorMessage {
+            "Couldn't open package file for writing, permission problems?"};
+        LOG(LogError) << errorMessage;
+        std::unique_lock<std::mutex> lock {mMutex};
+        mMessage = "Error: " + errorMessage;
+        return true;
+    }
+
+    writeFile.write(&fileContents[0], fileContents.length());
+    writeFile.close();
+
+    fileContents.clear();
+
+    std::filesystem::permissions(packageTempFile, std::filesystem::perms::owner_all |
+                                                      std::filesystem::perms::group_all |
+                                                      std::filesystem::perms::others_read |
+                                                      std::filesystem::perms::others_exec);
+
+    if (std::filesystem::status(packageTempFile).permissions() !=
+        (std::filesystem::perms::owner_all | std::filesystem::perms::group_all |
+         std::filesystem::perms::others_read | std::filesystem::perms::others_exec)) {
+        Utils::FileSystem::removeFile(packageTempFile);
+        const std::string errorMessage {"Couldn't set permissions on AppImage file"};
+        LOG(LogError) << errorMessage;
+        std::unique_lock<std::mutex> lock {mMutex};
+        mMessage = "Error: " + errorMessage;
+        return true;
+    }
+
+    LOG(LogInfo) << "Successfully downloaded package file \"" << packageTempFile << "\"";
+
+    std::unique_lock<std::mutex> lock {mMutex};
+    mMessage = "Downloaded " + mPackage.filename + "_" + mPackage.version;
+
+    mDownloading = false;
+    mReadyToInstall = true;
+
+    return false;
+}
+
+bool GuiApplicationUpdater::installAppImage()
+{
+    LOG(LogDebug) << "GuiApplicationUpdater::installAppImage(): Attempting to install new package";
+
+    mReadyToInstall = false;
+    mInstalling = true;
+
+    const std::string packageTempFile {
+        Utils::FileSystem::getParent(Utils::FileSystem::getEsBinary()) + "/" + mPackage.filename +
+        "_" + mPackage.version};
+    const std::string packageTargetFile {Utils::FileSystem::getEsBinary()};
+
+    if (packageTargetFile !=
+        Utils::FileSystem::getParent(Utils::FileSystem::getEsBinary()) + "/" + mPackage.filename) {
+        LOG(LogWarning) << "Running AppImage seems to have a non-standard filename: \""
+                        << packageTargetFile << "\"";
+    }
+
+    if (Utils::FileSystem::isSymlink(packageTargetFile)) {
+        LOG(LogInfo)
+            << "Target file is a symbolic link, this will be followed and the actual symlink file "
+            << "will not be touched ";
+    }
+
+    // Extra precaution, make sure that the file was actually correctly written to disk.
+    std::ifstream readFile;
+    readFile.open(packageTempFile.c_str(), std::ofstream::binary);
+
+    if (readFile.fail()) {
+        const std::string errorMessage {"Couldn't open AppImage update file for reading"};
+        LOG(LogError) << errorMessage;
+        mMessage = "Error: " + errorMessage;
+        mHasDownloaded = false;
+        return true;
+    }
+
+    readFile.seekg(0, std::ios::end);
+    const long fileLength {static_cast<long>(readFile.tellg())};
+    readFile.seekg(0, std::ios::beg);
+    std::string fileData(fileLength, 0);
+    readFile.read(&fileData[0], fileLength);
+    readFile.close();
+
+    if (Utils::Math::md5Hash(fileData) != mPackage.md5) {
+        const std::string errorMessage {"Downloaded file does not match expected MD5 checksum"};
+        LOG(LogError) << errorMessage;
+        mMessage = "Error: " + errorMessage;
+        mHasDownloaded = false;
+        return true;
+    }
+
+    const std::string packageOldFile {packageTargetFile + "_OLD_" + PROGRAM_VERSION_STRING};
+
+    if (Utils::FileSystem::renameFile(packageTargetFile, packageOldFile, true)) {
+        const std::string errorMessage {
+            "Couldn't rename running AppImage file, permission problems?"};
+        LOG(LogError) << errorMessage;
+        mMessage = "Error: " + errorMessage;
+        LOG(LogInfo) << "Attempting to rename \"" << packageOldFile
+                     << "\" back to running AppImage";
+        Utils::FileSystem::renameFile(packageOldFile, packageTargetFile, true);
+        mInstalling = false;
+        return true;
+    }
+
+    LOG(LogInfo) << "Renamed running AppImage to \"" << packageOldFile << "\"";
+
+    if (Utils::FileSystem::renameFile(packageTempFile, packageTargetFile, true)) {
+        const std::string errorMessage {
+            "Couldn't replace running AppImage file, permission problems?"};
+        LOG(LogError) << errorMessage;
+        mMessage = "Error: " + errorMessage;
+        LOG(LogInfo) << "Attempting to rename \"" << packageOldFile
+                     << "\" back to running AppImage";
+        Utils::FileSystem::renameFile(packageOldFile, packageTargetFile, true);
+        mInstalling = false;
+        return true;
+    }
+
+    LOG(LogInfo) << "Package was successfully installed as \"" << packageTargetFile << "\"";
+
+    std::unique_lock<std::mutex> lock {mMutex};
+    mMessage = "Package was successfully installed";
+    mHasInstalled = true;
+
+    return false;
+}
+
+void GuiApplicationUpdater::update(int deltaTime)
+{
+    {
+        std::unique_lock<std::mutex> lock {mMutex};
+        if (mMessage != "") {
+            mStatusMessage->setText(mMessage);
+            mDownloading = false;
+        }
+    }
+
+    if (mDownloading)
+        mBusyAnim.update(deltaTime);
+    else if (mReadyToInstall) {
+        mProcessStep1->setText(ViewController::TICKMARK_CHAR + " " + mProcessStep1->getValue());
+        mProcessStep1->setColor(mMenuColorGreen);
+        mButton1->setText("INSTALL", "install package");
+        mButton1->setPressedFunc([this] {
+            if (!mInstalling) {
+                mMessage = "";
+                mStatusMessage->setText(mMessage);
+                installAppImage();
+            }
+        });
+        mReadyToInstall = false;
+        mHasDownloaded = true;
+    }
+    else if (mHasInstalled) {
+        mProcessStep2->setText(ViewController::TICKMARK_CHAR + " " + mProcessStep2->getValue());
+        mProcessStep2->setColor(mMenuColorGreen);
+        mChangelogMessage->setText("Find the detailed changelog at https://es-de.org");
+        mButton1->setText("DONE", "quit application");
+        mButton1->setPressedFunc([this] {
+            delete this;
+            Utils::Platform::quitES();
+        });
+        mButton2->setText("QUIT", "quit application");
+        mButton2->setPressedFunc([this] {
+            delete this;
+            Utils::Platform::quitES();
+        });
+        mHasInstalled = false;
+    }
+}
+
+void GuiApplicationUpdater::render(const glm::mat4& parentTrans)
+{
+    glm::mat4 trans {parentTrans * getTransform()};
+
+    renderChildren(trans);
+
+    if (mDownloading)
+        mBusyAnim.render(parentTrans);
+}
+
+void GuiApplicationUpdater::onSizeChanged()
+{
+    const float screenSize {mRenderer->getIsVerticalOrientation() ? mRenderer->getScreenWidth() :
+                                                                    mRenderer->getScreenHeight()};
+    mGrid.setRowHeightPerc(0, (mTitle->getFont()->getLetterHeight() + screenSize * 0.2f) / mSize.y /
+                                  2.0f);
+    mGrid.setRowHeightPerc(1, (mStatusHeader->getFont()->getLetterHeight() + screenSize * 0.2f) /
+                                  mSize.y / 4.0f);
+    mGrid.setRowHeightPerc(2, (mProcessStep1->getFont()->getLetterHeight() + screenSize * 0.2f) /
+                                  mSize.y / 4.0f);
+    mGrid.setRowHeightPerc(3, (mProcessStep2->getFont()->getLetterHeight() + screenSize * 0.2f) /
+                                  mSize.y / 4.0f);
+    mGrid.setRowHeightPerc(4, (mProcessStep3->getFont()->getLetterHeight() + screenSize * 0.2f) /
+                                  mSize.y / 4.0f);
+    mGrid.setRowHeightPerc(
+        5,
+        (mStatusMessageHeader->getFont()->getLetterHeight() + screenSize * 0.2f) / mSize.y / 4.0f);
+    mGrid.setRowHeightPerc(
+        6,
+        (mStatusMessageHeader->getFont()->getLetterHeight() + screenSize * 0.2f) / mSize.y / 4.0f);
+    mGrid.setRowHeightPerc(7, (mStatusMessage->getFont()->getLetterHeight() + screenSize * 0.15f) /
+                                  mSize.y / 4.0f);
+    mGrid.setRowHeightPerc(
+        8, (mChangelogMessage->getFont()->getLetterHeight() + screenSize * 0.15f) / mSize.y / 4.0f);
+    mGrid.setRowHeightPerc(10, mButtons->getSize().y / mSize.y);
+
+    mGrid.setColWidthPerc(0, 0.01f);
+    mGrid.setColWidthPerc(3, 0.01f);
+
+    mGrid.setSize(mSize);
+    mBackground.fitTo(mSize);
+}
+
+std::vector<HelpPrompt> GuiApplicationUpdater::getHelpPrompts()
+{
+    std::vector<HelpPrompt> prompts {mGrid.getHelpPrompts()};
+    return prompts;
+}
\ No newline at end of file
diff --git a/es-app/src/guis/GuiApplicationUpdater.h b/es-app/src/guis/GuiApplicationUpdater.h
new file mode 100644
index 000000000..738ced36c
--- /dev/null
+++ b/es-app/src/guis/GuiApplicationUpdater.h
@@ -0,0 +1,80 @@
+//  SPDX-License-Identifier: MIT
+//
+//  EmulationStation Desktop Edition
+//  GuiApplicationUpdater.h
+//
+//  Installs application updates.
+//
+
+#ifndef ES_APP_GUIS_GUI_APPLICATION_UPDATER_H
+#define ES_APP_GUIS_GUI_APPLICATION_UPDATER_H
+
+#include "ApplicationUpdater.h"
+#include "AsyncHandle.h"
+#include "GuiComponent.h"
+#include "HttpReq.h"
+#include "components/BusyComponent.h"
+#include "guis/GuiSettings.h"
+#include "views/ViewController.h"
+
+#include <atomic>
+#include <mutex>
+#include <thread>
+
+class GuiApplicationUpdater : public GuiComponent
+{
+public:
+    GuiApplicationUpdater();
+    ~GuiApplicationUpdater();
+
+    bool downloadPackage();
+    bool installAppImage();
+
+    void update(int deltaTime) override;
+    void render(const glm::mat4& parentTrans) override;
+
+private:
+    void onSizeChanged() override;
+    std::vector<HelpPrompt> getHelpPrompts() override;
+    HelpStyle getHelpStyle() override
+    {
+        if (ViewController::getInstance()->getState().viewing == ViewController::ViewMode::NOTHING)
+            return HelpStyle();
+        else
+            return ViewController::getInstance()->getViewHelpStyle();
+    }
+
+    Renderer* mRenderer;
+    BusyComponent mBusyAnim;
+
+    NinePatchComponent mBackground;
+    ComponentGrid mGrid;
+    std::shared_ptr<ComponentGrid> mButtons;
+    std::shared_ptr<ButtonComponent> mButton1;
+    std::shared_ptr<ButtonComponent> mButton2;
+
+    std::shared_ptr<TextComponent> mTitle;
+    std::shared_ptr<TextComponent> mStatusHeader;
+    std::shared_ptr<TextComponent> mProcessStep1;
+    std::shared_ptr<TextComponent> mProcessStep2;
+    std::shared_ptr<TextComponent> mProcessStep3;
+    std::shared_ptr<TextComponent> mStatusMessageHeader;
+    std::shared_ptr<TextComponent> mStatusMessage;
+    std::shared_ptr<TextComponent> mChangelogMessage;
+
+    std::unique_ptr<std::thread> mThread;
+    std::unique_ptr<HttpReq> mRequest;
+    AsyncHandleStatus mStatus;
+    std::mutex mMutex;
+    std::string mMessage;
+
+    ApplicationUpdater::Package mPackage;
+    std::atomic<bool> mAbortDownload;
+    std::atomic<bool> mDownloading;
+    std::atomic<bool> mReadyToInstall;
+    bool mHasDownloaded;
+    bool mInstalling;
+    bool mHasInstalled;
+};
+
+#endif // ES_APP_GUIS_GUI_APPLICATION_UPDATER_H
diff --git a/es-app/src/views/ViewController.cpp b/es-app/src/views/ViewController.cpp
index dab874390..1dab9531a 100644
--- a/es-app/src/views/ViewController.cpp
+++ b/es-app/src/views/ViewController.cpp
@@ -27,6 +27,7 @@
 #include "animations/Animation.h"
 #include "animations/LambdaAnimation.h"
 #include "animations/MoveCameraAnimation.h"
+#include "guis/GuiApplicationUpdater.h"
 #include "guis/GuiMenu.h"
 #include "guis/GuiTextEditKeyboardPopup.h"
 #include "guis/GuiTextEditPopup.h"
@@ -306,11 +307,22 @@ void ViewController::updateAvailableDialog()
                       << "\"";
     }
 
-    mWindow->pushGui(new GuiMsgBox(getHelpStyle(), results, "OK", nullptr, "", nullptr, "", nullptr,
-                                   true, true,
-                                   (mRenderer->getIsVerticalOrientation() ?
-                                        0.70f :
-                                        0.45f * (1.778f / mRenderer->getScreenAspectRatio()))));
+    if (package.name == "LinuxAppImage" || package.name == "LinuxSteamDeckAppImage") {
+        mWindow->pushGui(new GuiMsgBox(
+            getHelpStyle(), results, "UPDATE",
+            [this] { mWindow->pushGui(new GuiApplicationUpdater()); }, "CANCEL", [] { return; }, "",
+            nullptr, true, true,
+            (mRenderer->getIsVerticalOrientation() ?
+                 0.70f :
+                 0.45f * (1.778f / mRenderer->getScreenAspectRatio()))));
+    }
+    else {
+        mWindow->pushGui(new GuiMsgBox(getHelpStyle(), results, "OK", nullptr, "", nullptr, "",
+                                       nullptr, true, true,
+                                       (mRenderer->getIsVerticalOrientation() ?
+                                            0.70f :
+                                            0.45f * (1.778f / mRenderer->getScreenAspectRatio()))));
+    }
 }
 
 void ViewController::goToStart(bool playTransition)