Recreated plugin with new project template. Started implementing server system daemon
This commit is contained in:
49
.gitignore
vendored
49
.gitignore
vendored
@@ -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
|
||||
|
||||
6
Hosts/MoonlightServers.Api.Host/Api.props
Normal file
6
Hosts/MoonlightServers.Api.Host/Api.props
Normal file
@@ -0,0 +1,6 @@
|
||||
<Project>
|
||||
<ItemGroup>
|
||||
<!-- Put your plugin references here -->
|
||||
<!-- E.g. <PackageReference Include="MoonlightServers.Api" Version="2.1.0" /> -->
|
||||
</ItemGroup>
|
||||
</Project>
|
||||
@@ -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>
|
||||
9
Hosts/MoonlightServers.Api.Host/Program.cs
Normal file
9
Hosts/MoonlightServers.Api.Host/Program.cs
Normal file
@@ -0,0 +1,9 @@
|
||||
using Moonlight.Api;
|
||||
using SimplePlugin.Generated;
|
||||
|
||||
var plugins = PluginRegistry
|
||||
.Modules
|
||||
.OfType<MoonlightPlugin>()
|
||||
.ToArray();
|
||||
|
||||
await StartupHandler.RunAsync(args, plugins);
|
||||
@@ -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"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
23
Hosts/MoonlightServers.Api.Host/appsettings.Example.json
Normal file
23
Hosts/MoonlightServers.Api.Host/appsettings.Example.json
Normal 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"
|
||||
}
|
||||
}
|
||||
}
|
||||
6
Hosts/MoonlightServers.Frontend.Host/Frontend.props
Normal file
6
Hosts/MoonlightServers.Frontend.Host/Frontend.props
Normal file
@@ -0,0 +1,6 @@
|
||||
<Project>
|
||||
<ItemGroup>
|
||||
<!-- Put your plugin references here -->
|
||||
<!-- E.g. <PackageReference Include="MoonlightServers.Frontend" Version="2.1.0" /> -->
|
||||
</ItemGroup>
|
||||
</Project>
|
||||
@@ -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>
|
||||
9
Hosts/MoonlightServers.Frontend.Host/Program.cs
Normal file
9
Hosts/MoonlightServers.Frontend.Host/Program.cs
Normal file
@@ -0,0 +1,9 @@
|
||||
using Moonlight.Frontend;
|
||||
using SimplePlugin.Generated;
|
||||
|
||||
var plugins = PluginRegistry
|
||||
.Modules
|
||||
.OfType<MoonlightPlugin>()
|
||||
.ToArray();
|
||||
|
||||
await StartupHandler.RunAsync(args, plugins);
|
||||
@@ -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;
|
||||
19
Hosts/MoonlightServers.Frontend.Host/Styles/package.json
Normal file
19
Hosts/MoonlightServers.Frontend.Host/Styles/package.json
Normal 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"
|
||||
}
|
||||
}
|
||||
@@ -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
|
||||
};
|
||||
31
Hosts/MoonlightServers.Frontend.Host/Styles/styles.css
Normal file
31
Hosts/MoonlightServers.Frontend.Host/Styles/styles.css
Normal 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;
|
||||
}
|
||||
}
|
||||
132
Hosts/MoonlightServers.Frontend.Host/Styles/theme.css
Normal file
132
Hosts/MoonlightServers.Frontend.Host/Styles/theme.css
Normal 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);
|
||||
}
|
||||
108
Hosts/MoonlightServers.Frontend.Host/wwwroot/index.html
Normal file
108
Hosts/MoonlightServers.Frontend.Host/wwwroot/index.html
Normal 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>
|
||||
21
MoonlightServers.Api/Http/Controllers/FormController.cs
Normal file
21
MoonlightServers.Api/Http/Controllers/FormController.cs
Normal 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)
|
||||
};
|
||||
}
|
||||
}
|
||||
36
MoonlightServers.Api/MoonlightServers.Api.csproj
Normal file
36
MoonlightServers.Api/MoonlightServers.Api.csproj
Normal 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>
|
||||
21
MoonlightServers.Api/Startup.cs
Normal file
21
MoonlightServers.Api/Startup.cs
Normal 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);
|
||||
});
|
||||
}
|
||||
}
|
||||
@@ -1,10 +0,0 @@
|
||||
using MoonCore.PluginFramework;
|
||||
using Moonlight.ApiServer.Plugins;
|
||||
|
||||
namespace MoonlightServers.ApiServer.Runtime;
|
||||
|
||||
[PluginLoader]
|
||||
public partial class DevPluginLoader : IPluginStartup
|
||||
{
|
||||
|
||||
}
|
||||
@@ -1,31 +0,0 @@
|
||||
<Project Sdk="Microsoft.NET.Sdk.Web">
|
||||
|
||||
<PropertyGroup>
|
||||
<TargetFramework>net9.0</TargetFramework>
|
||||
<Nullable>enable</Nullable>
|
||||
<ImplicitUsings>enable</ImplicitUsings>
|
||||
<NoDefaultLaunchSettingsFile>True</NoDefaultLaunchSettingsFile>
|
||||
</PropertyGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<ProjectReference Include="..\MoonlightServers.ApiServer\MoonlightServers.ApiServer.csproj"/>
|
||||
<ProjectReference Include="..\MoonlightServers.Frontend.Runtime\MoonlightServers.Frontend.Runtime.csproj" />
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<PackageReference Include="Microsoft.AspNetCore.Components.WebAssembly.Server" Version="9.0.9" />
|
||||
<PackageReference Include="MoonCore.PluginFramework" Version="1.0.9"/>
|
||||
<PackageReference Include="MoonCore.PluginFramework.Generator" Version="1.0.3"/>
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<Content Update="Properties\launchSettings.json">
|
||||
<CopyToPublishDirectory>Never</CopyToPublishDirectory>
|
||||
</Content>
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<Folder Include="wwwroot\" />
|
||||
</ItemGroup>
|
||||
|
||||
</Project>
|
||||
@@ -1,34 +0,0 @@
|
||||
using Moonlight.ApiServer.Configuration;
|
||||
using Moonlight.ApiServer.Startup;
|
||||
using MoonlightServers.ApiServer.Runtime;
|
||||
|
||||
var pluginLoader = new DevPluginLoader();
|
||||
pluginLoader.Initialize();
|
||||
|
||||
var builder = WebApplication.CreateBuilder(args);
|
||||
|
||||
builder.AddMoonlight(pluginLoader.Instances);
|
||||
|
||||
var app = builder.Build();
|
||||
|
||||
app.UseMoonlight(pluginLoader.Instances);
|
||||
|
||||
// Add frontend
|
||||
var configuration = AppConfiguration.CreateEmpty();
|
||||
builder.Configuration.Bind(configuration);
|
||||
|
||||
// Handle setup of wasm app hosting in the runtime
|
||||
// so the Moonlight.ApiServer doesn't need the wasm package
|
||||
if (configuration.Frontend.EnableHosting)
|
||||
{
|
||||
if (app.Environment.IsDevelopment())
|
||||
app.UseWebAssemblyDebugging();
|
||||
|
||||
app.UseBlazorFrameworkFiles();
|
||||
app.UseStaticFiles();
|
||||
}
|
||||
|
||||
app.MapMoonlight(pluginLoader.Instances);
|
||||
|
||||
|
||||
await app.RunAsync();
|
||||
@@ -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": ""
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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; }
|
||||
}
|
||||
@@ -1,21 +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; }
|
||||
}
|
||||
@@ -1,27 +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; }
|
||||
}
|
||||
@@ -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; }
|
||||
}
|
||||
@@ -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; }
|
||||
}
|
||||
@@ -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; }
|
||||
}
|
||||
@@ -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; }
|
||||
}
|
||||
@@ -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; }
|
||||
}
|
||||
@@ -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; }
|
||||
}
|
||||
@@ -1,529 +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("20250922091731_RecreatedModelsInNewSchema")]
|
||||
partial class RecreatedModelsInNewSchema
|
||||
{
|
||||
/// <inheritdoc />
|
||||
protected override void BuildTargetModel(ModelBuilder modelBuilder)
|
||||
{
|
||||
#pragma warning disable 612, 618
|
||||
modelBuilder
|
||||
.HasDefaultSchema("servers")
|
||||
.HasAnnotation("ProductVersion", "9.0.9")
|
||||
.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("Allocations", "servers");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("MoonlightServers.ApiServer.Database.Entities.Node", b =>
|
||||
{
|
||||
b.Property<int>("Id")
|
||||
.ValueGeneratedOnAdd()
|
||||
.HasColumnType("integer");
|
||||
|
||||
NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property<int>("Id"));
|
||||
|
||||
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("Nodes", "servers");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("MoonlightServers.ApiServer.Database.Entities.Server", b =>
|
||||
{
|
||||
b.Property<int>("Id")
|
||||
.ValueGeneratedOnAdd()
|
||||
.HasColumnType("integer");
|
||||
|
||||
NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property<int>("Id"));
|
||||
|
||||
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.HasKey("Id");
|
||||
|
||||
b.HasIndex("NodeId");
|
||||
|
||||
b.HasIndex("StarId");
|
||||
|
||||
b.ToTable("Servers", "servers");
|
||||
});
|
||||
|
||||
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("ServerBackups", "servers");
|
||||
});
|
||||
|
||||
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("ServerShares", "servers");
|
||||
});
|
||||
|
||||
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("ServerVariables", "servers");
|
||||
});
|
||||
|
||||
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("Stars", "servers");
|
||||
});
|
||||
|
||||
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("StarDockerImages", "servers");
|
||||
});
|
||||
|
||||
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("StarVariables", "servers");
|
||||
});
|
||||
|
||||
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("ServerShares", "servers");
|
||||
|
||||
b1.ToJson("Content");
|
||||
|
||||
b1.WithOwner()
|
||||
.HasForeignKey("ServerShareId");
|
||||
|
||||
b1.OwnsMany("MoonlightServers.ApiServer.Models.ServerShareContent+SharePermission", "Permissions", b2 =>
|
||||
{
|
||||
b2.Property<int>("ServerShareContentServerShareId")
|
||||
.HasColumnType("integer");
|
||||
|
||||
b2.Property<int>("__synthesizedOrdinal")
|
||||
.ValueGeneratedOnAdd()
|
||||
.HasColumnType("integer");
|
||||
|
||||
b2.Property<string>("Identifier")
|
||||
.IsRequired()
|
||||
.HasColumnType("text");
|
||||
|
||||
b2.Property<int>("Level")
|
||||
.HasColumnType("integer");
|
||||
|
||||
b2.HasKey("ServerShareContentServerShareId", "__synthesizedOrdinal");
|
||||
|
||||
b2.ToTable("ServerShares", "servers");
|
||||
|
||||
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
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,353 +0,0 @@
|
||||
using System;
|
||||
using Microsoft.EntityFrameworkCore.Migrations;
|
||||
using Npgsql.EntityFrameworkCore.PostgreSQL.Metadata;
|
||||
|
||||
#nullable disable
|
||||
|
||||
namespace MoonlightServers.ApiServer.Database.Migrations
|
||||
{
|
||||
/// <inheritdoc />
|
||||
public partial class RecreatedModelsInNewSchema : Migration
|
||||
{
|
||||
/// <inheritdoc />
|
||||
protected override void Up(MigrationBuilder migrationBuilder)
|
||||
{
|
||||
migrationBuilder.EnsureSchema(
|
||||
name: "servers");
|
||||
|
||||
migrationBuilder.CreateTable(
|
||||
name: "Nodes",
|
||||
schema: "servers",
|
||||
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),
|
||||
TokenId = 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)
|
||||
},
|
||||
constraints: table =>
|
||||
{
|
||||
table.PrimaryKey("PK_Nodes", x => x.Id);
|
||||
});
|
||||
|
||||
migrationBuilder.CreateTable(
|
||||
name: "Stars",
|
||||
schema: "servers",
|
||||
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_Stars", x => x.Id);
|
||||
});
|
||||
|
||||
migrationBuilder.CreateTable(
|
||||
name: "Servers",
|
||||
schema: "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)
|
||||
},
|
||||
constraints: table =>
|
||||
{
|
||||
table.PrimaryKey("PK_Servers", x => x.Id);
|
||||
table.ForeignKey(
|
||||
name: "FK_Servers_Nodes_NodeId",
|
||||
column: x => x.NodeId,
|
||||
principalSchema: "servers",
|
||||
principalTable: "Nodes",
|
||||
principalColumn: "Id",
|
||||
onDelete: ReferentialAction.Cascade);
|
||||
table.ForeignKey(
|
||||
name: "FK_Servers_Stars_StarId",
|
||||
column: x => x.StarId,
|
||||
principalSchema: "servers",
|
||||
principalTable: "Stars",
|
||||
principalColumn: "Id",
|
||||
onDelete: ReferentialAction.Cascade);
|
||||
});
|
||||
|
||||
migrationBuilder.CreateTable(
|
||||
name: "StarDockerImages",
|
||||
schema: "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),
|
||||
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_StarDockerImages", x => x.Id);
|
||||
table.ForeignKey(
|
||||
name: "FK_StarDockerImages_Stars_StarId",
|
||||
column: x => x.StarId,
|
||||
principalSchema: "servers",
|
||||
principalTable: "Stars",
|
||||
principalColumn: "Id",
|
||||
onDelete: ReferentialAction.Cascade);
|
||||
});
|
||||
|
||||
migrationBuilder.CreateTable(
|
||||
name: "StarVariables",
|
||||
schema: "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),
|
||||
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_StarVariables", x => x.Id);
|
||||
table.ForeignKey(
|
||||
name: "FK_StarVariables_Stars_StarId",
|
||||
column: x => x.StarId,
|
||||
principalSchema: "servers",
|
||||
principalTable: "Stars",
|
||||
principalColumn: "Id",
|
||||
onDelete: ReferentialAction.Cascade);
|
||||
});
|
||||
|
||||
migrationBuilder.CreateTable(
|
||||
name: "Allocations",
|
||||
schema: "servers",
|
||||
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_Allocations", x => x.Id);
|
||||
table.ForeignKey(
|
||||
name: "FK_Allocations_Nodes_NodeId",
|
||||
column: x => x.NodeId,
|
||||
principalSchema: "servers",
|
||||
principalTable: "Nodes",
|
||||
principalColumn: "Id",
|
||||
onDelete: ReferentialAction.Cascade);
|
||||
table.ForeignKey(
|
||||
name: "FK_Allocations_Servers_ServerId",
|
||||
column: x => x.ServerId,
|
||||
principalSchema: "servers",
|
||||
principalTable: "Servers",
|
||||
principalColumn: "Id");
|
||||
});
|
||||
|
||||
migrationBuilder.CreateTable(
|
||||
name: "ServerBackups",
|
||||
schema: "servers",
|
||||
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_ServerBackups", x => x.Id);
|
||||
table.ForeignKey(
|
||||
name: "FK_ServerBackups_Servers_ServerId",
|
||||
column: x => x.ServerId,
|
||||
principalSchema: "servers",
|
||||
principalTable: "Servers",
|
||||
principalColumn: "Id");
|
||||
});
|
||||
|
||||
migrationBuilder.CreateTable(
|
||||
name: "ServerShares",
|
||||
schema: "servers",
|
||||
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_ServerShares", x => x.Id);
|
||||
table.ForeignKey(
|
||||
name: "FK_ServerShares_Servers_ServerId",
|
||||
column: x => x.ServerId,
|
||||
principalSchema: "servers",
|
||||
principalTable: "Servers",
|
||||
principalColumn: "Id",
|
||||
onDelete: ReferentialAction.Cascade);
|
||||
});
|
||||
|
||||
migrationBuilder.CreateTable(
|
||||
name: "ServerVariables",
|
||||
schema: "servers",
|
||||
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_ServerVariables", x => x.Id);
|
||||
table.ForeignKey(
|
||||
name: "FK_ServerVariables_Servers_ServerId",
|
||||
column: x => x.ServerId,
|
||||
principalSchema: "servers",
|
||||
principalTable: "Servers",
|
||||
principalColumn: "Id",
|
||||
onDelete: ReferentialAction.Cascade);
|
||||
});
|
||||
|
||||
migrationBuilder.CreateIndex(
|
||||
name: "IX_Allocations_NodeId",
|
||||
schema: "servers",
|
||||
table: "Allocations",
|
||||
column: "NodeId");
|
||||
|
||||
migrationBuilder.CreateIndex(
|
||||
name: "IX_Allocations_ServerId",
|
||||
schema: "servers",
|
||||
table: "Allocations",
|
||||
column: "ServerId");
|
||||
|
||||
migrationBuilder.CreateIndex(
|
||||
name: "IX_ServerBackups_ServerId",
|
||||
schema: "servers",
|
||||
table: "ServerBackups",
|
||||
column: "ServerId");
|
||||
|
||||
migrationBuilder.CreateIndex(
|
||||
name: "IX_Servers_NodeId",
|
||||
schema: "servers",
|
||||
table: "Servers",
|
||||
column: "NodeId");
|
||||
|
||||
migrationBuilder.CreateIndex(
|
||||
name: "IX_Servers_StarId",
|
||||
schema: "servers",
|
||||
table: "Servers",
|
||||
column: "StarId");
|
||||
|
||||
migrationBuilder.CreateIndex(
|
||||
name: "IX_ServerShares_ServerId",
|
||||
schema: "servers",
|
||||
table: "ServerShares",
|
||||
column: "ServerId");
|
||||
|
||||
migrationBuilder.CreateIndex(
|
||||
name: "IX_ServerVariables_ServerId",
|
||||
schema: "servers",
|
||||
table: "ServerVariables",
|
||||
column: "ServerId");
|
||||
|
||||
migrationBuilder.CreateIndex(
|
||||
name: "IX_StarDockerImages_StarId",
|
||||
schema: "servers",
|
||||
table: "StarDockerImages",
|
||||
column: "StarId");
|
||||
|
||||
migrationBuilder.CreateIndex(
|
||||
name: "IX_StarVariables_StarId",
|
||||
schema: "servers",
|
||||
table: "StarVariables",
|
||||
column: "StarId");
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
protected override void Down(MigrationBuilder migrationBuilder)
|
||||
{
|
||||
migrationBuilder.DropTable(
|
||||
name: "Allocations",
|
||||
schema: "servers");
|
||||
|
||||
migrationBuilder.DropTable(
|
||||
name: "ServerBackups",
|
||||
schema: "servers");
|
||||
|
||||
migrationBuilder.DropTable(
|
||||
name: "ServerShares",
|
||||
schema: "servers");
|
||||
|
||||
migrationBuilder.DropTable(
|
||||
name: "ServerVariables",
|
||||
schema: "servers");
|
||||
|
||||
migrationBuilder.DropTable(
|
||||
name: "StarDockerImages",
|
||||
schema: "servers");
|
||||
|
||||
migrationBuilder.DropTable(
|
||||
name: "StarVariables",
|
||||
schema: "servers");
|
||||
|
||||
migrationBuilder.DropTable(
|
||||
name: "Servers",
|
||||
schema: "servers");
|
||||
|
||||
migrationBuilder.DropTable(
|
||||
name: "Nodes",
|
||||
schema: "servers");
|
||||
|
||||
migrationBuilder.DropTable(
|
||||
name: "Stars",
|
||||
schema: "servers");
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,526 +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
|
||||
.HasDefaultSchema("servers")
|
||||
.HasAnnotation("ProductVersion", "9.0.9")
|
||||
.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("Allocations", "servers");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("MoonlightServers.ApiServer.Database.Entities.Node", b =>
|
||||
{
|
||||
b.Property<int>("Id")
|
||||
.ValueGeneratedOnAdd()
|
||||
.HasColumnType("integer");
|
||||
|
||||
NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property<int>("Id"));
|
||||
|
||||
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("Nodes", "servers");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("MoonlightServers.ApiServer.Database.Entities.Server", b =>
|
||||
{
|
||||
b.Property<int>("Id")
|
||||
.ValueGeneratedOnAdd()
|
||||
.HasColumnType("integer");
|
||||
|
||||
NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property<int>("Id"));
|
||||
|
||||
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.HasKey("Id");
|
||||
|
||||
b.HasIndex("NodeId");
|
||||
|
||||
b.HasIndex("StarId");
|
||||
|
||||
b.ToTable("Servers", "servers");
|
||||
});
|
||||
|
||||
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("ServerBackups", "servers");
|
||||
});
|
||||
|
||||
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("ServerShares", "servers");
|
||||
});
|
||||
|
||||
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("ServerVariables", "servers");
|
||||
});
|
||||
|
||||
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("Stars", "servers");
|
||||
});
|
||||
|
||||
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("StarDockerImages", "servers");
|
||||
});
|
||||
|
||||
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("StarVariables", "servers");
|
||||
});
|
||||
|
||||
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("ServerShares", "servers");
|
||||
|
||||
b1.ToJson("Content");
|
||||
|
||||
b1.WithOwner()
|
||||
.HasForeignKey("ServerShareId");
|
||||
|
||||
b1.OwnsMany("MoonlightServers.ApiServer.Models.ServerShareContent+SharePermission", "Permissions", b2 =>
|
||||
{
|
||||
b2.Property<int>("ServerShareContentServerShareId")
|
||||
.HasColumnType("integer");
|
||||
|
||||
b2.Property<int>("__synthesizedOrdinal")
|
||||
.ValueGeneratedOnAdd()
|
||||
.HasColumnType("integer");
|
||||
|
||||
b2.Property<string>("Identifier")
|
||||
.IsRequired()
|
||||
.HasColumnType("text");
|
||||
|
||||
b2.Property<int>("Level")
|
||||
.HasColumnType("integer");
|
||||
|
||||
b2.HasKey("ServerShareContentServerShareId", "__synthesizedOrdinal");
|
||||
|
||||
b2.ToTable("ServerShares", "servers");
|
||||
|
||||
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
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,70 +0,0 @@
|
||||
using Microsoft.EntityFrameworkCore;
|
||||
using Moonlight.ApiServer.Configuration;
|
||||
using MoonlightServers.ApiServer.Database.Entities;
|
||||
using MoonlightServers.ApiServer.Models;
|
||||
|
||||
namespace MoonlightServers.ApiServer.Database;
|
||||
|
||||
public class ServersDataContext : DbContext
|
||||
{
|
||||
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; }
|
||||
|
||||
private readonly AppConfiguration Configuration;
|
||||
private readonly string Schema = "servers";
|
||||
|
||||
public ServersDataContext(AppConfiguration configuration)
|
||||
{
|
||||
Configuration = configuration;
|
||||
}
|
||||
|
||||
protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder)
|
||||
{
|
||||
if(optionsBuilder.IsConfigured)
|
||||
return;
|
||||
|
||||
var database = Configuration.Database;
|
||||
|
||||
var connectionString = $"Host={database.Host};" +
|
||||
$"Port={database.Port};" +
|
||||
$"Database={database.Database};" +
|
||||
$"Username={database.Username};" +
|
||||
$"Password={database.Password}";
|
||||
|
||||
optionsBuilder.UseNpgsql(connectionString, builder =>
|
||||
{
|
||||
builder.MigrationsHistoryTable("MigrationsHistory", Schema);
|
||||
});
|
||||
}
|
||||
|
||||
protected override void OnModelCreating(ModelBuilder modelBuilder)
|
||||
{
|
||||
modelBuilder.Model.SetDefaultSchema(Schema);
|
||||
|
||||
base.OnModelCreating(modelBuilder);
|
||||
|
||||
#region Shares
|
||||
|
||||
modelBuilder.Ignore<ServerShareContent>();
|
||||
modelBuilder.Ignore<ServerShareContent.SharePermission>();
|
||||
|
||||
modelBuilder.Entity<ServerShare>(builder =>
|
||||
{
|
||||
builder.OwnsOne(x => x.Content, navigationBuilder =>
|
||||
{
|
||||
navigationBuilder.ToJson();
|
||||
|
||||
navigationBuilder.OwnsMany(x => x.Permissions);
|
||||
});
|
||||
});
|
||||
|
||||
#endregion
|
||||
}
|
||||
}
|
||||
@@ -1,19 +0,0 @@
|
||||
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
|
||||
};
|
||||
}
|
||||
}
|
||||
@@ -1,8 +0,0 @@
|
||||
using Microsoft.AspNetCore.Authentication;
|
||||
|
||||
namespace MoonlightServers.ApiServer.Helpers;
|
||||
|
||||
public class NodeAuthOptions : AuthenticationSchemeOptions
|
||||
{
|
||||
|
||||
}
|
||||
@@ -1,78 +0,0 @@
|
||||
using System.Security.Claims;
|
||||
using System.Text.Encodings.Web;
|
||||
using Microsoft.AspNetCore.Authentication;
|
||||
using Microsoft.EntityFrameworkCore;
|
||||
using Microsoft.Extensions.DependencyInjection;
|
||||
using Microsoft.Extensions.Logging;
|
||||
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"
|
||||
)
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -1,237 +0,0 @@
|
||||
using Microsoft.AspNetCore.Authorization;
|
||||
using Microsoft.AspNetCore.Mvc;
|
||||
using Microsoft.EntityFrameworkCore;
|
||||
using MoonCore.Common;
|
||||
using MoonCore.Extended.Abstractions;
|
||||
using MoonlightServers.ApiServer.Database.Entities;
|
||||
using MoonlightServers.ApiServer.Mappers;
|
||||
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/{nodeId:int}/allocations")]
|
||||
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]
|
||||
[Authorize(Policy = "permissions:admin.servers.nodes.get")]
|
||||
public async Task<ActionResult<CountedData<NodeAllocationResponse>>> GetAsync(
|
||||
[FromRoute] int nodeId,
|
||||
[FromQuery] int startIndex,
|
||||
[FromQuery] int count
|
||||
)
|
||||
{
|
||||
if (count > 100)
|
||||
return Problem("Only 100 items can be fetched at a time", statusCode: 400);
|
||||
|
||||
var totalCount = await AllocationRepository
|
||||
.Get()
|
||||
.CountAsync(x => x.Node.Id == nodeId);
|
||||
|
||||
var allocations = await AllocationRepository
|
||||
.Get()
|
||||
.OrderBy(x => x.Id)
|
||||
.Skip(startIndex)
|
||||
.Take(count)
|
||||
.Where(x => x.Node.Id == nodeId)
|
||||
.AsNoTracking()
|
||||
.ProjectToAdminResponse()
|
||||
.ToArrayAsync();
|
||||
|
||||
return new CountedData<NodeAllocationResponse>()
|
||||
{
|
||||
Items = allocations,
|
||||
TotalCount = totalCount
|
||||
};
|
||||
}
|
||||
|
||||
[HttpGet("{id:int}")]
|
||||
[Authorize(Policy = "permissions:admin.servers.nodes.get")]
|
||||
public async Task<ActionResult<NodeAllocationResponse>> GetSingleAsync([FromRoute] int nodeId, [FromRoute] int id)
|
||||
{
|
||||
var allocation = await AllocationRepository
|
||||
.Get()
|
||||
.Where(x => x.Node.Id == nodeId)
|
||||
.AsNoTracking()
|
||||
.ProjectToAdminResponse()
|
||||
.FirstOrDefaultAsync(x => x.Id == id);
|
||||
|
||||
if (allocation == null)
|
||||
return Problem("No allocation with that id found", statusCode: 400);
|
||||
|
||||
return allocation;
|
||||
}
|
||||
|
||||
[HttpPost]
|
||||
[Authorize(Policy = "permissions:admin.servers.nodes.create")]
|
||||
public async Task<ActionResult<NodeAllocationResponse>> CreateAsync(
|
||||
[FromRoute] int nodeId,
|
||||
[FromBody] CreateNodeAllocationRequest request
|
||||
)
|
||||
{
|
||||
var node = await NodeRepository
|
||||
.Get()
|
||||
.FirstOrDefaultAsync(x => x.Id == nodeId);
|
||||
|
||||
if (node == null)
|
||||
return Problem("No node with that id found", statusCode: 404);
|
||||
|
||||
var allocation = AllocationMapper.ToAllocation(request);
|
||||
|
||||
var finalAllocation = await AllocationRepository.AddAsync(allocation);
|
||||
|
||||
return AllocationMapper.ToNodeAllocation(finalAllocation);
|
||||
}
|
||||
|
||||
[HttpPatch("{id:int}")]
|
||||
public async Task<ActionResult<NodeAllocationResponse>> UpdateAsync(
|
||||
[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)
|
||||
return Problem("No allocation with that id found", statusCode: 404);
|
||||
|
||||
AllocationMapper.Merge(request, allocation);
|
||||
await AllocationRepository.UpdateAsync(allocation);
|
||||
|
||||
return AllocationMapper.ToNodeAllocation(allocation);
|
||||
}
|
||||
|
||||
[HttpDelete("{id:int}")]
|
||||
public async Task<ActionResult> DeleteAsync([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)
|
||||
return Problem("No allocation with that id found", statusCode: 404);
|
||||
|
||||
await AllocationRepository.RemoveAsync(allocation);
|
||||
return NoContent();
|
||||
}
|
||||
|
||||
[HttpPost("range")]
|
||||
public async Task<ActionResult> CreateRangeAsync(
|
||||
[FromRoute] int nodeId,
|
||||
[FromBody] CreateNodeAllocationRangeRequest request
|
||||
)
|
||||
{
|
||||
if (request.Start > request.End)
|
||||
return Problem("Invalid start and end specified", statusCode: 400);
|
||||
|
||||
if (request.End - request.Start == 0)
|
||||
return Problem("Empty range specified", statusCode: 400);
|
||||
|
||||
var node = await NodeRepository
|
||||
.Get()
|
||||
.FirstOrDefaultAsync(x => x.Id == nodeId);
|
||||
|
||||
if (node == null)
|
||||
return Problem("No node with that id found", statusCode: 404);
|
||||
|
||||
var existingAllocations = await AllocationRepository
|
||||
.Get()
|
||||
.Where(x => x.Port >= request.Start && x.Port <= request.End &&
|
||||
x.IpAddress == request.IpAddress)
|
||||
.AsNoTracking()
|
||||
.ToArrayAsync();
|
||||
|
||||
var ports = new List<int>();
|
||||
|
||||
for (var i = request.Start; i < request.End; i++)
|
||||
{
|
||||
// Skip existing allocations
|
||||
if (existingAllocations.Any(x => x.Port == i))
|
||||
continue;
|
||||
|
||||
ports.Add(i);
|
||||
}
|
||||
|
||||
var allocations = ports
|
||||
.Select(port => new Allocation()
|
||||
{
|
||||
IpAddress = request.IpAddress,
|
||||
Port = port,
|
||||
Node = node
|
||||
})
|
||||
.ToArray();
|
||||
|
||||
await AllocationRepository.RunTransactionAsync(async set => { await set.AddRangeAsync(allocations); });
|
||||
return NoContent();
|
||||
}
|
||||
|
||||
[HttpDelete("all")]
|
||||
public async Task<ActionResult> DeleteAllAsync([FromRoute] int nodeId)
|
||||
{
|
||||
var allocations = AllocationRepository
|
||||
.Get()
|
||||
.Where(x => x.Node.Id == nodeId)
|
||||
.ToArray();
|
||||
|
||||
await AllocationRepository.RunTransactionAsync(set => { set.RemoveRange(allocations); });
|
||||
return NoContent();
|
||||
}
|
||||
|
||||
[HttpGet("free")]
|
||||
[Authorize(Policy = "permissions:admin.servers.nodes.get")]
|
||||
public async Task<ActionResult<CountedData<NodeAllocationResponse>>> GetFreeAsync(
|
||||
[FromRoute] int nodeId,
|
||||
[FromQuery] int startIndex,
|
||||
[FromQuery] int count,
|
||||
[FromQuery] int serverId = -1
|
||||
)
|
||||
{
|
||||
if (count > 100)
|
||||
return Problem("Only 100 items can be fetched at a time", statusCode: 400);
|
||||
|
||||
var node = NodeRepository
|
||||
.Get()
|
||||
.FirstOrDefault(x => x.Id == nodeId);
|
||||
|
||||
if (node == null)
|
||||
return Problem("A node with this id could not be found", statusCode: 404);
|
||||
|
||||
var freeAllocationsQuery = AllocationRepository
|
||||
.Get()
|
||||
.OrderBy(x => x.Id)
|
||||
.Where(x => x.Node.Id == node.Id)
|
||||
.Where(x => x.Server == null || x.Server.Id == serverId);
|
||||
|
||||
var totalCount = await freeAllocationsQuery.CountAsync();
|
||||
|
||||
var allocations = await freeAllocationsQuery
|
||||
.Skip(startIndex)
|
||||
.Take(count)
|
||||
.AsNoTracking()
|
||||
.ProjectToAdminResponse()
|
||||
.ToArrayAsync();
|
||||
|
||||
return new CountedData<NodeAllocationResponse>()
|
||||
{
|
||||
Items = allocations,
|
||||
TotalCount = totalCount
|
||||
};
|
||||
}
|
||||
}
|
||||
@@ -1,82 +0,0 @@
|
||||
using System.Diagnostics;
|
||||
using Microsoft.AspNetCore.Mvc;
|
||||
using MoonCore.Extended.Abstractions;
|
||||
using Microsoft.AspNetCore.Authorization;
|
||||
using Microsoft.EntityFrameworkCore;
|
||||
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:int}/system/status")]
|
||||
[Authorize(Policy = "permissions:admin.servers.nodes.status")]
|
||||
public async Task<ActionResult<NodeSystemStatusResponse>> GetStatusAsync([FromRoute] int nodeId)
|
||||
{
|
||||
var node = await GetNodeAsync(nodeId);
|
||||
|
||||
if (node.Value == null)
|
||||
return node.Result ?? Problem("Unable to retrieve node");
|
||||
|
||||
NodeSystemStatusResponse response;
|
||||
|
||||
var sw = new Stopwatch();
|
||||
sw.Start();
|
||||
|
||||
try
|
||||
{
|
||||
var statusResponse = await NodeService.GetSystemStatusAsync(node.Value);
|
||||
|
||||
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 async Task<ActionResult<Node>> GetNodeAsync(int nodeId)
|
||||
{
|
||||
var result = await NodeRepository
|
||||
.Get()
|
||||
.FirstOrDefaultAsync(x => x.Id == nodeId);
|
||||
|
||||
if (result == null)
|
||||
return Problem("A node with this id could not be found", statusCode: 404);
|
||||
|
||||
return result;
|
||||
}
|
||||
}
|
||||
@@ -1,114 +0,0 @@
|
||||
using Microsoft.AspNetCore.Mvc;
|
||||
using Microsoft.EntityFrameworkCore;
|
||||
using MoonCore.Extended.Abstractions;
|
||||
using Microsoft.AspNetCore.Authorization;
|
||||
using MoonCore.Common;
|
||||
using MoonCore.Helpers;
|
||||
using MoonlightServers.ApiServer.Database.Entities;
|
||||
using MoonlightServers.ApiServer.Mappers;
|
||||
using MoonlightServers.Shared.Http.Requests.Admin.Nodes;
|
||||
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 DatabaseRepository<Node> NodeRepository;
|
||||
|
||||
public NodesController(DatabaseRepository<Node> nodeRepository)
|
||||
{
|
||||
NodeRepository = nodeRepository;
|
||||
}
|
||||
|
||||
[HttpGet]
|
||||
[Authorize(Policy = "permissions:admin.servers.nodes.get")]
|
||||
public async Task<ActionResult<CountedData<NodeResponse>>> GetAsync(
|
||||
[FromQuery] int startIndex,
|
||||
[FromQuery] int count
|
||||
)
|
||||
{
|
||||
if (count > 100)
|
||||
return Problem("Only 100 items can be fetched at a time", statusCode: 400);
|
||||
|
||||
var totalCount = await NodeRepository.Get().CountAsync();
|
||||
|
||||
var items = await NodeRepository
|
||||
.Get()
|
||||
.OrderBy(x => x.Id)
|
||||
.Skip(startIndex)
|
||||
.Take(count)
|
||||
.AsNoTracking()
|
||||
.ProjectToAdminResponse()
|
||||
.ToArrayAsync();
|
||||
|
||||
return new CountedData<NodeResponse>()
|
||||
{
|
||||
Items = items,
|
||||
TotalCount = totalCount
|
||||
};
|
||||
}
|
||||
|
||||
[HttpGet("{id:int}")]
|
||||
[Authorize(Policy = "permissions:admin.servers.nodes.get")]
|
||||
public async Task<ActionResult<NodeResponse>> GetSingleAsync([FromRoute] int id)
|
||||
{
|
||||
var node = await NodeRepository
|
||||
.Get()
|
||||
.AsNoTracking()
|
||||
.ProjectToAdminResponse()
|
||||
.FirstOrDefaultAsync(x => x.Id == id);
|
||||
|
||||
if (node == null)
|
||||
return Problem("No node with this id found", statusCode: 404);
|
||||
|
||||
return node;
|
||||
}
|
||||
|
||||
[HttpPost]
|
||||
[Authorize(Policy = "permissions:admin.servers.nodes.create")]
|
||||
public async Task<ActionResult<NodeResponse>> CreateAsync([FromBody] CreateNodeRequest request)
|
||||
{
|
||||
var node = NodeMapper.ToNode(request);
|
||||
|
||||
node.TokenId = Formatter.GenerateString(6);
|
||||
node.Token = Formatter.GenerateString(32);
|
||||
|
||||
var finalNode = await NodeRepository.AddAsync(node);
|
||||
|
||||
return NodeMapper.ToAdminNodeResponse(finalNode);
|
||||
}
|
||||
|
||||
[HttpPatch("{id:int}")]
|
||||
[Authorize(Policy = "permissions:admin.servers.nodes.update")]
|
||||
public async Task<ActionResult<NodeResponse>> UpdateAsync([FromRoute] int id, [FromBody] UpdateNodeRequest request)
|
||||
{
|
||||
var node = await NodeRepository
|
||||
.Get()
|
||||
.FirstOrDefaultAsync(x => x.Id == id);
|
||||
|
||||
if (node == null)
|
||||
return Problem("No node with this id found", statusCode: 404);
|
||||
|
||||
NodeMapper.Merge(request, node);
|
||||
await NodeRepository.UpdateAsync(node);
|
||||
|
||||
return NodeMapper.ToAdminNodeResponse(node);
|
||||
}
|
||||
|
||||
[HttpDelete("{id:int}")]
|
||||
[Authorize(Policy = "permissions:admin.servers.nodes.delete")]
|
||||
public async Task<ActionResult> DeleteAsync([FromRoute] int id)
|
||||
{
|
||||
var node = await NodeRepository
|
||||
.Get()
|
||||
.FirstOrDefaultAsync(x => x.Id == id);
|
||||
|
||||
if (node == null)
|
||||
return Problem("No node with this id found", statusCode: 404);
|
||||
|
||||
await NodeRepository.RemoveAsync(node);
|
||||
return Ok();
|
||||
}
|
||||
}
|
||||
@@ -1,100 +0,0 @@
|
||||
using System.Diagnostics.CodeAnalysis;
|
||||
using Microsoft.AspNetCore.Authorization;
|
||||
using Microsoft.AspNetCore.Mvc;
|
||||
using Microsoft.EntityFrameworkCore;
|
||||
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/{nodeId:int}/statistics")]
|
||||
[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]
|
||||
[SuppressMessage("ReSharper.DPA", "DPA0011: High execution time of MVC action", MessageId = "time: 1142ms",
|
||||
Justification = "The daemon has an artificial delay of one second to calculate accurate cpu usage values")]
|
||||
public async Task<ActionResult<StatisticsResponse>> GetAsync([FromRoute] int nodeId)
|
||||
{
|
||||
var node = await GetNodeAsync(nodeId);
|
||||
|
||||
if (node.Value == null)
|
||||
return node.Result ?? Problem("Unable to retrieve node");
|
||||
|
||||
var statistics = await NodeService.GetStatisticsAsync(node.Value);
|
||||
|
||||
return new StatisticsResponse()
|
||||
{
|
||||
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("docker")]
|
||||
public async Task<ActionResult<DockerStatisticsResponse>> GetDockerAsync([FromRoute] int nodeId)
|
||||
{
|
||||
var node = await GetNodeAsync(nodeId);
|
||||
|
||||
if (node.Value == null)
|
||||
return node.Result ?? Problem("Unable to retrieve node");
|
||||
|
||||
var statistics = await NodeService.GetDockerStatisticsAsync(node.Value);
|
||||
|
||||
return new DockerStatisticsResponse()
|
||||
{
|
||||
BuildCacheReclaimable = statistics.BuildCacheReclaimable,
|
||||
BuildCacheUsed = statistics.BuildCacheUsed,
|
||||
ContainersReclaimable = statistics.ContainersReclaimable,
|
||||
ContainersUsed = statistics.ContainersUsed,
|
||||
ImagesReclaimable = statistics.ImagesReclaimable,
|
||||
ImagesUsed = statistics.ImagesUsed,
|
||||
Version = statistics.Version
|
||||
};
|
||||
}
|
||||
|
||||
private async Task<ActionResult<Node>> GetNodeAsync(int nodeId)
|
||||
{
|
||||
var result = await NodeRepository
|
||||
.Get()
|
||||
.FirstOrDefaultAsync(x => x.Id == nodeId);
|
||||
|
||||
if (result == null)
|
||||
return Problem("A node with this id could not be found", statusCode: 404);
|
||||
|
||||
return result;
|
||||
}
|
||||
}
|
||||
@@ -1,66 +0,0 @@
|
||||
using Microsoft.AspNetCore.Mvc;
|
||||
using Microsoft.EntityFrameworkCore;
|
||||
using Microsoft.AspNetCore.Authorization;
|
||||
using MoonCore.Common;
|
||||
using MoonCore.Extended.Abstractions;
|
||||
using MoonlightServers.ApiServer.Database.Entities;
|
||||
using MoonlightServers.ApiServer.Mappers;
|
||||
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:int}/variables")]
|
||||
[Authorize(Policy = "permissions:admin.servers.read")]
|
||||
public async Task<ActionResult<CountedData<ServerVariableResponse>>> GetAsync(
|
||||
[FromRoute] int serverId,
|
||||
[FromQuery] int startIndex,
|
||||
[FromQuery] int count
|
||||
)
|
||||
{
|
||||
if (count > 100)
|
||||
return Problem("Only 100 items can be fetched at a time", statusCode: 400);
|
||||
|
||||
var serverExists = await ServerRepository
|
||||
.Get()
|
||||
.AnyAsync(x => x.Id == serverId);
|
||||
|
||||
if (!serverExists)
|
||||
return Problem("No server with this id found", statusCode: 404);
|
||||
|
||||
var query = VariableRepository
|
||||
.Get()
|
||||
.Where(x => x.Server.Id == serverId);
|
||||
|
||||
var totalCount = await query.CountAsync();
|
||||
|
||||
var variables = await query
|
||||
.OrderBy(x => x.Id)
|
||||
.Skip(startIndex)
|
||||
.Take(count)
|
||||
.AsNoTracking()
|
||||
.ProjectToAdminResponse()
|
||||
.ToArrayAsync();
|
||||
|
||||
return new CountedData<ServerVariableResponse>()
|
||||
{
|
||||
Items = variables,
|
||||
TotalCount = totalCount
|
||||
};
|
||||
}
|
||||
}
|
||||
@@ -1,330 +0,0 @@
|
||||
using Microsoft.AspNetCore.Mvc;
|
||||
using Microsoft.EntityFrameworkCore;
|
||||
using Microsoft.AspNetCore.Authorization;
|
||||
using Microsoft.Extensions.Logging;
|
||||
using MoonCore.Common;
|
||||
using MoonCore.Extended.Abstractions;
|
||||
using Moonlight.ApiServer.Database.Entities;
|
||||
using MoonlightServers.ApiServer.Database.Entities;
|
||||
using MoonlightServers.ApiServer.Mappers;
|
||||
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 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(
|
||||
DatabaseRepository<Star> starRepository,
|
||||
DatabaseRepository<Node> nodeRepository,
|
||||
DatabaseRepository<Allocation> allocationRepository,
|
||||
DatabaseRepository<ServerVariable> variableRepository,
|
||||
DatabaseRepository<Server> serverRepository,
|
||||
DatabaseRepository<User> userRepository,
|
||||
ILogger<ServersController> logger,
|
||||
ServerService serverService
|
||||
)
|
||||
{
|
||||
StarRepository = starRepository;
|
||||
NodeRepository = nodeRepository;
|
||||
AllocationRepository = allocationRepository;
|
||||
VariableRepository = variableRepository;
|
||||
ServerRepository = serverRepository;
|
||||
UserRepository = userRepository;
|
||||
ServerService = serverService;
|
||||
Logger = logger;
|
||||
}
|
||||
|
||||
[HttpGet]
|
||||
[Authorize(Policy = "permissions:admin.servers.read")]
|
||||
public async Task<ActionResult<CountedData<ServerResponse>>> GetAsync(
|
||||
[FromQuery] int startIndex,
|
||||
[FromQuery] int count
|
||||
)
|
||||
{
|
||||
if (count > 100)
|
||||
return Problem("Only 100 items can be fetched at a time", statusCode: 400);
|
||||
|
||||
var totalCount = await ServerRepository.Get().CountAsync();
|
||||
|
||||
var servers = await ServerRepository
|
||||
.Get()
|
||||
.Include(x => x.Node)
|
||||
.Include(x => x.Allocations)
|
||||
.Include(x => x.Variables)
|
||||
.Include(x => x.Star)
|
||||
.OrderBy(x => x.Id)
|
||||
.Skip(startIndex)
|
||||
.Take(count)
|
||||
.AsNoTracking()
|
||||
.ProjectToAdminResponse()
|
||||
.ToArrayAsync();
|
||||
|
||||
return new CountedData<ServerResponse>()
|
||||
{
|
||||
Items = servers,
|
||||
TotalCount = totalCount
|
||||
};
|
||||
}
|
||||
|
||||
[HttpGet("{id:int}")]
|
||||
[Authorize(Policy = "permissions:admin.servers.read")]
|
||||
public async Task<ActionResult<ServerResponse>> GetSingleAsync([FromRoute] int id)
|
||||
{
|
||||
var server = await ServerRepository
|
||||
.Get()
|
||||
.Include(x => x.Node)
|
||||
.Include(x => x.Allocations)
|
||||
.Include(x => x.Variables)
|
||||
.Include(x => x.Star)
|
||||
.AsNoTracking()
|
||||
.Where(x => x.Id == id)
|
||||
.ProjectToAdminResponse()
|
||||
.FirstOrDefaultAsync();
|
||||
|
||||
if (server == null)
|
||||
return Problem("No server with that id found", statusCode: 404);
|
||||
|
||||
return server;
|
||||
}
|
||||
|
||||
[HttpPost]
|
||||
[Authorize(Policy = "permissions:admin.servers.write")]
|
||||
public async Task<ActionResult<ServerResponse>> CreateAsync([FromBody] CreateServerRequest request)
|
||||
{
|
||||
// Check if owner user exist
|
||||
if (UserRepository.Get().All(x => x.Id != request.OwnerId))
|
||||
return Problem("No user with this id found", statusCode: 400);
|
||||
|
||||
// Check if the star exists
|
||||
var star = await StarRepository
|
||||
.Get()
|
||||
.Include(x => x.Variables)
|
||||
.Include(x => x.DockerImages)
|
||||
.FirstOrDefaultAsync(x => x.Id == request.StarId);
|
||||
|
||||
if (star == null)
|
||||
return Problem("No star with this id found", statusCode: 400);
|
||||
|
||||
var node = await NodeRepository
|
||||
.Get()
|
||||
.FirstOrDefaultAsync(x => x.Id == request.NodeId);
|
||||
|
||||
if (node == null)
|
||||
return Problem("No node with this id found", statusCode: 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)
|
||||
{
|
||||
return Problem(
|
||||
$"Unable to find enough free allocations. Found: {allocations.Count}, Required: {star.RequiredAllocations}",
|
||||
statusCode: 400
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
var server = ServerMapper.ToServer(request);
|
||||
|
||||
// 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.AddAsync(server);
|
||||
|
||||
try
|
||||
{
|
||||
await ServerService.SyncAsync(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 won't have a bugged server in the database which doesn't exist on the node
|
||||
await ServerRepository.RemoveAsync(finalServer);
|
||||
|
||||
throw;
|
||||
}
|
||||
|
||||
return ServerMapper.ToAdminServerResponse(finalServer);
|
||||
}
|
||||
|
||||
[HttpPatch("{id:int}")]
|
||||
[Authorize(Policy = "permissions:admin.servers.write")]
|
||||
public async Task<ActionResult<ServerResponse>> UpdateAsync(
|
||||
[FromRoute] int id,
|
||||
[FromBody] UpdateServerRequest request
|
||||
)
|
||||
{
|
||||
//TODO: Handle shrinking virtual disk
|
||||
|
||||
var server = await ServerRepository
|
||||
.Get()
|
||||
.Include(x => x.Node)
|
||||
.Include(x => x.Allocations)
|
||||
.Include(x => x.Variables)
|
||||
.Include(x => x.Star)
|
||||
.FirstOrDefaultAsync(x => x.Id == id);
|
||||
|
||||
if (server == null)
|
||||
return Problem("No server with that id found", statusCode: 404);
|
||||
|
||||
ServerMapper.Merge(request, server);
|
||||
|
||||
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)
|
||||
{
|
||||
return Problem(
|
||||
$"You need to specify at least {server.Star.RequiredAllocations} allocation(s)",
|
||||
statusCode: 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.UpdateAsync(server);
|
||||
|
||||
// Notify the node about the changes
|
||||
await ServerService.SyncAsync(server);
|
||||
|
||||
return ServerMapper.ToAdminServerResponse(server);
|
||||
}
|
||||
|
||||
[HttpDelete("{id:int}")]
|
||||
public async Task<ActionResult> DeleteAsync([FromRoute] int id, [FromQuery] bool force = false)
|
||||
{
|
||||
var server = await ServerRepository
|
||||
.Get()
|
||||
.Include(x => x.Node)
|
||||
.Include(x => x.Star)
|
||||
.Include(x => x.Variables)
|
||||
.Include(x => x.Backups)
|
||||
.Include(x => x.Allocations)
|
||||
.FirstOrDefaultAsync(x => x.Id == id);
|
||||
|
||||
if (server == null)
|
||||
return Problem("No server with that id found", statusCode: 404);
|
||||
|
||||
server.Variables.Clear();
|
||||
server.Backups.Clear();
|
||||
server.Allocations.Clear();
|
||||
|
||||
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.SyncDeleteAsync(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 ServerRepository.RemoveAsync(server);
|
||||
return NoContent();
|
||||
}
|
||||
}
|
||||
@@ -1,159 +0,0 @@
|
||||
using Microsoft.AspNetCore.Mvc;
|
||||
using Microsoft.EntityFrameworkCore;
|
||||
using MoonCore.Extended.Abstractions;
|
||||
using Microsoft.AspNetCore.Authorization;
|
||||
using MoonCore.Common;
|
||||
using MoonlightServers.ApiServer.Database.Entities;
|
||||
using MoonlightServers.ApiServer.Mappers;
|
||||
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/{starId:int}/dockerImages")]
|
||||
public class StarDockerImagesController : Controller
|
||||
{
|
||||
private readonly DatabaseRepository<Star> StarRepository;
|
||||
private readonly DatabaseRepository<StarDockerImage> DockerImageRepository;
|
||||
|
||||
public StarDockerImagesController(
|
||||
DatabaseRepository<Star> starRepository,
|
||||
DatabaseRepository<StarDockerImage> dockerImageRepository
|
||||
)
|
||||
{
|
||||
StarRepository = starRepository;
|
||||
DockerImageRepository = dockerImageRepository;
|
||||
}
|
||||
|
||||
[HttpGet]
|
||||
[Authorize(Policy = "permissions:admin.servers.stars.get")]
|
||||
public async Task<ActionResult<CountedData<StarDockerImageResponse>>> GetAsync(
|
||||
[FromRoute] int starId,
|
||||
[FromQuery] int startIndex,
|
||||
[FromQuery] int count
|
||||
)
|
||||
{
|
||||
var starExists = StarRepository
|
||||
.Get()
|
||||
.Any(x => x.Id == starId);
|
||||
|
||||
if (!starExists)
|
||||
return Problem("No star with this id found", statusCode: 404);
|
||||
|
||||
var query = DockerImageRepository
|
||||
.Get()
|
||||
.Where(x => x.Star.Id == starId);
|
||||
|
||||
var totalCount = await query.CountAsync();
|
||||
|
||||
var dockerImages = await query
|
||||
.OrderBy(x => x.Id)
|
||||
.Skip(startIndex)
|
||||
.Take(count)
|
||||
.AsNoTracking()
|
||||
.ProjectToAdminResponse()
|
||||
.ToArrayAsync();
|
||||
|
||||
return new CountedData<StarDockerImageResponse>()
|
||||
{
|
||||
Items = dockerImages,
|
||||
TotalCount = totalCount
|
||||
};
|
||||
}
|
||||
|
||||
[HttpGet("{id:int}")]
|
||||
[Authorize(Policy = "permissions:admin.servers.stars.read")]
|
||||
public async Task<ActionResult<StarDockerImageResponse>> GetSingleAsync([FromRoute] int starId, [FromRoute] int id)
|
||||
{
|
||||
var starExists = StarRepository
|
||||
.Get()
|
||||
.Any(x => x.Id == starId);
|
||||
|
||||
if (!starExists)
|
||||
return Problem("No star with this id found", statusCode: 404);
|
||||
|
||||
var dockerImage = await DockerImageRepository
|
||||
.Get()
|
||||
.Where(x => x.Id == id && x.Star.Id == starId)
|
||||
.ProjectToAdminResponse()
|
||||
.FirstOrDefaultAsync();
|
||||
|
||||
if (dockerImage == null)
|
||||
return Problem("No star docker image with this id found", statusCode: 404);
|
||||
|
||||
return dockerImage;
|
||||
}
|
||||
|
||||
[HttpPost]
|
||||
[Authorize(Policy = "permissions:admin.servers.stars.write")]
|
||||
public async Task<ActionResult<StarDockerImageResponse>> CreateAsync(
|
||||
[FromRoute] int starId,
|
||||
[FromBody] CreateStarDockerImageRequest request
|
||||
)
|
||||
{
|
||||
var star = await StarRepository
|
||||
.Get()
|
||||
.FirstOrDefaultAsync(x => x.Id == starId);
|
||||
|
||||
if (star == null)
|
||||
return Problem("No star with this id found", statusCode: 404);
|
||||
|
||||
var dockerImage = DockerImageMapper.ToDockerImage(request);
|
||||
dockerImage.Star = star;
|
||||
|
||||
var finalDockerImage = await DockerImageRepository.AddAsync(dockerImage);
|
||||
|
||||
return DockerImageMapper.ToAdminResponse(finalDockerImage);
|
||||
}
|
||||
|
||||
[HttpPatch("{id:int}")]
|
||||
[Authorize(Policy = "permissions:admin.servers.stars.write")]
|
||||
public async Task<ActionResult<StarDockerImageResponse>> UpdateAsync(
|
||||
[FromRoute] int starId,
|
||||
[FromRoute] int id,
|
||||
[FromBody] UpdateStarDockerImageRequest request
|
||||
)
|
||||
{
|
||||
var starExists = StarRepository
|
||||
.Get()
|
||||
.Any(x => x.Id == starId);
|
||||
|
||||
if (!starExists)
|
||||
return Problem("No star with this id found", statusCode: 404);
|
||||
|
||||
var dockerImage = await DockerImageRepository
|
||||
.Get()
|
||||
.FirstOrDefaultAsync(x => x.Id == id && x.Star.Id == starId);
|
||||
|
||||
if (dockerImage == null)
|
||||
return Problem("No star docker image with this id found", statusCode: 404);
|
||||
|
||||
DockerImageMapper.Merge(request, dockerImage);
|
||||
await DockerImageRepository.UpdateAsync(dockerImage);
|
||||
|
||||
return DockerImageMapper.ToAdminResponse(dockerImage);
|
||||
}
|
||||
|
||||
[HttpDelete("{id:int}")]
|
||||
[Authorize(Policy = "permissions:admin.servers.stars.write")]
|
||||
public async Task<ActionResult> DeleteAsync([FromRoute] int starId, [FromRoute] int id)
|
||||
{
|
||||
var starExists = StarRepository
|
||||
.Get()
|
||||
.Any(x => x.Id == starId);
|
||||
|
||||
if (!starExists)
|
||||
return Problem("No star with this id found", statusCode: 404);
|
||||
|
||||
var dockerImage = await DockerImageRepository
|
||||
.Get()
|
||||
.FirstOrDefaultAsync(x => x.Id == id && x.Star.Id == starId);
|
||||
|
||||
if (dockerImage == null)
|
||||
return Problem("No star docker image with this id found", statusCode: 404);
|
||||
|
||||
await DockerImageRepository.RemoveAsync(dockerImage);
|
||||
return NoContent();
|
||||
}
|
||||
}
|
||||
@@ -1,49 +0,0 @@
|
||||
using System.Text;
|
||||
using Microsoft.AspNetCore.Mvc;
|
||||
using Microsoft.AspNetCore.Authorization;
|
||||
using MoonCore.Exceptions;
|
||||
using MoonlightServers.ApiServer.Mappers;
|
||||
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<ActionResult> ExportAsync([FromRoute] int starId)
|
||||
{
|
||||
var exportedStar = await ImportExportService.ExportAsync(starId);
|
||||
return Content(exportedStar, "application/json");
|
||||
}
|
||||
|
||||
[HttpPost("import")]
|
||||
[Authorize(Policy = "permissions:admin.servers.stars.create")]
|
||||
public async Task<StarResponse> ImportAsync()
|
||||
{
|
||||
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.ImportAsync(content);
|
||||
|
||||
return StarMapper.ToAdminResponse(star);
|
||||
}
|
||||
}
|
||||
@@ -1,160 +0,0 @@
|
||||
using Microsoft.AspNetCore.Mvc;
|
||||
using Microsoft.EntityFrameworkCore;
|
||||
using Microsoft.AspNetCore.Authorization;
|
||||
using MoonCore.Common;
|
||||
using MoonCore.Exceptions;
|
||||
using MoonCore.Extended.Abstractions;
|
||||
using MoonlightServers.ApiServer.Database.Entities;
|
||||
using MoonlightServers.ApiServer.Mappers;
|
||||
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/{starId:int}/variables")]
|
||||
public class StarVariablesController : Controller
|
||||
{
|
||||
private readonly DatabaseRepository<Star> StarRepository;
|
||||
private readonly DatabaseRepository<StarVariable> VariableRepository;
|
||||
|
||||
public StarVariablesController(
|
||||
DatabaseRepository<Star> starRepository,
|
||||
DatabaseRepository<StarVariable> variableRepository)
|
||||
{
|
||||
StarRepository = starRepository;
|
||||
VariableRepository = variableRepository;
|
||||
}
|
||||
|
||||
[HttpGet]
|
||||
[Authorize(Policy = "permissions:admin.servers.stars.get")]
|
||||
public async Task<ActionResult<CountedData<StarVariableResponse>>> GetAsync(
|
||||
[FromRoute] int starId,
|
||||
[FromQuery] int startIndex,
|
||||
[FromQuery] int count
|
||||
)
|
||||
{
|
||||
if (count > 100)
|
||||
return Problem("Only 100 items can be fetched at a time", statusCode: 400);
|
||||
|
||||
var starExists = StarRepository
|
||||
.Get()
|
||||
.Any(x => x.Id == starId);
|
||||
|
||||
if (!starExists)
|
||||
return Problem("No star with this id found", statusCode: 404);
|
||||
|
||||
var query = VariableRepository
|
||||
.Get()
|
||||
.Where(x => x.Star.Id == starId);
|
||||
|
||||
var totalCount = await query.CountAsync();
|
||||
|
||||
var variables = await query
|
||||
.OrderBy(x => x.Id)
|
||||
.Skip(startIndex)
|
||||
.Take(count)
|
||||
.AsNoTracking()
|
||||
.ProjectToAdminResponse()
|
||||
.ToArrayAsync();
|
||||
|
||||
return new CountedData<StarVariableResponse>()
|
||||
{
|
||||
Items = variables,
|
||||
TotalCount = totalCount
|
||||
};
|
||||
}
|
||||
|
||||
[HttpGet("{id:int}")]
|
||||
[Authorize(Policy = "permissions:admin.servers.stars.get")]
|
||||
public async Task<StarVariableResponse> GetSingleAsync(
|
||||
[FromRoute] int starId,
|
||||
[FromRoute] int id
|
||||
)
|
||||
{
|
||||
var starExists = StarRepository
|
||||
.Get()
|
||||
.Any(x => x.Id == starId);
|
||||
|
||||
if (!starExists)
|
||||
throw new HttpApiException("No star with this id found", 404);
|
||||
|
||||
var starVariable = await VariableRepository
|
||||
.Get()
|
||||
.FirstOrDefaultAsync(x => x.Id == id && x.Star.Id == starId);
|
||||
|
||||
if (starVariable == null)
|
||||
throw new HttpApiException("No variable with this id found", 404);
|
||||
|
||||
return StarVariableMapper.ToAdminResponse(starVariable);
|
||||
}
|
||||
|
||||
[HttpPost("")]
|
||||
[Authorize(Policy = "permissions:admin.servers.stars.create")]
|
||||
public async Task<StarVariableResponse> CreateAsync([FromRoute] int starId,
|
||||
[FromBody] CreateStarVariableRequest request)
|
||||
{
|
||||
var star = StarRepository
|
||||
.Get()
|
||||
.FirstOrDefault(x => x.Id == starId);
|
||||
|
||||
if (star == null)
|
||||
throw new HttpApiException("No star with this id found", 404);
|
||||
|
||||
var starVariable = StarVariableMapper.ToStarVariable(request);
|
||||
starVariable.Star = star;
|
||||
|
||||
await VariableRepository.AddAsync(starVariable);
|
||||
|
||||
return StarVariableMapper.ToAdminResponse(starVariable);
|
||||
}
|
||||
|
||||
[HttpPatch("{id:int}")]
|
||||
[Authorize(Policy = "permissions:admin.servers.stars.update")]
|
||||
public async Task<StarVariableResponse> UpdateAsync(
|
||||
[FromRoute] int starId,
|
||||
[FromRoute] int id,
|
||||
[FromBody] UpdateStarVariableRequest request
|
||||
)
|
||||
{
|
||||
var starExists = StarRepository
|
||||
.Get()
|
||||
.Any(x => x.Id == starId);
|
||||
|
||||
if (!starExists)
|
||||
throw new HttpApiException("No star with this id found", 404);
|
||||
|
||||
var starVariable = await VariableRepository
|
||||
.Get()
|
||||
.FirstOrDefaultAsync(x => x.Id == id && x.Star.Id == starId);
|
||||
|
||||
if (starVariable == null)
|
||||
throw new HttpApiException("No variable with this id found", 404);
|
||||
|
||||
StarVariableMapper.Merge(request, starVariable);
|
||||
await VariableRepository.UpdateAsync(starVariable);
|
||||
|
||||
return StarVariableMapper.ToAdminResponse(starVariable);
|
||||
}
|
||||
|
||||
[HttpDelete("{id:int}")]
|
||||
[Authorize(Policy = "permissions:admin.servers.stars.delete")]
|
||||
public async Task DeleteAsync([FromRoute] int starId, [FromRoute] int id)
|
||||
{
|
||||
var starExists = StarRepository
|
||||
.Get()
|
||||
.Any(x => x.Id == starId);
|
||||
|
||||
if (!starExists)
|
||||
throw new HttpApiException("No star with this id found", 404);
|
||||
|
||||
var starVariable = await VariableRepository
|
||||
.Get()
|
||||
.FirstOrDefaultAsync(x => x.Id == id && x.Star.Id == starId);
|
||||
|
||||
if (starVariable == null)
|
||||
throw new HttpApiException("No variable with this id found", 404);
|
||||
|
||||
await VariableRepository.RemoveAsync(starVariable);
|
||||
}
|
||||
}
|
||||
@@ -1,128 +0,0 @@
|
||||
using Microsoft.AspNetCore.Mvc;
|
||||
using Microsoft.EntityFrameworkCore;
|
||||
using Microsoft.AspNetCore.Authorization;
|
||||
using MoonCore.Common;
|
||||
using MoonCore.Extended.Abstractions;
|
||||
using MoonlightServers.ApiServer.Database.Entities;
|
||||
using MoonlightServers.ApiServer.Mappers;
|
||||
using MoonlightServers.Shared.Http.Requests.Admin.Stars;
|
||||
using MoonlightServers.Shared.Http.Responses.Admin.Stars;
|
||||
|
||||
namespace MoonlightServers.ApiServer.Http.Controllers.Admin.Stars;
|
||||
|
||||
[ApiController]
|
||||
[Route("api/admin/servers/stars")]
|
||||
public class StarsController : Controller
|
||||
{
|
||||
private readonly DatabaseRepository<Star> StarRepository;
|
||||
|
||||
public StarsController(DatabaseRepository<Star> starRepository)
|
||||
{
|
||||
StarRepository = starRepository;
|
||||
}
|
||||
|
||||
[HttpGet]
|
||||
[Authorize(Policy = "permissions:admin.servers.stars.read")]
|
||||
public async Task<ActionResult<CountedData<StarResponse>>> GetAsync(
|
||||
[FromQuery] int startIndex,
|
||||
[FromQuery] int count
|
||||
)
|
||||
{
|
||||
if (count > 100)
|
||||
return Problem("Only 100 items can be fetched at a time", statusCode: 400);
|
||||
|
||||
var totalCount = await StarRepository.Get().CountAsync();
|
||||
|
||||
var stars = await StarRepository
|
||||
.Get()
|
||||
.OrderBy(x => x.Id)
|
||||
.Skip(startIndex)
|
||||
.Take(count)
|
||||
.AsNoTracking()
|
||||
.ProjectToAdminResponse()
|
||||
.ToArrayAsync();
|
||||
|
||||
return new CountedData<StarResponse>()
|
||||
{
|
||||
Items = stars,
|
||||
TotalCount = totalCount
|
||||
};
|
||||
}
|
||||
|
||||
[HttpGet("{id:int}")]
|
||||
[Authorize(Policy = "permissions:admin.servers.stars.read")]
|
||||
public async Task<ActionResult<StarResponse>> GetSingleAsync([FromRoute] int id)
|
||||
{
|
||||
var star = await StarRepository
|
||||
.Get()
|
||||
.AsNoTracking()
|
||||
.ProjectToAdminResponse()
|
||||
.FirstOrDefaultAsync(x => x.Id == id);
|
||||
|
||||
if (star == null)
|
||||
return Problem("No star with that id found", statusCode: 404);
|
||||
|
||||
return star;
|
||||
}
|
||||
|
||||
[HttpPost]
|
||||
[Authorize(Policy = "permissions:admin.servers.stars.create")]
|
||||
public async Task<ActionResult<StarResponse>> CreateAsync([FromBody] CreateStarRequest request)
|
||||
{
|
||||
var star = StarMapper.ToStar(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.AddAsync(star);
|
||||
|
||||
return StarMapper.ToAdminResponse(finalStar);
|
||||
}
|
||||
|
||||
[HttpPatch("{id:int}")]
|
||||
[Authorize(Policy = "permissions:admin.servers.stars.update")]
|
||||
public async Task<ActionResult<StarResponse>> UpdateAsync(
|
||||
[FromRoute] int id,
|
||||
[FromBody] UpdateStarRequest request
|
||||
)
|
||||
{
|
||||
var star = await StarRepository
|
||||
.Get()
|
||||
.FirstOrDefaultAsync(x => x.Id == id);
|
||||
|
||||
if (star == null)
|
||||
return Problem("No star with that id found", statusCode: 404);
|
||||
|
||||
StarMapper.Merge(request, star);
|
||||
await StarRepository.UpdateAsync(star);
|
||||
|
||||
return StarMapper.ToAdminResponse(star);
|
||||
}
|
||||
|
||||
[HttpDelete("{id:int}")]
|
||||
[Authorize(Policy = "permissions:admin.servers.stars.delete")]
|
||||
public async Task<ActionResult> DeleteAsync([FromRoute] int id)
|
||||
{
|
||||
var star = await StarRepository
|
||||
.Get()
|
||||
.FirstOrDefaultAsync(x => x.Id == id);
|
||||
|
||||
if (star == null)
|
||||
return Problem("No star with that id found", statusCode: 404);
|
||||
|
||||
await StarRepository.RemoveAsync(star);
|
||||
return NoContent();
|
||||
}
|
||||
}
|
||||
@@ -1,227 +0,0 @@
|
||||
using Microsoft.AspNetCore.Authorization;
|
||||
using Microsoft.AspNetCore.Mvc;
|
||||
using Microsoft.EntityFrameworkCore;
|
||||
using MoonCore.Extended.Abstractions;
|
||||
using MoonlightServers.ApiServer.Database.Entities;
|
||||
using MoonlightServers.ApiServer.Services;
|
||||
using MoonlightServers.DaemonShared.Enums;
|
||||
using MoonlightServers.Shared.Constants;
|
||||
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/{serverId:int}/files")]
|
||||
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("list")]
|
||||
public async Task<ActionResult<ServerFilesEntryResponse[]>> ListAsync([FromRoute] int serverId, [FromQuery] string path)
|
||||
{
|
||||
var server = await GetServerByIdAsync(serverId, ServerPermissionLevel.Read);
|
||||
|
||||
if (server.Value == null)
|
||||
return server.Result ?? Problem("Unable to retrieve server");
|
||||
|
||||
var entries = await ServerFileSystemService.ListAsync(server.Value, path);
|
||||
|
||||
return entries.Select(x => new ServerFilesEntryResponse()
|
||||
{
|
||||
Name = x.Name,
|
||||
Size = x.Size,
|
||||
IsFolder = x.IsFolder,
|
||||
CreatedAt = x.CreatedAt,
|
||||
UpdatedAt = x.UpdatedAt
|
||||
}).ToArray();
|
||||
}
|
||||
|
||||
[HttpPost("move")]
|
||||
public async Task<ActionResult> MoveAsync([FromRoute] int serverId, [FromQuery] string oldPath, [FromQuery] string newPath)
|
||||
{
|
||||
var server = await GetServerByIdAsync(serverId, ServerPermissionLevel.ReadWrite);
|
||||
|
||||
if (server.Value == null)
|
||||
return server.Result ?? Problem("Unable to retrieve server");
|
||||
|
||||
await ServerFileSystemService.MoveAsync(server.Value, oldPath, newPath);
|
||||
return NoContent();
|
||||
}
|
||||
|
||||
[HttpDelete("delete")]
|
||||
public async Task<ActionResult> DeleteAsync([FromRoute] int serverId, [FromQuery] string path)
|
||||
{
|
||||
var server = await GetServerByIdAsync(serverId, ServerPermissionLevel.ReadWrite);
|
||||
|
||||
if (server.Value == null)
|
||||
return server.Result ?? Problem("Unable to retrieve server");
|
||||
|
||||
await ServerFileSystemService.DeleteAsync(server.Value, path);
|
||||
return NoContent();
|
||||
}
|
||||
|
||||
[HttpPost("mkdir")]
|
||||
public async Task<ActionResult> MkdirAsync([FromRoute] int serverId, [FromQuery] string path)
|
||||
{
|
||||
var server = await GetServerByIdAsync(serverId, ServerPermissionLevel.ReadWrite);
|
||||
|
||||
if (server.Value == null)
|
||||
return server.Result ?? Problem("Unable to retrieve server");
|
||||
|
||||
await ServerFileSystemService.MkdirAsync(server.Value, path);
|
||||
return NoContent();
|
||||
}
|
||||
|
||||
[HttpPost("touch")]
|
||||
public async Task<ActionResult> TouchAsync([FromRoute] int serverId, [FromQuery] string path)
|
||||
{
|
||||
var server = await GetServerByIdAsync(serverId, ServerPermissionLevel.ReadWrite);
|
||||
|
||||
if (server.Value == null)
|
||||
return server.Result ?? Problem("Unable to retrieve server");
|
||||
|
||||
await ServerFileSystemService.MkdirAsync(server.Value, path);
|
||||
return NoContent();
|
||||
}
|
||||
|
||||
[HttpGet("upload")]
|
||||
public async Task<ActionResult<ServerFilesUploadResponse>> UploadAsync([FromRoute] int serverId)
|
||||
{
|
||||
var serverResult = await GetServerByIdAsync(serverId, ServerPermissionLevel.ReadWrite);
|
||||
|
||||
if (serverResult.Value == null)
|
||||
return serverResult.Result ?? Problem("Unable to retrieve server");
|
||||
|
||||
var server = serverResult.Value;
|
||||
|
||||
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("download")]
|
||||
public async Task<ActionResult<ServerFilesDownloadResponse>> DownloadAsync([FromRoute] int serverId, [FromQuery] string path)
|
||||
{
|
||||
var serverResult = await GetServerByIdAsync(serverId, ServerPermissionLevel.Read);
|
||||
|
||||
if (serverResult.Value == null)
|
||||
return serverResult.Result ?? Problem("Unable to retrieve server");
|
||||
|
||||
var server = serverResult.Value;
|
||||
|
||||
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("compress")]
|
||||
public async Task<ActionResult> CompressAsync([FromRoute] int serverId, [FromBody] ServerFilesCompressRequest request)
|
||||
{
|
||||
var server = await GetServerByIdAsync(serverId, ServerPermissionLevel.ReadWrite);
|
||||
|
||||
if (server.Value == null)
|
||||
return server.Result ?? Problem("Unable to retrieve server");
|
||||
|
||||
if (!Enum.TryParse(request.Type, true, out CompressType type))
|
||||
return Problem("Invalid compress type provided", statusCode: 400);
|
||||
|
||||
await ServerFileSystemService.CompressAsync(server.Value, type, request.Items, request.Destination);
|
||||
return Ok();
|
||||
}
|
||||
|
||||
[HttpPost("decompress")]
|
||||
public async Task<ActionResult> DecompressAsync([FromRoute] int serverId, [FromBody] ServerFilesDecompressRequest request)
|
||||
{
|
||||
var server = await GetServerByIdAsync(serverId, ServerPermissionLevel.ReadWrite);
|
||||
|
||||
if (server.Value == null)
|
||||
return server.Result ?? Problem("Unable to retrieve server");
|
||||
|
||||
if (!Enum.TryParse(request.Type, true, out CompressType type))
|
||||
return Problem("Invalid decompress type provided", statusCode: 400);
|
||||
|
||||
await ServerFileSystemService.DecompressAsync(server.Value, type, request.Path, request.Destination);
|
||||
return NoContent();
|
||||
}
|
||||
|
||||
private async Task<ActionResult<Server>> GetServerByIdAsync(int serverId, ServerPermissionLevel level)
|
||||
{
|
||||
var server = await ServerRepository
|
||||
.Get()
|
||||
.Include(x => x.Node)
|
||||
.FirstOrDefaultAsync(x => x.Id == serverId);
|
||||
|
||||
if (server == null)
|
||||
return Problem("No server with this id found", statusCode: 404);
|
||||
|
||||
var authorizeResult = await AuthorizeService.AuthorizeAsync(
|
||||
User, server,
|
||||
ServerPermissionConstants.Files,
|
||||
level
|
||||
);
|
||||
|
||||
if (!authorizeResult.Succeeded)
|
||||
{
|
||||
return Problem(
|
||||
authorizeResult.Message ?? "No permission for the requested resource",
|
||||
statusCode: 403
|
||||
);
|
||||
}
|
||||
|
||||
return server;
|
||||
}
|
||||
}
|
||||
@@ -1,97 +0,0 @@
|
||||
using Microsoft.AspNetCore.Authorization;
|
||||
using Microsoft.AspNetCore.Mvc;
|
||||
using Microsoft.EntityFrameworkCore;
|
||||
using MoonCore.Extended.Abstractions;
|
||||
using MoonlightServers.ApiServer.Database.Entities;
|
||||
using MoonlightServers.ApiServer.Services;
|
||||
using MoonlightServers.Shared.Constants;
|
||||
using MoonlightServers.Shared.Enums;
|
||||
|
||||
namespace MoonlightServers.ApiServer.Http.Controllers.Client;
|
||||
|
||||
[ApiController]
|
||||
[Authorize]
|
||||
[Route("api/client/servers/{serverId:int}")]
|
||||
public class PowerController : Controller
|
||||
{
|
||||
private readonly DatabaseRepository<Server> ServerRepository;
|
||||
private readonly ServerService ServerService;
|
||||
private readonly ServerAuthorizeService AuthorizeService;
|
||||
|
||||
public PowerController(
|
||||
DatabaseRepository<Server> serverRepository,
|
||||
ServerService serverService,
|
||||
ServerAuthorizeService authorizeService
|
||||
)
|
||||
{
|
||||
ServerRepository = serverRepository;
|
||||
ServerService = serverService;
|
||||
AuthorizeService = authorizeService;
|
||||
}
|
||||
|
||||
[HttpPost("start")]
|
||||
[Authorize]
|
||||
public async Task<ActionResult> StartAsync([FromRoute] int serverId)
|
||||
{
|
||||
var server = await GetServerByIdAsync(serverId);
|
||||
|
||||
if (server.Value == null)
|
||||
return server.Result ?? Problem("Unable to retrieve server");
|
||||
|
||||
await ServerService.StartAsync(server.Value);
|
||||
return NoContent();
|
||||
}
|
||||
|
||||
[HttpPost("stop")]
|
||||
[Authorize]
|
||||
public async Task<ActionResult> StopAsync([FromRoute] int serverId)
|
||||
{
|
||||
var server = await GetServerByIdAsync(serverId);
|
||||
|
||||
if (server.Value == null)
|
||||
return server.Result ?? Problem("Unable to retrieve server");
|
||||
|
||||
await ServerService.StopAsync(server.Value);
|
||||
return NoContent();
|
||||
}
|
||||
|
||||
[HttpPost("kill")]
|
||||
[Authorize]
|
||||
public async Task<ActionResult> KillAsync([FromRoute] int serverId)
|
||||
{
|
||||
var server = await GetServerByIdAsync(serverId);
|
||||
|
||||
if (server.Value == null)
|
||||
return server.Result ?? Problem("Unable to retrieve server");
|
||||
|
||||
await ServerService.KillAsync(server.Value);
|
||||
return NoContent();
|
||||
}
|
||||
|
||||
private async Task<ActionResult<Server>> GetServerByIdAsync(int serverId)
|
||||
{
|
||||
var server = await ServerRepository
|
||||
.Get()
|
||||
.Include(x => x.Node)
|
||||
.FirstOrDefaultAsync(x => x.Id == serverId);
|
||||
|
||||
if (server == null)
|
||||
return Problem("No server with this id found", statusCode: 404);
|
||||
|
||||
var authorizeResult = await AuthorizeService.AuthorizeAsync(
|
||||
User, server,
|
||||
ServerPermissionConstants.Power,
|
||||
ServerPermissionLevel.ReadWrite
|
||||
);
|
||||
|
||||
if (!authorizeResult.Succeeded)
|
||||
{
|
||||
return Problem(
|
||||
authorizeResult.Message ?? "No permission for the requested resource",
|
||||
statusCode: 403
|
||||
);
|
||||
}
|
||||
|
||||
return server;
|
||||
}
|
||||
}
|
||||
@@ -1,380 +0,0 @@
|
||||
using System.Security.Claims;
|
||||
using Microsoft.AspNetCore.Authorization;
|
||||
using Microsoft.AspNetCore.Mvc;
|
||||
using Microsoft.EntityFrameworkCore;
|
||||
using MoonCore.Common;
|
||||
using MoonCore.Extended.Abstractions;
|
||||
using Moonlight.ApiServer.Database.Entities;
|
||||
using MoonlightServers.ApiServer.Database.Entities;
|
||||
using MoonlightServers.ApiServer.Extensions;
|
||||
using MoonlightServers.ApiServer.Mappers;
|
||||
using MoonlightServers.ApiServer.Services;
|
||||
using MoonlightServers.Shared.Constants;
|
||||
using MoonlightServers.Shared.Enums;
|
||||
using MoonlightServers.Shared.Http.Requests.Client.Servers;
|
||||
using MoonlightServers.Shared.Http.Responses.Client.Servers;
|
||||
using MoonlightServers.Shared.Http.Responses.Client.Servers.Allocations;
|
||||
|
||||
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<ActionResult<CountedData<ServerDetailResponse>>> GetAllAsync(
|
||||
[FromQuery] int startIndex,
|
||||
[FromQuery] int count
|
||||
)
|
||||
{
|
||||
if (count > 100)
|
||||
return Problem("Only 100 items can be fetched at a time", statusCode: 400);
|
||||
|
||||
var userIdClaim = User.FindFirstValue("UserId");
|
||||
|
||||
if (string.IsNullOrEmpty(userIdClaim))
|
||||
return Problem("Only users are able to use this endpoint", statusCode: 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 totalCount = await query.CountAsync();
|
||||
|
||||
var items = await query
|
||||
.OrderBy(x => x.Id)
|
||||
.Skip(startIndex)
|
||||
.Take(count)
|
||||
.AsNoTracking()
|
||||
.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 CountedData<ServerDetailResponse>()
|
||||
{
|
||||
Items = mappedItems,
|
||||
TotalCount = totalCount
|
||||
};
|
||||
}
|
||||
|
||||
[HttpGet("shared")]
|
||||
public async Task<ActionResult<CountedData<ServerDetailResponse>>> GetAllSharedAsync(
|
||||
[FromQuery] int startIndex,
|
||||
[FromQuery] int count
|
||||
)
|
||||
{
|
||||
if (count > 100)
|
||||
return Problem("Only 100 items can be fetched at a time", statusCode: 400);
|
||||
|
||||
var userIdClaim = User.FindFirstValue("UserId");
|
||||
|
||||
if (string.IsNullOrEmpty(userIdClaim))
|
||||
return Problem("Only users are able to use this endpoint", statusCode: 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 totalCount = await query.CountAsync();
|
||||
|
||||
var items = await query
|
||||
.OrderBy(x => x.Id)
|
||||
.Skip(startIndex)
|
||||
.Take(count)
|
||||
.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 = ShareMapper.MapToPermissionLevels(x.Content)
|
||||
}
|
||||
}).ToArray();
|
||||
|
||||
return new CountedData<ServerDetailResponse>()
|
||||
{
|
||||
Items = mappedItems,
|
||||
TotalCount = totalCount
|
||||
};
|
||||
}
|
||||
|
||||
[HttpGet("{serverId:int}")]
|
||||
public async Task<ActionResult<ServerDetailResponse>> GetAsync([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)
|
||||
return Problem("No server with this id found", statusCode: 404);
|
||||
|
||||
var authorizationResult = await AuthorizeService.AuthorizeAsync(
|
||||
User,
|
||||
server,
|
||||
String.Empty,
|
||||
ServerPermissionLevel.None
|
||||
);
|
||||
|
||||
if (!authorizationResult.Succeeded)
|
||||
{
|
||||
return Problem(
|
||||
authorizationResult.Message ?? "No server with this id found",
|
||||
statusCode: 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 = ShareMapper.MapToPermissionLevels(authorizationResult.Share.Content)
|
||||
};
|
||||
}
|
||||
|
||||
return response;
|
||||
}
|
||||
|
||||
[HttpGet("{serverId:int}/status")]
|
||||
public async Task<ActionResult<ServerStatusResponse>> GetStatusAsync([FromRoute] int serverId)
|
||||
{
|
||||
var server = await GetServerByIdAsync(
|
||||
serverId,
|
||||
ServerPermissionConstants.Console,
|
||||
ServerPermissionLevel.None
|
||||
);
|
||||
|
||||
if (server.Value == null)
|
||||
return server.Result ?? Problem("Unable to retrieve server");
|
||||
|
||||
var status = await ServerService.GetStatusAsync(server.Value);
|
||||
|
||||
return new ServerStatusResponse()
|
||||
{
|
||||
State = status.State.ToServerPowerState()
|
||||
};
|
||||
}
|
||||
|
||||
[HttpGet("{serverId:int}/ws")]
|
||||
public async Task<ActionResult<ServerWebSocketResponse>> GetWebSocketAsync([FromRoute] int serverId)
|
||||
{
|
||||
var serverResult = await GetServerByIdAsync(
|
||||
serverId,
|
||||
ServerPermissionConstants.Console,
|
||||
ServerPermissionLevel.Read
|
||||
);
|
||||
|
||||
if (serverResult.Value == null)
|
||||
return serverResult.Result ?? Problem("Unable to retrieve server");
|
||||
|
||||
var server = serverResult.Value;
|
||||
|
||||
// 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<ActionResult<ServerLogsResponse>> GetLogsAsync([FromRoute] int serverId)
|
||||
{
|
||||
var server = await GetServerByIdAsync(
|
||||
serverId,
|
||||
ServerPermissionConstants.Console,
|
||||
ServerPermissionLevel.Read
|
||||
);
|
||||
|
||||
if (server.Value == null)
|
||||
return server.Result ?? Problem("Unable to retrieve server");
|
||||
|
||||
var logs = await ServerService.GetLogsAsync(server.Value);
|
||||
|
||||
return new ServerLogsResponse()
|
||||
{
|
||||
Messages = logs.Messages
|
||||
};
|
||||
}
|
||||
|
||||
[HttpGet("{serverId:int}/stats")]
|
||||
public async Task<ActionResult<ServerStatsResponse>> GetStatsAsync([FromRoute] int serverId)
|
||||
{
|
||||
var server = await GetServerByIdAsync(
|
||||
serverId,
|
||||
ServerPermissionConstants.Console,
|
||||
ServerPermissionLevel.Read
|
||||
);
|
||||
|
||||
if (server.Value == null)
|
||||
return server.Result ?? Problem("Unable to retrieve server");
|
||||
|
||||
var stats = await ServerService.GetStatsAsync(server.Value);
|
||||
|
||||
return new ServerStatsResponse()
|
||||
{
|
||||
CpuUsage = stats.CpuUsage,
|
||||
MemoryUsage = stats.MemoryUsage,
|
||||
NetworkRead = stats.NetworkRead,
|
||||
NetworkWrite = stats.NetworkWrite,
|
||||
IoRead = stats.IoRead,
|
||||
IoWrite = stats.IoWrite
|
||||
};
|
||||
}
|
||||
|
||||
[HttpPost("{serverId:int}/command")]
|
||||
public async Task<ActionResult> CommandAsync([FromRoute] int serverId, [FromBody] ServerCommandRequest request)
|
||||
{
|
||||
var server = await GetServerByIdAsync(
|
||||
serverId,
|
||||
ServerPermissionConstants.Console,
|
||||
ServerPermissionLevel.ReadWrite
|
||||
);
|
||||
|
||||
if (server.Value == null)
|
||||
return server.Result ?? Problem("Unable to retrieve server");
|
||||
|
||||
await ServerService.RunCommandAsync(server.Value, request.Command);
|
||||
|
||||
return NoContent();
|
||||
}
|
||||
|
||||
private async Task<ActionResult<Server>> GetServerByIdAsync(int serverId, string permissionId,
|
||||
ServerPermissionLevel level)
|
||||
{
|
||||
var server = await ServerRepository
|
||||
.Get()
|
||||
.Include(x => x.Node)
|
||||
.FirstOrDefaultAsync(x => x.Id == serverId);
|
||||
|
||||
if (server == null)
|
||||
return Problem("No server with this id found", statusCode: 404);
|
||||
|
||||
var authorizeResult = await AuthorizeService.AuthorizeAsync(User, server, permissionId, level);
|
||||
|
||||
if (!authorizeResult.Succeeded)
|
||||
{
|
||||
return Problem(
|
||||
authorizeResult.Message ?? "No permission for the requested resource",
|
||||
statusCode: 403
|
||||
);
|
||||
}
|
||||
|
||||
return server;
|
||||
}
|
||||
}
|
||||
@@ -1,71 +0,0 @@
|
||||
using Microsoft.AspNetCore.Authorization;
|
||||
using Microsoft.AspNetCore.Mvc;
|
||||
using Microsoft.EntityFrameworkCore;
|
||||
using MoonCore.Extended.Abstractions;
|
||||
using MoonlightServers.ApiServer.Database.Entities;
|
||||
using MoonlightServers.ApiServer.Services;
|
||||
using MoonlightServers.Shared.Constants;
|
||||
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<ActionResult> InstallAsync([FromRoute] int serverId)
|
||||
{
|
||||
var server = await GetServerByIdAsync(serverId);
|
||||
|
||||
if (server.Value == null)
|
||||
return server.Result ?? Problem("Unable to retrieve server");
|
||||
|
||||
await ServerService.InstallAsync(server.Value);
|
||||
return NoContent();
|
||||
}
|
||||
|
||||
private async Task<ActionResult<Server>> GetServerByIdAsync(int serverId)
|
||||
{
|
||||
var server = await ServerRepository
|
||||
.Get()
|
||||
.Include(x => x.Node)
|
||||
.FirstOrDefaultAsync(x => x.Id == serverId);
|
||||
|
||||
if (server == null)
|
||||
return Problem("No server with this id found", statusCode: 404);
|
||||
|
||||
var authorizeResult = await AuthorizeService.AuthorizeAsync(
|
||||
User, server,
|
||||
ServerPermissionConstants.Settings,
|
||||
ServerPermissionLevel.ReadWrite
|
||||
);
|
||||
|
||||
if (!authorizeResult.Succeeded)
|
||||
{
|
||||
return Problem(
|
||||
authorizeResult.Message ?? "No permission for the requested resource",
|
||||
statusCode: 403
|
||||
);
|
||||
}
|
||||
|
||||
return server;
|
||||
}
|
||||
}
|
||||
@@ -1,251 +0,0 @@
|
||||
using Microsoft.AspNetCore.Authorization;
|
||||
using Microsoft.AspNetCore.Mvc;
|
||||
using Microsoft.EntityFrameworkCore;
|
||||
using MoonCore.Common;
|
||||
using MoonCore.Extended.Abstractions;
|
||||
using Moonlight.ApiServer.Database.Entities;
|
||||
using MoonlightServers.ApiServer.Database.Entities;
|
||||
using MoonlightServers.ApiServer.Mappers;
|
||||
using MoonlightServers.ApiServer.Services;
|
||||
using MoonlightServers.Shared.Constants;
|
||||
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/{serverId:int}/shares")]
|
||||
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]
|
||||
public async Task<ActionResult<CountedData<ServerShareResponse>>> GetAllAsync(
|
||||
[FromRoute] int serverId,
|
||||
[FromQuery] int startIndex,
|
||||
[FromQuery] int count
|
||||
)
|
||||
{
|
||||
if (count > 100)
|
||||
return Problem("Only 100 items can be fetched at a time", statusCode: 400);
|
||||
|
||||
var server = await GetServerByIdAsync(serverId);
|
||||
|
||||
if (server.Value == null)
|
||||
return server.Result ?? Problem("Unable to retrieve server");
|
||||
|
||||
var query = ShareRepository
|
||||
.Get()
|
||||
.Where(x => x.Server.Id == server.Value.Id);
|
||||
|
||||
var totalCount = await query.CountAsync();
|
||||
|
||||
var items = await query
|
||||
.OrderBy(x => x.Id)
|
||||
.Skip(startIndex)
|
||||
.Take(count)
|
||||
.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 = ShareMapper.MapToPermissionLevels(x.Content)
|
||||
}).ToArray();
|
||||
|
||||
return new CountedData<ServerShareResponse>()
|
||||
{
|
||||
Items = mappedItems,
|
||||
TotalCount = totalCount
|
||||
};
|
||||
}
|
||||
|
||||
[HttpGet("{id:int}")]
|
||||
public async Task<ActionResult<ServerShareResponse>> GetAsync(
|
||||
[FromRoute] int serverId,
|
||||
[FromRoute] int id
|
||||
)
|
||||
{
|
||||
var server = await GetServerByIdAsync(serverId);
|
||||
|
||||
if (server.Value == null)
|
||||
return server.Result ?? Problem("Unable to retrieve server");
|
||||
|
||||
var share = await ShareRepository
|
||||
.Get()
|
||||
.FirstOrDefaultAsync(x => x.Server.Id == server.Value.Id && x.Id == id);
|
||||
|
||||
if (share == null)
|
||||
return Problem("A share with that id cannot be found", statusCode: 404);
|
||||
|
||||
var user = await UserRepository
|
||||
.Get()
|
||||
.FirstAsync(x => x.Id == share.UserId);
|
||||
|
||||
var mappedItem = new ServerShareResponse()
|
||||
{
|
||||
Id = share.Id,
|
||||
Username = user.Username,
|
||||
Permissions = ShareMapper.MapToPermissionLevels(share.Content)
|
||||
};
|
||||
|
||||
return mappedItem;
|
||||
}
|
||||
|
||||
[HttpPost]
|
||||
public async Task<ActionResult<ServerShareResponse>> CreateAsync(
|
||||
[FromRoute] int serverId,
|
||||
[FromBody] CreateShareRequest request
|
||||
)
|
||||
{
|
||||
var server = await GetServerByIdAsync(serverId);
|
||||
|
||||
if (server.Value == null)
|
||||
return server.Result ?? Problem("Unable to retrieve server");
|
||||
|
||||
var user = await UserRepository
|
||||
.Get()
|
||||
.FirstOrDefaultAsync(x => x.Username == request.Username);
|
||||
|
||||
if (user == null)
|
||||
return Problem("A user with that username could not be found", statusCode: 400);
|
||||
|
||||
var share = new ServerShare()
|
||||
{
|
||||
Server = server.Value,
|
||||
Content = ShareMapper.MapToServerShareContent(request.Permissions),
|
||||
CreatedAt = DateTime.UtcNow,
|
||||
UpdatedAt = DateTime.UtcNow,
|
||||
UserId = user.Id
|
||||
};
|
||||
|
||||
var finalShare = await ShareRepository.AddAsync(share);
|
||||
|
||||
var mappedItem = new ServerShareResponse()
|
||||
{
|
||||
Id = finalShare.Id,
|
||||
Username = user.Username,
|
||||
Permissions = ShareMapper.MapToPermissionLevels(finalShare.Content)
|
||||
};
|
||||
|
||||
return mappedItem;
|
||||
}
|
||||
|
||||
[HttpPatch("{id:int}")]
|
||||
public async Task<ActionResult<ServerShareResponse>> UpdateAsync(
|
||||
[FromRoute] int serverId,
|
||||
[FromRoute] int id,
|
||||
[FromBody] UpdateShareRequest request
|
||||
)
|
||||
{
|
||||
var server = await GetServerByIdAsync(serverId);
|
||||
|
||||
if (server.Value == null)
|
||||
return server.Result ?? Problem("Unable to retrieve server");
|
||||
|
||||
var share = await ShareRepository
|
||||
.Get()
|
||||
.FirstOrDefaultAsync(x => x.Server.Id == server.Value.Id && x.Id == id);
|
||||
|
||||
if (share == null)
|
||||
return Problem("A share with that id cannot be found", statusCode: 404);
|
||||
|
||||
share.Content = ShareMapper.MapToServerShareContent(request.Permissions);
|
||||
|
||||
share.UpdatedAt = DateTime.UtcNow;
|
||||
|
||||
await ShareRepository.UpdateAsync(share);
|
||||
|
||||
var user = await UserRepository
|
||||
.Get()
|
||||
.FirstOrDefaultAsync(x => x.Id == share.UserId);
|
||||
|
||||
if (user == null)
|
||||
return Problem("A user with that id could not be found", statusCode: 400);
|
||||
|
||||
var mappedItem = new ServerShareResponse()
|
||||
{
|
||||
Id = share.Id,
|
||||
Username = user.Username,
|
||||
Permissions = ShareMapper.MapToPermissionLevels(share.Content)
|
||||
};
|
||||
|
||||
return mappedItem;
|
||||
}
|
||||
|
||||
[HttpDelete("{id:int}")]
|
||||
public async Task<ActionResult> DeleteAsync(
|
||||
[FromRoute] int serverId,
|
||||
[FromRoute] int id
|
||||
)
|
||||
{
|
||||
var server = await GetServerByIdAsync(serverId);
|
||||
|
||||
if (server.Value == null)
|
||||
return server.Result ?? Problem("Unable to retrieve server");
|
||||
|
||||
var share = await ShareRepository
|
||||
.Get()
|
||||
.FirstOrDefaultAsync(x => x.Server.Id == server.Value.Id && x.Id == id);
|
||||
|
||||
if (share == null)
|
||||
return Problem("A share with that id cannot be found", statusCode: 404);
|
||||
|
||||
await ShareRepository.RemoveAsync(share);
|
||||
return NoContent();
|
||||
}
|
||||
|
||||
private async Task<ActionResult<Server>> GetServerByIdAsync(int serverId)
|
||||
{
|
||||
var server = await ServerRepository
|
||||
.Get()
|
||||
.FirstOrDefaultAsync(x => x.Id == serverId);
|
||||
|
||||
if (server == null)
|
||||
return Problem("No server with this id found", statusCode: 404);
|
||||
|
||||
var authorizeResult = await AuthorizeService.AuthorizeAsync(
|
||||
User, server,
|
||||
ServerPermissionConstants.Shares,
|
||||
ServerPermissionLevel.ReadWrite
|
||||
);
|
||||
|
||||
if (!authorizeResult.Succeeded)
|
||||
{
|
||||
return Problem(
|
||||
authorizeResult.Message ?? "No permission for the requested resource",
|
||||
statusCode: 403
|
||||
);
|
||||
}
|
||||
|
||||
return server;
|
||||
}
|
||||
}
|
||||
@@ -1,205 +0,0 @@
|
||||
using Microsoft.AspNetCore.Authorization;
|
||||
using Microsoft.AspNetCore.Mvc;
|
||||
using Microsoft.EntityFrameworkCore;
|
||||
using MoonCore.Common;
|
||||
using MoonCore.Exceptions;
|
||||
using MoonCore.Extended.Abstractions;
|
||||
using MoonlightServers.ApiServer.Database.Entities;
|
||||
using MoonlightServers.ApiServer.Services;
|
||||
using MoonlightServers.Shared.Constants;
|
||||
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/{serverId:int}/variables")]
|
||||
public class VariablesController : Controller
|
||||
{
|
||||
private readonly DatabaseRepository<Server> ServerRepository;
|
||||
private readonly DatabaseRepository<ServerVariable> ServerVariableRepository;
|
||||
private readonly DatabaseRepository<StarVariable> StarVariableRepository;
|
||||
private readonly ServerAuthorizeService AuthorizeService;
|
||||
|
||||
public VariablesController(
|
||||
DatabaseRepository<Server> serverRepository,
|
||||
ServerAuthorizeService authorizeService,
|
||||
DatabaseRepository<ServerVariable> serverVariableRepository,
|
||||
DatabaseRepository<StarVariable> starVariableRepository
|
||||
)
|
||||
{
|
||||
ServerRepository = serverRepository;
|
||||
AuthorizeService = authorizeService;
|
||||
ServerVariableRepository = serverVariableRepository;
|
||||
StarVariableRepository = starVariableRepository;
|
||||
}
|
||||
|
||||
[HttpGet]
|
||||
public async Task<ActionResult<CountedData<ServerVariableDetailResponse>>> GetAsync(
|
||||
[FromRoute] int serverId,
|
||||
[FromQuery] int startIndex,
|
||||
[FromQuery] int count
|
||||
)
|
||||
{
|
||||
if (count > 100)
|
||||
return Problem("Only 100 items can be fetched at a time", statusCode: 400);
|
||||
|
||||
var server = await GetServerByIdAsync(serverId, ServerPermissionLevel.Read);
|
||||
|
||||
if (server.Value == null)
|
||||
return server.Result ?? Problem("Unable to retrieve server");
|
||||
|
||||
var query = StarVariableRepository
|
||||
.Get()
|
||||
.Where(x => x.Star.Id == server.Value.Star.Id);
|
||||
|
||||
var totalCount = await query.CountAsync();
|
||||
|
||||
var starVariables = await query
|
||||
.OrderBy(x => x.Id)
|
||||
.Skip(startIndex)
|
||||
.Take(count)
|
||||
.ToArrayAsync();
|
||||
|
||||
var starVariableKeys = starVariables
|
||||
.Select(x => x.Key)
|
||||
.ToArray();
|
||||
|
||||
var serverVariables = await ServerVariableRepository
|
||||
.Get()
|
||||
.Where(x => x.Server.Id == server.Value.Id && starVariableKeys.Contains(x.Key))
|
||||
.ToArrayAsync();
|
||||
|
||||
var responses = starVariables.Select(starVariable =>
|
||||
{
|
||||
var serverVariable = serverVariables.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();
|
||||
|
||||
return new CountedData<ServerVariableDetailResponse>()
|
||||
{
|
||||
Items = responses,
|
||||
TotalCount = totalCount
|
||||
};
|
||||
}
|
||||
|
||||
[HttpPut]
|
||||
public async Task<ActionResult<ServerVariableDetailResponse>> UpdateSingleAsync(
|
||||
[FromRoute] int serverId,
|
||||
[FromBody] UpdateServerVariableRequest request
|
||||
)
|
||||
{
|
||||
// TODO: Handle filter
|
||||
|
||||
var serverResult = await GetServerByIdAsync(serverId, ServerPermissionLevel.ReadWrite);
|
||||
|
||||
if (serverResult.Value == null)
|
||||
return serverResult.Result ?? Problem("Unable to retrieve server");
|
||||
|
||||
var server = serverResult.Value;
|
||||
|
||||
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.UpdateAsync(server);
|
||||
|
||||
return new ServerVariableDetailResponse()
|
||||
{
|
||||
Key = starVariable.Key,
|
||||
Value = serverVariable.Value,
|
||||
Type = starVariable.Type,
|
||||
Name = starVariable.Name,
|
||||
Description = starVariable.Description,
|
||||
Filter = starVariable.Filter
|
||||
};
|
||||
}
|
||||
|
||||
[HttpPatch]
|
||||
public async Task<ActionResult<ServerVariableDetailResponse[]>> UpdateAsync(
|
||||
[FromRoute] int serverId,
|
||||
[FromBody] UpdateServerVariableRangeRequest request
|
||||
)
|
||||
{
|
||||
var serverResult = await GetServerByIdAsync(serverId, ServerPermissionLevel.ReadWrite);
|
||||
|
||||
if (serverResult.Value == null)
|
||||
return serverResult.Result ?? Problem("Unable to retrieve server");
|
||||
|
||||
var server = serverResult.Value;
|
||||
|
||||
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.UpdateAsync(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<ActionResult<Server>> GetServerByIdAsync(int serverId, ServerPermissionLevel level)
|
||||
{
|
||||
var server = await ServerRepository
|
||||
.Get()
|
||||
.Include(x => x.Star)
|
||||
.ThenInclude(x => x.Variables)
|
||||
.Include(x => x.Variables)
|
||||
.FirstOrDefaultAsync(x => x.Id == serverId);
|
||||
|
||||
if (server == null)
|
||||
return Problem("No server with this id found", statusCode: 404);
|
||||
|
||||
var authorizeResult = await AuthorizeService.AuthorizeAsync(
|
||||
User, server,
|
||||
ServerPermissionConstants.Variables,
|
||||
level
|
||||
);
|
||||
|
||||
if (!authorizeResult.Succeeded)
|
||||
{
|
||||
return Problem(
|
||||
authorizeResult.Message ?? "No permission for the requested resource",
|
||||
statusCode: 403
|
||||
);
|
||||
}
|
||||
|
||||
return server;
|
||||
}
|
||||
}
|
||||
@@ -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 GetAsync() => Task.CompletedTask;
|
||||
}
|
||||
@@ -1,196 +0,0 @@
|
||||
using Microsoft.AspNetCore.Authorization;
|
||||
using Microsoft.AspNetCore.Mvc;
|
||||
using Microsoft.EntityFrameworkCore;
|
||||
using Microsoft.Extensions.Logging;
|
||||
using MoonCore.Common;
|
||||
using MoonCore.Exceptions;
|
||||
using MoonCore.Extended.Abstractions;
|
||||
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<ActionResult<CountedData<ServerDataResponse>>> GetAsync(
|
||||
[FromQuery] int startIndex,
|
||||
[FromQuery] int count
|
||||
)
|
||||
{
|
||||
if (count > 100)
|
||||
return Problem("Only 100 items can be fetched at a time", statusCode: 400);
|
||||
|
||||
// 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(startIndex)
|
||||
.Take(count)
|
||||
.ToArrayAsync();
|
||||
|
||||
var serverData = new List<ServerDataResponse>();
|
||||
|
||||
foreach (var server in servers)
|
||||
{
|
||||
var convertedData = ConvertToServerData(server);
|
||||
|
||||
if (convertedData == null)
|
||||
continue;
|
||||
|
||||
serverData.Add(convertedData);
|
||||
}
|
||||
|
||||
return new CountedData<ServerDataResponse>()
|
||||
{
|
||||
Items = serverData.ToArray(),
|
||||
TotalCount = total
|
||||
};
|
||||
}
|
||||
|
||||
[HttpGet("{id:int}")]
|
||||
public async Task<ServerDataResponse> GetAsync([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> GetInstallAsync([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),
|
||||
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,
|
||||
};
|
||||
}
|
||||
}
|
||||
@@ -1,35 +0,0 @@
|
||||
using System.Security.Claims;
|
||||
using Microsoft.AspNetCore.Authorization;
|
||||
using MoonlightServers.ApiServer.Database.Entities;
|
||||
using MoonlightServers.ApiServer.Interfaces;
|
||||
using MoonlightServers.ApiServer.Models;
|
||||
using MoonlightServers.Shared.Enums;
|
||||
|
||||
namespace MoonlightServers.ApiServer.Implementations.ServerAuthFilters;
|
||||
|
||||
public class AdminAuthFilter : IServerAuthorizationFilter
|
||||
{
|
||||
private readonly IAuthorizationService AuthorizationService;
|
||||
|
||||
public int Priority => 0;
|
||||
|
||||
public AdminAuthFilter(IAuthorizationService authorizationService)
|
||||
{
|
||||
AuthorizationService = authorizationService;
|
||||
}
|
||||
|
||||
public async Task<ServerAuthorizationResult?> ProcessAsync(
|
||||
ClaimsPrincipal user,
|
||||
Server server,
|
||||
string permissionId,
|
||||
ServerPermissionLevel requiredLevel
|
||||
)
|
||||
{
|
||||
var authResult = await AuthorizationService.AuthorizeAsync(
|
||||
user,
|
||||
"permissions:admin.servers.manage"
|
||||
);
|
||||
|
||||
return authResult.Succeeded ? ServerAuthorizationResult.Success() : null;
|
||||
}
|
||||
}
|
||||
@@ -1,34 +0,0 @@
|
||||
using System.Security.Claims;
|
||||
using MoonlightServers.ApiServer.Database.Entities;
|
||||
using MoonlightServers.ApiServer.Interfaces;
|
||||
using MoonlightServers.ApiServer.Models;
|
||||
using MoonlightServers.Shared.Enums;
|
||||
|
||||
namespace MoonlightServers.ApiServer.Implementations.ServerAuthFilters;
|
||||
|
||||
public class OwnerAuthFilter : IServerAuthorizationFilter
|
||||
{
|
||||
public int Priority => 0;
|
||||
|
||||
public Task<ServerAuthorizationResult?> ProcessAsync(
|
||||
ClaimsPrincipal user,
|
||||
Server server,
|
||||
string permissionId,
|
||||
ServerPermissionLevel requiredLevel
|
||||
)
|
||||
{
|
||||
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()
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -1,56 +0,0 @@
|
||||
using System.Security.Claims;
|
||||
using Microsoft.EntityFrameworkCore;
|
||||
using MoonCore.Extended.Abstractions;
|
||||
using MoonlightServers.ApiServer.Database.Entities;
|
||||
using MoonlightServers.ApiServer.Interfaces;
|
||||
using MoonlightServers.ApiServer.Models;
|
||||
using MoonlightServers.Shared.Enums;
|
||||
|
||||
namespace MoonlightServers.ApiServer.Implementations.ServerAuthFilters;
|
||||
|
||||
public class ShareAuthFilter : IServerAuthorizationFilter
|
||||
{
|
||||
private readonly DatabaseRepository<ServerShare> ShareRepository;
|
||||
|
||||
public ShareAuthFilter(DatabaseRepository<ServerShare> shareRepository)
|
||||
{
|
||||
ShareRepository = shareRepository;
|
||||
}
|
||||
|
||||
public int Priority => 0;
|
||||
|
||||
public async Task<ServerAuthorizationResult?> ProcessAsync(
|
||||
ClaimsPrincipal user,
|
||||
Server server,
|
||||
string permissionId,
|
||||
ServerPermissionLevel requiredLevel
|
||||
)
|
||||
{
|
||||
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 (string.IsNullOrEmpty(permissionId) || requiredLevel == ServerPermissionLevel.None)
|
||||
return ServerAuthorizationResult.Success(share);
|
||||
|
||||
var possiblePermShare = share.Content.Permissions.FirstOrDefault(x => x.Identifier == permissionId);
|
||||
|
||||
if (possiblePermShare == null)
|
||||
return null;
|
||||
|
||||
if (possiblePermShare.Level >= requiredLevel)
|
||||
return ServerAuthorizationResult.Success(share);
|
||||
|
||||
return null;
|
||||
}
|
||||
}
|
||||
@@ -1,21 +0,0 @@
|
||||
using System.Security.Claims;
|
||||
using MoonlightServers.ApiServer.Database.Entities;
|
||||
using MoonlightServers.ApiServer.Models;
|
||||
using MoonlightServers.Shared.Enums;
|
||||
|
||||
namespace MoonlightServers.ApiServer.Interfaces;
|
||||
|
||||
public interface IServerAuthorizationFilter
|
||||
{
|
||||
// Return null => skip to next filter / handler
|
||||
// Return any value, instant complete
|
||||
|
||||
public int Priority { get; }
|
||||
|
||||
public Task<ServerAuthorizationResult?> ProcessAsync(
|
||||
ClaimsPrincipal user,
|
||||
Server server,
|
||||
string permissionId,
|
||||
ServerPermissionLevel requiredLevel
|
||||
);
|
||||
}
|
||||
@@ -1,18 +0,0 @@
|
||||
using MoonlightServers.ApiServer.Database.Entities;
|
||||
using MoonlightServers.Shared.Http.Requests.Admin.NodeAllocations;
|
||||
using MoonlightServers.Shared.Http.Responses.Admin.NodeAllocations;
|
||||
using Riok.Mapperly.Abstractions;
|
||||
|
||||
namespace MoonlightServers.ApiServer.Mappers;
|
||||
|
||||
[Mapper(AllowNullPropertyAssignment = false)]
|
||||
public static partial class AllocationMapper
|
||||
{
|
||||
public static partial NodeAllocationResponse ToNodeAllocation(Allocation allocation);
|
||||
public static partial Allocation ToAllocation(CreateNodeAllocationRequest request);
|
||||
public static partial void Merge(UpdateNodeAllocationRequest request, Allocation allocation);
|
||||
|
||||
// EF Projections
|
||||
|
||||
public static partial IQueryable<NodeAllocationResponse> ProjectToAdminResponse(this IQueryable<Allocation> allocations);
|
||||
}
|
||||
@@ -1,18 +0,0 @@
|
||||
using MoonlightServers.ApiServer.Database.Entities;
|
||||
using MoonlightServers.Shared.Http.Requests.Admin.StarDockerImages;
|
||||
using MoonlightServers.Shared.Http.Responses.Admin.StarDockerImages;
|
||||
using Riok.Mapperly.Abstractions;
|
||||
|
||||
namespace MoonlightServers.ApiServer.Mappers;
|
||||
|
||||
[Mapper(AllowNullPropertyAssignment = false)]
|
||||
public static partial class DockerImageMapper
|
||||
{
|
||||
public static partial StarDockerImageResponse ToAdminResponse(StarDockerImage dockerImage);
|
||||
public static partial StarDockerImage ToDockerImage(CreateStarDockerImageRequest request);
|
||||
public static partial void Merge(UpdateStarDockerImageRequest request, StarDockerImage variable);
|
||||
|
||||
// EF Migrations
|
||||
|
||||
public static partial IQueryable<StarDockerImageResponse> ProjectToAdminResponse(this IQueryable<StarDockerImage> dockerImages);
|
||||
}
|
||||
@@ -1,18 +0,0 @@
|
||||
using MoonlightServers.ApiServer.Database.Entities;
|
||||
using MoonlightServers.Shared.Http.Requests.Admin.Nodes;
|
||||
using MoonlightServers.Shared.Http.Responses.Admin.Nodes;
|
||||
using Riok.Mapperly.Abstractions;
|
||||
|
||||
namespace MoonlightServers.ApiServer.Mappers;
|
||||
|
||||
[Mapper(AllowNullPropertyAssignment = false)]
|
||||
public static partial class NodeMapper
|
||||
{
|
||||
public static partial NodeResponse ToAdminNodeResponse(Node node);
|
||||
public static partial Node ToNode(CreateNodeRequest request);
|
||||
public static partial void Merge(UpdateNodeRequest request, Node node);
|
||||
|
||||
// EF Projections
|
||||
|
||||
public static partial IQueryable<NodeResponse> ProjectToAdminResponse(this IQueryable<Node> nodes);
|
||||
}
|
||||
@@ -1,30 +0,0 @@
|
||||
using MoonlightServers.ApiServer.Database.Entities;
|
||||
using MoonlightServers.Shared.Http.Requests.Admin.Servers;
|
||||
using MoonlightServers.Shared.Http.Responses.Admin.Servers;
|
||||
using Riok.Mapperly.Abstractions;
|
||||
|
||||
namespace MoonlightServers.ApiServer.Mappers;
|
||||
|
||||
[Mapper(AllowNullPropertyAssignment = false)]
|
||||
public static partial class ServerMapper
|
||||
{
|
||||
[UserMapping(Default = true)]
|
||||
public static ServerResponse ToAdminServerResponse(Server server)
|
||||
{
|
||||
var response = ToAdminServerResponse_Internal(server);
|
||||
|
||||
response.AllocationIds = server.Allocations.Select(x => x.Id).ToArray();
|
||||
|
||||
return response;
|
||||
}
|
||||
|
||||
private static partial ServerResponse ToAdminServerResponse_Internal(Server server);
|
||||
|
||||
[MapperIgnoreSource(nameof(CreateServerRequest.Variables))]
|
||||
public static partial Server ToServer(CreateServerRequest request);
|
||||
public static partial void Merge(UpdateServerRequest request, Server server);
|
||||
|
||||
// EF Projections
|
||||
|
||||
public static partial IQueryable<ServerResponse> ProjectToAdminResponse(this IQueryable<Server> servers);
|
||||
}
|
||||
@@ -1,15 +0,0 @@
|
||||
using MoonlightServers.ApiServer.Database.Entities;
|
||||
using MoonlightServers.Shared.Http.Responses.Admin.ServerVariables;
|
||||
using Riok.Mapperly.Abstractions;
|
||||
|
||||
namespace MoonlightServers.ApiServer.Mappers;
|
||||
|
||||
[Mapper(AllowNullPropertyAssignment = false)]
|
||||
public static partial class ServerVariableMapper
|
||||
{
|
||||
public static partial ServerVariableResponse ToAdminResponse(ServerVariable serverVariable);
|
||||
|
||||
// EF Projections
|
||||
|
||||
public static partial IQueryable<ServerVariableResponse> ProjectToAdminResponse(this IQueryable<ServerVariable> variables);
|
||||
}
|
||||
@@ -1,27 +0,0 @@
|
||||
using MoonlightServers.ApiServer.Models;
|
||||
using MoonlightServers.Shared.Enums;
|
||||
using Riok.Mapperly.Abstractions;
|
||||
|
||||
namespace MoonlightServers.ApiServer.Mappers;
|
||||
|
||||
[Mapper]
|
||||
public static partial class ShareMapper
|
||||
{
|
||||
public static ServerShareContent MapToServerShareContent(Dictionary<string, ServerPermissionLevel> permissionLevels)
|
||||
{
|
||||
return new ServerShareContent()
|
||||
{
|
||||
Permissions = permissionLevels.Select(x => new ServerShareContent.SharePermission()
|
||||
{
|
||||
Identifier = x.Key,
|
||||
Level = x.Value
|
||||
}).ToList()
|
||||
};
|
||||
}
|
||||
|
||||
public static Dictionary<string, ServerPermissionLevel> MapToPermissionLevels(
|
||||
ServerShareContent content)
|
||||
{
|
||||
return content.Permissions.ToDictionary(x => x.Identifier, x => x.Level);
|
||||
}
|
||||
}
|
||||
@@ -1,18 +0,0 @@
|
||||
using MoonlightServers.ApiServer.Database.Entities;
|
||||
using MoonlightServers.Shared.Http.Requests.Admin.Stars;
|
||||
using MoonlightServers.Shared.Http.Responses.Admin.Stars;
|
||||
using Riok.Mapperly.Abstractions;
|
||||
|
||||
namespace MoonlightServers.ApiServer.Mappers;
|
||||
|
||||
[Mapper(AllowNullPropertyAssignment = false)]
|
||||
public static partial class StarMapper
|
||||
{
|
||||
public static partial StarResponse ToAdminResponse(Star star);
|
||||
public static partial Star ToStar(CreateStarRequest request);
|
||||
public static partial void Merge(UpdateStarRequest request, Star star);
|
||||
|
||||
// EF Projections
|
||||
|
||||
public static partial IQueryable<StarResponse> ProjectToAdminResponse(this IQueryable<Star> stars);
|
||||
}
|
||||
@@ -1,18 +0,0 @@
|
||||
using MoonlightServers.ApiServer.Database.Entities;
|
||||
using MoonlightServers.Shared.Http.Requests.Admin.StarVariables;
|
||||
using MoonlightServers.Shared.Http.Responses.Admin.StarVariables;
|
||||
using Riok.Mapperly.Abstractions;
|
||||
|
||||
namespace MoonlightServers.ApiServer.Mappers;
|
||||
|
||||
[Mapper(AllowNullPropertyAssignment = false)]
|
||||
public static partial class StarVariableMapper
|
||||
{
|
||||
public static partial StarVariableResponse ToAdminResponse(StarVariable variable);
|
||||
public static partial StarVariable ToStarVariable(CreateStarVariableRequest request);
|
||||
public static partial void Merge(UpdateStarVariableRequest request, StarVariable variable);
|
||||
|
||||
// EF Projections
|
||||
|
||||
public static partial IQueryable<StarVariableResponse> ProjectToAdminResponse(this IQueryable<StarVariable> variables);
|
||||
}
|
||||
@@ -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
|
||||
};
|
||||
}
|
||||
}
|
||||
@@ -1,14 +0,0 @@
|
||||
using MoonlightServers.Shared.Enums;
|
||||
|
||||
namespace MoonlightServers.ApiServer.Models;
|
||||
|
||||
public record ServerShareContent
|
||||
{
|
||||
public List<SharePermission> Permissions { get; set; } = new();
|
||||
|
||||
public record SharePermission
|
||||
{
|
||||
public string Identifier { get; set; }
|
||||
public ServerPermissionLevel Level { get; set; }
|
||||
}
|
||||
}
|
||||
@@ -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; }
|
||||
}
|
||||
}
|
||||
@@ -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();
|
||||
}
|
||||
@@ -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; }
|
||||
}
|
||||
}
|
||||
@@ -1,39 +0,0 @@
|
||||
<Project Sdk="Microsoft.NET.Sdk.Razor">
|
||||
|
||||
<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>
|
||||
<IsPackable>true</IsPackable>
|
||||
</PropertyGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<PackageReference Include="Moonlight.ApiServer" Version="2.1.*" />
|
||||
<PackageReference Include="Microsoft.EntityFrameworkCore.Design" Version="9.0.9"/>
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<ProjectReference Include="..\MoonlightServers.DaemonShared\MoonlightServers.DaemonShared.csproj"/>
|
||||
<ProjectReference Include="..\MoonlightServers.Shared\MoonlightServers.Shared.csproj"/>
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<Folder Include="Database\Migrations\"/>
|
||||
<Folder Include="Http\Middleware\"/>
|
||||
<Folder Include="Startup\" />
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup Label="Build instruction for nuget package building">
|
||||
<Compile Remove="storage\**\*"/>
|
||||
<Content Remove="storage\**\*"/>
|
||||
<None Remove="storage\**\*"/>
|
||||
</ItemGroup>
|
||||
|
||||
</Project>
|
||||
@@ -1,59 +0,0 @@
|
||||
using Microsoft.AspNetCore.Builder;
|
||||
using Microsoft.Extensions.Configuration;
|
||||
using Microsoft.Extensions.DependencyInjection;
|
||||
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;
|
||||
|
||||
public class PluginStartup : IPluginStartup
|
||||
{
|
||||
public void AddPlugin(WebApplicationBuilder 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 = AppConfiguration.CreateEmpty();
|
||||
builder.Configuration.Bind(configuration);
|
||||
|
||||
if (configuration.Frontend.EnableHosting)
|
||||
{
|
||||
builder.Services.AddSingleton(new FrontendConfigurationOption()
|
||||
{
|
||||
Scripts =
|
||||
[
|
||||
"/_content/MoonlightServers.Frontend/js/XtermBlazor.min.js",
|
||||
"/_content/MoonlightServers.Frontend/js/addon-fit.js",
|
||||
"/_content/MoonlightServers.Frontend/js/moonlightServers.js"
|
||||
],
|
||||
Styles = ["/_content/MoonlightServers.Frontend/css/XtermBlazor.min.css"]
|
||||
});
|
||||
}
|
||||
|
||||
// Add server auth filters
|
||||
builder.Services.AddSingleton<IServerAuthorizationFilter, OwnerAuthFilter>();
|
||||
builder.Services.AddScoped<IServerAuthorizationFilter, AdminAuthFilter>();
|
||||
builder.Services.AddScoped<IServerAuthorizationFilter, ShareAuthFilter>();
|
||||
}
|
||||
|
||||
public void UsePlugin(WebApplication app)
|
||||
{
|
||||
}
|
||||
|
||||
public void MapPlugin(WebApplication app)
|
||||
{
|
||||
}
|
||||
}
|
||||
@@ -1,84 +0,0 @@
|
||||
using System.IdentityModel.Tokens.Jwt;
|
||||
using System.Text;
|
||||
using Microsoft.IdentityModel.Tokens;
|
||||
using MoonCore.Attributes;
|
||||
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> GetSystemStatusAsync(Node node)
|
||||
{
|
||||
using var apiClient = CreateApiClient(node);
|
||||
return await apiClient.GetJson<SystemStatusResponse>("api/system/status");
|
||||
}
|
||||
|
||||
#region Statistics
|
||||
|
||||
public async Task<StatisticsResponse> GetStatisticsAsync(Node node)
|
||||
{
|
||||
using var apiClient = CreateApiClient(node);
|
||||
return await apiClient.GetJson<StatisticsResponse>("api/statistics");
|
||||
}
|
||||
|
||||
public async Task<StatisticsDockerResponse> GetDockerStatisticsAsync(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
|
||||
}
|
||||
@@ -1,44 +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.Enums;
|
||||
|
||||
namespace MoonlightServers.ApiServer.Services;
|
||||
|
||||
[Scoped]
|
||||
public class ServerAuthorizeService
|
||||
{
|
||||
private readonly IServerAuthorizationFilter[] AuthorizationFilters;
|
||||
|
||||
public ServerAuthorizeService(
|
||||
IEnumerable<IServerAuthorizationFilter> authorizationFilters
|
||||
)
|
||||
{
|
||||
AuthorizationFilters = authorizationFilters.ToArray();
|
||||
}
|
||||
|
||||
public async Task<ServerAuthorizationResult> AuthorizeAsync(
|
||||
ClaimsPrincipal user,
|
||||
Server server,
|
||||
string permissionIdentifier,
|
||||
ServerPermissionLevel permissionLevel
|
||||
)
|
||||
{
|
||||
foreach (var authorizationFilter in AuthorizationFilters)
|
||||
{
|
||||
var result = await authorizationFilter.ProcessAsync(
|
||||
user,
|
||||
server,
|
||||
permissionIdentifier,
|
||||
permissionLevel
|
||||
);
|
||||
|
||||
if (result != null)
|
||||
return result;
|
||||
}
|
||||
|
||||
return ServerAuthorizationResult.Failed();
|
||||
}
|
||||
}
|
||||
@@ -1,122 +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[]> ListAsync(Server server, string path)
|
||||
{
|
||||
using var apiClient = await GetApiClientAsync(server);
|
||||
|
||||
return await apiClient.GetJson<ServerFileSystemResponse[]>(
|
||||
$"api/servers/{server.Id}/files/list?path={path}"
|
||||
);
|
||||
}
|
||||
|
||||
public async Task MoveAsync(Server server, string oldPath, string newPath)
|
||||
{
|
||||
using var apiClient = await GetApiClientAsync(server);
|
||||
|
||||
await apiClient.Post(
|
||||
$"api/servers/{server.Id}/files/move?oldPath={oldPath}&newPath={newPath}"
|
||||
);
|
||||
}
|
||||
|
||||
public async Task DeleteAsync(Server server, string path)
|
||||
{
|
||||
using var apiClient = await GetApiClientAsync(server);
|
||||
|
||||
await apiClient.Delete(
|
||||
$"api/servers/{server.Id}/files/delete?path={path}"
|
||||
);
|
||||
}
|
||||
|
||||
public async Task MkdirAsync(Server server, string path)
|
||||
{
|
||||
using var apiClient = await GetApiClientAsync(server);
|
||||
|
||||
await apiClient.Post(
|
||||
$"api/servers/{server.Id}/files/mkdir?path={path}"
|
||||
);
|
||||
}
|
||||
|
||||
public async Task TouchAsync(Server server, string path)
|
||||
{
|
||||
using var apiClient = await GetApiClientAsync(server);
|
||||
|
||||
await apiClient.Post(
|
||||
$"api/servers/{server.Id}/files/touch?path={path}"
|
||||
);
|
||||
}
|
||||
|
||||
public async Task CompressAsync(Server server, CompressType type, string[] items, string destination)
|
||||
{
|
||||
using var apiClient = await GetApiClientAsync(server);
|
||||
|
||||
await apiClient.Post(
|
||||
$"api/servers/{server.Id}/files/compress",
|
||||
new ServerFilesCompressRequest()
|
||||
{
|
||||
Type = type,
|
||||
Items = items,
|
||||
Destination = destination
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
public async Task DecompressAsync(Server server, CompressType type, string path, string destination)
|
||||
{
|
||||
using var apiClient = await GetApiClientAsync(server);
|
||||
|
||||
await apiClient.Post(
|
||||
$"api/servers/{server.Id}/files/decompress",
|
||||
new ServerFilesDecompressRequest()
|
||||
{
|
||||
Type = type,
|
||||
Path = path,
|
||||
Destination = destination
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
#region Helpers
|
||||
|
||||
private async Task<HttpApiClient> GetApiClientAsync(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
|
||||
}
|
||||
@@ -1,194 +0,0 @@
|
||||
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.Requests;
|
||||
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 StartAsync(Server server)
|
||||
{
|
||||
try
|
||||
{
|
||||
using var apiClient = await GetApiClientAsync(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 StopAsync(Server server)
|
||||
{
|
||||
try
|
||||
{
|
||||
using var apiClient = await GetApiClientAsync(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 KillAsync(Server server)
|
||||
{
|
||||
try
|
||||
{
|
||||
using var apiClient = await GetApiClientAsync(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 InstallAsync(Server server)
|
||||
{
|
||||
try
|
||||
{
|
||||
using var apiClient = await GetApiClientAsync(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 SyncAsync(Server server)
|
||||
{
|
||||
try
|
||||
{
|
||||
using var apiClient = await GetApiClientAsync(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 SyncDeleteAsync(Server server)
|
||||
{
|
||||
try
|
||||
{
|
||||
using var apiClient = await GetApiClientAsync(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> GetStatusAsync(Server server)
|
||||
{
|
||||
try
|
||||
{
|
||||
using var apiClient = await GetApiClientAsync(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> GetLogsAsync(Server server)
|
||||
{
|
||||
try
|
||||
{
|
||||
using var apiClient = await GetApiClientAsync(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> GetStatsAsync(Server server)
|
||||
{
|
||||
try
|
||||
{
|
||||
using var apiClient = await GetApiClientAsync(server);
|
||||
return await apiClient.GetJson<ServerStatsResponse>($"api/servers/{server.Id}/stats");
|
||||
}
|
||||
catch (HttpRequestException)
|
||||
{
|
||||
throw new HttpApiException("Unable to access the node the server is running on", 502);
|
||||
}
|
||||
}
|
||||
|
||||
public async Task RunCommandAsync(Server server, string command)
|
||||
{
|
||||
try
|
||||
{
|
||||
using var apiClient = await GetApiClientAsync(server);
|
||||
|
||||
await apiClient.Post(
|
||||
$"api/servers/{server.Id}/command",
|
||||
new ServerCommandRequest()
|
||||
{
|
||||
Command = command
|
||||
}
|
||||
);
|
||||
}
|
||||
catch (HttpRequestException)
|
||||
{
|
||||
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;
|
||||
|
||||
return PermissionHelper.HasPermission(user.Permissions, "admin.servers.get");
|
||||
}
|
||||
|
||||
private async Task<HttpApiClient> GetApiClientAsync(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
|
||||
}
|
||||
@@ -1,410 +0,0 @@
|
||||
using System.Text.Json;
|
||||
using Microsoft.EntityFrameworkCore;
|
||||
using Microsoft.Extensions.Logging;
|
||||
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> ExportAsync(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> ImportAsync(string json)
|
||||
{
|
||||
// Determine which importer to use based on simple patterns
|
||||
if (json.Contains("RequiredAllocations"))
|
||||
return await ImportStarAsync(json);
|
||||
else if (json.Contains("AllocationsNeeded"))
|
||||
return await ImportImageAsync(json);
|
||||
else if (json.Contains("_comment"))
|
||||
return await ImportEggAsync(json);
|
||||
else
|
||||
throw new HttpApiException("Unable to determine the format of the imported star/image/egg", 400);
|
||||
}
|
||||
|
||||
public async Task<Star> ImportStarAsync(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.AddAsync(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> ImportImageAsync(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.AddAsync(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> ImportEggAsync(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.AddAsync(star);
|
||||
|
||||
return finalStar;
|
||||
}
|
||||
}
|
||||
@@ -1,72 +0,0 @@
|
||||
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; } = Path.Combine("storage", "volumes");
|
||||
public string VirtualDisks { get; set; } = Path.Combine("storage", "virtualDisks");
|
||||
public string Backups { get; set; } = Path.Combine("storage", "backups");
|
||||
public string Install { get; set; } =Path.Combine("storage", "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;
|
||||
}
|
||||
}
|
||||
@@ -1,10 +0,0 @@
|
||||
namespace MoonlightServers.Daemon.Enums;
|
||||
|
||||
public enum ServerState
|
||||
{
|
||||
Offline = 0,
|
||||
Starting = 1,
|
||||
Online = 2,
|
||||
Stopping = 3,
|
||||
Installing = 4
|
||||
}
|
||||
@@ -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
|
||||
}
|
||||
@@ -1,359 +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,
|
||||
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;
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
76
MoonlightServers.Daemon/Helpers/AppConsoleFormatter.cs
Normal file
76
MoonlightServers.Daemon/Helpers/AppConsoleFormatter.cs
Normal 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", "")
|
||||
};
|
||||
}
|
||||
}
|
||||
@@ -1,31 +0,0 @@
|
||||
namespace MoonlightServers.Daemon.Helpers;
|
||||
|
||||
public class CompositeServiceProvider : IServiceProvider
|
||||
{
|
||||
private readonly List<IServiceProvider> ServiceProviders;
|
||||
|
||||
public CompositeServiceProvider(params IServiceProvider[] serviceProviders)
|
||||
{
|
||||
ServiceProviders = new List<IServiceProvider>(serviceProviders);
|
||||
}
|
||||
|
||||
public object? GetService(Type serviceType)
|
||||
{
|
||||
foreach (var provider in ServiceProviders)
|
||||
{
|
||||
try
|
||||
{
|
||||
var service = provider.GetService(serviceType);
|
||||
|
||||
if (service != null)
|
||||
return service;
|
||||
}
|
||||
catch (InvalidOperationException)
|
||||
{
|
||||
// Ignored
|
||||
}
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
}
|
||||
@@ -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> GetCpuUsageAsync()
|
||||
{
|
||||
var result = new CpuUsageDetails();
|
||||
var perCoreUsages = new List<double>();
|
||||
|
||||
// Initial read
|
||||
var (cpuLastStats, cpuLastSums) = await ReadAllCpuStatsAsync();
|
||||
|
||||
await Task.Delay(1000);
|
||||
|
||||
// Second read
|
||||
var (cpuNowStats, cpuNowSums) = await ReadAllCpuStatsAsync();
|
||||
|
||||
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)> ReadAllCpuStatsAsync()
|
||||
{
|
||||
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 ClearCachedMemoryAsync()
|
||||
{
|
||||
await File.WriteAllTextAsync("/proc/sys/vm/drop_caches", "3");
|
||||
}
|
||||
|
||||
public async Task<MemoryUsageDetails> GetMemoryUsageAsync()
|
||||
{
|
||||
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[]> GetDiskUsagesAsync()
|
||||
{
|
||||
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
|
||||
}
|
||||
@@ -1,354 +0,0 @@
|
||||
using System.Text;
|
||||
using ICSharpCode.SharpZipLib.GZip;
|
||||
using ICSharpCode.SharpZipLib.Tar;
|
||||
using ICSharpCode.SharpZipLib.Zip;
|
||||
using Mono.Unix.Native;
|
||||
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[]> ListAsync(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,
|
||||
IsFolder = x.IsDirectory,
|
||||
Size = x.Size,
|
||||
UpdatedAt = x.LastChanged,
|
||||
CreatedAt = x.CreatedAt
|
||||
})
|
||||
.ToArray();
|
||||
|
||||
return Task.FromResult(result);
|
||||
}
|
||||
|
||||
public Task MoveAsync(string inputOldPath, string inputNewPath)
|
||||
{
|
||||
var oldPath = Normalize(inputOldPath);
|
||||
var newPath = Normalize(inputNewPath);
|
||||
|
||||
FileSystem.Rename(oldPath, newPath);
|
||||
|
||||
return Task.CompletedTask;
|
||||
}
|
||||
|
||||
public Task DeleteAsync(string inputPath)
|
||||
{
|
||||
var path = Normalize(inputPath);
|
||||
|
||||
FileSystem.RemoveAll(path);
|
||||
|
||||
return Task.CompletedTask;
|
||||
}
|
||||
|
||||
public Task MkdirAsync(string inputPath)
|
||||
{
|
||||
var path = Normalize(inputPath);
|
||||
|
||||
FileSystem.MkdirAll(path, FilePermissions.ACCESSPERMS);
|
||||
|
||||
return Task.CompletedTask;
|
||||
}
|
||||
|
||||
public Task TouchAsync(string inputPath)
|
||||
{
|
||||
var path = Normalize(inputPath);
|
||||
|
||||
var parentDirectory = Path.GetDirectoryName(path);
|
||||
|
||||
if (!string.IsNullOrEmpty(parentDirectory) && parentDirectory != "/")
|
||||
FileSystem.MkdirAll(parentDirectory, FilePermissions.ACCESSPERMS);
|
||||
|
||||
FileSystem.OpenFileWrite(
|
||||
path,
|
||||
_ => { },
|
||||
OpenFlags.O_CREAT
|
||||
); // We use these custom flags to ensure we aren't overwriting the file
|
||||
|
||||
return Task.CompletedTask;
|
||||
}
|
||||
|
||||
public Task CreateChunkAsync(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 CreateAsync(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 ReadAsync(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 CompressAsync(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 DecompressAsync(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('/');
|
||||
}
|
||||
}
|
||||
@@ -1,8 +0,0 @@
|
||||
using Microsoft.AspNetCore.Authentication;
|
||||
|
||||
namespace MoonlightServers.Daemon.Helpers;
|
||||
|
||||
public class TokenAuthOptions : AuthenticationSchemeOptions
|
||||
{
|
||||
public string Token { get; set; }
|
||||
}
|
||||
@@ -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"
|
||||
)
|
||||
));
|
||||
}
|
||||
}
|
||||
@@ -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> CreateHttpClientAsync()
|
||||
{
|
||||
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> GetDataUsageAsync()
|
||||
{
|
||||
using var client = await CreateHttpClientAsync();
|
||||
var responseJson = await client.GetStringAsync("http://some.random.domain/v1.47/system/df");
|
||||
var response = JsonSerializer.Deserialize<DataUsageResponse>(responseJson)!;
|
||||
|
||||
return response;
|
||||
}
|
||||
}
|
||||
@@ -1,77 +0,0 @@
|
||||
using Microsoft.AspNetCore.Mvc;
|
||||
using MoonlightServers.Daemon.ServerSystem.Enums;
|
||||
using MoonlightServers.Daemon.Services;
|
||||
|
||||
namespace MoonlightServers.Daemon.Http.Controllers.Servers;
|
||||
|
||||
[ApiController]
|
||||
[Route("api/servers/{id:int}")]
|
||||
public class PowerController : Controller
|
||||
{
|
||||
private readonly ServerService ServerService;
|
||||
|
||||
public PowerController(ServerService serverService)
|
||||
{
|
||||
ServerService = serverService;
|
||||
}
|
||||
|
||||
[HttpPost("start")]
|
||||
public async Task<ActionResult> StartAsync([FromRoute] int id)
|
||||
{
|
||||
var server = ServerService.GetById(id);
|
||||
|
||||
if (server == null)
|
||||
return Problem("No server with this id found", statusCode: 404);
|
||||
|
||||
if (!server.StateMachine.CanFire(ServerTrigger.Start))
|
||||
return Problem("Cannot fire start trigger in this state");
|
||||
|
||||
await server.StateMachine.FireAsync(ServerTrigger.Start);
|
||||
return NoContent();
|
||||
}
|
||||
|
||||
[HttpPost("stop")]
|
||||
public async Task<ActionResult> StopAsync([FromRoute] int id)
|
||||
{
|
||||
var server = ServerService.GetById(id);
|
||||
|
||||
if (server == null)
|
||||
return Problem("No server with this id found", statusCode: 404);
|
||||
|
||||
if (!server.StateMachine.CanFire(ServerTrigger.Stop))
|
||||
return Problem("Cannot fire stop trigger in this state");
|
||||
|
||||
await server.StateMachine.FireAsync(ServerTrigger.Stop);
|
||||
return NoContent();
|
||||
}
|
||||
|
||||
[HttpPost("kill")]
|
||||
public async Task<ActionResult> KillAsync([FromRoute] int id)
|
||||
{
|
||||
var server = ServerService.GetById(id);
|
||||
|
||||
if (server == null)
|
||||
return Problem("No server with this id found", statusCode: 404);
|
||||
|
||||
if (!server.StateMachine.CanFire(ServerTrigger.Kill))
|
||||
return Problem("Cannot fire kill trigger in this state");
|
||||
|
||||
await server.StateMachine.FireAsync(ServerTrigger.Kill);
|
||||
return NoContent();
|
||||
}
|
||||
|
||||
[HttpPost("install")]
|
||||
public async Task<ActionResult> InstallAsync([FromRoute] int id)
|
||||
{
|
||||
var server = ServerService.GetById(id);
|
||||
|
||||
if (server == null)
|
||||
return Problem("No server with this id found", statusCode: 404);
|
||||
|
||||
if (!server.StateMachine.CanFire(ServerTrigger.Install))
|
||||
return Problem("Cannot fire install trigger in this state");
|
||||
|
||||
await server.StateMachine.FireAsync(ServerTrigger.Install);
|
||||
return NoContent();
|
||||
}
|
||||
}
|
||||
@@ -1,67 +0,0 @@
|
||||
using Microsoft.AspNetCore.Mvc;
|
||||
using MoonlightServers.Daemon.Mappers;
|
||||
using MoonlightServers.Daemon.Services;
|
||||
using MoonlightServers.DaemonShared.DaemonSide.Http.Responses.Servers;
|
||||
using MoonlightServers.DaemonShared.Enums;
|
||||
|
||||
namespace MoonlightServers.Daemon.Http.Controllers.Servers;
|
||||
|
||||
[ApiController]
|
||||
[Route("api/servers/{id:int}")]
|
||||
public class ServersController : Controller
|
||||
{
|
||||
private readonly ServerService ServerService;
|
||||
private readonly ServerConfigurationMapper ConfigurationMapper;
|
||||
|
||||
public ServersController(ServerService serverService, ServerConfigurationMapper configurationMapper)
|
||||
{
|
||||
ServerService = serverService;
|
||||
ConfigurationMapper = configurationMapper;
|
||||
}
|
||||
|
||||
[HttpPost("sync")]
|
||||
public async Task<ActionResult> SyncAsync([FromRoute] int id)
|
||||
{
|
||||
await ServerService.InitializeByIdAsync(id);
|
||||
return NoContent();
|
||||
}
|
||||
|
||||
[HttpGet("status")]
|
||||
public async Task<ActionResult<ServerStatusResponse>> StatusAsync([FromRoute] int id)
|
||||
{
|
||||
var server = ServerService.GetById(id);
|
||||
|
||||
if (server == null)
|
||||
return Problem("No server with this id found", statusCode: 404);
|
||||
|
||||
return new ServerStatusResponse()
|
||||
{
|
||||
State = (ServerState)server.StateMachine.State
|
||||
};
|
||||
}
|
||||
|
||||
[HttpGet("logs")]
|
||||
public async Task<ActionResult<ServerLogsResponse>> LogsAsync([FromRoute] int id)
|
||||
{
|
||||
var server = ServerService.GetById(id);
|
||||
|
||||
if (server == null)
|
||||
return Problem("No server with this id found", statusCode: 404);
|
||||
|
||||
var messages = await server.Console.GetCacheAsync();
|
||||
|
||||
return new ServerLogsResponse()
|
||||
{
|
||||
Messages = messages.ToArray()
|
||||
};
|
||||
}
|
||||
|
||||
[HttpGet("stats")]
|
||||
public async Task<ServerStatsResponse> GetStatsAsync([FromRoute] int id)
|
||||
{
|
||||
return new ServerStatsResponse()
|
||||
{
|
||||
|
||||
};
|
||||
}
|
||||
}
|
||||
@@ -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> GetAsync()
|
||||
{
|
||||
var response = new StatisticsResponse();
|
||||
|
||||
var cpuUsage = await HostSystemHelper.GetCpuUsageAsync();
|
||||
|
||||
response.Cpu.Model = cpuUsage.Model;
|
||||
response.Cpu.Usage = cpuUsage.OverallUsage;
|
||||
response.Cpu.UsagePerCore = cpuUsage.PerCoreUsage;
|
||||
|
||||
var memoryUsage = await HostSystemHelper.GetMemoryUsageAsync();
|
||||
|
||||
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.GetDiskUsagesAsync();
|
||||
|
||||
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;
|
||||
}
|
||||
}
|
||||
@@ -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> GetAsync()
|
||||
{
|
||||
var usage = await DockerInfoService.GetDataUsageAsync();
|
||||
|
||||
return new StatisticsDockerResponse
|
||||
{
|
||||
Version = await DockerInfoService.GetDockerVersionAsync(),
|
||||
ContainersReclaimable = usage.Containers.Reclaimable,
|
||||
ContainersUsed = usage.Containers.Used,
|
||||
BuildCacheReclaimable = usage.BuildCache.Reclaimable,
|
||||
BuildCacheUsed = usage.BuildCache.Used,
|
||||
ImagesUsed = usage.Images.Used,
|
||||
ImagesReclaimable = usage.Images.Reclaimable
|
||||
};
|
||||
}
|
||||
}
|
||||
@@ -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> GetAsync()
|
||||
{
|
||||
SystemStatusResponse response;
|
||||
|
||||
var sw = new Stopwatch();
|
||||
sw.Start();
|
||||
|
||||
try
|
||||
{
|
||||
await RemoteService.GetStatusAsync();
|
||||
|
||||
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;
|
||||
}
|
||||
}
|
||||
@@ -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
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -1,363 +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.Mappers;
|
||||
|
||||
public class ServerConfigurationMapper
|
||||
{
|
||||
private readonly AppConfiguration AppConfiguration;
|
||||
|
||||
public ServerConfigurationMapper(AppConfiguration appConfiguration)
|
||||
{
|
||||
AppConfiguration = appConfiguration;
|
||||
}
|
||||
|
||||
public ServerConfiguration FromServerDataResponse(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,
|
||||
Cpu = response.Cpu,
|
||||
Disk = response.Disk,
|
||||
Memory = response.Memory,
|
||||
StopCommand = response.StopCommand,
|
||||
};
|
||||
}
|
||||
|
||||
public CreateContainerParameters ToRuntimeParameters(
|
||||
ServerConfiguration serverConfiguration,
|
||||
string hostPath,
|
||||
string containerName
|
||||
)
|
||||
{
|
||||
var parameters = ToSharedParameters(serverConfiguration);
|
||||
|
||||
#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 = serverConfiguration.DockerImage;
|
||||
|
||||
#endregion
|
||||
|
||||
#region Working Dir
|
||||
|
||||
parameters.WorkingDir = "/home/container";
|
||||
|
||||
#endregion
|
||||
|
||||
#region User
|
||||
|
||||
// TODO: Extract this to an external service with config options and return a userspace user id and a install user id
|
||||
// in order to know which permissions are required in order to run the container with the correct permissions
|
||||
|
||||
var userId = Syscall.getuid();
|
||||
|
||||
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 serverConfiguration.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
|
||||
|
||||
// TODO: Implement a way to directly startup a server without using the entrypoint.sh and parsing the startup command here
|
||||
// in the daemon instead of letting it the entrypoint do. iirc pelican wants to do that as well so we need to do that
|
||||
// sooner or later in order to stay compatible to pelican
|
||||
// Possible flag name: LegacyEntrypointMode
|
||||
|
||||
return parameters;
|
||||
}
|
||||
|
||||
public CreateContainerParameters ToInstallParameters(
|
||||
ServerConfiguration serverConfiguration,
|
||||
ServerInstallDataResponse installData,
|
||||
string runtimeHostPath,
|
||||
string installationHostPath,
|
||||
string containerName
|
||||
)
|
||||
{
|
||||
var parameters = ToSharedParameters(serverConfiguration);
|
||||
|
||||
// - Name
|
||||
parameters.Name = containerName;
|
||||
parameters.Hostname = containerName;
|
||||
|
||||
// - Image
|
||||
parameters.Image = installData.DockerImage;
|
||||
|
||||
// -- 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 = [installData.Shell, "/mnt/install/install.sh"];
|
||||
|
||||
return parameters;
|
||||
}
|
||||
|
||||
public CreateContainerParameters ToSharedParameters(ServerConfiguration serverConfiguration)
|
||||
{
|
||||
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 = serverConfiguration.Cpu * 1000;
|
||||
parameters.HostConfig.CPUPeriod = 100000;
|
||||
parameters.HostConfig.CPUShares = 1024;
|
||||
|
||||
#endregion
|
||||
|
||||
#region Memory & Swap
|
||||
|
||||
var memoryLimit = serverConfiguration.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", serverConfiguration.Id.ToString());
|
||||
|
||||
#endregion
|
||||
|
||||
#region Environment
|
||||
|
||||
parameters.Env = CreateEnvironmentVariables(serverConfiguration);
|
||||
|
||||
#endregion
|
||||
|
||||
return parameters;
|
||||
}
|
||||
|
||||
private List<string> CreateEnvironmentVariables(ServerConfiguration serverConfiguration)
|
||||
{
|
||||
var result = new Dictionary<string, string>
|
||||
{
|
||||
//TODO: Add timezone, add server ip
|
||||
{ "STARTUP", serverConfiguration.StartupCommand },
|
||||
{ "SERVER_MEMORY", serverConfiguration.Memory.ToString() }
|
||||
};
|
||||
|
||||
if (serverConfiguration.Allocations.Length > 0)
|
||||
{
|
||||
for (var i = 0; i < serverConfiguration.Allocations.Length; i++)
|
||||
{
|
||||
var allocation = serverConfiguration.Allocations[i];
|
||||
|
||||
result.Add($"ML_PORT_{i}", allocation.Port.ToString());
|
||||
|
||||
if (i == 0) // TODO: Implement a way to set the default/main allocation
|
||||
{
|
||||
result.Add("SERVER_IP", allocation.IpAddress);
|
||||
result.Add("SERVER_PORT", allocation.Port.ToString());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Copy variables as env vars
|
||||
foreach (var variable in serverConfiguration.Variables)
|
||||
result.Add(variable.Key, variable.Value);
|
||||
|
||||
// Convert to the format of the docker library
|
||||
return result.Select(variable => $"{variable.Key}={variable.Value}").ToList();
|
||||
}
|
||||
}
|
||||
@@ -1,28 +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; }
|
||||
|
||||
// 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; }
|
||||
}
|
||||
}
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user