Creating server daemon. Added system info (not host info) and data usage endpoints

This commit is contained in:
2024-12-12 23:04:39 +01:00
parent 4168b93d61
commit 3c88b60e8d
17 changed files with 713 additions and 0 deletions

View File

@@ -0,0 +1,26 @@
namespace MoonlightServers.Daemon.Configuration;
public class AppConfiguration
{
public DockerData Docker { get; set; } = new();
public StorageData Storage { get; set; } = new();
public SecurityData Security { get; set; } = new();
public class DockerData
{
public string Uri { get; set; } = "unix:///var/run/docker.sock";
}
public class SecurityData
{
public string Token { get; set; }
}
public class StorageData
{
public string Volumes { get; set; }
public string VirtualDisks { get; set; }
public string Backups { get; set; }
public string Install { get; set; }
}
}

View File

@@ -0,0 +1,46 @@
using System.Net.Sockets;
using System.Text.Json;
using MoonCore.Attributes;
using MoonCore.Helpers;
using MoonlightServers.Daemon.Configuration;
using MoonlightServers.Daemon.Models.UnsafeDocker;
namespace MoonlightServers.Daemon.Helpers;
[Singleton]
public class UnsafeDockerClient
{
private readonly AppConfiguration Configuration;
public UnsafeDockerClient(AppConfiguration configuration)
{
Configuration = configuration;
}
public Task<HttpClient> CreateHttpClient()
{
var client = new HttpClient(new SocketsHttpHandler()
{
ConnectCallback = async (context, token) =>
{
var socket = new Socket(AddressFamily.Unix, SocketType.Stream, ProtocolType.IP);
var endpoint = new UnixDomainSocketEndPoint(
Formatter.ReplaceStart(Configuration.Docker.Uri, "unix://", "")
);
await socket.ConnectAsync(endpoint, token);
return new NetworkStream(socket, ownsSocket: true);
}
});
return Task.FromResult(client);
}
public async Task<DataUsageResponse> GetDataUsage()
{
using var client = await CreateHttpClient();
var responseJson = await client.GetStringAsync("http://some.random.domain/v1.47/system/df");
var response = JsonSerializer.Deserialize<DataUsageResponse>(responseJson)!;
return response;
}
}

View File

@@ -0,0 +1,33 @@
using Microsoft.AspNetCore.Mvc;
using MoonlightServers.Daemon.Services;
using MoonlightServers.DaemonShared.Http.Responses.Sys;
namespace MoonlightServers.Daemon.Http.Controllers.Sys;
[ApiController]
[Route("api/system/dataUsage")]
public class SystemDataUsageController : Controller
{
private readonly DockerInfoService DockerInfoService;
public SystemDataUsageController(DockerInfoService dockerInfoService)
{
DockerInfoService = dockerInfoService;
}
[HttpGet]
public async Task<SystemDataUsageResponse> Get()
{
var report = await DockerInfoService.GetDataUsage();
return new SystemDataUsageResponse()
{
ImagesReclaimable = report.Images.Reclaimable,
ImagesUsed = report.Images.Used,
ContainersReclaimable = report.Containers.Reclaimable,
ContainersUsed = report.Containers.Used,
BuildCacheReclaimable = report.BuildCache.Reclaimable,
BuildCacheUsed = report.BuildCache.Used
};
}
}

View File

@@ -0,0 +1,33 @@
using Microsoft.AspNetCore.Mvc;
using MoonlightServers.Daemon.Services;
using MoonlightServers.DaemonShared.Http.Responses.Sys;
namespace MoonlightServers.Daemon.Http.Controllers.Sys;
[ApiController]
[Route("api/system/info")]
public class SystemInfoController : Controller
{
private readonly SystemInfoService SystemInfoService;
private readonly DockerInfoService DockerInfoService;
public SystemInfoController(SystemInfoService systemInfoService, DockerInfoService dockerInfoService)
{
SystemInfoService = systemInfoService;
DockerInfoService = dockerInfoService;
}
[HttpGet]
public async Task<SystemInfoResponse> GetInfo()
{
return new SystemInfoResponse()
{
Uptime = await SystemInfoService.GetUptime(),
Version = "2.1.0",
CpuUsage = await SystemInfoService.GetCpuUsage(),
DockerVersion = await DockerInfoService.GetDockerVersion(),
MemoryUsage = await SystemInfoService.GetMemoryUsage(),
OsName = await SystemInfoService.GetOsName()
};
}
}

