31 Commits

Author SHA1 Message Date
2d1b48b0d4 Implemented statistics. Refactored storage abstractions. Added config options for docker and local storage. Added server service and server updating. 2026-03-02 15:51:05 +00:00
52dbd13fb5 Recreated plugin with new project template. Started implementing server system daemon 2026-03-01 21:09:29 +01:00
f6b71f4de6 Upgraded mooncore. Done required refactoring to function with new version 2025-10-20 19:27:31 +00:00
85392208c4 Updated to latest moonlight and mooncore version. Done refactoring to async scheme and other changes. Recreated database migrations and cleaned models 2025-09-22 12:13:57 +02:00
91fb15a03e Started implementing server service and daemon controllers 2025-09-15 21:47:07 +02:00
32f447d268 Deed debug handler. Added installation handler. Improved docker console streaming 2025-09-13 20:53:03 +02:00
160446eed0 Added online detection handler 2025-09-09 23:08:01 +02:00
b90100d250 Implemented restorer, runtime and dummy statistics. Added service registering and fixed server factory. Moved logger to server context 2025-09-07 23:15:48 +02:00
282096595d Improved comments. Started implementing docker components and other base components. Updated dependencies 2025-09-06 21:44:22 +02:00
348e9560ab Cleaned up interfaces. Extracted server state machine trigger handler to seperated classes. Removed legacy code 2025-09-06 15:34:35 +02:00
7587a7e8e3 Cleaned up project files from legacy plugin settings 2025-08-24 11:51:09 +02:00
7c2bc9d19b Improved logging in server components 2025-08-02 21:54:58 +02:00
2e4c933fbe Started implementing server deletion 2025-08-02 21:12:38 +02:00
5c170935b4 Implemented online detection. Extended ServerContext to include self reference so sub components can subscribe to the state. Improved console module detach handling. Implemented new server service to replace the old one. Added log restore when restoring 2025-07-30 20:52:24 +02:00
eaf8c36f7f Fixed event/observer issues 2025-07-30 17:12:21 +02:00
bb81ca9674 Implemented first iteration of the docker-based server installer. Added restore functionality for the installer. Wired up for basic installer testing 2025-07-29 22:24:46 +02:00
f57d33cb1e Fixed usage of IAsyncObservable. Added runtime exit handler 2025-07-29 21:14:41 +02:00
b546a168d2 Implemented restorer, wired up for basic testing. Improved abstractions and fixed observer pattern issues 2025-07-26 23:19:57 +02:00
84b3d1caf6 Implemented factory pattern for server abstraction creation. Implemented raw fs and docker provisioner. Implemented docker event service with observer pattern 2025-07-26 19:14:02 +02:00
0bef60dbc8 For extensions of base system like podman and btrfs: Started improving server abstractions to make it more extendable in order to support multiple implementations 2025-07-25 13:45:47 +02:00
bdc4ad8265 Added id ordering as postgres would return the last changed elements first which breaks pagination 2025-07-24 20:24:00 +02:00
431cdcb260 Improved server share permission handling and share ui 2025-07-24 20:19:49 +02:00
1f94752c54 Started improving server shares and general api controller structure 2025-07-24 18:28:10 +02:00
a2db7be26f Improved server header and variables page 2025-07-18 23:17:56 +02:00
265a4b280b Refactored ui. Improved console experience. Added command endpoint 2025-07-18 21:16:52 +02:00
f8c11b2dd8 Updated mooncore version 2025-07-17 22:56:52 +02:00
e83d1351cb Fixed server create ui 2025-07-16 21:29:43 +02:00
61253919cf Refactored frontend to work with the latest mooncore changes 2025-07-16 20:46:45 +02:00
383d4bb24b Improved some route templates 2025-07-15 21:06:40 +02:00
f22f0c0e51 Refactored api server project for latest mooncore changes 2025-07-15 21:04:46 +02:00
514f862a9d Started refactoring to be compatible with the changed nuget packages and the mooncore changes 2025-07-15 19:21:44 +02:00
317 changed files with 3116 additions and 18317 deletions

51
.gitignore vendored
View File

@@ -301,7 +301,7 @@ node_modules/
*.dsw
*.dsp
# Visual Studio 6 technical files
# Visual Studio 6 technical files
*.ncb
*.aps
@@ -395,44 +395,15 @@ FodyWeavers.xsd
*.msp
# JetBrains Rider
*.sln.iml
**/.idea/**
# User-specific stuff
.idea/**/workspace.xml
.idea/**/tasks.xml
.idea/**/usage.statistics.xml
.idea/**/dictionaries
.idea/**/shelf
# Style builds
**/style.min.css
**/package-lock.json
**/bun.lock
# Generated files
.idea/**/contentModel.xml
# Sensitive or high-churn files
.idea/**/dataSources/
.idea/**/dataSources.ids
.idea/**/dataSources.local.xml
.idea/**/sqlDataSources.xml
.idea/**/dynamic.xml
.idea/**/uiDesigner.xml
.idea/**/dbnavigator.xml
# Gradle
.idea/**/gradle.xml
.idea/**/libraries
# Moonlight
storage/
.idea/**/.idea
MoonlightServers.min.css
core.min.css
# Build script for nuget packages
finalPackages/
nupkgs/
# Local daemon tests
**/data/volumes/**
# Local plugin build
tmp/
style.min.css
# Secrets
**/.env
**/appsettings.json
**/appsettings.Development.json
**/storage

View File

@@ -0,0 +1,6 @@
<Project>
<ItemGroup>
<!-- Put your plugin references here -->
<!-- E.g. <PackageReference Include="MoonlightServers.Api" Version="2.1.0" /> -->
</ItemGroup>
</Project>

View File

@@ -0,0 +1,29 @@
<Project Sdk="Microsoft.NET.Sdk.Web">
<PropertyGroup>
<TargetFramework>net10.0</TargetFramework>
<Nullable>enable</Nullable>
<ImplicitUsings>enable</ImplicitUsings>
</PropertyGroup>
<ItemGroup>
<PackageReference Include="Microsoft.AspNetCore.Components.WebAssembly.Server" Version="10.0.1"/>
<PackageReference Include="Microsoft.EntityFrameworkCore.Design" Version="10.0.1">
<PrivateAssets>all</PrivateAssets>
<IncludeAssets>runtime; build; native; analyzers; buildtransitive</IncludeAssets>
</PackageReference>
<PackageReference Include="SimplePlugin" Version="1.0.2"/>
<PackageReference Include="SimplePlugin.Abstractions" Version="1.0.2"/>
</ItemGroup>
<ItemGroup>
<ProjectReference Include="..\..\MoonlightServers.Api\MoonlightServers.Api.csproj" />
<ProjectReference Include="..\MoonlightServers.Frontend.Host\MoonlightServers.Frontend.Host.csproj" />
</ItemGroup>
<Import Project="Api.props"/>
</Project>

View File

@@ -0,0 +1,9 @@
using Moonlight.Api;
using SimplePlugin.Generated;
var plugins = PluginRegistry
.Modules
.OfType<MoonlightPlugin>()
.ToArray();
await StartupHandler.RunAsync(args, plugins);

View File

@@ -0,0 +1,23 @@
{
"$schema": "https://json.schemastore.org/launchsettings.json",
"profiles": {
"http": {
"commandName": "Project",
"dotnetRunMessages": true,
"launchBrowser": false,
"applicationUrl": "http://localhost:5031",
"environmentVariables": {
"ASPNETCORE_ENVIRONMENT": "Development"
}
},
"https": {
"commandName": "Project",
"dotnetRunMessages": true,
"launchBrowser": false,
"applicationUrl": "https://localhost:7240;http://localhost:5031",
"environmentVariables": {
"ASPNETCORE_ENVIRONMENT": "Development"
}
}
}
}

View File

@@ -0,0 +1,23 @@
{
"Logging": {
"LogLevel": {
"Default": "Information",
"Microsoft.AspNetCore": "Warning"
}
},
"AllowedHosts": "*",
"Moonlight": {
"Database": {
"Host": "your-db.host",
"Username": "change_me",
"Password": "change_me",
"Database": "change_me"
},
"Oidc": {
"Authority": "http://localhost:8092",
"RequireHttpsMetadata": false,
"ClientId": "clientId",
"ClientSecret": "clientSecret"
}
}
}

View File

@@ -0,0 +1,6 @@
<Project>
<ItemGroup>
<!-- Put your plugin references here -->
<!-- E.g. <PackageReference Include="MoonlightServers.Frontend" Version="2.1.0" /> -->
</ItemGroup>
</Project>

View File

@@ -0,0 +1,22 @@
<Project Sdk="Microsoft.NET.Sdk.BlazorWebAssembly">
<PropertyGroup>
<TargetFramework>net10.0</TargetFramework>
<Nullable>enable</Nullable>
<ImplicitUsings>enable</ImplicitUsings>
<OverrideHtmlAssetPlaceholders>true</OverrideHtmlAssetPlaceholders>
</PropertyGroup>
<ItemGroup>
<PackageReference Include="Microsoft.AspNetCore.Components.WebAssembly" Version="10.0.1"/>
<PackageReference Include="Microsoft.AspNetCore.Components.WebAssembly.DevServer" Version="10.0.1" PrivateAssets="all"/>
<PackageReference Include="SimplePlugin" Version="1.0.2"/>
<PackageReference Include="SimplePlugin.Abstractions" Version="1.0.2"/>
</ItemGroup>
<ItemGroup>
<ProjectReference Include="..\..\MoonlightServers.Frontend\MoonlightServers.Frontend.csproj" />
</ItemGroup>
<Import Project="Frontend.props"/>
</Project>

View File

@@ -0,0 +1,9 @@
using Moonlight.Frontend;
using SimplePlugin.Generated;
var plugins = PluginRegistry
.Modules
.OfType<MoonlightPlugin>()
.ToArray();
await StartupHandler.RunAsync(args, plugins);

View File

@@ -0,0 +1,25 @@
import fs from 'fs';
import selectorParser from 'postcss-selector-parser';
export default function extractTailwindClasses(opts = {}) {
const classSet = new Set();
return {
postcssPlugin: 'extract-tailwind-classes',
Rule(rule) {
selectorParser(selectors => {
selectors.walkClasses(node => {
classSet.add(node.value);
});
}).processSync(rule.selector);
},
OnceExit() {
const classArray = Array.from(classSet).sort();
fs.mkdirSync('../../../Servers.Frontend/Styles', { recursive: true });
fs.writeFileSync('../../../Servers.Frontend/Styles/Servers.Frontend.map', classArray.join('\n'));
console.log(`Extracted classes ${classArray.length}`);
}
};
}
extractTailwindClasses.postcss = true;

View File

@@ -0,0 +1,19 @@
{
"scripts": {
"dev": "npx postcss styles.css -o ../wwwroot/style.min.css --watch",
"dev-build": "npx postcss styles.css -o ../wwwroot/style.min.css",
"build": "cross-env EXTRACT_CLASSES=true npx postcss styles.css -o ../wwwroot/style.min.css"
},
"dependencies": {
"@tailwindcss/postcss": "^4.1.18",
"cssnano": "^7.1.2",
"postcss": "^8.5.6",
"postcss-cli": "^11.0.1",
"postcss-selector-parser": "^7.1.1",
"tailwindcss": "^4.1.18",
"tw-animate-css": "^1.4.0"
},
"devDependencies": {
"cross-env": "^10.1.0"
}
}

View File

@@ -0,0 +1,18 @@
import tailwindcss from '@tailwindcss/postcss';
import cssnano from 'cssnano';
import extractTailwindClasses from './extract-classes.mjs';
const plugins = [
tailwindcss,
cssnano({
preset: 'default'
})
];
if (process.env.EXTRACT_CLASSES === "true") {
plugins.push(extractTailwindClasses());
}
export default {
plugins
};

View File

@@ -0,0 +1,31 @@
@import "tailwindcss";
@import "tw-animate-css";
@import "../bin/ShadcnBlazor/scrollbar.css";
@import "../bin/ShadcnBlazor/default-theme.css";
@import "./theme.css";
@source "../bin/Moonlight.Frontend/*.map";
@source "../../../Moonlight.Api/**/*.razor";
@source "../../../Moonlight.Api/**/*.cs";
@source "../../../Moonlight.Api/**/*.html";
@source "../../../Moonlight.Frontend/**/*.razor";
@source "../../../Moonlight.Frontend/**/*.cs";
@source "../../../Moonlight.Frontend/**/*.html";
@custom-variant dark (&:is(.dark *));
#blazor-error-ui {
display: none;
}
@layer base {
* {
@apply border-border outline-ring/50;
}
body {
@apply bg-background text-foreground;
}
}

View File

@@ -0,0 +1,132 @@
:root {
--radius: 0.625rem;
--background: oklch(1 0 0);
--foreground: oklch(0.129 0.042 264.695);
--card: oklch(1 0 0);
--card-foreground: oklch(0.129 0.042 264.695);
--popover: oklch(1 0 0);
--popover-foreground: oklch(0.129 0.042 264.695);
--primary: oklch(0.208 0.042 265.755);
--primary-foreground: oklch(0.984 0.003 247.858);
--secondary: oklch(0.968 0.007 247.896);
--secondary-foreground: oklch(0.208 0.042 265.755);
--muted: oklch(0.968 0.007 247.896);
--muted-foreground: oklch(0.554 0.046 257.417);
--accent: oklch(0.968 0.007 247.896);
--accent-foreground: oklch(0.208 0.042 265.755);
--destructive: oklch(0.577 0.245 27.325);
--border: oklch(0.929 0.013 255.508);
--input: oklch(0.929 0.013 255.508);
--ring: oklch(0.704 0.04 256.788);
--chart-1: oklch(0.646 0.222 41.116);
--chart-2: oklch(0.6 0.118 184.704);
--chart-3: oklch(0.398 0.07 227.392);
--chart-4: oklch(0.828 0.189 84.429);
--chart-5: oklch(0.769 0.188 70.08);
--sidebar: var(--background);
--sidebar-foreground: var(--foreground);
--sidebar-primary: var(--primary);
--sidebar-primary-foreground: var(--primary-foreground);
--sidebar-accent: var(--accent);
--sidebar-accent-foreground: var(--accent-foreground);
--sidebar-border: var(--border);
--sidebar-ring: var(--ring);
--font-sans: Inter, sans-serif;
--font-serif: Georgia, serif;
}
.dark {
/* Deep blue-slate background with purple undertones */
--background: oklch(0.16 0.028 260);
--foreground: oklch(0.98 0.008 260);
/* Cards with slightly lighter blue-slate */
--card: oklch(0.21 0.032 260);
--card-foreground: oklch(0.98 0.008 260);
/* Popovers with medium depth */
--popover: oklch(0.24 0.035 260);
--popover-foreground: oklch(0.98 0.008 260);
/* Vibrant blue-purple primary */
--primary: oklch(0.62 0.18 270);
--primary-foreground: oklch(0.99 0.005 260);
/* Secondary with blue-slate tone */
--secondary: oklch(0.27 0.038 260);
--secondary-foreground: oklch(0.98 0.008 260);
/* Muted elements */
--muted: oklch(0.25 0.035 260);
--muted-foreground: oklch(0.66 0.025 260);
/* Accent with purple-blue blend */
--accent: oklch(0.36 0.065 268);
--accent-foreground: oklch(0.98 0.008 260);
/* Destructive red with good contrast */
--destructive: oklch(0.62 0.22 25);
--destructive-foreground: oklch(0.99 0.005 260);
/* Subtle borders and inputs */
--border: oklch(0.32 0.025 260);
--input: oklch(0.30 0.030 260);
--ring: oklch(0.62 0.18 270);
/* Chart colors with blue-purple harmony */
--chart-1: oklch(0.58 0.18 270);
--chart-2: oklch(0.62 0.16 245);
--chart-3: oklch(0.68 0.15 290);
--chart-4: oklch(0.60 0.20 260);
--chart-5: oklch(0.65 0.14 280);
/* Sidebar with slightly different depth */
--sidebar: oklch(0.18 0.030 260);
--sidebar-foreground: oklch(0.97 0.008 260);
--sidebar-primary: oklch(0.60 0.17 270);
--sidebar-primary-foreground: oklch(0.99 0.005 260);
--sidebar-accent: oklch(0.26 0.038 260);
--sidebar-accent-foreground: oklch(0.98 0.008 260);
--sidebar-border: oklch(0.30 0.025 260);
--sidebar-ring: oklch(0.58 0.17 270);
}
@theme inline {
--radius-sm: calc(var(--radius) - 4px);
--radius-md: calc(var(--radius) - 2px);
--radius-lg: var(--radius);
--radius-xl: calc(var(--radius) + 4px);
--color-background: var(--background);
--color-foreground: var(--foreground);
--color-card: var(--card);
--color-card-foreground: var(--card-foreground);
--color-popover: var(--popover);
--color-popover-foreground: var(--popover-foreground);
--color-primary: var(--primary);
--color-primary-foreground: var(--primary-foreground);
--color-secondary: var(--secondary);
--color-secondary-foreground: var(--secondary-foreground);
--color-muted: var(--muted);
--color-muted-foreground: var(--muted-foreground);
--color-accent: var(--accent);
--color-accent-foreground: var(--accent-foreground);
--color-destructive: var(--destructive);
--color-border: var(--border);
--color-input: var(--input);
--color-ring: var(--ring);
--color-chart-1: var(--chart-1);
--color-chart-2: var(--chart-2);
--color-chart-3: var(--chart-3);
--color-chart-4: var(--chart-4);
--color-chart-5: var(--chart-5);
--color-sidebar: var(--sidebar);
--color-sidebar-foreground: var(--sidebar-foreground);
--color-sidebar-primary: var(--sidebar-primary);
--color-sidebar-primary-foreground: var(--sidebar-primary-foreground);
--color-sidebar-accent: var(--sidebar-accent);
--color-sidebar-accent-foreground: var(--sidebar-accent-foreground);
--color-sidebar-border: var(--sidebar-border);
--color-sidebar-ring: var(--sidebar-ring);
}

View File

@@ -0,0 +1,108 @@
<!DOCTYPE html>
<html lang="en" class="dark">
<head>
<meta charset="utf-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>Moonlight</title>
<base href="/" />
<link rel="preload" id="webassembly" />
<link rel="stylesheet" href="https://fonts.googleapis.com/css2?family=Inter:wght@400;500;600;700&display=fallback" />
<link rel="stylesheet" href="style.min.css" />
<script type="importmap"></script>
<script>
window.frontendConfig = {
STYLE_TAG_ID: 'theme-variables',
configuration: {},
applyTheme: function(cssContent) {
// Find or create the style tag
let styleTag = document.getElementById(this.STYLE_TAG_ID);
if (!styleTag) {
styleTag = document.createElement('style');
styleTag.id = this.STYLE_TAG_ID;
document.head.appendChild(styleTag);
}
// Update the style tag content
styleTag.textContent = cssContent;
},
reloadConfiguration: function (){
try {
const xhr = new XMLHttpRequest();
xhr.open('GET', '/api/frontend/config', false);
xhr.send(null);
if (xhr.status === 200) {
this.configuration = JSON.parse(xhr.responseText);
}
} catch (error) {
console.error('Failed to load initial theme:', error);
}
},
getConfiguration: function (){
return this.configuration;
},
reload: function () {
this.reloadConfiguration();
document.title = this.configuration.name;
this.applyTheme(this.configuration.themeCss);
}
};
window.frontendConfig.reload();
</script>
</head>
<body class="bg-background text-foreground">
<div id="app">
<div class="h-screen w-full flex items-center justify-center">
<div class="flex min-w-0 flex-1 flex-col items-center justify-center gap-3 rounded-lg border-dashed p-6 text-center text-balance md:p-12">
<div class="flex max-w-sm flex-col items-center gap-2 text-center">
<div class="flex shrink-0 items-center justify-center [&_svg]:pointer-events-none [&_svg]:shrink-0 bg-muted text-foreground size-10 rounded-lg [&_svg:not([class*='size-'])]:size-6">
<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none"
stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"
class="lucide lucide-zap-icon lucide-zap size-6">
<path d="M4 14a1 1 0 0 1-.78-1.63l9.9-10.2a.5.5 0 0 1 .86.46l-1.92 6.02A1 1 0 0 0 13 10h7a1 1 0 0 1 .78 1.63l-9.9 10.2a.5.5 0 0 1-.86-.46l1.92-6.02A1 1 0 0 0 11 14z"/>
</svg>
</div>
</div>
<div class="text-lg font-medium tracking-tight">
Loading application
</div>
<div class="flex w-full max-w-sm min-w-0 flex-col items-center gap-4 text-sm text-balance">
<div class="bg-primary/20 w-full relative h-2 overflow-hidden rounded-full">
<div class="bg-primary h-full w-[var(--blazor-load-percentage,0)] flex-1 transition-all">
</div>
</div>
</div>
</div>
</div>
</div>
<div id="blazor-error-ui">
An unhandled error has occurred.
<a href="." class="reload">Reload</a>
<span class="dismiss">🗙</span>
</div>
<script src="/_content/ShadcnBlazor/interop.js" defer></script>
<script src="/_content/ShadcnBlazor.Extras/interop.js" defer></script>
<script src="/_content/ShadcnBlazor.Extras/codemirror-bundle.js" defer></script>
<script src="_framework/blazor.webassembly#[.{fingerprint}].js"></script>
</body>
</html>

View File

@@ -0,0 +1,21 @@
using Microsoft.AspNetCore.Authorization;
using Microsoft.AspNetCore.Mvc;
using MoonlightServers.Shared.Http.Requests;
using MoonlightServers.Shared.Http.Responses;
namespace MoonlightServers.Api.Http.Controllers;
[Authorize]
[ApiController]
[Route("api/form")]
public class FormController : Controller
{
[HttpPost]
public async Task<ActionResult<FormResultDto>> PostAsync([FromBody] FormSubmitDto dto)
{
return new FormResultDto()
{
Result = dto.TextField.Replace(" ", string.Empty)
};
}
}

View File

@@ -0,0 +1,36 @@
<Project Sdk="Microsoft.NET.Sdk.Razor">
<PropertyGroup>
<TargetFramework>net10.0</TargetFramework>
<Nullable>enable</Nullable>
<ImplicitUsings>enable</ImplicitUsings>
</PropertyGroup>
<ItemGroup>
<SupportedPlatform Include="browser"/>
</ItemGroup>
<ItemGroup>
<PackageReference Include="Moonlight.Api" Version="2.1.0">
<ExcludeAssets>content;contentfiles</ExcludeAssets>
</PackageReference>
</ItemGroup>
<ItemGroup>
<Folder Include="Configuration\"/>
<Folder Include="Database\Entities\"/>
<Folder Include="Database\Migrations\"/>
<Folder Include="Helpers\"/>
<Folder Include="Implementations\"/>
<Folder Include="Interfaces\"/>
<Folder Include="Mappers\"/>
<Folder Include="Models\"/>
<Folder Include="Services\"/>
</ItemGroup>
<ItemGroup>
<ProjectReference Include="..\MoonlightServers.Shared\MoonlightServers.Shared.csproj" />
</ItemGroup>
</Project>

View File

@@ -0,0 +1,21 @@
using Microsoft.AspNetCore.Builder;
using Microsoft.Extensions.DependencyInjection;
using Moonlight.Api;
using SimplePlugin.Abstractions;
using SerializationContext = MoonlightServers.Shared.SerializationContext;
namespace MoonlightServers.Api;
[PluginModule]
public class Startup : MoonlightPlugin
{
public override void PreBuild(WebApplicationBuilder builder)
{
builder.Services.AddControllers()
.AddApplicationPart(typeof(Startup).Assembly)
.AddJsonOptions(options =>
{
options.JsonSerializerOptions.TypeInfoResolverChain.Add(SerializationContext.Default);
});
}
}

View File

@@ -1,13 +0,0 @@
namespace MoonlightServers.ApiServer.Database.Entities;
public class Allocation
{
public int Id { get; set; }
// Relations
public Node Node { get; set; }
public Server? Server { get; set; }
public string IpAddress { get; set; }
public int Port { get; set; }
}

View File

@@ -1,25 +0,0 @@
namespace MoonlightServers.ApiServer.Database.Entities;
public class Node
{
public int Id { get; set; }
// Relations
public List<Server> Servers { get; set; } = new();
public List<Allocation> Allocations { get; set; } = new();
// Meta
public string Name { get; set; }
// Connection details
public string Fqdn { get; set; }
public string Token { get; set; }
public string TokenId { get; set; }
public int HttpPort { get; set; }
public int FtpPort { get; set; }
public bool UseSsl { get; set; }
// Misc
public bool EnableTransparentMode { get; set; }
public bool EnableDynamicFirewall { get; set; }
}

View File

@@ -1,29 +0,0 @@
namespace MoonlightServers.ApiServer.Database.Entities;
public class Server
{
public int Id { get; set; }
// Relations
public Star Star { get; set; }
public Node Node { get; set; }
public List<Allocation> Allocations { get; set; } = new();
public List<ServerVariable> Variables { get; set; } = new();
public List<ServerBackup> Backups { get; set; } = new();
public List<ServerShare> Shares { get; set; } = new();
// Meta
public string Name { get; set; }
public int OwnerId { get; set; }
// Star specific stuff
public string? StartupOverride { get; set; }
public int DockerImageIndex { get; set; }
// Resources and limits
public int Cpu { get; set; }
public int Memory { get; set; }
public int Disk { get; set; }
public bool UseVirtualDisk { get; set; }
public int Bandwidth { get; set; }
}

View File

@@ -1,13 +0,0 @@
namespace MoonlightServers.ApiServer.Database.Entities;
public class ServerBackup
{
public int Id { get; set; }
public DateTime CreatedAt { get; set; }
public DateTime CompletedAt { get; set; }
public long Size { get; set; }
public bool Successful { get; set; }
public bool Completed { get; set; }
}

View File

@@ -1,20 +0,0 @@
using System.ComponentModel.DataAnnotations.Schema;
using MoonlightServers.ApiServer.Models;
namespace MoonlightServers.ApiServer.Database.Entities;
public class ServerShare
{
public int Id { get; set; }
public int UserId { get; set; }
public Server Server { get; set; }
public ServerShareContent Content { get; set; } = new();
[Column(TypeName="timestamp with time zone")]
public DateTime CreatedAt { get; set; }
[Column(TypeName="timestamp with time zone")]
public DateTime UpdatedAt { get; set; }
}

View File

@@ -1,12 +0,0 @@
namespace MoonlightServers.ApiServer.Database.Entities;
public class ServerVariable
{
public int Id { get; set; }
// Relations
public Server Server { get; set; }
public string Key { get; set; }
public string Value { get; set; }
}

View File

@@ -1,33 +0,0 @@
namespace MoonlightServers.ApiServer.Database.Entities;
public class Star
{
public int Id { get; set; }
// References
public List<StarVariable> Variables { get; set; } = new();
public List<StarDockerImage> DockerImages { get; set; } = new();
// Meta
public string Name { get; set; }
public string Version { get; set; }
public string Author { get; set; }
public string? UpdateUrl { get; set; }
public string? DonateUrl { get; set; }
// Start and stop
public string StartupCommand { get; set; }
public string StopCommand { get; set; }
public string OnlineDetection { get; set; }
// Install
public string InstallShell { get; set; }
public string InstallDockerImage { get; set; }
public string InstallScript { get; set; }
// Misc
public int RequiredAllocations { get; set; }
public bool AllowDockerImageChange { get; set; }
public int DefaultDockerImage { get; set; }
public string ParseConfiguration { get; set; }
}

View File

@@ -1,11 +0,0 @@
namespace MoonlightServers.ApiServer.Database.Entities;
public class StarDockerImage
{
public int Id { get; set; }
public Star Star { get; set; }
public string DisplayName { get; set; }
public string Identifier { get; set; }
public bool AutoPulling { get; set; }
}

View File

@@ -1,21 +0,0 @@
using MoonlightServers.Shared.Enums;
namespace MoonlightServers.ApiServer.Database.Entities;
public class StarVariable
{
public int Id { get; set; }
public Star Star { get; set; }
public string Name { get; set; }
public string Description { get; set; }
public string Key { get; set; }
public string DefaultValue { get; set; }
public bool AllowViewing { get; set; }
public bool AllowEditing { get; set; }
public StarVariableType Type { get; set; }
public string? Filter { get; set; }
}

View File

