fix(launcher): require executable Linux hosts

This commit is contained in:
Erik 2026-08-14 19:12:32 +02:00
parent 10a712d66b
commit ae2cbbee8c
5 changed files with 134 additions and 10 deletions

View file

@ -12,12 +12,14 @@ namespace AcDream.Launcher.Core.Orchestration;
public sealed class LauncherExecutableSet
{
private readonly Func<string, bool> _fileExists;
private readonly Func<string, bool> _hasUnixExecutePermission;
public LauncherExecutableSet(
string graphicalHostPath,
string headlessHostPath,
string? workingDirectory = null,
Func<string, bool>? fileExists = null)
Func<string, bool>? fileExists = null,
Func<string, bool>? hasUnixExecutePermission = null)
{
ArgumentException.ThrowIfNullOrWhiteSpace(graphicalHostPath);
ArgumentException.ThrowIfNullOrWhiteSpace(headlessHostPath);
@ -25,6 +27,8 @@ public sealed class LauncherExecutableSet
HeadlessHostPath = headlessHostPath;
WorkingDirectory = workingDirectory;
_fileExists = fileExists ?? File.Exists;
_hasUnixExecutePermission =
hasUnixExecutePermission ?? HasUnixExecutePermission;
}
public string GraphicalHostPath { get; }
@ -38,17 +42,25 @@ public sealed class LauncherExecutableSet
string path = mode == LaunchMode.Headless
? HeadlessHostPath
: GraphicalHostPath;
if (_fileExists(path))
{
return LauncherCapability.Available;
}
string host = mode == LaunchMode.Headless
? "headless host"
: "graphical client";
return LauncherCapability.Unavailable(
$"The co-deployed {host} is missing at '{path}'. Reinstall or update "
+ "the client before launching.");
if (!_fileExists(path))
{
return LauncherCapability.Unavailable(
$"The co-deployed {host} is missing at '{path}'. Reinstall or update "
+ "the client before launching.");
}
if (OperatingSystem.IsLinux() && !_hasUnixExecutePermission(path))
{
return LauncherCapability.Unavailable(
$"The co-deployed {host} at '{path}' exists but is not executable. "
+ "Restore its executable permission (for example, chmod +x) or "
+ "reinstall/update the client before launching.");
}
return LauncherCapability.Available;
}
public LauncherProcessSpec CreatePlaySpec(
@ -99,4 +111,27 @@ public sealed class LauncherExecutableSet
capability.Reason ?? "The selected launcher host is unavailable.");
}
}
private static bool HasUnixExecutePermission(string path)
{
if (!OperatingSystem.IsLinux())
{
return true;
}
try
{
const UnixFileMode executeBits =
UnixFileMode.UserExecute
| UnixFileMode.GroupExecute
| UnixFileMode.OtherExecute;
return (File.GetUnixFileMode(path) & executeBits) != 0;
}
catch (Exception ex) when (ex is IOException or UnauthorizedAccessException)
{
// Fail closed if the file vanished or its metadata cannot be read
// after the existence check. The next capability refresh retries.
return false;
}
}
}