View File

@@ -0,0 +1,75 @@
using System.Text.Json.Serialization;
namespace MoonlightServers.Daemon.Models.UnsafeDocker;
public class DataUsageResponse
{
[JsonPropertyName("BuildCache")]
public BuildCacheData[] BuildCache { get; set; }
[JsonPropertyName("LayersSize")]
public long LayersSize { get; set; }
[JsonPropertyName("Images")]
public ImageData[] Images { get; set; }
[JsonPropertyName("Containers")]
public ContainerData[] Containers { get; set; }
[JsonPropertyName("Volumes")]
public VolumeData[] Volumes { get; set; }
public class BuildCacheData
{
[JsonPropertyName("Size")]
public long Size { get; set; }
[JsonPropertyName("InUse")]
public bool InUse { get; set; }
}
public class ContainerData
{
[JsonPropertyName("Id")]
public string Id { get; set; }
[JsonPropertyName("SizeRw")]
public long SizeRw { get; set; }
[JsonPropertyName("SizeRootFs")]
public long SizeRootFs { get; set; }
}
public class ImageData
{
[JsonPropertyName("Containers")]
public long Containers { get; set; }
[JsonPropertyName("Id")]
public string Id { get; set; }
[JsonPropertyName("SharedSize")]
public long SharedSize { get; set; }
[JsonPropertyName("Size")]
public long Size { get; set; }
}
public class VolumeData
{
[JsonPropertyName("Name")]
public string Name { get; set; }
[JsonPropertyName("UsageData")]
public VolumeUsageData UsageData { get; set; }
}
public class VolumeUsageData
{
[JsonPropertyName("RefCount")]
public long RefCount { get; set; }
[JsonPropertyName("Size")]
public long Size { get; set; }
}
}

View File

@@ -0,0 +1,7 @@
namespace MoonlightServers.Daemon.Models.UnsafeDocker;
public class UsageData
{
public long Used { get; set; }
public long Reclaimable { get; set; }
}

View File

@@ -0,0 +1,8 @@
namespace MoonlightServers.Daemon.Models.UnsafeDocker;
public class UsageDataReport
{
public UsageData Containers { get; set; }
public UsageData Images { get; set; }
public UsageData BuildCache { get; set; }
}

View File

@@ -0,0 +1,24 @@
<Project Sdk="Microsoft.NET.Sdk.Web">
<PropertyGroup>
<TargetFramework>net8.0</TargetFramework>
<Nullable>enable</Nullable>
<ImplicitUsings>enable</ImplicitUsings>
</PropertyGroup>
<ItemGroup>
<PackageReference Include="Docker.DotNet" Version="3.125.15" />
<PackageReference Include="MoonCore" Version="1.8.0" />
<PackageReference Include="MoonCore.Extended" Version="1.2.4" />
<PackageReference Include="Swashbuckle.AspNetCore" Version="6.6.2"/>
</ItemGroup>
<ItemGroup>
<Folder Include="Http\Middleware\" />
</ItemGroup>
<ItemGroup>
<ProjectReference Include="..\MoonlightServers.DaemonShared\MoonlightServers.DaemonShared.csproj" />
</ItemGroup>
</Project>

View File

@@ -0,0 +1,5 @@
using MoonlightServers.Daemon;
var startup = new Startup();
await startup.Run(args);

View File

@@ -0,0 +1,15 @@
{
"$schema": "http://json.schemastore.org/launchsettings.json",
"profiles": {
"http": {
"commandName": "Project",
"dotnetRunMessages": true,
"launchBrowser": false,
"launchUrl": "swagger",
"applicationUrl": "http://localhost:5275",
"environmentVariables": {
"ASPNETCORE_ENVIRONMENT": "Development"
}
}
}
}

View File