@@ -1,452 +0,0 @@
// <auto-generated />
using System;
using Microsoft.EntityFrameworkCore;
using Microsoft.EntityFrameworkCore.Infrastructure;
using Microsoft.EntityFrameworkCore.Migrations;
using Microsoft.EntityFrameworkCore.Storage.ValueConversion;
using MoonlightServers.ApiServer.Database;
using Npgsql.EntityFrameworkCore.PostgreSQL.Metadata;
#nullable disable
namespace MoonlightServers.ApiServer.Database.Migrations
{
[DbContext(typeof(ServersDataContext))]
[Migration("20250226210232_RecreatedMigrationsForPostgresql")]
partial class RecreatedMigrationsForPostgresql
{
/// <inheritdoc />
protected override void BuildTargetModel(ModelBuilder modelBuilder)
{
#pragma warning disable 612, 618
modelBuilder
.HasAnnotation("ProductVersion", "8.0.11")
.HasAnnotation("Relational:MaxIdentifierLength", 63);
NpgsqlModelBuilderExtensions.UseIdentityByDefaultColumns(modelBuilder);
modelBuilder.Entity("MoonlightServers.ApiServer.Database.Entities.Allocation", b =>
{
b.Property<int>("Id")
.ValueGeneratedOnAdd()
.HasColumnType("integer");
NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property<int>("Id"));
b.Property<string>("IpAddress")
.IsRequired()
.HasColumnType("text");
b.Property<int>("NodeId")
.HasColumnType("integer");
b.Property<int>("Port")
.HasColumnType("integer");
b.Property<int?>("ServerId")
.HasColumnType("integer");
b.HasKey("Id");
b.HasIndex("NodeId");
b.HasIndex("ServerId");
b.ToTable("Servers_Allocations", (string)null);
});
modelBuilder.Entity("MoonlightServers.ApiServer.Database.Entities.Node", b =>
{
b.Property<int>("Id")
.ValueGeneratedOnAdd()
.HasColumnType("integer");
NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property<int>("Id"));
b.Property<bool>("EnableDynamicFirewall")
.HasColumnType("boolean");
b.Property<bool>("EnableTransparentMode")
.HasColumnType("boolean");
b.Property<string>("Fqdn")
.IsRequired()
.HasColumnType("text");
b.Property<int>("FtpPort")
.HasColumnType("integer");
b.Property<int>("HttpPort")
.HasColumnType("integer");
b.Property<string>("Name")
.IsRequired()
.HasColumnType("text");
b.Property<string>("Token")
.IsRequired()
.HasColumnType("text");
b.Property<bool>("UseSsl")
.HasColumnType("boolean");
b.HasKey("Id");
b.ToTable("Servers_Nodes", (string)null);
});
modelBuilder.Entity("MoonlightServers.ApiServer.Database.Entities.Server", b =>
{
b.Property<int>("Id")
.ValueGeneratedOnAdd()
.HasColumnType("integer");
NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property<int>("Id"));
b.Property<int>("Bandwidth")
.HasColumnType("integer");
b.Property<int>("Cpu")
.HasColumnType("integer");
b.Property<int>("Disk")
.HasColumnType("integer");
b.Property<int>("DockerImageIndex")
.HasColumnType("integer");
b.Property<int>("Memory")
.HasColumnType("integer");
b.Property<string>("Name")
.IsRequired()
.HasColumnType("text");
b.Property<int>("NodeId")
.HasColumnType("integer");
b.Property<int>("OwnerId")
.HasColumnType("integer");
b.Property<int>("StarId")
.HasColumnType("integer");
b.Property<string>("StartupOverride")
.HasColumnType("text");
b.Property<bool>("UseVirtualDisk")
.HasColumnType("boolean");
b.HasKey("Id");
b.HasIndex("NodeId");
b.HasIndex("StarId");
b.ToTable("Servers_Servers", (string)null);
});
modelBuilder.Entity("MoonlightServers.ApiServer.Database.Entities.ServerBackup", b =>
{
b.Property<int>("Id")
.ValueGeneratedOnAdd()
.HasColumnType("integer");
NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property<int>("Id"));
b.Property<bool>("Completed")
.HasColumnType("boolean");
b.Property<DateTime>("CompletedAt")
.HasColumnType("timestamp with time zone");
b.Property<DateTime>("CreatedAt")
.HasColumnType("timestamp with time zone");
b.Property<int?>("ServerId")
.HasColumnType("integer");
b.Property<long>("Size")
.HasColumnType("bigint");
b.Property<bool>("Successful")
.HasColumnType("boolean");
b.HasKey("Id");
b.HasIndex("ServerId");
b.ToTable("Servers_ServerBackups", (string)null);
});
modelBuilder.Entity("MoonlightServers.ApiServer.Database.Entities.ServerVariable", b =>
{
b.Property<int>("Id")
.ValueGeneratedOnAdd()
.HasColumnType("integer");
NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property<int>("Id"));
b.Property<string>("Key")
.IsRequired()
.HasColumnType("text");
b.Property<int>("ServerId")
.HasColumnType("integer");
b.Property<string>("Value")
.IsRequired()
.HasColumnType("text");
b.HasKey("Id");
b.HasIndex("ServerId");
b.ToTable("Servers_ServerVariables", (string)null);
});
modelBuilder.Entity("MoonlightServers.ApiServer.Database.Entities.Star", b =>
{
b.Property<int>("Id")
.ValueGeneratedOnAdd()
.HasColumnType("integer");
NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property<int>("Id"));
b.Property<bool>("AllowDockerImageChange")
.HasColumnType("boolean");
b.Property<string>("Author")
.IsRequired()
.HasColumnType("text");
b.Property<int>("DefaultDockerImage")
.HasColumnType("integer");
b.Property<string>("DonateUrl")
.HasColumnType("text");
b.Property<string>("InstallDockerImage")
.IsRequired()
.HasColumnType("text");
b.Property<string>("InstallScript")
.IsRequired()
.HasColumnType("text");
b.Property<string>("InstallShell")
.IsRequired()
.HasColumnType("text");
b.Property<string>("Name")
.IsRequired()
.HasColumnType("text");
b.Property<string>("OnlineDetection")
.IsRequired()
.HasColumnType("text");
b.Property<string>("ParseConfiguration")
.IsRequired()
.HasColumnType("text");
b.Property<int>("RequiredAllocations")
.HasColumnType("integer");
b.Property<string>("StartupCommand")
.IsRequired()
.HasColumnType("text");
b.Property<string>("StopCommand")
.IsRequired()
.HasColumnType("text");
b.Property<string>("UpdateUrl")
.HasColumnType("text");
b.Property<string>("Version")
.IsRequired()
.HasColumnType("text");
b.HasKey("Id");
b.ToTable("Servers_Stars", (string)null);
});
modelBuilder.Entity("MoonlightServers.ApiServer.Database.Entities.StarDockerImage", b =>
{
b.Property<int>("Id")
.ValueGeneratedOnAdd()
.HasColumnType("integer");
NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property<int>("Id"));
b.Property<bool>("AutoPulling")
.HasColumnType("boolean");
b.Property<string>("DisplayName")
.IsRequired()
.HasColumnType("text");
b.Property<string>("Identifier")
.IsRequired()
.HasColumnType("text");
b.Property<int>("StarId")
.HasColumnType("integer");
b.HasKey("Id");
b.HasIndex("StarId");
b.ToTable("Servers_StarDockerImages", (string)null);
});
modelBuilder.Entity("MoonlightServers.ApiServer.Database.Entities.StarVariable", b =>
{
b.Property<int>("Id")
.ValueGeneratedOnAdd()
.HasColumnType("integer");
NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property<int>("Id"));
b.Property<bool>("AllowEditing")
.HasColumnType("boolean");
b.Property<bool>("AllowViewing")
.HasColumnType("boolean");
b.Property<string>("DefaultValue")
.IsRequired()
.HasColumnType("text");
b.Property<string>("Description")
.IsRequired()
.HasColumnType("text");
b.Property<string>("Filter")
.HasColumnType("text");
b.Property<string>("Key")
.IsRequired()
.HasColumnType("text");
b.Property<string>("Name")
.IsRequired()
.HasColumnType("text");
b.Property<int>("StarId")
.HasColumnType("integer");
b.Property<int>("Type")
.HasColumnType("integer");
b.HasKey("Id");
b.HasIndex("StarId");
b.ToTable("Servers_StarVariables", (string)null);
});
modelBuilder.Entity("MoonlightServers.ApiServer.Database.Entities.Allocation", b =>
{
b.HasOne("MoonlightServers.ApiServer.Database.Entities.Node", "Node")
.WithMany("Allocations")
.HasForeignKey("NodeId")
.OnDelete(DeleteBehavior.Cascade)
.IsRequired();
b.HasOne("MoonlightServers.ApiServer.Database.Entities.Server", "Server")
.WithMany("Allocations")
.HasForeignKey("ServerId");
b.Navigation("Node");
b.Navigation("Server");
});
modelBuilder.Entity("MoonlightServers.ApiServer.Database.Entities.Server", b =>
{
b.HasOne("MoonlightServers.ApiServer.Database.Entities.Node", "Node")
.WithMany("Servers")
.HasForeignKey("NodeId")
.OnDelete(DeleteBehavior.Cascade)
.IsRequired();
b.HasOne("MoonlightServers.ApiServer.Database.Entities.Star", "Star")
.WithMany()
.HasForeignKey("StarId")
.OnDelete(DeleteBehavior.Cascade)
.IsRequired();
b.Navigation("Node");
b.Navigation("Star");
});
modelBuilder.Entity("MoonlightServers.ApiServer.Database.Entities.ServerBackup", b =>
{
b.HasOne("MoonlightServers.ApiServer.Database.Entities.Server", null)
.WithMany("Backups")
.HasForeignKey("ServerId");
});
modelBuilder.Entity("MoonlightServers.ApiServer.Database.Entities.ServerVariable", b =>
{
b.HasOne("MoonlightServers.ApiServer.Database.Entities.Server", "Server")
.WithMany("Variables")
.HasForeignKey("ServerId")
.OnDelete(DeleteBehavior.Cascade)
.IsRequired();
b.Navigation("Server");
});
modelBuilder.Entity("MoonlightServers.ApiServer.Database.Entities.StarDockerImage", b =>
{
b.HasOne("MoonlightServers.ApiServer.Database.Entities.Star", "Star")
.WithMany("DockerImages")
.HasForeignKey("StarId")
.OnDelete(DeleteBehavior.Cascade)
.IsRequired();
b.Navigation("Star");
});
modelBuilder.Entity("MoonlightServers.ApiServer.Database.Entities.StarVariable", b =>
{
b.HasOne("MoonlightServers.ApiServer.Database.Entities.Star", "Star")
.WithMany("Variables")
.HasForeignKey("StarId")
.OnDelete(DeleteBehavior.Cascade)
.IsRequired();
b.Navigation("Star");
});
modelBuilder.Entity("MoonlightServers.ApiServer.Database.Entities.Node", b =>
{
b.Navigation("Allocations");
b.Navigation("Servers");
});
modelBuilder.Entity("MoonlightServers.ApiServer.Database.Entities.Server", b =>
{
b.Navigation("Allocations");
b.Navigation("Backups");
b.Navigation("Variables");
});
modelBuilder.Entity("MoonlightServers.ApiServer.Database.Entities.Star", b =>
{
b.Navigation("DockerImages");
b.Navigation("Variables");
});
#pragma warning restore 612, 618
}
}
}

View File

@@ -1,286 +0,0 @@
using System;
using Microsoft.EntityFrameworkCore.Migrations;
using Npgsql.EntityFrameworkCore.PostgreSQL.Metadata;
#nullable disable
namespace MoonlightServers.ApiServer.Database.Migrations
{
/// <inheritdoc />
public partial class RecreatedMigrationsForPostgresql : Migration
{
/// <inheritdoc />
protected override void Up(MigrationBuilder migrationBuilder)
{
migrationBuilder.CreateTable(
name: "Servers_Nodes",
columns: table => new
{
Id = table.Column<int>(type: "integer", nullable: false)
.Annotation("Npgsql:ValueGenerationStrategy", NpgsqlValueGenerationStrategy.IdentityByDefaultColumn),
Name = table.Column<string>(type: "text", nullable: false),
Fqdn = table.Column<string>(type: "text", nullable: false),
Token = table.Column<string>(type: "text", nullable: false),
HttpPort = table.Column<int>(type: "integer", nullable: false),
FtpPort = table.Column<int>(type: "integer", nullable: false),
UseSsl = table.Column<bool>(type: "boolean", nullable: false),
EnableTransparentMode = table.Column<bool>(type: "boolean", nullable: false),
EnableDynamicFirewall = table.Column<bool>(type: "boolean", nullable: false)
},
constraints: table =>
{
table.PrimaryKey("PK_Servers_Nodes", x => x.Id);
});
migrationBuilder.CreateTable(
name: "Servers_Stars",
columns: table => new
{
Id = table.Column<int>(type: "integer", nullable: false)
.Annotation("Npgsql:ValueGenerationStrategy", NpgsqlValueGenerationStrategy.IdentityByDefaultColumn),
Name = table.Column<string>(type: "text", nullable: false),
Version = table.Column<string>(type: "text", nullable: false),
Author = table.Column<string>(type: "text", nullable: false),
UpdateUrl = table.Column<string>(type: "text", nullable: true),
DonateUrl = table.Column<string>(type: "text", nullable: true),
StartupCommand = table.Column<string>(type: "text", nullable: false),
StopCommand = table.Column<string>(type: "text", nullable: false),
OnlineDetection = table.Column<string>(type: "text", nullable: false),
InstallShell = table.Column<string>(type: "text", nullable: false),
InstallDockerImage = table.Column<string>(type: "text", nullable: false),
InstallScript = table.Column<string>(type: "text", nullable: false),
RequiredAllocations = table.Column<int>(type: "integer", nullable: false),
AllowDockerImageChange = table.Column<bool>(type: "boolean", nullable: false),
DefaultDockerImage = table.Column<int>(type: "integer", nullable: false),
ParseConfiguration = table.Column<string>(type: "text", nullable: false)
},
constraints: table =>
{
table.PrimaryKey("PK_Servers_Stars", x => x.Id);
});
migrationBuilder.CreateTable(
name: "Servers_Servers",
columns: table => new
{
Id = table.Column<int>(type: "integer", nullable: false)
.Annotation("Npgsql:ValueGenerationStrategy", NpgsqlValueGenerationStrategy.IdentityByDefaultColumn),
StarId = table.Column<int>(type: "integer", nullable: false),
NodeId = table.Column<int>(type: "integer", nullable: false),
Name = table.Column<string>(type: "text", nullable: false),
OwnerId = table.Column<int>(type: "integer", nullable: false),
StartupOverride = table.Column<string>(type: "text", nullable: true),
DockerImageIndex = table.Column<int>(type: "integer", nullable: false),
Cpu = table.Column<int>(type: "integer", nullable: false),
Memory = table.Column<int>(type: "integer", nullable: false),
Disk = table.Column<int>(type: "integer", nullable: false),
UseVirtualDisk = table.Column<bool>(type: "boolean", nullable: false),
Bandwidth = table.Column<int>(type: "integer", nullable: false)
},
constraints: table =>
{
table.PrimaryKey("PK_Servers_Servers", x => x.Id);
table.ForeignKey(
name: "FK_Servers_Servers_Servers_Nodes_NodeId",
column: x => x.NodeId,
principalTable: "Servers_Nodes",
principalColumn: "Id",
onDelete: ReferentialAction.Cascade);
table.ForeignKey(
name: "FK_Servers_Servers_Servers_Stars_StarId",
column: x => x.StarId,
principalTable: "Servers_Stars",
principalColumn: "Id",
onDelete: ReferentialAction.Cascade);
});
migrationBuilder.CreateTable(
name: "Servers_StarDockerImages",
columns: table => new
{
Id = table.Column<int>(type: "integer", nullable: false)
.Annotation("Npgsql:ValueGenerationStrategy", NpgsqlValueGenerationStrategy.IdentityByDefaultColumn),
StarId = table.Column<int>(type: "integer", nullable: false),
DisplayName = table.Column<string>(type: "text", nullable: false),
Identifier = table.Column<string>(type: "text", nullable: false),
AutoPulling = table.Column<bool>(type: "boolean", nullable: false)
},
constraints: table =>
{
table.PrimaryKey("PK_Servers_StarDockerImages", x => x.Id);
table.ForeignKey(
name: "FK_Servers_StarDockerImages_Servers_Stars_StarId",
column: x => x.StarId,
principalTable: "Servers_Stars",
principalColumn: "Id",
onDelete: ReferentialAction.Cascade);
});
migrationBuilder.CreateTable(
name: "Servers_StarVariables",
columns: table => new
{
Id = table.Column<int>(type: "integer", nullable: false)
.Annotation("Npgsql:ValueGenerationStrategy", NpgsqlValueGenerationStrategy.IdentityByDefaultColumn),
StarId = table.Column<int>(type: "integer", nullable: false),
Name = table.Column<string>(type: "text", nullable: false),
Description = table.Column<string>(type: "text", nullable: false),
Key = table.Column<string>(type: "text", nullable: false),
DefaultValue = table.Column<string>(type: "text", nullable: false),
AllowViewing = table.Column<bool>(type: "boolean", nullable: false),
AllowEditing = table.Column<bool>(type: "boolean", nullable: false),
Type = table.Column<int>(type: "integer", nullable: false),
Filter = table.Column<string>(type: "text", nullable: true)
},
constraints: table =>
{
table.PrimaryKey("PK_Servers_StarVariables", x => x.Id);
table.ForeignKey(
name: "FK_Servers_StarVariables_Servers_Stars_StarId",
column: x => x.StarId,
principalTable: "Servers_Stars",
principalColumn: "Id",
onDelete: ReferentialAction.Cascade);
});
migrationBuilder.CreateTable(
name: "Servers_Allocations",
columns: table => new
{
Id = table.Column<int>(type: "integer", nullable: false)
.Annotation("Npgsql:ValueGenerationStrategy", NpgsqlValueGenerationStrategy.IdentityByDefaultColumn),
NodeId = table.Column<int>(type: "integer", nullable: false),
ServerId = table.Column<int>(type: "integer", nullable: true),
IpAddress = table.Column<string>(type: "text", nullable: false),
Port = table.Column<int>(type: "integer", nullable: false)
},
constraints: table =>
{
table.PrimaryKey("PK_Servers_Allocations", x => x.Id);
table.ForeignKey(
name: "FK_Servers_Allocations_Servers_Nodes_NodeId",
column: x => x.NodeId,
principalTable: "Servers_Nodes",
principalColumn: "Id",
onDelete: ReferentialAction.Cascade);
table.ForeignKey(
name: "FK_Servers_Allocations_Servers_Servers_ServerId",
column: x => x.ServerId,
principalTable: "Servers_Servers",
principalColumn: "Id");
});
migrationBuilder.CreateTable(
name: "Servers_ServerBackups",
columns: table => new
{
Id = table.Column<int>(type: "integer", nullable: false)
.Annotation("Npgsql:ValueGenerationStrategy", NpgsqlValueGenerationStrategy.IdentityByDefaultColumn),
CreatedAt = table.Column<DateTime>(type: "timestamp with time zone", nullable: false),
CompletedAt = table.Column<DateTime>(type: "timestamp with time zone", nullable: false),
Size = table.Column<long>(type: "bigint", nullable: false),
Successful = table.Column<bool>(type: "boolean", nullable: false),
Completed = table.Column<bool>(type: "boolean", nullable: false),
ServerId = table.Column<int>(type: "integer", nullable: true)
},
constraints: table =>
{
table.PrimaryKey("PK_Servers_ServerBackups", x => x.Id);
table.ForeignKey(
name: "FK_Servers_ServerBackups_Servers_Servers_ServerId",
column: x => x.ServerId,
principalTable: "Servers_Servers",
principalColumn: "Id");
});
migrationBuilder.CreateTable(
name: "Servers_ServerVariables",
columns: table => new
{
Id = table.Column<int>(type: "integer", nullable: false)
.Annotation("Npgsql:ValueGenerationStrategy", NpgsqlValueGenerationStrategy.IdentityByDefaultColumn),
ServerId = table.Column<int>(type: "integer", nullable: false),
Key = table.Column<string>(type: "text", nullable: false),
Value = table.Column<string>(type: "text", nullable: false)
},
constraints: table =>
{
table.PrimaryKey("PK_Servers_ServerVariables", x => x.Id);
table.ForeignKey(
name: "FK_Servers_ServerVariables_Servers_Servers_ServerId",
column: x => x.ServerId,
principalTable: "Servers_Servers",
principalColumn: "Id",
onDelete: ReferentialAction.Cascade);
});
migrationBuilder.CreateIndex(
name: "IX_Servers_Allocations_NodeId",
table: "Servers_Allocations",
column: "NodeId");
migrationBuilder.CreateIndex(
name: "IX_Servers_Allocations_ServerId",
table: "Servers_Allocations",
column: "ServerId");
migrationBuilder.CreateIndex(
name: "IX_Servers_ServerBackups_ServerId",
table: "Servers_ServerBackups",
column: "ServerId");
migrationBuilder.CreateIndex(
name: "IX_Servers_Servers_NodeId",
table: "Servers_Servers",
column: "NodeId");
migrationBuilder.CreateIndex(
name: "IX_Servers_Servers_StarId",
table: "Servers_Servers",
column: "StarId");
migrationBuilder.CreateIndex(
name: "IX_Servers_ServerVariables_ServerId",
table: "Servers_ServerVariables",
column: "ServerId");
migrationBuilder.CreateIndex(
name: "IX_Servers_StarDockerImages_StarId",
table: "Servers_StarDockerImages",
column: "StarId");
migrationBuilder.CreateIndex(
name: "IX_Servers_StarVariables_StarId",
table: "Servers_StarVariables",
column: "StarId");
}
/// <inheritdoc />
protected override void Down(MigrationBuilder migrationBuilder)
{
migrationBuilder.DropTable(
name: "Servers_Allocations");
migrationBuilder.DropTable(
name: "Servers_ServerBackups");
migrationBuilder.DropTable(
name: "Servers_ServerVariables");
migrationBuilder.DropTable(
name: "Servers_StarDockerImages");
migrationBuilder.DropTable(
name: "Servers_StarVariables");
migrationBuilder.DropTable(
name: "Servers_Servers");
migrationBuilder.DropTable(
name: "Servers_Nodes");
migrationBuilder.DropTable(
name: "Servers_Stars");
}
}
}

View File

@@ -1,456 +0,0 @@
// <auto-generated />
using System;
using Microsoft.EntityFrameworkCore;
using Microsoft.EntityFrameworkCore.Infrastructure;
using Microsoft.EntityFrameworkCore.Migrations;
using Microsoft.EntityFrameworkCore.Storage.ValueConversion;
using MoonlightServers.ApiServer.Database;
using Npgsql.EntityFrameworkCore.PostgreSQL.Metadata;
#nullable disable
namespace MoonlightServers.ApiServer.Database.Migrations
{
[DbContext(typeof(ServersDataContext))]
[Migration("20250301142415_AddedTokenIdField")]
partial class AddedTokenIdField
{
/// <inheritdoc />
protected override void BuildTargetModel(ModelBuilder modelBuilder)
{
#pragma warning disable 612, 618
modelBuilder
.HasAnnotation("ProductVersion", "8.0.11")
.HasAnnotation("Relational:MaxIdentifierLength", 63);
NpgsqlModelBuilderExtensions.UseIdentityByDefaultColumns(modelBuilder);
modelBuilder.Entity("MoonlightServers.ApiServer.Database.Entities.Allocation", b =>
{
b.Property<int>("Id")
.ValueGeneratedOnAdd()
.HasColumnType("integer");
NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property<int>("Id"));
b.Property<string>("IpAddress")
.IsRequired()
.HasColumnType("text");
b.Property<int>("NodeId")
.HasColumnType("integer");
b.Property<int>("Port")
.HasColumnType("integer");
b.Property<int?>("ServerId")
.HasColumnType("integer");
b.HasKey("Id");
b.HasIndex("NodeId");
b.HasIndex("ServerId");
b.ToTable("Servers_Allocations", (string)null);
});
modelBuilder.Entity("MoonlightServers.ApiServer.Database.Entities.Node", b =>
{
b.Property<int>("Id")
.ValueGeneratedOnAdd()
.HasColumnType("integer");
NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property<int>("Id"));
b.Property<bool>("EnableDynamicFirewall")
.HasColumnType("boolean");
b.Property<bool>("EnableTransparentMode")
.HasColumnType("boolean");
b.Property<string>("Fqdn")
.IsRequired()
.HasColumnType("text");
b.Property<int>("FtpPort")
.HasColumnType("integer");
b.Property<int>("HttpPort")
.HasColumnType("integer");
b.Property<string>("Name")
.IsRequired()
.HasColumnType("text");
b.Property<string>("Token")
.IsRequired()
.HasColumnType("text");
b.Property<string>("TokenId")
.IsRequired()
.HasColumnType("text");
b.Property<bool>("UseSsl")
.HasColumnType("boolean");
b.HasKey("Id");
b.ToTable("Servers_Nodes", (string)null);
});
modelBuilder.Entity("MoonlightServers.ApiServer.Database.Entities.Server", b =>
{
b.Property<int>("Id")
.ValueGeneratedOnAdd()
.HasColumnType("integer");
NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property<int>("Id"));
b.Property<int>("Bandwidth")
.HasColumnType("integer");
b.Property<int>("Cpu")
.HasColumnType("integer");
b.Property<int>("Disk")
.HasColumnType("integer");
b.Property<int>("DockerImageIndex")
.HasColumnType("integer");
b.Property<int>("Memory")
.HasColumnType("integer");
b.Property<string>("Name")
.IsRequired()
.HasColumnType("text");
b.Property<int>("NodeId")
.HasColumnType("integer");
b.Property<int>("OwnerId")
.HasColumnType("integer");
b.Property<int>("StarId")
.HasColumnType("integer");
b.Property<string>("StartupOverride")
.HasColumnType("text");
b.Property<bool>("UseVirtualDisk")
.HasColumnType("boolean");
b.HasKey("Id");
b.HasIndex("NodeId");
b.HasIndex("StarId");
b.ToTable("Servers_Servers", (string)null);
});
modelBuilder.Entity("MoonlightServers.ApiServer.Database.Entities.ServerBackup", b =>
{
b.Property<int>("Id")
.ValueGeneratedOnAdd()
.HasColumnType("integer");
NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property<int>("Id"));
b.Property<bool>("Completed")
.HasColumnType("boolean");
b.Property<DateTime>("CompletedAt")
.HasColumnType("timestamp with time zone");
b.Property<DateTime>("CreatedAt")
.HasColumnType("timestamp with time zone");
b.Property<int?>("ServerId")
.HasColumnType("integer");
b.Property<long>("Size")
.HasColumnType("bigint");
b.Property<bool>("Successful")
.HasColumnType("boolean");
b.HasKey("Id");
b.HasIndex("ServerId");
b.ToTable("Servers_ServerBackups", (string)null);
});
modelBuilder.Entity("MoonlightServers.ApiServer.Database.Entities.ServerVariable", b =>
{
b.Property<int>("Id")
.ValueGeneratedOnAdd()
.HasColumnType("integer");
NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property<int>("Id"));
b.Property<string>("Key")
.IsRequired()
.HasColumnType("text");
b.Property<int>("ServerId")
.HasColumnType("integer");
b.Property<string>("Value")
.IsRequired()
.HasColumnType("text");
b.HasKey("Id");
b.HasIndex("ServerId");
b.ToTable("Servers_ServerVariables", (string)null);
});
modelBuilder.Entity("MoonlightServers.ApiServer.Database.Entities.Star", b =>
{
b.Property<int>("Id")
.ValueGeneratedOnAdd()
.HasColumnType("integer");
NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property<int>("Id"));
b.Property<bool>("AllowDockerImageChange")
.HasColumnType("boolean");
b.Property<string>("Author")
.IsRequired()
.HasColumnType("text");
b.Property<int>("DefaultDockerImage")
.HasColumnType("integer");
b.Property<string>("DonateUrl")
.HasColumnType("text");
b.Property<string>("InstallDockerImage")
.IsRequired()
.HasColumnType("text");
b.Property<string>("InstallScript")
.IsRequired()
.HasColumnType("text");
b.Property<string>("InstallShell")
.IsRequired()
.HasColumnType("text");
b.Property<string>("Name")
.IsRequired()
.HasColumnType("text");
b.Property<string>("OnlineDetection")
.IsRequired()
.HasColumnType("text");
b.Property<string>("ParseConfiguration")
.IsRequired()
.HasColumnType("text");
b.Property<int>("RequiredAllocations")
.HasColumnType("integer");
b.Property<string>("StartupCommand")
.IsRequired()
.HasColumnType("text");
b.Property<string>("StopCommand")
.IsRequired()
.HasColumnType("text");
b.Property<string>("UpdateUrl")
.HasColumnType("text");
b.Property<string>("Version")
.IsRequired()
.HasColumnType("text");
b.HasKey("Id");
b.ToTable("Servers_Stars", (string)null);
});
modelBuilder.Entity("MoonlightServers.ApiServer.Database.Entities.StarDockerImage", b =>
{
b.Property<int>("Id")
.ValueGeneratedOnAdd()
.HasColumnType("integer");
NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property<int>("Id"));
b.Property<bool>("AutoPulling")
.HasColumnType("boolean");
b.Property<string>("DisplayName")
.IsRequired()
.HasColumnType("text");
b.Property<string>("Identifier")
.IsRequired()
.HasColumnType("text");
b.Property<int>("StarId")
.HasColumnType("integer");
b.HasKey("Id");
b.HasIndex("StarId");
b.ToTable("Servers_StarDockerImages", (string)null);
});
modelBuilder.Entity("MoonlightServers.ApiServer.Database.Entities.StarVariable", b =>
{
b.Property<int>("Id")
.ValueGeneratedOnAdd()
.HasColumnType("integer");
NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property<int>("Id"));
b.Property<bool>("AllowEditing")
.HasColumnType("boolean");
b.Property<bool>("AllowViewing")
.HasColumnType("boolean");
b.Property<string>("DefaultValue")
.IsRequired()
.HasColumnType("text");
b.Property<string>("Description")
.IsRequired()
.HasColumnType("text");
b.Property<string>("Filter")
.HasColumnType("text");
b.Property<string>("Key")
.IsRequired()
.HasColumnType("text");
b.Property<string>("Name")
.IsRequired()
.HasColumnType("text");
b.Property<int>("StarId")
.HasColumnType("integer");
b.Property<int>("Type")
.HasColumnType("integer");
b.HasKey("Id");
b.HasIndex("StarId");
b.ToTable("Servers_StarVariables", (string)null);
});
modelBuilder.Entity("MoonlightServers.ApiServer.Database.Entities.Allocation", b =>
{
b.HasOne("MoonlightServers.ApiServer.Database.Entities.Node", "Node")
.WithMany("Allocations")
.HasForeignKey("NodeId")
.OnDelete(DeleteBehavior.Cascade)
.IsRequired();
b.HasOne("MoonlightServers.ApiServer.Database.Entities.Server", "Server")
.WithMany("Allocations")
.HasForeignKey("ServerId");
b.Navigation("Node");
b.Navigation("Server");
});
modelBuilder.Entity("MoonlightServers.ApiServer.Database.Entities.Server", b =>
{
b.HasOne("MoonlightServers.ApiServer.Database.Entities.Node", "Node")
.WithMany("Servers")
.HasForeignKey("NodeId")
.OnDelete(DeleteBehavior.Cascade)
.IsRequired();
b.HasOne("MoonlightServers.ApiServer.Database.Entities.Star", "Star")
.WithMany()
.HasForeignKey("StarId")
.OnDelete(DeleteBehavior.Cascade)
.IsRequired();
b.Navigation("Node");
b.Navigation("Star");
});
modelBuilder.Entity("MoonlightServers.ApiServer.Database.Entities.ServerBackup", b =>
{
b.HasOne("MoonlightServers.ApiServer.Database.Entities.Server", null)
.WithMany("Backups")
.HasForeignKey("ServerId");
});
modelBuilder.Entity("MoonlightServers.ApiServer.Database.Entities.ServerVariable", b =>
{
b.HasOne("MoonlightServers.ApiServer.Database.Entities.Server", "Server")
.WithMany("Variables")
.HasForeignKey("ServerId")
.OnDelete(DeleteBehavior.Cascade)
.IsRequired();
b.Navigation("Server");
});
modelBuilder.Entity("MoonlightServers.ApiServer.Database.Entities.StarDockerImage", b =>
{
b.HasOne("MoonlightServers.ApiServer.Database.Entities.Star", "Star")
.WithMany("DockerImages")
.HasForeignKey("StarId")
.OnDelete(DeleteBehavior.Cascade)
.IsRequired();
b.Navigation("Star");
});
modelBuilder.Entity("MoonlightServers.ApiServer.Database.Entities.StarVariable", b =>
{
b.HasOne("MoonlightServers.ApiServer.Database.Entities.Star", "Star")
.WithMany("Variables")
.HasForeignKey("StarId")
.OnDelete(DeleteBehavior.Cascade)
.IsRequired();
b.Navigation("Star");
});
modelBuilder.Entity("MoonlightServers.ApiServer.Database.Entities.Node", b =>
{
b.Navigation("Allocations");
b.Navigation("Servers");
});
modelBuilder.Entity("MoonlightServers.ApiServer.Database.Entities.Server", b =>
{
b.Navigation("Allocations");
b.Navigation("Backups");
b.Navigation("Variables");
});
modelBuilder.Entity("MoonlightServers.ApiServer.Database.Entities.Star", b =>
{
b.Navigation("DockerImages");
b.Navigation("Variables");
});
#pragma warning restore 612, 618
}
}
}

View File

@@ -1,29 +0,0 @@
using Microsoft.EntityFrameworkCore.Migrations;
#nullable disable
namespace MoonlightServers.ApiServer.Database.Migrations
{
/// <inheritdoc />
public partial class AddedTokenIdField : Migration
{
/// <inheritdoc />
protected override void Up(MigrationBuilder migrationBuilder)
{
migrationBuilder.AddColumn<string>(
name: "TokenId",
table: "Servers_Nodes",
type: "text",
nullable: false,
defaultValue: "");
}
/// <inheritdoc />
protected override void Down(MigrationBuilder migrationBuilder)
{
migrationBuilder.DropColumn(
name: "TokenId",
table: "Servers_Nodes");
}
}
}

View File

