mirror of
https://github.com/immense/Remotely.git
synced 2025-10-26 11:27:15 +00:00
253 lines
9.2 KiB
C#
253 lines
9.2 KiB
C#
using Microsoft.AspNetCore.SignalR.Client;
|
|
using Microsoft.Extensions.Logging;
|
|
using Remotely.Agent.Interfaces;
|
|
using Remotely.Shared.Extensions;
|
|
using Remotely.Shared.Models;
|
|
using Remotely.Shared.Primitives;
|
|
using Remotely.Shared.Services;
|
|
using Remotely.Shared.Utilities;
|
|
using System;
|
|
using System.Diagnostics;
|
|
using System.IO;
|
|
using System.Linq;
|
|
using System.Text.RegularExpressions;
|
|
using System.Threading.Tasks;
|
|
|
|
namespace Remotely.Agent.Services.Linux;
|
|
|
|
|
|
public class AppLauncherLinux : IAppLauncher
|
|
{
|
|
private readonly ConnectionInfo _connectionInfo;
|
|
private readonly ILogger<AppLauncherLinux> _logger;
|
|
private readonly IProcessInvoker _processInvoker;
|
|
|
|
private readonly string _rcBinaryPath = Path.Combine(
|
|
AppDomain.CurrentDomain.BaseDirectory,
|
|
"Desktop",
|
|
EnvironmentHelper.DesktopExecutableFileName);
|
|
|
|
public AppLauncherLinux(
|
|
IConfigService configService,
|
|
IProcessInvoker processInvoker,
|
|
ILogger<AppLauncherLinux> logger)
|
|
{
|
|
_processInvoker = processInvoker;
|
|
_connectionInfo = configService.GetConnectionInfo();
|
|
_logger = logger;
|
|
}
|
|
|
|
|
|
public async Task<int> LaunchChatService(string pipeName, string userConnectionId, string requesterName, string orgName, string orgId, HubConnection hubConnection)
|
|
{
|
|
try
|
|
{
|
|
if (!File.Exists(_rcBinaryPath))
|
|
{
|
|
await hubConnection.SendAsync("DisplayMessage",
|
|
"Chat executable not found on target device.",
|
|
"Executable not found on device.",
|
|
"bg-danger",
|
|
userConnectionId);
|
|
}
|
|
|
|
|
|
// Start Desktop app.
|
|
await hubConnection.SendAsync("DisplayMessage", $"Starting chat service.", "Starting chat service.", "bg-success", userConnectionId);
|
|
var args =
|
|
_rcBinaryPath +
|
|
$" --mode Chat" +
|
|
$" --host \"{_connectionInfo.Host}\"" +
|
|
$" --pipe-name {pipeName}" +
|
|
$" --requester-name \"{requesterName}\"" +
|
|
$" --org-name \"{orgName}\"" +
|
|
$" --org-id \"{orgId}\"";
|
|
return StartLinuxDesktopApp(args);
|
|
}
|
|
catch (Exception ex)
|
|
{
|
|
_logger.LogError(ex, "Error while starting chat.");
|
|
await hubConnection.SendAsync("DisplayMessage", "Chat service failed to start on target device.", "Failed to start chat service.", "bg-danger", userConnectionId);
|
|
}
|
|
return -1;
|
|
}
|
|
|
|
public async Task LaunchRemoteControl(int targetSessionId, string sessionId, string accessKey, string userConnectionId, string requesterName, string orgName, string orgId, HubConnection hubConnection)
|
|
{
|
|
try
|
|
{
|
|
if (!File.Exists(_rcBinaryPath))
|
|
{
|
|
await hubConnection.SendAsync("DisplayMessage",
|
|
"Remote control executable not found on target device.",
|
|
"Executable not found on device.",
|
|
"bg-danger",
|
|
userConnectionId);
|
|
return;
|
|
}
|
|
|
|
|
|
// Start Desktop app.
|
|
await hubConnection.SendAsync("DisplayMessage", "Starting remote control.", "Starting remote control.", "bg-success", userConnectionId);
|
|
var args =
|
|
_rcBinaryPath +
|
|
$" --mode Unattended" +
|
|
$" --host {_connectionInfo.Host}" +
|
|
$" --requester-name \"{requesterName}\"" +
|
|
$" --org-name \"{orgName}\"" +
|
|
$" --org-id \"{orgId}\"" +
|
|
$" --session-id \"{sessionId}\"" +
|
|
$" --access-key \"{accessKey}\"";
|
|
StartLinuxDesktopApp(args);
|
|
}
|
|
catch (Exception ex)
|
|
{
|
|
_logger.LogError(ex, "Error while launching remote control.");
|
|
await hubConnection.SendAsync("DisplayMessage", "Remote control failed to start on target device.", "Failed to start remote control.", "bg-danger", userConnectionId);
|
|
}
|
|
}
|
|
|
|
public async Task RestartScreenCaster(string[] viewerIds, string sessionId, string accessKey, string userConnectionId, string requesterName, string orgName, string orgId, HubConnection hubConnection, int targetSessionID = -1)
|
|
{
|
|
try
|
|
{
|
|
var args =
|
|
_rcBinaryPath +
|
|
$" --mode Unattended" +
|
|
$" --host {_connectionInfo.Host}" +
|
|
$" --requester-name \"{requesterName}\"" +
|
|
$" --org-name \"{orgName}\"" +
|
|
$" --org-id \"{orgId}\"" +
|
|
$" --session-id \"{sessionId}\"" +
|
|
$" --access-key \"{accessKey}\"";
|
|
StartLinuxDesktopApp(args);
|
|
}
|
|
catch (Exception ex)
|
|
{
|
|
await hubConnection.SendAsync("SendConnectionFailedToViewers", viewerIds);
|
|
_logger.LogError(ex, "Error while restarting screen caster.");
|
|
throw;
|
|
}
|
|
}
|
|
|
|
private int StartLinuxDesktopApp(string args)
|
|
{
|
|
var xdisplay = ":0";
|
|
var xauthority = string.Empty;
|
|
|
|
var xResult = TryGetXAuth("Xorg");
|
|
|
|
if (!xResult.IsSuccess)
|
|
{
|
|
// If running Wayland, this still ends up still being unusable.
|
|
// This X server will only provide a black screen with any apps
|
|
// launched within the display it's using, but the display won't
|
|
// show anything being rendered by the Wayland compositor. It's
|
|
// better than simply crashing, though, so I'll leave it here
|
|
// until Wayland support is added.
|
|
xResult = TryGetXAuth("Xwayland");
|
|
}
|
|
|
|
if (xResult.IsSuccess)
|
|
{
|
|
xdisplay = xResult.Value.XDisplay;
|
|
xauthority = xResult.Value.XAuthority;
|
|
}
|
|
else
|
|
{
|
|
_logger.LogError("Failed to get X server auth.");
|
|
}
|
|
|
|
var whoString = _processInvoker.InvokeProcessOutput("who", "")?.Trim();
|
|
var username = "";
|
|
|
|
if (!string.IsNullOrWhiteSpace(whoString))
|
|
{
|
|
try
|
|
{
|
|
var whoLines = whoString.Split('\n', StringSplitOptions.RemoveEmptyEntries);
|
|
|
|
var whoLine = whoLines.FirstOrDefault(x =>
|
|
Regex.IsMatch(
|
|
x.Split(" ", StringSplitOptions.RemoveEmptyEntries).Last(),
|
|
@"\(:[\d]*\)"));
|
|
|
|
if (!string.IsNullOrWhiteSpace(whoLine))
|
|
{
|
|
var whoSplit = whoLine.Split(' ', StringSplitOptions.RemoveEmptyEntries);
|
|
username = whoSplit[0];
|
|
xdisplay = whoSplit.Last().TrimStart('(').TrimEnd(')');
|
|
xauthority = $"/home/{username}/.Xauthority";
|
|
args = $"-u {username} {args}";
|
|
|
|
}
|
|
}
|
|
catch (Exception ex)
|
|
{
|
|
_logger.LogError(ex, "Error while getting current X11 user.");
|
|
}
|
|
}
|
|
|
|
var psi = new ProcessStartInfo()
|
|
{
|
|
FileName = "sudo",
|
|
Arguments = args
|
|
};
|
|
|
|
psi.Environment.Add("DISPLAY", xdisplay);
|
|
psi.Environment.Add("XAUTHORITY", xauthority);
|
|
_logger.LogInformation(
|
|
"Attempting to launch screen caster with username {username}, xauthority {xauthority}, display {display}, and args {args}.",
|
|
username,
|
|
xauthority,
|
|
xdisplay,
|
|
args);
|
|
return Process.Start(psi)?.Id ?? throw new InvalidOperationException("Failed to launch desktop app.");
|
|
}
|
|
|
|
private Result<XAuthInfo> TryGetXAuth(string xServerProcess)
|
|
{
|
|
try
|
|
{
|
|
var xdisplay = ":0";
|
|
var xauthority = string.Empty;
|
|
|
|
var xprocess = _processInvoker
|
|
.InvokeProcessOutput("ps", $"-C {xServerProcess} -f")
|
|
?.Split(Environment.NewLine)
|
|
?.FirstOrDefault(x => x.Contains(" -auth "));
|
|
|
|
if (string.IsNullOrWhiteSpace(xprocess))
|
|
{
|
|
_logger.LogInformation("{xServerProcess} process not found.", xServerProcess);
|
|
return Result.Fail<XAuthInfo>($"{xServerProcess} process not found.");
|
|
}
|
|
|
|
_logger.LogInformation("Resolved X server process: {xprocess}", xprocess);
|
|
|
|
var xprocSplit = xprocess
|
|
.Split(" ", StringSplitOptions.RemoveEmptyEntries)
|
|
.ToList();
|
|
|
|
var xProcIndex = xprocSplit.IndexWhere(x => x.EndsWith(xServerProcess));
|
|
if (xProcIndex > -1 && xprocSplit[xProcIndex + 1].StartsWith(":"))
|
|
{
|
|
xdisplay = xprocSplit[xProcIndex + 1];
|
|
}
|
|
|
|
var authIndex = xprocSplit.IndexOf("-auth");
|
|
if (authIndex > -1)
|
|
{
|
|
xauthority = xprocSplit[authIndex + 1];
|
|
}
|
|
return Result.Ok(new XAuthInfo(xdisplay, xauthority));
|
|
}
|
|
catch (Exception ex)
|
|
{
|
|
_logger.LogError(ex, "Error while geting X auth for {xServerProcess}.", xServerProcess);
|
|
return Result.Fail<XAuthInfo>($"Error while geting X auth for {xServerProcess}.");
|
|
}
|
|
}
|
|
private record XAuthInfo(string XDisplay, string XAuthority);
|
|
}
|