@@ -0,0 +1,52 @@
using Docker.DotNet;
using MoonCore.Attributes;
using MoonlightServers.Daemon.Helpers;
using MoonlightServers.Daemon.Models.UnsafeDocker;
namespace MoonlightServers.Daemon.Services;
[Singleton]
public class DockerInfoService
{
private readonly DockerClient DockerClient;
private readonly UnsafeDockerClient UnsafeDockerClient;
public DockerInfoService(DockerClient dockerClient, UnsafeDockerClient unsafeDockerClient)
{
DockerClient = dockerClient;
UnsafeDockerClient = unsafeDockerClient;
}
public async Task<string> GetDockerVersion()
{
var version = await DockerClient.System.GetVersionAsync();
return $"{version.Version} commit {version.GitCommit} ({version.APIVersion})";
}
public async Task<UsageDataReport> GetDataUsage()
{
var response = await UnsafeDockerClient.GetDataUsage();
var report = new UsageDataReport()
{
Containers = new UsageData()
{
Used = response.Containers.Sum(x => x.SizeRw),
Reclaimable = 0
},
Images = new UsageData()
{
Used = response.Images.Sum(x => x.Size),
Reclaimable = response.Images.Where(x => x.Containers == 0).Sum(x => x.Size)
},
BuildCache = new UsageData()
{
Used = response.BuildCache.Sum(x => x.Size),
Reclaimable = response.BuildCache.Where(x => !x.InUse).Sum(x => x.Size)
}
};
return report;
}
}

View File

@@ -0,0 +1,101 @@
using System.Diagnostics;
using System.Runtime.InteropServices;
using Docker.DotNet;
using MoonCore.Attributes;
using MoonlightServers.Daemon.Helpers;
namespace MoonlightServers.Daemon.Services;
[Singleton]
public class SystemInfoService
{
private readonly ILogger<SystemInfoService> Logger;
private readonly IHost Host;
public SystemInfoService(
ILogger<SystemInfoService> logger,
IHost host
)
{
Logger = logger;
Host = host;
}
public Task<string> GetOsName()
{
if (RuntimeInformation.IsOSPlatform(OSPlatform.Windows))
{
// Windows platform detected
var osVersion = Environment.OSVersion.Version;
return Task.FromResult($"Windows {osVersion.Major}.{osVersion.Minor}.{osVersion.Build}");
}
if (RuntimeInformation.IsOSPlatform(OSPlatform.Linux))
{
var releaseRaw = File
.ReadAllLines("/etc/os-release")
.FirstOrDefault(x => x.StartsWith("PRETTY_NAME="));
if (string.IsNullOrEmpty(releaseRaw))
return Task.FromResult("Linux (unknown release)");
var release = releaseRaw
.Replace("PRETTY_NAME=", "")
.Replace("\"", "");
if (string.IsNullOrEmpty(release))
return Task.FromResult("Linux (unknown release)");
return Task.FromResult(release);
}
if (RuntimeInformation.IsOSPlatform(OSPlatform.OSX))
{
// macOS platform detected
var osVersion = Environment.OSVersion.Version;
return Task.FromResult($"macOS {osVersion.Major}.{osVersion.Minor}.{osVersion.Build}");
}
// Unknown platform
return Task.FromResult("N/A");
}
public Task<long> GetMemoryUsage()
{
var process = Process.GetCurrentProcess();
var bytes = process.PrivateMemorySize64;
return Task.FromResult(bytes);
}
public Task<TimeSpan> GetUptime()
{
var process = Process.GetCurrentProcess();
var uptime = DateTime.Now - process.StartTime;
return Task.FromResult(uptime);
}
public Task<int> GetCpuUsage()
{
var process = Process.GetCurrentProcess();
var cpuTime = process.TotalProcessorTime;
var wallClockTime = DateTime.UtcNow - process.StartTime.ToUniversalTime();
var cpuUsage = (int)(100.0 * cpuTime.TotalMilliseconds / wallClockTime.TotalMilliseconds /
Environment.ProcessorCount);
return Task.FromResult(cpuUsage);
}
public Task Shutdown()
{
Logger.LogCritical("Restart of daemon has been requested");
Task.Run(async () =>
{
await Task.Delay(TimeSpan.FromSeconds(1));
await Host.StopAsync(CancellationToken.None);
});
return Task.CompletedTask;
}
}

View File