@@ -1,540 +0,0 @@
// <auto-generated />
using System;
using Microsoft.EntityFrameworkCore;
using Microsoft.EntityFrameworkCore.Infrastructure;
using Microsoft.EntityFrameworkCore.Migrations;
using Microsoft.EntityFrameworkCore.Storage.ValueConversion;
using MoonlightServers.ApiServer.Database;
using Npgsql.EntityFrameworkCore.PostgreSQL.Metadata;
#nullable disable
namespace MoonlightServers.ApiServer.Database.Migrations
{
[DbContext(typeof(ServersDataContext))]
[Migration("20250606121013_AddedShares")]
partial class AddedShares
{
/// <inheritdoc />
protected override void BuildTargetModel(ModelBuilder modelBuilder)
{
#pragma warning disable 612, 618
modelBuilder
.HasAnnotation("ProductVersion", "9.0.5")
.HasAnnotation("Relational:MaxIdentifierLength", 63);
NpgsqlModelBuilderExtensions.UseIdentityByDefaultColumns(modelBuilder);
modelBuilder.Entity("MoonlightServers.ApiServer.Database.Entities.Allocation", b =>
{
b.Property<int>("Id")
.ValueGeneratedOnAdd()
.HasColumnType("integer");
NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property<int>("Id"));
b.Property<string>("IpAddress")
.IsRequired()
.HasColumnType("text");
b.Property<int>("NodeId")
.HasColumnType("integer");
b.Property<int>("Port")
.HasColumnType("integer");
b.Property<int?>("ServerId")
.HasColumnType("integer");
b.HasKey("Id");
b.HasIndex("NodeId");
b.HasIndex("ServerId");
b.ToTable("Servers_Allocations", (string)null);
});
modelBuilder.Entity("MoonlightServers.ApiServer.Database.Entities.Node", b =>
{
b.Property<int>("Id")
.ValueGeneratedOnAdd()
.HasColumnType("integer");
NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property<int>("Id"));
b.Property<bool>("EnableDynamicFirewall")
.HasColumnType("boolean");
b.Property<bool>("EnableTransparentMode")
.HasColumnType("boolean");
b.Property<string>("Fqdn")
.IsRequired()
.HasColumnType("text");
b.Property<int>("FtpPort")
.HasColumnType("integer");
b.Property<int>("HttpPort")
.HasColumnType("integer");
b.Property<string>("Name")
.IsRequired()
.HasColumnType("text");
b.Property<string>("Token")
.IsRequired()
.HasColumnType("text");
b.Property<string>("TokenId")
.IsRequired()
.HasColumnType("text");
b.Property<bool>("UseSsl")
.HasColumnType("boolean");
b.HasKey("Id");
b.ToTable("Servers_Nodes", (string)null);
});
modelBuilder.Entity("MoonlightServers.ApiServer.Database.Entities.Server", b =>
{
b.Property<int>("Id")
.ValueGeneratedOnAdd()
.HasColumnType("integer");
NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property<int>("Id"));
b.Property<int>("Bandwidth")
.HasColumnType("integer");
b.Property<int>("Cpu")
.HasColumnType("integer");
b.Property<int>("Disk")
.HasColumnType("integer");
b.Property<int>("DockerImageIndex")
.HasColumnType("integer");
b.Property<int>("Memory")
.HasColumnType("integer");
b.Property<string>("Name")
.IsRequired()
.HasColumnType("text");
b.Property<int>("NodeId")
.HasColumnType("integer");
b.Property<int>("OwnerId")
.HasColumnType("integer");
b.Property<int>("StarId")
.HasColumnType("integer");
b.Property<string>("StartupOverride")
.HasColumnType("text");
b.Property<bool>("UseVirtualDisk")
.HasColumnType("boolean");
b.HasKey("Id");
b.HasIndex("NodeId");
b.HasIndex("StarId");
b.ToTable("Servers_Servers", (string)null);
});
modelBuilder.Entity("MoonlightServers.ApiServer.Database.Entities.ServerBackup", b =>
{
b.Property<int>("Id")
.ValueGeneratedOnAdd()
.HasColumnType("integer");
NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property<int>("Id"));
b.Property<bool>("Completed")
.HasColumnType("boolean");
b.Property<DateTime>("CompletedAt")
.HasColumnType("timestamp with time zone");
b.Property<DateTime>("CreatedAt")
.HasColumnType("timestamp with time zone");
b.Property<int?>("ServerId")
.HasColumnType("integer");
b.Property<long>("Size")
.HasColumnType("bigint");
b.Property<bool>("Successful")
.HasColumnType("boolean");
b.HasKey("Id");
b.HasIndex("ServerId");
b.ToTable("Servers_ServerBackups", (string)null);
});
modelBuilder.Entity("MoonlightServers.ApiServer.Database.Entities.ServerShare", b =>
{
b.Property<int>("Id")
.ValueGeneratedOnAdd()
.HasColumnType("integer");
NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property<int>("Id"));
b.Property<DateTime>("CreatedAt")
.HasColumnType("timestamp with time zone");
b.Property<int>("ServerId")
.HasColumnType("integer");
b.Property<DateTime>("UpdatedAt")
.HasColumnType("timestamp with time zone");
b.Property<int>("UserId")
.HasColumnType("integer");
b.HasKey("Id");
b.HasIndex("ServerId");
b.ToTable("Servers_ServerShares", (string)null);
});
modelBuilder.Entity("MoonlightServers.ApiServer.Database.Entities.ServerVariable", b =>
{
b.Property<int>("Id")
.ValueGeneratedOnAdd()
.HasColumnType("integer");
NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property<int>("Id"));
b.Property<string>("Key")
.IsRequired()
.HasColumnType("text");
b.Property<int>("ServerId")
.HasColumnType("integer");
b.Property<string>("Value")
.IsRequired()
.HasColumnType("text");
b.HasKey("Id");
b.HasIndex("ServerId");
b.ToTable("Servers_ServerVariables", (string)null);
});
modelBuilder.Entity("MoonlightServers.ApiServer.Database.Entities.Star", b =>
{
b.Property<int>("Id")
.ValueGeneratedOnAdd()
.HasColumnType("integer");
NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property<int>("Id"));
b.Property<bool>("AllowDockerImageChange")
.HasColumnType("boolean");
b.Property<string>("Author")
.IsRequired()
.HasColumnType("text");
b.Property<int>("DefaultDockerImage")
.HasColumnType("integer");
b.Property<string>("DonateUrl")
.HasColumnType("text");
b.Property<string>("InstallDockerImage")
.IsRequired()
.HasColumnType("text");
b.Property<string>("InstallScript")
.IsRequired()
.HasColumnType("text");
b.Property<string>("InstallShell")
.IsRequired()
.HasColumnType("text");
b.Property<string>("Name")
.IsRequired()
.HasColumnType("text");
b.Property<string>("OnlineDetection")
.IsRequired()
.HasColumnType("text");
b.Property<string>("ParseConfiguration")
.IsRequired()
.HasColumnType("text");
b.Property<int>("RequiredAllocations")
.HasColumnType("integer");
b.Property<string>("StartupCommand")
.IsRequired()
.HasColumnType("text");
b.Property<string>("StopCommand")
.IsRequired()
.HasColumnType("text");
b.Property<string>("UpdateUrl")
.HasColumnType("text");
b.Property<string>("Version")
.IsRequired()
.HasColumnType("text");
b.HasKey("Id");
b.ToTable("Servers_Stars", (string)null);
});
modelBuilder.Entity("MoonlightServers.ApiServer.Database.Entities.StarDockerImage", b =>
{
b.Property<int>("Id")
.ValueGeneratedOnAdd()
.HasColumnType("integer");
NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property<int>("Id"));
b.Property<bool>("AutoPulling")
.HasColumnType("boolean");
b.Property<string>("DisplayName")
.IsRequired()
.HasColumnType("text");
b.Property<string>("Identifier")
.IsRequired()
.HasColumnType("text");
b.Property<int>("StarId")
.HasColumnType("integer");
b.HasKey("Id");
b.HasIndex("StarId");
b.ToTable("Servers_StarDockerImages", (string)null);
});
modelBuilder.Entity("MoonlightServers.ApiServer.Database.Entities.StarVariable", b =>
{
b.Property<int>("Id")
.ValueGeneratedOnAdd()
.HasColumnType("integer");
NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property<int>("Id"));
b.Property<bool>("AllowEditing")
.HasColumnType("boolean");
b.Property<bool>("AllowViewing")
.HasColumnType("boolean");
b.Property<string>("DefaultValue")
.IsRequired()
.HasColumnType("text");
b.Property<string>("Description")
.IsRequired()
.HasColumnType("text");
b.Property<string>("Filter")
.HasColumnType("text");
b.Property<string>("Key")
.IsRequired()
.HasColumnType("text");
b.Property<string>("Name")
.IsRequired()
.HasColumnType("text");
b.Property<int>("StarId")
.HasColumnType("integer");
b.Property<int>("Type")
.HasColumnType("integer");
b.HasKey("Id");
b.HasIndex("StarId");
b.ToTable("Servers_StarVariables", (string)null);
});
modelBuilder.Entity("MoonlightServers.ApiServer.Database.Entities.Allocation", b =>
{
b.HasOne("MoonlightServers.ApiServer.Database.Entities.Node", "Node")
.WithMany("Allocations")
.HasForeignKey("NodeId")
.OnDelete(DeleteBehavior.Cascade)
.IsRequired();
b.HasOne("MoonlightServers.ApiServer.Database.Entities.Server", "Server")
.WithMany("Allocations")
.HasForeignKey("ServerId");
b.Navigation("Node");
b.Navigation("Server");
});
modelBuilder.Entity("MoonlightServers.ApiServer.Database.Entities.Server", b =>
{
b.HasOne("MoonlightServers.ApiServer.Database.Entities.Node", "Node")
.WithMany("Servers")
.HasForeignKey("NodeId")
.OnDelete(DeleteBehavior.Cascade)
.IsRequired();
b.HasOne("MoonlightServers.ApiServer.Database.Entities.Star", "Star")
.WithMany()
.HasForeignKey("StarId")
.OnDelete(DeleteBehavior.Cascade)
.IsRequired();
b.Navigation("Node");
b.Navigation("Star");
});
modelBuilder.Entity("MoonlightServers.ApiServer.Database.Entities.ServerBackup", b =>
{
b.HasOne("MoonlightServers.ApiServer.Database.Entities.Server", null)
.WithMany("Backups")
.HasForeignKey("ServerId");
});
modelBuilder.Entity("MoonlightServers.ApiServer.Database.Entities.ServerShare", b =>
{
b.HasOne("MoonlightServers.ApiServer.Database.Entities.Server", "Server")
.WithMany("Shares")
.HasForeignKey("ServerId")
.OnDelete(DeleteBehavior.Cascade)
.IsRequired();
b.OwnsOne("MoonlightServers.ApiServer.Models.ServerShareContent", "Content", b1 =>
{
b1.Property<int>("ServerShareId")
.HasColumnType("integer");
b1.HasKey("ServerShareId");
b1.ToTable("Servers_ServerShares");
b1.ToJson("Content");
b1.WithOwner()
.HasForeignKey("ServerShareId");
b1.OwnsMany("MoonlightServers.ApiServer.Models.ServerSharePermission", "Permissions", b2 =>
{
b2.Property<int>("ServerShareContentServerShareId")
.HasColumnType("integer");
b2.Property<int>("__synthesizedOrdinal")
.ValueGeneratedOnAdd()
.HasColumnType("integer");
b2.Property<string>("Name")
.IsRequired()
.HasColumnType("text");
b2.Property<int>("Type")
.HasColumnType("integer");
b2.HasKey("ServerShareContentServerShareId", "__synthesizedOrdinal");
b2.ToTable("Servers_ServerShares");
b2.WithOwner()
.HasForeignKey("ServerShareContentServerShareId");
});
b1.Navigation("Permissions");
});
b.Navigation("Content")
.IsRequired();
b.Navigation("Server");
});
modelBuilder.Entity("MoonlightServers.ApiServer.Database.Entities.ServerVariable", b =>
{
b.HasOne("MoonlightServers.ApiServer.Database.Entities.Server", "Server")
.WithMany("Variables")
.HasForeignKey("ServerId")
.OnDelete(DeleteBehavior.Cascade)
.IsRequired();
b.Navigation("Server");
});
modelBuilder.Entity("MoonlightServers.ApiServer.Database.Entities.StarDockerImage", b =>
{
b.HasOne("MoonlightServers.ApiServer.Database.Entities.Star", "Star")
.WithMany("DockerImages")
.HasForeignKey("StarId")
.OnDelete(DeleteBehavior.Cascade)
.IsRequired();
b.Navigation("Star");
});
modelBuilder.Entity("MoonlightServers.ApiServer.Database.Entities.StarVariable", b =>
{
b.HasOne("MoonlightServers.ApiServer.Database.Entities.Star", "Star")
.WithMany("Variables")
.HasForeignKey("StarId")
.OnDelete(DeleteBehavior.Cascade)
.IsRequired();
b.Navigation("Star");
});
modelBuilder.Entity("MoonlightServers.ApiServer.Database.Entities.Node", b =>
{
b.Navigation("Allocations");
b.Navigation("Servers");
});
modelBuilder.Entity("MoonlightServers.ApiServer.Database.Entities.Server", b =>
{
b.Navigation("Allocations");
b.Navigation("Backups");
b.Navigation("Shares");
b.Navigation("Variables");
});
modelBuilder.Entity("MoonlightServers.ApiServer.Database.Entities.Star", b =>
{
b.Navigation("DockerImages");
b.Navigation("Variables");
});
#pragma warning restore 612, 618
}
}
}

View File

@@ -1,51 +0,0 @@
using System;
using Microsoft.EntityFrameworkCore.Migrations;
using Npgsql.EntityFrameworkCore.PostgreSQL.Metadata;
#nullable disable
namespace MoonlightServers.ApiServer.Database.Migrations
{
/// <inheritdoc />
public partial class AddedShares : Migration
{
/// <inheritdoc />
protected override void Up(MigrationBuilder migrationBuilder)
{
migrationBuilder.CreateTable(
name: "Servers_ServerShares",
columns: table => new
{
Id = table.Column<int>(type: "integer", nullable: false)
.Annotation("Npgsql:ValueGenerationStrategy", NpgsqlValueGenerationStrategy.IdentityByDefaultColumn),
UserId = table.Column<int>(type: "integer", nullable: false),
ServerId = table.Column<int>(type: "integer", nullable: false),
CreatedAt = table.Column<DateTime>(type: "timestamp with time zone", nullable: false),
UpdatedAt = table.Column<DateTime>(type: "timestamp with time zone", nullable: false),
Content = table.Column<string>(type: "jsonb", nullable: false)
},
constraints: table =>
{
table.PrimaryKey("PK_Servers_ServerShares", x => x.Id);
table.ForeignKey(
name: "FK_Servers_ServerShares_Servers_Servers_ServerId",
column: x => x.ServerId,
principalTable: "Servers_Servers",
principalColumn: "Id",
onDelete: ReferentialAction.Cascade);
});
migrationBuilder.CreateIndex(
name: "IX_Servers_ServerShares_ServerId",
table: "Servers_ServerShares",
column: "ServerId");
}
/// <inheritdoc />
protected override void Down(MigrationBuilder migrationBuilder)
{
migrationBuilder.DropTable(
name: "Servers_ServerShares");
}
}
}

View File

@@ -1,537 +0,0 @@
// <auto-generated />
using System;
using Microsoft.EntityFrameworkCore;
using Microsoft.EntityFrameworkCore.Infrastructure;
using Microsoft.EntityFrameworkCore.Storage.ValueConversion;
using MoonlightServers.ApiServer.Database;
using Npgsql.EntityFrameworkCore.PostgreSQL.Metadata;
#nullable disable
namespace MoonlightServers.ApiServer.Database.Migrations
{
[DbContext(typeof(ServersDataContext))]
partial class ServersDataContextModelSnapshot : ModelSnapshot
{
protected override void BuildModel(ModelBuilder modelBuilder)
{
#pragma warning disable 612, 618
modelBuilder
.HasAnnotation("ProductVersion", "9.0.5")
.HasAnnotation("Relational:MaxIdentifierLength", 63);
NpgsqlModelBuilderExtensions.UseIdentityByDefaultColumns(modelBuilder);
modelBuilder.Entity("MoonlightServers.ApiServer.Database.Entities.Allocation", b =>
{
b.Property<int>("Id")
.ValueGeneratedOnAdd()
.HasColumnType("integer");
NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property<int>("Id"));
b.Property<string>("IpAddress")
.IsRequired()
.HasColumnType("text");
b.Property<int>("NodeId")
.HasColumnType("integer");
b.Property<int>("Port")
.HasColumnType("integer");
b.Property<int?>("ServerId")
.HasColumnType("integer");
b.HasKey("Id");
b.HasIndex("NodeId");
b.HasIndex("ServerId");
b.ToTable("Servers_Allocations", (string)null);
});
modelBuilder.Entity("MoonlightServers.ApiServer.Database.Entities.Node", b =>
{
b.Property<int>("Id")
.ValueGeneratedOnAdd()
.HasColumnType("integer");
NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property<int>("Id"));
b.Property<bool>("EnableDynamicFirewall")
.HasColumnType("boolean");
b.Property<bool>("EnableTransparentMode")
.HasColumnType("boolean");
b.Property<string>("Fqdn")
.IsRequired()
.HasColumnType("text");
b.Property<int>("FtpPort")
.HasColumnType("integer");
b.Property<int>("HttpPort")
.HasColumnType("integer");
b.Property<string>("Name")
.IsRequired()
.HasColumnType("text");
b.Property<string>("Token")
.IsRequired()
.HasColumnType("text");
b.Property<string>("TokenId")
.IsRequired()
.HasColumnType("text");
b.Property<bool>("UseSsl")
.HasColumnType("boolean");
b.HasKey("Id");
b.ToTable("Servers_Nodes", (string)null);
});
modelBuilder.Entity("MoonlightServers.ApiServer.Database.Entities.Server", b =>
{
b.Property<int>("Id")
.ValueGeneratedOnAdd()
.HasColumnType("integer");
NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property<int>("Id"));
b.Property<int>("Bandwidth")
.HasColumnType("integer");
b.Property<int>("Cpu")
.HasColumnType("integer");
b.Property<int>("Disk")
.HasColumnType("integer");
b.Property<int>("DockerImageIndex")
.HasColumnType("integer");
b.Property<int>("Memory")
.HasColumnType("integer");
b.Property<string>("Name")
.IsRequired()
.HasColumnType("text");
b.Property<int>("NodeId")
.HasColumnType("integer");
b.Property<int>("OwnerId")
.HasColumnType("integer");
b.Property<int>("StarId")
.HasColumnType("integer");
b.Property<string>("StartupOverride")
.HasColumnType("text");
b.Property<bool>("UseVirtualDisk")
.HasColumnType("boolean");
b.HasKey("Id");
b.HasIndex("NodeId");
b.HasIndex("StarId");
b.ToTable("Servers_Servers", (string)null);
});
modelBuilder.Entity("MoonlightServers.ApiServer.Database.Entities.ServerBackup", b =>
{
b.Property<int>("Id")
.ValueGeneratedOnAdd()
.HasColumnType("integer");
NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property<int>("Id"));
b.Property<bool>("Completed")
.HasColumnType("boolean");
b.Property<DateTime>("CompletedAt")
.HasColumnType("timestamp with time zone");
b.Property<DateTime>("CreatedAt")
.HasColumnType("timestamp with time zone");
b.Property<int?>("ServerId")
.HasColumnType("integer");
b.Property<long>("Size")
.HasColumnType("bigint");
b.Property<bool>("Successful")
.HasColumnType("boolean");
b.HasKey("Id");
b.HasIndex("ServerId");
b.ToTable("Servers_ServerBackups", (string)null);
});
modelBuilder.Entity("MoonlightServers.ApiServer.Database.Entities.ServerShare", b =>
{
b.Property<int>("Id")
.ValueGeneratedOnAdd()
.HasColumnType("integer");
NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property<int>("Id"));
b.Property<DateTime>("CreatedAt")
.HasColumnType("timestamp with time zone");
b.Property<int>("ServerId")
.HasColumnType("integer");
b.Property<DateTime>("UpdatedAt")
.HasColumnType("timestamp with time zone");
b.Property<int>("UserId")
.HasColumnType("integer");
b.HasKey("Id");
b.HasIndex("ServerId");
b.ToTable("Servers_ServerShares", (string)null);
});
modelBuilder.Entity("MoonlightServers.ApiServer.Database.Entities.ServerVariable", b =>
{
b.Property<int>("Id")
.ValueGeneratedOnAdd()
.HasColumnType("integer");
NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property<int>("Id"));
b.Property<string>("Key")
.IsRequired()
.HasColumnType("text");
b.Property<int>("ServerId")
.HasColumnType("integer");
b.Property<string>("Value")
.IsRequired()
.HasColumnType("text");
b.HasKey("Id");
b.HasIndex("ServerId");
b.ToTable("Servers_ServerVariables", (string)null);
});
modelBuilder.Entity("MoonlightServers.ApiServer.Database.Entities.Star", b =>
{
b.Property<int>("Id")
.ValueGeneratedOnAdd()
.HasColumnType("integer");
NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property<int>("Id"));
b.Property<bool>("AllowDockerImageChange")
.HasColumnType("boolean");
b.Property<string>("Author")
.IsRequired()
.HasColumnType("text");
b.Property<int>("DefaultDockerImage")
.HasColumnType("integer");
b.Property<string>("DonateUrl")
.HasColumnType("text");
b.Property<string>("InstallDockerImage")
.IsRequired()
.HasColumnType("text");
b.Property<string>("InstallScript")
.IsRequired()
.HasColumnType("text");
b.Property<string>("InstallShell")
.IsRequired()
.HasColumnType("text");
b.Property<string>("Name")
.IsRequired()
.HasColumnType("text");
b.Property<string>("OnlineDetection")
.IsRequired()
.HasColumnType("text");
b.Property<string>("ParseConfiguration")
.IsRequired()
.HasColumnType("text");
b.Property<int>("RequiredAllocations")
.HasColumnType("integer");
b.Property<string>("StartupCommand")
.IsRequired()
.HasColumnType("text");
b.Property<string>("StopCommand")
.IsRequired()
.HasColumnType("text");
b.Property<string>("UpdateUrl")
.HasColumnType("text");
b.Property<string>("Version")
.IsRequired()
.HasColumnType("text");
b.HasKey("Id");
b.ToTable("Servers_Stars", (string)null);
});
modelBuilder.Entity("MoonlightServers.ApiServer.Database.Entities.StarDockerImage", b =>
{
b.Property<int>("Id")
.ValueGeneratedOnAdd()
.HasColumnType("integer");
NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property<int>("Id"));
b.Property<bool>("AutoPulling")
.HasColumnType("boolean");
b.Property<string>("DisplayName")
.IsRequired()
.HasColumnType("text");
b.Property<string>("Identifier")
.IsRequired()
.HasColumnType("text");
b.Property<int>("StarId")
.HasColumnType("integer");
b.HasKey("Id");
b.HasIndex("StarId");
b.ToTable("Servers_StarDockerImages", (string)null);
});
modelBuilder.Entity("MoonlightServers.ApiServer.Database.Entities.StarVariable", b =>
{
b.Property<int>("Id")
.ValueGeneratedOnAdd()
.HasColumnType("integer");
NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property<int>("Id"));
b.Property<bool>("AllowEditing")
.HasColumnType("boolean");
b.Property<bool>("AllowViewing")
.HasColumnType("boolean");
b.Property<string>("DefaultValue")
.IsRequired()
.HasColumnType("text");
b.Property<string>("Description")
.IsRequired()
.HasColumnType("text");
b.Property<string>("Filter")
.HasColumnType("text");
b.Property<string>("Key")
.IsRequired()
.HasColumnType("text");
b.Property<string>("Name")
.IsRequired()
.HasColumnType("text");
b.Property<int>("StarId")
.HasColumnType("integer");
b.Property<int>("Type")
.HasColumnType("integer");
b.HasKey("Id");
b.HasIndex("StarId");
b.ToTable("Servers_StarVariables", (string)null);
});
modelBuilder.Entity("MoonlightServers.ApiServer.Database.Entities.Allocation", b =>
{
b.HasOne("MoonlightServers.ApiServer.Database.Entities.Node", "Node")
.WithMany("Allocations")
.HasForeignKey("NodeId")
.OnDelete(DeleteBehavior.Cascade)
.IsRequired();
b.HasOne("MoonlightServers.ApiServer.Database.Entities.Server", "Server")
.WithMany("Allocations")
.HasForeignKey("ServerId");
b.Navigation("Node");
b.Navigation("Server");
});
modelBuilder.Entity("MoonlightServers.ApiServer.Database.Entities.Server", b =>
{
b.HasOne("MoonlightServers.ApiServer.Database.Entities.Node", "Node")
.WithMany("Servers")
.HasForeignKey("NodeId")
.OnDelete(DeleteBehavior.Cascade)
.IsRequired();
b.HasOne("MoonlightServers.ApiServer.Database.Entities.Star", "Star")
.WithMany()
.HasForeignKey("StarId")
.OnDelete(DeleteBehavior.Cascade)
.IsRequired();
b.Navigation("Node");
b.Navigation("Star");
});
modelBuilder.Entity("MoonlightServers.ApiServer.Database.Entities.ServerBackup", b =>
{
b.HasOne("MoonlightServers.ApiServer.Database.Entities.Server", null)
.WithMany("Backups")
.HasForeignKey("ServerId");
});
modelBuilder.Entity("MoonlightServers.ApiServer.Database.Entities.ServerShare", b =>
{
b.HasOne("MoonlightServers.ApiServer.Database.Entities.Server", "Server")
.WithMany("Shares")
.HasForeignKey("ServerId")
.OnDelete(DeleteBehavior.Cascade)
.IsRequired();
b.OwnsOne("MoonlightServers.ApiServer.Models.ServerShareContent", "Content", b1 =>
{
b1.Property<int>("ServerShareId")
.HasColumnType("integer");
b1.HasKey("ServerShareId");
b1.ToTable("Servers_ServerShares");
b1.ToJson("Content");
b1.WithOwner()
.HasForeignKey("ServerShareId");
b1.OwnsMany("MoonlightServers.ApiServer.Models.ServerSharePermission", "Permissions", b2 =>
{
b2.Property<int>("ServerShareContentServerShareId")
.HasColumnType("integer");
b2.Property<int>("__synthesizedOrdinal")
.ValueGeneratedOnAdd()
.HasColumnType("integer");
b2.Property<string>("Name")
.IsRequired()
.HasColumnType("text");
b2.Property<int>("Type")
.HasColumnType("integer");
b2.HasKey("ServerShareContentServerShareId", "__synthesizedOrdinal");
b2.ToTable("Servers_ServerShares");
b2.WithOwner()
.HasForeignKey("ServerShareContentServerShareId");
});
b1.Navigation("Permissions");
});
b.Navigation("Content")
.IsRequired();
b.Navigation("Server");
});
modelBuilder.Entity("MoonlightServers.ApiServer.Database.Entities.ServerVariable", b =>
{
b.HasOne("MoonlightServers.ApiServer.Database.Entities.Server", "Server")
.WithMany("Variables")
.HasForeignKey("ServerId")
.OnDelete(DeleteBehavior.Cascade)
.IsRequired();
b.Navigation("Server");
});
modelBuilder.Entity("MoonlightServers.ApiServer.Database.Entities.StarDockerImage", b =>
{
b.HasOne("MoonlightServers.ApiServer.Database.Entities.Star", "Star")
.WithMany("DockerImages")
.HasForeignKey("StarId")
.OnDelete(DeleteBehavior.Cascade)
.IsRequired();
b.Navigation("Star");
});
modelBuilder.Entity("MoonlightServers.ApiServer.Database.Entities.StarVariable", b =>
{
b.HasOne("MoonlightServers.ApiServer.Database.Entities.Star", "Star")
.WithMany("Variables")
.HasForeignKey("StarId")
.OnDelete(DeleteBehavior.Cascade)
.IsRequired();
b.Navigation("Star");
});
modelBuilder.Entity("MoonlightServers.ApiServer.Database.Entities.Node", b =>
{
b.Navigation("Allocations");
b.Navigation("Servers");
});
modelBuilder.Entity("MoonlightServers.ApiServer.Database.Entities.Server", b =>
{
b.Navigation("Allocations");
b.Navigation("Backups");
b.Navigation("Shares");
b.Navigation("Variables");
});
modelBuilder.Entity("MoonlightServers.ApiServer.Database.Entities.Star", b =>
{
b.Navigation("DockerImages");
b.Navigation("Variables");
});
#pragma warning restore 612, 618
}
}
}

View File

@@ -1,57 +0,0 @@
using Microsoft.EntityFrameworkCore;
using MoonCore.Extended.SingleDb;
using Moonlight.ApiServer.Configuration;
using MoonlightServers.ApiServer.Database.Entities;
using MoonlightServers.ApiServer.Models;
using MoonlightServers.Shared.Models;
namespace MoonlightServers.ApiServer.Database;
public class ServersDataContext : DatabaseContext
{
public override string Prefix { get; } = "Servers";
public DbSet<Allocation> Allocations { get; set; }
public DbSet<Node> Nodes { get; set; }
public DbSet<Server> Servers { get; set; }
public DbSet<ServerBackup> ServerBackups { get; set; }
public DbSet<ServerShare> ServerShares { get; set; }
public DbSet<ServerVariable> ServerVariables { get; set; }
public DbSet<Star> Stars { get; set; }
public DbSet<StarDockerImage> StarDockerImages { get; set; }
public DbSet<StarVariable> StarVariables { get; set; }
public ServersDataContext(AppConfiguration configuration)
{
Options = new()
{
Host = configuration.Database.Host,
Port = configuration.Database.Port,
Username = configuration.Database.Username,
Password = configuration.Database.Password,
Database = configuration.Database.Database
};
}
protected override void OnModelCreating(ModelBuilder modelBuilder)
{
base.OnModelCreating(modelBuilder);
#region Shares
modelBuilder.Ignore<ServerShareContent>();
modelBuilder.Ignore<ServerSharePermission>();
modelBuilder.Entity<ServerShare>(builder =>
{
builder.OwnsOne(x => x.Content, navigationBuilder =>
{
navigationBuilder.ToJson();
navigationBuilder.OwnsMany(x => x.Permissions);
});
});
#endregion
}
}

View File

@@ -1,21 +0,0 @@
using MoonlightServers.DaemonShared.Enums;
using MoonlightServers.Shared.Enums;
using ServerState = MoonlightServers.Shared.Enums.ServerState;
namespace MoonlightServers.ApiServer.Extensions;
public static class ServerStateExtensions
{
public static ServerState ToServerPowerState(this DaemonShared.Enums.ServerState state)
{
return state switch
{
DaemonShared.Enums.ServerState.Installing => ServerState.Installing,
DaemonShared.Enums.ServerState.Stopping => ServerState.Stopping,
DaemonShared.Enums.ServerState.Online => ServerState.Online,
DaemonShared.Enums.ServerState.Starting => ServerState.Starting,
DaemonShared.Enums.ServerState.Offline => ServerState.Offline,
_ => ServerState.Offline
};
}
}

View File

@@ -1,8 +0,0 @@
using Microsoft.AspNetCore.Authentication;
namespace MoonlightServers.ApiServer.Helpers;
public class NodeAuthOptions : AuthenticationSchemeOptions
{
}

View File

