mirror of
https://github.com/kunkundi/crossdesk.git
synced 2026-10-26 23:33:02 +08:00
[fix] authorize POSIX updates before closing CrossDesk
This commit is contained in:
@@ -27,6 +27,18 @@ Request ReadRequest(const fs::path& job) {
|
|||||||
return {application, artifact};
|
return {application, artifact};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void WaitForCommit(const fs::path& job) {
|
||||||
|
const auto deadline = std::chrono::steady_clock::now() + std::chrono::seconds(120);
|
||||||
|
while (true) {
|
||||||
|
if (fs::exists(job / "cancel"))
|
||||||
|
throw std::runtime_error("Update cancelled before application exit");
|
||||||
|
if (fs::exists(job / "commit")) return;
|
||||||
|
if (std::chrono::steady_clock::now() >= deadline)
|
||||||
|
throw std::runtime_error("Application did not confirm update installation");
|
||||||
|
std::this_thread::sleep_for(std::chrono::milliseconds(100));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
int Install(const fs::path& job) {
|
int Install(const fs::path& job) {
|
||||||
if (!IsPrivileged()) return 2;
|
if (!IsPrivileged()) return 2;
|
||||||
fs::path stage;
|
fs::path stage;
|
||||||
@@ -41,7 +53,19 @@ int Install(const fs::path& job) {
|
|||||||
// Copy into private storage, then verify the exact copy being installed.
|
// Copy into private storage, then verify the exact copy being installed.
|
||||||
CopyPackage(job / request.artifact.filename, package, request.artifact);
|
CopyPackage(job / request.artifact.filename, package, request.artifact);
|
||||||
},
|
},
|
||||||
[&] { WaitForApplicationExit(request.application); },
|
[&] {
|
||||||
|
#ifndef _WIN32
|
||||||
|
// Authorization and staging finish while the GUI can still service
|
||||||
|
// the remote connection. An empty directory publishes readiness
|
||||||
|
// atomically without writing through a caller-controlled file.
|
||||||
|
if (fs::exists(job / "cancel"))
|
||||||
|
throw std::runtime_error("Update cancelled before application exit");
|
||||||
|
if (!fs::create_directory(job / "ready"))
|
||||||
|
throw std::runtime_error("Update readiness was already published");
|
||||||
|
WaitForCommit(job);
|
||||||
|
#endif
|
||||||
|
WaitForApplicationExit(request.application);
|
||||||
|
},
|
||||||
[&] { InstallPackage(package, request.application); }});
|
[&] { InstallPackage(package, request.application); }});
|
||||||
result = 0;
|
result = 0;
|
||||||
} catch (const std::exception& error) {
|
} catch (const std::exception& error) {
|
||||||
@@ -69,14 +93,20 @@ int Coordinate(const fs::path& job) {
|
|||||||
{
|
{
|
||||||
maintenance = std::make_unique<MaintenanceLock>(application);
|
maintenance = std::make_unique<MaintenanceLock>(application);
|
||||||
VerifyPackage(job / request.artifact.filename, request.artifact);
|
VerifyPackage(job / request.artifact.filename, request.artifact);
|
||||||
|
#ifdef _WIN32
|
||||||
WriteText(job / "ready", "ready\n");
|
WriteText(job / "ready", "ready\n");
|
||||||
const auto deadline = std::chrono::steady_clock::now() + std::chrono::seconds(120);
|
WaitForCommit(job);
|
||||||
while (!fs::exists(job / "commit")) {
|
|
||||||
if (fs::exists(job / "cancel") || std::chrono::steady_clock::now() >= deadline)
|
|
||||||
throw std::runtime_error("Update cancelled before application exit");
|
|
||||||
std::this_thread::sleep_for(std::chrono::milliseconds(100));
|
|
||||||
}
|
|
||||||
committed = true;
|
committed = true;
|
||||||
|
#else
|
||||||
|
// The root-owned helper publishes ready only after authorization and
|
||||||
|
// verification of its private package copy, then waits for the GUI's
|
||||||
|
// commit before touching the installation.
|
||||||
|
const int code = RunElevated(HelperPath(application), job);
|
||||||
|
committed = fs::exists(job / "commit");
|
||||||
|
if (!committed)
|
||||||
|
throw std::runtime_error("Update preparation failed or authorization was cancelled (code " +
|
||||||
|
std::to_string(code) + ")");
|
||||||
|
#endif
|
||||||
platform::SingleInstanceGuard client;
|
platform::SingleInstanceGuard client;
|
||||||
platform::SingleInstanceGuard launcher;
|
platform::SingleInstanceGuard launcher;
|
||||||
const auto exit_deadline = std::chrono::steady_clock::now() + std::chrono::seconds(45);
|
const auto exit_deadline = std::chrono::steady_clock::now() + std::chrono::seconds(45);
|
||||||
@@ -96,12 +126,8 @@ int Coordinate(const fs::path& job) {
|
|||||||
exited = true;
|
exited = true;
|
||||||
#ifdef _WIN32
|
#ifdef _WIN32
|
||||||
// This relocated copy is held read-only across UAC and installation.
|
// This relocated copy is held read-only across UAC and installation.
|
||||||
const auto installer_helper = ExecutablePath();
|
const int code = RunElevated(ExecutablePath(), job);
|
||||||
#else
|
|
||||||
// Use the root-owned installed helper for privileged execution.
|
|
||||||
const auto installer_helper = HelperPath(application);
|
|
||||||
#endif
|
#endif
|
||||||
const int code = RunElevated(installer_helper, job);
|
|
||||||
if (code != 0) throw std::runtime_error("Installation failed or authorization was cancelled (code " +
|
if (code != 0) throw std::runtime_error("Installation failed or authorization was cancelled (code " +
|
||||||
std::to_string(code) + ")");
|
std::to_string(code) + ")");
|
||||||
success = true;
|
success = true;
|
||||||
|
|||||||
@@ -99,7 +99,9 @@ bool Controller::Poll() {
|
|||||||
if (state_ == State::Downloading && pending_.valid() &&
|
if (state_ == State::Downloading && pending_.valid() &&
|
||||||
pending_.wait_for(std::chrono::seconds(0)) == std::future_status::ready) {
|
pending_.wait_for(std::chrono::seconds(0)) == std::future_status::ready) {
|
||||||
job_ = pending_.get();
|
job_ = pending_.get();
|
||||||
deadline_ = std::chrono::steady_clock::now() + std::chrono::seconds(30);
|
// POSIX readiness includes the system authorization dialog and staging
|
||||||
|
// the verified package; keep the GUI available while the user approves.
|
||||||
|
deadline_ = std::chrono::steady_clock::now() + std::chrono::minutes(5);
|
||||||
state_ = State::Preparing;
|
state_ = State::Preparing;
|
||||||
}
|
}
|
||||||
if (state_ != State::Preparing) return false;
|
if (state_ != State::Preparing) return false;
|
||||||
|
|||||||
+11
-6
@@ -12,11 +12,15 @@ Windows 安装版、macOS PKG 安装版和 Linux DEB 安装版共用
|
|||||||
|
|
||||||
1. 用户点击“安装更新”并确认连接和文件传输将中断。
|
1. 用户点击“安装更新”并确认连接和文件传输将中断。
|
||||||
2. 主程序异步下载,显示进度,并验证签名、大小和 SHA-256。
|
2. 主程序异步下载,显示进度,并验证签名、大小和 SHA-256。
|
||||||
3. 升级器从私有暂存目录启动,取得安装目录对应的维护锁并确认就绪。
|
3. 升级器从私有暂存目录启动,取得安装目录对应的维护锁。
|
||||||
4. 主程序正常退出,升级器等待同一用户的 GUI 和守护进程释放实例锁。
|
4. macOS/Linux 先请求系统授权,再由提权后的安装进程再次验证签名、
|
||||||
5. 升级器请求系统安装授权。取消授权会尝试重新打开原程序。
|
将包复制到只有管理员可写的私有目录并验证副本。完成这些步骤后才
|
||||||
6. 获得授权的安装进程再次验证签名,将包复制到只有管理员可写的
|
通知主程序退出;等待授权期间远程连接继续运行,取消授权不会关闭
|
||||||
私有目录,再验证该副本。确认相关程序退出后,调用平台安装器。
|
主程序。准备阶段最多等待五分钟。
|
||||||
|
5. 主程序收到就绪通知后提交安装并正常退出。Windows 保持原有流程:
|
||||||
|
先等待同一用户的 GUI 和守护进程退出,再启动提权安装进程验证副本。
|
||||||
|
6. 安装进程确认相关程序退出后调用平台安装器。macOS/Linux 未收到
|
||||||
|
主程序的安装提交、收到取消通知或等待提交超时时,不执行安装。
|
||||||
7. 升级器以原调用者身份重启程序。新程序进入 GUI 事件循环后确认启动;
|
7. 升级器以原调用者身份重启程序。新程序进入 GUI 事件循环后确认启动;
|
||||||
90 秒内未收到确认会记录失败。启动确认不依赖远程服务器连通性。
|
90 秒内未收到确认会记录失败。启动确认不依赖远程服务器连通性。
|
||||||
|
|
||||||
@@ -106,7 +110,8 @@ xmake b crossdesk
|
|||||||
|
|
||||||
## 失败处理与验证
|
## 失败处理与验证
|
||||||
|
|
||||||
下载、验签或准备失败时不关闭主程序。安装失败或授权取消后尝试重启。
|
下载、验签或准备失败时不关闭主程序。主程序退出后的安装失败会尝试
|
||||||
|
重启;macOS/Linux 在退出前取消授权时保留原程序和连接。
|
||||||
`last-update` 位于应用缓存目录,指向私有暂存目录;`request.json` 和
|
`last-update` 位于应用缓存目录,指向私有暂存目录;`request.json` 和
|
||||||
`result.json` 保留升级版本及结果,未完成的任务不会自动重放。成功启动后
|
`result.json` 保留升级版本及结果,未完成的任务不会自动重放。成功启动后
|
||||||
删除大型安装包,保留少量诊断文件。失败任务保留下载包便于排查。
|
删除大型安装包,保留少量诊断文件。失败任务保留下载包便于排查。
|
||||||
|
|||||||
Reference in New Issue
Block a user