@@ -0,0 +1,241 @@
using System.Text.Json;
using Docker.DotNet;
using MoonCore.Configuration;
using MoonCore.Extended.Extensions;
using MoonCore.Extensions;
using MoonCore.Helpers;
using MoonCore.Services;
using MoonlightServers.Daemon.Configuration;
namespace MoonlightServers.Daemon;
public class Startup
{
private string[] Args;
// Configuration
private AppConfiguration Configuration;
private ConfigurationService ConfigurationService;
private ConfigurationOptions ConfigurationOptions;
// Logging
private ILoggerProvider[] LoggerProviders;
private ILoggerFactory LoggerFactory;
private ILogger<Startup> Logger;
// WebApplication Stuff
private WebApplication WebApplication;
private WebApplicationBuilder WebApplicationBuilder;
public async Task Run(string[] args)
{
Args = args;
await SetupStorage();
await SetupAppConfiguration();
await SetupLogging();
await CreateWebApplicationBuilder();
await RegisterAppConfiguration();
await RegisterLogging();
await RegisterBase();
await RegisterDocker();
await BuildWebApplication();
await UseBase();
await UseBaseMiddleware();
await MapBase();
await WebApplication.RunAsync();
}
private Task SetupStorage()
{
Directory.CreateDirectory("storage");
Directory.CreateDirectory(PathBuilder.Dir("storage", "logs"));
Directory.CreateDirectory(PathBuilder.Dir("storage", "plugins"));
return Task.CompletedTask;
}
#region Base
private Task RegisterBase()
{
WebApplicationBuilder.Services.AutoAddServices<Startup>();
WebApplicationBuilder.Services.AddControllers();
WebApplicationBuilder.Services.AddApiExceptionHandler();
WebApplicationBuilder.Services.AddSignalR();
return Task.CompletedTask;
}
private Task UseBase()
{
WebApplication.UseRouting();
WebApplication.UseApiExceptionHandler();
return Task.CompletedTask;
}
private Task UseBaseMiddleware()
{
return Task.CompletedTask;
}
private Task MapBase()
{
WebApplication.MapControllers();
return Task.CompletedTask;
}
#endregion
#region Docker
private Task RegisterDocker()
{
var dockerClient = new DockerClientConfiguration(
new Uri(Configuration.Docker.Uri)
).CreateClient();
WebApplicationBuilder.Services.AddSingleton(dockerClient);
return Task.CompletedTask;
}
#endregion
#region Configurations
private Task SetupAppConfiguration()
{
ConfigurationService = new ConfigurationService();
// Setup options
ConfigurationOptions = new ConfigurationOptions();
ConfigurationOptions.AddConfiguration<AppConfiguration>("app");
ConfigurationOptions.Path = PathBuilder.Dir("storage");
ConfigurationOptions.EnvironmentPrefix = "WebAppTemplate".ToUpper();
// Create minimal logger
var loggerFactory = new LoggerFactory();
loggerFactory.AddMoonCore(configuration =>
{
configuration.Console.Enable = true;
configuration.Console.EnableAnsiMode = true;
configuration.FileLogging.Enable = false;
});
var logger = loggerFactory.CreateLogger<ConfigurationService>();
// Retrieve configuration
Configuration = ConfigurationService.GetConfiguration<AppConfiguration>(
ConfigurationOptions,
logger
);
return Task.CompletedTask;
}
private Task RegisterAppConfiguration()
{
ConfigurationService.RegisterInDi(ConfigurationOptions, WebApplicationBuilder.Services);
WebApplicationBuilder.Services.AddSingleton(ConfigurationService);
return Task.CompletedTask;
}
#endregion
#region Web Application
private Task CreateWebApplicationBuilder()
{
WebApplicationBuilder = WebApplication.CreateBuilder(Args);
return Task.CompletedTask;
}
private Task BuildWebApplication()
{
WebApplication = WebApplicationBuilder.Build();
return Task.CompletedTask;
}
#endregion
#region Logging
private Task SetupLogging()
{
LoggerProviders = LoggerBuildHelper.BuildFromConfiguration(configuration =>
{
configuration.Console.Enable = true;
configuration.Console.EnableAnsiMode = true;
configuration.FileLogging.Enable = true;
configuration.FileLogging.EnableLogRotation = true;
configuration.FileLogging.Path = PathBuilder.File("storage", "logs", "WebAppTemplate.log");
configuration.FileLogging.RotateLogNameTemplate =
PathBuilder.File("storage", "logs", "WebAppTemplate.log.{0}");
});
LoggerFactory = new LoggerFactory();
LoggerFactory.AddProviders(LoggerProviders);
Logger = LoggerFactory.CreateLogger<Startup>();
return Task.CompletedTask;
}
private async Task RegisterLogging()
{
// Configure application logging
WebApplicationBuilder.Logging.ClearProviders();
WebApplicationBuilder.Logging.AddProviders(LoggerProviders);
// Logging levels
var logConfigPath = PathBuilder.File("storage", "logConfig.json");
// Ensure logging config, add a default one is missing
if (!File.Exists(logConfigPath))
{
var logLevels = new Dictionary<string, string>
{
{ "Default", "Information" },
{ "Microsoft.AspNetCore", "Warning" },
{ "System.Net.Http.HttpClient", "Warning" }
};
var logLevelsJson = JsonSerializer.Serialize(logLevels);
var logConfig = "{\"LogLevel\":" + logLevelsJson + "}";
await File.WriteAllTextAsync(logConfigPath, logConfig);
}
// Configure logging configuration
WebApplicationBuilder.Logging.AddConfiguration(
await File.ReadAllTextAsync(logConfigPath)
);
// Mute exception handler middleware
// https://github.com/dotnet/aspnetcore/issues/19740
WebApplicationBuilder.Logging.AddFilter(
"Microsoft.AspNetCore.Diagnostics.ExceptionHandlerMiddleware",
LogLevel.Critical
);
WebApplicationBuilder.Logging.AddFilter(
"Microsoft.AspNetCore.Diagnostics.DeveloperExceptionPageMiddleware",
LogLevel.Critical
);
}
#endregion
}