@@ -1,76 +0,0 @@
using System.Security.Claims;
using System.Text.Encodings.Web;
using Microsoft.AspNetCore.Authentication;
using Microsoft.EntityFrameworkCore;
using Microsoft.Extensions.Options;
using MoonCore.Extended.Abstractions;
using MoonlightServers.ApiServer.Database.Entities;
namespace MoonlightServers.ApiServer.Helpers;
public class NodeAuthScheme : AuthenticationHandler<NodeAuthOptions>
{
public NodeAuthScheme(IOptionsMonitor<NodeAuthOptions> options, ILoggerFactory logger, UrlEncoder encoder,
ISystemClock clock) : base(options, logger, encoder, clock)
{
}
public NodeAuthScheme(IOptionsMonitor<NodeAuthOptions> options, ILoggerFactory logger, UrlEncoder encoder) : base(
options, logger, encoder)
{
}
protected override async Task<AuthenticateResult> HandleAuthenticateAsync()
{
if (!Request.Headers.ContainsKey("Authorization"))
return AuthenticateResult.NoResult();
var authHeaderValue = Request.Headers["Authorization"].FirstOrDefault();
if (string.IsNullOrEmpty(authHeaderValue))
return AuthenticateResult.NoResult();
if (!authHeaderValue.Contains("Bearer "))
return AuthenticateResult.NoResult();
var tokenParts = authHeaderValue
.Replace("Bearer ", "")
.Trim()
.Split('.');
if (tokenParts.Length != 2)
return AuthenticateResult.NoResult();
var tokenId = tokenParts[0];
var token = tokenParts[1];
if (tokenId.Length != 6)
return AuthenticateResult.NoResult();
var nodeRepo = Context.RequestServices.GetRequiredService<DatabaseRepository<Node>>();
var node = await nodeRepo
.Get()
.FirstOrDefaultAsync(x => x.TokenId == tokenId);
if (node == null)
return AuthenticateResult.NoResult();
if (node.Token != token)
return AuthenticateResult.NoResult();
return AuthenticateResult.Success(
new AuthenticationTicket(
new ClaimsPrincipal(
new ClaimsIdentity(
[
new Claim("nodeId", node.Id.ToString())
],
"nodeAuthentication"
)
),
"nodeAuthentication"
)
);
}
}

View File

@@ -1,239 +0,0 @@
using System.ComponentModel.DataAnnotations;
using Microsoft.AspNetCore.Authorization;
using Microsoft.AspNetCore.Mvc;
using Microsoft.EntityFrameworkCore;
using MoonCore.Exceptions;
using MoonCore.Extended.Abstractions;
using MoonCore.Models;
using MoonlightServers.ApiServer.Database.Entities;
using MoonlightServers.Shared.Http.Requests.Admin.NodeAllocations;
using MoonlightServers.Shared.Http.Responses.Admin.NodeAllocations;
namespace MoonlightServers.ApiServer.Http.Controllers.Admin.Nodes;
[ApiController]
[Route("api/admin/servers/nodes")]
public class NodeAllocationsController : Controller
{
private readonly DatabaseRepository<Node> NodeRepository;
private readonly DatabaseRepository<Allocation> AllocationRepository;
public NodeAllocationsController(
DatabaseRepository<Node> nodeRepository,
DatabaseRepository<Allocation> allocationRepository
)
{
NodeRepository = nodeRepository;
AllocationRepository = allocationRepository;
}
[HttpGet("{nodeId:int}/allocations")]
[Authorize(Policy = "permissions:admin.servers.nodes.get")]
public async Task<IPagedData<NodeAllocationDetailResponse>> Get(
[FromRoute] int nodeId,
[FromQuery] int page,
[FromQuery] [Range(1, 100)] int pageSize
)
{
var count = await AllocationRepository.Get().CountAsync(x => x.Node.Id == nodeId);
var allocations = await AllocationRepository
.Get()
.Skip(page * pageSize)
.Take(pageSize)
.Where(x => x.Node.Id == nodeId)
.ToArrayAsync();
var mappedAllocations = allocations.Select(x => new NodeAllocationDetailResponse()
{
Id = x.Id,
IpAddress = x.IpAddress,
Port = x.Port
}).ToArray();
return new PagedData<NodeAllocationDetailResponse>()
{
Items = mappedAllocations,
CurrentPage = page,
PageSize = pageSize,
TotalItems = count,
TotalPages = count == 0 ? 0 : (count - 1) / pageSize
};
}
[HttpGet("{nodeId:int}/allocations/{id:int}")]
[Authorize(Policy = "permissions:admin.servers.nodes.get")]
public async Task<NodeAllocationDetailResponse> GetSingle([FromRoute] int nodeId, [FromRoute] int id)
{
var allocation = await AllocationRepository
.Get()
.Where(x => x.Node.Id == nodeId)
.FirstOrDefaultAsync(x => x.Id == id);
if (allocation == null)
throw new HttpApiException("No allocation with that id found", 404);
return new()
{
Id = allocation.Id,
IpAddress = allocation.IpAddress,
Port = allocation.Port
};
}
[HttpPost("{nodeId:int}/allocations")]
[Authorize(Policy = "permissions:admin.servers.nodes.create")]
public async Task<NodeAllocationDetailResponse> Create(
[FromRoute] int nodeId,
[FromBody] CreateNodeAllocationRequest request
)
{
var node = await NodeRepository
.Get()
.FirstOrDefaultAsync(x => x.Id == nodeId);
if (node == null)
throw new HttpApiException("No node with that id found", 404);
var allocation = new Allocation
{
IpAddress = request.IpAddress,
Port = request.Port,
Node = node
};
var finalVariable = await AllocationRepository.Add(allocation);
return new()
{
Id = finalVariable.Id,
IpAddress = finalVariable.IpAddress,
Port = finalVariable.Port
};
}
[HttpPatch("{nodeId:int}/allocations/{id:int}")]
public async Task<NodeAllocationDetailResponse> Update([FromRoute] int nodeId, [FromRoute] int id,
[FromBody] UpdateNodeAllocationRequest request)
{
var allocation = await AllocationRepository
.Get()
.Where(x => x.Node.Id == nodeId)
.FirstOrDefaultAsync(x => x.Id == id);
if (allocation == null)
throw new HttpApiException("No allocation with that id found", 404);
allocation.IpAddress = request.IpAddress;
allocation.Port = request.Port;
await AllocationRepository.Update(allocation);
return new()
{
Id = allocation.Id,
IpAddress = allocation.IpAddress,
Port = allocation.Port
};
}
[HttpDelete("{nodeId:int}/allocations/{id:int}")]
public async Task Delete([FromRoute] int nodeId, [FromRoute] int id)
{
var allocation = await AllocationRepository
.Get()
.Where(x => x.Node.Id == nodeId)
.FirstOrDefaultAsync(x => x.Id == id);
if (allocation == null)
throw new HttpApiException("No allocation with that id found", 404);
await AllocationRepository.Remove(allocation);
}
[HttpPost("{nodeId:int}/allocations/range")]
public async Task CreateRange([FromRoute] int nodeId, [FromBody] CreateNodeAllocationRangeRequest rangeRequest)
{
var node = await NodeRepository
.Get()
.FirstOrDefaultAsync(x => x.Id == nodeId);
if (node == null)
throw new HttpApiException("No node with that id found", 404);
var existingAllocations = AllocationRepository
.Get()
.Where(x => x.Node.Id == nodeId)
.ToArray();
var ports = new List<int>();
for (var i = rangeRequest.Start; i < rangeRequest.End; i++)
{
// Skip existing allocations
if (existingAllocations.Any(x => x.Port == i && x.IpAddress == rangeRequest.IpAddress))
continue;
ports.Add(i);
}
var allocations = ports
.Select(port => new Allocation()
{
IpAddress = rangeRequest.IpAddress,
Port = port,
Node = node
})
.ToArray();
await AllocationRepository.RunTransaction(async set => { await set.AddRangeAsync(allocations); });
}
[HttpDelete("{nodeId:int}/allocations/all")]
public async Task DeleteAll([FromRoute] int nodeId)
{
var allocations = AllocationRepository
.Get()
.Where(x => x.Node.Id == nodeId)
.ToArray();
await AllocationRepository.RunTransaction(set => { set.RemoveRange(allocations); });
}
[HttpGet("{nodeId:int}/allocations/free")]
[Authorize(Policy = "permissions:admin.servers.nodes.get")]
public async Task<IPagedData<NodeAllocationDetailResponse>> GetFree([FromRoute] int nodeId, [FromQuery] int page,
[FromQuery][Range(1, 100)] int pageSize, [FromQuery] int serverId = -1)
{
var node = NodeRepository
.Get()
.FirstOrDefault(x => x.Id == nodeId);
if (node == null)
throw new HttpApiException("A node with this id could not be found", 404);
var freeAllocationsQuery = AllocationRepository
.Get()
.Where(x => x.Node.Id == node.Id)
.Where(x => x.Server == null || x.Server.Id == serverId);
var count = await freeAllocationsQuery.CountAsync();
var allocations = await freeAllocationsQuery.ToArrayAsync();
var mappedAllocations = allocations.Select(x => new NodeAllocationDetailResponse()
{
Id = x.Id,
IpAddress = x.IpAddress,
Port = x.Port
}).ToArray();
return new PagedData<NodeAllocationDetailResponse>()
{
Items = mappedAllocations,
CurrentPage = page,
PageSize = pageSize,
TotalItems = count,
TotalPages = count == 0 ? 0 : (count - 1) / pageSize
};
}
}

View File

@@ -1,79 +0,0 @@
using System.Diagnostics;
using Microsoft.AspNetCore.Mvc;
using MoonCore.Exceptions;
using MoonCore.Extended.Abstractions;
using Microsoft.AspNetCore.Authorization;
using MoonlightServers.ApiServer.Database.Entities;
using MoonlightServers.ApiServer.Services;
using MoonlightServers.Shared.Http.Responses.Admin.Nodes.Sys;
namespace MoonlightServers.ApiServer.Http.Controllers.Admin.Nodes;
[ApiController]
[Route("api/admin/servers/nodes")]
public class NodeStatusController : Controller
{
private readonly DatabaseRepository<Node> NodeRepository;
private readonly NodeService NodeService;
public NodeStatusController(DatabaseRepository<Node> nodeRepository, NodeService nodeService)
{
NodeRepository = nodeRepository;
NodeService = nodeService;
}
[HttpGet("{nodeId}/system/status")]
[Authorize(Policy = "permissions:admin.servers.nodes.status")]
public async Task<NodeSystemStatusResponse> GetStatus([FromRoute] int nodeId)
{
var node = GetNode(nodeId);
NodeSystemStatusResponse response;
var sw = new Stopwatch();
sw.Start();
try
{
var statusResponse = await NodeService.GetSystemStatus(node);
sw.Stop();
response = new()
{
Version = statusResponse.Version,
RoundtripError = statusResponse.TripError,
RoundtripSuccess = statusResponse.TripSuccess,
RoundtripTime = statusResponse.TripTime + sw.Elapsed,
RoundtripRemoteFailure = !statusResponse.TripSuccess // When the remote trip failed, it's the remotes fault
};
}
catch (Exception e)
{
sw.Stop();
response = new()
{
Version = "Unknown",
RoundtripError = e.Message,
RoundtripSuccess = false,
RoundtripTime = sw.Elapsed,
RoundtripRemoteFailure = false
};
}
return response;
}
private Node GetNode(int nodeId)
{
var result = NodeRepository
.Get()
.FirstOrDefault(x => x.Id == nodeId);
if (result == null)
throw new HttpApiException("A node with this id could not be found", 404);
return result;
}
}

View File

@@ -1,72 +0,0 @@
using Microsoft.AspNetCore.Mvc;
using Microsoft.EntityFrameworkCore;
using MoonCore.Extended.Abstractions;
using MoonCore.Extended.Helpers;
using Microsoft.AspNetCore.Authorization;
using MoonCore.Helpers;
using MoonCore.Models;
using MoonlightServers.ApiServer.Database.Entities;
using MoonlightServers.Shared.Http.Requests.Admin.Nodes;
using MoonlightServers.Shared.Http.Responses.Admin.NodeAllocations;
using MoonlightServers.Shared.Http.Responses.Admin.Nodes;
namespace MoonlightServers.ApiServer.Http.Controllers.Admin.Nodes;
[ApiController]
[Route("api/admin/servers/nodes")]
public class NodesController : Controller
{
private readonly CrudHelper<Node, NodeDetailResponse> CrudHelper;
private readonly DatabaseRepository<Node> NodeRepository;
public NodesController(
CrudHelper<Node, NodeDetailResponse> crudHelper,
DatabaseRepository<Node> nodeRepository
)
{
CrudHelper = crudHelper;
NodeRepository = nodeRepository;
}
[HttpGet]
[Authorize(Policy = "permissions:admin.servers.nodes.get")]
public async Task<IPagedData<NodeDetailResponse>> Get([FromQuery] int page, [FromQuery] int pageSize)
{
return await CrudHelper.Get(page, pageSize);
}
[HttpGet("{id:int}")]
[Authorize(Policy = "permissions:admin.servers.nodes.get")]
public async Task<NodeDetailResponse> GetSingle([FromRoute] int id)
{
return await CrudHelper.GetSingle(id);
}
[HttpPost]
[Authorize(Policy = "permissions:admin.servers.nodes.create")]
public async Task<NodeDetailResponse> Create([FromBody] CreateNodeRequest request)
{
var node = Mapper.Map<Node>(request);
node.TokenId = Formatter.GenerateString(6);
node.Token = Formatter.GenerateString(32);
var finalNode = await NodeRepository.Add(node);
return CrudHelper.MapToResult(finalNode);
}
[HttpPatch("{id:int}")]
[Authorize(Policy = "permissions:admin.servers.nodes.update")]
public async Task<NodeDetailResponse> Update([FromRoute] int id, [FromBody] UpdateNodeRequest request)
{
return await CrudHelper.Update(id, request);
}
[HttpDelete("{id:int}")]
[Authorize(Policy = "permissions:admin.servers.nodes.delete")]
public async Task Delete([FromRoute] int id)
{
await CrudHelper.Delete(id);
}
}

View File

@@ -1,90 +0,0 @@
using Microsoft.AspNetCore.Authorization;
using Microsoft.AspNetCore.Mvc;
using Microsoft.EntityFrameworkCore;
using MoonCore.Exceptions;
using MoonCore.Extended.Abstractions;
using MoonlightServers.ApiServer.Database.Entities;
using MoonlightServers.ApiServer.Services;
using MoonlightServers.Shared.Http.Responses.Admin.Nodes.Statistics;
namespace MoonlightServers.ApiServer.Http.Controllers.Admin.Nodes;
[ApiController]
[Route("api/admin/servers/nodes")]
[Authorize(Policy = "permissions:admin.servers.nodes.statistics")]
public class StatisticsController : Controller
{
private readonly NodeService NodeService;
private readonly DatabaseRepository<Node> NodeRepository;
public StatisticsController(NodeService nodeService, DatabaseRepository<Node> nodeRepository)
{
NodeService = nodeService;
NodeRepository = nodeRepository;
}
[HttpGet("{nodeId:int}/statistics")]
public async Task<StatisticsResponse> Get([FromRoute] int nodeId)
{
var node = await GetNode(nodeId);
var statistics = await NodeService.GetStatistics(node);
return new()
{
Cpu = new()
{
Model = statistics.Cpu.Model,
Usage = statistics.Cpu.Usage,
UsagePerCore = statistics.Cpu.UsagePerCore
},
Memory = new()
{
Available = statistics.Memory.Available,
Total = statistics.Memory.Total,
Cached = statistics.Memory.Cached,
Free = statistics.Memory.Free,
SwapFree = statistics.Memory.SwapFree,
SwapTotal = statistics.Memory.SwapTotal
},
Disks = statistics.Disks.Select(x => new StatisticsResponse.DiskData()
{
Device = x.Device,
DiskFree = x.DiskFree,
DiskTotal = x.DiskTotal,
InodesFree = x.InodesFree,
InodesTotal = x.InodesTotal,
MountPath = x.MountPath
}).ToArray()
};
}
[HttpGet("{nodeId:int}/statistics/docker")]
public async Task<DockerStatisticsResponse> GetDocker([FromRoute] int nodeId)
{
var node = await GetNode(nodeId);
var statistics = await NodeService.GetDockerStatistics(node);
return new()
{
BuildCacheReclaimable = statistics.BuildCacheReclaimable,
BuildCacheUsed = statistics.BuildCacheUsed,
ContainersReclaimable = statistics.ContainersReclaimable,
ContainersUsed = statistics.ContainersUsed,
ImagesReclaimable = statistics.ImagesReclaimable,
ImagesUsed = statistics.ImagesUsed,
Version = statistics.Version
};
}
private async Task<Node> GetNode(int nodeId)
{
var result = await NodeRepository
.Get()
.FirstOrDefaultAsync(x => x.Id == nodeId);
if (result == null)
throw new HttpApiException("A node with this id could not be found", 404);
return result;
}
}

View File

@@ -1,48 +0,0 @@
using Microsoft.AspNetCore.Mvc;
using Microsoft.EntityFrameworkCore;
using Microsoft.AspNetCore.Authorization;
using MoonCore.Exceptions;
using MoonCore.Extended.Abstractions;
using MoonCore.Helpers;
using MoonCore.Models;
using MoonlightServers.ApiServer.Database.Entities;
using MoonlightServers.Shared.Http.Responses.Admin.ServerVariables;
namespace MoonlightServers.ApiServer.Http.Controllers.Admin.Servers;
[ApiController]
[Route("api/admin/servers")]
public class ServerVariablesController : Controller
{
private readonly DatabaseRepository<ServerVariable> VariableRepository;
private readonly DatabaseRepository<Server> ServerRepository;
public ServerVariablesController(DatabaseRepository<ServerVariable> variableRepository, DatabaseRepository<Server> serverRepository)
{
VariableRepository = variableRepository;
ServerRepository = serverRepository;
}
[HttpGet("{serverId}/variables")]
[Authorize(Policy = "permissions:admin.servers.get")]
public async Task<PagedData<ServerVariableDetailResponse>> Get([FromRoute] int serverId, [FromQuery] int page, [FromQuery] int pageSize)
{
var server = await ServerRepository
.Get()
.FirstOrDefaultAsync(x => x.Id == serverId);
if (server == null)
throw new HttpApiException("No server with this id found", 404);
var variables = await VariableRepository
.Get()
.Where(x => x.Server.Id == server.Id)
.ToArrayAsync();
var castedVariables = variables
.Select(x => Mapper.Map<ServerVariableDetailResponse>(x))
.ToArray();
return PagedData<ServerVariableDetailResponse>.Create(castedVariables, page, pageSize);
}
}

View File

@@ -1,282 +0,0 @@
using Microsoft.AspNetCore.Mvc;
using Microsoft.EntityFrameworkCore;
using Microsoft.AspNetCore.Authorization;
using MoonCore.Exceptions;
using MoonCore.Extended.Abstractions;
using MoonCore.Extended.Helpers;
using MoonCore.Helpers;
using MoonCore.Models;
using Moonlight.ApiServer.Database.Entities;
using MoonlightServers.ApiServer.Database.Entities;
using MoonlightServers.ApiServer.Services;
using MoonlightServers.Shared.Http.Requests.Admin.Servers;
using MoonlightServers.Shared.Http.Responses.Admin.Servers;
namespace MoonlightServers.ApiServer.Http.Controllers.Admin.Servers;
[ApiController]
[Route("api/admin/servers")]
public class ServersController : Controller
{
private readonly CrudHelper<Server, ServerDetailResponse> CrudHelper;
private readonly DatabaseRepository<Star> StarRepository;
private readonly DatabaseRepository<Node> NodeRepository;
private readonly DatabaseRepository<Allocation> AllocationRepository;
private readonly DatabaseRepository<ServerVariable> VariableRepository;
private readonly DatabaseRepository<Server> ServerRepository;
private readonly DatabaseRepository<User> UserRepository;
private readonly ILogger<ServersController> Logger;
private readonly ServerService ServerService;
public ServersController(
CrudHelper<Server, ServerDetailResponse> crudHelper,
DatabaseRepository<Star> starRepository,
DatabaseRepository<Node> nodeRepository,
DatabaseRepository<Allocation> allocationRepository,
DatabaseRepository<ServerVariable> variableRepository,
DatabaseRepository<Server> serverRepository,
DatabaseRepository<User> userRepository,
ILogger<ServersController> logger,
ServerService serverService
)
{
CrudHelper = crudHelper;
StarRepository = starRepository;
NodeRepository = nodeRepository;
AllocationRepository = allocationRepository;
VariableRepository = variableRepository;
ServerRepository = serverRepository;
UserRepository = userRepository;
ServerService = serverService;
Logger = logger;
CrudHelper.QueryModifier = servers => servers
.Include(x => x.Node)
.Include(x => x.Allocations)
.Include(x => x.Variables)
.Include(x => x.Star);
CrudHelper.LateMapper = (server, response) =>
{
response.NodeId = server.Node.Id;
response.StarId = server.Star.Id;
response.AllocationIds = server.Allocations.Select(x => x.Id).ToArray();
return response;
};
}
[HttpGet]
[Authorize(Policy = "permissions:admin.servers.get")]
public async Task<IPagedData<ServerDetailResponse>> Get([FromQuery] int page, [FromQuery] int pageSize)
{
return await CrudHelper.Get(page, pageSize);
}
[HttpGet("{id:int}")]
[Authorize(Policy = "permissions:admin.servers.get")]
public async Task<ServerDetailResponse> GetSingle([FromRoute] int id)
{
return await CrudHelper.GetSingle(id);
}
[HttpPost]
[Authorize(Policy = "permissions:admin.servers.create")]
public async Task<ServerDetailResponse> Create([FromBody] CreateServerRequest request)
{
// Construct model
var server = Mapper.Map<Server>(request);
// Check if owner user exist
if (UserRepository.Get().All(x => x.Id != request.OwnerId))
throw new HttpApiException("No user with this id found", 400);
var star = await StarRepository
.Get()
.Include(x => x.Variables)
.Include(x => x.DockerImages)
.FirstOrDefaultAsync(x => x.Id == request.StarId);
if (star == null)
throw new HttpApiException("No star with this id found", 400);
var node = await NodeRepository
.Get()
.FirstOrDefaultAsync(x => x.Id == request.NodeId);
if (node == null)
throw new HttpApiException("No node with this id found", 400);
var allocations = new List<Allocation>();
// Fetch specified allocations from the request
foreach (var allocationId in request.AllocationIds)
{
var allocation = await AllocationRepository
.Get()
.Where(x => x.Server == null)
.Where(x => x.Node.Id == node.Id)
.FirstOrDefaultAsync(x => x.Id == allocationId);
if (allocation == null)
continue;
allocations.Add(allocation);
}
// Check if the specified allocations are enough for the star
if (allocations.Count < star.RequiredAllocations)
{
var amountRequiredToSatisfy = star.RequiredAllocations - allocations.Count;
var freeAllocations = await AllocationRepository
.Get()
.Where(x => x.Server == null)
.Where(x => x.Node.Id == node.Id)
.Take(amountRequiredToSatisfy)
.ToArrayAsync();
allocations.AddRange(freeAllocations);
if (allocations.Count < star.RequiredAllocations)
{
throw new HttpApiException(
$"Unable to find enough free allocations. Found: {allocations.Count}, Required: {star.RequiredAllocations}",
400
);
}
}
// Set allocations
server.Allocations = allocations;
// Variables
foreach (var variable in star.Variables)
{
var requestVar = request.Variables.FirstOrDefault(x => x.Key == variable.Key);
var serverVar = new ServerVariable()
{
Key = variable.Key,
Value = requestVar != null
? requestVar.Value
: variable.DefaultValue
};
server.Variables.Add(serverVar);
}
// Set relations
server.Node = node;
server.Star = star;
var finalServer = await ServerRepository.Add(server);
try
{
await ServerService.Sync(finalServer);
}
catch (Exception e)
{
Logger.LogError("Unable to sync server to node the server is assigned to: {e}", e);
// We are deleting the server from the database after the creation has failed
// to ensure we wont have a bugged server in the database which doesnt exist on the node
await ServerRepository.Remove(finalServer);
throw;
}
return CrudHelper.MapToResult(finalServer);
}
[HttpPatch("{id:int}")]
public async Task<ServerDetailResponse> Update([FromRoute] int id, [FromBody] UpdateServerRequest request)
{
//TODO: Handle shrinking virtual disk
var server = await CrudHelper.GetSingleModel(id);
server = Mapper.Map(server, request);
var allocations = new List<Allocation>();
// Fetch specified allocations from the request
foreach (var allocationId in request.AllocationIds)
{
var allocation = await AllocationRepository
.Get()
.Where(x => x.Server == null || x.Server.Id == server.Id)
.Where(x => x.Node.Id == server.Node.Id)
.FirstOrDefaultAsync(x => x.Id == allocationId);
// ^ This loads the allocations specified in the request.
// Valid allocations are either free ones or ones which are already allocated to this server
if (allocation == null)
continue;
allocations.Add(allocation);
}
// Check if the specified allocations are enough for the star
if (allocations.Count < server.Star.RequiredAllocations)
{
throw new HttpApiException(
$"You need to specify at least {server.Star.RequiredAllocations} allocation(s)",
400
);
}
// Set allocations
server.Allocations = allocations;
// Process variables
foreach (var variable in request.Variables)
{
// Search server variable associated to the variable in the request
var serverVar = server.Variables
.FirstOrDefault(x => x.Key == variable.Key);
if (serverVar == null)
continue;
// Update value
serverVar.Value = variable.Value;
}
await ServerRepository.Update(server);
// Notify the node about the changes
await ServerService.Sync(server);
return CrudHelper.MapToResult(server);
}
[HttpDelete("{id:int}")]
public async Task Delete([FromRoute] int id, [FromQuery] bool force = false)
{
var server = await CrudHelper.GetSingleModel(id);
try
{
// If the sync fails on the node and we aren't forcing the deletion,
// we don't want to delete it from the database yet
await ServerService.SyncDelete(server);
}
catch (Exception e)
{
if (force)
{
Logger.LogWarning(
"An error occured while syncing deletion of a server to the node. Continuing anyways. Error: {e}",
e
);
}
else
throw;
}
await CrudHelper.Delete(id);
}
}

View File

@@ -1,101 +0,0 @@
using Microsoft.AspNetCore.Mvc;
using Microsoft.EntityFrameworkCore;
using MoonCore.Exceptions;
using MoonCore.Extended.Abstractions;
using MoonCore.Extended.Helpers;
using Microsoft.AspNetCore.Authorization;
using MoonCore.Helpers;
using MoonCore.Models;
using MoonlightServers.ApiServer.Database.Entities;
using MoonlightServers.Shared.Http.Requests.Admin.StarDockerImages;
using MoonlightServers.Shared.Http.Responses.Admin.StarDockerImages;
namespace MoonlightServers.ApiServer.Http.Controllers.Admin.Stars;
[ApiController]
[Route("api/admin/servers/stars")]
public class StarDockerImagesController : Controller
{
private readonly CrudHelper<StarDockerImage, StarDockerImageDetailResponse> CrudHelper;
private readonly DatabaseRepository<Star> StarRepository;
private readonly DatabaseRepository<StarDockerImage> StarDockerImageRepository;
private Star Star;
public StarDockerImagesController(
CrudHelper<StarDockerImage, StarDockerImageDetailResponse> crudHelper,
DatabaseRepository<Star> starRepository,
DatabaseRepository<StarDockerImage> starDockerImageRepository
)
{
CrudHelper = crudHelper;
StarRepository = starRepository;
StarDockerImageRepository = starDockerImageRepository;
}
private async Task ApplyStar(int id)
{
var star = await StarRepository
.Get()
.FirstOrDefaultAsync(x => x.Id == id);
if (star == null)
throw new HttpApiException("A star with this id could not be found", 404);
Star = star;
CrudHelper.QueryModifier = dockerImages =>
dockerImages.Where(x => x.Star.Id == star.Id);
}
[HttpGet("{starId:int}/dockerImages")]
[Authorize(Policy = "permissions:admin.servers.stars.get")]
public async Task<IPagedData<StarDockerImageDetailResponse>> Get([FromRoute] int starId, [FromQuery] int page, [FromQuery] int pageSize)
{
await ApplyStar(starId);
return await CrudHelper.Get(page, pageSize);
}
[HttpGet("{starId:int}/dockerImages/{id:int}")]
[Authorize(Policy = "permissions:admin.servers.stars.get")]
public async Task<StarDockerImageDetailResponse> GetSingle([FromRoute] int starId, [FromRoute] int id)
{
await ApplyStar(starId);
return await CrudHelper.GetSingle(id);
}
[HttpPost("{starId:int}/dockerImages")]
[Authorize(Policy = "permissions:admin.servers.stars.create")]
public async Task<StarDockerImageDetailResponse> Create([FromRoute] int starId, [FromBody] CreateStarDockerImageRequest request)
{
await ApplyStar(starId);
var starDockerImage = Mapper.Map<StarDockerImage>(request);
starDockerImage.Star = Star;
var finalVariable = await StarDockerImageRepository.Add(starDockerImage);
return CrudHelper.MapToResult(finalVariable);
}
[HttpPatch("{starId:int}/dockerImages/{id:int}")]
[Authorize(Policy = "permissions:admin.servers.stars.update")]
public async Task<StarDockerImageDetailResponse> Update([FromRoute] int starId, [FromRoute] int id,
[FromBody] UpdateStarDockerImageRequest request)
{
await ApplyStar(starId);
return await CrudHelper.Update(id, request);
}
[HttpDelete("{starId:int}/dockerImages/{id:int}")]
[Authorize(Policy = "permissions:admin.servers.stars.delete")]
public async Task Delete([FromRoute] int starId, [FromRoute] int id)
{
await ApplyStar(starId);
await CrudHelper.Delete(id);
}
}

View File

@@ -1,52 +0,0 @@
using System.Text;
using Microsoft.AspNetCore.Mvc;
using Microsoft.AspNetCore.Authorization;
using MoonCore.Exceptions;
using MoonCore.Helpers;
using MoonlightServers.ApiServer.Services;
using MoonlightServers.Shared.Http.Responses.Admin.Stars;
namespace MoonlightServers.ApiServer.Http.Controllers.Admin.Stars;
[ApiController]
[Route("api/admin/servers/stars")]
public class StarImportExportController : Controller
{
private readonly StarImportExportService ImportExportService;
public StarImportExportController(StarImportExportService importExportService)
{
ImportExportService = importExportService;
}
[HttpGet("{starId:int}/export")]
[Authorize(Policy = "permissions:admin.servers.stars.get")]
public async Task Export([FromRoute] int starId)
{
var exportedStar = await ImportExportService.Export(starId);
Response.StatusCode = 200;
Response.ContentType = "application/json";
await Response.WriteAsync(exportedStar);
}
[HttpPost("import")]
[Authorize(Policy = "permissions:admin.servers.stars.create")]
public async Task<StarDetailResponse> Import()
{
if (Request.Form.Files.Count == 0)
throw new HttpApiException("No file to import provided", 400);
if (Request.Form.Files.Count > 1)
throw new HttpApiException("Only one file to import allowed", 400);
var file = Request.Form.Files[0];
await using var stream = file.OpenReadStream();
using var sr = new StreamReader(stream, Encoding.UTF8);
var content = await sr.ReadToEndAsync();
var star = await ImportExportService.Import(content);
return Mapper.Map<StarDetailResponse>(star);
}
}

View File

@@ -1,106 +0,0 @@
using Microsoft.AspNetCore.Mvc;
using Microsoft.EntityFrameworkCore;
using Microsoft.AspNetCore.Authorization;
using MoonCore.Exceptions;
using MoonCore.Extended.Abstractions;
using MoonCore.Extended.Helpers;
using MoonCore.Helpers;
using MoonCore.Models;
using MoonlightServers.ApiServer.Database.Entities;
using MoonlightServers.Shared.Http.Requests.Admin.StarVariables;
using MoonlightServers.Shared.Http.Responses.Admin.StarVariables;
namespace MoonlightServers.ApiServer.Http.Controllers.Admin.Stars;
[ApiController]
[Route("api/admin/servers/stars")]
public class StarVariablesController : Controller
{
private readonly CrudHelper<StarVariable, StarVariableDetailResponse> CrudHelper;
private readonly DatabaseRepository<Star> StarRepository;
private readonly DatabaseRepository<StarVariable> StarVariableRepository;
private Star Star;
public StarVariablesController(
CrudHelper<StarVariable, StarVariableDetailResponse> crudHelper,
DatabaseRepository<Star> starRepository,
DatabaseRepository<StarVariable> starVariableRepository)
{
CrudHelper = crudHelper;
StarRepository = starRepository;
StarVariableRepository = starVariableRepository;
}
private async Task ApplyStar(int id)
{
var star = await StarRepository
.Get()
.FirstOrDefaultAsync(x => x.Id == id);
if (star == null)
throw new HttpApiException("A star with this id could not be found", 404);
Star = star;
CrudHelper.QueryModifier = variables =>
variables.Where(x => x.Star.Id == star.Id);
}
[HttpGet("{starId:int}/variables")]
[Authorize(Policy = "permissions:admin.servers.stars.get")]
public async Task<IPagedData<StarVariableDetailResponse>> Get([FromRoute] int starId, [FromQuery] int page, [FromQuery] int pageSize)
{
await ApplyStar(starId);
return await CrudHelper.Get(page, pageSize);
}
[HttpGet("{starId:int}/variables/{id:int}")]
[Authorize(Policy = "permissions:admin.servers.stars.get")]
public async Task<StarVariableDetailResponse> GetSingle([FromRoute] int starId, [FromRoute] int id)
{
await ApplyStar(starId);
return await CrudHelper.GetSingle(id);
}
[HttpPost("{starId:int}/variables")]
[Authorize(Policy = "permissions:admin.servers.stars.create")]
public async Task<StarVariableDetailResponse> Create([FromRoute] int starId, [FromBody] CreateStarVariableRequest request)
{
await ApplyStar(starId);
var starVariable = Mapper.Map<StarVariable>(request);
starVariable.Star = Star;
var finalVariable = await StarVariableRepository.Add(starVariable);
return CrudHelper.MapToResult(finalVariable);
}
[HttpPatch("{starId:int}/variables/{id:int}")]
[Authorize(Policy = "permissions:admin.servers.stars.update")]
public async Task<StarVariableDetailResponse> Update([FromRoute] int starId, [FromRoute] int id,
[FromBody] UpdateStarVariableRequest request)
{
await ApplyStar(starId);
var variable = await CrudHelper.GetSingleModel(id);
variable = Mapper.Map(variable, request);
await StarVariableRepository.Update(variable);
return CrudHelper.MapToResult(variable);
}
[HttpDelete("{starId:int}/variables/{id:int}")]
[Authorize(Policy = "permissions:admin.servers.stars.delete")]
public async Task Delete([FromRoute] int starId, [FromRoute] int id)
{
await ApplyStar(starId);
await CrudHelper.Delete(id);
}
}

View File

@@ -1,82 +0,0 @@
using Microsoft.AspNetCore.Mvc;
using Microsoft.EntityFrameworkCore;
using Microsoft.AspNetCore.Authorization;
using MoonCore.Extended.Abstractions;
using MoonCore.Extended.Helpers;
using MoonCore.Helpers;
using MoonCore.Models;
using MoonlightServers.ApiServer.Database.Entities;
using MoonlightServers.Shared.Http.Requests.Admin.Stars;
using MoonlightServers.Shared.Http.Responses.Admin.StarDockerImages;
using MoonlightServers.Shared.Http.Responses.Admin.Stars;
using MoonlightServers.Shared.Http.Responses.Admin.StarVariables;
namespace MoonlightServers.ApiServer.Http.Controllers.Admin.Stars;
[ApiController]
[Route("api/admin/servers/stars")]
public class StarsController : Controller
{
private readonly CrudHelper<Star, StarDetailResponse> CrudHelper;
private readonly DatabaseRepository<Star> StarRepository;
public StarsController(CrudHelper<Star, StarDetailResponse> crudHelper, DatabaseRepository<Star> starRepository)
{
CrudHelper = crudHelper;
StarRepository = starRepository;
}
[HttpGet]
[Authorize(Policy = "permissions:admin.servers.stars.get")]
public async Task<IPagedData<StarDetailResponse>> Get([FromQuery] int page, [FromQuery] int pageSize)
{
return await CrudHelper.Get(page, pageSize);
}
[HttpGet("{id:int}")]
[Authorize(Policy = "permissions:admin.servers.stars.get")]
public async Task<StarDetailResponse> GetSingle([FromRoute] int id)
{
return await CrudHelper.GetSingle(id);
}
[HttpPost]
[Authorize(Policy = "permissions:admin.servers.stars.create")]
public async Task<StarDetailResponse> Create([FromBody] CreateStarRequest request)
{
var star = Mapper.Map<Star>(request);
// Default values
star.DonateUrl = null;
star.UpdateUrl = null;
star.Version = "1.0.0";
star.StartupCommand = "echo Starting up :)";
star.StopCommand = "^C";
star.OnlineDetection = "Online text";
star.InstallShell = "/bin/bash";
star.InstallDockerImage = "debian:latest";
star.InstallScript = "echo Installing...";
star.RequiredAllocations = 1;
star.AllowDockerImageChange = false;
star.DefaultDockerImage = -1;
star.ParseConfiguration = "[]";
var finalStar = await StarRepository.Add(star);
return CrudHelper.MapToResult(finalStar);
}
[HttpPatch("{id:int}")]
[Authorize(Policy = "permissions:admin.servers.stars.update")]
public async Task<StarDetailResponse> Update([FromRoute] int id, [FromBody] UpdateStarRequest request)
{
return await CrudHelper.Update(id, request);
}
[HttpDelete("{id:int}")]
[Authorize(Policy = "permissions:admin.servers.stars.delete")]
public async Task Delete([FromRoute] int id)
{
await CrudHelper.Delete(id);
}
}

View File

@@ -1,182 +0,0 @@
using Microsoft.AspNetCore.Authorization;
using Microsoft.AspNetCore.Mvc;
using Microsoft.EntityFrameworkCore;
using MoonCore.Exceptions;
using MoonCore.Extended.Abstractions;
using Moonlight.ApiServer.Database.Entities;
using MoonlightServers.ApiServer.Database.Entities;
using MoonlightServers.ApiServer.Services;
using MoonlightServers.DaemonShared.Enums;
using MoonlightServers.Shared.Enums;
using MoonlightServers.Shared.Http.Requests.Client.Servers.Files;
using MoonlightServers.Shared.Http.Responses.Client.Servers.Files;
namespace MoonlightServers.ApiServer.Http.Controllers.Client;
[Authorize]
[ApiController]
[Route("api/client/servers")]
public class FilesController : Controller
{
private readonly DatabaseRepository<Server> ServerRepository;
private readonly ServerFileSystemService ServerFileSystemService;
private readonly NodeService NodeService;
private readonly ServerAuthorizeService AuthorizeService;
public FilesController(
DatabaseRepository<Server> serverRepository,
ServerFileSystemService serverFileSystemService,
NodeService nodeService,
ServerAuthorizeService authorizeService
)
{
ServerRepository = serverRepository;
ServerFileSystemService = serverFileSystemService;
NodeService = nodeService;
AuthorizeService = authorizeService;
}
[HttpGet("{serverId:int}/files/list")]
public async Task<ServerFilesEntryResponse[]> List([FromRoute] int serverId, [FromQuery] string path)
{
var server = await GetServerById(serverId, ServerPermissionType.Read);
var entries = await ServerFileSystemService.List(server, path);
return entries.Select(x => new ServerFilesEntryResponse()
{
Name = x.Name,
Size = x.Size,
IsFile = x.IsFile,
CreatedAt = x.CreatedAt,
UpdatedAt = x.UpdatedAt
}).ToArray();
}
[HttpPost("{serverId:int}/files/move")]
public async Task Move([FromRoute] int serverId, [FromQuery] string oldPath, [FromQuery] string newPath)
{
var server = await GetServerById(serverId, ServerPermissionType.ReadWrite);
await ServerFileSystemService.Move(server, oldPath, newPath);
}
[HttpDelete("{serverId:int}/files/delete")]
public async Task Delete([FromRoute] int serverId, [FromQuery] string path)
{
var server = await GetServerById(serverId, ServerPermissionType.ReadWrite);
await ServerFileSystemService.Delete(server, path);
}
[HttpPost("{serverId:int}/files/mkdir")]
public async Task Mkdir([FromRoute] int serverId, [FromQuery] string path)
{
var server = await GetServerById(serverId, ServerPermissionType.ReadWrite);
await ServerFileSystemService.Mkdir(server, path);
}
[HttpGet("{serverId:int}/files/upload")]
public async Task<ServerFilesUploadResponse> Upload([FromRoute] int serverId)
{
var server = await GetServerById(serverId, ServerPermissionType.ReadWrite);
var accessToken = NodeService.CreateAccessToken(
server.Node,
parameters =>
{
parameters.Add("type", "upload");
parameters.Add("serverId", server.Id);
},
TimeSpan.FromMinutes(1)
);
var url = "";
url += server.Node.UseSsl ? "https://" : "http://";
url += $"{server.Node.Fqdn}:{server.Node.HttpPort}/";
url += $"api/servers/upload?access_token={accessToken}";
return new ServerFilesUploadResponse()
{
UploadUrl = url
};
}
[HttpGet("{serverId:int}/files/download")]
public async Task<ServerFilesDownloadResponse> Download([FromRoute] int serverId, [FromQuery] string path)
{
var server = await GetServerById(serverId, ServerPermissionType.Read);
var accessToken = NodeService.CreateAccessToken(
server.Node,
parameters =>
{
parameters.Add("type", "download");
parameters.Add("path", path);
parameters.Add("serverId", server.Id);
},
TimeSpan.FromMinutes(1)
);
var url = "";
url += server.Node.UseSsl ? "https://" : "http://";
url += $"{server.Node.Fqdn}:{server.Node.HttpPort}/";
url += $"api/servers/download?access_token={accessToken}";
return new ServerFilesDownloadResponse()
{
DownloadUrl = url
};
}
[HttpPost("{serverId:int}/files/compress")]
public async Task Compress([FromRoute] int serverId, [FromBody] ServerFilesCompressRequest request)
{
var server = await GetServerById(serverId, ServerPermissionType.ReadWrite);
if (!Enum.TryParse(request.Type, true, out CompressType type))
throw new HttpApiException("Invalid compress type provided", 400);
await ServerFileSystemService.Compress(server, type, request.Items, request.Destination);
}
[HttpPost("{serverId:int}/files/decompress")]
public async Task Decompress([FromRoute] int serverId, [FromBody] ServerFilesDecompressRequest request)
{
var server = await GetServerById(serverId, ServerPermissionType.ReadWrite);
if (!Enum.TryParse(request.Type, true, out CompressType type))
throw new HttpApiException("Invalid compress type provided", 400);
await ServerFileSystemService.Decompress(server, type, request.Path, request.Destination);
}
private async Task<Server> GetServerById(int serverId, ServerPermissionType type)
{
var server = await ServerRepository
.Get()
.Include(x => x.Node)
.FirstOrDefaultAsync(x => x.Id == serverId);
if (server == null)
throw new HttpApiException("No server with this id found", 404);
var authorizeResult = await AuthorizeService.Authorize(
User, server,
permission => permission.Name == "files" && permission.Type >= type
);
if (!authorizeResult.Succeeded)
{
throw new HttpApiException(
authorizeResult.Message ?? "No permission for the requested resource",
403
);
}
return server;
}
}

View File

@@ -1,86 +0,0 @@
using Microsoft.AspNetCore.Authorization;
using Microsoft.AspNetCore.Mvc;
using Microsoft.EntityFrameworkCore;
using MoonCore.Exceptions;
using MoonCore.Extended.Abstractions;
using MoonCore.Helpers;
using Moonlight.ApiServer.Database.Entities;
using MoonlightServers.ApiServer.Database.Entities;
using MoonlightServers.ApiServer.Services;
using MoonlightServers.Shared.Enums;
namespace MoonlightServers.ApiServer.Http.Controllers.Client;
[ApiController]
[Authorize]
[Route("api/client/servers")]
public class PowerController : Controller
{
private readonly DatabaseRepository<Server> ServerRepository;
private readonly DatabaseRepository<User> UserRepository;
private readonly ServerService ServerService;
private readonly ServerAuthorizeService AuthorizeService;
public PowerController(
DatabaseRepository<Server> serverRepository,
DatabaseRepository<User> userRepository,
ServerService serverService,
ServerAuthorizeService authorizeService
)
{
ServerRepository = serverRepository;
UserRepository = userRepository;
ServerService = serverService;
AuthorizeService = authorizeService;
}
[HttpPost("{serverId:int}/start")]
[Authorize]
public async Task Start([FromRoute] int serverId)
{
var server = await GetServerById(serverId);
await ServerService.Start(server);
}
[HttpPost("{serverId:int}/stop")]
[Authorize]
public async Task Stop([FromRoute] int serverId)
{
var server = await GetServerById(serverId);
await ServerService.Stop(server);
}
[HttpPost("{serverId:int}/kill")]
[Authorize]
public async Task Kill([FromRoute] int serverId)
{
var server = await GetServerById(serverId);
await ServerService.Kill(server);
}
private async Task<Server> GetServerById(int serverId)
{
var server = await ServerRepository
.Get()
.Include(x => x.Node)
.FirstOrDefaultAsync(x => x.Id == serverId);
if (server == null)
throw new HttpApiException("No server with this id found", 404);
var authorizeResult = await AuthorizeService.Authorize(
User, server,
permission => permission.Name == "power" && permission.Type >= ServerPermissionType.ReadWrite
);
if (!authorizeResult.Succeeded)
{
throw new HttpApiException(
authorizeResult.Message ?? "No permission for the requested resource",
403
);
}
return server;
}
}

View File

@@ -1,318 +0,0 @@
using System.Security.Claims;
using Microsoft.AspNetCore.Authorization;
using Microsoft.AspNetCore.Mvc;
using Microsoft.EntityFrameworkCore;
using MoonCore.Exceptions;
using MoonCore.Extended.Abstractions;
using MoonCore.Models;
using Moonlight.ApiServer.Database.Entities;
using MoonlightServers.ApiServer.Database.Entities;
using MoonlightServers.ApiServer.Extensions;
using MoonlightServers.ApiServer.Models;
using MoonlightServers.ApiServer.Services;
using MoonlightServers.Shared.Enums;
using MoonlightServers.Shared.Http.Responses.Client.Servers;
using MoonlightServers.Shared.Http.Responses.Client.Servers.Allocations;
using MoonlightServers.Shared.Models;
namespace MoonlightServers.ApiServer.Http.Controllers.Client;
[Authorize]
[ApiController]
[Route("api/client/servers")]
public class ServersController : Controller
{
private readonly ServerService ServerService;
private readonly DatabaseRepository<Server> ServerRepository;
private readonly DatabaseRepository<ServerShare> ShareRepository;
private readonly DatabaseRepository<User> UserRepository;
private readonly NodeService NodeService;
private readonly ServerAuthorizeService AuthorizeService;
public ServersController(
DatabaseRepository<Server> serverRepository,
NodeService nodeService,
ServerService serverService,
ServerAuthorizeService authorizeService,
DatabaseRepository<ServerShare> shareRepository,
DatabaseRepository<User> userRepository
)
{
ServerRepository = serverRepository;
NodeService = nodeService;
ServerService = serverService;
AuthorizeService = authorizeService;
ShareRepository = shareRepository;
UserRepository = userRepository;
}
[HttpGet]
public async Task<PagedData<ServerDetailResponse>> GetAll([FromQuery] int page, [FromQuery] int pageSize)
{
var userIdClaim = User.FindFirstValue("userId");
if (string.IsNullOrEmpty(userIdClaim))
throw new HttpApiException("Only users are able to use this endpoint", 400);
var userId = int.Parse(userIdClaim);
var query = ServerRepository
.Get()
.Include(x => x.Allocations)
.Include(x => x.Star)
.Include(x => x.Node)
.Where(x => x.OwnerId == userId);
var count = await query.CountAsync();
var items = await query.Skip(page * pageSize).Take(pageSize).ToArrayAsync();
var mappedItems = items.Select(x => new ServerDetailResponse()
{
Id = x.Id,
Name = x.Name,
NodeName = x.Node.Name,
StarName = x.Star.Name,
Cpu = x.Cpu,
Memory = x.Memory,
Disk = x.Disk,
Allocations = x.Allocations.Select(y => new AllocationDetailResponse()
{
Id = y.Id,
Port = y.Port,
IpAddress = y.IpAddress
}).ToArray()
}).ToArray();
return new PagedData<ServerDetailResponse>()
{
Items = mappedItems,
CurrentPage = page,
PageSize = pageSize,
TotalItems = count,
TotalPages = count == 0 ? 0 : count / pageSize
};
}
[HttpGet("shared")]
public async Task<PagedData<ServerDetailResponse>> GetAllShared([FromQuery] int page, [FromQuery] int pageSize)
{
var userIdClaim = User.FindFirstValue("userId");
if (string.IsNullOrEmpty(userIdClaim))
throw new HttpApiException("Only users are able to use this endpoint", 400);
var userId = int.Parse(userIdClaim);
var query = ShareRepository
.Get()
.Include(x => x.Server)
.ThenInclude(x => x.Node)
.Include(x => x.Server)
.ThenInclude(x => x.Star)
.Include(x => x.Server)
.ThenInclude(x => x.Allocations)
.Where(x => x.UserId == userId);
var count = await query.CountAsync();
var items = await query.Skip(page * pageSize).Take(pageSize).ToArrayAsync();
var ownerIds = items
.Select(x => x.Server.OwnerId)
.Distinct()
.ToArray();
var owners = await UserRepository
.Get()
.Where(x => ownerIds.Contains(x.Id))
.ToArrayAsync();
var mappedItems = items.Select(x => new ServerDetailResponse()
{
Id = x.Server.Id,
Name = x.Server.Name,
NodeName = x.Server.Node.Name,
StarName = x.Server.Star.Name,
Cpu = x.Server.Cpu,
Memory = x.Server.Memory,
Disk = x.Server.Disk,
Allocations = x.Server.Allocations.Select(y => new AllocationDetailResponse()
{
Id = y.Id,
Port = y.Port,
IpAddress = y.IpAddress
}).ToArray(),
Share = new()
{
SharedBy = owners.First(y => y.Id == x.Server.OwnerId).Username,
Permissions = x.Content.Permissions.ToArray()
}
}).ToArray();
return new PagedData<ServerDetailResponse>()
{
Items = mappedItems,
CurrentPage = page,
PageSize = pageSize,
TotalItems = count,
TotalPages = count == 0 ? 0 : count / pageSize
};
}
[HttpGet("{serverId:int}")]
public async Task<ServerDetailResponse> Get([FromRoute] int serverId)
{
var server = await ServerRepository
.Get()
.Include(x => x.Allocations)
.Include(x => x.Star)
.Include(x => x.Node)
.FirstOrDefaultAsync(x => x.Id == serverId);
if (server == null)
throw new HttpApiException("No server with this id found", 404);
var authorizationResult = await AuthorizeService.Authorize(User, server);
if (!authorizationResult.Succeeded)
{
throw new HttpApiException(
authorizationResult.Message ?? "No server with this id found",
404
);
}
// Create mapped response
var response = new ServerDetailResponse()
{
Id = server.Id,
Name = server.Name,
NodeName = server.Node.Name,
StarName = server.Star.Name,
Cpu = server.Cpu,
Memory = server.Memory,
Disk = server.Disk,
Allocations = server.Allocations.Select(y => new AllocationDetailResponse()
{
Id = y.Id,
Port = y.Port,
IpAddress = y.IpAddress
}).ToArray()
};
// Handle requests on shared servers
if (authorizationResult.Share != null)
{
var owner = await UserRepository
.Get()
.FirstAsync(x => x.Id == server.OwnerId);
response.Share = new()
{
SharedBy = owner.Username,
Permissions = authorizationResult.Share.Content.Permissions.ToArray()
};
}
return response;
}
[HttpGet("{serverId:int}/status")]
public async Task<ServerStatusResponse> GetStatus([FromRoute] int serverId)
{
var server = await GetServerById(serverId);
var status = await ServerService.GetStatus(server);
return new ServerStatusResponse()
{
State = status.State.ToServerPowerState()
};
}
[HttpGet("{serverId:int}/ws")]
public async Task<ServerWebSocketResponse> GetWebSocket([FromRoute] int serverId)
{
var server = await GetServerById(
serverId,
permission => permission is { Name: "console", Type: >= ServerPermissionType.Read }
);
// TODO: Handle transparent node proxy
var accessToken = NodeService.CreateAccessToken(server.Node, parameters =>
{
parameters.Add("type", "websocket");
parameters.Add("serverId", server.Id);
}, TimeSpan.FromMinutes(15)); // TODO: Configurable
var url = "";
url += server.Node.UseSsl ? "https://" : "http://";
url += $"{server.Node.Fqdn}:{server.Node.HttpPort}/api/servers/ws";
return new ServerWebSocketResponse()
{
Target = url,
AccessToken = accessToken
};
}
[HttpGet("{serverId:int}/logs")]
public async Task<ServerLogsResponse> GetLogs([FromRoute] int serverId)
{
var server = await GetServerById(
serverId,
permission => permission is { Name: "console", Type: >= ServerPermissionType.Read }
);
var logs = await ServerService.GetLogs(server);
return new ServerLogsResponse()
{
Messages = logs.Messages
};
}
[HttpGet("{serverId:int}/stats")]
public async Task<ServerStatsResponse> GetStats([FromRoute] int serverId)
{
var server = await GetServerById(
serverId
);
var stats = await ServerService.GetStats(server);
return new ServerStatsResponse()
{
CpuUsage = stats.CpuUsage,
MemoryUsage = stats.MemoryUsage,
NetworkRead = stats.NetworkRead,
NetworkWrite = stats.NetworkWrite,
IoRead = stats.IoRead,
IoWrite = stats.IoWrite
};
}
private async Task<Server> GetServerById(int serverId, Func<ServerSharePermission, bool>? filter = null)
{
var server = await ServerRepository
.Get()
.Include(x => x.Node)
.FirstOrDefaultAsync(x => x.Id == serverId);
if (server == null)
throw new HttpApiException("No server with this id found", 404);
var authorizeResult = await AuthorizeService.Authorize(User, server, filter);
if (!authorizeResult.Succeeded)
{
throw new HttpApiException(
authorizeResult.Message ?? "No permission for the requested resource",
403
);
}
return server;
}
}

View File

@@ -1,65 +0,0 @@
using Microsoft.AspNetCore.Authorization;
using Microsoft.AspNetCore.Mvc;
using Microsoft.EntityFrameworkCore;
using MoonCore.Exceptions;
using MoonCore.Extended.Abstractions;
using MoonlightServers.ApiServer.Database.Entities;
using MoonlightServers.ApiServer.Services;
using MoonlightServers.Shared.Enums;
namespace MoonlightServers.ApiServer.Http.Controllers.Client;
[Authorize]
[ApiController]
[Route("api/client/servers")]
public class SettingsController : Controller
{
private readonly ServerService ServerService;
private readonly DatabaseRepository<Server> ServerRepository;
private readonly ServerAuthorizeService AuthorizeService;
public SettingsController(
ServerService serverService,
DatabaseRepository<Server> serverRepository,
ServerAuthorizeService authorizeService
)
{
ServerService = serverService;
ServerRepository = serverRepository;
AuthorizeService = authorizeService;
}
[HttpPost("{serverId:int}/install")]
[Authorize]
public async Task Install([FromRoute] int serverId)
{
var server = await GetServerById(serverId);
await ServerService.Install(server);
}
private async Task<Server> GetServerById(int serverId)
{
var server = await ServerRepository
.Get()
.Include(x => x.Node)
.FirstOrDefaultAsync(x => x.Id == serverId);
if (server == null)
throw new HttpApiException("No server with this id found", 404);
var authorizeResult = await AuthorizeService.Authorize(
User, server,
permission => permission is { Name: "settings", Type: >= ServerPermissionType.ReadWrite }
);
if (!authorizeResult.Succeeded)
{
throw new HttpApiException(
authorizeResult.Message ?? "No permission for the requested resource",
403
);
}
return server;
}
}

View File

@@ -1,232 +0,0 @@
using System.ComponentModel.DataAnnotations;
using Microsoft.AspNetCore.Authorization;
using Microsoft.AspNetCore.Mvc;
using Microsoft.EntityFrameworkCore;
using MoonCore.Exceptions;
using MoonCore.Extended.Abstractions;
using MoonCore.Models;
using Moonlight.ApiServer.Database.Entities;
using MoonlightServers.ApiServer.Database.Entities;
using MoonlightServers.ApiServer.Services;
using MoonlightServers.Shared.Enums;
using MoonlightServers.Shared.Http.Requests.Client.Servers.Shares;
using MoonlightServers.Shared.Http.Responses.Client.Servers.Shares;
namespace MoonlightServers.ApiServer.Http.Controllers.Client;
[Authorize]
[ApiController]
[Route("api/client/servers")]
public class SharesController : Controller
{
private readonly DatabaseRepository<Server> ServerRepository;
private readonly DatabaseRepository<ServerShare> ShareRepository;
private readonly DatabaseRepository<User> UserRepository;
private readonly ServerAuthorizeService AuthorizeService;
public SharesController(
DatabaseRepository<Server> serverRepository,
DatabaseRepository<ServerShare> shareRepository,
DatabaseRepository<User> userRepository,
ServerAuthorizeService authorizeService
)
{
ServerRepository = serverRepository;
ShareRepository = shareRepository;
UserRepository = userRepository;
AuthorizeService = authorizeService;
}
[HttpGet("{serverId:int}/shares")]
public async Task<PagedData<ServerShareResponse>> GetAll(
[FromRoute] int serverId,
[FromQuery] [Range(0, int.MaxValue)] int page,
[FromQuery] [Range(1, 100)] int pageSize
)
{
var server = await GetServerById(serverId);
var query = ShareRepository
.Get()
.Where(x => x.Server.Id == server.Id);
var count = await query.CountAsync();
var items = await query.Skip(page * pageSize).Take(pageSize).ToArrayAsync();
var userIds = items
.Select(x => x.UserId)
.Distinct()
.ToArray();
var users = await UserRepository
.Get()
.Where(x => userIds.Contains(x.Id))
.ToArrayAsync();
var mappedItems = items.Select(x => new ServerShareResponse()
{
Id = x.Id,
Username = users.First(y => y.Id == x.UserId).Username,
Permissions = x.Content.Permissions.ToArray()
}).ToArray();
return new PagedData<ServerShareResponse>()
{
Items = mappedItems,
CurrentPage = page,
PageSize = pageSize,
TotalItems = count,
TotalPages = count == 0 ? 0 : count / pageSize
};
}
[HttpGet("{serverId:int}/shares/{id:int}")]
public async Task<ServerShareResponse> Get(
[FromRoute] int serverId,
[FromRoute] int id
)
{
var server = await GetServerById(serverId);
var share = await ShareRepository
.Get()
.FirstOrDefaultAsync(x => x.Server.Id == server.Id && x.Id == id);
if (share == null)
throw new HttpApiException("A share with that id cannot be found", 404);
var user = await UserRepository
.Get()
.FirstAsync(x => x.Id == share.UserId);
var mappedItem = new ServerShareResponse()
{
Id = share.Id,
Username = user.Username,
Permissions = share.Content.Permissions.ToArray()
};
return mappedItem;
}
[HttpPost("{serverId:int}/shares")]
public async Task<ServerShareResponse> Create(
[FromRoute] int serverId,
[FromBody] CreateShareRequest request
)
{
var server = await GetServerById(serverId);
var user = await UserRepository
.Get()
.FirstOrDefaultAsync(x => x.Username == request.Username);
if (user == null)
throw new HttpApiException("A user with that username could not be found", 400);
var share = new ServerShare()
{
Server = server,
Content = new()
{
Permissions = request.Permissions
},
CreatedAt = DateTime.UtcNow,
UpdatedAt = DateTime.UtcNow,
UserId = user.Id
};
var finalShare = await ShareRepository.Add(share);
var mappedItem = new ServerShareResponse()
{
Id = finalShare.Id,
Username = user.Username,
Permissions = finalShare.Content.Permissions.ToArray()
};
return mappedItem;
}
[HttpPatch("{serverId:int}/shares/{id:int}")]
public async Task<ServerShareResponse> Update(
[FromRoute] int serverId,
[FromRoute] int id,
[FromBody] UpdateShareRequest request
)
{
var server = await GetServerById(serverId);
var share = await ShareRepository
.Get()
.FirstOrDefaultAsync(x => x.Server.Id == server.Id && x.Id == id);
if (share == null)
throw new HttpApiException("A share with that id cannot be found", 404);
share.Content.Permissions = request.Permissions;
share.UpdatedAt = DateTime.UtcNow;
await ShareRepository.Update(share);
var user = await UserRepository
.Get()
.FirstOrDefaultAsync(x => x.Id == share.UserId);
if (user == null)
throw new HttpApiException("A user with that id could not be found", 400);
var mappedItem = new ServerShareResponse()
{
Id = share.Id,
Username = user.Username,
Permissions = share.Content.Permissions.ToArray()
};
return mappedItem;
}
[HttpDelete("{serverId:int}/shares/{id:int}")]
public async Task Delete(
[FromRoute] int serverId,
[FromRoute] int id
)
{
var server = await GetServerById(serverId);
var share = await ShareRepository
.Get()
.FirstOrDefaultAsync(x => x.Server.Id == server.Id && x.Id == id);
if (share == null)
throw new HttpApiException("A share with that id cannot be found", 404);
await ShareRepository.Remove(share);
}
private async Task<Server> GetServerById(int serverId)
{
var server = await ServerRepository
.Get()
.Include(x => x.Node)
.FirstOrDefaultAsync(x => x.Id == serverId);
if (server == null)
throw new HttpApiException("No server with this id found", 404);
var authorizeResult = await AuthorizeService.Authorize(
User, server,
permission => permission is { Name: "shares", Type: >= ServerPermissionType.ReadWrite }
);
if (!authorizeResult.Succeeded)
{
throw new HttpApiException(
authorizeResult.Message ?? "No permission for the requested resource",
403
);
}
return server;
}
}

View File