View File

@@ -0,0 +1,11 @@
namespace MoonlightServers.DaemonShared.Http.Responses.Sys;
public class SystemDataUsageResponse
{
public long ImagesUsed { get; set; }
public long ImagesReclaimable { get; set; }
public long ContainersUsed { get; set; }
public long ContainersReclaimable { get; set; }
public long BuildCacheUsed { get; set; }
public long BuildCacheReclaimable { get; set; }
}

View File

@@ -0,0 +1,11 @@
namespace MoonlightServers.DaemonShared.Http.Responses.Sys;
public class SystemInfoResponse
{
public string DockerVersion { get; set; }
public string Version { get; set; }
public string OsName { get; set; }
public long MemoryUsage { get; set; }
public TimeSpan Uptime { get; set; }
public int CpuUsage { get; set; }
}

View File

@@ -0,0 +1,13 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<TargetFramework>net8.0</TargetFramework>
<ImplicitUsings>enable</ImplicitUsings>
<Nullable>enable</Nullable>
</PropertyGroup>
<ItemGroup>
<Folder Include="Http\Requests\" />
</ItemGroup>
</Project>

View File

@@ -6,6 +6,10 @@ Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "MoonlightServers.Frontend",
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "MoonlightServers.Shared", "MoonlightServers.Shared\MoonlightServers.Shared.csproj", "{70FAFFFB-9EA6-4BB7-B4C0-A6BEF9684B32}"
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "MoonlightServers.Daemon", "MoonlightServers.Daemon\MoonlightServers.Daemon.csproj", "{5EAFCB32-500B-477C-937A-3DAD63C5BB1A}"
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "MoonlightServers.DaemonShared", "MoonlightServers.DaemonShared\MoonlightServers.DaemonShared.csproj", "{15EBCC5D-2440-4B5B-A046-F8327E0C1CB8}"
EndProject
Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution
Debug|Any CPU = Debug|Any CPU
@@ -24,5 +28,13 @@ Global
{70FAFFFB-9EA6-4BB7-B4C0-A6BEF9684B32}.Debug|Any CPU.Build.0 = Debug|Any CPU
{70FAFFFB-9EA6-4BB7-B4C0-A6BEF9684B32}.Release|Any CPU.ActiveCfg = Release|Any CPU
{70FAFFFB-9EA6-4BB7-B4C0-A6BEF9684B32}.Release|Any CPU.Build.0 = Release|Any CPU
{5EAFCB32-500B-477C-937A-3DAD63C5BB1A}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{5EAFCB32-500B-477C-937A-3DAD63C5BB1A}.Debug|Any CPU.Build.0 = Debug|Any CPU
{5EAFCB32-500B-477C-937A-3DAD63C5BB1A}.Release|Any CPU.ActiveCfg = Release|Any CPU
{5EAFCB32-500B-477C-937A-3DAD63C5BB1A}.Release|Any CPU.Build.0 = Release|Any CPU
{15EBCC5D-2440-4B5B-A046-F8327E0C1CB8}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{15EBCC5D-2440-4B5B-A046-F8327E0C1CB8}.Debug|Any CPU.Build.0 = Debug|Any CPU
{15EBCC5D-2440-4B5B-A046-F8327E0C1CB8}.Release|Any CPU.ActiveCfg = Release|Any CPU
{15EBCC5D-2440-4B5B-A046-F8327E0C1CB8}.Release|Any CPU.Build.0 = Release|Any CPU
EndGlobalSection
EndGlobal