@@ -1,150 +0,0 @@
using Microsoft.AspNetCore.Authorization;
using Microsoft.AspNetCore.Mvc;
using Microsoft.EntityFrameworkCore;
using MoonCore.Exceptions;
using MoonCore.Extended.Abstractions;
using Moonlight.ApiServer.Database.Entities;
using MoonlightServers.ApiServer.Database.Entities;
using MoonlightServers.ApiServer.Services;
using MoonlightServers.Shared.Enums;
using MoonlightServers.Shared.Http.Requests.Client.Servers.Variables;
using MoonlightServers.Shared.Http.Responses.Client.Servers.Variables;
namespace MoonlightServers.ApiServer.Http.Controllers.Client;
[Authorize]
[ApiController]
[Route("api/client/servers")]
public class VariablesController : Controller
{
private readonly DatabaseRepository<Server> ServerRepository;
private readonly ServerAuthorizeService AuthorizeService;
public VariablesController(
DatabaseRepository<Server> serverRepository,
ServerAuthorizeService authorizeService
)
{
ServerRepository = serverRepository;
AuthorizeService = authorizeService;
}
[HttpGet("{serverId:int}/variables")]
public async Task<ServerVariableDetailResponse[]> Get([FromRoute] int serverId)
{
var server = await GetServerById(serverId, ServerPermissionType.Read);
return server.Star.Variables.Select(starVariable =>
{
var serverVariable = server.Variables.First(x => x.Key == starVariable.Key);
return new ServerVariableDetailResponse()
{
Key = starVariable.Key,
Value = serverVariable.Value,
Type = starVariable.Type,
Name = starVariable.Name,
Description = starVariable.Description,
Filter = starVariable.Filter
};
}).ToArray();
}
[HttpPut("{serverId:int}/variables")]
public async Task<ServerVariableDetailResponse> UpdateSingle(
[FromRoute] int serverId,
[FromBody] UpdateServerVariableRequest request
)
{
// TODO: Handle filter
var server = await GetServerById(serverId, ServerPermissionType.ReadWrite);
var serverVariable = server.Variables.FirstOrDefault(x => x.Key == request.Key);
var starVariable = server.Star.Variables.FirstOrDefault(x => x.Key == request.Key);
if (serverVariable == null || starVariable == null)
throw new HttpApiException($"No variable with the key found: {request.Key}", 400);
serverVariable.Value = request.Value;
await ServerRepository.Update(server);
return new ServerVariableDetailResponse()
{
Key = starVariable.Key,
Value = serverVariable.Value,
Type = starVariable.Type,
Name = starVariable.Name,
Description = starVariable.Description,
Filter = starVariable.Filter
};
}
[HttpPatch("{serverId:int}/variables")]
public async Task<ServerVariableDetailResponse[]> Update(
[FromRoute] int serverId,
[FromBody] UpdateServerVariableRangeRequest request
)
{
var server = await GetServerById(serverId, ServerPermissionType.ReadWrite);
foreach (var variable in request.Variables)
{
// TODO: Handle filter
var serverVariable = server.Variables.FirstOrDefault(x => x.Key == variable.Key);
var starVariable = server.Star.Variables.FirstOrDefault(x => x.Key == variable.Key);
if (serverVariable == null || starVariable == null)
throw new HttpApiException($"No variable with the key found: {variable.Key}", 400);
serverVariable.Value = variable.Value;
}
await ServerRepository.Update(server);
return request.Variables.Select(requestVariable =>
{
var serverVariable = server.Variables.First(x => x.Key == requestVariable.Key);
var starVariable = server.Star.Variables.First(x => x.Key == requestVariable.Key);
return new ServerVariableDetailResponse()
{
Key = starVariable.Key,
Value = serverVariable.Value,
Type = starVariable.Type,
Name = starVariable.Name,
Description = starVariable.Description,
Filter = starVariable.Filter
};
}).ToArray();
}
private async Task<Server> GetServerById(int serverId, ServerPermissionType type)
{
var server = await ServerRepository
.Get()
.Include(x => x.Variables)
.Include(x => x.Star)
.ThenInclude(x => x.Variables)
.FirstOrDefaultAsync(x => x.Id == serverId);
if (server == null)
throw new HttpApiException("No server with this id found", 404);
var authorizeResult = await AuthorizeService.Authorize(
User, server,
permission => permission.Name == "variables" && permission.Type >= type
);
if (!authorizeResult.Succeeded)
{
throw new HttpApiException(
authorizeResult.Message ?? "No permission for the requested resource",
403
);
}
return server;
}
}

View File

@@ -1,13 +0,0 @@
using Microsoft.AspNetCore.Authorization;
using Microsoft.AspNetCore.Mvc;
namespace MoonlightServers.ApiServer.Http.Controllers.Remote.Nodes;
[ApiController]
[Route("api/remote/server/node")]
[Authorize(AuthenticationSchemes = "nodeAuthentication")]
public class NodeTripController : Controller
{
[HttpGet("trip")]
public Task Get() => Task.CompletedTask;
}

View File

@@ -1,194 +0,0 @@
using Microsoft.AspNetCore.Authorization;
using Microsoft.AspNetCore.Mvc;
using Microsoft.EntityFrameworkCore;
using MoonCore.Exceptions;
using MoonCore.Extended.Abstractions;
using MoonCore.Models;
using MoonlightServers.ApiServer.Database.Entities;
using MoonlightServers.DaemonShared.PanelSide.Http.Responses;
namespace MoonlightServers.ApiServer.Http.Controllers.Remote;
[ApiController]
[Route("api/remote/servers")]
[Authorize(AuthenticationSchemes = "nodeAuthentication")]
public class ServersController : Controller
{
private readonly DatabaseRepository<Server> ServerRepository;
private readonly DatabaseRepository<Node> NodeRepository;
private readonly ILogger<ServersController> Logger;
public ServersController(
DatabaseRepository<Server> serverRepository,
DatabaseRepository<Node> nodeRepository,
ILogger<ServersController> logger
)
{
ServerRepository = serverRepository;
NodeRepository = nodeRepository;
Logger = logger;
}
[HttpGet]
public async Task<PagedData<ServerDataResponse>> Get([FromQuery] int page, [FromQuery] int pageSize)
{
// Load the node via the id
var nodeId = int.Parse(User.Claims.First(x => x.Type == "nodeId").Value);
var node = await NodeRepository
.Get()
.FirstAsync(x => x.Id == nodeId);
var total = await ServerRepository
.Get()
.Where(x => x.Node.Id == node.Id)
.CountAsync();
var servers = await ServerRepository
.Get()
.Where(x => x.Node.Id == node.Id)
.Include(x => x.Star)
.ThenInclude(x => x.DockerImages)
.Include(x => x.Variables)
.Include(x => x.Allocations)
.Skip(page * pageSize)
.Take(pageSize)
.ToArrayAsync();
var serverData = new List<ServerDataResponse>();
foreach (var server in servers)
{
var convertedData = ConvertToServerData(server);
if (convertedData == null)
continue;
serverData.Add(convertedData);
}
return new PagedData<ServerDataResponse>()
{
Items = serverData.ToArray(),
CurrentPage = page,
PageSize = pageSize,
TotalItems = total,
TotalPages = total == 0 ? 0 : total / pageSize
};
}
[HttpGet("{id:int}")]
public async Task<ServerDataResponse> Get([FromRoute] int id)
{
// Load the node via the id
var nodeId = int.Parse(User.Claims.First(x => x.Type == "nodeId").Value);
var node = await NodeRepository
.Get()
.FirstAsync(x => x.Id == nodeId);
// Load the server with the star data attached. We filter by the node to ensure the node can only access
// servers linked to it
var server = await ServerRepository
.Get()
.Where(x => x.Node.Id == node.Id)
.Include(x => x.Star)
.ThenInclude(x => x.DockerImages)
.Include(x => x.Variables)
.Include(x => x.Allocations)
.FirstOrDefaultAsync(x => x.Id == id);
if (server == null)
throw new HttpApiException("No server with this id found", 404);
var convertedData = ConvertToServerData(server);
if (convertedData == null)
throw new HttpApiException("An error occured while creating the server data model", 500);
return convertedData;
}
[HttpGet("{id:int}/install")]
public async Task<ServerInstallDataResponse> GetInstall([FromRoute] int id)
{
// Load the node via the id
var nodeId = int.Parse(User.Claims.First(x => x.Type == "nodeId").Value);
var node = await NodeRepository
.Get()
.FirstAsync(x => x.Id == nodeId);
// Load the server with the star data attached. We filter by the node to ensure the node can only access
// servers linked to it
var server = await ServerRepository
.Get()
.Where(x => x.Node.Id == node.Id)
.Include(x => x.Star)
.FirstOrDefaultAsync(x => x.Id == id);
if (server == null)
throw new HttpApiException("No server with this id found", 404);
return new ServerInstallDataResponse()
{
Script = server.Star.InstallScript,
DockerImage = server.Star.InstallDockerImage,
Shell = server.Star.InstallShell
};
}
private ServerDataResponse? ConvertToServerData(Server server)
{
// Find the docker image to use for this server
StarDockerImage? dockerImage = null;
// Handle server set image if specified
if (server.DockerImageIndex != -1)
{
dockerImage = server.Star.DockerImages
.Skip(server.DockerImageIndex)
.FirstOrDefault();
}
// Handle star default image if set
if (dockerImage == null && server.Star.DefaultDockerImage != -1)
{
dockerImage = server.Star.DockerImages
.Skip(server.Star.DefaultDockerImage)
.FirstOrDefault();
}
if (dockerImage == null)
dockerImage = server.Star.DockerImages.LastOrDefault();
if (dockerImage == null)
{
Logger.LogWarning("Unable to map server data for server {id}: No docker image available", server.Id);
return null;
}
// Convert model
return new ServerDataResponse()
{
Id = server.Id,
StartupCommand = server.StartupOverride ?? server.Star.StartupCommand,
Allocations = server.Allocations.Select(x => new AllocationDataResponse()
{
IpAddress = x.IpAddress,
Port = x.Port
}).ToArray(),
Variables = server.Variables.ToDictionary(x => x.Key, x => x.Value),
Bandwidth = server.Bandwidth,
Cpu = server.Cpu,
Disk = server.Disk,
Memory = server.Memory,
OnlineDetection = server.Star.OnlineDetection,
DockerImage = dockerImage.Identifier,
PullDockerImage = dockerImage.AutoPulling,
ParseConiguration = server.Star.ParseConfiguration,
StopCommand = server.Star.StopCommand,
UseVirtualDisk = server.UseVirtualDisk
};
}
}

View File

@@ -1,33 +0,0 @@
using System.Security.Claims;
using Microsoft.AspNetCore.Authorization;
using MoonCore.Attributes;
using MoonlightServers.ApiServer.Database.Entities;
using MoonlightServers.ApiServer.Interfaces;
using MoonlightServers.ApiServer.Models;
using MoonlightServers.Shared.Models;
namespace MoonlightServers.ApiServer.Implementations.ServerAuthFilters;
public class AdminAuthFilter : IServerAuthorizationFilter
{
private readonly IAuthorizationService AuthorizationService;
public AdminAuthFilter(IAuthorizationService authorizationService)
{
AuthorizationService = authorizationService;
}
public async Task<ServerAuthorizationResult?> Process(
ClaimsPrincipal user,
Server server,
Func<ServerSharePermission, bool>? filter = null
)
{
var authResult = await AuthorizationService.AuthorizeAsync(
user,
"permissions:admin.servers.manage"
);
return authResult.Succeeded ? ServerAuthorizationResult.Success() : null;
}
}

View File

@@ -1,28 +0,0 @@
using System.Security.Claims;
using MoonCore.Attributes;
using MoonlightServers.ApiServer.Database.Entities;
using MoonlightServers.ApiServer.Interfaces;
using MoonlightServers.ApiServer.Models;
using MoonlightServers.Shared.Models;
namespace MoonlightServers.ApiServer.Implementations.ServerAuthFilters;
public class OwnerAuthFilter : IServerAuthorizationFilter
{
public Task<ServerAuthorizationResult?> Process(ClaimsPrincipal user, Server server, Func<ServerSharePermission, bool>? filter = null)
{
var userIdValue = user.FindFirstValue("userId");
if (string.IsNullOrEmpty(userIdValue)) // This is the case for api keys
return Task.FromResult<ServerAuthorizationResult?>(null);
var userId = int.Parse(userIdValue);
if(server.OwnerId != userId)
return Task.FromResult<ServerAuthorizationResult?>(null);
return Task.FromResult<ServerAuthorizationResult?>(
ServerAuthorizationResult.Success()
);
}
}

View File

@@ -1,49 +0,0 @@
using System.Security.Claims;
using Microsoft.EntityFrameworkCore;
using MoonCore.Attributes;
using MoonCore.Extended.Abstractions;
using MoonlightServers.ApiServer.Database.Entities;
using MoonlightServers.ApiServer.Interfaces;
using MoonlightServers.ApiServer.Models;
using MoonlightServers.Shared.Models;
namespace MoonlightServers.ApiServer.Implementations.ServerAuthFilters;
public class ShareAuthFilter : IServerAuthorizationFilter
{
private readonly DatabaseRepository<ServerShare> ShareRepository;
public ShareAuthFilter(DatabaseRepository<ServerShare> shareRepository)
{
ShareRepository = shareRepository;
}
public async Task<ServerAuthorizationResult?> Process(
ClaimsPrincipal user,
Server server,
Func<ServerSharePermission, bool>? filter = null
)
{
var userIdValue = user.FindFirstValue("userId");
if (string.IsNullOrEmpty(userIdValue))
return null;
var userId = int.Parse(userIdValue);
var share = await ShareRepository
.Get()
.FirstOrDefaultAsync(x => x.Server.Id == server.Id && x.UserId == userId);
if (share == null)
return null;
if(filter == null)
return ServerAuthorizationResult.Success(share);
if(share.Content.Permissions.Any(filter))
return ServerAuthorizationResult.Success(share);
return null;
}
}

View File

@@ -1,17 +0,0 @@
using System.Security.Claims;
using MoonlightServers.ApiServer.Database.Entities;
using MoonlightServers.ApiServer.Models;
using MoonlightServers.Shared.Models;
namespace MoonlightServers.ApiServer.Interfaces;
public interface IServerAuthorizationFilter
{
// Return null => skip to next filter / handler
// Return any value, instant return
public Task<ServerAuthorizationResult?> Process(
ClaimsPrincipal user,
Server server,
Func<ServerSharePermission, bool>? filter = null
);
}

View File

@@ -1,28 +0,0 @@
using MoonlightServers.ApiServer.Database.Entities;
namespace MoonlightServers.ApiServer.Models;
public record ServerAuthorizationResult
{
public bool Succeeded { get; set; }
public ServerShare? Share { get; set; }
public string? Message { get; set; }
public static ServerAuthorizationResult Success(ServerShare? share = null)
{
return new()
{
Succeeded = true,
Share = share
};
}
public static ServerAuthorizationResult Failed(string? message = null)
{
return new()
{
Succeeded = false,
Message = message
};
}
}

View File

@@ -1,8 +0,0 @@
using MoonlightServers.Shared.Models;
namespace MoonlightServers.ApiServer.Models;
public class ServerShareContent
{
public List<ServerSharePermission> Permissions { get; set; } = [];
}

View File

@@ -1,48 +0,0 @@
namespace MoonlightServers.ApiServer.Models.Stars;
public class LegacyImageImportModel
{
public string Name { get; set; } = "";
public string Author { get; set; } = "";
public string? UpdateUrl { get; set; }
public string? DonateUrl { get; set; }
public string StartupCommand { get; set; } = "";
public string OnlineDetection { get; set; } = "";
public string StopCommand { get; set; } = "";
public string InstallShell { get; set; } = "";
public string InstallDockerImage { get; set; } = "";
public string InstallScript { get; set; } = "";
public string ParseConfiguration { get; set; } = "[]";
public int AllocationsNeeded { get; set; } = 1;
public List<ImageVariable> Variables { get; set; } = new();
public int DefaultDockerImage { get; set; } = 0;
public bool AllowDockerImageChange { get; set; } = false;
public List<ImageDockerImage> DockerImages { get; set; } = new();
public class ImageVariable
{
public string Key { get; set; } = "";
public string DefaultValue { get; set; } = "";
public string DisplayName { get; set; } = "";
public string Description { get; set; } = "";
public bool AllowView { get; set; } = false;
public bool AllowEdit { get; set; } = false;
public string? Filter { get; set; }
}
public class ImageDockerImage // Weird name xd
{
public string DisplayName { get; set; } = "";
public string Name { get; set; } = "";
public bool AutoPull { get; set; }
}
}

View File

@@ -1,8 +0,0 @@
namespace MoonlightServers.ApiServer.Models.Stars;
public class LegacyImageParseConfigModel
{
public string Type { get; set; } = "";
public string File { get; set; } = "";
public Dictionary<string, string> Configuration { get; set; } = new();
}

View File

@@ -1,55 +0,0 @@
using MoonlightServers.Shared.Enums;
namespace MoonlightServers.ApiServer.Models.Stars;
public class StarExportModel
{
// Meta
public string Name { get; set; }
public string Author { get; set; }
public string Version { get; set; }
public string? DonateUrl { get; set; }
public string? UpdateUrl { get; set; }
// Start and stop
public string StartupCommand { get; set; }
public string StopCommand { get; set; }
public string OnlineDetection { get; set; }
// Install
public string InstallShell { get; set; }
public string InstallDockerImage { get; set; }
public string InstallScript { get; set; }
// Misc
public int RequiredAllocations { get; set; }
public bool AllowDockerImageChange { get; set; }
public int DefaultDockerImage { get; set; }
public string ParseConfiguration { get; set; }
// Relations
public StarVariableExportModel[] Variables { get; set; }
public StarDockerImageExportModel[] DockerImages { get; set; }
public class StarDockerImageExportModel
{
public string DisplayName { get; set; }
public string Identifier { get; set; }
public bool AutoPulling { get; set; }
}
public class StarVariableExportModel
{
public string Name { get; set; }
public string Description { get; set; }
public string Key { get; set; }
public string DefaultValue { get; set; }
public bool AllowViewing { get; set; }
public bool AllowEditing { get; set; }
public StarVariableType Type { get; set; }
public string? Filter { get; set; }
}
}

View File

@@ -1,50 +0,0 @@
<Project Sdk="Microsoft.NET.Sdk.Web">
<PropertyGroup>
<TargetFramework>net9.0</TargetFramework>
<Nullable>enable</Nullable>
<ImplicitUsings>enable</ImplicitUsings>
<DockerDefaultTargetOS>Linux</DockerDefaultTargetOS>
</PropertyGroup>
<PropertyGroup Label="Nuget Settings">
<PackageId>MoonlightServers.ApiServer</PackageId>
<Title>MoonlightServers.ApiServer</Title>
<Version>2.1.0</Version>
<PackageTags>apiserver</PackageTags>
<IsPackable>true</IsPackable>
</PropertyGroup>
<ItemGroup>
<PackageReference Include="Moonlight.ApiServer" Version="2.1.0"/>
<PackageReference Include="Microsoft.EntityFrameworkCore.Design" Version="9.0.5"/>
</ItemGroup>
<ItemGroup>
<ProjectReference Include="..\MoonlightServers.DaemonShared\MoonlightServers.DaemonShared.csproj"/>
<ProjectReference Include="..\MoonlightServers.Frontend\MoonlightServers.Frontend.csproj"/>
<ProjectReference Include="..\MoonlightServers.Shared\MoonlightServers.Shared.csproj"/>
</ItemGroup>
<ItemGroup>
<Folder Include="Database\Migrations\"/>
<Folder Include="Http\Middleware\"/>
</ItemGroup>
<ItemGroup Label="Build instruction for nuget package building">
<None Include="**\*.cs" Exclude="storage\**\*;bin\**\*;obj\**\*">
<Pack>true</Pack>
<PackagePath>src</PackagePath>
<CopyToOutputDirectory>Never</CopyToOutputDirectory>
</None>
<None Include="**\*.razor" Exclude="storage\**\*;bin\**\*;obj\**\*">
<Pack>true</Pack>
<PackagePath>src</PackagePath>
<CopyToOutputDirectory>Never</CopyToOutputDirectory>
</None>
<Compile Remove="storage\**\*"/>
<Content Remove="storage\**\*"/>
<None Remove="storage\**\*"/>
</ItemGroup>
</Project>

View File

@@ -1,18 +0,0 @@
using System.Text.Json;
using Moonlight.ApiServer;
using Moonlight.ApiServer.Models;
using MoonlightServers.ApiServer.Startup;
// Development Server Startup
// This file is a small helper for development instances for moonlight.
// It calls the moonlight startup with the current project loaded as a plugin.
// This allows you to develop and debug projects without any hassle
// !!! DO NOT HARDCORE ANY SECRETS HERE !!!
var startup = new Startup();
await startup.Run(args, [
new PluginStartup()
]);

View File

@@ -1,18 +0,0 @@
{
"$schema": "http://json.schemastore.org/launchsettings.json",
"profiles": {
"Dev Server": {
"commandName": "Project",
"dotnetRunMessages": true,
"launchBrowser": false,
"launchUrl": "swagger",
"applicationUrl": "http://localhost:5269",
"environmentVariables": {
"ASPNETCORE_ENVIRONMENT": "Development",
"MOONLIGHT_PUBLICURL": "http://localhost:5269",
"HTTP_PROXY": "",
"HTTPS_PROXY": ""
}
}
}
}

View File

@@ -1,28 +0,0 @@
namespace MoonlightServers.ApiServer.Services;
public class NodeBootService : IHostedLifecycleService
{
public async Task StartedAsync(CancellationToken cancellationToken)
{
// TODO: Add node boot calls here
}
#region Unused
public Task StartAsync(CancellationToken cancellationToken)
=> Task.CompletedTask;
public Task StopAsync(CancellationToken cancellationToken)
=> Task.CompletedTask;
public Task StartingAsync(CancellationToken cancellationToken)
=> Task.CompletedTask;
public Task StoppedAsync(CancellationToken cancellationToken)
=> Task.CompletedTask;
public Task StoppingAsync(CancellationToken cancellationToken)
=> Task.CompletedTask;
#endregion
}

View File

@@ -1,86 +0,0 @@
using System.IdentityModel.Tokens.Jwt;
using System.Security.Claims;
using System.Text;
using Microsoft.IdentityModel.Tokens;
using MoonCore.Attributes;
using MoonCore.Extended.Helpers;
using MoonCore.Helpers;
using MoonlightServers.ApiServer.Database.Entities;
using MoonlightServers.DaemonShared.DaemonSide.Http.Responses.Statistics;
using MoonlightServers.DaemonShared.DaemonSide.Http.Responses.Sys;
namespace MoonlightServers.ApiServer.Services;
[Singleton]
public class NodeService
{
public string CreateAccessToken(Node node, Action<Dictionary<string, object>> parameters, TimeSpan duration)
{
var claims = new Dictionary<string, object>();
parameters.Invoke(claims);
var jwtSecurityTokenHandler = new JwtSecurityTokenHandler();
var securityTokenDescriptor = new SecurityTokenDescriptor()
{
Expires = DateTime.UtcNow.Add(duration),
NotBefore = DateTime.UtcNow.AddSeconds(-1),
Claims = claims,
IssuedAt = DateTime.UtcNow,
SigningCredentials = new SigningCredentials(
new SymmetricSecurityKey(Encoding.UTF8.GetBytes(
node.Token
)),
SecurityAlgorithms.HmacSha256
),
Audience = node.TokenId
};
var securityToken = jwtSecurityTokenHandler.CreateJwtSecurityToken(securityTokenDescriptor);
return jwtSecurityTokenHandler.WriteToken(securityToken);
}
public async Task<SystemStatusResponse> GetSystemStatus(Node node)
{
using var apiClient = CreateApiClient(node);
return await apiClient.GetJson<SystemStatusResponse>("api/system/status");
}
#region Statistics
public async Task<StatisticsResponse> GetStatistics(Node node)
{
using var apiClient = CreateApiClient(node);
return await apiClient.GetJson<StatisticsResponse>("api/statistics");
}
public async Task<StatisticsDockerResponse> GetDockerStatistics(Node node)
{
using var apiClient = CreateApiClient(node);
return await apiClient.GetJson<StatisticsDockerResponse>("api/statistics/docker");
}
#endregion
#region Helpers
public HttpApiClient CreateApiClient(Node node)
{
var url = "";
url += node.UseSsl ? "https://" : "http://";
url += $"{node.Fqdn}:{node.HttpPort}/";
var httpClient = new HttpClient()
{
BaseAddress = new Uri(url)
};
httpClient.DefaultRequestHeaders.Add("Authorization", $"Bearer {node.Token}");
return new HttpApiClient(httpClient);
}
#endregion
}

View File

@@ -1,38 +0,0 @@
using System.Security.Claims;
using MoonCore.Attributes;
using MoonlightServers.ApiServer.Database.Entities;
using MoonlightServers.ApiServer.Interfaces;
using MoonlightServers.ApiServer.Models;
using MoonlightServers.Shared.Models;
namespace MoonlightServers.ApiServer.Services;
[Scoped]
public class ServerAuthorizeService
{
private readonly IEnumerable<IServerAuthorizationFilter> AuthorizationFilters;
public ServerAuthorizeService(
IEnumerable<IServerAuthorizationFilter> authorizationFilters
)
{
AuthorizationFilters = authorizationFilters;
}
public async Task<ServerAuthorizationResult> Authorize(
ClaimsPrincipal user,
Server server,
Func<ServerSharePermission, bool>? filter = null
)
{
foreach (var authorizationFilter in AuthorizationFilters)
{
var result = await authorizationFilter.Process(user, server, filter);
if (result != null)
return result;
}
return ServerAuthorizationResult.Failed();
}
}

View File

@@ -1,113 +0,0 @@
using Microsoft.EntityFrameworkCore;
using MoonCore.Attributes;
using MoonCore.Extended.Abstractions;
using MoonCore.Helpers;
using MoonlightServers.ApiServer.Database.Entities;
using MoonlightServers.DaemonShared.DaemonSide.Http.Requests;
using MoonlightServers.DaemonShared.DaemonSide.Http.Responses.Servers;
using MoonlightServers.DaemonShared.Enums;
namespace MoonlightServers.ApiServer.Services;
[Scoped]
public class ServerFileSystemService
{
private readonly NodeService NodeService;
private readonly DatabaseRepository<Server> ServerRepository;
public ServerFileSystemService(
NodeService nodeService,
DatabaseRepository<Server> serverRepository
)
{
NodeService = nodeService;
ServerRepository = serverRepository;
}
public async Task<ServerFileSystemResponse[]> List(Server server, string path)
{
using var apiClient = await GetApiClient(server);
return await apiClient.GetJson<ServerFileSystemResponse[]>(
$"api/servers/{server.Id}/files/list?path={path}"
);
}
public async Task Move(Server server, string oldPath, string newPath)
{
using var apiClient = await GetApiClient(server);
await apiClient.Post(
$"api/servers/{server.Id}/files/move?oldPath={oldPath}&newPath={newPath}"
);
}
public async Task Delete(Server server, string path)
{
using var apiClient = await GetApiClient(server);
await apiClient.Delete(
$"api/servers/{server.Id}/files/delete?path={path}"
);
}
public async Task Mkdir(Server server, string path)
{
using var apiClient = await GetApiClient(server);
await apiClient.Post(
$"api/servers/{server.Id}/files/mkdir?path={path}"
);
}
public async Task Compress(Server server, CompressType type, string[] items, string destination)
{
using var apiClient = await GetApiClient(server);
await apiClient.Post(
$"api/servers/{server.Id}/files/compress",
new ServerFilesCompressRequest()
{
Type = type,
Items = items,
Destination = destination
}
);
}
public async Task Decompress(Server server, CompressType type, string path, string destination)
{
using var apiClient = await GetApiClient(server);
await apiClient.Post(
$"api/servers/{server.Id}/files/decompress",
new ServerFilesDecompressRequest()
{
Type = type,
Path = path,
Destination = destination
}
);
}
#region Helpers
private async Task<HttpApiClient> GetApiClient(Server server)
{
var serverWithNode = server;
// ReSharper disable once ConditionIsAlwaysTrueOrFalseAccordingToNullableAPIContract
// It can be null when its not included when loading via ef !!!
if (server.Node == null)
{
serverWithNode = await ServerRepository
.Get()
.Include(x => x.Node)
.FirstAsync(x => x.Id == server.Id);
}
return NodeService.CreateApiClient(serverWithNode.Node);
}
#endregion
}

View File

@@ -1,178 +0,0 @@
using System.Text.Json;
using Microsoft.EntityFrameworkCore;
using MoonCore.Attributes;
using MoonCore.Exceptions;
using MoonCore.Extended.Abstractions;
using MoonCore.Helpers;
using Moonlight.ApiServer.Database.Entities;
using MoonlightServers.ApiServer.Database.Entities;
using MoonlightServers.DaemonShared.DaemonSide.Http.Responses.Servers;
namespace MoonlightServers.ApiServer.Services;
[Scoped]
public class ServerService
{
private readonly NodeService NodeService;
private readonly DatabaseRepository<Server> ServerRepository;
public ServerService(NodeService nodeService, DatabaseRepository<Server> serverRepository)
{
NodeService = nodeService;
ServerRepository = serverRepository;
}
#region Power Actions
public async Task Start(Server server)
{
try
{
using var apiClient = await GetApiClient(server);
await apiClient.Post($"api/servers/{server.Id}/start");
}
catch (HttpRequestException e)
{
throw new HttpApiException("Unable to access the node the server is running on", 502);
}
}
public async Task Stop(Server server)
{
try
{
using var apiClient = await GetApiClient(server);
await apiClient.Post($"api/servers/{server.Id}/stop");
}
catch (HttpRequestException e)
{
throw new HttpApiException("Unable to access the node the server is running on", 502);
}
}
public async Task Kill(Server server)
{
try
{
using var apiClient = await GetApiClient(server);
await apiClient.Post($"api/servers/{server.Id}/kill");
}
catch (HttpRequestException e)
{
throw new HttpApiException("Unable to access the node the server is running on", 502);
}
}
#endregion
public async Task Install(Server server)
{
try
{
using var apiClient = await GetApiClient(server);
await apiClient.Post($"api/servers/{server.Id}/install");
}
catch (HttpRequestException e)
{
throw new HttpApiException("Unable to access the node the server is running on", 502);
}
}
public async Task Sync(Server server)
{
try
{
using var apiClient = await GetApiClient(server);
await apiClient.Post($"api/servers/{server.Id}/sync");
}
catch (HttpRequestException e)
{
throw new HttpApiException("Unable to access the node the server is running on", 502);
}
}
public async Task SyncDelete(Server server)
{
try
{
using var apiClient = await GetApiClient(server);
await apiClient.Delete($"api/servers/{server.Id}");
}
catch (HttpRequestException e)
{
throw new HttpApiException("Unable to access the node the server is running on", 502);
}
}
public async Task<ServerStatusResponse> GetStatus(Server server)
{
try
{
using var apiClient = await GetApiClient(server);
return await apiClient.GetJson<ServerStatusResponse>($"api/servers/{server.Id}/status");
}
catch (HttpRequestException e)
{
throw new HttpApiException("Unable to access the node the server is running on", 502);
}
}
public async Task<ServerLogsResponse> GetLogs(Server server)
{
try
{
using var apiClient = await GetApiClient(server);
return await apiClient.GetJson<ServerLogsResponse>($"api/servers/{server.Id}/logs");
}
catch (HttpRequestException e)
{
throw new HttpApiException("Unable to access the node the server is running on", 502);
}
}
public async Task<ServerStatsResponse> GetStats(Server server)
{
try
{
using var apiClient = await GetApiClient(server);
return await apiClient.GetJson<ServerStatsResponse>($"api/servers/{server.Id}/stats");
}
catch (HttpRequestException e)
{
throw new HttpApiException("Unable to access the node the server is running on", 502);
}
}
#region Helpers
public bool IsAllowedToAccess(User user, Server server)
{
if (server.OwnerId == user.Id)
return true;
var permissions = JsonSerializer.Deserialize<string[]>(
user.PermissionsJson
) ?? [];
return PermissionHelper.HasPermission(permissions, "admin.servers.get");
}
private async Task<HttpApiClient> GetApiClient(Server server)
{
var serverWithNode = server;
// ReSharper disable once ConditionIsAlwaysTrueOrFalseAccordingToNullableAPIContract
// It can be null when its not included when loading via ef !!!
if (server.Node == null)
{
serverWithNode = await ServerRepository
.Get()
.Include(x => x.Node)
.FirstAsync(x => x.Id == server.Id);
}
return NodeService.CreateApiClient(serverWithNode.Node);
}
#endregion
}

View File

@@ -1,409 +0,0 @@
using System.Text.Json;
using Microsoft.EntityFrameworkCore;
using MoonCore.Attributes;
using MoonCore.Exceptions;
using MoonCore.Extended.Abstractions;
using MoonlightServers.ApiServer.Database.Entities;
using MoonlightServers.ApiServer.Models.Stars;
using MoonlightServers.Shared.Enums;
using MoonlightServers.Shared.Models;
namespace MoonlightServers.ApiServer.Services;
[Scoped]
public class StarImportExportService
{
private readonly DatabaseRepository<Star> StarRepository;
private readonly ILogger<StarImportExportService> Logger;
public StarImportExportService(DatabaseRepository<Star> starRepository, ILogger<StarImportExportService> logger)
{
StarRepository = starRepository;
Logger = logger;
}
public async Task<string> Export(int id)
{
var star = StarRepository
.Get()
.Include(x => x.DockerImages)
.Include(x => x.Variables)
.FirstOrDefault(x => x.Id == id);
if (star == null)
throw new HttpApiException("No star with this id found", 404);
var exportModel = new StarExportModel()
{
Name = star.Name,
Author = star.Author,
Version = star.Version,
DonateUrl = star.DonateUrl,
UpdateUrl = star.UpdateUrl,
InstallScript = star.InstallScript,
InstallShell = star.InstallShell,
InstallDockerImage = star.InstallDockerImage,
OnlineDetection = star.OnlineDetection,
StopCommand = star.StopCommand,
StartupCommand = star.StartupCommand,
ParseConfiguration = star.ParseConfiguration,
RequiredAllocations = star.RequiredAllocations,
AllowDockerImageChange = star.AllowDockerImageChange,
DefaultDockerImage = star.DefaultDockerImage,
Variables = star.Variables.Select(x => new StarExportModel.StarVariableExportModel()
{
Name = x.Name,
Type = x.Type,
Description = x.Description,
Filter = x.Filter,
Key = x.Key,
AllowEditing = x.AllowEditing,
AllowViewing = x.AllowViewing,
DefaultValue = x.DefaultValue
}).ToArray(),
DockerImages = star.DockerImages.Select(x => new StarExportModel.StarDockerImageExportModel()
{
Identifier = x.Identifier,
AutoPulling = x.AutoPulling,
DisplayName = x.DisplayName
}).ToArray()
};
var json = JsonSerializer.Serialize(exportModel, new JsonSerializerOptions()
{
WriteIndented = true
});
return json;
}
public async Task<Star> Import(string json)
{
// Determine which importer to use based on simple patterns
if (json.Contains("RequiredAllocations"))
return await ImportStar(json);
else if (json.Contains("AllocationsNeeded"))
return await ImportImage(json);
else if (json.Contains("_comment"))
return await ImportEgg(json);
else
throw new HttpApiException("Unable to determine the format of the imported star/image/egg", 400);
}
public async Task<Star> ImportStar(string json)
{
try
{
var model = JsonSerializer.Deserialize<StarExportModel>(json, new JsonSerializerOptions()
{
PropertyNameCaseInsensitive = true
});
ArgumentNullException.ThrowIfNull(model);
var star = new Star()
{
Name = model.Name,
Author = model.Author,
Version = model.Version,
DonateUrl = model.DonateUrl,
UpdateUrl = model.UpdateUrl,
InstallScript = model.InstallScript,
InstallShell = model.InstallShell,
InstallDockerImage = model.InstallDockerImage,
OnlineDetection = model.OnlineDetection,
StopCommand = model.StopCommand,
StartupCommand = model.StartupCommand,
ParseConfiguration = model.ParseConfiguration,
RequiredAllocations = model.RequiredAllocations,
AllowDockerImageChange = model.AllowDockerImageChange,
DefaultDockerImage = model.DefaultDockerImage,
Variables = model.Variables.Select(x => new StarVariable()
{
DefaultValue = x.DefaultValue,
Description = x.Description,
Filter = x.Filter,
Key = x.Key,
AllowEditing = x.AllowEditing,
AllowViewing = x.AllowViewing,
Type = x.Type,
Name = x.Name
}).ToList(),
DockerImages = model.DockerImages.Select(x => new StarDockerImage()
{
DisplayName = x.DisplayName,
AutoPulling = x.AutoPulling,
Identifier = x.Identifier
}).ToList()
};
var finalStar = await StarRepository.Add(star);
return finalStar;
}
catch (Exception e)
{
Logger.LogError("An unhandled error occured while importing star: {e}", e);
throw new HttpApiException("An unhandled error occured while importing star", 400);
}
}
public async Task<Star> ImportImage(string json)
{
try
{
var model = JsonSerializer.Deserialize<LegacyImageImportModel>(json, new JsonSerializerOptions()
{
PropertyNameCaseInsensitive = true
});
ArgumentNullException.ThrowIfNull(model);
var star = new Star()
{
Name = model.Name,
Author = model.Author,
Version = "Imported from v2.0",
DonateUrl = model.DonateUrl,
UpdateUrl = model.UpdateUrl,
InstallScript = model.InstallScript,
InstallShell = model.InstallShell,
InstallDockerImage = model.InstallDockerImage,
OnlineDetection = model.OnlineDetection,
StopCommand = model.StopCommand,
StartupCommand = model.StartupCommand,
RequiredAllocations = model.AllocationsNeeded,
AllowDockerImageChange = model.AllowDockerImageChange,
DefaultDockerImage = model.DefaultDockerImage,
Variables = model.Variables.Select(x => new StarVariable()
{
DefaultValue = x.DefaultValue,
Description = x.Description,
Filter = x.Filter,
Key = x.Key,
AllowEditing = x.AllowEdit,
AllowViewing = x.AllowView,
Type = StarVariableType.Text,
Name = x.DisplayName
}).ToList(),
DockerImages = model.DockerImages.Select(x => new StarDockerImage()
{
DisplayName = x.DisplayName,
AutoPulling = x.AutoPull,
Identifier = x.Name
}).ToList()
};
#region Convert parse configurations
var oldParseConfig = JsonSerializer.Deserialize<LegacyImageParseConfigModel[]>(model.ParseConfiguration,
new JsonSerializerOptions()
{
PropertyNameCaseInsensitive = true
}
);
ArgumentNullException.ThrowIfNull(oldParseConfig);
var newParseConfig = new List<ParseConfiguration>();
// Remap values
foreach (var config in oldParseConfig)
{
var parseConfiguration = new ParseConfiguration()
{
File = config.File,
Parser = Enum.TryParse(config.Type, true, out FileParsers parserType)
? parserType
: FileParsers.File
};
foreach (var option in config.Configuration)
{
parseConfiguration.Entries.Add(new ParseConfiguration.ParseConfigurationEntry()
{
Key = option.Key,
Value = option.Value
});
}
newParseConfig.Add(parseConfiguration);
}
star.ParseConfiguration = JsonSerializer.Serialize(newParseConfig);
#endregion
var finalStar = await StarRepository.Add(star);
return finalStar;
}
catch (Exception e)
{
Logger.LogError("An unhandled error occured while importing image: {e}", e);
throw new HttpApiException("An unhandled error occured while importing image", 400);
}
}
public async Task<Star> ImportEgg(string json)
{
// Create result
var star = new Star();
// Prepare json
var fixedJson = json;
fixedJson = fixedJson.Replace("\\/", "/");
var jsonDocument = JsonDocument.Parse(fixedJson);
var egg = jsonDocument.RootElement;
// Let's go :O
// Basic meta
star.Name = egg.GetProperty("name").GetString() ?? "Parse error";
star.Author = egg.GetProperty("author").GetString() ?? "Parse error";
// Start & Stop and Status
var configSection = egg.GetProperty("config");
star.StartupCommand = egg.GetProperty("startup").GetString() ?? "Parse error";
star.StopCommand = configSection.GetProperty("stop").GetString() ?? "Parse error";
var startupSectionJson = configSection.GetProperty("startup").GetString() ?? "{}";
var startupSectionDocument = JsonDocument.Parse(startupSectionJson);
var startupSection = startupSectionDocument.RootElement;
var doneProperty = startupSection.GetProperty("done");
if (doneProperty.ValueKind == JsonValueKind.Array)
star.OnlineDetection = doneProperty.Deserialize<string[]>()?.First() ?? "Parse error";
else
star.OnlineDetection = doneProperty.GetString() ?? "Parse error";
// Installation
var installationSection = egg.GetProperty("scripts").GetProperty("installation");
var shell = installationSection.GetProperty("entrypoint").GetString() ?? "bash";
star.InstallShell = shell.StartsWith("/") ? shell : $"/bin/{shell}";
star.InstallDockerImage = installationSection.GetProperty("container").GetString() ?? "Parse error";
star.InstallScript = installationSection.GetProperty("script").GetString() ?? "Parse error";
// Variables
var variables = egg.GetProperty("variables");
foreach (var variable in variables.EnumerateArray())
{
var starVariable = new StarVariable()
{
Name = variable.GetProperty("name").GetString() ?? "Parse error",
Description = variable.GetProperty("description").GetString() ?? "Parse error",
Key = variable.GetProperty("env_variable").GetString() ?? "Parse error",
DefaultValue = variable.GetProperty("default_value").GetString() ?? "Parse error",
Type = StarVariableType.Text
};
// Check if the provided value is an int or a boolean as both are apparently valid
if (variable.GetProperty("user_editable").ValueKind == JsonValueKind.Number)
{
starVariable.AllowEditing = variable.GetProperty("user_editable").GetInt32() == 1;
starVariable.AllowViewing = variable.GetProperty("user_viewable").GetInt32() == 1;
}
else
{
starVariable.AllowEditing = variable.GetProperty("user_editable").GetBoolean();
starVariable.AllowViewing = variable.GetProperty("user_viewable").GetBoolean();
}
star.Variables.Add(starVariable);
}
// Docker images
if (egg.TryGetProperty("image", out var imageProperty)) // Variant 1
{
star.DockerImages.Add(new StarDockerImage()
{
Identifier = imageProperty.GetString() ?? "Parse error",
DisplayName = imageProperty.GetString() ?? "Parse error",
AutoPulling = true
});
}
else if (egg.TryGetProperty("images", out var imagesProperty)) // Variant 2
{
foreach (var di in imagesProperty.EnumerateObject())
{
star.DockerImages.Add(new StarDockerImage()
{
DisplayName = di.Name,
Identifier = di.Value.GetString() ?? "Parse error",
AutoPulling = true
});
}
}
else if (egg.TryGetProperty("docker_images", out var dockerImages)) // Variant 3
{
foreach (var di in dockerImages.EnumerateObject())
{
star.DockerImages.Add(new StarDockerImage()
{
DisplayName = di.Name,
Identifier = di.Value.GetString() ?? "Parse error",
AutoPulling = true
});
}
}
// Parse configuration
var parseConfigurationJson = configSection.GetProperty("files").GetString() ?? "{}";
var parseConfigurationDocument = JsonDocument.Parse(parseConfigurationJson);
var parseConfiguration = parseConfigurationDocument.RootElement;
var resultPcs = new List<ParseConfiguration>();
foreach (var pConfig in parseConfiguration.EnumerateObject())
{
var pc = new ParseConfiguration()
{
File = pConfig.Name
};
var parser = pConfig.Value.GetProperty("parser").GetString() ?? "Parse error";
pc.Parser = Enum.TryParse(parser, true, out FileParsers fileParser) ? fileParser : FileParsers.File;
foreach (var pConfigFind in pConfig.Value.GetProperty("find").EnumerateObject())
{
var entry = new ParseConfiguration.ParseConfigurationEntry()
{
Key = pConfigFind.Name,
Value = pConfigFind.Value.GetString() ?? "Parse error"
};
// Fix up special variables
entry.Value = entry.Value.Replace("server.allocations.default.port", "SERVER_PORT");
entry.Value = entry.Value.Replace("server.build.default.port", "SERVER_PORT");
pc.Entries.Add(entry);
}
resultPcs.Add(pc);
}
star.ParseConfiguration = JsonSerializer.Serialize(resultPcs);
// Post parse fixes
// - Stop command signal
// Some weird eggs use ^^C in as a stop command, so we need to handle this as well
// because moonlight handles power signals correctly, wings does/did not
star.StopCommand = star.StopCommand.Replace("^^C", "^C");
// - Set moonlight native values
star.RequiredAllocations = 1;
star.Version = "Imported egg";
star.AllowDockerImageChange = true;
// Finally save it to the db
var finalStar = await StarRepository.Add(star);
return finalStar;
}
}

View File

@@ -1,56 +0,0 @@
using MoonCore.Extensions;
using Moonlight.ApiServer.Configuration;
using Moonlight.ApiServer.Models;
using Moonlight.ApiServer.Plugins;
using MoonlightServers.ApiServer.Database;
using MoonlightServers.ApiServer.Helpers;
using MoonlightServers.ApiServer.Implementations.ServerAuthFilters;
using MoonlightServers.ApiServer.Interfaces;
namespace MoonlightServers.ApiServer.Startup;
[PluginStartup]
public class PluginStartup : IPluginStartup
{
public Task BuildApplication(IServiceProvider serviceProvider, IHostApplicationBuilder builder)
{
// Scan the current plugin assembly for di services
builder.Services.AutoAddServices<PluginStartup>();
builder.Services.AddDbContext<ServersDataContext>();
// Configure authentication for the remote endpoints
builder.Services
.AddAuthentication()
.AddScheme<NodeAuthOptions, NodeAuthScheme>("nodeAuthentication", null);
var configuration = serviceProvider.GetRequiredService<AppConfiguration>();
if (configuration.Client.Enable)
{
builder.Services.AddSingleton(new FrontendConfigurationOption()
{
Scripts =
[
"js/XtermBlazor.min.js",
"js/addon-fit.js",
"js/moonlightServers.js"
],
Styles = ["css/XtermBlazor.min.css"]
});
}
// Add server auth filters
builder.Services.AddSingleton<IServerAuthorizationFilter, OwnerAuthFilter>();
builder.Services.AddScoped<IServerAuthorizationFilter, AdminAuthFilter>();
builder.Services.AddScoped<IServerAuthorizationFilter, ShareAuthFilter>();
return Task.CompletedTask;
}
public Task ConfigureApplication(IServiceProvider serviceProvider, IApplicationBuilder app)
=> Task.CompletedTask;
public Task ConfigureEndpoints(IServiceProvider serviceProvider, IEndpointRouteBuilder routeBuilder)
=> Task.CompletedTask;
}

View File

@@ -1,74 +0,0 @@
using MoonCore.Helpers;
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 RemoteData Remote { get; set; } = new();
public FilesData Files { get; set; } = new();
public ServerData Server { get; set; } = new();
public KestrelData Kestrel { get; set; } = new();
public class KestrelData
{
public int RequestBodySizeLimit { get; set; } = 25;
}
public class RemoteData
{
public string Url { get; set; }
}
public class DockerData
{
public string Uri { get; set; } = "unix:///var/run/docker.sock";
public DockerCredentialData[] Credentials { get; set; } = [];
public class DockerCredentialData
{
public string Domain { get; set; }
public string Username { get; set; }
public string Password { get; set; }
public string Email { get; set; }
}
}
public class SecurityData
{
public string Token { get; set; }
public string TokenId { get; set; }
}
public class StorageData
{
public string Volumes { get; set; } = PathBuilder.Dir("volumes");
public string VirtualDisks { get; set; } = PathBuilder.Dir("virtualDisks");
public string Backups { get; set; } = PathBuilder.Dir("backups");
public string Install { get; set; } = PathBuilder.Dir("install");
public VirtualDiskData VirtualDiskOptions { get; set; } = new();
}
public record VirtualDiskData
{
public string FileSystemType { get; set; } = "ext4";
public string E2FsckParameters { get; set; } = "-pf";
}
public class FilesData
{
public int UploadSizeLimit { get; set; } = 1024 * 2;
public int UploadChunkSize { get; set; } = 20;
}
public class ServerData
{
public int WaitBeforeKillSeconds { get; set; } = 30;
public int TmpFsSize { get; set; } = 100;
public float MemoryOverheadMultiplier { get; set; } = 0.05f;
public int ConsoleMessageCacheLimit { get; set; } = 250;
}
}

View File

@@ -0,0 +1,6 @@
namespace MoonlightServers.Daemon.Configuration;
public class DockerOptions
{
public string SocketUri { get; set; } = "unix:///var/run/docker.sock";
}

View File

@@ -0,0 +1,7 @@
namespace MoonlightServers.Daemon.Configuration;
public class LocalStorageOptions
{
public string InstallPath { get; set; } = "/data/install";
public string RuntimePath { get; set; } = "/data/runtime";
}

View File

@@ -1,10 +0,0 @@
namespace MoonlightServers.Daemon.Enums;
public enum ServerState
{
Offline = 0,
Starting = 1,
Online = 2,
Stopping = 3,
Installing = 4
}

View File

@@ -1,14 +0,0 @@
namespace MoonlightServers.Daemon.Enums;
public enum ServerTrigger
{
Start = 0,
Stop = 1,
Restart = 2,
Kill = 3,
Reinstall = 4,
NotifyOnline = 5,
NotifyRuntimeContainerDied = 6,
NotifyInstallationContainerDied = 7,
NotifyInternalError = 8
}

View File

@@ -1,361 +0,0 @@
using Docker.DotNet.Models;
using Mono.Unix.Native;
using MoonCore.Helpers;
using MoonlightServers.Daemon.Configuration;
using MoonlightServers.Daemon.Models.Cache;
using MoonlightServers.DaemonShared.PanelSide.Http.Responses;
namespace MoonlightServers.Daemon.Extensions;
public static class ServerConfigurationExtensions
{
public static ServerConfiguration ToServerConfiguration(this ServerDataResponse response)
{
return new ServerConfiguration()
{
Id = response.Id,
StartupCommand = response.StartupCommand,
Allocations = response.Allocations.Select(y => new ServerConfiguration.AllocationConfiguration()
{
IpAddress = y.IpAddress,
Port = y.Port
}).ToArray(),
Variables = response.Variables,
OnlineDetection = response.OnlineDetection,
DockerImage = response.DockerImage,
UseVirtualDisk = response.UseVirtualDisk,
Bandwidth = response.Bandwidth,
Cpu = response.Cpu,
Disk = response.Disk,
Memory = response.Memory,
StopCommand = response.StopCommand
};
}
public static CreateContainerParameters ToRuntimeCreateParameters(
this ServerConfiguration configuration,
AppConfiguration appConfiguration,
string hostPath,
string containerName
)
{
var parameters = configuration.ToSharedCreateParameters(appConfiguration);
#region Security
parameters.HostConfig.CapDrop = new List<string>()
{
"setpcap", "mknod", "audit_write", "net_raw", "dac_override",
"fowner", "fsetid", "net_bind_service", "sys_chroot", "setfcap"
};
parameters.HostConfig.ReadonlyRootfs = true;
parameters.HostConfig.SecurityOpt = new List<string>()
{
"no-new-privileges"
};
#endregion
#region Name
parameters.Name = containerName;
parameters.Hostname = containerName;
#endregion
#region Docker Image
parameters.Image = configuration.DockerImage;
#endregion
#region Environment
parameters.Env = configuration.ToEnvironmentVariables()
.Select(x => $"{x.Key}={x.Value}")
.ToList();
#endregion
#region Working Dir
parameters.WorkingDir = "/home/container";
#endregion
#region User
var userId = Syscall.getuid(); // TODO: Extract to external service?
if (userId == 0)
userId = 998;
parameters.User = $"{userId}:{userId}";
/*
if (userId == 0)
{
// We are running as root, so we need to run the container as another user and chown the files when we make changes
parameters.User = $"998:998";
}
else
{
// We are not running as root, so we start the container as the same user,
// as we are not able to chown the container content to a different user
parameters.User = $"{userId}:{userId}";
}*/
#endregion
#region Mounts
parameters.HostConfig.Mounts = new List<Mount>();
parameters.HostConfig.Mounts.Add(new()
{
Source = hostPath,
Target = "/home/container",
ReadOnly = false,
Type = "bind"
});
#endregion
#region Port Bindings
if (true) // TODO: Add network toggle
{
parameters.ExposedPorts = new Dictionary<string, EmptyStruct>();
parameters.HostConfig.PortBindings = new Dictionary<string, IList<PortBinding>>();
foreach (var allocation in configuration.Allocations)
{
parameters.ExposedPorts.Add($"{allocation.Port}/tcp", new());
parameters.ExposedPorts.Add($"{allocation.Port}/udp", new());
parameters.HostConfig.PortBindings.Add($"{allocation.Port}/tcp", new List<PortBinding>
{
new()
{
HostPort = allocation.Port.ToString(),
HostIP = allocation.IpAddress
}
});
parameters.HostConfig.PortBindings.Add($"{allocation.Port}/udp", new List<PortBinding>
{
new()
{
HostPort = allocation.Port.ToString(),
HostIP = allocation.IpAddress
}
});
}
}
#endregion
return parameters;
}
public static CreateContainerParameters ToInstallationCreateParameters(
this ServerConfiguration configuration,
AppConfiguration appConfiguration,
string runtimeHostPath,
string installationHostPath,
string containerName,
string installDockerImage,
string installShell
)
{
var parameters = configuration.ToSharedCreateParameters(appConfiguration);
// - Name
parameters.Name = containerName;
parameters.Hostname = containerName;
// - Image
parameters.Image = installDockerImage;
// - Env
parameters.Env = configuration
.ToEnvironmentVariables()
.Select(x => $"{x.Key}={x.Value}")
.ToList();
// -- Working directory
parameters.WorkingDir = "/mnt/server";
// - User
// Note: Some images might not work if we set a user here
var userId = Syscall.getuid();
// If we are root, we are able to change owner permissions after the installation
// so we run the installation as root, otherwise we need to run it as our current user,
// so we are able to access the files created by the installer
if (userId == 0)
parameters.User = "0:0";
else
parameters.User = $"{userId}:{userId}";
// -- Mounts
parameters.HostConfig.Mounts = new List<Mount>();
parameters.HostConfig.Mounts.Add(new()
{
Source = runtimeHostPath,
Target = "/mnt/server",
ReadOnly = false,
Type = "bind"
});
parameters.HostConfig.Mounts.Add(new()
{
Source = installationHostPath,
Target = "/mnt/install",
ReadOnly = false,
Type = "bind"
});
parameters.Cmd = [installShell, "/mnt/install/install.sh"];
return parameters;
}
private static CreateContainerParameters ToSharedCreateParameters(this ServerConfiguration configuration,
AppConfiguration appConfiguration)
{
var parameters = new CreateContainerParameters()
{
HostConfig = new()
};
#region Input, output & error streams and tty
parameters.Tty = true;
parameters.AttachStderr = true;
parameters.AttachStdin = true;
parameters.AttachStdout = true;
parameters.OpenStdin = true;
#endregion
#region CPU
parameters.HostConfig.CPUQuota = configuration.Cpu * 1000;
parameters.HostConfig.CPUPeriod = 100000;
parameters.HostConfig.CPUShares = 1024;
#endregion
#region Memory & Swap
var memoryLimit = configuration.Memory;
// The overhead multiplier gives the container a little bit more memory to prevent crashes
var memoryOverhead = memoryLimit + (memoryLimit * appConfiguration.Server.MemoryOverheadMultiplier);
long swapLimit = -1;
/*
// If swap is enabled globally and not disabled on this server, set swap
if (!configuration.Limits.DisableSwap && config.Server.EnableSwap)
swapLimit = (long)(memoryOverhead + memoryOverhead * config.Server.SwapMultiplier);
co
*/
// Finalize limits by converting and updating the host config
parameters.HostConfig.Memory = ByteConverter.FromMegaBytes((long)memoryOverhead, 1000).Bytes;
parameters.HostConfig.MemoryReservation = ByteConverter.FromMegaBytes(memoryLimit, 1000).Bytes;
parameters.HostConfig.MemorySwap =
swapLimit == -1 ? swapLimit : ByteConverter.FromMegaBytes(swapLimit, 1000).Bytes;
#endregion
#region Misc Limits
// -- Other limits
parameters.HostConfig.BlkioWeight = 100;
//container.HostConfig.PidsLimit = configuration.Limits.PidsLimit;
parameters.HostConfig.OomKillDisable = true; //!configuration.Limits.EnableOomKill;
#endregion
#region DNS
// TODO: Read hosts dns settings?
parameters.HostConfig.DNS = /*config.Docker.DnsServers.Any() ? config.Docker.DnsServers :*/ new List<string>()
{
"1.1.1.1",
"9.9.9.9"
};
#endregion
#region Tmpfs
parameters.HostConfig.Tmpfs = new Dictionary<string, string>()
{
{ "/tmp", $"rw,exec,nosuid,size={appConfiguration.Server.TmpFsSize}M" }
};
#endregion
#region Logging
parameters.HostConfig.LogConfig = new()
{
Type = "json-file", // We need to use this provider, as the GetLogs endpoint needs it
Config = new Dictionary<string, string>()
};
#endregion
#region Labels
parameters.Labels = new Dictionary<string, string>();
parameters.Labels.Add("Software", "Moonlight-Panel");
parameters.Labels.Add("ServerId", configuration.Id.ToString());
#endregion
return parameters;
}
public static Dictionary<string, string> ToEnvironmentVariables(this ServerConfiguration configuration)
{
var result = new Dictionary<string, string>
{
//TODO: Add timezone, add server ip
{ "STARTUP", configuration.StartupCommand },
{ "SERVER_MEMORY", configuration.Memory.ToString() }
};
if (configuration.Allocations.Length > 0)
{
var mainAllocation = configuration.Allocations.First();
result.Add("SERVER_IP", mainAllocation.IpAddress);
result.Add("SERVER_PORT", mainAllocation.Port.ToString());
}
// Handle allocation variables
var i = 1;
foreach (var allocation in configuration.Allocations)
{
result.Add($"ML_PORT_{i}", allocation.Port.ToString());
i++;
}
// Copy variables as env vars
foreach (var variable in configuration.Variables)
result.Add(variable.Key, variable.Value);
return result;
}
}

View File

@@ -1,66 +0,0 @@
using Stateless;
namespace MoonlightServers.Daemon.Extensions;
public static class StateConfigurationExtensions
{
public static StateMachine<TState, TTrigger>.StateConfiguration OnExitFrom<TState, TTrigger>(
this StateMachine<TState, TTrigger>.StateConfiguration configuration, TTrigger trigger, Action entryAction
)
{
configuration.OnExit(transition =>
{
if(!transition.Trigger!.Equals(trigger))
return;
entryAction.Invoke();
});
return configuration;
}
public static StateMachine<TState, TTrigger>.StateConfiguration OnExitFrom<TState, TTrigger>(
this StateMachine<TState, TTrigger>.StateConfiguration configuration, TTrigger trigger, Action<StateMachine<TState, TTrigger>.Transition> entryAction
)
{
configuration.OnExit(transition =>
{
if(!transition.Trigger!.Equals(trigger))
return;
entryAction.Invoke(transition);
});
return configuration;
}
public static StateMachine<TState, TTrigger>.StateConfiguration OnExitFromAsync<TState, TTrigger>(
this StateMachine<TState, TTrigger>.StateConfiguration configuration, TTrigger trigger, Func<Task> entryAction
)
{
configuration.OnExitAsync(transition =>
{
if(!transition.Trigger!.Equals(trigger))
return Task.CompletedTask;
return entryAction.Invoke();
});
return configuration;
}
public static StateMachine<TState, TTrigger>.StateConfiguration OnExitFromAsync<TState, TTrigger>(
this StateMachine<TState, TTrigger>.StateConfiguration configuration, TTrigger trigger, Func<StateMachine<TState, TTrigger>.Transition, Task> entryAction
)
{
configuration.OnExitAsync(transition =>
{
if(!transition.Trigger!.Equals(trigger))
return Task.CompletedTask;
return entryAction.Invoke(transition);
});
return configuration;
}
}

View File

@@ -0,0 +1,76 @@
using Microsoft.Extensions.Logging.Abstractions;
using Microsoft.Extensions.Logging.Console;
namespace MoonlightServers.Daemon.Helpers;
public class AppConsoleFormatter : ConsoleFormatter
{
private const string TimestampColor = "\e[38;2;148;148;148m";
private const string CategoryColor = "\e[38;2;198;198;198m";
private const string MessageColor = "\e[38;2;255;255;255m";
private const string Bold = "\e[1m";
// Pre-computed ANSI color codes for each log level
private const string CriticalColor = "\e[38;2;255;0;0m";
private const string ErrorColor = "\e[38;2;255;0;0m";
private const string WarningColor = "\e[38;2;215;215;0m";
private const string InfoColor = "\e[38;2;135;215;255m";
private const string DebugColor = "\e[38;2;198;198;198m";
private const string TraceColor = "\e[38;2;68;68;68m";
public AppConsoleFormatter() : base(nameof(AppConsoleFormatter))
{
}
public override void Write<TState>(
in LogEntry<TState> logEntry,
IExternalScopeProvider? scopeProvider,
TextWriter textWriter)
{
var message = logEntry.Formatter(logEntry.State, logEntry.Exception);
// Timestamp
textWriter.Write(TimestampColor);
textWriter.Write(DateTime.Now.ToString("dd.MM.yy HH:mm:ss"));
textWriter.Write(' ');
// Log level with color and bold
var (levelText, levelColor) = GetLevelInfo(logEntry.LogLevel);
textWriter.Write(levelColor);
textWriter.Write(Bold);
textWriter.Write(levelText);
textWriter.Write(' ');
// Category
textWriter.Write(CategoryColor);
textWriter.Write(logEntry.Category);
// Message
textWriter.Write(MessageColor);
textWriter.Write(": ");
textWriter.Write(message);
// Exception
if (logEntry.Exception != null)
{
textWriter.Write(MessageColor);
textWriter.WriteLine(logEntry.Exception.ToString());
}
else
textWriter.WriteLine();
}
private static (string text, string color) GetLevelInfo(LogLevel logLevel)
{
return logLevel switch
{
LogLevel.Critical => ("CRIT", CriticalColor),
LogLevel.Error => ("ERRO", ErrorColor),
LogLevel.Warning => ("WARN", WarningColor),
LogLevel.Information => ("INFO", InfoColor),
LogLevel.Debug => ("DEBG", DebugColor),
LogLevel.Trace => ("TRCE", TraceColor),
_ => ("NONE", "")
};
}
}

View File

@@ -1,263 +0,0 @@
using System.Runtime.InteropServices;
using Mono.Unix.Native;
using MoonCore.Attributes;
using MoonCore.Helpers;
using MoonlightServers.Daemon.Models;
namespace MoonlightServers.Daemon.Helpers;
[Singleton]
public class HostSystemHelper
{
private readonly ILogger<HostSystemHelper> Logger;
public HostSystemHelper(ILogger<HostSystemHelper> logger)
{
Logger = logger;
}
public string GetOsName()
{
if (RuntimeInformation.IsOSPlatform(OSPlatform.Windows))
{
// Windows platform detected
var osVersion = Environment.OSVersion.Version;
return $"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 "Linux (unknown release)";
var release = releaseRaw
.Replace("PRETTY_NAME=", "")
.Replace("\"", "");
if (string.IsNullOrEmpty(release))
return "Linux (unknown release)";
return release;
}
if (RuntimeInformation.IsOSPlatform(OSPlatform.OSX))
{
// macOS platform detected
var osVersion = Environment.OSVersion.Version;
return $"Shitty macOS {osVersion.Major}.{osVersion.Minor}.{osVersion.Build}";
}
// Unknown platform
return "Unknown";
}
#region CPU Usage
public async Task<CpuUsageDetails> GetCpuUsage()
{
var result = new CpuUsageDetails();
var perCoreUsages = new List<double>();
// Initial read
var (cpuLastStats, cpuLastSums) = await ReadAllCpuStats();
await Task.Delay(1000);
// Second read
var (cpuNowStats, cpuNowSums) = await ReadAllCpuStats();
for (var i = 0; i < cpuNowStats.Length; i++)
{
var cpuDelta = cpuNowSums[i] - cpuLastSums[i];
var cpuIdle = cpuNowStats[i][3] - cpuLastStats[i][3];
var cpuUsed = cpuDelta - cpuIdle;
var usage = 100.0 * cpuUsed / cpuDelta;
if (i == 0)
result.OverallUsage = usage;
else
perCoreUsages.Add(usage);
}
result.PerCoreUsage = perCoreUsages.ToArray();
// Get model name
var cpuInfoLines = await File.ReadAllLinesAsync("/proc/cpuinfo");
var modelLine = cpuInfoLines.FirstOrDefault(x => x.StartsWith("model name"));
result.Model = modelLine?.Split(":")[1].Trim() ?? "N/A";
return result;
}
private async Task<(long[][] cpuStatsList, long[] cpuSums)> ReadAllCpuStats()
{
var lines = await File.ReadAllLinesAsync("/proc/stat");
lines = lines.Where(line => line.StartsWith("cpu"))
.TakeWhile(line => line.StartsWith("cpu")) // Ensures only CPU lines are read
.ToArray();
var statsList = new List<long[]>();
var sumList = new List<long>();
foreach (var line in lines)
{
var parts = line.Split(' ', StringSplitOptions.RemoveEmptyEntries)
.Skip(1) // Skip the "cpu" label
.ToArray();
var cpuTimes = parts
.Select(long.Parse)
.ToArray();
var sum = cpuTimes.Sum();
statsList.Add(cpuTimes);
sumList.Add(sum);
}
return (statsList.ToArray(), sumList.ToArray());
}
#endregion
#region Memory
public async Task ClearCachedMemory()
{
await File.WriteAllTextAsync("/proc/sys/vm/drop_caches", "3");
}
public async Task<MemoryUsageDetails> GetMemoryUsage()
{
var details = new MemoryUsageDetails();
var lines = await File.ReadAllLinesAsync("/proc/meminfo");
foreach (var line in lines)
{
// We want to ignore all non kilobyte values
if (!line.Contains("kB"))
continue;
// Split the line up so we can extract the id and the value
// to map it to the model field
var parts = line.Split(":");
var id = parts[0];
var value = parts[1]
.Replace("kB", "")
.Trim();
if (!long.TryParse(value, out var longValue))
continue;
var bytes = ByteConverter.FromKiloBytes(longValue).Bytes;
switch (id)
{
case "MemTotal":
details.Total = bytes;
break;
case "MemFree":
details.Free = bytes;
break;
case "MemAvailable":
details.Available = bytes;
break;
case "Cached":
details.Cached = bytes;
break;
case "SwapTotal":
details.SwapTotal = bytes;
break;
case "SwapFree":
details.SwapFree = bytes;
break;
}
}
return details;
}
#endregion
#region Disks
public async Task<DiskUsageDetails[]> GetDiskUsages()
{
var details = new List<DiskUsageDetails>();
// First we need to check which mounts actually exist
var diskDevices = new Dictionary<string, string>();
string[] ignoredMounts = ["/boot/efi", "/boot"];
var mountLines = await File.ReadAllLinesAsync("/proc/mounts");
foreach (var mountLine in mountLines)
{
var parts = mountLine.Split(" ");
var device = parts[0];
var mountedAt = parts[1];
// We only want to handle mounted physical devices
if (!device.StartsWith("/dev/"))
continue;
// Ignore certain mounts which we dont want to show
if (ignoredMounts.Contains(mountedAt))
continue;
diskDevices.Add(device, mountedAt);
}
foreach (var diskMount in diskDevices)
{
var device = diskMount.Key;
var mount = diskMount.Value;
var statusCode = Syscall.statvfs(mount, out var statvfs);
if (statusCode != 0)
{
var error = Stdlib.GetLastError();
Logger.LogError(
"An error occured while checking disk stats for mount {mount}: {error}",
mount,
error
);
continue;
}
// Source: https://man7.org/linux/man-pages/man3/statvfs.3.html
var detail = new DiskUsageDetails()
{
Device = device,
MountPath = mount,
DiskTotal = statvfs.f_blocks * statvfs.f_frsize,
DiskFree = statvfs.f_bfree * statvfs.f_frsize,
InodesTotal = statvfs.f_files,
InodesFree = statvfs.f_ffree
};
details.Add(detail);
}
return details.ToArray();
}
#endregion
}

View File

@@ -1,340 +0,0 @@
using System.Text;
using ICSharpCode.SharpZipLib.GZip;
using ICSharpCode.SharpZipLib.Tar;
using ICSharpCode.SharpZipLib.Zip;
using Mono.Unix.Native;
using MoonCore.Unix.Exceptions;
using MoonCore.Unix.SecureFs;
using MoonlightServers.DaemonShared.DaemonSide.Http.Responses.Servers;
using MoonlightServers.DaemonShared.Enums;
namespace MoonlightServers.Daemon.Helpers;
public class ServerFileSystem
{
private readonly SecureFileSystem FileSystem;
public ServerFileSystem(SecureFileSystem fileSystem)
{
FileSystem = fileSystem;
}
public Task<ServerFileSystemResponse[]> List(string inputPath)
{
var path = Normalize(inputPath);
var entries = FileSystem.ReadDir(path);
IEnumerable<SecureFsEntry> entryQuery = entries;
// Filter all lost+found directories on the root of the file system
// to hide the folder shown by virtual disk volumes
if (string.IsNullOrEmpty(inputPath) || inputPath == "/")
entryQuery = entryQuery.Where(x => x.Name != "lost+found");
var result = entryQuery
.Select(x => new ServerFileSystemResponse()
{
Name = x.Name,
IsFile = x.IsFile,
Size = x.Size,
UpdatedAt = x.LastChanged,
CreatedAt = x.CreatedAt
})
.ToArray();
return Task.FromResult(result);
}
public Task Move(string inputOldPath, string inputNewPath)
{
var oldPath = Normalize(inputOldPath);
var newPath = Normalize(inputNewPath);
FileSystem.Rename(oldPath, newPath);
return Task.CompletedTask;
}
public Task Delete(string inputPath)
{
var path = Normalize(inputPath);
FileSystem.RemoveAll(path);
return Task.CompletedTask;
}
public Task Mkdir(string inputPath)
{
var path = Normalize(inputPath);
FileSystem.MkdirAll(path, FilePermissions.ACCESSPERMS);
return Task.CompletedTask;
}
public Task CreateChunk(string inputPath, long totalSize, long positionToSkip, Stream chunkStream)
{
var path = Normalize(inputPath);
var parentDirectory = Path.GetDirectoryName(path);
if (!string.IsNullOrEmpty(parentDirectory) && parentDirectory != "/")
FileSystem.MkdirAll(parentDirectory, FilePermissions.ACCESSPERMS);
FileSystem.OpenFileWrite(path, fileStream =>
{
if (fileStream.Length != totalSize)
fileStream.SetLength(totalSize);
fileStream.Position = positionToSkip;
chunkStream.CopyTo(fileStream);
fileStream.Flush();
}, OpenFlags.O_CREAT | OpenFlags.O_RDWR); // We use these custom flags to ensure we aren't overwriting the file
return Task.CompletedTask;
}
public Task Create(string inputPath, Stream dataStream)
{
var path = Normalize(inputPath);
var parentDirectory = Path.GetDirectoryName(path);
if (!string.IsNullOrEmpty(parentDirectory) && parentDirectory != "/")
FileSystem.MkdirAll(parentDirectory, FilePermissions.ACCESSPERMS);
FileSystem.OpenFileWrite(path, stream =>
{
stream.Position = 0;
dataStream.CopyTo(stream);
stream.Flush();
});
return Task.CompletedTask;
}
public Task Read(string inputPath, Func<Stream, Task> onHandle)
{
var path = Normalize(inputPath);
FileSystem.OpenFileRead(path, stream =>
{
// No try catch here because the safe fs abstraction already handles every error occuring in the handle
onHandle.Invoke(stream).Wait();
});
return Task.CompletedTask;
}
#region Compression
public Task Compress(string[] itemsInput, string destinationInput, CompressType type)
{
var destination = Normalize(destinationInput);
var items = itemsInput.Select(Normalize);
if (type == CompressType.Zip)
{
FileSystem.OpenFileWrite(destination, stream =>
{
using var zipStream = new ZipOutputStream(stream);
foreach (var item in items)
AddItemToZip(item, zipStream);
zipStream.Flush();
stream.Flush();
zipStream.Close();
});
}
else if (type == CompressType.TarGz)
{
FileSystem.OpenFileWrite(destination, stream =>
{
using var gzStream = new GZipOutputStream(stream);
using var tarStream = new TarOutputStream(gzStream, Encoding.UTF8);
foreach (var item in items)
AddItemToTar(item, tarStream);
tarStream.Flush();
gzStream.Flush();
stream.Flush();
tarStream.Close();
gzStream.Close();
});
}
return Task.CompletedTask;
}
public Task Decompress(string pathInput, string destinationInput, CompressType type)
{
var path = Normalize(pathInput);
var destination = Normalize(destinationInput);
if (type == CompressType.Zip)
{
FileSystem.OpenFileRead(path, fileStream =>
{
var zipInputStream = new ZipInputStream(fileStream);
ExtractZip(zipInputStream, destination);
});
}
else if (type == CompressType.TarGz)
{
FileSystem.OpenFileRead(path, fileStream =>
{
var gzInputStream = new GZipInputStream(fileStream);
var zipInputStream = new TarInputStream(gzInputStream, Encoding.UTF8);
ExtractTar(zipInputStream, destination);
});
}
return Task.CompletedTask;
}
private void AddItemToZip(string path, ZipOutputStream outputStream)
{
var item = FileSystem.Stat(path);
if (item.IsDirectory)
{
var contents = FileSystem.ReadDir(path);
foreach (var content in contents)
{
AddItemToZip(
Path.Combine(path, content.Name),
outputStream
);
}
}
else
{
var entry = new ZipEntry(path)
{
Size = item.Size,
DateTime = item.LastChanged
};
outputStream.PutNextEntry(entry);
FileSystem.OpenFileRead(path, stream => { stream.CopyTo(outputStream); });
outputStream.CloseEntry();
}
}
private void AddItemToTar(string path, TarOutputStream outputStream)
{
var item = FileSystem.Stat(path);
if (item.IsDirectory)
{
var contents = FileSystem.ReadDir(path);
foreach (var content in contents)
{
AddItemToTar(
Path.Combine(path, content.Name),
outputStream
);
}
}
else
{
var entry = TarEntry.CreateTarEntry(path);
entry.Name = path;
entry.Size = item.Size;
entry.ModTime = item.LastChanged;
outputStream.PutNextEntry(entry);
FileSystem.OpenFileRead(path, stream =>
{
stream.CopyTo(outputStream);
});
outputStream.CloseEntry();
}
}
private void ExtractZip(ZipInputStream inputStream, string destination)
{
while (true)
{
var entry = inputStream.GetNextEntry();
if(entry == null)
break;
if(entry.IsDirectory)
continue;
var fileDestination = Path.Combine(destination, entry.Name);
var parentDirectory = Path.GetDirectoryName(fileDestination);
if (!string.IsNullOrEmpty(parentDirectory) && parentDirectory != "/")
FileSystem.MkdirAll(parentDirectory, FilePermissions.ACCESSPERMS);
FileSystem.OpenFileWrite(fileDestination, stream =>
{
stream.Position = 0;
inputStream.CopyTo(stream);
stream.Flush();
}); // This will override the file if it exists
}
}
private void ExtractTar(TarInputStream inputStream, string destination)
{
while (true)
{
var entry = inputStream.GetNextEntry();
if(entry == null)
break;
if(entry.IsDirectory)
continue;
var fileDestination = Path.Combine(destination, entry.Name);
var parentDirectory = Path.GetDirectoryName(fileDestination);
if (!string.IsNullOrEmpty(parentDirectory) && parentDirectory != "/")
FileSystem.MkdirAll(parentDirectory, FilePermissions.ACCESSPERMS);
FileSystem.OpenFileWrite(fileDestination, stream =>
{
stream.Position = 0;
inputStream.CopyTo(stream);
stream.Flush();
}); // This will override the file if it exists
}
}
#endregion
private string Normalize(string path)
{
return path
.Replace("//", "/")
.Replace("..", "")
.TrimStart('/');
}
}

View File

@@ -1,8 +0,0 @@
using Microsoft.AspNetCore.Authentication;
namespace MoonlightServers.Daemon.Helpers;
public class TokenAuthOptions : AuthenticationSchemeOptions
{
public string Token { get; set; }
}

View File

@@ -1,49 +0,0 @@
using System.Security.Claims;
using System.Text.Encodings.Web;
using Microsoft.AspNetCore.Authentication;
using Microsoft.Extensions.Options;
namespace MoonlightServers.Daemon.Helpers;
public class TokenAuthScheme : AuthenticationHandler<TokenAuthOptions>
{
public TokenAuthScheme(IOptionsMonitor<TokenAuthOptions> options, ILoggerFactory logger, UrlEncoder encoder,
ISystemClock clock) : base(options, logger, encoder, clock)
{
}
public TokenAuthScheme(IOptionsMonitor<TokenAuthOptions> options, ILoggerFactory logger, UrlEncoder encoder) : base(
options, logger, encoder)
{
}
protected override Task<AuthenticateResult> HandleAuthenticateAsync()
{
if (!Request.Headers.ContainsKey("Authorization"))
return Task.FromResult(AuthenticateResult.NoResult());
var authHeaderValue = Request.Headers["Authorization"].FirstOrDefault();
if (string.IsNullOrEmpty(authHeaderValue))
return Task.FromResult(AuthenticateResult.NoResult());
if (!authHeaderValue.Contains("Bearer "))
return Task.FromResult(AuthenticateResult.NoResult());
var providedToken = authHeaderValue
.Replace("Bearer ", "")
.Trim();
if (providedToken != Options.Token)
return Task.FromResult(AuthenticateResult.NoResult());
return Task.FromResult(AuthenticateResult.Success(
new AuthenticationTicket(
new ClaimsPrincipal(
new ClaimsIdentity("token")
),
"token"
)
));
}
}

View File

@@ -1,46 +0,0 @@
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

@@ -1,44 +0,0 @@
using Microsoft.AspNetCore.Authorization;
using Microsoft.AspNetCore.Mvc;
using MoonCore.Exceptions;
using MoonlightServers.Daemon.ServerSystem.SubSystems;
using MoonlightServers.Daemon.Services;
namespace MoonlightServers.Daemon.Http.Controllers.Servers;
[ApiController]
[Route("api/servers/download")]
[Authorize(AuthenticationSchemes = "accessToken", Policy = "serverDownload")]
public class DownloadController : Controller
{
private readonly ServerService ServerService;
public DownloadController(ServerService serverService)
{
ServerService = serverService;
}
[HttpGet]
public async Task Download()
{
var serverId = int.Parse(User.Claims.First(x => x.Type == "serverId").Value);
var path = User.Claims.First(x => x.Type == "path").Value;
var server = ServerService.Find(serverId);
if (server == null)
throw new HttpApiException("No server with this id found", 404);
var storageSubSystem = server.GetRequiredSubSystem<StorageSubSystem>();
var fileSystem = await storageSubSystem.GetFileSystem();
await fileSystem.Read(
path,
async dataStream =>
{
await Results.File(dataStream).ExecuteAsync(HttpContext);
}
);
}
}

View File

@@ -1,91 +0,0 @@
using Microsoft.AspNetCore.Authorization;
using Microsoft.AspNetCore.Mvc;
using MoonCore.Exceptions;
using MoonlightServers.Daemon.Helpers;
using MoonlightServers.Daemon.ServerSystem.SubSystems;
using MoonlightServers.Daemon.Services;
using MoonlightServers.DaemonShared.DaemonSide.Http.Requests;
using MoonlightServers.DaemonShared.DaemonSide.Http.Responses.Servers;
namespace MoonlightServers.Daemon.Http.Controllers.Servers;
[Authorize]
[ApiController]
[Route("api/servers")]
public class ServerFileSystemController : Controller
{
private readonly ServerService ServerService;
public ServerFileSystemController(ServerService serverService)
{
ServerService = serverService;
}
[HttpGet("{id:int}/files/list")]
public async Task<ServerFileSystemResponse[]> List([FromRoute] int id, [FromQuery] string path = "")
{
var fileSystem = await GetFileSystemById(id);
return await fileSystem.List(path);
}
[HttpPost("{id:int}/files/move")]
public async Task Move([FromRoute] int id, [FromQuery] string oldPath, [FromQuery] string newPath)
{
var fileSystem = await GetFileSystemById(id);
await fileSystem.Move(oldPath, newPath);
}
[HttpDelete("{id:int}/files/delete")]
public async Task Delete([FromRoute] int id, [FromQuery] string path)
{
var fileSystem = await GetFileSystemById(id);
await fileSystem.Delete(path);
}
[HttpPost("{id:int}/files/mkdir")]
public async Task Mkdir([FromRoute] int id, [FromQuery] string path)
{
var fileSystem = await GetFileSystemById(id);
await fileSystem.Mkdir(path);
}
[HttpPost("{id:int}/files/compress")]
public async Task Compress([FromRoute] int id, [FromBody] ServerFilesCompressRequest request)
{
var fileSystem = await GetFileSystemById(id);
await fileSystem.Compress(
request.Items,
request.Destination,
request.Type
);
}
[HttpPost("{id:int}/files/decompress")]
public async Task Decompress([FromRoute] int id, [FromBody] ServerFilesDecompressRequest request)
{
var fileSystem = await GetFileSystemById(id);
await fileSystem.Decompress(
request.Path,
request.Destination,
request.Type
);
}
private async Task<ServerFileSystem> GetFileSystemById(int serverId)
{
var server = ServerService.Find(serverId);
if (server == null)
throw new HttpApiException("No server with this id found", 404);
var storageSubSystem = server.GetRequiredSubSystem<StorageSubSystem>();
return await storageSubSystem.GetFileSystem();
}
}

View File

@@ -1,65 +0,0 @@
using Microsoft.AspNetCore.Authorization;
using Microsoft.AspNetCore.Mvc;
using MoonCore.Exceptions;
using MoonlightServers.Daemon.Enums;
using MoonlightServers.Daemon.Services;
using ServerTrigger = MoonlightServers.Daemon.ServerSystem.ServerTrigger;
namespace MoonlightServers.Daemon.Http.Controllers.Servers;
[Authorize]
[ApiController]
[Route("api/servers")]
public class ServerPowerController : Controller
{
private readonly ServerService ServerService;
public ServerPowerController(ServerService serverService)
{
ServerService = serverService;
}
[HttpPost("{serverId:int}/start")]
public async Task Start(int serverId)
{
var server = ServerService.Find(serverId);
if (server == null)
throw new HttpApiException("No server with this id found", 404);
await server.Trigger(ServerTrigger.Start);
}
[HttpPost("{serverId:int}/stop")]
public async Task Stop(int serverId)
{
var server = ServerService.Find(serverId);
if (server == null)
throw new HttpApiException("No server with this id found", 404);
await server.Trigger(ServerTrigger.Stop);
}
[HttpPost("{serverId:int}/install")]
public async Task Install(int serverId)
{
var server = ServerService.Find(serverId);
if (server == null)
throw new HttpApiException("No server with this id found", 404);
await server.Trigger(ServerTrigger.Install);
}
[HttpPost("{serverId:int}/kill")]
public async Task Kill(int serverId)
{
var server = ServerService.Find(serverId);
if (server == null)
throw new HttpApiException("No server with this id found", 404);
await server.Trigger(ServerTrigger.Kill);
}
}

View File

@@ -1,88 +0,0 @@
using Microsoft.AspNetCore.Authorization;
using Microsoft.AspNetCore.Mvc;
using MoonCore.Exceptions;
using MoonlightServers.Daemon.ServerSystem.SubSystems;
using MoonlightServers.Daemon.Services;
using MoonlightServers.DaemonShared.DaemonSide.Http.Responses.Servers;
using MoonlightServers.DaemonShared.Enums;
namespace MoonlightServers.Daemon.Http.Controllers.Servers;
[Authorize]
[ApiController]
[Route("api/servers")]
public class ServersController : Controller
{
private readonly ServerService ServerService;
public ServersController(ServerService serverService)
{
ServerService = serverService;
}
[HttpPost("{serverId:int}/sync")]
public async Task Sync([FromRoute] int serverId)
{
await ServerService.Sync(serverId);
}
[HttpDelete("{serverId:int}")]
public async Task Delete([FromRoute] int serverId)
{
await ServerService.Delete(serverId);
}
[HttpGet("{serverId:int}/status")]
public Task<ServerStatusResponse> GetStatus([FromRoute] int serverId)
{
var server = ServerService.Find(serverId);
if (server == null)
throw new HttpApiException("No server with this id found", 404);
var result = new ServerStatusResponse()
{
State = (ServerState)server.StateMachine.State
};
return Task.FromResult(result);
}
[HttpGet("{serverId:int}/logs")]
public async Task<ServerLogsResponse> GetLogs([FromRoute] int serverId)
{
var server = ServerService.Find(serverId);
if (server == null)
throw new HttpApiException("No server with this id found", 404);
var consoleSubSystem = server.GetRequiredSubSystem<ConsoleSubSystem>();
var messages = await consoleSubSystem.RetrieveCache();
return new ServerLogsResponse()
{
Messages = messages
};
}
[HttpGet("{serverId:int}/stats")]
public Task<ServerStatsResponse> GetStats([FromRoute] int serverId)
{
var server = ServerService.Find(serverId);
if (server == null)
throw new HttpApiException("No server with this id found", 404);
var statsSubSystem = server.GetRequiredSubSystem<StatsSubSystem>();
return Task.FromResult<ServerStatsResponse>(new()
{
CpuUsage = statsSubSystem.CurrentStats.CpuUsage,
MemoryUsage = statsSubSystem.CurrentStats.MemoryUsage,
NetworkRead = statsSubSystem.CurrentStats.NetworkRead,
NetworkWrite = statsSubSystem.CurrentStats.NetworkWrite,
IoRead = statsSubSystem.CurrentStats.IoRead,
IoWrite = statsSubSystem.CurrentStats.IoWrite
});
}
}

View File

@@ -1,85 +0,0 @@
using Microsoft.AspNetCore.Authorization;
using Microsoft.AspNetCore.Mvc;
using MoonCore.Exceptions;
using MoonCore.Helpers;
using MoonlightServers.Daemon.Configuration;
using MoonlightServers.Daemon.ServerSystem.SubSystems;
using MoonlightServers.Daemon.Services;
namespace MoonlightServers.Daemon.Http.Controllers.Servers;
[ApiController]
[Route("api/servers/upload")]
[Authorize(AuthenticationSchemes = "accessToken", Policy = "serverUpload")]
public class UploadController : Controller
{
private readonly AppConfiguration Configuration;
private readonly ServerService ServerService;
public UploadController(
ServerService serverService,
AppConfiguration configuration
)
{
ServerService = serverService;
Configuration = configuration;
}
[HttpPost]
public async Task Upload(
[FromQuery] long totalSize,
[FromQuery] int chunkId,
[FromQuery] string path
)
{
var chunkSize = ByteConverter.FromMegaBytes(Configuration.Files.UploadChunkSize).Bytes;
var uploadLimit = ByteConverter.FromMegaBytes(Configuration.Files.UploadSizeLimit).Bytes;
#region File validation
if (Request.Form.Files.Count != 1)
throw new HttpApiException("You need to provide exactly one file", 400);
var file = Request.Form.Files[0];
if (file.Length > chunkSize)
throw new HttpApiException("The provided data exceeds the chunk size limit", 400);
#endregion
var serverId = int.Parse(User.Claims.First(x => x.Type == "serverId").Value);
#region Chunk calculation and validation
if(totalSize > uploadLimit)
throw new HttpApiException("Invalid upload request: Exceeding upload limit", 400);
var chunks = totalSize / chunkSize;
chunks += totalSize % chunkSize > 0 ? 1 : 0;
if (chunkId > chunks)
throw new HttpApiException("Invalid chunk id: Out of bounds", 400);
var positionToSkipTo = chunkSize * chunkId;
#endregion
var server = ServerService.Find(serverId);
if (server == null)
throw new HttpApiException("No server with this id found", 404);
var storageSubSystem = server.GetRequiredSubSystem<StorageSubSystem>();
var fileSystem = await storageSubSystem.GetFileSystem();
var dataStream = file.OpenReadStream();
await fileSystem.CreateChunk(
path,
totalSize,
positionToSkipTo,
dataStream
);
}
}

View File

@@ -1,54 +0,0 @@
using Microsoft.AspNetCore.Authorization;
using Microsoft.AspNetCore.Mvc;
using MoonlightServers.Daemon.Helpers;
using MoonlightServers.DaemonShared.DaemonSide.Http.Responses.Statistics;
namespace MoonlightServers.Daemon.Http.Controllers.Statistics;
[Authorize]
[ApiController]
[Route("api/statistics")]
public class StatisticsController : Controller
{
private readonly HostSystemHelper HostSystemHelper;
public StatisticsController(HostSystemHelper hostSystemHelper)
{
HostSystemHelper = hostSystemHelper;
}
[HttpGet]
public async Task<StatisticsResponse> Get()
{
var response = new StatisticsResponse();
var cpuUsage = await HostSystemHelper.GetCpuUsage();
response.Cpu.Model = cpuUsage.Model;
response.Cpu.Usage = cpuUsage.OverallUsage;
response.Cpu.UsagePerCore = cpuUsage.PerCoreUsage;
var memoryUsage = await HostSystemHelper.GetMemoryUsage();
response.Memory.Available = memoryUsage.Available;
response.Memory.Cached = memoryUsage.Cached;
response.Memory.Free = memoryUsage.Free;
response.Memory.Total = memoryUsage.Total;
response.Memory.SwapTotal = memoryUsage.SwapTotal;
response.Memory.SwapFree = memoryUsage.SwapFree;
var diskDetails = await HostSystemHelper.GetDiskUsages();
response.Disks = diskDetails.Select(x => new StatisticsResponse.DiskData()
{
Device = x.Device,
MountPath = x.MountPath,
DiskFree = x.DiskFree,
DiskTotal = x.DiskTotal,
InodesFree = x.InodesFree,
InodesTotal = x.InodesTotal
}).ToArray();
return response;
}
}

View File

@@ -1,38 +0,0 @@
using Microsoft.AspNetCore.Authorization;
using Microsoft.AspNetCore.Mvc;
using MoonlightServers.Daemon.Services;
using MoonlightServers.DaemonShared.DaemonSide.Http.Responses.Statistics;
namespace MoonlightServers.Daemon.Http.Controllers.Statistics;
// This controller hosts endpoints for the statistics for the docker environment
[Authorize]
[ApiController]
[Route("api/statistics/docker")]
public class StatisticsDockerController : Controller
{
private readonly DockerInfoService DockerInfoService;
public StatisticsDockerController(DockerInfoService dockerInfoService)
{
DockerInfoService = dockerInfoService;
}
[HttpGet]
public async Task<StatisticsDockerResponse> Get()
{
var usage = await DockerInfoService.GetDataUsage();
return new StatisticsDockerResponse
{
Version = await DockerInfoService.GetDockerVersion(),
ContainersReclaimable = usage.Containers.Reclaimable,
ContainersUsed = usage.Containers.Used,
BuildCacheReclaimable = usage.BuildCache.Reclaimable,
BuildCacheUsed = usage.BuildCache.Used,
ImagesUsed = usage.Images.Used,
ImagesReclaimable = usage.Images.Reclaimable
};
}
}

View File

@@ -1,56 +0,0 @@
using System.Diagnostics;
using Microsoft.AspNetCore.Authorization;
using Microsoft.AspNetCore.Mvc;
using MoonlightServers.Daemon.Services;
using MoonlightServers.DaemonShared.DaemonSide.Http.Responses.Sys;
namespace MoonlightServers.Daemon.Http.Controllers.Sys;
[Authorize]
[ApiController]
[Route("api/system/status")]
public class SystemStatusController : Controller
{
private readonly RemoteService RemoteService;
public SystemStatusController(RemoteService remoteService)
{
RemoteService = remoteService;
}
public async Task<SystemStatusResponse> Get()
{
SystemStatusResponse response;
var sw = new Stopwatch();
sw.Start();
try
{
await RemoteService.GetStatus();
sw.Stop();
response = new()
{
TripSuccess = true,
TripTime = sw.Elapsed,
Version = "2.1.0" // TODO: Set global
};
}
catch (Exception e)
{
sw.Stop();
response = new()
{
TripError = e.Message,
TripTime = sw.Elapsed,
TripSuccess = false,
Version = "2.1.0" // TODO: Set global
};
}
return response;
}
}

View File

@@ -1,28 +0,0 @@
using Microsoft.AspNetCore.Authorization;
using Microsoft.AspNetCore.SignalR;
namespace MoonlightServers.Daemon.Http.Hubs;
[Authorize(AuthenticationSchemes = "accessToken", Policy = "serverWebsocket")]
public class ServerWebSocketHub : Hub
{
private readonly ILogger<ServerWebSocketHub> Logger;
public ServerWebSocketHub(ILogger<ServerWebSocketHub> logger)
{
Logger = logger;
}
public override async Task OnConnectedAsync()
{
// The policies validated already the type and the token so we can assume we are authenticated
// and just start adding ourselves into the desired group
var serverId = Context.User!.Claims.First(x => x.Type == "serverId").Value;
await Groups.AddToGroupAsync(
Context.ConnectionId,
serverId
);
}
}

View File

@@ -1,30 +0,0 @@
namespace MoonlightServers.Daemon.Models.Cache;
public class ServerConfiguration
{
public int Id { get; set; }
// Limits
public int Cpu { get; set; }
public int Memory { get; set; }
public int Disk { get; set; }
public int Bandwidth { get; set; }
public bool UseVirtualDisk { get; set; }
// Start, Stop & Status
public string StartupCommand { get; set; }
public string StopCommand { get; set; }
public string OnlineDetection { get; set; }
// Container
public string DockerImage { get; set; }
public AllocationConfiguration[] Allocations { get; set; }
public Dictionary<string, string> Variables { get; set; }
public struct AllocationConfiguration
{
public string IpAddress { get; set; }
public int Port { get; set; }
}
}

View File

@@ -1,8 +0,0 @@
namespace MoonlightServers.Daemon.Models;
public class CpuUsageDetails
{
public string Model { get; set; }
public double OverallUsage { get; set; }
public double[] PerCoreUsage { get; set; }
}

View File

@@ -1,11 +0,0 @@
namespace MoonlightServers.Daemon.Models;
public class DiskUsageDetails
{
public string Device { get; set; }
public string MountPath { get; set; }
public ulong DiskTotal { get; set; }
public ulong DiskFree { get; set; }
public ulong InodesTotal { get; set; }
public ulong InodesFree { get; set; }
}

Some files were not shown because too many files have changed in this diff Show More